X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FNBI.git;a=blobdiff_plain;f=osm_nbi%2Fdescriptor_topics.py;h=6285e38de6d00a8650aa86c35dace2102d84433f;hp=6ecf7dc0f9941ce8a2d419ac7c3c2ddadccb9d45;hb=4c57e2e64537d6263c618da58bcd8f8767b8076e;hpb=5e72d153a809b6855d14c29ec5e4180650c0d282;ds=sidebyside diff --git a/osm_nbi/descriptor_topics.py b/osm_nbi/descriptor_topics.py index 6ecf7dc..6285e38 100644 --- a/osm_nbi/descriptor_topics.py +++ b/osm_nbi/descriptor_topics.py @@ -9,6 +9,10 @@ from osm_common.dbbase import DbException, deep_update_rfc7396 from http import HTTPStatus from validation import ValidationError, pdu_new_schema, pdu_edit_schema from 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 +from pyangbind.lib.serialise import pybindJSONDecoder +import pyangbind.lib.pybindJSON as pybindJSON __author__ = "Alfonso Tierno " @@ -19,24 +23,36 @@ class DescriptorTopic(BaseTopic): BaseTopic.__init__(self, db, fs, msg) def check_conflict_on_edit(self, session, final_content, edit_content, _id, force=False): - # check that this id is not present - _filter = {"id": final_content["id"]} - if _id: + # 1. validate again with pyangbind + # 1.1. remove internal keys + internal_keys = {} + for k in ("_id", "_admin"): + if k in final_content: + internal_keys[k] = final_content.pop(k) + serialized = self._validate_input_new(final_content, force) + # 1.2. modify final_content with a serialized version + final_content.clear() + final_content.update(serialized) + # 1.3. restore internal keys + for k, v in internal_keys.items(): + final_content[k] = v + + # 2. check that this id is not present + if "id" in edit_content: + _filter = self._get_project_filter(session, write=False, show_all=False) + _filter["id"] = final_content["id"] _filter["_id.neq"] = _id - - _filter.update(self._get_project_filter(session, write=False, show_all=False)) - 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) - # TODO validate with pyangbind. Load and dumps to convert data types + 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) @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) content["_admin"]["onboardingState"] = "CREATED" content["_admin"]["operationalState"] = "DISABLED" - content["_admin"]["usageSate"] = "NOT_IN_USE" + content["_admin"]["usageState"] = "NOT_IN_USE" def delete(self, session, _id, force=False, dry_run=False): """ @@ -335,6 +351,29 @@ 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") + 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 + + except Exception as e: + raise EngineException("Error in pyangbind validation: {}".format(str(e)), + http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + class VnfdTopic(DescriptorTopic): topic = "vnfds" @@ -354,10 +393,31 @@ class VnfdTopic(DescriptorTopic): 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 only one element") + 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] return clean_indata + def check_conflict_on_edit(self, session, final_content, edit_content, _id, force=False): + super().check_conflict_on_edit(session, final_content, edit_content, _id, force=force) + + # set type of vnfd + contains_pdu = False + contains_vdu = False + for vdu in get_iterable(final_content.get("vdu")): + if vdu.get("pdu-type"): + contains_pdu = True + else: + contains_vdu = True + if contains_pdu: + final_content["_admin"]["type"] = "hnfd" if contains_vdu else "pnfd" + elif contains_vdu: + final_content["_admin"]["type"] = "vnfd" + # if neither vud nor pdu do not fill type + def check_conflict_on_del(self, session, _id, force=False): """ Check that there is not any NSD that uses this VNFD. Only NSDs belonging to this project are considered. Note @@ -387,44 +447,43 @@ class VnfdTopic(DescriptorTopic): raise EngineException("There is soame NSD that depends on this VNFD", http_code=HTTPStatus.CONFLICT) def _validate_input_new(self, indata, force=False): - # TODO validate with pyangbind, serialize - + indata = self.pyangbind_validation("vnfds", indata, force) # Cross references validation in the descriptor - if indata.get("mgmt-interface"): + if indata.get("vdu"): + 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"].get("cp")), + raise EngineException("mgmt-interface:cp='{}' must match an existing connection-point" + .format(indata["mgmt-interface"]["cp"]), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) - else: - raise EngineException("'mgmt-interface' is a mandatory field and it is not defined", - http_code=HTTPStatus.UNPROCESSABLE_ENTITY) for vdu in get_iterable(indata.get("vdu")): for interface in get_iterable(vdu.get("interface")): if interface.get("external-connection-point-ref"): for cp in get_iterable(indata.get("connection-point")): - if cp["name"] == interface.get("external-connection-point-ref"): + if cp["name"] == interface["external-connection-point-ref"]: break else: - raise EngineException("vdu[id='{}']:interface[name='{}']:external-connection-point-ref='{} " + 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) - for interface in get_iterable(vdu.get("interface")): - if interface.get("internal-connection-point-ref"): + + elif interface.get("internal-connection-point-ref"): for internal_cp in get_iterable(vdu.get("internal-connection-point")): - if interface.get("internal-connection-point-ref") == internal_cp.get("id"): + if interface["internal-connection-point-ref"] == internal_cp.get("id"): break else: - raise EngineException("vdu[id='{}']:interface[name='{}']:internal-connection-point-ref='{}' is " - "not associated to an internal-connection-point".format - (vdu["id"], interface["name"], - interface["internal-connection-point-ref"]), + 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 ivld in get_iterable(indata.get("internal-vld")): for icp in get_iterable(ivld.get("internal-connection-point")): @@ -437,15 +496,15 @@ class VnfdTopic(DescriptorTopic): if icp_mark: break else: - raise EngineException("'internal-vld='{}':internal-connection-point='{}'' must match an existing " - "internal-connection-point".format(ivld["id"], icp["id-ref"]), + 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["ip-profiles"]): + 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='{}':ip-profile-ref='{}' does not exist".format( + raise EngineException("internal-vld[id='{}']:ip-profile-ref='{}' does not exist".format( ivld["id"], ivld["ip-profile-ref"]), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) for mp in get_iterable(indata.get("monitoring-param")): @@ -453,7 +512,7 @@ class VnfdTopic(DescriptorTopic): 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"]["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 @@ -461,7 +520,7 @@ class VnfdTopic(DescriptorTopic): break else: raise EngineException("monitoring-param:vdu-monitoring-param:vdu-monitoring-param-ref='{}' not " - "defined in VDU vdu[id='{}'] or does not exist an VDU with this id" + "defined at vdu[id='{}'] or vdu does not exist" .format(mp["vdu-monitoring-param"]["vdu-monitoring-param-ref"], mp["vdu-monitoring-param"]["vdu-ref"]), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) @@ -477,8 +536,8 @@ class VnfdTopic(DescriptorTopic): if mp_vm_mark: break else: - raise EngineException("monitoring-param:vdu-metric:vdu-metric-name-ref='{}' not defined in VDU " - "vdu[id='{}'] or or does not exist an VDU with this id" + 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"]), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) @@ -490,9 +549,9 @@ class VnfdTopic(DescriptorTopic): 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" - "['id']'".format(sgd["name"], sc["name"], sc["vnf-monitoring-param-ref"]), + 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"]), http_code=HTTPStatus.UNPROCESSABLE_ENTITY) for sgd_vdu in get_iterable(sgd.get("vdu")): sgd_vdu_mark = False @@ -503,32 +562,29 @@ class VnfdTopic(DescriptorTopic): if sgd_vdu_mark: break else: - raise EngineException("scaling-group-descriptor[name='{}']:vdu not defined and" - "it is mandatory in any 'scaling-group-descriptor'" - .format(sgd["name"]), + 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")): - sca_vcp_mark = False - if indata.get("vnf-configuration"): - for primitive in get_iterable(indata["vnf-configuration"].get("config-primitive")): - if primitive["name"] == sca["vnf-config-primitive-name-ref"]: - sca_vcp_mark = True - break - else: - raise EngineException("scaling-group-descriptor:name='{}':scaling-config-action:vnf-config-" - "primitive-name-ref='{}' not defined in " - "'vnf-configuration:config-primitive'" - .format(sgd["name"], sca["vnf-config-primitive-name-ref"]), - http_code=HTTPStatus.UNPROCESSABLE_ENTITY) + 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("'vnf-configuration' not defined in the descriptor but it is referenced", + 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) - if sca_vcp_mark: - break + # TODO validata that if contains cloud-init-file or charms, have artifacts _admin.storage."pkg-dir" is not none return indata def _validate_input_edit(self, indata, force=False): - # TODO validate with pyangbind, serialize + # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit return indata @@ -551,22 +607,21 @@ class NsdTopic(DescriptorTopic): clean_indata = clean_indata['nsd-catalog'] 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 only one element") + 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, force=False): - # transform constituent-vnfd:member-vnf-index to string - if indata.get("constituent-vnfd"): - for constituent_vnfd in indata["constituent-vnfd"]: - if "member-vnf-index" in constituent_vnfd: - constituent_vnfd["member-vnf-index"] = str(constituent_vnfd["member-vnf-index"]) - - # TODO validate with pyangbind, serialize + indata = self.pyangbind_validation("nsds", indata, force) + # TODO validata that if contains cloud-init-file or charms, have artifacts _admin.storage."pkg-dir" is not none return indata def _validate_input_edit(self, indata, force=False): - # TODO validate with pyangbind, serialize + # not needed to validate with pyangbind becuase it will be validated at check_conflict_on_edit return indata def _check_descriptor_dependencies(self, session, descriptor): @@ -589,7 +644,8 @@ class NsdTopic(DescriptorTopic): def check_conflict_on_edit(self, session, final_content, edit_content, _id, force=False): super().check_conflict_on_edit(session, final_content, edit_content, _id, force=force) - self._check_descriptor_dependencies(session, final_content) + if not force: + self._check_descriptor_dependencies(session, final_content) def check_conflict_on_del(self, session, _id, force=False): """ @@ -608,6 +664,79 @@ class NsdTopic(DescriptorTopic): raise EngineException("There is some NSR that depends on this NSD", http_code=HTTPStatus.CONFLICT) +class NstTopic(DescriptorTopic): + topic = "nsts" + topic_msg = "nst" + + def __init__(self, db, fs, msg): + DescriptorTopic.__init__(self, db, fs, msg) + + @staticmethod + def _remove_envelop(indata=None): + if not indata: + return {} + clean_indata = indata + + if clean_indata.get('nst:nst'): + clean_indata = clean_indata['nst:nst'] + elif clean_indata.get('nst'): + clean_indata = clean_indata['nst'] + if clean_indata.get('nst'): + if not isinstance(clean_indata['nst'], list) or len(clean_indata['nst']) != 1: + raise EngineException("'nst' must be a list only one element") + clean_indata = clean_indata['nst'][0] + return clean_indata + + def _validate_input_edit(self, indata, force=False): + # TODO validate with pyangbind, serialize + return indata + + def _check_descriptor_dependencies(self, session, descriptor): + """ + Check that the dependent descriptors exist on a new descriptor or edition + :param session: client session information + :param descriptor: descriptor to be inserted or edit + :return: None or raises exception + """ + if not descriptor.get("netslice-subnet"): + return + for nsd in descriptor["netslice-subnet"]: + nsd_id = nsd["nsd-ref"] + filter_q = self._get_project_filter(session, write=False, show_all=True) + filter_q["id"] = nsd_id + if not self.db.get_list("nsds", filter_q): + raise EngineException("Descriptor error at 'netslice-subnet':'nsd-ref'='{}' references a non " + "existing nsd".format(nsd_id), http_code=HTTPStatus.CONFLICT) + + def check_conflict_on_edit(self, session, final_content, edit_content, _id, force=False): + super().check_conflict_on_edit(session, final_content, edit_content, _id, force=force) + + self._check_descriptor_dependencies(session, final_content) + + def check_conflict_on_del(self, session, _id, force=False): + """ + Check that there is not any NSIR that uses this NST. Only NSIRs belonging to this project are considered. Note + that NST can be public and be used by other projects. + :param session: + :param _id: nst internal id + :param force: Avoid this checking + :return: None or raises EngineException with the conflict + """ + # TODO: Check this method + if force: + return + # Get Network Slice Template from Database + _filter = self._get_project_filter(session, write=False, show_all=False) + _filter["_id"] = _id + nst = self.db.get_one("nst", _filter) + + # Search NSIs using NST via nst-ref + _filter = self._get_project_filter(session, write=False, show_all=False) + _filter["nst-ref"] = nst["id"] + if self.db.get_list("nsis", _filter): + raise EngineException("There is some NSIS that depends on this NST", http_code=HTTPStatus.CONFLICT) + + class PduTopic(BaseTopic): topic = "pdus" topic_msg = "pdu" @@ -619,10 +748,10 @@ class PduTopic(BaseTopic): @staticmethod def format_on_new(content, project_id=None, make_public=False): - BaseTopic.format_on_new(content, project_id=None, make_public=make_public) + BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public) content["_admin"]["onboardingState"] = "CREATED" - content["_admin"]["operationalState"] = "DISABLED" - content["_admin"]["usageSate"] = "NOT_IN_USE" + content["_admin"]["operationalState"] = "ENABLED" + content["_admin"]["usageState"] = "NOT_IN_USE" def check_conflict_on_del(self, session, _id, force=False): if force: