Feature 10908: Update Charms in Running VNF instances 43/11843/14
authoraticig <gulsum.atici@canonical.com>
Tue, 5 Apr 2022 06:00:17 +0000 (09:00 +0300)
committergarciadeblas <gerardo.garciadeblas@telefonica.com>
Wed, 18 May 2022 13:47:33 +0000 (15:47 +0200)
New URL in NBI to accept the update and put the
update task into Kafka bus.

Change-Id: Iaeaabc4e33a0662f9e7afaf7794cddd2bd6dea1a
Signed-off-by: aticig <gulsum.atici@canonical.com>
osm_nbi/instance_topics.py
osm_nbi/nbi.py
osm_nbi/resources_to_operations.yml
osm_nbi/tests/test_instance_topics.py
osm_nbi/validation.py

index 8632e94..74815ca 100644 (file)
@@ -26,6 +26,7 @@ from osm_nbi.validation import (
     ns_terminate,
     ns_action,
     ns_scale,
+    ns_update,
     nsi_instantiate,
 )
 from osm_nbi.base_topic import (
@@ -1154,6 +1155,7 @@ class NsLcmOpTopic(BaseTopic):
     operation_schema = {  # mapping between operation and jsonschema to validate
         "instantiate": ns_instantiate,
         "action": ns_action,
+        "update": ns_update,
         "scale": ns_scale,
         "terminate": ns_terminate,
     }
@@ -1165,7 +1167,7 @@ class NsLcmOpTopic(BaseTopic):
         """
         Check that user has enter right parameters for the operation
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
-        :param operation: it can be: instantiate, terminate, action, TODO: update, heal
+        :param operation: it can be: instantiate, terminate, action, update. TODO: heal
         :param indata: descriptor with the parameters of the operation
         :return: None
         """
@@ -1173,6 +1175,8 @@ class NsLcmOpTopic(BaseTopic):
             self._check_action_ns_operation(indata, nsr)
         elif operation == "scale":
             self._check_scale_ns_operation(indata, nsr)
+        elif operation == "update":
+            self._check_update_ns_operation(indata, nsr)
         elif operation == "instantiate":
             self._check_instantiate_ns_operation(indata, nsr, session)
 
@@ -1265,6 +1269,85 @@ class NsLcmOpTopic(BaseTopic):
                 )
             )
 
