8b9191026e29520bba76b3a12d5bf5c2c0b16b98
[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 vld.provider_network.overlay_type = 'VLAN'
180
181 vnf_member_index_dict = create_consituent_vnfds()
182 create_connections(vnf_member_index_dict)
183
184
185 class RiftVnfd(RiftManoDescriptor):
186 def __init__(self, openmano=None):
187 super().__init__(openmano)
188 self.vnfd_catalog = None
189 self.vnfd = None
190
191 def find_external_connection(self, vdu_name, if_name):
192 """
193 Find if the vdu interface has an external connection.
194 """
195 openmano_vnfd = self.openmano.dictionary['vnf']
196 if 'external-connections' not in openmano_vnfd:
197 return None
198
199 ext_conn_list = openmano_vnfd['external-connections']
200 for ext_conn in ext_conn_list:
201 if ((ext_conn['VNFC'] == vdu_name) and
202 (ext_conn['local_iface_name'] == if_name)):
203 return ext_conn
204
205 return None
206
207 def openmano2rift(self):
208 self.descriptor = RwVnfdYang.YangData_RwProject_Project_VnfdCatalog()
209 vnfd = self.descriptor.vnfd.add()
210 self.vnfd = vnfd
211 vnfd.id = str(uuid.uuid1())
212
213 openmano_vnfd = self.openmano.dictionary['vnf']
214 self.name = openmano_vnfd['name']
215 vnfd.name = self.name
216 if "description" in openmano_vnfd:
217 vnfd.description = openmano_vnfd['description']
218
219 # Parse and add all the external connection points
220 if 'external-connections' in openmano_vnfd:
221 ext_conn_list = openmano_vnfd['external-connections']
222
223 for ext_conn in ext_conn_list:
224 # TODO: Fix this
225 if ext_conn['name'] == 'eth0':
226 continue
227 conn_point = vnfd.connection_point.add()
228 conn_point.name = ext_conn['name']
229 conn_point.type_yang = 'VPORT'
230
231 # TODO: Need a concrete example of how openmano descriptor
232 # uses internal connections.
233 if 'internal-connections' in openmano_vnfd:
234 int_conn_list = openmano_vnfd['internal-connections']
235
236 def add_external_interfaces(vdu, numa):
237 if 'interfaces' not in numa:
238 return
239
240 numa_if_list = numa['interfaces']
241 for numa_if in numa_if_list:
242 ext_conn = self.find_external_connection(vdu.name, numa_if['name'])
243 if not ext_conn:
244 continue
245
246 ext_iface = vdu.interface.add()
247 ext_iface.name = numa_if['name']
248 ext_iface.type_yang = 'EXTERNAL'
249 ext_iface.external_connection_point_ref = ext_conn['name']
250 ext_iface.virtual_interface.vpci = numa_if['vpci']
251 if numa_if['dedicated'] == 'no':
252 ext_iface.virtual_interface.type_yang = 'SR_IOV'
253 else:
254 ext_iface.virtual_interface.type_yang = 'PCI_PASSTHROUGH'
255
256 vnfc_list = openmano_vnfd['VNFC']
257 for vnfc in vnfc_list:
258 vdu = vnfd.vdu.add()
259 vdu_dict = dict(
260 id=str(uuid.uuid1()),
261 name=vnfc['name'],
262 image=vnfc['VNFC image'],
263 vm_flavor={"storage_gb": vnfc["disk"] if "disk" in vnfc else 20},
264 )
265 if "description" in vnfc:
266 vdu_dict["description"] = vnfc['description']
267
268 vdu.from_dict(vdu_dict)
269
270 vnfd.mgmt_interface.vdu_id = vdu.id
271
272 numa_list = vnfc['numas']
273 memory = 0
274 vcpu_count = 0
275 numa_node_cnt = 0
276
277 for numa in numa_list:
278 node = vdu.guest_epa.numa_node_policy.node.add()
279 node.id = numa_node_cnt
280 # node.memory_mb = int(numa['memory']) * 1024
281 numa_node_cnt += 1
282
283 memory = memory + node.memory_mb
284 # Need a better explanation of "cores", "paired-threads", "threads"
285 # in openmano descriptor. Particularly how they map to cpu and
286 # thread pinning policies
287 if 'paired-threads' in numa:
288 vcpu_count = vcpu_count + int(numa['paired-threads']) * 2
289
290 if 'cores' in numa:
291 vcpu_count = vcpu_count + int(numa['cores'])
292
293 add_external_interfaces(vdu, numa)
294
295
296 # vdu.vm_flavor.memory_mb = memory
297 vdu.vm_flavor.memory_mb = 12 * 1024
298 vdu.vm_flavor.vcpu_count = vcpu_count
299 vdu.guest_epa.numa_node_policy.node_cnt = numa_node_cnt
300 vdu.guest_epa.numa_node_policy.mem_policy = 'STRICT'
301 vdu.guest_epa.mempage_size = 'LARGE'
302 vdu.guest_epa.cpu_pinning_policy = 'DEDICATED'
303 vdu.guest_epa.cpu_thread_pinning_policy = 'PREFER'
304
305 # TODO: Enable hypervisor epa
306 # vdu.hypervisor_epa.version = vnfc['hypervisor']['version']
307 # if vnfc['hypervisor']['type'] == 'QEMU-kvm':
308 # vdu.hypervisor_epa.type_yang = 'REQUIRE_KVM'
309 # else:
310 # vdu.hypervisor_epa.type_yang = 'PREFER_KVM'
311
312 # TODO: Enable host epa
313 # vdu.host_epa.cpu_feature = vnfc['processor']['features']
314
315 # Parse the bridge interfaces
316 if 'bridge-ifaces' in vnfc:
317 bridge_ifaces = vnfc['bridge-ifaces']
318
319
320 for bridge_iface in bridge_ifaces:
321 # TODO: Fix this
322 if bridge_iface['name'] == 'eth0':
323 continue
324
325 ext_conn = self.find_external_connection(vdu.name,
326 bridge_iface['name'])
327 if ext_conn:
328 ext_iface = vdu.interface.add()
329 ext_iface.name = bridge_iface['name']
330 ext_iface.type_yang = 'EXTERNAL'
331 ext_iface.external_connection_point_ref = ext_conn['name']
332 if 'vpci' in bridge_iface:
333 ext_iface.virtual_interface.vpci = bridge_iface['vpci']
334 ext_iface.virtual_interface.type_yang = 'VIRTIO'
335
336 # set vpci information for the 'default' network
337 # TODO: This needs to be inferred gtom bridge ifaces,
338 # need input from TEF
339 vdu.mgmt_vpci = "0000:00:0a.0"
340
341
342 class OpenManoDescriptor(object):
343 def __init__(self, yaml_file_hdl):
344 self.dictionary = yaml.load(yaml_file_hdl)
345
346 @property
347 def type(self):
348 """ The descriptor type (ns or vnf)"""
349 if 'vnf' in self.dictionary:
350 return "vnf"
351 else:
352 return "ns"
353
354 def dump(self):
355 """ Dump the Descriptor out to stdout """
356 print(yaml.dump(self.dictionary))
357
358
359 def is_writable_directory(dir_path):
360 """ Returns True if dir_path is writable, False otherwise
361
362 Arguments:
363 dir_path - A directory path
364 """
365 if not os.path.exists(dir_path):
366 raise ValueError("Directory does not exist: %s", dir_path)
367
368 try:
369 testfile = tempfile.TemporaryFile(dir=dir_path)
370 testfile.close()
371 except OSError:
372 return False
373
374 return True
375
376
377 def create_vnfs_from_yaml_files(yaml_file_hdls):
378 """ Create a list of RiftVnfd instances from yaml file handles
379
380 Arguments:
381 yaml_file_hdls - OpenMano Yaml file handles
382
383 Returns:
384 A list of RiftVnfd instances
385 """
386 vnf_list = []
387 for yaml_file_hdl in yaml_file_hdls:
388 openmano = OpenManoDescriptor(yaml_file_hdl)
389 yaml_file_hdl.seek(0)
390
391 if openmano.type != "vnf":
392 continue
393
394 vnf = RiftVnfd(openmano)
395 vnf.openmano2rift()
396 vnf_list.append(vnf)
397
398 return vnf_list
399
400
401 def create_ns_from_yaml_files(yaml_file_hdls, vnf_list):
402 """ Create a list of RiftNS instances from yaml file handles
403
404 Arguments:
405 yaml_file_hdls - OpenMano Yaml file handles
406 vnf_list - list of RiftVnfd
407
408 Returns:
409 A list of RiftNS instances
410 """
411 ns_list = []
412 for yaml_file_hdl in yaml_file_hdls:
413 openmano = OpenManoDescriptor(yaml_file_hdl)
414 if openmano.type != "ns":
415 continue
416
417 net_svc = RiftNS(openmano)
418 net_svc.openmano2rift(vnf_list)
419 ns_list.append(net_svc)
420
421 return ns_list
422
423
424 def parse_args(argv=sys.argv[1:]):
425 """ Parse the command line arguments
426
427 Arguments:
428 arv - The list of arguments to parse
429
430 Returns:
431 Argparse Namespace instance
432
433 """
434 parser = argparse.ArgumentParser()
435 parser.add_argument(
436 '-o', '--outdir',
437 default='.',
438 help="Directory to output converted descriptors",
439 )
440
441 parser.add_argument(
442 '-f', '--format',
443 choices=['yaml', 'xml', 'json'],
444 default='xml',
445 help="Descriptor output format",
446 )
447
448 parser.add_argument(
449 'yaml_file_hdls',
450 metavar="yaml_file",
451 nargs="+",
452 type=argparse.FileType('r'),
453 help="OpenMano YAML Descriptor File",
454 )
455
456 args = parser.parse_args(argv)
457
458 if not os.path.exists(args.outdir):
459 os.makedirs(args.outdir)
460
461 if not is_writable_directory(args.outdir):
462 logging.error("Directory %s is not writable", args.outdir)
463 sys.exit(1)
464
465 return args
466
467
468 def main(argv=sys.argv[1:]):
469 args = parse_args(argv)
470
471 vnf_list = create_vnfs_from_yaml_files(args.yaml_file_hdls)
472 ns_list = create_ns_from_yaml_files(args.yaml_file_hdls, vnf_list)
473
474 # TODO (Philip): Relook at the model generation
475 writer = DescriptorFileWriter(
476 module_list=['rw-project', 'project-nsd', 'rw-project-nsd', 'project-vnfd', 'rw-project-vnfd'],
477 output_dir=args.outdir,
478 output_format=args.format,
479 )
480
481 for nw_svc in ns_list:
482 writer.write_descriptor(nw_svc, subdir="nsd")
483
484 for vnf in vnf_list:
485 writer.write_descriptor(vnf, subdir="vnfd")
486
487
488 if __name__ == "__main__":
489 main()
490