473ff3dba21d9b9b17f79c9b04472f33019989ca
[osm/osmclient.git] / osmclient / common / package_tool.py
1 # /bin/env python3
2 # Copyright 2019 ATOS
3 #
4 # All Rights Reserved.
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License"); you may
7 # not use this file except in compliance with the License. You may obtain
8 # 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, WITHOUT
14 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15 # License for the specific language governing permissions and limitations
16 # under the License.
17
18 import glob
19 import hashlib
20 import logging
21 import os
22 import shutil
23 import subprocess
24 import tarfile
25 import time
26
27 from jinja2 import Environment, PackageLoader
28 from osm_im.validation import Validation as validation_im
29 from osm_im.validation import ValidationException
30 from osm_im import im_translation
31 from osmclient.common.exceptions import ClientException
32 import yaml
33
34
35 class PackageTool(object):
36 def __init__(self, client=None):
37 self._client = client
38 self._logger = logging.getLogger('osmclient')
39 self._validator = validation_im()
40
41 def create(self, package_type, base_directory, package_name, override, image, vdus, vcpu, memory, storage,
42 interfaces, vendor, detailed, netslice_subnets, netslice_vlds):
43 """
44 **Create a package descriptor**
45
46 :params:
47 - package_type: [vnf, ns, nst]
48 - base directory: path of destination folder
49 - package_name: is the name of the package to be created
50 - image: specify the image of the vdu
51 - vcpu: number of virtual cpus of the vdu
52 - memory: amount of memory in MB pf the vdu
53 - storage: amount of storage in GB of the vdu
54 - interfaces: number of interfaces besides management interface
55 - vendor: vendor name of the vnf/ns
56 - detailed: include all possible values for NSD, VNFD, NST
57 - netslice_subnets: number of netslice_subnets for the NST
58 - netslice_vlds: number of virtual link descriptors for the NST
59
60 :return: status
61 """
62 self._logger.debug("")
63 # print("location: {}".format(osmclient.__path__))
64 file_loader = PackageLoader("osmclient")
65 env = Environment(loader=file_loader)
66 if package_type == 'ns':
67 template = env.get_template('nsd.yaml.j2')
68 content = {"name": package_name, "vendor": vendor, "vdus": vdus, "clean": False, "interfaces": interfaces,
69 "detailed": detailed}
70 elif package_type == 'vnf':
71 template = env.get_template('vnfd.yaml.j2')
72 content = {"name": package_name, "vendor": vendor, "vdus": vdus, "clean": False, "interfaces": interfaces,
73 "image": image, "vcpu": vcpu, "memory": memory, "storage": storage, "detailed": detailed}
74 elif package_type == 'nst':
75 template = env.get_template('nst.yaml.j2')
76 content = {"name": package_name, "vendor": vendor, "interfaces": interfaces,
77 "netslice_subnets": netslice_subnets, "netslice_vlds": netslice_vlds, "detailed": detailed}
78 else:
79 raise ClientException("Wrong descriptor type {}. Options: ns, vnf, nst".format(package_type))
80
81 # print("To be rendered: {}".format(content))
82 output = template.render(content)
83 # print(output)
84
85 structure = self.discover_folder_structure(base_directory, package_name, override)
86 if structure.get("folders"):
87 self.create_folders(structure["folders"], package_type)
88 if structure.get("files"):
89 self.create_files(structure["files"], output, package_type)
90 return "Created"
91
92 def validate(self, base_directory, recursive=True, old_format=False):
93 """
94 **Validate OSM Descriptors given a path**
95
96 :params:
97 - base_directory is the root path for all descriptors
98
99 :return: List of dict of validated descriptors. keys: type, path, valid, error
100 """
101 self._logger.debug("")
102 table = []
103 if recursive:
104 descriptors_paths = [f for f in glob.glob(base_directory + "/**/*.yaml", recursive=recursive)]
105 else:
106 descriptors_paths = [f for f in glob.glob(base_directory + "/*.yaml", recursive=recursive)]
107 print("Base directory: {}".format(base_directory))
108 print("{} Descriptors found to validate".format(len(descriptors_paths)))
109 for desc_path in descriptors_paths:
110 with open(desc_path) as descriptor_file:
111 descriptor_data = descriptor_file.read()
112 desc_type = "-"
113 try:
114 desc_type, descriptor_data = validation_im.yaml_validation(self, descriptor_data)
115 if not old_format:
116 if ( desc_type=="vnfd" or desc_type=="nsd" ):
117 print("OSM descriptor '{}' written in an unsupported format. Please update to ETSI SOL006 format".format(desc_path))
118 print("Package validation skipped. It can still be done with 'osm package-validate --old'")
119 print("Package build can still be done with 'osm package-build --skip-validation'")
120 raise Exception("Not SOL006 format")
121 validation_im.pyangbind_validation(self, desc_type, descriptor_data)
122 table.append({"type": desc_type, "path": desc_path, "valid": "OK", "error": "-"})
123 except Exception as e:
124 table.append({"type": desc_type, "path": desc_path, "valid": "ERROR", "error": str(e)})
125 return table
126
127 def translate(self, base_directory, recursive=True, dryrun=False):
128 """
129 **Translate OSM Packages given a path**
130
131 :params:
132 - base_directory is the root path for all packages
133
134 :return: List of dict of translated packages. keys: current type, new type, path, valid, translated, error
135 """
136 self._logger.debug("")
137 table = []
138 if recursive:
139 descriptors_paths = [f for f in glob.glob(base_directory + "/**/*.yaml", recursive=recursive)]
140 else:
141 descriptors_paths = [f for f in glob.glob(base_directory + "/*.yaml", recursive=recursive)]
142 print("Base directory: {}".format(base_directory))
143 print("{} Descriptors found to validate".format(len(descriptors_paths)))
144 for desc_path in descriptors_paths:
145 with open(desc_path) as descriptor_file:
146 descriptor_data = descriptor_file.read()
147 desc_type = "-"
148 try:
149 desc_type, descriptor_data = validation_im.yaml_validation(self, descriptor_data)
150 self._logger.debug("desc_type: {}".format(desc_type))
151 self._logger.debug("descriptor_data:\n{}".format(descriptor_data))
152 self._validator.pyangbind_validation(desc_type, descriptor_data)
153 if not ( desc_type=="vnfd" or desc_type=="nsd" ):
154 table.append({"current type": desc_type, "new type": desc_type, "path": desc_path, "valid": "OK", "translated": "N/A", "error": "-"})
155 else:
156 new_desc_type = desc_type
157 try:
158 sol006_model = yaml.safe_dump(im_translation.translate_im_model_to_sol006(descriptor_data), indent=4, default_flow_style=False)
159 new_desc_type, new_descriptor_data = self._validator.yaml_validation(sol006_model)
160 self._validator.pyangbind_validation(new_desc_type, new_descriptor_data)
161 if not dryrun:
162 with open(desc_path, 'w') as descriptor_file:
163 descriptor_file.write(sol006_model)
164 table.append({"current type": desc_type, "new type": new_desc_type, "path": desc_path, "valid": "OK", "translated": "OK", "error": "-"})
165 except ValidationException as ve2:
166 table.append({"current type": desc_type, "new type": new_desc_type, "path": desc_path, "valid": "OK", "translated": "ERROR", "error": "Error in the post-validation: {}".format(str(ve2))})
167 except Exception as e2:
168 table.append({"current type": desc_type, "new type": new_desc_type, "path": desc_path, "valid": "OK", "translated": "ERROR", "error": "Error in the translation: {}".format(str(e2))})
169 except ValidationException as ve:
170 table.append({"current type": desc_type, "new type": "N/A", "path": desc_path, "valid": "ERROR", "translated": "N/A", "error": "Error in the pre-validation: {}".format(str(ve))})
171 except Exception as e:
172 table.append({"current type": desc_type, "new type": "N/A", "path": desc_path, "valid": "ERROR", "translated": "N/A", "error": str(e)})
173 return table
174
175 def descriptor_translate(self, descriptor_file):
176 """
177 **Translate input descriptor file from Rel EIGHT OSM to SOL006**
178
179 :params:
180 - base_directory is the root path for all packages
181
182 :return: YAML descriptor in the new format
183 """
184 self._logger.debug("")
185 with open(descriptor_file, 'r') as df:
186 im_model = yaml.safe_load(df.read())
187 sol006_model = im_translation.translate_im_model_to_sol006(im_model)
188 return yaml.safe_dump(sol006_model, indent=4, default_flow_style=False)
189
190 def build(self, package_folder, skip_validation=False, skip_charm_build=False):
191 """
192 **Creates a .tar.gz file given a package_folder**
193
194 :params:
195 - package_folder: is the name of the folder to be packaged
196 - skip_validation: is the flag to validate or not the descriptors on the folder before build
197
198 :returns: message result for the build process
199 """
200 self._logger.debug("")
201 package_folder = package_folder.rstrip('/')
202 if not os.path.exists("{}".format(package_folder)):
203 return "Fail, package is not in the specified path"
204 if not skip_validation:
205 print('Validating package {}'.format(package_folder))
206 results = self.validate(package_folder, recursive=False)
207 if results:
208 for result in results:
209 if result["valid"] != "OK":
210 raise ClientException("There was an error validating the file {} with error: {}"
211 .format(result["path"], result["error"]))
212 print('Validation OK')
213 else:
214 raise ClientException("No descriptor file found in: {}".format(package_folder))
215 charm_list = self.build_all_charms(package_folder, skip_charm_build)
216 return self.build_tarfile(package_folder, charm_list)
217
218 def calculate_checksum(self, package_folder):
219 """
220 **Function to calculate the checksum given a folder**
221
222 :params:
223 - package_folder: is the folder where we have the files to calculate the checksum
224 :returns: None
225 """
226 self._logger.debug("")
227 files = [f for f in glob.glob(package_folder + "/**/*.*", recursive=True) if os.path.isfile(f)]
228 with open("{}/checksums.txt".format(package_folder), "w+") as checksum:
229 for file_item in files:
230 if "checksums.txt" in file_item:
231 continue
232 # from https://www.quickprogrammingtips.com/python/how-to-calculate-md5-hash-of-a-file-in-python.html
233 md5_hash = hashlib.md5()
234 with open(file_item, "rb") as f:
235 # Read and update hash in chunks of 4K
236 for byte_block in iter(lambda: f.read(4096), b""):
237 md5_hash.update(byte_block)
238 checksum.write("{}\t{}\n".format(md5_hash.hexdigest(), file_item))
239
240 def create_folders(self, folders, package_type):
241 """
242 **Create folder given a list of folders**
243
244 :params:
245 - folders: [List] list of folders paths to be created
246 - package_type: is the type of package to be created
247 :return: None
248 """
249 self._logger.debug("")
250 for folder in folders:
251 try:
252 # print("Folder {} == package_type {}".format(folder[1], package_type))
253 if folder[1] == package_type:
254 print("Creating folder:\t{}".format(folder[0]))
255 os.makedirs(folder[0])
256 except FileExistsError:
257 pass
258
259 def save_file(self, file_name, file_body):
260 """
261 **Create a file given a name and the content**
262
263 :params:
264 - file_name: is the name of the file with the relative route
265 - file_body: is the content of the file
266 :return: None
267 """
268 self._logger.debug("")
269 print("Creating file: \t{}".format(file_name))
270 try:
271 with open(file_name, "w+") as f:
272 f.write(file_body)
273 except Exception as e:
274 raise ClientException(e)
275
276 def generate_readme(self):
277 """
278 **Creates the README content**
279
280 :returns: readme content
281 """
282 self._logger.debug("")
283 return """# Descriptor created by OSM descriptor package generated\n\n**Created on {} **""".format(
284 time.strftime("%m/%d/%Y, %H:%M:%S", time.localtime()))
285
286 def generate_cloud_init(self):
287 """
288 **Creates the cloud-init content**
289
290 :returns: cloud-init content
291 """
292 self._logger.debug("")
293 return "---\n#cloud-config"
294
295 def create_files(self, files, file_content, package_type):
296 """
297 **Creates the files given the file list and type**
298
299 :params:
300 - files: is the list of files structure
301 - file_content: is the content of the descriptor rendered by the template
302 - package_type: is the type of package to filter the creation structure
303
304 :return: None
305 """
306 self._logger.debug("")
307 for file_item, file_package, file_type in files:
308 if package_type == file_package:
309 if file_type == "descriptor":
310 self.save_file(file_item, file_content)
311 elif file_type == "readme":
312 self.save_file(file_item, self.generate_readme())
313 elif file_type == "cloud_init":
314 self.save_file(file_item, self.generate_cloud_init())
315
316 def check_files_folders(self, path_list, override):
317 """
318 **Find files and folders missing given a directory structure {"folders": [], "files": []}**
319
320 :params:
321 - path_list: is the list of files and folders to be created
322 - override: is the flag used to indicate the creation of the list even if the file exist to override it
323
324 :return: Missing paths Dict
325 """
326 self._logger.debug("")
327 missing_paths = {}
328 folders = []
329 files = []
330 for folder in path_list.get("folders"):
331 if not os.path.exists(folder[0]):
332 folders.append(folder)
333 missing_paths["folders"] = folders
334
335 for file_item in path_list.get("files"):
336 if not os.path.exists(file_item[0]) or override is True:
337 files.append(file_item)
338 missing_paths["files"] = files
339
340 return missing_paths
341
342 def build_all_charms(self, package_folder, skip_charm_build):
343 """
344 **Read the descriptor file, check that the charms referenced are in the folder and compiles them**
345
346 :params:
347 - packet_folder: is the location of the package
348 :return: Files and Folders not found. In case of override, it will return all file list
349 """
350 self._logger.debug("")
351 listCharms = []
352 descriptor_file = False
353 descriptors_paths = [f for f in glob.glob(package_folder + "/*.yaml")]
354 for file in descriptors_paths:
355 if file.endswith('nfd.yaml'):
356 descriptor_file = True
357 listCharms = self.charms_search(file, 'vnf')
358 if file.endswith('nsd.yaml'):
359 descriptor_file = True
360 listCharms = self.charms_search(file, 'ns')
361 print("List of charms in the descriptor: {}".format(listCharms))
362 if not descriptor_file:
363 raise ClientException('Descriptor filename is not correct in: {}. It should end with "nfd.yaml" or "nsd.yaml"'.format(package_folder))
364 if listCharms and not skip_charm_build:
365 for charmName in listCharms:
366 if os.path.isdir('{}/charms/layers/{}'.format(package_folder, charmName)):
367 print('Building charm {}/charms/layers/{}'.format(package_folder, charmName))
368 self.charm_build(package_folder, charmName)
369 print('Charm built: {}'.format(charmName))
370 elif os.path.isdir('{}/charms/ops/{}'.format(package_folder, charmName)):
371 self.charmcraft_build(package_folder, charmName)
372 else:
373 if not os.path.isdir('{}/charms/{}'.format(package_folder, charmName)):
374 raise ClientException('The charm: {} referenced in the descriptor file '
375 'is not present either in {}/charms or in {}/charms/layers'.
376 format(charmName, package_folder, package_folder))
377 self._logger.debug("Return list of charms: {}".format(listCharms))
378 return listCharms
379
380 def discover_folder_structure(self, base_directory, name, override):
381 """
382 **Discover files and folders structure for OSM descriptors given a base_directory and name**
383
384 :params:
385 - base_directory: is the location of the package to be created
386 - name: is the name of the package
387 - override: is the flag used to indicate the creation of the list even if the file exist to override it
388 :return: Files and Folders not found. In case of override, it will return all file list
389 """
390 self._logger.debug("")
391 prefix = "{}/{}".format(base_directory, name)
392 files_folders = {"folders": [("{}_ns".format(prefix), "ns"),
393 ("{}_ns/icons".format(prefix), "ns"),
394 ("{}_ns/charms".format(prefix), "ns"),
395 ("{}_vnf".format(name), "vnf"),
396 ("{}_vnf/charms".format(prefix), "vnf"),
397 ("{}_vnf/cloud_init".format(prefix), "vnf"),
398 ("{}_vnf/images".format(prefix), "vnf"),
399 ("{}_vnf/icons".format(prefix), "vnf"),
400 ("{}_vnf/scripts".format(prefix), "vnf"),
401 ("{}_nst".format(prefix), "nst"),
402 ("{}_nst/icons".format(prefix), "nst")
403 ],
404 "files": [("{}_ns/{}_nsd.yaml".format(prefix, name), "ns", "descriptor"),
405 ("{}_ns/README.md".format(prefix), "ns", "readme"),
406 ("{}_vnf/{}_vnfd.yaml".format(prefix, name), "vnf", "descriptor"),
407 ("{}_vnf/cloud_init/cloud-config.txt".format(prefix), "vnf", "cloud_init"),
408 ("{}_vnf/README.md".format(prefix), "vnf", "readme"),
409 ("{}_nst/{}_nst.yaml".format(prefix, name), "nst", "descriptor"),
410 ("{}_nst/README.md".format(prefix), "nst", "readme")
411 ]
412 }
413 missing_files_folders = self.check_files_folders(files_folders, override)
414 # print("Missing files and folders: {}".format(missing_files_folders))
415 return missing_files_folders
416
417 def charm_build(self, charms_folder, build_name):
418 """
419 Build the charms inside the package.
420 params: package_folder is the name of the folder where is the charms to compile.
421 build_name is the name of the layer or interface
422 """
423 self._logger.debug("")
424 os.environ['JUJU_REPOSITORY'] = "{}/charms".format(charms_folder)
425 os.environ['CHARM_LAYERS_DIR'] = "{}/layers".format(os.environ['JUJU_REPOSITORY'])
426 os.environ['CHARM_INTERFACES_DIR'] = "{}/interfaces".format(os.environ['JUJU_REPOSITORY'])
427 os.environ['CHARM_BUILD_DIR'] = "{}/charms/builds".format(charms_folder)
428 if not os.path.exists(os.environ['CHARM_BUILD_DIR']):
429 os.makedirs(os.environ['CHARM_BUILD_DIR'])
430 src_folder = '{}/{}'.format(os.environ['CHARM_LAYERS_DIR'], build_name)
431 result = subprocess.run(["charm", "build", "{}".format(src_folder)])
432 if result.returncode == 1:
433 raise ClientException("failed to build the charm: {}".format(src_folder))
434 self._logger.verbose("charm {} built".format(src_folder))
435
436 def charmcraft_build(self, package_folder, charm_name):
437 """
438 Build the charms inside the package (new operator framework charms)
439 params: package_folder is the name of the folder where is the charms to compile.
440 build_name is the name of the layer or interface
441 """
442 self._logger.debug("Building charm {}".format(charm_name))
443 src_folder = f"{package_folder}/charms/ops/{charm_name}"
444 current_directory = os.getcwd()
445 os.chdir(src_folder)
446 try:
447 result = subprocess.run(["charmcraft", "build"])
448 if result.returncode == 1:
449 raise ClientException("failed to build the charm: {}".format(src_folder))
450 subprocess.run(["rm", "-rf", f"../../{charm_name}"])
451 subprocess.run(["mv", "build", f"../../{charm_name}"])
452 self._logger.verbose("charm {} built".format(src_folder))
453 finally:
454 os.chdir(current_directory)
455
456 def build_tarfile(self, package_folder, charm_list=None):
457 """
458 Creates a .tar.gz file given a package_folder
459 params: package_folder is the name of the folder to be packaged
460 returns: .tar.gz name
461 """
462 self._logger.debug("")
463 cwd = None
464 try:
465 directory_name, package_name = self.create_temp_dir(package_folder, charm_list)
466 cwd = os.getcwd()
467 os.chdir(directory_name)
468 self.calculate_checksum(package_name)
469 with tarfile.open("{}.tar.gz".format(package_name), mode='w:gz') as archive:
470 print("Adding File: {}".format(package_name))
471 archive.add('{}'.format(package_name), recursive=True)
472 # return "Created {}.tar.gz".format(package_folder)
473 # self.build("{}".format(os.path.basename(package_folder)))
474 os.chdir(cwd)
475 cwd = None
476 created_package = "{}/{}.tar.gz".format(os.path.dirname(package_folder) or '.', package_name)
477 os.rename("{}/{}.tar.gz".format(directory_name, package_name),
478 created_package)
479 os.rename("{}/{}/checksums.txt".format(directory_name, package_name),
480 "{}/checksums.txt".format(package_folder))
481 print("Package created: {}".format(created_package))
482 return created_package
483 except Exception as exc:
484 raise ClientException('failure during build of targz file (create temp dir, calculate checksum, '
485 'tar.gz file): {}'.format(exc))
486 finally:
487 if cwd:
488 os.chdir(cwd)
489 shutil.rmtree(os.path.join(package_folder, "tmp"))
490
491 def create_temp_dir(self, package_folder, charm_list=None):
492 """
493 Method to create a temporary folder where we can move the files in package_folder
494 """
495 self._logger.debug("")
496 ignore_patterns = ('.gitignore')
497 ignore = shutil.ignore_patterns(ignore_patterns)
498 directory_name = os.path.abspath(package_folder)
499 package_name = os.path.basename(directory_name)
500 directory_name += "/tmp"
501 os.makedirs("{}/{}".format(directory_name, package_name), exist_ok=True)
502 self._logger.debug("Makedirs DONE: {}/{}".format(directory_name, package_name))
503 for item in os.listdir(package_folder):
504 self._logger.debug("Item: {}".format(item))
505 if item != "tmp":
506 s = os.path.join(package_folder, item)
507 d = os.path.join(os.path.join(directory_name, package_name), item)
508 if os.path.isdir(s):
509 if item == "charms":
510 os.makedirs(d, exist_ok=True)
511 s_builds = os.path.join(s, "builds")
512 for charm in charm_list:
513 self._logger.debug("Copying charm {}".format(charm))
514 if charm in os.listdir(s):
515 s_charm = os.path.join(s, charm)
516 elif charm in os.listdir(s_builds):
517 s_charm = os.path.join(s_builds, charm)
518 else:
519 raise ClientException('The charm {} referenced in the descriptor file '
520 'could not be found in {}/charms or in {}/charms/builds'.
521 format(charm, package_folder, package_folder))
522 d_temp = os.path.join(d, charm)
523 self._logger.debug("Copying tree: {} -> {}".format(s_charm, d_temp))
524 shutil.copytree(s_charm, d_temp, symlinks=True, ignore=ignore)
525 self._logger.debug("DONE")
526 else:
527 self._logger.debug("Copying tree: {} -> {}".format(s, d))
528 shutil.copytree(s, d, symlinks=True, ignore=ignore)
529 self._logger.debug("DONE")
530 else:
531 if item in ignore_patterns:
532 continue
533 self._logger.debug("Copying file: {} -> {}".format(s, d))
534 shutil.copy2(s, d)
535 self._logger.debug("DONE")
536 return directory_name, package_name
537
538 def charms_search(self, descriptor_file, desc_type):
539 self._logger.debug("descriptor_file: {}, desc_type: {}".format(descriptor_file,
540 desc_type))
541 with open("{}".format(descriptor_file)) as yaml_desc:
542 descriptor_dict = yaml.safe_load(yaml_desc)
543 #self._logger.debug("\n"+yaml.safe_dump(descriptor_dict, indent=4, default_flow_style=False))
544
545 if ( (desc_type=="vnf" and ("vnfd:vnfd-catalog" in descriptor_dict or "vnfd-catalog" in descriptor_dict)) or
546 (desc_type=="ns" and ( "nsd:nsd-catalog" in descriptor_dict or "nsd-catalog" in descriptor_dict)) ):
547 charms_list = self._charms_search_on_osm_im_dict(descriptor_dict, desc_type)
548 else:
549 if desc_type == "ns":
550 get_charm_list = self._charms_search_on_nsd_sol006_dict
551 elif desc_type == "vnf":
552 get_charm_list = self._charms_search_on_vnfd_sol006_dict
553 else:
554 raise Exception("Bad descriptor type")
555 charms_list = get_charm_list(descriptor_dict)
556 return charms_list
557
558 def _charms_search_on_osm_im_dict(self, osm_im_dict, desc_type):
559 self._logger.debug("")
560 charms_list = []
561 for k1, v1 in osm_im_dict.items():
562 for k2, v2 in v1.items():
563 for entry in v2:
564 if '{}-configuration'.format(desc_type) in entry:
565 vnf_config = entry['{}-configuration'.format(desc_type)]
566 for k3, v3 in vnf_config.items():
567 if 'charm' in v3:
568 charms_list.append((v3['charm']))
569 if 'vdu' in entry:
570 vdus = entry['vdu']
571 for vdu in vdus:
572 if 'vdu-configuration' in vdu:
573 for k4, v4 in vdu['vdu-configuration'].items():
574 if 'charm' in v4:
575 charms_list.append((v4['charm']))
576 return charms_list
577
578 def _charms_search_on_vnfd_sol006_dict(self, sol006_dict):
579 self._logger.debug("")
580 charms_list = []
581 dfs = sol006_dict.get("vnfd", {}).get("df", [])
582 for df in dfs:
583 day_1_2s = df.get("lcm-operations-configuration", {}).get("operate-vnf-op-config", {}).get("day1-2")
584 for day_1_2 in day_1_2s:
585 exec_env_list = day_1_2.get("execution-environment-list", [])
586 for exec_env in exec_env_list:
587 if "juju" in exec_env and "charm" in exec_env["juju"]:
588 charms_list.append(exec_env["juju"]["charm"])
589 return charms_list
590
591 def _charms_search_on_nsd_sol006_dict(self, sol006_dict):
592 self._logger.debug("")
593 charms_list = []
594 nsd_list = sol006_dict.get("nsd", {}).get("nsd", [])
595 for nsd in nsd_list:
596 charm = nsd.get("ns-configuration", {}).get("juju", {}).get("charm")
597 if charm:
598 charms_list.append(charm)
599 return charms_list