Feature7912- Enhancement of NBI API to collect VNF metrics 68/7968/9
authorpreethika.p <preethika.p@tataelxsi.co.in>
Fri, 20 Sep 2019 11:07:50 +0000 (16:37 +0530)
committerpreethika.p <preethika.p@tataelxsi.co.in>
Tue, 19 Nov 2019 09:03:48 +0000 (14:33 +0530)
Addressed the comments
Unittest added
Made changes in tox file
Change-Id: Iad1e625f609272556bcc51b36c96a336d34b5c86
Signed-off-by: preethika.p <preethika.p@tataelxsi.co.in>
osm_nbi/engine.py
osm_nbi/pmjobs_topics.py
osm_nbi/tests/pmjob_mocks/response.py [new file with mode: 0644]
osm_nbi/tests/test_db_descriptors.py
osm_nbi/tests/test_pmjobs_topic.py [new file with mode: 0644]
requirements.txt
test-requirements.txt
tox.ini

index 0211bd6..0cd0666 100644 (file)
@@ -159,7 +159,8 @@ class Engine(object):
                 else:
                     self.map_topic[topic] = topic_class(self.db, self.fs, self.msg, self.auth)
             
-            self.map_topic["pm_jobs"] = PmJobsTopic(config["prometheus"].get("host"), config["prometheus"].get("port"))
+            self.map_topic["pm_jobs"] = PmJobsTopic(self.db, config["prometheus"].get("host"),
+                                                    config["prometheus"].get("port"))
         except (DbException, FsException, MsgException) as e:
             raise EngineException(str(e), http_code=e.http_code)
 
index 08b0b1d..7fa2035 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-
 import asyncio
 import aiohttp
+from http import HTTPStatus
+from urllib.parse import quote
 from osm_nbi.base_topic import EngineException
 
 __author__ = "Vijay R S <vijay.r@tataelxsi.co.in>"
 
 
 class PmJobsTopic():
-    def __init__(self, host=None, port=None):
+    def __init__(self, db, host=None, port=None):
+        self.db = db
         self.url = 'http://{}:{}'.format(host, port)
-        self.metric_list = ['cpu_utilization', 'average_memory_utilization', 'disk_read_ops',
-                            'disk_write_ops', 'disk_read_bytes', 'disk_write_bytes', 'packets_dropped',
-                            'packets_sent', 'packets_received']
+        self.nfvi_metric_list = ['cpu_utilization', 'average_memory_utilization', 'disk_read_ops',
+                                 'disk_write_ops', 'disk_read_bytes', 'disk_write_bytes',
+                                 'packets_dropped', 'packets_sent', 'packets_received']
+
+    def _get_vnf_metric_list(self, ns_id):
+        metric_list = self.nfvi_metric_list.copy()
+        vnfr_desc = self.db.get_list("vnfrs", {"nsr-id-ref": ns_id})
+        if not vnfr_desc:
+            raise EngineException("NS not found with id {}".format(ns_id), http_code=HTTPStatus.NOT_FOUND)
+        else:
+            for vnfr in vnfr_desc:
+                vnfd_desc = self.db.get_one("vnfds", {"_id": vnfr["vnfd-id"]}, fail_on_empty=True, fail_on_more=False)
+                if vnfd_desc.get("vdu"):
+                    for vdu in vnfd_desc['vdu']:
+                        # Checks for vdu metric in vdu-configuration
+                        if 'vdu-configuration' in vdu and 'metrics' in vdu['vdu-configuration']:
+                            metric_list.extend([quote(metric['name']) 
+                                               for metric in vdu["vdu-configuration"]["metrics"]])
+                # Checks for vnf metric in vnf-configutaion
+                if 'vnf-configuration' in vnfd_desc and 'metrics' in vnfd_desc['vnf-configuration']:
+                    metric_list.extend([quote(metric['name']) for metric in vnfd_desc["vnf-configuration"]["metrics"]])
+        metric_list = list(set(metric_list))
+        return metric_list
 
-    async def _prom_metric_request(self, ns_id):
+    async def _prom_metric_request(self, ns_id, metrics_list):
         try:
             async with aiohttp.ClientSession() as session:
                 data = []
-                for metlist in self.metric_list:
+                for metlist in metrics_list:
                     request_url = self.url+'/api/v1/query?query=osm_'+metlist+"{ns_id='"+ns_id+"'}"
                     async with session.get(request_url) as resp:
                         resp = await resp.json()
@@ -41,12 +63,13 @@ class PmJobsTopic():
                             data.append(resp)
                 return data
         except aiohttp.client_exceptions.ClientConnectorError as e:
