X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FNBI.git;a=blobdiff_plain;f=osm_nbi%2Fdescriptor_topics.py;h=df218ccb2b41445b3590761dcd1276b852b01d80;hp=4e056f53035938c9fadffbbfca8f6447e8e7aa03;hb=4ca5152329fcba2f575085df9c0921f6d1135020;hpb=7cbd03c5c21202fff473ae943e75dd49a18f2516 diff --git a/osm_nbi/descriptor_topics.py b/osm_nbi/descriptor_topics.py index 4e056f5..df218cc 100644 --- a/osm_nbi/descriptor_topics.py +++ b/osm_nbi/descriptor_topics.py @@ -17,6 +17,7 @@ import tarfile import yaml import json import importlib +import copy # import logging from hashlib import md5 from osm_common.dbbase import DbException, deep_update_rfc7396 @@ -32,6 +33,7 @@ etsi_nfv_nsd = importlib.import_module("osm_im.etsi-nfv-nsd") from osm_im.nst import nst as nst_im from pyangbind.lib.serialise import pybindJSONDecoder import pyangbind.lib.pybindJSON as pybindJSON +from osm_nbi import utils __author__ = "Alfonso Tierno " @@ -42,7 +44,7 @@ class DescriptorTopic(BaseTopic): BaseTopic.__init__(self, db, fs, msg, auth) def check_conflict_on_edit(self, session, final_content, edit_content, _id): - super().check_conflict_on_edit(session, final_content, edit_content, _id) + final_content = super().check_conflict_on_edit(session, final_content, edit_content, _id) def _check_unique_id_name(descriptor, position=""): for desc_key, desc_item in descriptor.items(): @@ -73,24 +75,29 @@ class DescriptorTopic(BaseTopic): internal_keys[k] = final_content.pop(k) storage_params = internal_keys["_admin"].get("storage") serialized = self._validate_input_new(final_content, storage_params, session["force"]) + # 1.2. modify final_content with a serialized version - final_content.clear() - final_content.update(serialized) + final_content = copy.deepcopy(serialized) # 1.3. restore internal keys for k, v in internal_keys.items(): final_content[k] = v if session["force"]: - return + return final_content + # 2. check that this id is not present if "id" in edit_content: _filter = self._get_project_filter(session) + _filter["id"] = final_content["id"] _filter["_id.neq"] = _id + if self.db.get_one(self.topic, _filter, fail_on_empty=False): raise EngineException("{} with id '{}' already exists for this project".format(self.topic[:-1], final_content["id"]), HTTPStatus.CONFLICT) + return final_content + @staticmethod def format_on_new(content, project_id=None, make_public=False): BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public) @@ -303,7 +310,7 @@ class DescriptorTopic(BaseTopic): self._update_input_with_kwargs(indata, kwargs) deep_update_rfc7396(current_desc, indata) - self.check_conflict_on_edit(session, current_desc, indata, _id=_id) + current_desc = self.check_conflict_on_edit(session, current_desc, indata, _id=_id) current_desc["_admin"]["modified"] = time() self.db.replace(self.topic, _id, current_desc) self.fs.dir_rename(temp_folder, _id) @@ -456,24 +463,25 @@ class VnfdTopic(DescriptorTopic): DescriptorTopic.__init__(self, db, fs, msg, auth) def pyangbind_validation(self, item, data, force=False): + if self._descriptor_data_is_in_old_format(data): + raise EngineException("ERROR: Unsupported descriptor format. Please, use an ETSI SOL006 descriptor.", + http_code=HTTPStatus.UNPROCESSABLE_ENTITY) try: - virtual_compute_descriptors = data.get('virtual-compute-desc') - virtual_storage_descriptors = data.get('virtual-storage-desc') myvnfd = etsi_nfv_vnfd.etsi_nfv_vnfd() pybindJSONDecoder.load_ietf_json({'etsi-nfv-vnfd:vnfd': data}, None, None, obj=myvnfd, path_helper=True, skip_unknown=force) out = pybindJSON.dumps(myvnfd, mode="ietf") desc_out = self._remove_envelop(yaml.safe_load(out)) desc_out = self._remove_yang_prefixes_from_descriptor(desc_out) - if virtual_compute_descriptors: - desc_out['virtual-compute-desc'] = virtual_compute_descriptors - if virtual_storage_descriptors: - desc_out['virtual-storage-desc'] = virtual_storage_descriptors - return desc_out + return utils.deep_update_dict(data, desc_out) except Exception as e: raise EngineException("Error in pyangbind validation: {}".format(str(e)), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + @staticmethod + def _descriptor_data_is_in_old_format(data): + return ('vnfd-catalog' in data) or ('vnfd:vnfd-catalog' in data) + @staticmethod def _remove_envelop(indata=None): if not indata: @@ -492,7 +500,7 @@ class VnfdTopic(DescriptorTopic): return clean_indata def check_conflict_on_edit(self, session, final_content, edit_content, _id): - super().check_conflict_on_edit(session, final_content, edit_content, _id) + final_content = super().check_conflict_on_edit(session, final_content, edit_content, _id) # set type of vnfd contains_pdu = False @@ -507,6 +515,7 @@ class VnfdTopic(DescriptorTopic): elif contains_vdu: final_content["_admin"]["type"] = "vnfd" # if neither vud nor pdu do not fill type + return final_content def check_conflict_on_del(self, session, _id, db_content): """ @@ -530,13 +539,14 @@ class VnfdTopic(DescriptorTopic): # check vnfrs using this vnfd _filter["vnfd-id"] = _id if self.db.get_list("vnfrs", _filter): - raise EngineException("There is at least one VNF using this descriptor", http_code=HTTPStatus.CONFLICT) + raise EngineException("There is at least one VNF instance using this descriptor", + http_code=HTTPStatus.CONFLICT) # check NSD referencing this VNFD del _filter["vnfd-id"] - _filter["constituent-vnfd.ANYINDEX.vnfd-id-ref"] = descriptor_id + _filter["vnfd-id"] = descriptor_id if self.db.get_list("nsds", _filter): - raise EngineException("There is at least one NSD referencing this descriptor", + raise EngineException("There is at least one NS package referencing this descriptor", http_code=HTTPStatus.CONFLICT) def _validate_input_new(self, indata, storage_params, force=False): @@ -552,8 +562,8 @@ class VnfdTopic(DescriptorTopic): for vdu in get_iterable(indata.get("vdu")): self.validate_vdu_internal_connection_points(vdu) - self._validate_vdu_charms_in_package(storage_params, vdu, indata) self._validate_vdu_cloud_init_in_package(storage_params, vdu, indata) + self._validate_vdu_charms_in_package(storage_params, indata) self._validate_vnf_charms_in_package(storage_params, indata) @@ -612,14 +622,20 @@ class VnfdTopic(DescriptorTopic): http_code=HTTPStatus.UNPROCESSABLE_ENTITY) # TODO: Validate k8s-cluster-net points to a valid k8s-cluster:nets ? - def _validate_vdu_charms_in_package(self, storage_params, vdu, indata): - if not vdu.get("vdu-configuration"): - return - for vdu_configuration in get_iterable(indata.get("vdu-configuration")): - if vdu_configuration.get("juju"): - if not self._validate_package_folders(storage_params, 'charms'): - raise EngineException("Charm defined in vnf[id={}] but not present in " - "package".format(indata["id"])) + def _validate_vdu_charms_in_package(self, storage_params, indata): + for df in indata["df"]: + if "lcm-operations-configuration" in df and "operate-vnf-op-config" in df["lcm-operations-configuration"]: + configs = df["lcm-operations-configuration"]["operate-vnf-op-config"].get("day1-2", []) + vdus = df.get("vdu-profile", []) + for vdu in vdus: + for config in configs: + if config["id"] == vdu["id"] and utils.find_in_list( + config.get("execution-environment-list", []), + lambda ee: "juju" in ee + ): + if not self._validate_package_folders(storage_params, 'charms'): + raise EngineException("Charm defined in vnf[id={}] but not present in " + "package".format(indata["id"])) def _validate_vdu_cloud_init_in_package(self, storage_params, vdu, indata): if not vdu.get("cloud-init-file"): @@ -629,13 +645,21 @@ class VnfdTopic(DescriptorTopic): "package".format(indata["id"], vdu["id"])) def _validate_vnf_charms_in_package(self, storage_params, indata): - if not indata.get("vnf-configuration"): - return - for vnf_configuration in get_iterable(indata.get("vnf-configuration")): - if vnf_configuration.get("juju"): - if not self._validate_package_folders(storage_params, 'charms'): - raise EngineException("Charm defined in vnf[id={}] but not present in " - "package".format(indata["id"])) + # Get VNF configuration through new container + for deployment_flavor in indata.get('df', []): + if "lcm-operations-configuration" not in deployment_flavor: + return + if "operate-vnf-op-config" not in deployment_flavor["lcm-operations-configuration"]: + return + for day_1_2_config in deployment_flavor["lcm-operations-configuration"]["operate-vnf-op-config"]["day1-2"]: + if day_1_2_config["id"] == indata["id"]: + if utils.find_in_list( + day_1_2_config.get("execution-environment-list", []), + lambda ee: "juju" in ee + ): + if not self._validate_package_folders(storage_params, 'charms'): + raise EngineException("Charm defined in vnf[id={}] but not present in " + "package".format(indata["id"])) def _validate_package_folders(self, storage_params, folder, file=None): if not storage_params or not storage_params.get("pkg-dir"): @@ -746,19 +770,25 @@ class VnfdTopic(DescriptorTopic): http_code=HTTPStatus.UNPROCESSABLE_ENTITY) for sca in get_iterable(sa.get("scaling-config-action")): - if not indata.get("vnf-configuration"): - raise EngineException("'vnf-configuration' not defined in the descriptor but it is referenced " - "by df[id='{}']:scaling-aspect[id='{}']:scaling-config-action" + if "lcm-operations-configuration" not in df \ + or "operate-vnf-op-config" not in df["lcm-operations-configuration"] \ + or not utils.find_in_list( + df["lcm-operations-configuration"]["operate-vnf-op-config"].get("day1-2", []), + lambda config: config["id"] == indata["id"]): + raise EngineException("'day1-2 configuration' not defined in the descriptor but it is " + "referenced by df[id='{}']:scaling-aspect[id='{}']:scaling-config-action" .format(df["id"], sa["id"]), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) - for configuration in get_iterable(indata["vnf-configuration"]): + for configuration in get_iterable( + df["lcm-operations-configuration"]["operate-vnf-op-config"].get("day1-2", []) + ): for primitive in get_iterable(configuration.get("config-primitive")): if primitive["name"] == sca["vnf-config-primitive-name-ref"]: break else: raise EngineException("df[id='{}']:scaling-aspect[id='{}']:scaling-config-action:vnf-" "config-primitive-name-ref='{}' does not match any " - "vnf-configuration:config-primitive:name" + "day1-2 configuration:config-primitive:name" .format(df["id"], sa["id"], sca["vnf-config-primitive-name-ref"]), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) @@ -797,18 +827,28 @@ class NsdTopic(DescriptorTopic): DescriptorTopic.__init__(self, db, fs, msg, auth) def pyangbind_validation(self, item, data, force=False): + if self._descriptor_data_is_in_old_format(data): + raise EngineException("ERROR: Unsupported descriptor format. Please, use an ETSI SOL006 descriptor.", + http_code=HTTPStatus.UNPROCESSABLE_ENTITY) try: + nsd_vnf_profiles = data.get('df', [{}])[0].get('vnf-profile', []) mynsd = etsi_nfv_nsd.etsi_nfv_nsd() pybindJSONDecoder.load_ietf_json({'nsd': {'nsd': [data]}}, None, None, obj=mynsd, path_helper=True, skip_unknown=force) out = pybindJSON.dumps(mynsd, mode="ietf") desc_out = self._remove_envelop(yaml.safe_load(out)) desc_out = self._remove_yang_prefixes_from_descriptor(desc_out) + if nsd_vnf_profiles: + desc_out['df'][0]['vnf-profile'] = nsd_vnf_profiles return desc_out except Exception as e: raise EngineException("Error in pyangbind validation: {}".format(str(e)), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + @staticmethod + def _descriptor_data_is_in_old_format(data): + return ('nsd-catalog' in data) or ('nsd:nsd-catalog' in data) + @staticmethod def _remove_envelop(indata=None): if not indata: @@ -954,10 +994,12 @@ class NsdTopic(DescriptorTopic): http_code=HTTPStatus.UNPROCESSABLE_ENTITY) def check_conflict_on_edit(self, session, final_content, edit_content, _id): - super().check_conflict_on_edit(session, final_content, edit_content, _id) + final_content = super().check_conflict_on_edit(session, final_content, edit_content, _id) self._check_descriptor_dependencies(session, final_content) + return final_content + def check_conflict_on_del(self, session, _id, db_content): """ Check that there is not any NSR that uses this NSD. Only NSRs belonging to this project are considered. Note @@ -978,7 +1020,8 @@ class NsdTopic(DescriptorTopic): _filter = self._get_project_filter(session) _filter["nsd-id"] = _id if self.db.get_list("nsrs", _filter): - raise EngineException("There is at least one NS using this descriptor", http_code=HTTPStatus.CONFLICT) + raise EngineException("There is at least one NS instance using this descriptor", + http_code=HTTPStatus.CONFLICT) # check NSD referenced by NST del _filter["nsd-id"] @@ -1061,9 +1104,10 @@ class NstTopic(DescriptorTopic): "existing nsd".format(nsd_id), http_code=HTTPStatus.CONFLICT) def check_conflict_on_edit(self, session, final_content, edit_content, _id): - super().check_conflict_on_edit(session, final_content, edit_content, _id) + final_content = super().check_conflict_on_edit(session, final_content, edit_content, _id) self._check_descriptor_dependencies(session, final_content) + return final_content def check_conflict_on_del(self, session, _id, db_content): """ @@ -1128,7 +1172,7 @@ class PduTopic(BaseTopic): _filter = self._get_project_filter(session) _filter["vdur.pdu-id"] = _id if self.db.get_list("vnfrs", _filter): - raise EngineException("There is at least one VNF using this PDU", http_code=HTTPStatus.CONFLICT) + raise EngineException("There is at least one VNF instance using this PDU", http_code=HTTPStatus.CONFLICT) class VnfPkgOpTopic(BaseTopic):