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