-            raise EngineException("Connection Failure: {}".format(e))
+            raise EngineException("Connection to '{}'Failure: {}".format(self.url, e))
 
     def show(self, session, ns_id):
+        metrics_list = self._get_vnf_metric_list(ns_id)
         loop = asyncio.new_event_loop()
         asyncio.set_event_loop(loop)
-        prom_metric = loop.run_until_complete(self._prom_metric_request(ns_id))
+        prom_metric = loop.run_until_complete(self._prom_metric_request(ns_id, metrics_list))
         metric = {}
         metric_temp = []
         for index_list in prom_metric:
@@ -58,7 +81,10 @@ class PmJobsTopic():
                 process_metric['performanceValue']['performanceValue']['performanceValue'] = index['value'][1]
                 process_metric['performanceValue']['performanceValue']['vnfMemberIndex'] \
                     = index['metric']['vnf_member_index']
-                process_metric['performanceValue']['performanceValue']['vduName'] = index['metric']['vdu_name']
+                if 'vdu_name' not in index['metric']:
+                    pass
+                else:
+                    process_metric['performanceValue']['performanceValue']['vduName'] = index['metric']['vdu_name']
                 metric_temp.append(process_metric)
         metric['entries'] = metric_temp
         return metric
diff --git a/osm_nbi/tests/pmjob_mocks/response.py b/osm_nbi/tests/pmjob_mocks/response.py
new file mode 100644 (file)
index 0000000..fa674ad
--- /dev/null
@@ -0,0 +1,139 @@
+# Copyright 2019 Preethika P(Tata Elxsi)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+__author__ = "Preethika P,preethika.p@tataelxsi.co.in"
+
+"""Excepted results for pmjob functions and prometheus"""
+
+
+show_res = """
+---
+entries:
+-   objectInstanceId: f48163a6-c807-47bc-9682-f72caef5af85
+    performanceMetric: osm_users
+    performanceValue:
+        performanceValue:
+            performanceValue: '1'
+            vduName: test_metric-1-ubuntuvdu1-1
+            vnfMemberIndex: '1'
+        timestamp: 1573552141.409
+-   objectInstanceId: f48163a6-c807-47bc-9682-f72caef5af85
+    performanceMetric: osm_cpu_utilization
+    performanceValue:
+        performanceValue:
+            performanceValue: '0.7622979249'
+            vduName: test_metric-1-ubuntuvdu1-1
+            vnfMemberIndex: '1'
+        timestamp: 1573556383.439
+-   objectInstanceId: f48163a6-c807-47bc-9682-f72caef5af85
+    performanceMetric: osm_load
+    performanceValue:
+        performanceValue:
+            performanceValue: '0'
+            vduName: test_metric-1-ubuntuvdu1-1
+            vnfMemberIndex: '1'
+        timestamp: 1573552060.035
+"""
+prom_res = """
+---
+- - metric:
+      __name__: osm_users
+      instance: mon:8000
+      job: prometheus
+      ns_id: f48163a6-c807-47bc-9682-f72caef5af85
+      vdu_name: test_metric-1-ubuntuvdu1-1
+      vnf_member_index: '1'
+    value:
+    - 1573552141.409
+    - '1'
+- - metric:
+      __name__: osm_load
+      instance: mon:8000
+      job: prometheus
+      ns_id: f48163a6-c807-47bc-9682-f72caef5af85
+      vdu_name: test_metric-1-ubuntuvdu1-1
+      vnf_member_index: '1'
+    value:
+    - 1573552060.035
+    - '0'
+- - metric:
+      __name__: osm_cpu_utilization
+      instance: mon:8000
+      job: prometheus
+      ns_id: f48163a6-c807-47bc-9682-f72caef5af85
+      vdu_name: test_metric-1-ubuntuvdu1-1
+      vnf_member_index: '1'
+    value:
+    - 1573556383.439
+    - '0.7622979249'
+"""
+cpu_utilization = """
+---
+status: success
+data:
+  resultType: vector
+  result:
+  - metric:
+      __name__: osm_cpu_utilization
+      instance: mon:8000
+      job: prometheus
+      ns_id: f48163a6-c807-47bc-9682-f72caef5af85
+      vdu_name: test_metric-1-ubuntuvdu1-1
+      vnf_member_index: '1'
+    value:
+    - 1573556383.439
+    - '0.7622979249'
+"""
+users = """
+---
+status: success
+data:
+  resultType: vector
+  result:
+  - metric:
+      __name__: osm_users
+      instance: mon:8000
+      job: prometheus
+      ns_id: f48163a6-c807-47bc-9682-f72caef5af85
+      vdu_name: test_metric-1-ubuntuvdu1-1
+      vnf_member_index: '1'
+    value:
+    - 1573552141.409
+    - '1'
+"""
+load = """
+---
+status: success
+data:
+  resultType: vector
+  result:
+  - metric:
+      __name__: osm_load
+      instance: mon:8000
+      job: prometheus
+      ns_id: f48163a6-c807-47bc-9682-f72caef5af85
+      vdu_name: test_metric-1-ubuntuvdu1-1
+      vnf_member_index: '1'
+    value:
+    - 1573552060.035
+    - '0'
+"""
+empty = """
+---
+status: success
+data:
+  resultType: vector
+  result: []
+"""
index d445d09..057230c 100644 (file)
@@ -105,6 +105,18 @@ db_vnfds_text = """
         vdu-monitoring-param:
             vdu-monitoring-param-ref: dataVM_cpu_util
             vdu-ref: dataVM
+    -   id: dataVM_users
+        aggregation-type: AVERAGE
+        name: dataVM_users
+        vdu-metric:
+            vdu-metric-name-ref: users
+            vdu-ref: dataVM
+    -   id: dataVM_load
+        aggregation-type: AVERAGE
+        name: dataVM_load
+        vdu-metric:
+            vdu-metric-name-ref: load
+            vdu-ref: dataVM
     name: hackfest3charmed-vnf
     scaling-group-descriptor:
     -   max-instance-count: 10
@@ -183,6 +195,23 @@ db_vnfds_text = """
         -   id: dataVM_cpu_util
             nfvi-metric: cpu_utilization
         name: dataVM
