From 19b9752a7f43e1fcf1bdfd0040c1e8edfe9e6f3a Mon Sep 17 00:00:00 2001 From: Frank Bryden Date: Fri, 10 Jul 2020 12:32:02 +0000 Subject: [PATCH 1/1] Fix for Bug 1115: Rename state attributes for SOL005 conformance Change-Id: I4c6eda2916c59b9631ffc51e419803554f01b574 Signed-off-by: Frank Bryden --- osm_nbi/base_topic.py | 27 ++++++++++++++++--- osm_nbi/descriptor_topics.py | 52 ++++++++++++++++++++++++++++++++++++ osm_nbi/engine.py | 10 ++++--- osm_nbi/nbi.py | 4 +-- 4 files changed, 83 insertions(+), 10 deletions(-) diff --git a/osm_nbi/base_topic.py b/osm_nbi/base_topic.py index 216c9df..5492a8f 100644 --- a/osm_nbi/base_topic.py +++ b/osm_nbi/base_topic.py @@ -354,11 +354,16 @@ class BaseTopic: except YAMLError: raise EngineException("Invalid query string '{}' yaml format".format(k)) - def show(self, session, _id): + def sol005_projection(self, data): + # Projection was moved to child classes + return data + + def show(self, session, _id, api_req=False): """ Get complete information on an topic :param session: contains "username", "admin", "force", "public", "project_id", "set_project" :param _id: server internal id + :param api_req: True if this call is serving an external API request. False if serving internal request. :return: dictionary, raise exception if not found. """ if not self.multiproject: @@ -367,7 +372,14 @@ class BaseTopic: filter_db = self._get_project_filter(session) # To allow project&user addressing by name AS WELL AS _id filter_db[BaseTopic.id_field(self.topic, _id)] = _id - return self.db.get_one(self.topic, filter_db) + data = self.db.get_one(self.topic, filter_db) + + # Only perform SOL005 projection if we are serving an external request + if api_req: + self.sol005_projection(data) + + return data + # TODO transform data for SOL005 URL requests # TODO remove _admin if not admin @@ -382,11 +394,12 @@ class BaseTopic: """ raise EngineException("Method get_file not valid for this topic", HTTPStatus.INTERNAL_SERVER_ERROR) - def list(self, session, filter_q=None): + def list(self, session, filter_q=None, api_req=False): """ Get a list of the topic that matches a filter :param session: contains the used login username and working project :param filter_q: filter of data to be applied + :param api_req: True if this call is serving an external API request. False if serving internal request. :return: The list, it can be empty if no one match the filter. """ if not filter_q: @@ -396,7 +409,13 @@ class BaseTopic: # TODO transform data for SOL005 URL requests. Transform filtering # TODO implement "field-type" query string SOL005 - return self.db.get_list(self.topic, filter_q) + data = self.db.get_list(self.topic, filter_q) + + # Only perform SOL005 projection if we are serving an external request + if api_req: + data = [self.sol005_projection(inst) for inst in data] + + return data def new(self, rollback, session, indata=None, kwargs=None, headers=None): """ diff --git a/osm_nbi/descriptor_topics.py b/osm_nbi/descriptor_topics.py index ab0467a..9113b0c 100644 --- a/osm_nbi/descriptor_topics.py +++ b/osm_nbi/descriptor_topics.py @@ -527,6 +527,12 @@ 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"): @@ -733,6 +739,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): @@ -763,6 +782,12 @@ 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 @@ -913,6 +938,18 @@ class NsdTopic(DescriptorTopic): if self.db.get_list("nsts", _filter): 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): @@ -940,6 +977,9 @@ class NstTopic(DescriptorTopic): return clean_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() @@ -984,6 +1024,18 @@ 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" diff --git a/osm_nbi/engine.py b/osm_nbi/engine.py index 33d7791..133cb9d 100644 --- a/osm_nbi/engine.py +++ b/osm_nbi/engine.py @@ -217,29 +217,31 @@ class Engine(object): with self.write_lock: return self.map_topic[topic].upload_content(session, _id, indata, kwargs, headers) - def get_item_list(self, session, topic, filter_q=None): + def get_item_list(self, session, topic, filter_q=None, api_req=False): """ Get a list of items :param session: contains the used login username and working project :param topic: it can be: users, projects, vnfds, nsds, ... :param filter_q: filter of data to be applied + :param api_req: True if this call is serving an external API request. False if serving internal request. :return: The list, it can be empty if no one match the filter_q. """ if topic not in self.map_topic: raise EngineException("Unknown topic {}!!!".format(topic), HTTPStatus.INTERNAL_SERVER_ERROR) - return self.map_topic[topic].list(session, filter_q) + return self.map_topic[topic].list(session, filter_q, api_req) - def get_item(self, session, topic, _id): + def get_item(self, session, topic, _id, api_req=False): """ Get complete information on an item :param session: contains the used login username and working project :param topic: it can be: users, projects, vnfds, nsds, :param _id: server id of the item + :param api_req: True if this call is serving an external API request. False if serving internal request. :return: dictionary, raise exception if not found. """ if topic not in self.map_topic: raise EngineException("Unknown topic {}!!!".format(topic), HTTPStatus.INTERNAL_SERVER_ERROR) - return self.map_topic[topic].show(session, _id) + return self.map_topic[topic].show(session, _id, api_req) def get_file(self, session, topic, _id, path=None, accept_header=None): """ diff --git a/osm_nbi/nbi.py b/osm_nbi/nbi.py index fd2b3e8..2b54ae5 100644 --- a/osm_nbi/nbi.py +++ b/osm_nbi/nbi.py @@ -1054,12 +1054,12 @@ class Server(object): cherrypy.request.headers.get("Accept")) outdata = file elif not _id: - outdata = self.engine.get_item_list(engine_session, engine_topic, kwargs) + outdata = self.engine.get_item_list(engine_session, engine_topic, kwargs, api_req=True) else: if item == "reports": # TODO check that project_id (_id in this context) has permissions _id = args[0] - outdata = self.engine.get_item(engine_session, engine_topic, _id) + outdata = self.engine.get_item(engine_session, engine_topic, _id, True) elif method == "POST": cherrypy.response.status = HTTPStatus.CREATED.value -- 2.17.1