Feature-9904: Enhancing NG-UI to enable Juju operational view dashboard 69/10469/6 release-v10.0-start
authorK Sai Kiran <saikiran.k@tataelxsi.co.in>
Wed, 27 Jan 2021 16:08:34 +0000 (21:38 +0530)
committerksaikiranr <saikiran.k@tataelxsi.co.in>
Tue, 18 May 2021 16:25:01 +0000 (21:55 +0530)
Added query parameter to NSR api and to send a message to kafka when
conditions are met to trigger vca status refresh. Included unit tests
for functions implemented

Change-Id: Ibf691082cde15d8ee42f1a4ebaebedd99d674e8c
Signed-off-by: ksaikiranr <saikiran.k@tataelxsi.co.in>
osm_nbi/admin_topics.py
osm_nbi/base_topic.py
osm_nbi/engine.py
osm_nbi/instance_topics.py
osm_nbi/nbi.py
osm_nbi/pmjobs_topics.py
osm_nbi/tests/test_instance_topics.py

index 0006917..786d237 100644 (file)
@@ -922,12 +922,13 @@ class UserTopicAuth(UserTopic):
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
 
-    def show(self, session, _id, api_req=False):
+    def show(self, session, _id, filter_q=None, 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 or username
+        :param filter_q: dict: query parameter
         :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.
         """
@@ -1295,12 +1296,13 @@ class ProjectTopicAuth(ProjectTopic):
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
 
-    def show(self, session, _id, api_req=False):
+    def show(self, session, _id, filter_q=None, 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 filter_q: dict: query parameter
         :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.
         """
@@ -1594,12 +1596,13 @@ class RoleTopicAuth(BaseTopic):
             final_content["permissions"]["admin"] = False
         return None
 
-    def show(self, session, _id, api_req=False):
+    def show(self, session, _id, filter_q=None, 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 filter_q: dict: query parameter
         :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.
         """
index 722ff59..8c67c2d 100644 (file)
@@ -411,11 +411,12 @@ class BaseTopic:
         # Projection was moved to child classes
         return data
 
-    def show(self, session, _id, api_req=False):
+    def show(self, session, _id, filter_q=None, 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 filter_q: dict: query parameter
         :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.
         """
index 1bc9171..cb27414 100644 (file)
@@ -288,25 +288,22 @@ class Engine(object):
         :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
-            )
+            raise EngineException("Unknown topic {}!!!".format(topic), HTTPStatus.INTERNAL_SERVER_ERROR)
         return self.map_topic[topic].list(session, filter_q, api_req)
 
-    def get_item(self, session, topic, _id, api_req=False):
+    def get_item(self, session, topic, _id, filter_q=None, 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 filter_q: other arguments
         :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, api_req)
+            raise EngineException("Unknown topic {}!!!".format(topic), HTTPStatus.INTERNAL_SERVER_ERROR)
+        return self.map_topic[topic].show(session, _id, filter_q, api_req)
 
     def get_file(self, session, topic, _id, path=None, accept_header=None):
         """
index 87b186e..1486133 100644 (file)
@@ -945,6 +945,41 @@ class NsrTopic(BaseTopic):
 
         return vnfr_descriptor
 
+    def vca_status_refresh(self, session, ns_instance_content, filter_q):
+        """
+        vcaStatus in ns_instance_content maybe stale, check if it is stale and create lcm op
+        to refresh vca status by sending message to LCM when it is stale. Ignore otherwise.
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
+        :param ns_instance_content:  ns instance content
+        :param filter_q: dict: query parameter containing vcaStatus-refresh as true or false
+        :return: None
+        """
+        time_now, time_delta = time(), time() - ns_instance_content["_admin"]["modified"]
+        force_refresh = isinstance(filter_q, dict) and filter_q.get('vcaStatusRefresh') == 'true'
+        threshold_reached = time_delta > 120
+        if force_refresh or threshold_reached:
+            operation, _id = "vca_status_refresh", ns_instance_content["_id"]
+            ns_instance_content["_admin"]["modified"] = time_now
+            self.db.set_one(self.topic, {"_id": _id}, ns_instance_content)
+            nslcmop_desc = NsLcmOpTopic._create_nslcmop(_id, operation, None)
+            self.format_on_new(nslcmop_desc, session["project_id"], make_public=session["public"])
+            nslcmop_desc["_admin"].pop("nsState")
+            self.msg.write("ns", operation, nslcmop_desc)
+        return
+
+    def show(self, session, _id, filter_q=None, api_req=False):
+        """
+        Get complete information on an ns instance.
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
+        :param _id: string, ns instance id
+        :param filter_q: dict: query parameter containing vcaStatusRefresh as true or false
+        :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.
+        """
+        ns_instance_content = super().show(session, _id, api_req)
+        self.vca_status_refresh(session, ns_instance_content, filter_q)
+        return ns_instance_content
+
     def edit(self, session, _id, indata=None, kwargs=None, content=None):
         raise EngineException(
             "Method edit called directly", HTTPStatus.INTERNAL_SERVER_ERROR
index bec0cfa..d5a81ce 100644 (file)
@@ -1297,9 +1297,10 @@ class Server(object):
                     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, True
-                    )
+                    filter_q = None
+                    if "vcaStatusRefresh" in kwargs:
+                        filter_q = {"vcaStatusRefresh": kwargs["vcaStatusRefresh"]}
+                    outdata = self.engine.get_item(engine_session, engine_topic, _id, filter_q, True)
 
             elif method == "POST":
                 cherrypy.response.status = HTTPStatus.CREATED.value
index f8d7714..514d442 100644 (file)
@@ -90,7 +90,7 @@ class PmJobsTopic:
         except aiohttp.client_exceptions.ClientConnectorError as e:
             raise EngineException("Connection to '{}'Failure: {}".format(self.url, e))
 
-    def show(self, session, ns_id, api_req=False):
+    def show(self, session, ns_id, filter_q=None, api_req=False):
         metrics_list = self._get_vnf_metric_list(ns_id)
         loop = asyncio.new_event_loop()
         asyncio.set_event_loop(loop)
index 5e86ae5..8efb1f9 100644 (file)
@@ -16,7 +16,8 @@
 ##
 
 import unittest
-from unittest.mock import Mock, mock_open  # patch, MagicMock
+from time import time
+from unittest.mock import Mock, mock_open   # patch, MagicMock
 from osm_common.dbbase import DbException
 from osm_nbi.engine import EngineException
 from osm_common.dbmemory import DbMemory
@@ -375,11 +376,54 @@ class TestNsrTopic(unittest.TestCase):
                 self.assertTrue(e.exception.http_code == expect_code)
             if expect_text_list:
                 for expect_text in expect_text_list:
-                    self.assertIn(
-                        expect_text,
-                        str(e.exception).lower(),
-                        "Expected '{}' at exception text".format(expect_text),
-                    )
+                    self.assertIn(expect_text, str(e.exception).lower(),
+                                  "Expected '{}' at exception text".format(expect_text))
+
+    def test_show_instance(self):
+        session = {"force": False, "admin": False, "public": False, "project_id": [self.nsd_project], "method": "write"}
+        filter_q = {}
+        for refresh_status in ("true", "false"):
+            self.db.create_list("nsrs", yaml.load(db_nsrs_text, Loader=yaml.Loader))
+            actual_nsr = self.db.get_list("nsrs")[0]
+            nsr_id = actual_nsr["_id"]
+            filter_q['vcaStatus-refresh'] = refresh_status
+            expected_nsr = self.nsr_topic.show(session, nsr_id, filter_q=filter_q)
+            self.nsr_topic.delete(session, nsr_id)
+            actual_nsr.pop("_admin")
+            expected_nsr.pop("_admin")
+            self.assertEqual(expected_nsr, actual_nsr, "Database nsr and show() nsr do not match.")
+
+    def test_vca_status_refresh(self):
+        session = {"force": False, "admin": False, "public": False, "project_id": [self.nsd_project], "method": "write"}
+        filter_q = {'vcaStatus-refresh': 'true'}
+        time_delta = 120
+        self.db.create_list("nsrs", yaml.load(db_nsrs_text, Loader=yaml.Loader))
+        nsr = self.db.get_list("nsrs")[0]
+
+        # When vcaStatus-refresh is true
+        filter_q['vcaStatus-refresh'] = "true"
+        self.nsr_topic.vca_status_refresh(session, nsr, filter_q)
+        msg_args = self.msg.write.call_args[0]
+        self.assertEqual(msg_args[1], "vca_status_refresh", "Wrong message action")
+        self.assertGreater(nsr["_admin"]["modified"], time() - time_delta)
+
+        # When vcaStatus-refresh is false but modified time is within threshold
+        filter_q['vcaStatus-refresh'] = "false"
+        time_now = time()
+        nsr["_admin"]["modified"] = time_now
+        self.nsr_topic.vca_status_refresh(session, nsr, filter_q)
+        msg_args = self.msg.write.call_args[1]
+        self.assertEqual(msg_args, {}, "Message should not be sent.")
+        self.assertEqual(nsr["_admin"]["modified"], time_now, "Modified time should not be changed.")
+
+        # When vcaStatus-refresh is false but modified time is less than threshold
+        filter_q['vcaStatus-refresh'] = "false"
+        nsr["_admin"]["modified"] = time() - (2*time_delta)
+        self.nsr_topic.vca_status_refresh(session, nsr, filter_q)
+        msg_args = self.msg.write.call_args[0]
+        self.assertEqual(msg_args[1], "vca_status_refresh", "Wrong message action")
+        self.nsr_topic.delete(session, nsr["_id"])
+        self.assertGreater(nsr["_admin"]["modified"], time() - time_delta, "Modified time is not changed.")
 
     def test_delete_ns(self):
         self.db.create_list("nsrs", yaml.load(db_nsrs_text, Loader=yaml.Loader))