Merge "Fix SO restart without test-name"
[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,openmano_vnfd_ids):
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 openmano_vnfd_id = openmano_vnfd_ids.get(vnfd_id,None)
255 if openmano_vnfd_id:
256 topology["nodes"][rift_vnfd.name + "__" + str(member_idx)] = {
257 "type": "VNF",
258 "vnf_id": openmano_vnfd_id
259 }
260 else:
261 topology["nodes"][rift_vnfd.name + "__" + str(member_idx)] = {
262 "type": "VNF",
263 "VNF model": rift_vnfd.name
264 }
265
266 for vld in rift_nsd.vlds:
267 # Openmano has both bridge_net and dataplane_net models for network types
268 # For now, since we are using openmano in developer mode lets just hardcode
269 # to bridge_net since it won't matter anyways.
270 # topology["nodes"][vld.name] = {"type": "network", "model": "bridge_net"}
271 pass
272
273 topology["connections"] = {}
274 for vld in rift_nsd.vlds:
275
276 # Create a connections entry for each external VLD
277 topology["connections"][vld.name] = {}
278 topology["connections"][vld.name]["nodes"] = []
279
280 #if vld.vim_network_name:
281 if True:
282 if vld.name not in topology["nodes"]:
283 topology["nodes"][vld.name] = {
284 "type": "external_network",
285 "model": vld.name,
286 }
287
288 # Add the external network to the list of connection points
289 topology["connections"][vld.name]["nodes"].append(
290 {vld.name: "0"}
291 )
292 elif vld.provider_network.has_field("physical_network"):
293 # Add the external datacenter network to the topology
294 # node list if it isn't already added
295 ext_net_name = vld.provider_network.physical_network
296 ext_net_name_with_seg = ext_net_name
297 if vld.provider_network.has_field("segmentation_id"):
298 ext_net_name_with_seg += ":{}".format(vld.provider_network.segmentation_id)
299
300 if ext_net_name not in topology["nodes"]:
301 topology["nodes"][ext_net_name] = {
302 "type": "external_network",
303 "model": ext_net_name_with_seg,
304 }
305
306 # Add the external network to the list of connection points
307 topology["connections"][vld.name]["nodes"].append(
308 {ext_net_name: "0"}
309 )
310
311
312 for vnfd_cp in vld.vnfd_connection_point_ref:
313
314 # Get the RIFT VNF for this external VLD connection point
315 vnfd = rift_vnfds[vnfd_cp.vnfd_id_ref]
316
317 # For each VNF in this connection, use the same interface name
318 topology["connections"][vld.name]["type"] = "link"
319 # Vnf ref is the vnf name with the member_vnf_idx appended
320 member_idx = vnfd_cp.member_vnf_index_ref
321 vnf_ref = vnfd.name + "__" + str(member_idx)
322 topology["connections"][vld.name]["nodes"].append(
323 {
324 vnf_ref: vnfd_cp.vnfd_connection_point_ref
325 }
326 )
327
328 return openmano
329
330
331 def rift2openmano_vnfd(rift_vnfd):
332 openmano_vnf = {"vnf":{}}
333 vnf = openmano_vnf["vnf"]
334
335 vnf["name"] = rift_vnfd.name
336 vnf["description"] = rift_vnfd.description
337
338 vnf["external-connections"] = []
339
340 def find_vdu_and_ext_if_by_cp_ref(cp_ref_name):
341 for vdu in rift_vnfd.vdus:
342 for ext_if in vdu.external_interface:
343 if ext_if.vnfd_connection_point_ref == cp_ref_name:
344 return vdu, ext_if
345
346 raise ValueError("External connection point reference %s not found" % cp_ref_name)
347
348 def find_vdu_and_int_if_by_cp_ref(cp_ref_id):
349 for vdu in rift_vnfd.vdus:
350 for int_if in vdu.internal_interface:
351 if int_if.vdu_internal_connection_point_ref == cp_ref_id:
352 return vdu, int_if
353
354 raise ValueError("Internal connection point reference %s not found" % cp_ref_id)
355
356 def rift2openmano_if_type(rift_type):
357 if rift_type == "OM_MGMT":
358 return "mgmt"
359 elif rift_type == "VIRTIO":
360 return "bridge"
361 else:
362 return "data"
363
364 # Add all external connections
365 for cp in rift_vnfd.cps:
366 # Find the VDU and and external interface for this connection point
367 vdu, ext_if = find_vdu_and_ext_if_by_cp_ref(cp.name)
368 connection = {
369 "name": cp.name,
370 "type": rift2openmano_if_type(ext_if.virtual_interface.type_yang),
371 "VNFC": vdu.name,
372 "local_iface_name": ext_if.name,
373 "description": "%s iface on VDU %s" % (ext_if.name, vdu.name),
374 }
375
376 vnf["external-connections"].append(connection)
377
378 # Add all internal networks
379 for vld in rift_vnfd.internal_vlds:
380 connection = {
381 "name": vld.name,
382 "description": vld.description,
383 "type": "data",
384 "elements": [],
385 }
386
387 # Add the specific VDU connection points
388 for int_cp_ref in vld.internal_connection_point_ref:
389 vdu, int_if = find_vdu_and_int_if_by_cp_ref(int_cp_ref)
390 connection["elements"].append({
391 "VNFC": vdu.name,
392 "local_iface_name": int_if.name,
393 })
394 if "internal-connections" not in vnf:
395 vnf["internal-connections"] = []
396
397 vnf["internal-connections"].append(connection)
398
399 # Add VDU's
400 vnf["VNFC"] = []
401 for vdu in rift_vnfd.vdus:
402 vnfc = {
403 "name": vdu.name,
404 "description": vdu.name,
405 "VNFC image": vdu.image if os.path.isabs(vdu.image) else "/var/images/{}".format(vdu.image),
406 "numas": [{
407 "memory": max(int(vdu.vm_flavor.memory_mb/1024), 1),
408 "interfaces":[],
409 }],
410 "bridge-ifaces": [],
411 }
412
413 numa_node_policy = vdu.guest_epa.numa_node_policy
414 if numa_node_policy.has_field("node"):
415 numa_node = numa_node_policy.node[0]
416
417 if numa_node.has_field("paired_threads"):
418 if numa_node.paired_threads.has_field("num_paired_threads"):
419 vnfc["numas"][0]["paired-threads"] = numa_node.paired_threads.num_paired_threads
420 if len(numa_node.paired_threads.paired_thread_ids) > 0:
421 vnfc["numas"][0]["paired-threads-id"] = []
422 for pair in numa_node.paired_threads.paired_thread_ids:
423 vnfc["numas"][0]["paired-threads-id"].append(
424 [pair.thread_a, pair.thread_b]
425 )
426
427 else:
428 if vdu.vm_flavor.has_field("vcpu_count"):
429 vnfc["numas"][0]["cores"] = max(vdu.vm_flavor.vcpu_count, 1)
430
431 if vdu.has_field("hypervisor_epa"):
432 vnfc["hypervisor"] = {}
433 if vdu.hypervisor_epa.has_field("type"):
434 if vdu.hypervisor_epa.type_yang == "REQUIRE_KVM":
435 vnfc["hypervisor"]["type"] = "QEMU-kvm"
436
437 if vdu.hypervisor_epa.has_field("version"):
438 vnfc["hypervisor"]["version"] = vdu.hypervisor_epa.version
439
440 if vdu.has_field("host_epa"):
441 vnfc["processor"] = {}
442 if vdu.host_epa.has_field("om_cpu_model_string"):
443 vnfc["processor"]["model"] = vdu.host_epa.om_cpu_model_string
444 if vdu.host_epa.has_field("om_cpu_feature"):
445 vnfc["processor"]["features"] = []
446 for feature in vdu.host_epa.om_cpu_feature:
447 vnfc["processor"]["features"].append(feature)
448
449
450 if vdu.vm_flavor.has_field("storage_gb"):
451 vnfc["disk"] = vdu.vm_flavor.storage_gb
452
453 vnf["VNFC"].append(vnfc)
454
455 for int_if in list(vdu.internal_interface) + list(vdu.external_interface):
456 intf = {
457 "name": int_if.name,
458 }
459 if int_if.virtual_interface.has_field("vpci"):
460 intf["vpci"] = int_if.virtual_interface.vpci
461
462 if int_if.virtual_interface.type_yang in ["VIRTIO", "OM_MGMT"]:
463 vnfc["bridge-ifaces"].append(intf)
464
465 elif int_if.virtual_interface.type_yang == "SR-IOV":
466 intf["bandwidth"] = "10 Gbps"
467 intf["dedicated"] = "yes:sriov"
468 vnfc["numas"][0]["interfaces"].append(intf)
469
470 elif int_if.virtual_interface.type_yang == "PCI_PASSTHROUGH":
471 intf["bandwidth"] = "10 Gbps"
472 intf["dedicated"] = "yes"
473 if "interfaces" not in vnfc["numas"][0]:
474 vnfc["numas"][0]["interfaces"] = []
475 vnfc["numas"][0]["interfaces"].append(intf)
476 else:
477 raise ValueError("Interface type %s not supported" % int_if.virtual_interface)
478
479 if int_if.virtual_interface.has_field("bandwidth"):
480 if int_if.virtual_interface.bandwidth != 0:
481 bps = int_if.virtual_interface.bandwidth
482
483 # Calculate the bits per second conversion
484 for x in [('M', 1000000), ('G', 1000000000)]:
485 if bps/x[1] >= 1:
486 intf["bandwidth"] = "{} {}bps".format(math.ceil(bps/x[1]), x[0])
487
488
489 return openmano_vnf
490
491
492 def parse_args(argv=sys.argv[1:]):
493 """ Parse the command line arguments
494
495 Arguments:
496 arv - The list of arguments to parse
497
498 Returns:
499 Argparse Namespace instance
500 """
501 parser = argparse.ArgumentParser()
502 parser.add_argument(
503 '-o', '--outdir',
504 default='-',
505 help="Directory to output converted descriptors. Default is stdout",
506 )
507
508 parser.add_argument(
509 '-n', '--nsd-file-hdl',
510 metavar="nsd_xml_file",
511 type=argparse.FileType('r'),
512 help="Rift NSD Descriptor File",
513 )
514
515 parser.add_argument(
516 '-v', '--vnfd-file-hdls',
517 metavar="vnfd_xml_file",
518 action='append',
519 type=argparse.FileType('r'),
520 help="Rift VNFD Descriptor File",
521 )
522
523 args = parser.parse_args(argv)
524
525 if not os.path.exists(args.outdir):
526 os.makedirs(args.outdir)
527
528 if not is_writable_directory(args.outdir):
529 logging.error("Directory %s is not writable", args.outdir)
530 sys.exit(1)
531
532 return args
533
534
535 def write_yaml_to_file(name, outdir, desc_dict):
536 file_name = "%s.yaml" % name
537 yaml_str = yaml.dump(desc_dict)
538 if outdir == "-":
539 sys.stdout.write(yaml_str)
540 return
541
542 file_path = os.path.join(outdir, file_name)
543 dir_path = os.path.dirname(file_path)
544 if not os.path.exists(dir_path):
545 os.makedirs(dir_path)
546
547 with open(file_path, "w") as hdl:
548 hdl.write(yaml_str)
549
550 logger.info("Wrote descriptor to %s", file_path)
551
552
553 def main(argv=sys.argv[1:]):
554 args = parse_args(argv)
555
556 nsd = None
557 if args.vnfd_file_hdls is not None:
558 vnf_dict = create_vnfd_from_xml_files(args.vnfd_file_hdls)
559
560 if args.nsd_file_hdl is not None:
561 nsd = create_nsd_from_xml_file(args.nsd_file_hdl)
562
563 openmano_nsd = rift2openmano_nsd(nsd, vnf_dict)
564
565 write_yaml_to_file(openmano_nsd["name"], args.outdir, openmano_nsd)
566
567 for vnf in vnf_dict.values():
568 openmano_vnf = rift2openmano_vnfd(vnf)
569 write_yaml_to_file(openmano_vnf["vnf"]["name"], args.outdir, openmano_vnf)
570
571
572 if __name__ == "__main__":
573 logging.basicConfig(level=logging.WARNING)
574 main()