From 4ff4b51325ee03faf991cd0fd435658c9365ecf0 Mon Sep 17 00:00:00 2001 From: govindarajul Date: Mon, 2 May 2022 20:02:41 +0530 Subject: [PATCH] Feature 10911-Vertical scaling of VM instances from OSM Added implementation code for Vertical Scaling of VDUs in OSM LCM Review comments addressed Change-Id: If36df196ecbadc6f1f348bd9d43f3b7cf0365602 Signed-off-by: govindarajul --- osm_lcm/lcm.py | 10 +++ osm_lcm/ng_ro.py | 32 ++++++++++ osm_lcm/ns.py | 96 ++++++++++++++++++++++++++++ osm_lcm/tests/test_db_descriptors.py | 76 ++++++++++++++++++++++ osm_lcm/tests/test_ns.py | 33 ++++++++++ 5 files changed, 247 insertions(+) diff --git a/osm_lcm/lcm.py b/osm_lcm/lcm.py index 9378b1d..d6c10e8 100644 --- a/osm_lcm/lcm.py +++ b/osm_lcm/lcm.py @@ -472,6 +472,15 @@ class Lcm: task = asyncio.ensure_future(self.ns.migrate(nsr_id, nslcmop_id)) self.lcm_tasks.register("ns", nsr_id, nslcmop_id, "ns_migrate", task) return + elif command == "verticalscale": + nslcmop = params + nslcmop_id = nslcmop["_id"] + nsr_id = nslcmop["nsInstanceId"] + task = asyncio.ensure_future(self.ns.vertical_scale(nsr_id, nslcmop_id)) + self.logger.debug("nsr_id,nslcmop_id,task {},{},{}".format(nsr_id, nslcmop_id, task)) + self.lcm_tasks.register("ns", nsr_id, nslcmop_id, "ns_verticalscale", task) + self.logger.debug("LCM task registered {},{},{} ".format(nsr_id, nslcmop_id, task)) + return elif command == "show": nsr_id = params try: @@ -504,6 +513,7 @@ class Lcm: "actioned", "updated", "migrated", + "verticalscaled", ): # "scaled-cooldown-time" return diff --git a/osm_lcm/ng_ro.py b/osm_lcm/ng_ro.py index 5dd3cc4..03819c8 100644 --- a/osm_lcm/ng_ro.py +++ b/osm_lcm/ng_ro.py @@ -335,6 +335,38 @@ class NgRoClient: except asyncio.TimeoutError: raise NgRoException("Timeout", http_code=504) + async def vertical_scale(self, nsr_id, target): + """ + Performs migration of VNFs + :param nsr_id: NS Instance Id + :param target: payload data for migrate operation + :return: dictionary with the information or raises NgRoException on Error + """ + try: + if isinstance(target, str): + target = self._parse_yaml(target) + payload_req = yaml.safe_dump(target) + + url = "{}/ns/v1/verticalscale/{nsr_id}".format(self.endpoint_url, nsr_id=nsr_id) + async with aiohttp.ClientSession(loop=self.loop) as session: + self.logger.debug("NG-RO POST %s %s", url, payload_req) + async with session.post( + url, headers=self.headers_req, data=payload_req + ) as response: + response_text = await response.read() + self.logger.debug( + "POST {} [{}] {}".format( + url, response.status, response_text[:100] + ) + ) + if response.status >= 300: + raise NgRoException(response_text, http_code=response.status) + return self._parse_yaml(response_text, response=True) + except (aiohttp.ClientOSError, aiohttp.ClientError) as e: + raise NgRoException(e, http_code=504) + except asyncio.TimeoutError: + raise NgRoException("Timeout", http_code=504) + @staticmethod def _parse_yaml(descriptor, response=False): try: diff --git a/osm_lcm/ns.py b/osm_lcm/ns.py index 33e1d18..6aed304 100644 --- a/osm_lcm/ns.py +++ b/osm_lcm/ns.py @@ -134,6 +134,7 @@ class NsLcm(LcmBase): ) # timeout for some progress in a primitive execution timeout_migrate = 1800 # default global timeout for migrating vnfs timeout_operate = 1800 # default global timeout for migrating vnfs + timeout_verticalscale = 1800 # default global timeout for Vertical Sclaing SUBOPERATION_STATUS_NOT_FOUND = -1 SUBOPERATION_STATUS_NEW = -2 SUBOPERATION_STATUS_SKIP = -3 @@ -8374,3 +8375,98 @@ class NsLcm(LcmBase): await asyncio.sleep(15, loop=self.loop) else: # timeout_ns_deploy raise NgRoException("Timeout waiting ns to deploy") + + async def vertical_scale(self, nsr_id, nslcmop_id): + """ + Vertical Scale the VDUs in a NS + + :param: nsr_id: NS Instance ID + :param: nslcmop_id: nslcmop ID of migrate + + """ + # Try to lock HA task here + task_is_locked_by_me = self.lcm_tasks.lock_HA("ns", "nslcmops", nslcmop_id) + if not task_is_locked_by_me: + return + logging_text = "Task ns={} vertical scale ".format(nsr_id) + self.logger.debug(logging_text + "Enter") + # get all needed from database + db_nslcmop = None + db_nslcmop_update = {} + nslcmop_operation_state = None + db_nsr_update = {} + target = {} + exc = None + # in case of error, indicates what part of scale was failed to put nsr at error status + start_deploy = time() + + try: + # wait for any previous tasks in process + step = "Waiting for previous operations to terminate" + await self.lcm_tasks.waitfor_related_HA('ns', 'nslcmops', nslcmop_id) + + self._write_ns_status( + nsr_id=nsr_id, + ns_state=None, + current_operation="VerticalScale", + current_operation_id=nslcmop_id + ) + step = "Getting nslcmop from database" + self.logger.debug(step + " after having waited for previous tasks to be completed") + db_nslcmop = self.db.get_one("nslcmops", {"_id": nslcmop_id}) + operationParams = db_nslcmop.get("operationParams") + target = {} + target.update(operationParams) + desc = await self.RO.vertical_scale(nsr_id, target) + self.logger.debug("RO return > {}".format(desc)) + action_id = desc["action_id"] + await self._wait_ng_ro( + nsr_id, action_id, nslcmop_id, start_deploy, self.timeout_verticalscale + ) + except (ROclient.ROClientException, DbException, LcmException) as e: + self.logger.error("Exit Exception {}".format(e)) + exc = e + except asyncio.CancelledError: + self.logger.error("Cancelled Exception while '{}'".format(step)) + exc = "Operation was cancelled" + except Exception as e: + exc = traceback.format_exc() + self.logger.critical("Exit Exception {} {}".format(type(e).__name__, e), exc_info=True) + finally: + self._write_ns_status( + nsr_id=nsr_id, + ns_state=None, + current_operation="IDLE", + current_operation_id=None, + ) + if exc: + db_nslcmop_update[ + "detailed-status" + ] = "FAILED {}: {}".format(step, exc) + nslcmop_operation_state = "FAILED" + else: + nslcmop_operation_state = "COMPLETED" + db_nslcmop_update["detailed-status"] = "Done" + db_nsr_update["detailed-status"] = "Done" + + self._write_op_status( + op_id=nslcmop_id, + stage="", + error_message="", + operation_state=nslcmop_operation_state, + other_update=db_nslcmop_update, + ) + if nslcmop_operation_state: + try: + msg = { + "nsr_id": nsr_id, + "nslcmop_id": nslcmop_id, + "operationState": nslcmop_operation_state, + } + await self.msg.aiowrite("ns", "verticalscaled", msg, loop=self.loop) + except Exception as e: + self.logger.error( + logging_text + "kafka_write notification Exception {}".format(e) + ) + self.logger.debug(logging_text + "Exit") + self.lcm_tasks.remove("ns", nsr_id, nslcmop_id, "ns_verticalscale") diff --git a/osm_lcm/tests/test_db_descriptors.py b/osm_lcm/tests/test_db_descriptors.py index dbb3e1d..9304908 100644 --- a/osm_lcm/tests/test_db_descriptors.py +++ b/osm_lcm/tests/test_db_descriptors.py @@ -424,6 +424,77 @@ db_nslcmops_text = """ worker: fbf6b5aa99e2 detailed-status: Done +- _admin: + created: 1566823354.4148262 + modified: 1566823354.4148262 + projects_read: + - 25b5aebf-3da1-49ed-99de-1d2b4a86d6e4 + projects_write: + - 25b5aebf-3da1-49ed-99de-1d2b4a86d6e4 + worker: 86434c2948e2 + _id: 8b838aa8-53a3-4955-80bd-fbba6a7957ed + detailed-status: 'FAILED executing proxy charm initial primitives for member_vnf_index=1 + vdu_id=None: charm error executing primitive verify-ssh-credentials for member_vnf_index=1 + vdu_id=None: ''timeout after 600 seconds''' + id: 8b838aa8-53a3-4955-80bd-fbba6a7957ed + isAutomaticInvocation: false + isCancelPending: false + lcmOperationType: scale + links: + nsInstance: /osm/nslcm/v1/ns_instances/f48163a6-c807-47bc-9682-f72caef5af85 + self: /osm/nslcm/v1/ns_lcm_op_occs/8b838aa8-53a3-4955-80bd-fbba6a7957ed + nsInstanceId: f48163a6-c807-47bc-9682-f72caef5af85 + operationParams: + additionalParamsForVnf: + - additionalParams: + touch_filename: /home/ubuntu/first-touch-1 + touch_filename2: /home/ubuntu/second-touch-1 + member-vnf-index: '1' + lcmOperationType: instantiate + nsDescription: default description + nsInstanceId: f48163a6-c807-47bc-9682-f72caef5af85 + nsName: ALF + nsdId: 8c2f8b95-bb1b-47ee-8001-36dc090678da + vimAccountId: ea958ba5-4e58-4405-bf42-6e3be15d4c3a + operationState: FAILED + startTime: 1566823354.414689 + statusEnteredTime: 1566824534.5112448 + +- _admin: + created: 1566823354.4148262 + modified: 1566823354.4148262 + projects_read: + - 25b5aebf-3da1-49ed-99de-1d2b4a86d6e4 + projects_write: + - 25b5aebf-3da1-49ed-99de-1d2b4a86d6e4 + worker: 86434c2948e2 + _id: a21af1d4-7f1a-4f7b-b666-222315113a62 + detailed-status: 'FAILED executing proxy charm initial primitives for member_vnf_index=1 + vdu_id=None: charm error executing primitive verify-ssh-credentials for member_vnf_index=1 + vdu_id=None: ''timeout after 600 seconds''' + id: a21af1d4-7f1a-4f7b-b666-222315113a62 + isAutomaticInvocation: false + isCancelPending: false + lcmOperationType: scale + links: + nsInstance: /osm/nslcm/v1/ns_instances/f48163a6-c807-47bc-9682-f72caef5af85 + self: /osm/nslcm/v1/ns_lcm_op_occs/a21af1d4-7f1a-4f7b-b666-222315113a62 + nsInstanceId: f48163a6-c807-47bc-9682-f72caef5af85 + operationParams: + additionalParamsForVnf: + - additionalParams: + touch_filename: /home/ubuntu/first-touch-1 + touch_filename2: /home/ubuntu/second-touch-1 + member-vnf-index: '1' + lcmOperationType: instantiate + nsDescription: default description + nsInstanceId: f48163a6-c807-47bc-9682-f72caef5af85 + nsName: ALF + nsdId: 8c2f8b95-bb1b-47ee-8001-36dc090678da + vimAccountId: ea958ba5-4e58-4405-bf42-6e3be15d4c3a + operationState: COMPLETED + startTime: 1566823354.414689 + statusEnteredTime: 1566824534.5112448 """ db_nsrs_text = """ @@ -2555,4 +2626,9 @@ test_ids = { "nslcmops1": "6eace44b-2ef4-4de5-b15f-63f2e8898bfb", "vnfrs": "a6df8aa0-1271-4dfc-85a5-e0484fea303f", }, + "TEST-V-SCALE": { + "ns": "f48163a6-c807-47bc-9682-f72caef5af85", + "instantiate-1": "8b838aa8-53a3-4955-80bd-fbba6a7957ed", + "instantiate": "a21af1d4-7f1a-4f7b-b666-222315113a62", + }, } diff --git a/osm_lcm/tests/test_ns.py b/osm_lcm/tests/test_ns.py index 466976c..d7192c9 100644 --- a/osm_lcm/tests/test_ns.py +++ b/osm_lcm/tests/test_ns.py @@ -841,6 +841,39 @@ class TestMyNS(asynctest.TestCase): self.db.get_one("vnfrs", {"_id": vnf_instance_id}) self.assertTrue("database exception Not found entry with filter" in str(context.exception)) + # test vertical scale executes sucessfully + # @patch("osm_lcm.ng_ro.status.response") + @asynctest.fail_on(active_handles=True) + async def test_vertical_scaling(self): + nsr_id = descriptors.test_ids["TEST-V-SCALE"]["ns"] + nslcmop_id = descriptors.test_ids["TEST-V-SCALE"]["instantiate"] + + # calling the vertical scale fucntion + # self.my_ns.RO.status = asynctest.CoroutineMock(self.my_ns.RO.status, side_effect=self._ro_status("update")) + mock_wait_ng_ro = asynctest.CoroutineMock() + with patch("osm_lcm.ns.NsLcm._wait_ng_ro", mock_wait_ng_ro): + await self.my_ns.vertical_scale(nsr_id, nslcmop_id) + return_value = self.db.get_one("nslcmops", {"_id": nslcmop_id}).get( + "operationState" + ) + expected_value = "COMPLETED" + self.assertEqual(return_value, expected_value) + + # test vertical scale executes fail + @asynctest.fail_on(active_handles=True) + async def test_vertical_scaling_fail(self): + # get th nsr nad nslcmops id from descriptors + nsr_id = descriptors.test_ids["TEST-V-SCALE"]["ns"] + nslcmop_id = descriptors.test_ids["TEST-V-SCALE"]["instantiate-1"] + + # calling the vertical scale fucntion + await self.my_ns.vertical_scale(nsr_id, nslcmop_id) + return_value = self.db.get_one("nslcmops", {"_id": nslcmop_id}).get( + "operationState" + ) + expected_value = "FAILED" + self.assertEqual(return_value, expected_value) + # async def test_instantiate_pdu(self): # nsr_id = descriptors.test_ids["TEST-A"]["ns"] # nslcmop_id = descriptors.test_ids["TEST-A"]["instantiate"] -- 2.25.1