X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=osm_nbi%2Fdescriptor_topics.py;h=590380a4b7aa99dac42b868d132754c3d0d43f01;hb=8bf978ed8d25bfdc9fe91033b57555946fef8e36;hp=4661964a32581cec1d578dfb64b8a31618fb23f8;hpb=81b30e916f0cdbcfe0da4868325591c34fb5acb2;p=osm%2FNBI.git diff --git a/osm_nbi/descriptor_topics.py b/osm_nbi/descriptor_topics.py index 4661964..590380a 100644 --- a/osm_nbi/descriptor_topics.py +++ b/osm_nbi/descriptor_topics.py @@ -19,8 +19,10 @@ import json import copy import os import shutil +import functools # import logging +from deepdiff import DeepDiff from hashlib import md5 from osm_common.dbbase import DbException, deep_update_rfc7396 from http import HTTPStatus @@ -1184,6 +1186,158 @@ class VnfdTopic(DescriptorTopic): return super().sol005_projection(data) + @staticmethod + def find_software_version(vnfd: dict) -> str: + """Find the sotware version in the VNFD descriptors + + Args: + vnfd (dict): Descriptor as a dictionary + + Returns: + software-version (str) + """ + default_sw_version = "1.0" + if vnfd.get("vnfd"): + vnfd = vnfd["vnfd"] + if vnfd.get("software-version"): + return vnfd["software-version"] + else: + return default_sw_version + + @staticmethod + def extract_policies(vnfd: dict) -> dict: + """Removes the policies from the VNFD descriptors + + Args: + vnfd (dict): Descriptor as a dictionary + + Returns: + vnfd (dict): VNFD which does not include policies + """ + # TODO: Extract the policy related parts from the VNFD + return vnfd + + @staticmethod + def extract_day12_primitives(vnfd: dict) -> dict: + """Removes the day12 primitives from the VNFD descriptors + + Args: + vnfd (dict): Descriptor as a dictionary + + Returns: + vnfd (dict) + """ + for df_id, df in enumerate(vnfd.get("df", {})): + if ( + df.get("lcm-operations-configuration", {}) + .get("operate-vnf-op-config", {}) + .get("day1-2") + ): + day12 = df["lcm-operations-configuration"]["operate-vnf-op-config"].get( + "day1-2" + ) + for config_id, config in enumerate(day12): + for key in [ + "initial-config-primitive", + "config-primitive", + "terminate-config-primitive", + ]: + config.pop(key, None) + day12[config_id] = config + df["lcm-operations-configuration"]["operate-vnf-op-config"][ + "day1-2" + ] = day12 + vnfd["df"][df_id] = df + return vnfd + + def remove_modifiable_items(self, vnfd: dict) -> dict: + """Removes the modifiable parts from the VNFD descriptors + + It calls different extract functions according to different update types + to clear all the modifiable items from VNFD + + Args: + vnfd (dict): Descriptor as a dictionary + + Returns: + vnfd (dict): Descriptor which does not include modifiable contents + """ + if vnfd.get("vnfd"): + vnfd = vnfd["vnfd"] + vnfd.pop("_admin", None) + # If the other extractions need to be done from VNFD, + # the new extract methods could be appended to below list. + for extract_function in [self.extract_day12_primitives, self.extract_policies]: + vnfd_temp = extract_function(vnfd) + vnfd = vnfd_temp + return vnfd + + def _validate_descriptor_changes( + self, + descriptor_file_name: str, + old_descriptor_directory: str, + new_descriptor_directory: str, + ): + """Compares the old and new VNFD descriptors and validates the new descriptor. + + Args: + old_descriptor_directory (str): Directory of descriptor which is in-use + new_descriptor_directory (str): Directory of directory which is proposed to update (new revision) + + Returns: + None + + Raises: + EngineException: In case of error when there are unallowed changes + """ + try: + with self.fs.file_open( + (old_descriptor_directory.rstrip("/"), descriptor_file_name), "r" + ) as old_descriptor_file: + with self.fs.file_open( + (new_descriptor_directory, descriptor_file_name), "r" + ) as new_descriptor_file: + old_content = yaml.load( + old_descriptor_file.read(), Loader=yaml.SafeLoader + ) + new_content = yaml.load( + new_descriptor_file.read(), Loader=yaml.SafeLoader + ) + if old_content and new_content: + if self.find_software_version( + old_content + ) != self.find_software_version(new_content): + return + disallowed_change = DeepDiff( + self.remove_modifiable_items(old_content), + self.remove_modifiable_items(new_content), + ) + if disallowed_change: + changed_nodes = functools.reduce( + lambda a, b: a + " , " + b, + [ + node.lstrip("root") + for node in disallowed_change.get( + "values_changed" + ).keys() + ], + ) + raise EngineException( + f"Error in validating new descriptor: {changed_nodes} cannot be modified, " + "there are disallowed changes in the vnf descriptor.", + http_code=HTTPStatus.UNPROCESSABLE_ENTITY, + ) + except ( + DbException, + AttributeError, + IndexError, + KeyError, + ValueError, + ) as e: + raise type(e)( + "VNF Descriptor could not be processed with error: {}.".format(e) + ) + class NsdTopic(DescriptorTopic): topic = "nsds" @@ -1458,6 +1612,112 @@ class NsdTopic(DescriptorTopic): super().delete_extra(session, _id, db_content, not_send_msg) self.db.del_list(self.topic+"_revisions", { "_id": { "$regex": _id}}) + @staticmethod + def extract_day12_primitives(nsd: dict) -> dict: + """Removes the day12 primitives from the NSD descriptors + + Args: + nsd (dict): Descriptor as a dictionary + + Returns: + nsd (dict): Cleared NSD + """ + if nsd.get("ns-configuration"): + for key in [ + "config-primitive", + "initial-config-primitive", + "terminate-config-primitive", + ]: + nsd["ns-configuration"].pop(key, None) + return nsd + + def remove_modifiable_items(self, nsd: dict) -> dict: + """Removes the modifiable parts from the VNFD descriptors + + It calls different extract functions according to different update types + to clear all the modifiable items from NSD + + Args: + nsd (dict): Descriptor as a dictionary + + Returns: + nsd (dict): Descriptor which does not include modifiable contents + """ + while isinstance(nsd, dict) and nsd.get("nsd"): + nsd = nsd["nsd"] + if isinstance(nsd, list): + nsd = nsd[0] + nsd.pop("_admin", None) + # If the more extractions need to be done from NSD, + # the new extract methods could be appended to below list. + for extract_function in [self.extract_day12_primitives]: + nsd_temp = extract_function(nsd) + nsd = nsd_temp + return nsd + + def _validate_descriptor_changes( + self, + descriptor_file_name: str, + old_descriptor_directory: str, + new_descriptor_directory: str, + ): + """Compares the old and new NSD descriptors and validates the new descriptor + + Args: + old_descriptor_directory: Directory of descriptor which is in-use + new_descriptor_directory: Directory of directory which is proposed to update (new revision) + + Returns: + None + + Raises: + EngineException: In case of error if the changes are not allowed + """ + + try: + with self.fs.file_open( + (old_descriptor_directory, descriptor_file_name), "r" + ) as old_descriptor_file: + with self.fs.file_open( + (new_descriptor_directory.rstrip("/"), descriptor_file_name), "r" + ) as new_descriptor_file: + old_content = yaml.load( + old_descriptor_file.read(), Loader=yaml.SafeLoader + ) + new_content = yaml.load( + new_descriptor_file.read(), Loader=yaml.SafeLoader + ) + if old_content and new_content: + disallowed_change = DeepDiff( + self.remove_modifiable_items(old_content), + self.remove_modifiable_items(new_content), + ) + if disallowed_change: + changed_nodes = functools.reduce( + lambda a, b: a + ", " + b, + [ + node.lstrip("root") + for node in disallowed_change.get( + "values_changed" + ).keys() + ], + ) + raise EngineException( + f"Error in validating new descriptor: {changed_nodes} cannot be modified, " + "there are disallowed changes in the ns descriptor. ", + http_code=HTTPStatus.UNPROCESSABLE_ENTITY, + ) + except ( + DbException, + AttributeError, + IndexError, + KeyError, + ValueError, + ) as e: + raise type(e)( + "NS Descriptor could not be processed with error: {}.".format(e) + ) + def sol005_projection(self, data): data["nsdOnboardingState"] = data["_admin"]["onboardingState"] data["nsdOperationalState"] = data["_admin"]["operationalState"]