X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FNBI.git;a=blobdiff_plain;f=osm_nbi%2Fdescriptor_topics.py;h=3bdaa03381bec85df94ddfe3d508776209790ff5;hp=fdf46e5109436d8bbb77f3ff36f1ed801e66040c;hb=960531ab7417bf6705399dae790899463a659da9;hpb=75d5a4e66239b1f9aa7f9c201c0588cc78463ffc diff --git a/osm_nbi/descriptor_topics.py b/osm_nbi/descriptor_topics.py index fdf46e5..3bdaa03 100644 --- a/osm_nbi/descriptor_topics.py +++ b/osm_nbi/descriptor_topics.py @@ -62,6 +62,7 @@ class DescriptorTopic(BaseTopic): .format(desc_item_id, list_item[desc_item_id], position), HTTPStatus.UNPROCESSABLE_ENTITY) used_ids.append(list_item[desc_item_id]) + _check_unique_id_name(final_content) # 1. validate again with pyangbind # 1.1. remove internal keys @@ -146,29 +147,27 @@ class DescriptorTopic(BaseTopic): :return: _id, None: identity of the inserted data; and None as there is not any operation """ - try: - # Check Quota - self.check_quota(session) + # No needed to capture exceptions + # Check Quota + self.check_quota(session) - # _remove_envelop - if indata: - if "userDefinedData" in indata: - indata = indata['userDefinedData'] + # _remove_envelop + if indata: + if "userDefinedData" in indata: + indata = indata['userDefinedData'] - # Override descriptor with query string kwargs - self._update_input_with_kwargs(indata, kwargs) - # uncomment when this method is implemented. - # Avoid override in this case as the target is userDefinedData, but not vnfd,nsd descriptors - # indata = DescriptorTopic._validate_input_new(self, indata, project_id=session["force"]) - - content = {"_admin": {"userDefinedData": indata}} - self.format_on_new(content, session["project_id"], make_public=session["public"]) - _id = self.db.create(self.topic, content) - rollback.append({"topic": self.topic, "_id": _id}) - self._send_msg("created", {"_id": _id}) - return _id, None - except ValidationError as e: - raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY) + # Override descriptor with query string kwargs + self._update_input_with_kwargs(indata, kwargs) + # uncomment when this method is implemented. + # Avoid override in this case as the target is userDefinedData, but not vnfd,nsd descriptors + # indata = DescriptorTopic._validate_input_new(self, indata, project_id=session["force"]) + + content = {"_admin": {"userDefinedData": indata}} + self.format_on_new(content, session["project_id"], make_public=session["public"]) + _id = self.db.create(self.topic, content) + rollback.append({"topic": self.topic, "_id": _id}) + self._send_msg("created", {"_id": _id}) + return _id, None def upload_content(self, session, _id, indata, kwargs, headers): """ @@ -240,9 +239,9 @@ class DescriptorTopic(BaseTopic): break file_pkg.write(indata_text) if content_range_text: - if indata_len != end-start: + if indata_len != end - start: raise EngineException("Mismatch between Content-Range header {}-{} and body length of {}".format( - start, end-1, indata_len), HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE) + start, end - 1, indata_len), HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE) if end != total: # TODO update to UPLOADING return False @@ -363,7 +362,7 @@ class DescriptorTopic(BaseTopic): "onboardingState is {}".format(content["_admin"]["onboardingState"]), http_code=HTTPStatus.CONFLICT) storage = content["_admin"]["storage"] - if path is not None and path != "$DESCRIPTOR": # artifacts + if path is not None and path != "$DESCRIPTOR": # artifacts if not storage.get('pkg-dir'): raise EngineException("Packages does not contains artifacts", http_code=HTTPStatus.BAD_REQUEST) if self.fs.file_exists((storage['folder'], storage['pkg-dir'], *path), 'dir'): @@ -371,7 +370,7 @@ class DescriptorTopic(BaseTopic): return folder_content, "text/plain" # TODO manage folders in http else: - return self.fs.file_open((storage['folder'], storage['pkg-dir'], *path), "rb"),\ + return self.fs.file_open((storage['folder'], storage['pkg-dir'], *path), "rb"), \ "application/octet-stream" # pkgtype accept ZIP TEXT -> result @@ -379,10 +378,15 @@ class DescriptorTopic(BaseTopic): # no yes -> error # onefile yes no -> zip # X yes -> text - - if accept_text and (not storage.get('pkg-dir') or path == "$DESCRIPTOR"): + contain_many_files = False + if storage.get('pkg-dir'): + # check if there are more than one file in the package, ignoring checksums.txt. + pkg_files = self.fs.dir_ls((storage['folder'], storage['pkg-dir'])) + if len(pkg_files) >= 3 or (len(pkg_files) == 2 and 'checksums.txt' not in pkg_files): + contain_many_files = True + if accept_text and (not contain_many_files or path == "$DESCRIPTOR"): return self.fs.file_open((storage['folder'], storage['descriptor']), "r"), "text/plain" - elif storage.get('pkg-dir') and not accept_zip: + elif contain_many_files and not accept_zip: raise EngineException("Packages that contains several files need to be retrieved with 'application/zip'" "Accept header", http_code=HTTPStatus.NOT_ACCEPTABLE) else: @@ -420,6 +424,39 @@ class DescriptorTopic(BaseTopic): raise EngineException("Error in pyangbind validation: {}".format(str(e)), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + def _validate_input_edit(self, indata, content, force=False): + # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit + if "_id" in indata: + indata.pop("_id") + if "_admin" not in indata: + indata["_admin"] = {} + + if "operationalState" in indata: + if indata["operationalState"] in ("ENABLED", "DISABLED"): + indata["_admin"]["operationalState"] = indata.pop("operationalState") + else: + raise EngineException("State '{}' is not a valid operational state" + .format(indata["operationalState"]), + http_code=HTTPStatus.BAD_REQUEST) + + # In the case of user defined data, we need to put the data in the root of the object + # to preserve current expected behaviour + if "userDefinedData" in indata: + data = indata.pop("userDefinedData") + if type(data) == dict: + indata["_admin"]["userDefinedData"] = data + else: + raise EngineException("userDefinedData should be an object, but is '{}' instead" + .format(type(data)), + http_code=HTTPStatus.BAD_REQUEST) + + if ("operationalState" in indata["_admin"] and + content["_admin"]["operationalState"] == indata["_admin"]["operationalState"]): + raise EngineException("operationalState already {}".format(content["_admin"]["operationalState"]), + http_code=HTTPStatus.CONFLICT) + + return indata + class VnfdTopic(DescriptorTopic): topic = "vnfds" @@ -496,88 +533,136 @@ class VnfdTopic(DescriptorTopic): http_code=HTTPStatus.CONFLICT) def _validate_input_new(self, indata, storage_params, force=False): + indata.pop("onboardingState", None) + indata.pop("operationalState", None) + indata.pop("usageState", None) + + indata.pop("links", None) + indata = self.pyangbind_validation("vnfds", indata, force) # Cross references validation in the descriptor - if indata.get("vdu"): - if not indata.get("mgmt-interface"): - raise EngineException("'mgmt-interface' is a mandatory field and it is not defined", + self.validate_mgmt_interfaces_connection_points(indata) + + for vdu in get_iterable(indata.get("vdu")): + self.validate_vdu_connection_point_refs(vdu, indata) + self._validate_vdu_charms_in_package(storage_params, vdu, indata) + self._validate_vdu_cloud_init_in_package(storage_params, vdu, indata) + + self._validate_vnf_charms_in_package(storage_params, indata) + + self.validate_internal_vlds(indata) + self.validate_monitoring_params(indata) + self.validate_scaling_group_descriptor(indata) + + return indata + + @staticmethod + def validate_mgmt_interfaces_connection_points(indata): + if not indata.get("vdu"): + return + if not indata.get("mgmt-interface"): + raise EngineException("'mgmt-interface' is a mandatory field and it is not defined", + http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + if indata["mgmt-interface"].get("cp"): + for cp in get_iterable(indata.get("connection-point")): + if cp["name"] == indata["mgmt-interface"]["cp"]: + break + else: + raise EngineException("mgmt-interface:cp='{}' must match an existing connection-point" + .format(indata["mgmt-interface"]["cp"]), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) - if indata["mgmt-interface"].get("cp"): + + @staticmethod + def validate_vdu_connection_point_refs(vdu, indata): + icp_refs = [] + ecp_refs = [] + for interface in get_iterable(vdu.get("interface")): + if interface.get("external-connection-point-ref"): + if interface.get("external-connection-point-ref") in ecp_refs: + raise EngineException("vdu[id='{}']:interface[name='{}']:external-connection-point-ref='{}' " + "is referenced by other interface" + .format(vdu["id"], interface["name"], + interface["external-connection-point-ref"]), + http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + ecp_refs.append(interface.get("external-connection-point-ref")) for cp in get_iterable(indata.get("connection-point")): - if cp["name"] == indata["mgmt-interface"]["cp"]: + if cp["name"] == interface["external-connection-point-ref"]: break else: - raise EngineException("mgmt-interface:cp='{}' must match an existing connection-point" - .format(indata["mgmt-interface"]["cp"]), + raise EngineException("vdu[id='{}']:interface[name='{}']:external-connection-point-ref='{}' " + "must match an existing connection-point" + .format(vdu["id"], interface["name"], + interface["external-connection-point-ref"]), + http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + elif interface.get("internal-connection-point-ref"): + if interface.get("internal-connection-point-ref") in icp_refs: + raise EngineException("vdu[id='{}']:interface[name='{}']:internal-connection-point-ref='{}' " + "is referenced by other interface" + .format(vdu["id"], interface["name"], + interface["internal-connection-point-ref"]), + http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + icp_refs.append(interface.get("internal-connection-point-ref")) + for internal_cp in get_iterable(vdu.get("internal-connection-point")): + if interface["internal-connection-point-ref"] == internal_cp.get("id"): + break + else: + raise EngineException("vdu[id='{}']:interface[name='{}']:internal-connection-point-ref='{}' " + "must match an existing vdu:internal-connection-point" + .format(vdu["id"], interface["name"], + interface["internal-connection-point-ref"]), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) - for vdu in get_iterable(indata.get("vdu")): - icp_refs = [] - ecp_refs = [] - for interface in get_iterable(vdu.get("interface")): - if interface.get("external-connection-point-ref"): - if interface.get("external-connection-point-ref") in ecp_refs: - raise EngineException("vdu[id='{}']:interface[name='{}']:external-connection-point-ref='{}' " - "is referenced by other interface" - .format(vdu["id"], interface["name"], - interface["external-connection-point-ref"]), - http_code=HTTPStatus.UNPROCESSABLE_ENTITY) - ecp_refs.append(interface.get("external-connection-point-ref")) - for cp in get_iterable(indata.get("connection-point")): - if cp["name"] == interface["external-connection-point-ref"]: - break - else: - raise EngineException("vdu[id='{}']:interface[name='{}']:external-connection-point-ref='{}' " - "must match an existing connection-point" - .format(vdu["id"], interface["name"], - interface["external-connection-point-ref"]), - http_code=HTTPStatus.UNPROCESSABLE_ENTITY) - elif interface.get("internal-connection-point-ref"): - if interface.get("internal-connection-point-ref") in icp_refs: - raise EngineException("vdu[id='{}']:interface[name='{}']:internal-connection-point-ref='{}' " - "is referenced by other interface" - .format(vdu["id"], interface["name"], - interface["internal-connection-point-ref"]), - http_code=HTTPStatus.UNPROCESSABLE_ENTITY) - icp_refs.append(interface.get("internal-connection-point-ref")) - for internal_cp in get_iterable(vdu.get("internal-connection-point")): - if interface["internal-connection-point-ref"] == internal_cp.get("id"): - break - else: - raise EngineException("vdu[id='{}']:interface[name='{}']:internal-connection-point-ref='{}' " - "must match an existing vdu:internal-connection-point" - .format(vdu["id"], interface["name"], - interface["internal-connection-point-ref"]), - http_code=HTTPStatus.UNPROCESSABLE_ENTITY) - # Validate that if descriptor contains charms, artifacts _admin.storage."pkg-dir" is not none - if vdu.get("vdu-configuration"): - if vdu["vdu-configuration"].get("juju"): - if not self._validate_package_folders(storage_params, 'charms'): - raise EngineException("Charm defined in vnf[id={}]:vdu[id={}] but not present in " - "package".format(indata["id"], vdu["id"])) - # Validate that if descriptor contains cloud-init, artifacts _admin.storage."pkg-dir" is not none - if vdu.get("cloud-init-file"): - if not self._validate_package_folders(storage_params, 'cloud_init', vdu["cloud-init-file"]): - raise EngineException("Cloud-init defined in vnf[id={}]:vdu[id={}] but not present in " - "package".format(indata["id"], vdu["id"])) - # Validate that if descriptor contains charms, artifacts _admin.storage."pkg-dir" is not none - if indata.get("vnf-configuration"): - if indata["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"])) + def _validate_vdu_charms_in_package(self, storage_params, vdu, indata): + if not vdu.get("vdu-configuration"): + return + if vdu["vdu-configuration"].get("juju"): + if not self._validate_package_folders(storage_params, 'charms'): + raise EngineException("Charm defined in vnf[id={}]:vdu[id={}] but not present in " + "package".format(indata["id"], vdu["id"])) + + def _validate_vdu_cloud_init_in_package(self, storage_params, vdu, indata): + if not vdu.get("cloud-init-file"): + return + if not self._validate_package_folders(storage_params, 'cloud_init', vdu["cloud-init-file"]): + raise EngineException("Cloud-init defined in vnf[id={}]:vdu[id={}] but not present in " + "package".format(indata["id"], vdu["id"])) + + def _validate_vnf_charms_in_package(self, storage_params, indata): + if not indata.get("vnf-configuration"): + return + if indata["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"])) + + def _validate_package_folders(self, storage_params, folder, file=None): + if not storage_params or not storage_params.get("pkg-dir"): + return False + else: + if self.fs.file_exists("{}_".format(storage_params["folder"]), 'dir'): + f = "{}_/{}/{}".format(storage_params["folder"], storage_params["pkg-dir"], folder) + else: + f = "{}/{}/{}".format(storage_params["folder"], storage_params["pkg-dir"], folder) + if file: + return self.fs.file_exists("{}/{}".format(f, file), 'file') + else: + if self.fs.file_exists(f, 'dir'): + if self.fs.dir_ls(f): + return True + return False + + @staticmethod + def validate_internal_vlds(indata): vld_names = [] # For detection of duplicated VLD names for ivld in get_iterable(indata.get("internal-vld")): - # BEGIN Detection of duplicated VLD names ivld_name = ivld.get("name") - if ivld_name: - if ivld_name in vld_names: - raise EngineException("Duplicated VLD name '{}' in vnfd[id={}]:internal-vld[id={}]" - .format(ivld["name"], indata["id"], ivld["id"]), - http_code=HTTPStatus.UNPROCESSABLE_ENTITY) - else: - vld_names.append(ivld_name) - # END Detection of duplicated VLD names + if ivld_name and ivld_name in vld_names: + raise EngineException("Duplicated VLD name '{}' in vnfd[id={}]:internal-vld[id={}]" + .format(ivld["name"], indata["id"], ivld["id"]), + http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + else: + vld_names.append(ivld_name) + for icp in get_iterable(ivld.get("internal-connection-point")): icp_mark = False for vdu in get_iterable(indata.get("vdu")): @@ -599,12 +684,15 @@ class VnfdTopic(DescriptorTopic): raise EngineException("internal-vld[id='{}']:ip-profile-ref='{}' does not exist".format( ivld["id"], ivld["ip-profile-ref"]), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + + @staticmethod + def validate_monitoring_params(indata): for mp in get_iterable(indata.get("monitoring-param")): if mp.get("vdu-monitoring-param"): mp_vmp_mark = False for vdu in get_iterable(indata.get("vdu")): for vmp in get_iterable(vdu.get("monitoring-param")): - if vmp["id"] == mp["vdu-monitoring-param"].get("vdu-monitoring-param-ref") and vdu["id"] ==\ + if vmp["id"] == mp["vdu-monitoring-param"].get("vdu-monitoring-param-ref") and vdu["id"] == \ mp["vdu-monitoring-param"]["vdu-ref"]: mp_vmp_mark = True break @@ -634,6 +722,8 @@ class VnfdTopic(DescriptorTopic): mp["vdu-metric"]["vdu-ref"]), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + @staticmethod + def validate_scaling_group_descriptor(indata): for sgd in get_iterable(indata.get("scaling-group-descriptor")): for sp in get_iterable(sgd.get("scaling-policy")): for sc in get_iterable(sp.get("scaling-criteria")): @@ -672,27 +762,6 @@ class VnfdTopic(DescriptorTopic): "vnf-configuration:config-primitive:name" .format(sgd["name"], sca["vnf-config-primitive-name-ref"]), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) - return indata - - def _validate_input_edit(self, indata, force=False): - # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit - return indata - - def _validate_package_folders(self, storage_params, folder, file=None): - if not storage_params or not storage_params.get("pkg-dir"): - return False - else: - if self.fs.file_exists("{}_".format(storage_params["folder"]), 'dir'): - f = "{}_/{}/{}".format(storage_params["folder"], storage_params["pkg-dir"], folder) - else: - f = "{}/{}/{}".format(storage_params["folder"], storage_params["pkg-dir"], folder) - if file: - return self.fs.file_exists("{}/{}".format(f, file), 'file') - else: - if self.fs.file_exists(f, 'dir'): - if self.fs.dir_ls(f): - return True - return False def delete_extra(self, session, _id, db_content, not_send_msg=None): """ @@ -707,6 +776,19 @@ class VnfdTopic(DescriptorTopic): super().delete_extra(session, _id, db_content, not_send_msg) self.db.del_list("vnfpkgops", {"vnfPkgId": _id}) + def sol005_projection(self, data): + data["onboardingState"] = data["_admin"]["onboardingState"] + data["operationalState"] = data["_admin"]["operationalState"] + data["usageState"] = data["_admin"]["usageState"] + + links = {} + links["self"] = {"href": "/vnfpkgm/v1/vnf_packages/{}".format(data["_id"])} + links["vnfd"] = {"href": "/vnfpkgm/v1/vnf_packages/{}/vnfd".format(data["_id"])} + links["packageContent"] = {"href": "/vnfpkgm/v1/vnf_packages/{}/package_content".format(data["_id"])} + data["_links"] = links + + return super().sol005_projection(data) + class NsdTopic(DescriptorTopic): topic = "nsds" @@ -736,47 +818,99 @@ class NsdTopic(DescriptorTopic): return clean_indata def _validate_input_new(self, indata, storage_params, force=False): + indata.pop("nsdOnboardingState", None) + indata.pop("nsdOperationalState", None) + indata.pop("nsdUsageState", None) + + indata.pop("links", None) + indata = self.pyangbind_validation("nsds", indata, force) # Cross references validation in the descriptor # TODO validata that if contains cloud-init-file or charms, have artifacts _admin.storage."pkg-dir" is not none for vld in get_iterable(indata.get("vld")): - if vld.get("mgmt-network") and vld.get("ip-profile-ref"): - raise EngineException("Error at vld[id='{}']:ip-profile-ref" - " You cannot set an ip-profile when mgmt-network is True" - .format(vld["id"]), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) - for vnfd_cp in get_iterable(vld.get("vnfd-connection-point-ref")): - for constituent_vnfd in get_iterable(indata.get("constituent-vnfd")): - if vnfd_cp["member-vnf-index-ref"] == constituent_vnfd["member-vnf-index"]: - if vnfd_cp.get("vnfd-id-ref") and vnfd_cp["vnfd-id-ref"] != constituent_vnfd["vnfd-id-ref"]: - raise EngineException("Error at vld[id='{}']:vnfd-connection-point-ref[vnfd-id-ref='{}'] " - "does not match constituent-vnfd[member-vnf-index='{}']:vnfd-id-ref" - " '{}'".format(vld["id"], vnfd_cp["vnfd-id-ref"], - constituent_vnfd["member-vnf-index"], - constituent_vnfd["vnfd-id-ref"]), - http_code=HTTPStatus.UNPROCESSABLE_ENTITY) - break - else: - raise EngineException("Error at vld[id='{}']:vnfd-connection-point-ref[member-vnf-index-ref='{}'] " - "does not match any constituent-vnfd:member-vnf-index" - .format(vld["id"], vnfd_cp["member-vnf-index-ref"]), - http_code=HTTPStatus.UNPROCESSABLE_ENTITY) - # Check VNFFGD + self.validate_vld_mgmt_network_with_ip_profile_ref(vld) + self.validate_vld_connection_point_refs(vld, indata) + for fgd in get_iterable(indata.get("vnffgd")): - for cls in get_iterable(fgd.get("classifier")): - rspref = cls.get("rsp-id-ref") - for rsp in get_iterable(fgd.get("rsp")): - rspid = rsp.get("id") - if rspid and rspref and rspid == rspref: - break - else: - raise EngineException( - "Error at vnffgd[id='{}']:classifier[id='{}']:rsp-id-ref '{}' does not match any rsp:id" - .format(fgd["id"], cls["id"], rspref), - http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + self.validate_fgd_classifiers(fgd) + return indata - def _validate_input_edit(self, indata, force=False): + @staticmethod + def validate_vld_mgmt_network_with_ip_profile_ref(vld): + if vld.get("mgmt-network") and vld.get("ip-profile-ref"): + raise EngineException("Error at vld[id='{}']:ip-profile-ref" + " You cannot set an ip-profile when mgmt-network is True" + .format(vld["id"]), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + + @staticmethod + def validate_vld_connection_point_refs(vld, indata): + for vnfd_cp in get_iterable(vld.get("vnfd-connection-point-ref")): + for constituent_vnfd in get_iterable(indata.get("constituent-vnfd")): + if vnfd_cp["member-vnf-index-ref"] == constituent_vnfd["member-vnf-index"]: + if vnfd_cp.get("vnfd-id-ref") and vnfd_cp["vnfd-id-ref"] != constituent_vnfd["vnfd-id-ref"]: + raise EngineException("Error at vld[id='{}']:vnfd-connection-point-ref[vnfd-id-ref='{}'] " + "does not match constituent-vnfd[member-vnf-index='{}']:vnfd-id-ref" + " '{}'".format(vld["id"], vnfd_cp["vnfd-id-ref"], + constituent_vnfd["member-vnf-index"], + constituent_vnfd["vnfd-id-ref"]), + http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + break + else: + raise EngineException("Error at vld[id='{}']:vnfd-connection-point-ref[member-vnf-index-ref='{}'] " + "does not match any constituent-vnfd:member-vnf-index" + .format(vld["id"], vnfd_cp["member-vnf-index-ref"]), + http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + + @staticmethod + def validate_fgd_classifiers(fgd): + for cls in get_iterable(fgd.get("classifier")): + rspref = cls.get("rsp-id-ref") + for rsp in get_iterable(fgd.get("rsp")): + rspid = rsp.get("id") + if rspid and rspref and rspid == rspref: + break + else: + raise EngineException( + "Error at vnffgd[id='{}']:classifier[id='{}']:rsp-id-ref '{}' does not match any rsp:id" + .format(fgd["id"], cls["id"], rspref), + http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + + def _validate_input_edit(self, indata, content, force=False): # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit + """ + indata looks as follows: + - In the new case (conformant) + {'nsdOperationalState': 'DISABLED', 'userDefinedData': {'id': 'string23', + '_id': 'c6ddc544-cede-4b94-9ebe-be07b298a3c1', 'name': 'simon46'}} + - In the old case (backwards-compatible) + {'id': 'string23', '_id': 'c6ddc544-cede-4b94-9ebe-be07b298a3c1', 'name': 'simon46'} + """ + if "_admin" not in indata: + indata["_admin"] = {} + + if "nsdOperationalState" in indata: + if indata["nsdOperationalState"] in ("ENABLED", "DISABLED"): + indata["_admin"]["operationalState"] = indata.pop("nsdOperationalState") + else: + raise EngineException("State '{}' is not a valid operational state" + .format(indata["nsdOperationalState"]), + http_code=HTTPStatus.BAD_REQUEST) + + # In the case of user defined data, we need to put the data in the root of the object + # to preserve current expected behaviour + if "userDefinedData" in indata: + data = indata.pop("userDefinedData") + if type(data) == dict: + indata["_admin"]["userDefinedData"] = data + else: + raise EngineException("userDefinedData should be an object, but is '{}' instead" + .format(type(data)), + http_code=HTTPStatus.BAD_REQUEST) + if ("operationalState" in indata["_admin"] and + content["_admin"]["operationalState"] == indata["_admin"]["operationalState"]): + raise EngineException("nsdOperationalState already {}".format(content["_admin"]["operationalState"]), + http_code=HTTPStatus.CONFLICT) return indata def _check_descriptor_dependencies(self, session, descriptor): @@ -789,36 +923,42 @@ class NsdTopic(DescriptorTopic): """ if session["force"]: return + member_vnfd_index = self._get_descriptor_constituent_vnfds_by_member_vnfd_index(session, descriptor) + + # Cross references validation in the descriptor and vnfd connection point validation + for vld in get_iterable(descriptor.get("vld")): + self.validate_vld_connection_point_refs_vnfd_connection_points(vld, member_vnfd_index) + + def _get_descriptor_constituent_vnfds_by_member_vnfd_index(self, session, descriptor): member_vnfd_index = {} if descriptor.get("constituent-vnfd") and not session["force"]: for vnf in descriptor["constituent-vnfd"]: vnfd_id = vnf["vnfd-id-ref"] - filter_q = self._get_project_filter(session) - filter_q["id"] = vnfd_id - vnf_list = self.db.get_list("vnfds", filter_q) + query_filter = self._get_project_filter(session) + query_filter["id"] = vnfd_id + vnf_list = self.db.get_list("vnfds", query_filter) if not vnf_list: raise EngineException("Descriptor error at 'constituent-vnfd':'vnfd-id-ref'='{}' references a non " "existing vnfd".format(vnfd_id), http_code=HTTPStatus.CONFLICT) - # elif len(vnf_list) > 1: - # raise EngineException("More than one vnfd found for id='{}'".format(vnfd_id), - # http_code=HTTPStatus.CONFLICT) + member_vnfd_index[vnf["member-vnf-index"]] = vnf_list[0] + return member_vnfd_index - # Cross references validation in the descriptor and vnfd connection point validation - for vld in get_iterable(descriptor.get("vld")): - for referenced_vnfd_cp in get_iterable(vld.get("vnfd-connection-point-ref")): - # look if this vnfd contains this connection point - vnfd = member_vnfd_index.get(referenced_vnfd_cp["member-vnf-index-ref"]) - for vnfd_cp in get_iterable(vnfd.get("connection-point")): - if referenced_vnfd_cp.get("vnfd-connection-point-ref") == vnfd_cp["name"]: - break - else: - raise EngineException( - "Error at vld[id='{}']:vnfd-connection-point-ref[member-vnf-index-ref='{}']:vnfd-" - "connection-point-ref='{}' references a non existing conection-point:name inside vnfd '{}'" - .format(vld["id"], referenced_vnfd_cp["member-vnf-index-ref"], - referenced_vnfd_cp["vnfd-connection-point-ref"], vnfd["id"]), - http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + @staticmethod + def validate_vld_connection_point_refs_vnfd_connection_points(vld, member_vnfd_index): + for referenced_vnfd_cp in get_iterable(vld.get("vnfd-connection-point-ref")): + # look if this vnfd contains this connection point + vnfd = member_vnfd_index.get(referenced_vnfd_cp["member-vnf-index-ref"]) + for vnfd_cp in get_iterable(vnfd.get("connection-point")): + if referenced_vnfd_cp.get("vnfd-connection-point-ref") == vnfd_cp["name"]: + break + else: + raise EngineException( + "Error at vld[id='{}']:vnfd-connection-point-ref[member-vnf-index-ref='{}']:vnfd-" + "connection-point-ref='{}' references a non existing conection-point:name inside vnfd '{}'" + .format(vld["id"], referenced_vnfd_cp["member-vnf-index-ref"], + referenced_vnfd_cp["vnfd-connection-point-ref"], vnfd["id"]), + 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) @@ -854,10 +994,23 @@ class NsdTopic(DescriptorTopic): raise EngineException("There is at least one NetSlice Template referencing this descriptor", http_code=HTTPStatus.CONFLICT) + def sol005_projection(self, data): + data["nsdOnboardingState"] = data["_admin"]["onboardingState"] + data["nsdOperationalState"] = data["_admin"]["operationalState"] + data["nsdUsageState"] = data["_admin"]["usageState"] + + links = {} + links["self"] = {"href": "/nsd/v1/ns_descriptors/{}".format(data["_id"])} + links["nsd_content"] = {"href": "/nsd/v1/ns_descriptors/{}/nsd_content".format(data["_id"])} + data["_links"] = links + + return super().sol005_projection(data) + class NstTopic(DescriptorTopic): topic = "nsts" topic_msg = "nst" + quota_name = "slice_templates" def __init__(self, db, fs, msg, auth): DescriptorTopic.__init__(self, db, fs, msg, auth) @@ -878,11 +1031,10 @@ class NstTopic(DescriptorTopic): clean_indata = clean_indata['nst:nst'][0] return clean_indata - def _validate_input_edit(self, indata, force=False): - # TODO validate with pyangbind, serialize - return indata - def _validate_input_new(self, indata, storage_params, force=False): + indata.pop("onboardingState", None) + indata.pop("operationalState", None) + indata.pop("usageState", None) indata = self.pyangbind_validation("nsts", indata, force) return indata.copy() @@ -927,10 +1079,23 @@ class NstTopic(DescriptorTopic): raise EngineException("there is at least one Netslice Instance using this descriptor", http_code=HTTPStatus.CONFLICT) + def sol005_projection(self, data): + data["onboardingState"] = data["_admin"]["onboardingState"] + data["operationalState"] = data["_admin"]["operationalState"] + data["usageState"] = data["_admin"]["usageState"] + + links = {} + links["self"] = {"href": "/nst/v1/netslice_templates/{}".format(data["_id"])} + links["nst"] = {"href": "/nst/v1/netslice_templates/{}/nst".format(data["_id"])} + data["_links"] = links + + return super().sol005_projection(data) + class PduTopic(BaseTopic): topic = "pdus" topic_msg = "pdu" + quota_name = "pduds" schema_new = pdu_new_schema schema_edit = pdu_edit_schema