Support image name and checksum
[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 "numas": [{
406 "memory": max(int(vdu.vm_flavor.memory_mb/1024), 1),
407 "interfaces":[],
408 }],
409 "bridge-ifaces": [],
410 }
411
412 if os.path.isabs(vdu.image):
413 vnfc["VNFC image"] = vdu.image
414 else:
415 vnfc["image name"] = vdu.image
416 if vdu.has_field("image_checksum"):
417 vnfc["image checksum"] = vdu.image_checksum
418
419 numa_node_policy = vdu.guest_epa.numa_node_policy
420 if numa_node_policy.has_field("node"):
421 numa_node = numa_node_policy.node[0]
422
423 if numa_node.has_field("paired_threads"):
424 if numa_node.paired_threads.has_field("num_paired_threads"):
425 vnfc["numas"][0]["paired-threads"] = numa_node.paired_threads.num_paired_threads
426 if len(numa_node.paired_threads.paired_thread_ids) > 0:
427 vnfc["numas"][0]["paired-threads-id"] = []
428 for pair in numa_node.paired_threads.paired_thread_ids:
429 vnfc["numas"][0]["paired-threads-id"].append(
430 [pair.thread_a, pair.thread_b]
431 )
432
433 else:
434 if vdu.vm_flavor.has_field("vcpu_count"):
435 vnfc["numas"][0]["cores"] = max(vdu.vm_flavor.vcpu_count, 1)
436
437 if vdu.has_field("hypervisor_epa"):
438 vnfc["hypervisor"] = {}
439 if vdu.hypervisor_epa.has_field("type"):
440 if vdu.hypervisor_epa.type_yang == "REQUIRE_KVM":
441 vnfc["hypervisor"]["type"] = "QEMU-kvm"
442
443 if vdu.hypervisor_epa.has_field("version"):
444 vnfc["hypervisor"]["version"] = vdu.hypervisor_epa.version
445
446 if vdu.has_field("host_epa"):
447 vnfc["processor"] = {}
448 if vdu.host_epa.has_field("om_cpu_model_string"):
449 vnfc["processor"]["model"] = vdu.host_epa.om_cpu_model_string
450 if vdu.host_epa.has_field("om_cpu_feature"):
451 vnfc["processor"]["features"] = []
452 for feature in vdu.host_epa.om_cpu_feature:
453 vnfc["processor"]["features"].append(feature)
454
455
456 if vdu.vm_flavor.has_field("storage_gb"):
457 vnfc["disk"] = vdu.vm_flavor.storage_gb
458
459 vnf["VNFC"].append(vnfc)
460
461 for int_if in list(vdu.internal_interface) + list(vdu.external_interface):
462 intf = {
463 "name": int_if.name,
464 }
465 if int_if.virtual_interface.has_field("vpci"):
466 intf["vpci"] = int_if.virtual_interface.vpci
467
468 if int_if.virtual_interface.type_yang in ["VIRTIO", "OM_MGMT"]:
469 vnfc["bridge-ifaces"].append(intf)
470
471 elif int_if.virtual_interface.type_yang == "SR-IOV":
472 intf["bandwidth"] = "10 Gbps"
473 intf["dedicated"] = "yes:sriov"
474 vnfc["numas"][0]["interfaces"].append(intf)
475
476 elif int_if.virtual_interface.type_yang == "PCI_PASSTHROUGH":
477 intf["bandwidth"] = "10 Gbps"
478 intf["dedicated"] = "yes"
479 if "interfaces" not in vnfc["numas"][0]:
480 vnfc["numas"][0]["interfaces"] = []
481 vnfc["numas"][0]["interfaces"].append(intf)
482 else:
483 raise ValueError("Interface type %s not supported" % int_if.virtual_interface)
484
485 if int_if.virtual_interface.has_field("bandwidth"):
486 if int_if.virtual_interface.bandwidth != 0:
487 bps = int_if.virtual_interface.bandwidth
488
489 # Calculate the bits per second conversion
490 for x in [('M', 1000000), ('G', 1000000000)]:
491 if bps/x[1] >= 1:
492 intf["bandwidth"] = "{} {}bps".format(math.ceil(bps/x[1]), x[0])
493
494
495 return openmano_vnf
496
497
498 def parse_args(argv=sys.argv[1:]):
499 """ Parse the command line arguments
500
501 Arguments:
502 arv - The list of arguments to parse
503
504 Returns:
505 Argparse Namespace instance
506 """
507 parser = argparse.ArgumentParser()
508 parser.add_argument(
509 '-o', '--outdir',
510 default='-',
511 help="Directory to output converted descriptors. Default is stdout",
512 )
513
514 parser.add_argument(
515 '-n', '--nsd-file-hdl',
516 metavar="nsd_xml_file",
517 type=argparse.FileType('r'),
518 help="Rift NSD Descriptor File",
519 )
520
521 parser.add_argument(
522 '-v', '--vnfd-file-hdls',
523 metavar="vnfd_xml_file",
524 action='append',
525 type=argparse.FileType('r'),
526 help="Rift VNFD Descriptor File",
527 )
528
529 args = parser.parse_args(argv)
530
531 if not os.path.exists(args.outdir):
532 os.makedirs(args.outdir)
533
534 if not is_writable_directory(args.outdir):
535 logging.error("Directory %s is not writable", args.outdir)
536 sys.exit(1)
537
538 return args
539
540
541 def write_yaml_to_file(name, outdir, desc_dict):
542 file_name = "%s.yaml" % name
543 yaml_str = yaml.dump(desc_dict)
544 if outdir == "-":
545 sys.stdout.write(yaml_str)
546 return
547
548 file_path = os.path.join(outdir, file_name)
549 dir_path = os.path.dirname(file_path)
550 if not os.path.exists(dir_path):
551 os.makedirs(dir_path)
552
553 with open(file_path, "w") as hdl:
554 hdl.write(yaml_str)
555
556 logger.info("Wrote descriptor to %s", file_path)
557
558
559 def main(argv=sys.argv[1:]):
560 args = parse_args(argv)
561
562 nsd = None
563 if args.vnfd_file_hdls is not None:
564 vnf_dict = create_vnfd_from_xml_files(args.vnfd_file_hdls)
565
566 if args.nsd_file_hdl is not None:
567 nsd = create_nsd_from_xml_file(args.nsd_file_hdl)
568
569 openmano_nsd = rift2openmano_nsd(nsd, vnf_dict)
570
571 write_yaml_to_file(openmano_nsd["name"], args.outdir, openmano_nsd)
572
573 for vnf in vnf_dict.values():
574 openmano_vnf = rift2openmano_vnfd(vnf)
575 write_yaml_to_file(openmano_vnf["vnf"]["name"], args.outdir, openmano_vnf)
576
577
578 if __name__ == "__main__":
579 logging.basicConfig(level=logging.WARNING)
580 main()