+        vdu-configuration:
+          initial-config-primitive:
+          -   parameter:
+              -   value: "<rw_mgmt_ip>"
+                  name: ssh-hostname
+              -   value: ubuntu
+                  name: ssh-username
+              -   value: osm2018
+                  name: ssh-password
+              name: config
+              seq: '1'
+          metrics:
+          -   name: users
+          -   name: load
+          juju:
+            proxy: true
+            charm: testmetrics
         vm-flavor:
             memory-mb: '1024'
             storage-gb: '10'
diff --git a/osm_nbi/tests/test_pmjobs_topic.py b/osm_nbi/tests/test_pmjobs_topic.py
new file mode 100644 (file)
index 0000000..5bcb383
--- /dev/null
@@ -0,0 +1,105 @@
+# Copyright 2019 Preethika P(Tata Elxsi)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+__author__ = "Preethika P,preethika.p@tataelxsi.co.in"
+
+import asynctest
+import yaml
+import re
+from aioresponses import aioresponses
+from http import HTTPStatus
+from osm_nbi.engine import EngineException
+from osm_common.dbmemory import DbMemory
+from osm_nbi.pmjobs_topics import PmJobsTopic
+from osm_nbi.tests.test_db_descriptors import db_nsds_text, db_vnfds_text, db_nsrs_text, db_vnfrs_text
+from osm_nbi.tests.pmjob_mocks.response import show_res, prom_res, cpu_utilization, users, load, empty
+
+
+class PmJobsTopicTest(asynctest.TestCase):
+
+    def setUp(self):
+        self.db = DbMemory()
+        self.pmjobs_topic = PmJobsTopic(self.db, host="prometheus", port=9091)
+        self.db.create_list("nsds", yaml.load(db_nsds_text, Loader=yaml.Loader))
+        self.db.create_list("vnfds", yaml.load(db_vnfds_text, Loader=yaml.Loader))
+        self.db.create_list("vnfrs", yaml.load(db_vnfrs_text, Loader=yaml.Loader))
+        self.db.create_list("nsrs", yaml.load(db_nsrs_text, Loader=yaml.Loader))
+        self.nsr = self.db.get_list("nsrs")[0]
+        self.nsr_id = self.nsr["_id"]
+        project_id = self.nsr["_admin"]["projects_write"]
+        """metric_check_list contains the vnf metric name used in descriptor i.e users,load"""
+        self.metric_check_list = ['cpu_utilization', 'average_memory_utilization', 'disk_read_ops',
+                                  'disk_write_ops', 'disk_read_bytes', 'disk_write_bytes',
+                                  'packets_dropped', 'packets_sent', 'packets_received', 'users', 'load']
+        self.session = {"username": "admin", "project_id": project_id, "method": None,
+                        "admin": True, "force": False, "public": False, "allow_show_user_project_role": True}
+
+    def set_get_mock_res(self, mock_res, ns_id, metric_list):
+        site = "http://prometheus:9091/api/v1/query?query=osm_metric_name{ns_id='nsr'}"
+        site = re.sub(r'nsr', ns_id, site)
+        for metric in metric_list:
+            endpoint = re.sub(r'metric_name', metric, site)
+            if metric == 'cpu_utilization':
+                response = yaml.load(cpu_utilization, Loader=yaml.Loader)
+            elif metric == 'users':
+                response = yaml.load(users, Loader=yaml.Loader)
+            elif metric == 'load':
+                response = yaml.load(load, Loader=yaml.Loader)
+            else:
+                response = yaml.load(empty, Loader=yaml.Loader)
+            mock_res.get(endpoint, payload=response)
+
+    def test_get_vnf_metric_list(self):
+        with self.subTest("Test case1 failed in test_get_vnf_metric_list"):
+            metric_list = self.pmjobs_topic._get_vnf_metric_list(self.nsr_id)
+            self.assertCountEqual(metric_list, self.metric_check_list,
+                                  "VNF metric list is not correctly fetched")
+        with self.subTest("Test case2 failed in test_get_vnf_metric_list"):
+            wrong_ns_id = "88d90b0c-faff-4bbc-cccc-aaaaaaaaaaaa"
+            with self.assertRaises(EngineException, msg="ns not found") as e:
+                self.pmjobs_topic._get_vnf_metric_list(wrong_ns_id)
+            self.assertEqual(e.exception.http_code, HTTPStatus.NOT_FOUND, "Wrong HTTP status code")
+            self.assertIn("NS not found with id {}".format(wrong_ns_id),
+                          str(e.exception), "Wrong exception text")
+
+    async def test_prom_metric_request(self):
+        with self.subTest("Test case1 failed in test_prom"):
+            prom_response = yaml.load(prom_res, Loader=yaml.Loader)
+            with aioresponses() as mock_res:
+                self.set_get_mock_res(mock_res, self.nsr_id, self.metric_check_list)
+                result = await self.pmjobs_topic._prom_metric_request(self.nsr_id, self.metric_check_list)
+            self.assertCountEqual(result, prom_response, "Metric Data is valid")
+        with self.subTest("Test case2 failed in test_prom"):
+            with self.assertRaises(EngineException, msg="Prometheus not reachable") as e:
+                await self.pmjobs_topic._prom_metric_request(self.nsr_id, self.metric_check_list)
+            self.assertIn("Connection to ", str(e.exception), "Wrong exception text")
+
+    def test_show(self):
+        with self.subTest("Test case1 failed in test_show"):
+            show_response = yaml.load(show_res, Loader=yaml.Loader)
+            with aioresponses() as mock_res:
+                self.set_get_mock_res(mock_res, self.nsr_id, self.metric_check_list)
+                result = self.pmjobs_topic.show(self.session, self.nsr_id)
+            self.assertEqual(len(result['entries']), 3, "Number of metrics returned")
+            self.assertCountEqual(result, show_response, "Response is valid")
+        with self.subTest("Test case2 failed in test_show"):
+            wrong_ns_id = "88d90b0c-faff-4bbc-cccc-aaaaaaaaaaaa"
+            with aioresponses() as mock_res:
+                self.set_get_mock_res(mock_res, wrong_ns_id, self.metric_check_list)
+                with self.assertRaises(EngineException, msg="ns not found") as e:
+                    self.pmjobs_topic.show(self.session, wrong_ns_id)
+                self.assertEqual(e.exception.http_code, HTTPStatus.NOT_FOUND, "Wrong HTTP status code")
+                self.assertIn("NS not found with id {}".format(wrong_ns_id), str(e.exception),
+                              "Wrong exception text")
index 6928de1..41bc5f6 100644 (file)
@@ -17,5 +17,5 @@ python-keystoneclient
 requests
 git+https://osm.etsi.org/gerrit/osm/common.git#egg=osm-common
 git+https://osm.etsi.org/gerrit/osm/IM.git#egg=osm-im
-aiohttp==0.20.2
+aiohttp==2.3.10
 
index 8f9f3b4..560046d 100644 (file)
@@ -15,4 +15,6 @@
 # mock
 # pyangbind
 pyang
+aioresponses
+asynctest
 
diff --git a/tox.ini b/tox.ini
index ef4af53..d56ee5c 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -21,7 +21,6 @@ toxworkdir={toxinidir}/.tox
 usedevelop = True
 basepython = python3
 install_command = python3 -m pip install -r requirements.txt   -U {opts} {packages}
-deps = -r{toxinidir}/test-requirements.txt
 
 [testenv:flake8]
 basepython = python3
@@ -37,5 +36,7 @@ commands = python3 setup.py --command-packages=stdeb.command bdist_deb
 
 [testenv:unittest]
 basepython = python3
+deps = asynctest
+       aioresponses
 commands = python3 -m unittest discover {toxinidir}/osm_nbi/tests -v