rel3 IM fixes - removed references to rw-pb and corrected config-primitive instead...
[osm/SO.git] / models / openmano / src / openmano2rift.py
1 #!/usr/bin/env python3
2
3 #
4 # Copyright 2016-2017 RIFT.IO Inc
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 #
18
19
20 import argparse
21 import itertools
22 import logging
23 import os
24 import sys
25 import tempfile
26 import uuid
27 import yaml
28
29 import gi
30 gi.require_version('RwYang', '1.0')
31 gi.require_version('RwProjectVnfdYang', '1.0')
32 gi.require_version('RwProjectNsdYang', '1.0')
33 gi.require_version('RwProjectYang', '1.0')
34 from gi.repository import (
35 RwYang,
36 RwProjectVnfdYang as RwVnfdYang,
37 RwProjectNsdYang as RwNsdYang,
38 RwProjectYang,
39 )
40
41 logging.basicConfig(level=logging.WARNING)
42 logger = logging.getLogger("openmano2rift.py")
43
44
45 class UnknownVNFError(Exception):
46 pass
47
48
49 class DescriptorFileWriter(object):
50 def __init__(self, module_list, output_dir, output_format):
51 self._model = RwYang.Model.create_libyang()
52 for module in module_list:
53 self._model.load_module(module)
54
55 self._output_dir = output_dir
56 self._output_format = output_format
57
58 def _write_file(self, file_name, output):
59 file_path = os.path.join(self._output_dir, file_name)
60 dir_path = os.path.dirname(file_path)
61 if not os.path.exists(dir_path):
62 os.makedirs(dir_path)
63
64 with open(file_path, "w") as hdl:
65 hdl.write(output)
66
67 logger.info("Wrote descriptor to %s", file_path)
68
69 def _write_json(self, descriptor, subdir):
70 self._write_file(
71 '%s.json' % os.path.join(descriptor.name, subdir, descriptor.name),
72 descriptor.descriptor.to_json(self._model)
73 )
74
75 def _write_xml(self, descriptor, subdir):
76 self._write_file(
77 '%s.xml' % os.path.join(descriptor.name, subdir, descriptor.name),
78 descriptor.descriptor.to_xml_v2(self._model, pretty_print=True)
79 )
80
81 def _write_yaml(self, descriptor, subdir):
82 self._write_file(
83 '%s.yaml' % os.path.join(descriptor.name, subdir, descriptor.name),
84 yaml.dump(descriptor.descriptor.as_dict()),
85 )
86
87 def write_descriptor(self, descriptor, subdir=""):
88 if self._output_format == 'json':
89 self._write_json(descriptor, subdir=subdir)
90
91 elif self._output_format == 'xml':
92 self._write_xml(descriptor, subdir=subdir)
93
94 elif self._output_format == 'yaml':
95 self._write_yaml(descriptor, subdir=subdir)
96
97
98 class RiftManoDescriptor(object):
99 def __init__(self, openmano=None):
100 self.openmano = openmano
101 self.descriptor = None
102
103
104 class RiftNS(RiftManoDescriptor):
105 def __init__(self, openmano=None):
106 super().__init__(openmano)
107 self.nsd_catalog = None
108 self.nsd = None
109 self.name = None
110
111 def get_vnfd_id(self, vnf_list, vnf_name):
112 for vnf in vnf_list:
113 if vnf.name == vnf_name:
114 return vnf.vnfd.id
115
116 # Didn't find the vnf just return the vnf_name
117 return vnf_name
118
119 def openmano2rift(self, vnf_list):
120 self.descriptor = RwNsdYang.YangData_RwProject_Project_NsdCatalog()
121 openmano_nsd = self.openmano.dictionary
122 self.name = openmano_nsd['name']
123 nsd = self.descriptor.nsd.add()
124 nsd.id = str(uuid.uuid1())
125 nsd.name = self.name
126 nsd.short_name = self.name
127 nsd.description = openmano_nsd['description']
128
129 nodes = openmano_nsd['topology']['nodes']
130 connections = openmano_nsd['topology']['connections']
131
132 def create_consituent_vnfds():
133 vnf_member_index_dict = {}
134
135 vnfd_idx_gen = itertools.count(1)
136 for key in nodes:
137 node = nodes[key]
138 if node['type'] != 'VNF':
139 continue
140
141 vnfd_idx = next(vnfd_idx_gen)
142 constituent_vnfd = nsd.constituent_vnfd.add()
143 constituent_vnfd.member_vnf_index = vnfd_idx
144 constituent_vnfd.vnfd_id_ref = self.get_vnfd_id(vnf_list, node['VNF model'])
145 vnf_member_index_dict[key] = vnfd_idx
146
147 return vnf_member_index_dict
148
149 def create_connections(vnf_member_index_dict):
150 keys = connections.keys()
151 for key in keys:
152 # TODO: Need clarification from TEF
153 # skip the mgmtnet in OpenMANO descriptor
154 if key == 'mgmtnet':
155 continue
156 conn = connections[key]
157 vld = nsd.vld.add()
158 vld.from_dict(dict(
159 id=str(uuid.uuid1()),
160 name=key,
161 short_name=key,
162 type_yang='ELAN',
163 ))
164
165 nodes = conn['nodes']
166 for node, node_keys in [(node, node.keys()) for node in nodes]:
167 for node_key in node_keys:
168 topo_node = openmano_nsd['topology']['nodes'][node_key]
169 if topo_node['type'] == 'VNF':
170 cpref = vld.vnfd_connection_point_ref.add()
171 cpref.from_dict(dict(
172 member_vnf_index_ref=vnf_member_index_dict[node_key],
173 vnfd_id_ref=self.get_vnfd_id(vnf_list, topo_node['VNF model']),
174 #vnfd_id_ref=topo_node['VNF model'],
175 vnfd_connection_point_ref=node[node_key],
176 ))
177 if key != 'control-net':
178 vld.provider_network.physical_network = 'physnet_sriov'
179
180 vnf_member_index_dict = create_consituent_vnfds()
181 create_connections(vnf_member_index_dict)
182
183
184 class RiftVnfd(RiftManoDescriptor):
185 def __init__(self, openmano=None):
186 super().__init__(openmano)
187 self.vnfd_catalog = None
188 self.vnfd = None
189
190 def find_external_connection(self, vdu_name, if_name):
191 """
192 Find if the vdu interface has an external connection.
193 """
194 openmano_vnfd = self.openmano.dictionary['vnf']
195 if 'external-connections' not in openmano_vnfd:
196 return None
197
198 ext_conn_list = openmano_vnfd['external-connections']
199 for ext_conn in ext_conn_list:
200 if ((ext_conn['VNFC'] == vdu_name) and
201 (ext_conn['local_iface_name'] == if_name)):
202 return ext_conn
203
204 return None
205
206 def openmano2rift(self):
207 self.descriptor = RwVnfdYang.YangData_RwProject_Project_VnfdCatalog()
208 vnfd = self.descriptor.vnfd.add()
209 self.vnfd = vnfd
210 vnfd.id = str(uuid.uuid1())
211
212 openmano_vnfd = self.openmano.dictionary['vnf']
213 self.name = openmano_vnfd['name']
214 vnfd.name = self.name
215 if "description" in openmano_vnfd:
216 vnfd.description = openmano_vnfd['description']
217
218 # Parse and add all the external connection points
219 if 'external-connections' in openmano_vnfd:
220 ext_conn_list = openmano_vnfd['external-connections']
221
222 for ext_conn in ext_conn_list:
223 # TODO: Fix this
224 if ext_conn['name'] == 'eth0':
225 continue
226 conn_point = vnfd.connection_point.add()
227 conn_point.name = ext_conn['name']
228 conn_point.type_yang = 'VPORT'
229
230 # TODO: Need a concrete example of how openmano descriptor
231 # uses internal connections.
232 if 'internal-connections' in openmano_vnfd:
233 int_conn_list = openmano_vnfd['internal-connections']
234
235 def add_external_interfaces(vdu, numa):
236 if 'interfaces' not in numa:
237 return
238
239 numa_if_list = numa['interfaces']
240 for numa_if in numa_if_list:
241 ext_conn = self.find_external_connection(vdu.name, numa_if['name'])
242 if not ext_conn:
243 continue
244
245 ext_iface = vdu.interface.add()
246 ext_iface.name = numa_if['name']
247 ext_iface.type_yang = 'EXTERNAL'
248 ext_iface.external_connection_point_ref = ext_conn['name']
249 ext_iface.virtual_interface.vpci = numa_if['vpci']
250 if numa_if['dedicated'] == 'no':
251 ext_iface.virtual_interface.type_yang = 'SR_IOV'
252 else:
253 ext_iface.virtual_interface.type_yang = 'PCI_PASSTHROUGH'
254
255 vnfc_list = openmano_vnfd['VNFC']
256 for vnfc in vnfc_list:
257 vdu = vnfd.vdu.add()
258 vdu_dict = dict(
259 id=str(uuid.uuid1()),
260 name=vnfc['name'],
261 image=vnfc['VNFC image'],
262 vm_flavor={"storage_gb": vnfc["disk"] if "disk" in vnfc else 20},
263 )
264 if "description" in vnfc:
265 vdu_dict["description"] = vnfc['description']
266
267 vdu.from_dict(vdu_dict)
268
269 vnfd.mgmt_interface.vdu_id = vdu.id
270
271 numa_list = vnfc['numas']
272 memory = 0
273 vcpu_count = 0
274 numa_node_cnt = 0
275
276 for numa in numa_list:
277 node = vdu.guest_epa.numa_node_policy.node.add()
278 node.id = numa_node_cnt
279 # node.memory_mb = int(numa['memory']) * 1024
280 numa_node_cnt += 1
281
282 memory = memory + node.memory_mb
283 # Need a better explanation of "cores", "paired-threads", "threads"
284 # in openmano descriptor. Particularly how they map to cpu and
285 # thread pinning policies
286 if 'paired-threads' in numa:
287 vcpu_count = vcpu_count + int(numa['paired-threads']) * 2
288
289 if 'cores' in numa:
290 vcpu_count = vcpu_count + int(numa['cores'])
291
292 add_external_interfaces(vdu, numa)
293
294
295 # vdu.vm_flavor.memory_mb = memory
296 vdu.vm_flavor.memory_mb = 12 * 1024
297 vdu.vm_flavor.vcpu_count = vcpu_count
298 vdu.guest_epa.numa_node_policy.node_cnt = numa_node_cnt
299 vdu.guest_epa.numa_node_policy.mem_policy = 'STRICT'
300 vdu.guest_epa.mempage_size = 'LARGE'
301 vdu.guest_epa.cpu_pinning_policy = 'DEDICATED'
302 vdu.guest_epa.cpu_thread_pinning_policy = 'PREFER'
303
304 # TODO: Enable hypervisor epa
305 # vdu.hypervisor_epa.version = vnfc['hypervisor']['version']
306 # if vnfc['hypervisor']['type'] == 'QEMU-kvm':
307 # vdu.hypervisor_epa.type_yang = 'REQUIRE_KVM'
308 # else:
309 # vdu.hypervisor_epa.type_yang = 'PREFER_KVM'
310
311 # TODO: Enable host epa
312 # vdu.host_epa.cpu_feature = vnfc['processor']['features']
313
314 # Parse the bridge interfaces
315 if 'bridge-ifaces' in vnfc:
316 bridge_ifaces = vnfc['bridge-ifaces']
317
318
319 for bridge_iface in bridge_ifaces:
320 # TODO: Fix this
321 if bridge_iface['name'] == 'eth0':
322 continue
323
324 ext_conn = self.find_external_connection(vdu.name,
325 bridge_iface['name'])
326 if ext_conn:
327 ext_iface = vdu.interface.add()
328 ext_iface.name = bridge_iface['name']
329 ext_iface.type_yang = 'EXTERNAL'
330 ext_iface.external_connection_point_ref = ext_conn['name']
331 if 'vpci' in bridge_iface:
332 ext_iface.virtual_interface.vpci = bridge_iface['vpci']
333 ext_iface.virtual_interface.type_yang = 'VIRTIO'
334
335 # set vpci information for the 'default' network
336 # TODO: This needs to be inferred gtom bridge ifaces,
337 # need input from TEF
338 vdu.mgmt_vpci = "0000:00:0a.0"
339
340
341 class OpenManoDescriptor(object):
342 def __init__(self, yaml_file_hdl):
343 self.dictionary = yaml.load(yaml_file_hdl)
344
345 @property
346 def type(self):
347 """ The descriptor type (ns or vnf)"""
348 if 'vnf' in self.dictionary:
349 return "vnf"
350 else:
351 return "ns"
352
353 def dump(self):
354 """ Dump the Descriptor out to stdout """
355 print(yaml.dump(self.dictionary))
356
357
358 def is_writable_directory(dir_path):
359 """ Returns True if dir_path is writable, False otherwise
360
361 Arguments:
362 dir_path - A directory path
363 """
364 if not os.path.exists(dir_path):
365 raise ValueError("Directory does not exist: %s", dir_path)
366
367 try:
368 testfile = tempfile.TemporaryFile(dir=dir_path)
369 testfile.close()
370 except OSError:
371 return False
372
373 return True
374
375
376 def create_vnfs_from_yaml_files(yaml_file_hdls):
377 """ Create a list of RiftVnfd instances from yaml file handles
378
379 Arguments:
380 yaml_file_hdls - OpenMano Yaml file handles
381
382 Returns:
383 A list of RiftVnfd instances
384 """
385 vnf_list = []
386 for yaml_file_hdl in yaml_file_hdls:
387 openmano = OpenManoDescriptor(yaml_file_hdl)
388 yaml_file_hdl.seek(0)
389
390 if openmano.type != "vnf":
391 continue
392
393 vnf = RiftVnfd(openmano)
394 vnf.openmano2rift()
395 vnf_list.append(vnf)
396
397 return vnf_list
398
399
400 def create_ns_from_yaml_files(yaml_file_hdls, vnf_list):
401 """ Create a list of RiftNS instances from yaml file handles
402
403 Arguments:
404 yaml_file_hdls - OpenMano Yaml file handles
405 vnf_list - list of RiftVnfd
406
407 Returns:
408 A list of RiftNS instances
409 """
410 ns_list = []
411 for yaml_file_hdl in yaml_file_hdls:
412 openmano = OpenManoDescriptor(yaml_file_hdl)
413 if openmano.type != "ns":
414 continue
415
416 net_svc = RiftNS(openmano)
417 net_svc.openmano2rift(vnf_list)
418 ns_list.append(net_svc)
419
420 return ns_list
421
422
423 def parse_args(argv=sys.argv[1:]):
424 """ Parse the command line arguments
425
426 Arguments:
427 arv - The list of arguments to parse
428
429 Returns:
430 Argparse Namespace instance
431
432 """
433 parser = argparse.ArgumentParser()
434 parser.add_argument(
435 '-o', '--outdir',
436 default='.',
437 help="Directory to output converted descriptors",
438 )
439
440 parser.add_argument(
441 '-f', '--format',
442 choices=['yaml', 'xml', 'json'],
443 default='xml',
444 help="Descriptor output format",
445 )
446
447 parser.add_argument(
448 'yaml_file_hdls',
449 metavar="yaml_file",
450 nargs="+",
451 type=argparse.FileType('r'),
452 help="OpenMano YAML Descriptor File",
453 )
454
455 args = parser.parse_args(argv)
456
457 if not os.path.exists(args.outdir):
458 os.makedirs(args.outdir)
459
460 if not is_writable_directory(args.outdir):
461 logging.error("Directory %s is not writable", args.outdir)
462 sys.exit(1)
463
464 return args
465
466
467 def main(argv=sys.argv[1:]):
468 args = parse_args(argv)
469
470 vnf_list = create_vnfs_from_yaml_files(args.yaml_file_hdls)
471 ns_list = create_ns_from_yaml_files(args.yaml_file_hdls, vnf_list)
472
473 # TODO (Philip): Relook at the model generation
474 writer = DescriptorFileWriter(
475 module_list=['rw-project', 'project-nsd', 'rw-project-nsd', 'project-vnfd', 'rw-project-vnfd'],
476 output_dir=args.outdir,
477 output_format=args.format,
478 )
479
480 for nw_svc in ns_list:
481 writer.write_descriptor(nw_svc, subdir="nsd")
482
483 for vnf in vnf_list:
484 writer.write_descriptor(vnf, subdir="vnfd")
485
486
487 if __name__ == "__main__":
488 main()
489