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