Feature 10911-Vertical scaling of VM instances from OSM 05/12005/13
authorsritharan <priyadarshini@tataelxsi.co.in>
Thu, 5 May 2022 12:15:04 +0000 (12:15 +0000)
committersritharan <priyadarshini@tataelxsi.co.in>
Tue, 21 Jun 2022 12:47:54 +0000 (12:47 +0000)
Change-Id: Ie52edb13367a253ed3fc1a434ca2a170803035e4
Signed-off-by: sritharan <priyadarshini@tataelxsi.co.in>
13 files changed:
NG-RO/osm_ng_ro/ns.py
NG-RO/osm_ng_ro/ns_thread.py
NG-RO/osm_ng_ro/ro_main.py
NG-RO/osm_ng_ro/tests/test_ns.py
NG-RO/osm_ng_ro/tests/test_ns_thread.py
RO-VIM-aws/osm_rovim_aws/vimconn_aws.py
RO-VIM-azure/osm_rovim_azure/vimconn_azure.py
RO-VIM-gcp/osm_rovim_gcp/vimconn_gcp.py
RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py
RO-VIM-openvim/osm_rovim_openvim/vimconn_openvim.py
RO-VIM-vmware/osm_rovim_vmware/vimconn_vmware.py
RO-plugin/osm_ro_plugin/vimconn.py
releasenotes/notes/Feature_10911_Vertical_scaling_of_VM_instances_from_OSM-6b1b717a7e2b8377.yaml [new file with mode: 0644]

index 3f14c58..a823dbe 100644 (file)
@@ -2606,3 +2606,100 @@ class Ns(object):
                     exc_info=True,
                 )
             raise NsException(e)
+
+    def verticalscale_task(
+        self, vdu, vnf, vdu_index, action_id, nsr_id, task_index, extra_dict
+    ):
+        target_vim, vim_info = next(k_v for k_v in vdu["vim_info"].items())
+        self._assign_vim(target_vim)
+        target_record = "vnfrs:{}:vdur.{}".format(vnf["_id"], vdu_index)
+        target_record_id = "vnfrs:{}:vdur.{}".format(vnf["_id"], vdu["id"])
+        deployment_info = {
+            "action_id": action_id,
+            "nsr_id": nsr_id,
+            "task_index": task_index,
+        }
+
+        task = Ns._create_task(
+            deployment_info=deployment_info,
+            target_id=target_vim,
+            item="verticalscale",
+            action="EXEC",
+            target_record=target_record,
+            target_record_id=target_record_id,
+            extra_dict=extra_dict,
+        )
+        return task
+
+    def verticalscale(self, session, indata, version, nsr_id, *args, **kwargs):
+        task_index = 0
+        extra_dict = {}
+        now = time()
+        action_id = indata.get("action_id", str(uuid4()))
+        step = ""
+        logging_text = "Task deploy nsr_id={} action_id={} ".format(nsr_id, action_id)
+        self.logger.debug(logging_text + "Enter")
+        try:
+            VnfFlavorData = indata.get("changeVnfFlavorData")
+            vnf_instance_id = VnfFlavorData["vnfInstanceId"]
+            step = "Getting vnfrs from db"
+            db_vnfr = self.db.get_one("vnfrs", {"_id": vnf_instance_id})
+            vduid = VnfFlavorData["additionalParams"]["vduid"]
+            vduCountIndex = VnfFlavorData["additionalParams"]["vduCountIndex"]
+            virtualMemory = VnfFlavorData["additionalParams"]["virtualMemory"]
+            numVirtualCpu = VnfFlavorData["additionalParams"]["numVirtualCpu"]
+            sizeOfStorage = VnfFlavorData["additionalParams"]["sizeOfStorage"]
+            flavor_dict = {
+                "name": vduid + "-flv",
+                "ram": virtualMemory,
+                "vcpus": numVirtualCpu,
+                "disk": sizeOfStorage,
+            }
+            db_new_tasks = []
+            step = "Creating Tasks for vertical scaling"
+            with self.write_lock:
+                for vdu_index, vdu in enumerate(db_vnfr["vdur"]):
+                    if (
+                        vdu["vdu-id-ref"] == vduid
+                        and vdu["count-index"] == vduCountIndex
+                    ):
+                        extra_dict["params"] = {
+                            "vim_vm_id": vdu["vim-id"],
+                            "flavor_dict": flavor_dict,
+                        }
+                        task = self.verticalscale_task(
+                            vdu,
+                            db_vnfr,
+                            vdu_index,
+                            action_id,
+                            nsr_id,
+                            task_index,
+                            extra_dict,
+                        )
+                        db_new_tasks.append(task)
+                        task_index += 1
+                        break
+                self.upload_all_tasks(
+                    db_new_tasks=db_new_tasks,
+                    now=now,
+                )
+            self.logger.debug(
+                logging_text + "Exit. Created {} tasks".format(len(db_new_tasks))
+            )
+            return (
+                {"status": "ok", "nsr_id": nsr_id, "action_id": action_id},
+                action_id,
+                True,
+            )
+        except Exception as e:
+            if isinstance(e, (DbException, NsException)):
+                self.logger.error(
+                    logging_text + "Exit Exception while '{}': {}".format(step, e)
+                )
+            else:
+                e = traceback_format_exc()
+                self.logger.critical(
+                    logging_text + "Exit Exception while '{}': {}".format(step, e),
+                    exc_info=True,
+                )
+            raise NsException(e)
index 9ed04d7..8bb8d4b 100644 (file)
@@ -1402,6 +1402,75 @@ class VimInteractionMigration(VimInteractionBase):
             return "FAILED", ro_vim_item_update, db_task_update
 
 
+class VimInteractionResize(VimInteractionBase):
+    def exec(self, ro_task, task_index, task_depends):
+        task = ro_task["tasks"][task_index]
+        task_id = task["task_id"]
+        db_task_update = {"retries": 0}
+        created = False
+        target_flavor_uuid = None
+        created_items = {}
+        refreshed_vim_info = {}
+        target_vim = self.my_vims[ro_task["target_id"]]
+
+        try:
+            if task.get("params"):
+                vim_vm_id = task["params"].get("vim_vm_id")
+                flavor_dict = task["params"].get("flavor_dict")
+                self.logger.info("flavor_dict %s", flavor_dict)
+
+                try:
+                    target_flavor_uuid = target_vim.get_flavor_id_from_data(flavor_dict)
+                except Exception as e:
+                    self.logger.info("Cannot find any flavor matching  %s.", str(e))
+                    try:
+                        target_flavor_uuid = target_vim.new_flavor(flavor_dict)
+                    except Exception as e:
+                        self.logger.error("Error creating flavor at VIM  %s.", str(e))
+
+                if target_flavor_uuid is not None:
+                    resized_status = target_vim.resize_instance(
+                        vim_vm_id, target_flavor_uuid
+                    )
+
+                    if resized_status:
+                        # Refresh VM to get new vim_info
+                        vm_to_refresh_list = [vim_vm_id]
+                        vim_dict = target_vim.refresh_vms_status(vm_to_refresh_list)
+                        refreshed_vim_info = vim_dict[vim_vm_id]
+
+            ro_vim_item_update = {
+                "vim_id": vim_vm_id,
+                "vim_status": "DONE",
+                "created": created,
+                "created_items": created_items,
+                "vim_details": None,
+                "vim_message": None,
+            }
+
+            if refreshed_vim_info and refreshed_vim_info.get("status") not in (
+                "ERROR",
+                "VIM_ERROR",
+            ):
+                ro_vim_item_update["vim_details"] = refreshed_vim_info["vim_info"]
+
+            self.logger.debug(
+                "task={} {} resize done".format(task_id, ro_task["target_id"])
+            )
+            return "DONE", ro_vim_item_update, db_task_update
+        except (vimconn.VimConnException, NsWorkerException) as e:
+            self.logger.error(
+                "task={} vim={} Resize:" " {}".format(task_id, ro_task["target_id"], e)
+            )
+            ro_vim_item_update = {
+                "vim_status": "VIM_ERROR",
+                "created": created,
+                "vim_message": str(e),
+            }
+
+            return "FAILED", ro_vim_item_update, db_task_update
+
+
 class NsWorker(threading.Thread):
     REFRESH_BUILD = 5  # 5 seconds
     REFRESH_ACTIVE = 60  # 1 minute
@@ -1455,6 +1524,9 @@ class NsWorker(threading.Thread):
             "migrate": VimInteractionMigration(
                 self.db, self.my_vims, self.db_vims, self.logger
             ),
+            "verticalscale": VimInteractionResize(
+                self.db, self.my_vims, self.db_vims, self.logger
+            ),
         }
         self.time_last_task_processed = None
         # lists of tasks to delete because nsrs or vnfrs has been deleted from db
index bd2ea9e..2a2e22c 100644 (file)
@@ -143,6 +143,16 @@ valid_url_methods = {
                     },
                 },
             },
+            "verticalscale": {
+                "<ID>": {
+                    "METHODS": ("POST"),
+                    "ROLE_PERMISSION": "verticalscale:id:",
+                    "<ID>": {
+                        "METHODS": ("GET",),
+                        "ROLE_PERMISSION": "verticalscale:id:id:",
+                    },
+                },
+            },
         }
     },
 }
@@ -200,6 +210,7 @@ class Server(object):
             "recreate:id:post": self.ns.recreate,
             "recreate:id:id:get": self.ns.recreate_status,
             "migrate:id:post": self.ns.migrate,
+            "verticalscale:id:post": self.ns.verticalscale,
         }
 
     def _format_in(self, kwargs):
index 6c2615a..129e668 100644 (file)
@@ -2711,3 +2711,48 @@ class TestNs(unittest.TestCase):
             self.assertEqual(task.get("nsr_id"), nsr_id)
             self.assertEqual(task.get("target_id"), target_vim)
             self.assertDictEqual(task, expected_result)
+
+    @patch("osm_ng_ro.ns.Ns._assign_vim")
+    def test_verticalscale_task(self, assign_vim):
+        self.ns = Ns()
+        extra_dict = {}
+        vdu_index = "1"
+        action_id = "bb937f49-3870-4169-b758-9732e1ff40f3"
+        nsr_id = "993166fe-723e-4680-ac4b-b1af2541ae31"
+        task_index = 1
+        target_record_id = (
+            "vnfrs:665b4165-ce24-4320-bf19-b9a45bade49f:"
+            "vdur.bb9c43f9-10a2-4569-a8a8-957c3528b6d1"
+        )
+
+        expected_result = {
+            "target_id": "vim:f9f370ac-0d44-41a7-9000-457f2332bc35",
+            "action_id": "bb937f49-3870-4169-b758-9732e1ff40f3",
+            "nsr_id": "993166fe-723e-4680-ac4b-b1af2541ae31",
+            "task_id": "bb937f49-3870-4169-b758-9732e1ff40f3:1",
+            "status": "SCHEDULED",
+            "action": "EXEC",
+            "item": "verticalscale",
+            "target_record": "vnfrs:665b4165-ce24-4320-bf19-b9a45bade49f:vdur.1",
+            "target_record_id": target_record_id,
+            "params": {
+                "vim_vm_id": "f37b18ef-3caa-4dc9-ab91-15c669b16396",
+                "flavor_dict": "flavor_dict",
+            },
+        }
+        vdu = {
+            "id": "bb9c43f9-10a2-4569-a8a8-957c3528b6d1",
+            "vim_info": {
+                "vim:f9f370ac-0d44-41a7-9000-457f2332bc35": {"interfaces": []}
+            },
+        }
+        vnf = {"_id": "665b4165-ce24-4320-bf19-b9a45bade49f"}
+        extra_dict["params"] = {
+            "vim_vm_id": "f37b18ef-3caa-4dc9-ab91-15c669b16396",
+            "flavor_dict": "flavor_dict",
+        }
+        task = self.ns.verticalscale_task(
+            vdu, vnf, vdu_index, action_id, nsr_id, task_index, extra_dict
+        )
+
+        self.assertDictEqual(task, expected_result)
index 86a9dcd..c1d23a0 100644 (file)
@@ -19,7 +19,11 @@ import logging
 import unittest
 from unittest.mock import MagicMock, patch
 
-from osm_ng_ro.ns_thread import VimInteractionAffinityGroup, VimInteractionNet
+from osm_ng_ro.ns_thread import (
+    VimInteractionAffinityGroup,
+    VimInteractionNet,
+    VimInteractionResize,
+)
 from osm_ro_plugin.vimconn import VimConnConnectionException, VimConnException
 
 
@@ -1305,3 +1309,63 @@ class TestVimInteractionAffinityGroup(unittest.TestCase):
             self.assertEqual(result[1].get("vim_message"), "DELETED")
             self.assertEqual(result[1].get("created"), False)
             self.assertEqual(result[1].get("vim_status"), "DELETED")
+
+
+class TestVimInteractionResize(unittest.TestCase):
+    def setUp(self):
+        module_name = "osm_ro_plugin"
+        self.target_vim = MagicMock(name=f"{module_name}.vimconn.VimConnector")
+        self.task_depends = None
+
+        patches = [patch(f"{module_name}.vimconn.VimConnector", self.target_vim)]
+
+        # Enabling mocks and add cleanups
+        for mock in patches:
+            mock.start()
+            self.addCleanup(mock.stop)
+
+    def test__exec_resize_done(self):
+        """
+        create verticalscale task
+        """
+        db = "test_db"
+        logger = "test_logger"
+        my_vims = "test-vim"
+        db_vims = {
+            0: {
+                "config": {},
+            },
+        }
+        target_record_id = (
+            "vnfrs:665b4165-ce24-4320-bf19-b9a45bade49f:"
+            "vdur.bb9c43f9-10a2-4569-a8a8-957c3528b6d1"
+        )
+
+        instance = VimInteractionResize(db, logger, my_vims, db_vims)
+        with patch.object(instance, "my_vims", [self.target_vim]), patch.object(
+            instance, "logger", logging
+        ), patch.object(instance, "db_vims", db_vims):
+            ro_task = {
+                "target_id": 0,
+                "tasks": {
+                    "task_index_1": {
+                        "target_id": 0,
+                        "action_id": "bb937f49-3870-4169-b758-9732e1ff40f3",
+                        "nsr_id": "993166fe-723e-4680-ac4b-b1af2541ae31",
+                        "task_id": "bb937f49-3870-4169-b758-9732e1ff40f3:0",
+                        "status": "SCHEDULED",
+                        "action": "EXEC",
+                        "item": "verticalscale",
+                        "target_record": "vnfrs:665b4165-ce24-4320-bf19-b9a45bade49f:vdur.0",
+                        "target_record_id": target_record_id,
+                        "params": {
+                            "vim_vm_id": "f37b18ef-3caa-4dc9-ab91-15c669b16396",
+                            "flavor_dict": "flavor_dict",
+                        },
+                    }
+                },
+            }
+            task_index = "task_index_1"
+            result = instance.exec(ro_task, task_index, self.task_depends)
+            self.assertEqual(result[0], "DONE")
+            self.assertEqual(result[1].get("vim_status"), "DONE")
index 9eb1682..b6b7231 100644 (file)
@@ -1024,3 +1024,13 @@ class vimconnector(vimconn.VimConnector):
         """
         # TODO: Add support for migration
         raise vimconn.VimConnNotImplemented("Not implemented")
+
+    def resize_instance(self, vm_id, flavor_id=None):
+        """
+        resize a vdu
+        param:
+            vm_id: ID of an instance
+            flavor_id: flavor to resize the vdu
+        """
+        # TODO: Add support for resize
+        raise vimconn.VimConnNotImplemented("Not implemented")
index 362d14e..c3185b8 100755 (executable)
@@ -2010,6 +2010,16 @@ class vimconnector(vimconn.VimConnector):
         # TODO: Add support for migration
         raise vimconn.VimConnNotImplemented("Not implemented")
 
+    def resize_instance(self, vm_id, flavor_id=None):
+        """
+        resize a vdu
+        param:
+            vm_id: ID of an instance
+            flavor_id: flavor id to resize the vdu
+        """
+        # TODO: Add support for resize
+        raise vimconn.VimConnNotImplemented("Not implemented")
+
 
 if __name__ == "__main__":
     # Init logger
index e6823dc..67d8c41 100644 (file)
@@ -1495,3 +1495,13 @@ class vimconnector(vimconn.VimConnector):
         """
         # TODO: Add support for migration
         raise vimconn.VimConnNotImplemented("Not implemented")
+
+    def resize_instance(self, vm_id, flavor_id=None):
+        """
+        resize a vdu
+        param:
+            vm_id: ID of an instance
+            flavor_id: flavor_id to resize the vdu to
+        """
+        # TODO: Add support for resize
+        raise vimconn.VimConnNotImplemented("Not implemented")
index da1499d..ef5c740 100644 (file)
@@ -3740,3 +3740,62 @@ class vimconnector(vimconn.VimConnector):
             nvExceptions.NotFound,
         ) as e:
             self._format_exception(e)
+
+    def resize_instance(self, vm_id, new_flavor_id):
+        """
+        For resizing the vm based on the given
+        flavor details
+        param:
+            vm_id : ID of an instance
+            new_flavor_id : Flavor id to be resized
+        Return the status of a resized instance
+        """
+        self._reload_connection()
+        self.logger.debug("resize the flavor of an instance")
+        instance_status, old_flavor_id, compute_host, az = self.get_vdu_state(vm_id)
+        old_flavor_disk = self.nova.flavors.find(id=old_flavor_id).to_dict()["disk"]
+        new_flavor_disk = self.nova.flavors.find(id=new_flavor_id).to_dict()["disk"]
+        try:
+            if instance_status == "ACTIVE" or instance_status == "SHUTOFF":
+                if old_flavor_disk > new_flavor_disk:
+                    raise nvExceptions.BadRequest(
+                        400,
+                        message="Server disk resize failed. Resize to lower disk flavor is not allowed",
+                    )
+                else:
+                    self.nova.servers.resize(server=vm_id, flavor=new_flavor_id)
+                    vm_state = self.__wait_for_vm(vm_id, "VERIFY_RESIZE")
+                    if vm_state:
+                        instance_resized_status = self.confirm_resize(vm_id)
+                        return instance_resized_status
+                    else:
+                        raise nvExceptions.BadRequest(
+                            409,
+                            message="Cannot 'resize' vm_state is in ERROR",
+                        )
+
+            else:
+                self.logger.debug("ERROR : Instance is not in ACTIVE or SHUTOFF state")
+                raise nvExceptions.BadRequest(
+                    409,
+                    message="Cannot 'resize' instance while it is in vm_state resized",
+                )
+        except (
+            nvExceptions.BadRequest,
+            nvExceptions.ClientException,
+            nvExceptions.NotFound,
+        ) as e:
+            self._format_exception(e)
+
+    def confirm_resize(self, vm_id):
+        """
+        Confirm the resize of an instance
+        param:
+            vm_id: ID of an instance
+        """
+        self._reload_connection()
+        self.nova.servers.confirm_resize(server=vm_id)
+        if self.get_vdu_state(vm_id)[0] == "VERIFY_RESIZE":
+            self.__wait_for_vm(vm_id, "ACTIVE")
+        instance_status = self.get_vdu_state(vm_id)[0]
+        return instance_status
index a580800..553ec89 100644 (file)
@@ -1644,3 +1644,13 @@ class vimconnector(vimconn.VimConnector):
         """
         # TODO: Add support for migration
         raise vimconn.VimConnNotImplemented("Not implemented")
+
+    def resize_instance(self, vm_id, flavor_id=None):
+        """
+        resize a vdu
+        param:
+            vm_id: ID of an instance
+            flavor_id: flavor_id to resize the vdu to
+        """
+        # TODO: Add support for resize
+        raise vimconn.VimConnNotImplemented("Not implemented")
index b3cff59..ff89b5a 100644 (file)
@@ -8669,3 +8669,13 @@ if [ "$1" = "precustomization" ];then
         """
         # TODO: Add support for migration
         raise vimconn.VimConnNotImplemented("Should have implemented this")
+
+    def resize_instance(self, vm_id, flavor_id=None):
+        """
+        resize a vdu
+        param:
+            vm_id: ID of an instance
+            flavor_id: flavor_id to resize the vdu to
+        """
+        # TODO: Add support for resize
+        raise vimconn.VimConnNotImplemented("Should have implemented this")
index 2baa202..20ab3a3 100644 (file)
@@ -1098,6 +1098,15 @@ class VimConnector:
         """
         raise VimConnNotImplemented("Should have implemented this")
 
+    def resize_instance(self, vm_id, flavor_id=None):
+        """
+        resize a vdu
+        param:
+            vm_id: ID of an instance
+            flavor_id: flavor_id to resize the vdu to
+        """
+        raise VimConnNotImplemented("Should have implemented this")
+
     # NOT USED METHODS in current version. Deprecated
     @deprecated
     def host_vim2gui(self, host, server_dict):
diff --git a/releasenotes/notes/Feature_10911_Vertical_scaling_of_VM_instances_from_OSM-6b1b717a7e2b8377.yaml b/releasenotes/notes/Feature_10911_Vertical_scaling_of_VM_instances_from_OSM-6b1b717a7e2b8377.yaml
new file mode 100644 (file)
index 0000000..896bafa
--- /dev/null
@@ -0,0 +1,22 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# 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.
+#######################################################################################
+---
+features:
+  - |
+    Feature 10911 - Vertical scaling of VM instances from OSM
+    This feature enables resizing of virtual machine instances of VNFs.
+    Support added for Openstack VIM type.