Fix for Bug 1115: Rename state attributes for SOL005 conformance 34/9434/13
authorFrank Bryden <frank.bryden@etsi.org>
Fri, 10 Jul 2020 12:32:02 +0000 (12:32 +0000)
committertierno <alfonso.tiernosepulveda@telefonica.com>
Fri, 7 Aug 2020 14:06:03 +0000 (16:06 +0200)
Change-Id: I4c6eda2916c59b9631ffc51e419803554f01b574
Signed-off-by: Frank Bryden <frank.bryden@etsi.org>
osm_nbi/base_topic.py
osm_nbi/descriptor_topics.py
osm_nbi/engine.py
osm_nbi/nbi.py

index 216c9df..5492a8f 100644 (file)
@@ -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):
         """
index ab0467a..9113b0c 100644 (file)
@@ -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"
index 33d7791..133cb9d 100644 (file)
@@ -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):
         """
index fd2b3e8..2b54ae5 100644 (file)
@@ -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