RIFT OSM R1 Initial Submission
[osm/SO.git] / models / openmano / python / rift / openmano / rift2openmano.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 import argparse
20 import collections
21 import logging
22 import math
23 import os
24 import sys
25 import tempfile
26 import yaml
27
28 from gi.repository import (
29 RwYang,
30 RwVnfdYang,
31 RwNsdYang,
32 )
33
34 logger = logging.getLogger("rift2openmano.py")
35
36
37 class VNFNotFoundError(Exception):
38 pass
39
40
41 class RiftNSD(object):
42 model = RwYang.Model.create_libncx()
43 model.load_module('nsd')
44 model.load_module('rw-nsd')
45
46 def __init__(self, descriptor):
47 self._nsd = descriptor
48
49 def __str__(self):
50 return str(self._nsd)
51
52 @property
53 def name(self):
54 return self._nsd.name
55
56 @property
57 def id(self):
58 return self._nsd.id
59
60 @property
61 def vnfd_ids(self):
62 return [c.vnfd_id_ref for c in self._nsd.constituent_vnfd]
63
64 @property
65 def constituent_vnfds(self):
66 return self._nsd.constituent_vnfd
67
68 @property
69 def vlds(self):
70 return self._nsd.vld
71
72 @property
73 def cps(self):
74 return self._nsd.connection_point
75
76 @property
77 def description(self):
78 return self._nsd.description
79
80 @classmethod
81 def from_xml_file_hdl(cls, hdl):
82 hdl.seek(0)
83 descriptor = RwNsdYang.YangData_Nsd_NsdCatalog_Nsd()
84 descriptor.from_xml_v2(RiftNSD.model, hdl.read())
85 return cls(descriptor)
86
87 @classmethod
88 def from_yaml_file_hdl(cls, hdl):
89 hdl.seek(0)
90 descriptor = RwNsdYang.YangData_Nsd_NsdCatalog_Nsd()
91 descriptor.from_yaml(RiftNSD.model, hdl.read())
92 return cls(descriptor)
93
94 @classmethod
95 def from_dict(cls, nsd_dict):
96 descriptor = RwNsdYang.YangData_Nsd_NsdCatalog_Nsd.from_dict(nsd_dict)
97 return cls(descriptor)
98
99
100 class RiftVNFD(object):
101 model = RwYang.Model.create_libncx()
102 model.load_module('vnfd')
103 model.load_module('rw-vnfd')
104
105 def __init__(self, descriptor):
106 self._vnfd = descriptor
107
108 def __str__(self):
109 return str(self._vnfd)
110
111 @property
112 def id(self):
113 return self._vnfd.id
114
115 @property
116 def name(self):
117 return self._vnfd.name
118
119 @property
120 def description(self):
121 return self._vnfd.description
122
123 @property
124 def cps(self):
125 return self._vnfd.connection_point
126
127 @property
128 def vdus(self):
129 return self._vnfd.vdu
130
131 @property
132 def internal_vlds(self):
133 return self._vnfd.internal_vld
134
135 @classmethod
136 def from_xml_file_hdl(cls, hdl):
137 hdl.seek(0)
138 descriptor = RwVnfdYang.YangData_Vnfd_VnfdCatalog_Vnfd()
139 descriptor.from_xml_v2(RiftVNFD.model, hdl.read())
140 return cls(descriptor)
141
142 @classmethod
143 def from_yaml_file_hdl(cls, hdl):
144 hdl.seek(0)
145 descriptor = RwVnfdYang.YangData_Vnfd_VnfdCatalog_Vnfd()
146 descriptor.from_yaml(RiftVNFD.model, hdl.read())
147 return cls(descriptor)
148
149 @classmethod
150 def from_dict(cls, vnfd_dict):
151 descriptor = RwVnfdYang.YangData_Vnfd_VnfdCatalog_Vnfd.from_dict(vnfd_dict)
152 return cls(descriptor)
153
154
155 def is_writable_directory(dir_path):
156 """ Returns True if dir_path is writable, False otherwise
157
158 Arguments:
159 dir_path - A directory path
160 """
161 if not os.path.exists(dir_path):
162 raise ValueError("Directory does not exist: %s", dir_path)
163
164 try:
165 testfile = tempfile.TemporaryFile(dir=dir_path)
166 testfile.close()
167 except OSError:
168 return False
169
170 return True
171
172
173 def create_vnfd_from_xml_files(vnfd_file_hdls):
174 """ Create a list of RiftVNFD instances from xml file handles
175
176 Arguments:
177 vnfd_file_hdls - Rift VNFD XML file handles
178
179 Returns:
180 A list of RiftVNFD instances
181 """
182 vnfd_dict = {}
183 for vnfd_file_hdl in vnfd_file_hdls:
184 vnfd = RiftVNFD.from_xml_file_hdl(vnfd_file_hdl)
185 vnfd_dict[vnfd.id] = vnfd
186
187 return vnfd_dict
188
189 def create_vnfd_from_yaml_files(vnfd_file_hdls):
190 """ Create a list of RiftVNFD instances from xml file handles
191
192 Arguments:
193 vnfd_file_hdls - Rift VNFD YAML file handles
194
195 Returns:
196 A list of RiftVNFD instances
197 """
198 vnfd_dict = {}
199 for vnfd_file_hdl in vnfd_file_hdls:
200 vnfd = RiftVNFD.from_yaml_file_hdl(vnfd_file_hdl)
201 vnfd_dict[vnfd.id] = vnfd
202
203 return vnfd_dict
204
205
206 def create_nsd_from_xml_file(nsd_file_hdl):
207 """ Create a list of RiftNSD instances from xml file handles
208
209 Arguments:
210 nsd_file_hdls - Rift NSD XML file handles
211
212 Returns:
213 A list of RiftNSD instances
214 """
215 nsd = RiftNSD.from_xml_file_hdl(nsd_file_hdl)
216 return nsd
217
218 def create_nsd_from_yaml_file(nsd_file_hdl):
219 """ Create a list of RiftNSD instances from yaml file handles
220
221 Arguments:
222 nsd_file_hdls - Rift NSD XML file handles
223
224 Returns:
225 A list of RiftNSD instances
226 """
227 nsd = RiftNSD.from_yaml_file_hdl(nsd_file_hdl)
228 return nsd
229
230
231 def ddict():
232 return collections.defaultdict(dict)
233
234 def convert_vnfd_name(vnfd_name, member_idx):
235 return vnfd_name + "__" + str(member_idx)
236
237
238 def rift2openmano_nsd(rift_nsd, rift_vnfds):
239 for vnfd_id in rift_nsd.vnfd_ids:
240 if vnfd_id not in rift_vnfds:
241 raise VNFNotFoundError("VNF id %s not provided" % vnfd_id)
242
243 openmano = {}
244 openmano["name"] = rift_nsd.name
245 openmano["description"] = rift_nsd.description
246 topology = {}
247 openmano["topology"] = topology
248
249 topology["nodes"] = {}
250 for vnfd in rift_nsd.constituent_vnfds:
251 vnfd_id = vnfd.vnfd_id_ref
252 rift_vnfd = rift_vnfds[vnfd_id]
253 member_idx = vnfd.member_vnf_index
254 topology["nodes"][rift_vnfd.name + "__" + str(member_idx)] = {
255 "type": "VNF",
256 "VNF model": rift_vnfd.name
257 }
258
259 for vld in rift_nsd.vlds:
260 # Openmano has both bridge_net and dataplane_net models for network types
261 # For now, since we are using openmano in developer mode lets just hardcode
262 # to bridge_net since it won't matter anyways.
263 # topology["nodes"][vld.name] = {"type": "network", "model": "bridge_net"}
264 pass
265
266 topology["connections"] = {}
267 for vld in rift_nsd.vlds:
268
269 # Create a connections entry for each external VLD
270 topology["connections"][vld.name] = {}
271 topology["connections"][vld.name]["nodes"] = []
272
273 if vld.vim_network_name:
274 if vld.name not in topology["nodes"]:
275 topology["nodes"][vld.name] = {
276 "type": "external_network",
277 "model": vld.name,
278 }
279
280 # Add the external network to the list of connection points
281 topology["connections"][vld.name]["nodes"].append(
282 {vld.name: "0"}
283 )
284 elif vld.provider_network.has_field("physical_network"):
285 # Add the external datacenter network to the topology
286 # node list if it isn't already added
287 ext_net_name = vld.provider_network.physical_network
288 ext_net_name_with_seg = ext_net_name
289 if vld.provider_network.has_field("segmentation_id"):
290 ext_net_name_with_seg += ":{}".format(vld.provider_network.segmentation_id)
291
292 if ext_net_name not in topology["nodes"]:
293 topology["nodes"][ext_net_name] = {
294 "type": "external_network",
295 "model": ext_net_name_with_seg,
296 }
297
298 # Add the external network to the list of connection points
299 topology["connections"][vld.name]["nodes"].append(
300 {ext_net_name: "0"}
301 )
302
303
304 for vnfd_cp in vld.vnfd_connection_point_ref:
305
306 # Get the RIFT VNF for this external VLD connection point
307 vnfd = rift_vnfds[vnfd_cp.vnfd_id_ref]
308
309 # For each VNF in this connection, use the same interface name
310 topology["connections"][vld.name]["type"] = "link"
311 # Vnf ref is the vnf name with the member_vnf_idx appended
312 member_idx = vnfd_cp.member_vnf_index_ref
313 vnf_ref = vnfd.name + "__" + str(member_idx)
314 topology["connections"][vld.name]["nodes"].append(
315 {
316 vnf_ref: vnfd_cp.vnfd_connection_point_ref
317 }
318 )
319
320 return openmano
321
322
323 def rift2openmano_vnfd(rift_vnfd):
324 openmano_vnf = {"vnf":{}}
325 vnf = openmano_vnf["vnf"]
326
327 vnf["name"] = rift_vnfd.name
328 vnf["description"] = rift_vnfd.description
329
330 vnf["external-connections"] = []
331
332 def find_vdu_and_ext_if_by_cp_ref(cp_ref_name):
333 for vdu in rift_vnfd.vdus:
334 for ext_if in vdu.external_interface:
335 if ext_if.vnfd_connection_point_ref == cp_ref_name:
336 return vdu, ext_if
337
338 raise ValueError("External connection point reference %s not found" % cp_ref_name)
339
340 def find_vdu_and_int_if_by_cp_ref(cp_ref_id):
341 for vdu in rift_vnfd.vdus:
342 for int_if in vdu.internal_interface:
343 if int_if.vdu_internal_connection_point_ref == cp_ref_id:
344 return vdu, int_if
345
346 raise ValueError("Internal connection point reference %s not found" % cp_ref_id)
347
348 def rift2openmano_if_type(rift_type):
349 if rift_type == "OM_MGMT":
350 return "mgmt"
351 elif rift_type == "VIRTIO":
352 return "bridge"
353 else:
354 return "data"
355
356 # Add all external connections
357 for cp in rift_vnfd.cps:
358 # Find the VDU and and external interface for this connection point
359 vdu, ext_if = find_vdu_and_ext_if_by_cp_ref(cp.name)
360 connection = {
361 "name": cp.name,
362 "type": rift2openmano_if_type(ext_if.virtual_interface.type_yang),
363 "VNFC": vdu.name,
364 "local_iface_name": ext_if.name,
365 "description": "%s iface on VDU %s" % (ext_if.name, vdu.name),
366 }
367
368 vnf["external-connections"].append(connection)
369
370 # Add all internal networks
371 for vld in rift_vnfd.internal_vlds:
372 connection = {
373 "name": vld.name,
374 "description": vld.description,
375 "type": "data",
376 "elements": [],
377 }
378
379 # Add the specific VDU connection points
380 for int_cp_ref in vld.internal_connection_point_ref:
381 vdu, int_if = find_vdu_and_int_if_by_cp_ref(int_cp_ref)
382 connection["elements"].append({
383 "VNFC": vdu.name,
384 "local_iface_name": int_if.name,
385 })
386 if "internal-connections" not in vnf:
387 vnf["internal-connections"] = []
388
389 vnf["internal-connections"].append(connection)
390
391 # Add VDU's
392 vnf["VNFC"] = []
393 for vdu in rift_vnfd.vdus:
394 vnfc = {
395 "name": vdu.name,
396 "description": vdu.name,
397 "VNFC image": vdu.image if os.path.isabs(vdu.image) else "/var/images/{}".format(vdu.image),
398 "numas": [{
399 "memory": max(int(vdu.vm_flavor.memory_mb/1024), 1),
400 "interfaces":[],
401 }],
402 "bridge-ifaces": [],
403 }
404
405 numa_node_policy = vdu.guest_epa.numa_node_policy
406 if numa_node_policy.has_field("node"):
407 numa_node = numa_node_policy.node[0]
408
409 if numa_node.has_field("paired_threads"):
410 if numa_node.paired_threads.has_field("num_paired_threads"):
411 vnfc["numas"][0]["paired-threads"] = numa_node.paired_threads.num_paired_threads
412 if len(numa_node.paired_threads.paired_thread_ids) > 0:
413 vnfc["numas"][0]["paired-threads-id"] = []
414 for pair in numa_node.paired_threads.paired_thread_ids:
415 vnfc["numas"][0]["paired-threads-id"].append(
416 [pair.thread_a, pair.thread_b]
417 )
418
419 else:
420 if vdu.vm_flavor.has_field("vcpu_count"):
421 vnfc["numas"][0]["cores"] = max(vdu.vm_flavor.vcpu_count, 1)
422
423 if vdu.has_field("hypervisor_epa"):
424 vnfc["hypervisor"] = {}
425 if vdu.hypervisor_epa.has_field("type"):
426 if vdu.hypervisor_epa.type_yang == "REQUIRE_KVM":
427 vnfc["hypervisor"]["type"] = "QEMU-kvm"
428
429 if vdu.hypervisor_epa.has_field("version"):
430 vnfc["hypervisor"]["version"] = vdu.hypervisor_epa.version
431
432 if vdu.has_field("host_epa"):
433 vnfc["processor"] = {}
434 if vdu.host_epa.has_field("om_cpu_model_string"):
435 vnfc["processor"]["model"] = vdu.host_epa.om_cpu_model_string
436 if vdu.host_epa.has_field("om_cpu_feature"):
437 vnfc["processor"]["features"] = []
438 for feature in vdu.host_epa.om_cpu_feature:
439 vnfc["processor"]["features"].append(feature)
440
441
442 if vdu.vm_flavor.has_field("storage_gb"):
443 vnfc["disk"] = vdu.vm_flavor.storage_gb
444
445 vnf["VNFC"].append(vnfc)
446
447 for int_if in list(vdu.internal_interface) + list(vdu.external_interface):
448 intf = {
449 "name": int_if.name,
450 }
451 if int_if.virtual_interface.has_field("vpci"):
452 intf["vpci"] = int_if.virtual_interface.vpci
453
454 if int_if.virtual_interface.type_yang in ["VIRTIO", "OM_MGMT"]:
455 vnfc["bridge-ifaces"].append(intf)
456
457 elif int_if.virtual_interface.type_yang == "SR-IOV":
458 intf["bandwidth"] = "10 Gbps"
459 intf["dedicated"] = "yes:sriov"
460 vnfc["numas"][0]["interfaces"].append(intf)
461
462 elif int_if.virtual_interface.type_yang == "PCI_PASSTHROUGH":
463 intf["bandwidth"] = "10 Gbps"
464 intf["dedicated"] = "yes"
465 if "interfaces" not in vnfc["numas"][0]:
466 vnfc["numas"][0]["interfaces"] = []
467 vnfc["numas"][0]["interfaces"].append(intf)
468 else:
469 raise ValueError("Interface type %s not supported" % int_if.virtual_interface)
470
471 if int_if.virtual_interface.has_field("bandwidth"):
472 if int_if.virtual_interface.bandwidth != 0:
473 bps = int_if.virtual_interface.bandwidth
474
475 # Calculate the bits per second conversion
476 for x in [('M', 1000000), ('G', 1000000000)]:
477 if bps/x[1] >= 1:
478 intf["bandwidth"] = "{} {}bps".format(math.ceil(bps/x[1]), x[0])
479
480
481 return openmano_vnf
482
483
484 def parse_args(argv=sys.argv[1:]):
485 """ Parse the command line arguments
486
487 Arguments:
488 arv - The list of arguments to parse
489
490 Returns:
491 Argparse Namespace instance
492 """
493 parser = argparse.ArgumentParser()
494 parser.add_argument(
495 '-o', '--outdir',
496 default='-',
497 help="Directory to output converted descriptors. Default is stdout",
498 )
499
500 parser.add_argument(
501 '-n', '--nsd-file-hdl',
502 metavar="nsd_xml_file",
503 type=argparse.FileType('r'),
504 help="Rift NSD Descriptor File",
505 )
506
507 parser.add_argument(
508 '-v', '--vnfd-file-hdls',
509 metavar="vnfd_xml_file",
510 action='append',
511 type=argparse.FileType('r'),
512 help="Rift VNFD Descriptor File",
513 )
514
515 args = parser.parse_args(argv)
516
517 if not os.path.exists(args.outdir):
518 os.makedirs(args.outdir)
519
520 if not is_writable_directory(args.outdir):
521 logging.error("Directory %s is not writable", args.outdir)
522 sys.exit(1)
523
524 return args
525
526
527 def write_yaml_to_file(name, outdir, desc_dict):
528 file_name = "%s.yaml" % name
529 yaml_str = yaml.dump(desc_dict)
530 if outdir == "-":
531 sys.stdout.write(yaml_str)
532 return
533
534 file_path = os.path.join(outdir, file_name)
535 dir_path = os.path.dirname(file_path)
536 if not os.path.exists(dir_path):
537 os.makedirs(dir_path)
538
539 with open(file_path, "w") as hdl:
540 hdl.write(yaml_str)
541
542 logger.info("Wrote descriptor to %s", file_path)
543
544
545 def main(argv=sys.argv[1:]):
546 args = parse_args(argv)
547
548 nsd = None
549 if args.vnfd_file_hdls is not None:
550 vnf_dict = create_vnfd_from_xml_files(args.vnfd_file_hdls)
551
552 if args.nsd_file_hdl is not None:
553 nsd = create_nsd_from_xml_file(args.nsd_file_hdl)
554
555 openmano_nsd = rift2openmano_nsd(nsd, vnf_dict)
556
557 write_yaml_to_file(openmano_nsd["name"], args.outdir, openmano_nsd)
558
559 for vnf in vnf_dict.values():
560 openmano_vnf = rift2openmano_vnfd(vnf)
561 write_yaml_to_file(openmano_vnf["vnf"]["name"], args.outdir, openmano_vnf)
562
563
564 if __name__ == "__main__":
565 logging.basicConfig(level=logging.WARNING)
566 main()