+    def _check_update_ns_operation(self, indata, nsr) -> None:
+        """Validates the ns-update request according to updateType
+
+        If updateType is CHANGE_VNFPKG:
+        - it checks the vnfInstanceId, whether it's available under ns instance
+        - it checks the vnfdId whether it matches with the vnfd-id in the vnf-record of specified VNF.
+        Otherwise exception will be raised.
+
+        Args:
+            indata: includes updateType such as CHANGE_VNFPKG,
+            nsr: network service record
+
+        Raises:
+           EngineException:
+                a meaningful error if given update parameters are not proper such as
+                "Error in validating ns-update request: <ID> does not match
+                with the vnfd-id of vnfinstance
+                http_code=HTTPStatus.UNPROCESSABLE_ENTITY"
+
+        """
+        try:
+            if indata["updateType"] == "CHANGE_VNFPKG":
+                # vnfInstanceId, nsInstanceId, vnfdId are mandatory
+                vnf_instance_id = indata["changeVnfPackageData"]["vnfInstanceId"]
+                ns_instance_id = indata["nsInstanceId"]
+                vnfd_id_2update = indata["changeVnfPackageData"]["vnfdId"]
+
+                if vnf_instance_id not in nsr["constituent-vnfr-ref"]:
+
+                    raise EngineException(
+                        f"Error in validating ns-update request: vnf {vnf_instance_id} does not "
+                        f"belong to NS {ns_instance_id}",
+                        http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
+                    )
+
+                # Getting vnfrs through the ns_instance_id
+                vnfrs = self.db.get_list("vnfrs", {"nsr-id-ref": ns_instance_id})
+                constituent_vnfd_id = next(
+                    (
+                        vnfr["vnfd-id"]
+                        for vnfr in vnfrs
+                        if vnfr["id"] == vnf_instance_id
+                    ),
+                    None,
+                )
+
+                # Check the given vnfd-id belongs to given vnf instance
+                if constituent_vnfd_id and (vnfd_id_2update != constituent_vnfd_id):
+
+                    raise EngineException(
+                        f"Error in validating ns-update request: vnfd-id {vnfd_id_2update} does not "
+                        f"match with the vnfd-id: {constituent_vnfd_id} of VNF instance: {vnf_instance_id}",
+                        http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
+                    )
+
+                # Validating the ns update timeout
+                if (
+                    indata.get("timeout_ns_update")
+                    and indata["timeout_ns_update"] < 300
+                ):
+                    raise EngineException(
+                        "Error in validating ns-update request: {} second is not enough "
+                        "to upgrade the VNF instance: {}".format(
+                            indata["timeout_ns_update"], vnf_instance_id
+                        ),
+                        http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
+                    )
+
+        except (
+            DbException,
+            AttributeError,
+            IndexError,
+            KeyError,
+            ValueError,
+        ) as e:
+            raise type(e)(
+                "Ns update request could not be processed with error: {}.".format(e)
+            )
+
     def _check_scale_ns_operation(self, indata, nsr):
         vnfd = self._get_vnfd_from_vnf_member_index(
             indata["scaleVnfData"]["scaleByStepData"]["member-vnf-index"], nsr["_id"]
@@ -2001,7 +2084,7 @@ class NsLcmOpTopic(BaseTopic):
         """
         Creates a ns-lcm-opp content to be stored at database.
         :param nsr_id: internal id of the instance
-        :param operation: instantiate, terminate, scale, action, ...
+        :param operation: instantiate, terminate, scale, action, update ...
         :param params: user parameters for the operation
         :return: dictionary following SOL005 format
         """
@@ -2057,7 +2140,7 @@ class NsLcmOpTopic(BaseTopic):
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param indata: descriptor with the parameters of the operation. It must contains among others
             nsInstanceId: _id of the nsr to perform the operation
-            operation: it can be: instantiate, terminate, action, TODO: update, heal
+            operation: it can be: instantiate, terminate, action, update TODO: heal
         :param kwargs: used to override the indata descriptor
         :param headers: http request headers
         :return: id of the nslcmops
index 502207f..29fcacd 100644 (file)
@@ -86,6 +86,7 @@ URL: /osm                                                       GET     POST
                     action                                              O
                     scale                                               O5
                     heal                                                5
+                    update                                              05
             /ns_lcm_op_occs                                     5       5
                 /<nsLcmOpOccId>                                 5                       5       5
                     TO BE COMPLETED                             5               5
@@ -447,6 +448,10 @@ valid_url_methods = {
                         "METHODS": ("POST",),
                         "ROLE_PERMISSION": "ns_instances:id:action:",
                     },
+                    "update": {
+                        "METHODS": ("POST",),
+                        "ROLE_PERMISSION": "ns_instances:id:update:",
+                    },
                 },
             },
             "ns_lcm_op_occs": {
index 07f6a8f..8880817 100644 (file)
@@ -131,6 +131,8 @@ resources_to_operations:
 
   "POST /nslcm/v1/ns_instances/<nsInstanceId>/action": "ns_instances:id:action:post"
 
+  "POST /nslcm/v1/ns_instances/<nsInstanceId>/update": "ns_instances:id:update:post"
+
   "POST /nslcm/v1/ns_instances/<nsInstanceId>/scale": "ns_instances:id:scale:post"
 
   "GET /nslcm/v1/ns_lcm_op_occs": "ns_instances:opps:get"
index cbb80ef..b05ee0c 100644 (file)
@@ -15,6 +15,7 @@
 # contact: esousa@whitestack.com or alfonso.tiernosepulveda@telefonica.com
 ##
 
+from contextlib import contextmanager
 import unittest
 from time import time
 from unittest.mock import Mock, mock_open   # patch, MagicMock
@@ -255,6 +256,106 @@ class TestNsLcmOpTopicWithMock(unittest.TestCase):
         self.assertEqual(self.db.get_one.call_args_list[0][0][0], 'vnfrs', "Incorrect first DB lookup")
         self.assertEqual(self.db.get_one.call_args_list[1][0][0], 'vnfds_revisions', "Incorrect second DB lookup")
 
+    @contextmanager
+    def assertNotRaises(self, exception_type):
+        try:
+            yield None
+        except exception_type:
+            raise self.failureException("{} raised".format(exception_type.__name__))
+
+    def test_check_ns_update_operation(self):
+        self.db = DbMemory()
+        self.nslcmop_topic = NsLcmOpTopic(self.db, self.fs, self.msg, None)
+        session = {}
+
+        with self.subTest(i=1, t="VNF instance does not belong to NS"):
+            test_vnfr = yaml.load(db_vnfrs_text, Loader=yaml.Loader)
+            test_vnfr[0]["revision"] = 2
+            test_nsr = yaml.load(db_nsrs_text, Loader=yaml.Loader)
+            test_nsr[0]["constituent-vnfr-ref"][
+                0
+            ] = "99d90b0c-faff-4b9f-bccd-017f33985984"
+            self.db.create_list("vnfrs", test_vnfr)
+            self.db.create_list("nsrs", test_nsr)
+            nsrs = self.db.get_list("nsrs")[0]
+            indata = {
+                "updateType": "CHANGE_VNFPKG",
+                "changeVnfPackageData": {
+                    "vnfInstanceId": "88d90b0c-faff-4b9f-bccd-017f33985984",
+                    "vnfdId": "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77",
+                },
+                "nsInstanceId": "f48163a6-c807-47bc-9682-f72caef5af85",
+            }
+            with self.assertRaises(EngineException) as expected_exception:
+                self.nslcmop_topic._check_ns_operation(session, nsrs, "update", indata)
+            self.assertEqual(
+                str(expected_exception.exception),
+                "Error in validating ns-update request: vnf 88d90b0c-faff-4b9f-bccd-017f33985984"
+                " does not belong to NS f48163a6-c807-47bc-9682-f72caef5af85",
+            )
+
+        with self.subTest(i=2, t="Ns update request validated with no exception"):
+            test_vnfr = yaml.load(db_vnfrs_text, Loader=yaml.Loader)
+            test_vnfr[0]["revision"] = 2
+            test_nsr = yaml.load(db_nsrs_text, Loader=yaml.Loader)
+            self.db.create_list("vnfrs", test_vnfr)
+            self.db.create_list("nsrs", test_nsr)
+            nsrs = self.db.get_list("nsrs")[1]
+            indata = {
+                "updateType": "CHANGE_VNFPKG",
+                "changeVnfPackageData": {
+                    "vnfInstanceId": "88d90b0c-faff-4b9f-bccd-017f33985984",
+                    "vnfdId": "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77",
+                },
+                "nsInstanceId": "f48163a6-c807-47bc-9682-f72caef5af85",
+            }
+            with self.assertNotRaises(EngineException):
+                self.nslcmop_topic._check_ns_operation(session, nsrs, "update", indata)
+
+        with self.subTest(
+            i=3, t="Ns update request rejected because of too small timeout"
+        ):
+            indata = {
+                "updateType": "CHANGE_VNFPKG",
+                "changeVnfPackageData": {
+                    "vnfInstanceId": "88d90b0c-faff-4b9f-bccd-017f33985984",
+                    "vnfdId": "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77",
+                },
+                "nsInstanceId": "f48163a6-c807-47bc-9682-f72caef5af85",
+                "timeout_ns_update": 50,
+            }
+            with self.assertRaises(EngineException) as expected_exception:
+                self.nslcmop_topic._check_ns_operation(session, nsrs, "update", indata)
+            self.assertEqual(
+                str(expected_exception.exception),
+                "Error in validating ns-update request: 50 second is not enough "
+                "to upgrade the VNF instance: 88d90b0c-faff-4b9f-bccd-017f33985984",
+            )
+
+        with self.subTest(i=4, t="wrong vnfdid is given as an update parameter"):
+            test_vnfr = yaml.load(db_vnfrs_text, Loader=yaml.Loader)
+            test_vnfr[0]["revision"] = 2
+            test_nsr = yaml.load(db_nsrs_text, Loader=yaml.Loader)
+            self.db.create_list("vnfrs", test_vnfr)
+            self.db.create_list("nsrs", test_nsr)
+            nsrs = self.db.get_list("nsrs")[2]
+            indata = {
+                "updateType": "CHANGE_VNFPKG",
+                "changeVnfPackageData": {
+                    "vnfInstanceId": "88d90b0c-faff-4b9f-bccd-017f33985984",
+                    "vnfdId": "9637bcf8-cf14-42dc-ad70-c66fcf1e6e77",
+                },
+                "nsInstanceId": "f48163a6-c807-47bc-9682-f72caef5af85",
+            }
+            with self.assertRaises(EngineException) as expected_exception:
+                self.nslcmop_topic._check_ns_operation(session, nsrs, "update", indata)
+            self.assertEqual(
+                str(expected_exception.exception),
+                "Error in validating ns-update request: vnfd-id 9637bcf8-cf14-42dc-ad70-c66fcf1e6e77 does not "
+                "match with the vnfd-id: 7637bcf8-cf14-42dc-ad70-c66fcf1e6e77 of "
+                "VNF instance: 88d90b0c-faff-4b9f-bccd-017f33985984",
+            )
+
 
 class TestNsrTopic(unittest.TestCase):
     def setUp(self):
index 9bbde28..112461b 100644 (file)
@@ -440,6 +440,38 @@ ns_terminate = {
     "additionalProperties": False,
 }
 
+ns_update = {
+    "title": "ns update input schema",
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "type": "object",
+    "properties": {
+        "lcmOperationType": string_schema,
+        "nsInstanceId": id_schema,
+        "updateType": {
+            "enum": ["CHANGE_VNFPKG", "REMOVE_VNF", "MODIFY_VNF_INFORMATION"]
+        },
+        "modifyVnfInfoData": {
+            "type": "object",
+            "properties": {
+                "vnfInstanceId": id_schema,
+                "vnfdId": id_schema,
+            },
+            "required": ["vnfInstanceId", "vnfdId"],
+        },
+        "removeVnfInstanceId": id_schema,
+        "changeVnfPackageData": {
+            "type": "object",
+            "properties": {
+                "vnfInstanceId": id_schema,
+                "vnfdId": id_schema,
+            },
+            "required": ["vnfInstanceId", "vnfdId"],
+        },
+    },
+    "required": ["updateType"],
+    "additionalProperties": False,
+}
+
 ns_action = {  # TODO for the moment it is only contemplated the vnfd primitive execution
     "title": "ns action input schema",
     "$schema": "http://json-schema.org/draft-04/schema#",
@@ -1048,6 +1080,7 @@ nbi_new_input_schemas = {
     "ns_instantiate": ns_instantiate,
     "ns_action": ns_action,
     "ns_scale": ns_scale,
+    "ns_update": ns_update,
     "pdus": pdu_new_schema,
 }