X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FNBI.git;a=blobdiff_plain;f=osm_nbi%2Fdescriptor_topics.py;h=4e056f53035938c9fadffbbfca8f6447e8e7aa03;hp=3bdaa03381bec85df94ddfe3d508776209790ff5;hb=7cbd03c5c21202fff473ae943e75dd49a18f2516;hpb=7ddb0732d05743a56ee3376446f76be8fa73d3ad diff --git a/osm_nbi/descriptor_topics.py b/osm_nbi/descriptor_topics.py index 3bdaa03..4e056f5 100644 --- a/osm_nbi/descriptor_topics.py +++ b/osm_nbi/descriptor_topics.py @@ -16,6 +16,7 @@ import tarfile import yaml import json +import importlib # import logging from hashlib import md5 from osm_common.dbbase import DbException, deep_update_rfc7396 @@ -26,8 +27,8 @@ from re import fullmatch from osm_nbi.validation import ValidationError, pdu_new_schema, pdu_edit_schema, \ validate_input, vnfpkgop_new_schema from osm_nbi.base_topic import BaseTopic, EngineException, get_iterable -from osm_im.vnfd import vnfd as vnfd_im -from osm_im.nsd import nsd as nsd_im +etsi_nfv_vnfd = importlib.import_module("osm_im.etsi-nfv-vnfd") +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 @@ -78,7 +79,6 @@ class DescriptorTopic(BaseTopic): # 1.3. restore internal keys for k, v in internal_keys.items(): final_content[k] = v - if session["force"]: return # 2. check that this id is not present @@ -301,8 +301,6 @@ class DescriptorTopic(BaseTopic): # Override descriptor with query string kwargs if kwargs: self._update_input_with_kwargs(indata, kwargs) - # it will call overrides method at VnfdTopic or NsdTopic - # indata = self._validate_input_edit(indata, force=session["force"]) deep_update_rfc7396(current_desc, indata) self.check_conflict_on_edit(session, current_desc, indata, _id=_id) @@ -396,33 +394,25 @@ class DescriptorTopic(BaseTopic): "future versions", http_code=HTTPStatus.NOT_ACCEPTABLE) return self.fs.file_open((storage['folder'], storage['zipfile']), "rb"), accept_zip - def pyangbind_validation(self, item, data, force=False): - try: - if item == "vnfds": - myvnfd = vnfd_im() - pybindJSONDecoder.load_ietf_json({'vnfd:vnfd-catalog': {'vnfd': [data]}}, None, None, obj=myvnfd, - path_helper=True, skip_unknown=force) - out = pybindJSON.dumps(myvnfd, mode="ietf") - elif item == "nsds": - mynsd = nsd_im() - pybindJSONDecoder.load_ietf_json({'nsd:nsd-catalog': {'nsd': [data]}}, None, None, obj=mynsd, - path_helper=True, skip_unknown=force) - out = pybindJSON.dumps(mynsd, mode="ietf") - elif item == "nsts": - mynst = nst_im() - pybindJSONDecoder.load_ietf_json({'nst': [data]}, None, None, obj=mynst, - path_helper=True, skip_unknown=force) - out = pybindJSON.dumps(mynst, mode="ietf") - else: - raise EngineException("Not possible to validate '{}' item".format(item), - http_code=HTTPStatus.INTERNAL_SERVER_ERROR) - - desc_out = self._remove_envelop(yaml.safe_load(out)) - return desc_out + def _remove_yang_prefixes_from_descriptor(self, descriptor): + new_descriptor = {} + for k, v in descriptor.items(): + new_v = v + if isinstance(v, dict): + new_v = self._remove_yang_prefixes_from_descriptor(v) + elif isinstance(v, list): + new_v = list() + for x in v: + if isinstance(x, dict): + new_v.append(self._remove_yang_prefixes_from_descriptor(x)) + else: + new_v.append(x) + new_descriptor[k.split(':')[-1]] = new_v + return new_descriptor - except Exception as e: - raise EngineException("Error in pyangbind validation: {}".format(str(e)), - http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + def pyangbind_validation(self, item, data, force=False): + raise EngineException("Not possible to validate '{}' item".format(item), + http_code=HTTPStatus.INTERNAL_SERVER_ERROR) 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 @@ -465,23 +455,40 @@ class VnfdTopic(DescriptorTopic): def __init__(self, db, fs, msg, auth): DescriptorTopic.__init__(self, db, fs, msg, auth) + def pyangbind_validation(self, item, data, force=False): + 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 + except Exception as e: + raise EngineException("Error in pyangbind validation: {}".format(str(e)), + http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + @staticmethod def _remove_envelop(indata=None): if not indata: return {} clean_indata = indata - if clean_indata.get('vnfd:vnfd-catalog'): - clean_indata = clean_indata['vnfd:vnfd-catalog'] - elif clean_indata.get('vnfd-catalog'): - clean_indata = clean_indata['vnfd-catalog'] - if clean_indata.get('vnfd'): - if not isinstance(clean_indata['vnfd'], list) or len(clean_indata['vnfd']) != 1: - raise EngineException("'vnfd' must be a list of only one element") - clean_indata = clean_indata['vnfd'][0] - elif clean_indata.get('vnfd:vnfd'): - if not isinstance(clean_indata['vnfd:vnfd'], list) or len(clean_indata['vnfd:vnfd']) != 1: - raise EngineException("'vnfd:vnfd' must be a list of only one element") - clean_indata = clean_indata['vnfd:vnfd'][0] + + if clean_indata.get('etsi-nfv-vnfd:vnfd'): + if not isinstance(clean_indata['etsi-nfv-vnfd:vnfd'], dict): + raise EngineException("'etsi-nfv-vnfd:vnfd' must be a dict") + clean_indata = clean_indata['etsi-nfv-vnfd:vnfd'] + elif clean_indata.get('vnfd'): + if not isinstance(clean_indata['vnfd'], dict): + raise EngineException("'vnfd' must be dict") + clean_indata = clean_indata['vnfd'] + return clean_indata def check_conflict_on_edit(self, session, final_content, edit_content, _id): @@ -536,89 +543,83 @@ class VnfdTopic(DescriptorTopic): 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 - self.validate_mgmt_interfaces_connection_points(indata) + + self.validate_mgmt_interface_connection_point(indata) for vdu in get_iterable(indata.get("vdu")): - self.validate_vdu_connection_point_refs(vdu, indata) + 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_vnf_charms_in_package(storage_params, indata) - self.validate_internal_vlds(indata) + self.validate_external_connection_points(indata) + self.validate_internal_virtual_links(indata) self.validate_monitoring_params(indata) self.validate_scaling_group_descriptor(indata) return indata @staticmethod - def validate_mgmt_interfaces_connection_points(indata): + def validate_mgmt_interface_connection_point(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", + if not indata.get("mgmt-cp"): + raise EngineException("'mgmt-cp' 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"]), + + for cp in get_iterable(indata.get("ext-cpd")): + if cp["id"] == indata["mgmt-cp"]: + break + else: + raise EngineException("mgmt-cp='{}' must match an existing ext-cpd".format(indata["mgmt-cp"]), + http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + + @staticmethod + def validate_vdu_internal_connection_points(vdu): + int_cpds = set() + for cpd in get_iterable(vdu.get("int-cpd")): + cpd_id = cpd.get("id") + if cpd_id and cpd_id in int_cpds: + raise EngineException("vdu[id='{}']:int-cpd[id='{}'] is already used by other int-cpd" + .format(vdu["id"], cpd_id), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + int_cpds.add(cpd_id) @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"] == 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"]), + def validate_external_connection_points(indata): + all_vdus_int_cpds = set() + for vdu in get_iterable(indata.get("vdu")): + for int_cpd in get_iterable(vdu.get("int-cpd")): + all_vdus_int_cpds.add((vdu.get("id"), int_cpd.get("id"))) + + ext_cpds = set() + for cpd in get_iterable(indata.get("ext-cpd")): + cpd_id = cpd.get("id") + if cpd_id and cpd_id in ext_cpds: + raise EngineException("ext-cpd[id='{}'] is already used by other ext-cpd".format(cpd_id), + http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + ext_cpds.add(cpd_id) + + int_cpd = cpd.get("int-cpd") + if int_cpd: + if (int_cpd.get("vdu-id"), int_cpd.get("cpd")) not in all_vdus_int_cpds: + raise EngineException("ext-cpd[id='{}']:int-cpd must match an existing vdu int-cpd".format(cpd_id), 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 - 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"])) + 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_cloud_init_in_package(self, storage_params, vdu, indata): if not vdu.get("cloud-init-file"): @@ -630,10 +631,11 @@ class VnfdTopic(DescriptorTopic): 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"])) + 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"])) def _validate_package_folders(self, storage_params, folder, file=None): if not storage_params or not storage_params.get("pkg-dir"): @@ -652,116 +654,113 @@ class VnfdTopic(DescriptorTopic): 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")): - ivld_name = ivld.get("name") - 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"]), + def validate_internal_virtual_links(indata): + all_ivld_ids = set() + for ivld in get_iterable(indata.get("int-virtual-link-desc")): + ivld_id = ivld.get("id") + if ivld_id and ivld_id in all_ivld_ids: + raise EngineException("Duplicated VLD id in int-virtual-link-desc[id={}]".format(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")): - for internal_cp in get_iterable(vdu.get("internal-connection-point")): - if icp["id-ref"] == internal_cp["id"]: - icp_mark = True - break - if icp_mark: - break - else: - raise EngineException("internal-vld[id='{}']:internal-connection-point='{}' must match an existing " - "vdu:internal-connection-point".format(ivld["id"], icp["id-ref"]), - http_code=HTTPStatus.UNPROCESSABLE_ENTITY) - if ivld.get("ip-profile-ref"): - for ip_prof in get_iterable(indata.get("ip-profiles")): - if ip_prof["name"] == get_iterable(ivld.get("ip-profile-ref")): - break - else: - raise EngineException("internal-vld[id='{}']:ip-profile-ref='{}' does not exist".format( - ivld["id"], ivld["ip-profile-ref"]), + all_ivld_ids.add(ivld_id) + + for vdu in get_iterable(indata.get("vdu")): + for int_cpd in get_iterable(vdu.get("int-cpd")): + int_cpd_ivld_id = int_cpd.get("int-virtual-link-desc") + if int_cpd_ivld_id and int_cpd_ivld_id not in all_ivld_ids: + raise EngineException( + "vdu[id='{}']:int-cpd[id='{}']:int-virtual-link-desc='{}' must match an existing " + "int-virtual-link-desc".format(vdu["id"], int_cpd["id"], int_cpd_ivld_id), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + for df in get_iterable(indata.get("df")): + for vlp in get_iterable(df.get("virtual-link-profile")): + vlp_ivld_id = vlp.get("id") + if vlp_ivld_id and vlp_ivld_id not in all_ivld_ids: + raise EngineException("df[id='{}']:virtual-link-profile='{}' must match an existing " + "int-virtual-link-desc".format(df["id"], vlp_ivld_id), + 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"] == \ - mp["vdu-monitoring-param"]["vdu-ref"]: - mp_vmp_mark = True - break - if mp_vmp_mark: - break + all_monitoring_params = set() + for ivld in get_iterable(indata.get("int-virtual-link-desc")): + for mp in get_iterable(ivld.get("monitoring-parameters")): + mp_id = mp.get("id") + if mp_id and mp_id in all_monitoring_params: + raise EngineException("Duplicated monitoring-parameter id in " + "int-virtual-link-desc[id='{}']:monitoring-parameters[id='{}']" + .format(ivld["id"], mp_id), + http_code=HTTPStatus.UNPROCESSABLE_ENTITY) else: - raise EngineException("monitoring-param:vdu-monitoring-param:vdu-monitoring-param-ref='{}' not " - "defined at vdu[id='{}'] or vdu does not exist" - .format(mp["vdu-monitoring-param"]["vdu-monitoring-param-ref"], - mp["vdu-monitoring-param"]["vdu-ref"]), + all_monitoring_params.add(mp_id) + + for vdu in get_iterable(indata.get("vdu")): + for mp in get_iterable(vdu.get("monitoring-parameter")): + mp_id = mp.get("id") + if mp_id and mp_id in all_monitoring_params: + raise EngineException("Duplicated monitoring-parameter id in " + "vdu[id='{}']:monitoring-parameter[id='{}']" + .format(vdu["id"], mp_id), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) - elif mp.get("vdu-metric"): - mp_vm_mark = False - for vdu in get_iterable(indata.get("vdu")): - if vdu.get("vdu-configuration"): - for metric in get_iterable(vdu["vdu-configuration"].get("metrics")): - if metric["name"] == mp["vdu-metric"]["vdu-metric-name-ref"] and vdu["id"] == \ - mp["vdu-metric"]["vdu-ref"]: - mp_vm_mark = True - break - if mp_vm_mark: - break else: - raise EngineException("monitoring-param:vdu-metric:vdu-metric-name-ref='{}' not defined at " - "vdu[id='{}'] or vdu does not exist" - .format(mp["vdu-metric"]["vdu-metric-name-ref"], - mp["vdu-metric"]["vdu-ref"]), + all_monitoring_params.add(mp_id) + + for df in get_iterable(indata.get("df")): + for mp in get_iterable(df.get("monitoring-parameter")): + mp_id = mp.get("id") + if mp_id and mp_id in all_monitoring_params: + raise EngineException("Duplicated monitoring-parameter id in " + "df[id='{}']:monitoring-parameter[id='{}']" + .format(df["id"], mp_id), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + else: + all_monitoring_params.add(mp_id) @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")): - for mp in get_iterable(indata.get("monitoring-param")): - if mp["id"] == get_iterable(sc.get("vnf-monitoring-param-ref")): - break - else: - raise EngineException("scaling-group-descriptor[name='{}']:scaling-criteria[name='{}']:" - "vnf-monitoring-param-ref='{}' not defined in any monitoring-param" - .format(sgd["name"], sc["name"], sc["vnf-monitoring-param-ref"]), + all_monitoring_params = set() + for ivld in get_iterable(indata.get("int-virtual-link-desc")): + for mp in get_iterable(ivld.get("monitoring-parameters")): + all_monitoring_params.add(mp.get("id")) + + for vdu in get_iterable(indata.get("vdu")): + for mp in get_iterable(vdu.get("monitoring-parameter")): + all_monitoring_params.add(mp.get("id")) + + for df in get_iterable(indata.get("df")): + for mp in get_iterable(df.get("monitoring-parameter")): + all_monitoring_params.add(mp.get("id")) + + for df in get_iterable(indata.get("df")): + for sa in get_iterable(df.get("scaling-aspect")): + for sp in get_iterable(sa.get("scaling-policy")): + for sc in get_iterable(sp.get("scaling-criteria")): + sc_monitoring_param = sc.get("vnf-monitoring-param-ref") + if sc_monitoring_param and sc_monitoring_param not in all_monitoring_params: + raise EngineException("df[id='{}']:scaling-aspect[id='{}']:scaling-policy" + "[name='{}']:scaling-criteria[name='{}']: " + "vnf-monitoring-param-ref='{}' not defined in any monitoring-param" + .format(df["id"], sa["id"], sp["name"], sc["name"], + sc_monitoring_param), + 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" + .format(df["id"], sa["id"]), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) - for sgd_vdu in get_iterable(sgd.get("vdu")): - sgd_vdu_mark = False - for vdu in get_iterable(indata.get("vdu")): - if vdu["id"] == sgd_vdu["vdu-id-ref"]: - sgd_vdu_mark = True - break - if sgd_vdu_mark: - break - else: - raise EngineException("scaling-group-descriptor[name='{}']:vdu-id-ref={} does not match any vdu" - .format(sgd["name"], sgd_vdu["vdu-id-ref"]), - http_code=HTTPStatus.UNPROCESSABLE_ENTITY) - for sca in get_iterable(sgd.get("scaling-config-action")): - if not indata.get("vnf-configuration"): - raise EngineException("'vnf-configuration' not defined in the descriptor but it is referenced by " - "scaling-group-descriptor[name='{}']:scaling-config-action" - .format(sgd["name"]), - http_code=HTTPStatus.UNPROCESSABLE_ENTITY) - for primitive in get_iterable(indata["vnf-configuration"].get("config-primitive")): - if primitive["name"] == sca["vnf-config-primitive-name-ref"]: - break - else: - raise EngineException("scaling-group-descriptor[name='{}']:scaling-config-action:vnf-config-" - "primitive-name-ref='{}' does not match any " - "vnf-configuration:config-primitive:name" - .format(sgd["name"], sca["vnf-config-primitive-name-ref"]), - http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + for configuration in get_iterable(indata["vnf-configuration"]): + 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" + .format(df["id"], sa["id"], sca["vnf-config-primitive-name-ref"]), + http_code=HTTPStatus.UNPROCESSABLE_ENTITY) def delete_extra(self, session, _id, db_content, not_send_msg=None): """ @@ -797,24 +796,33 @@ class NsdTopic(DescriptorTopic): def __init__(self, db, fs, msg, auth): DescriptorTopic.__init__(self, db, fs, msg, auth) + def pyangbind_validation(self, item, data, force=False): + try: + 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) + return desc_out + except Exception as e: + raise EngineException("Error in pyangbind validation: {}".format(str(e)), + http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + @staticmethod def _remove_envelop(indata=None): if not indata: return {} clean_indata = indata - if clean_indata.get('nsd:nsd-catalog'): - clean_indata = clean_indata['nsd:nsd-catalog'] - elif clean_indata.get('nsd-catalog'): - clean_indata = clean_indata['nsd-catalog'] + if clean_indata.get('nsd'): + clean_indata = clean_indata['nsd'] + elif clean_indata.get('etsi-nfv-nsd:nsd'): + clean_indata = clean_indata['etsi-nfv-nsd:nsd'] if clean_indata.get('nsd'): if not isinstance(clean_indata['nsd'], list) or len(clean_indata['nsd']) != 1: raise EngineException("'nsd' must be a list of only one element") clean_indata = clean_indata['nsd'][0] - elif clean_indata.get('nsd:nsd'): - if not isinstance(clean_indata['nsd:nsd'], list) or len(clean_indata['nsd:nsd']) != 1: - raise EngineException("'nsd:nsd' must be a list of only one element") - clean_indata = clean_indata['nsd:nsd'][0] return clean_indata def _validate_input_new(self, indata, storage_params, force=False): @@ -827,54 +835,37 @@ class NsdTopic(DescriptorTopic): 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")): - self.validate_vld_mgmt_network_with_ip_profile_ref(vld) - self.validate_vld_connection_point_refs(vld, indata) + for vld in get_iterable(indata.get("virtual-link-desc")): + self.validate_vld_mgmt_network_with_virtual_link_protocol_data(vld, indata) - for fgd in get_iterable(indata.get("vnffgd")): - self.validate_fgd_classifiers(fgd) + self.validate_vnf_profiles_vnfd_id(indata) return indata @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) + def validate_vld_mgmt_network_with_virtual_link_protocol_data(vld, indata): + if not vld.get("mgmt-network"): + return + vld_id = vld.get("id") + for df in get_iterable(indata.get("df")): + for vlp in get_iterable(df.get("virtual-link-profile")): + if vld_id and vld_id == vlp.get("virtual-link-desc-id"): + if vlp.get("virtual-link-protocol-data"): + raise EngineException("Error at df[id='{}']:virtual-link-profile[id='{}']:virtual-link-" + "protocol-data You cannot set a virtual-link-protocol-data " + "when mgmt-network is True" + .format(df["id"], vlp["id"]), 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_vnf_profiles_vnfd_id(indata): + all_vnfd_ids = set(get_iterable(indata.get("vnfd-id"))) + for df in get_iterable(indata.get("df")): + for vnf_profile in get_iterable(df.get("vnf-profile")): + vnfd_id = vnf_profile.get("vnfd-id") + if vnfd_id and vnfd_id not in all_vnfd_ids: + raise EngineException("Error at df[id='{}']:vnf_profile[id='{}']:vnfd-id='{}' " + "does not match any vnfd-id".format(df["id"], vnf_profile["id"], vnfd_id), + 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 @@ -923,42 +914,44 @@ class NsdTopic(DescriptorTopic): """ if session["force"]: return - member_vnfd_index = self._get_descriptor_constituent_vnfds_by_member_vnfd_index(session, descriptor) + vnfds_index = self._get_descriptor_constituent_vnfds_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"] + for df in get_iterable(descriptor.get("df")): + self.validate_df_vnf_profiles_constituent_connection_points(df, vnfds_index) + + def _get_descriptor_constituent_vnfds_index(self, session, descriptor): + vnfds_index = {} + if descriptor.get("vnfd-id") and not session["force"]: + for vnfd_id in get_iterable(descriptor.get("vnfd-id")): 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 " + raise EngineException("Descriptor error at 'vnfd-id'='{}' references a non " "existing vnfd".format(vnfd_id), http_code=HTTPStatus.CONFLICT) - - member_vnfd_index[vnf["member-vnf-index"]] = vnf_list[0] - return member_vnfd_index + vnfds_index[vnfd_id] = vnf_list[0] + return vnfds_index @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 validate_df_vnf_profiles_constituent_connection_points(df, vnfds_index): + for vnf_profile in get_iterable(df.get("vnf-profile")): + vnfd = vnfds_index.get(vnf_profile["vnfd-id"]) + all_vnfd_ext_cpds = set() + for ext_cpd in get_iterable(vnfd.get("ext-cpd")): + if ext_cpd.get('id'): + all_vnfd_ext_cpds.add(ext_cpd.get('id')) + + for virtual_link in get_iterable(vnf_profile.get("virtual-link-connectivity")): + for vl_cpd in get_iterable(virtual_link.get("constituent-cpd-id")): + vl_cpd_id = vl_cpd.get('constituent-cpd-id') + if vl_cpd_id and vl_cpd_id not in all_vnfd_ext_cpds: + raise EngineException("Error at df[id='{}']:vnf-profile[id='{}']:virtual-link-connectivity" + "[virtual-link-profile-id='{}']:constituent-cpd-id='{}' references a " + "non existing ext-cpd:id inside vnfd '{}'" + .format(df["id"], vnf_profile["id"], + virtual_link["virtual-link-profile-id"], vl_cpd_id, 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) @@ -1015,6 +1008,18 @@ class NstTopic(DescriptorTopic): def __init__(self, db, fs, msg, auth): DescriptorTopic.__init__(self, db, fs, msg, auth) + def pyangbind_validation(self, item, data, force=False): + try: + mynst = nst_im() + pybindJSONDecoder.load_ietf_json({'nst': [data]}, None, None, obj=mynst, + path_helper=True, skip_unknown=force) + out = pybindJSON.dumps(mynst, mode="ietf") + desc_out = self._remove_envelop(yaml.safe_load(out)) + return desc_out + except Exception as e: + raise EngineException("Error in pyangbind validation: {}".format(str(e)), + http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + @staticmethod def _remove_envelop(indata=None): if not indata: