From 29a4c1a24050561a70f4f47b276a268d48c79067 Mon Sep 17 00:00:00 2001 From: sritharan Date: Thu, 5 May 2022 12:15:04 +0000 Subject: [PATCH] Feature 10911-Vertical scaling of VM instances from OSM Change-Id: Ie52edb13367a253ed3fc1a434ca2a170803035e4 Signed-off-by: sritharan --- NG-RO/osm_ng_ro/ns.py | 97 +++++++++++++++++++ NG-RO/osm_ng_ro/ns_thread.py | 72 ++++++++++++++ NG-RO/osm_ng_ro/ro_main.py | 11 +++ NG-RO/osm_ng_ro/tests/test_ns.py | 45 +++++++++ NG-RO/osm_ng_ro/tests/test_ns_thread.py | 66 ++++++++++++- RO-VIM-aws/osm_rovim_aws/vimconn_aws.py | 10 ++ RO-VIM-azure/osm_rovim_azure/vimconn_azure.py | 10 ++ RO-VIM-gcp/osm_rovim_gcp/vimconn_gcp.py | 10 ++ .../osm_rovim_openstack/vimconn_openstack.py | 59 +++++++++++ .../osm_rovim_openvim/vimconn_openvim.py | 10 ++ .../osm_rovim_vmware/vimconn_vmware.py | 10 ++ RO-plugin/osm_ro_plugin/vimconn.py | 9 ++ ...M_instances_from_OSM-6b1b717a7e2b8377.yaml | 22 +++++ 13 files changed, 430 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/Feature_10911_Vertical_scaling_of_VM_instances_from_OSM-6b1b717a7e2b8377.yaml diff --git a/NG-RO/osm_ng_ro/ns.py b/NG-RO/osm_ng_ro/ns.py index 3f14c587..a823dbe3 100644 --- a/NG-RO/osm_ng_ro/ns.py +++ b/NG-RO/osm_ng_ro/ns.py @@ -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) diff --git a/NG-RO/osm_ng_ro/ns_thread.py b/NG-RO/osm_ng_ro/ns_thread.py index 9ed04d7b..8bb8d4bc 100644 --- a/NG-RO/osm_ng_ro/ns_thread.py +++ b/NG-RO/osm_ng_ro/ns_thread.py @@ -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 diff --git a/NG-RO/osm_ng_ro/ro_main.py b/NG-RO/osm_ng_ro/ro_main.py index bd2ea9e0..2a2e22cf 100644 --- a/NG-RO/osm_ng_ro/ro_main.py +++ b/NG-RO/osm_ng_ro/ro_main.py @@ -143,6 +143,16 @@ valid_url_methods = { }, }, }, + "verticalscale": { + "": { + "METHODS": ("POST"), + "ROLE_PERMISSION": "verticalscale: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): diff --git a/NG-RO/osm_ng_ro/tests/test_ns.py b/NG-RO/osm_ng_ro/tests/test_ns.py index 6c2615a5..129e668a 100644 --- a/NG-RO/osm_ng_ro/tests/test_ns.py +++ b/NG-RO/osm_ng_ro/tests/test_ns.py @@ -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) diff --git a/NG-RO/osm_ng_ro/tests/test_ns_thread.py b/NG-RO/osm_ng_ro/tests/test_ns_thread.py index 86a9dcd6..c1d23a0c 100644 --- a/NG-RO/osm_ng_ro/tests/test_ns_thread.py +++ b/NG-RO/osm_ng_ro/tests/test_ns_thread.py @@ -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") diff --git a/RO-VIM-aws/osm_rovim_aws/vimconn_aws.py b/RO-VIM-aws/osm_rovim_aws/vimconn_aws.py index 9eb1682f..b6b7231d 100644 --- a/RO-VIM-aws/osm_rovim_aws/vimconn_aws.py +++ b/RO-VIM-aws/osm_rovim_aws/vimconn_aws.py @@ -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") diff --git a/RO-VIM-azure/osm_rovim_azure/vimconn_azure.py b/RO-VIM-azure/osm_rovim_azure/vimconn_azure.py index 362d14e0..c3185b80 100755 --- a/RO-VIM-azure/osm_rovim_azure/vimconn_azure.py +++ b/RO-VIM-azure/osm_rovim_azure/vimconn_azure.py @@ -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 diff --git a/RO-VIM-gcp/osm_rovim_gcp/vimconn_gcp.py b/RO-VIM-gcp/osm_rovim_gcp/vimconn_gcp.py index e6823dcb..67d8c410 100644 --- a/RO-VIM-gcp/osm_rovim_gcp/vimconn_gcp.py +++ b/RO-VIM-gcp/osm_rovim_gcp/vimconn_gcp.py @@ -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") diff --git a/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py b/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py index da1499d7..ef5c7409 100644 --- a/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py +++ b/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py @@ -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 diff --git a/RO-VIM-openvim/osm_rovim_openvim/vimconn_openvim.py b/RO-VIM-openvim/osm_rovim_openvim/vimconn_openvim.py index a5808003..553ec89f 100644 --- a/RO-VIM-openvim/osm_rovim_openvim/vimconn_openvim.py +++ b/RO-VIM-openvim/osm_rovim_openvim/vimconn_openvim.py @@ -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") diff --git a/RO-VIM-vmware/osm_rovim_vmware/vimconn_vmware.py b/RO-VIM-vmware/osm_rovim_vmware/vimconn_vmware.py index b3cff595..ff89b5ad 100644 --- a/RO-VIM-vmware/osm_rovim_vmware/vimconn_vmware.py +++ b/RO-VIM-vmware/osm_rovim_vmware/vimconn_vmware.py @@ -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") diff --git a/RO-plugin/osm_ro_plugin/vimconn.py b/RO-plugin/osm_ro_plugin/vimconn.py index 2baa2022..20ab3a3b 100644 --- a/RO-plugin/osm_ro_plugin/vimconn.py +++ b/RO-plugin/osm_ro_plugin/vimconn.py @@ -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 index 00000000..896bafa7 --- /dev/null +++ b/releasenotes/notes/Feature_10911_Vertical_scaling_of_VM_instances_from_OSM-6b1b717a7e2b8377.yaml @@ -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. -- 2.17.1