X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FNBI.git;a=blobdiff_plain;f=osm_nbi%2Fdescriptor_topics.py;h=ab0467a686edd2b50d5f55092efc24314ec312e9;hp=bcd6a039385476a9c3c7d1b5181164186b590354;hb=deba68eb04ae450a54ef73923f8034d07707a791;hpb=0f9b9661f5c7080ea85b158ffa418c1842e96ad2 diff --git a/osm_nbi/descriptor_topics.py b/osm_nbi/descriptor_topics.py index bcd6a03..ab0467a 100644 --- a/osm_nbi/descriptor_topics.py +++ b/osm_nbi/descriptor_topics.py @@ -21,7 +21,10 @@ from hashlib import md5 from osm_common.dbbase import DbException, deep_update_rfc7396 from http import HTTPStatus from time import time -from osm_nbi.validation import ValidationError, pdu_new_schema, pdu_edit_schema +from uuid import uuid4 +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 @@ -143,29 +146,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): """ @@ -417,6 +418,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" @@ -566,13 +600,14 @@ class VnfdTopic(DescriptorTopic): 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["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) + 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 for icp in get_iterable(ivld.get("internal-connection-point")): icp_mark = False @@ -670,10 +705,6 @@ class VnfdTopic(DescriptorTopic): 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 @@ -690,6 +721,19 @@ class VnfdTopic(DescriptorTopic): return True return False + def delete_extra(self, session, _id, db_content, not_send_msg=None): + """ + Deletes associate file system storage (via super) + Deletes associated vnfpkgops from database. + :param session: contains "username", "admin", "force", "public", "project_id", "set_project" + :param _id: server internal id + :param db_content: The database content of the descriptor + :return: None + :raises: FsException in case of error while deleting associated storage + """ + super().delete_extra(session, _id, db_content, not_send_msg) + self.db.del_list("vnfpkgops", {"vnfPkgId": _id}) + class NsdTopic(DescriptorTopic): topic = "nsds" @@ -758,8 +802,41 @@ class NsdTopic(DescriptorTopic): http_code=HTTPStatus.UNPROCESSABLE_ENTITY) return indata - def _validate_input_edit(self, indata, force=False): + 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): @@ -841,6 +918,7 @@ class NsdTopic(DescriptorTopic): 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) @@ -861,10 +939,6 @@ 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 = self.pyangbind_validation("nsts", indata, force) return indata.copy() @@ -914,6 +988,7 @@ class NstTopic(DescriptorTopic): class PduTopic(BaseTopic): topic = "pdus" topic_msg = "pdu" + quota_name = "pduds" schema_new = pdu_new_schema schema_edit = pdu_edit_schema @@ -942,3 +1017,97 @@ class PduTopic(BaseTopic): _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) + + +class VnfPkgOpTopic(BaseTopic): + topic = "vnfpkgops" + topic_msg = "vnfd" + schema_new = vnfpkgop_new_schema + schema_edit = None + + def __init__(self, db, fs, msg, auth): + BaseTopic.__init__(self, db, fs, msg, auth) + + def edit(self, session, _id, indata=None, kwargs=None, content=None): + raise EngineException("Method 'edit' not allowed for topic '{}'".format(self.topic), + HTTPStatus.METHOD_NOT_ALLOWED) + + def delete(self, session, _id, dry_run=False): + raise EngineException("Method 'delete' not allowed for topic '{}'".format(self.topic), + HTTPStatus.METHOD_NOT_ALLOWED) + + def delete_list(self, session, filter_q=None): + raise EngineException("Method 'delete_list' not allowed for topic '{}'".format(self.topic), + HTTPStatus.METHOD_NOT_ALLOWED) + + def new(self, rollback, session, indata=None, kwargs=None, headers=None): + """ + Creates a new entry into database. + :param rollback: list to append created items at database in case a rollback may to be done + :param session: contains "username", "admin", "force", "public", "project_id", "set_project" + :param indata: data to be inserted + :param kwargs: used to override the indata descriptor + :param headers: http request headers + :return: _id, op_id: + _id: identity of the inserted data. + op_id: None + """ + self._update_input_with_kwargs(indata, kwargs) + validate_input(indata, self.schema_new) + vnfpkg_id = indata["vnfPkgId"] + filter_q = BaseTopic._get_project_filter(session) + filter_q["_id"] = vnfpkg_id + vnfd = self.db.get_one("vnfds", filter_q) + operation = indata["lcmOperationType"] + kdu_name = indata["kdu_name"] + for kdu in vnfd.get("kdu", []): + if kdu["name"] == kdu_name: + helm_chart = kdu.get("helm-chart") + juju_bundle = kdu.get("juju-bundle") + break + else: + raise EngineException("Not found vnfd[id='{}']:kdu[name='{}']".format(vnfpkg_id, kdu_name)) + if helm_chart: + indata["helm-chart"] = helm_chart + match = fullmatch(r"([^/]*)/([^/]*)", helm_chart) + repo_name = match.group(1) if match else None + elif juju_bundle: + indata["juju-bundle"] = juju_bundle + match = fullmatch(r"([^/]*)/([^/]*)", juju_bundle) + repo_name = match.group(1) if match else None + else: + raise EngineException("Found neither 'helm-chart' nor 'juju-bundle' in vnfd[id='{}']:kdu[name='{}']" + .format(vnfpkg_id, kdu_name)) + if repo_name: + del filter_q["_id"] + filter_q["name"] = repo_name + repo = self.db.get_one("k8srepos", filter_q) + k8srepo_id = repo.get("_id") + k8srepo_url = repo.get("url") + else: + k8srepo_id = None + k8srepo_url = None + indata["k8srepoId"] = k8srepo_id + indata["k8srepo_url"] = k8srepo_url + vnfpkgop_id = str(uuid4()) + vnfpkgop_desc = { + "_id": vnfpkgop_id, + "operationState": "PROCESSING", + "vnfPkgId": vnfpkg_id, + "lcmOperationType": operation, + "isAutomaticInvocation": False, + "isCancelPending": False, + "operationParams": indata, + "links": { + "self": "/osm/vnfpkgm/v1/vnfpkg_op_occs/" + vnfpkgop_id, + "vnfpkg": "/osm/vnfpkgm/v1/vnf_packages/" + vnfpkg_id, + } + } + self.format_on_new(vnfpkgop_desc, session["project_id"], make_public=session["public"]) + ctime = vnfpkgop_desc["_admin"]["created"] + vnfpkgop_desc["statusEnteredTime"] = ctime + vnfpkgop_desc["startTime"] = ctime + self.db.create(self.topic, vnfpkgop_desc) + rollback.append({"topic": self.topic, "_id": vnfpkgop_id}) + self.msg.write(self.topic_msg, operation, vnfpkgop_desc) + return vnfpkgop_id, None