diff --git a/releasenotes/notes/suppot-error-handling-of-mgmt-b71d8312ae7626f9.yaml b/releasenotes/notes/suppot-error-handling-of-mgmt-b71d8312ae7626f9.yaml new file mode 100644 index 000000000..08f18af04 --- /dev/null +++ b/releasenotes/notes/suppot-error-handling-of-mgmt-b71d8312ae7626f9.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Support error handling for LCM using MgmtDriver in V2 API. We added the + ``user_script_err_handling_data`` variable to hold the data that needs + to be preserved when the LCM fails. + + At the same time, we updated the MgmtDriver of the FaultNotification + function to support error handling. diff --git a/tacker/sol_refactored/conductor/conductor_v2.py b/tacker/sol_refactored/conductor/conductor_v2.py index 9eb943057..b751e8d0f 100644 --- a/tacker/sol_refactored/conductor/conductor_v2.py +++ b/tacker/sol_refactored/conductor/conductor_v2.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import copy import threading from oslo_log import log as logging @@ -99,13 +100,19 @@ class ConductorV2(object): self.nfvo_client.send_lcmocc_notification(context, lcmocc, inst, self.endpoint) - def _set_lcmocc_error(self, lcmocc, ex): + def _set_lcmocc_error( + self, lcmocc, ex, user_script_err_handling_data={}): if isinstance(ex, sol_ex.SolException): problem_details = ex.make_problem_details() else: # program bug. it occurs only under development. problem_details = {'status': 500, 'detail': str(ex)} + + if user_script_err_handling_data: + problem_details[ + 'userScriptErrHandlingData'] = user_script_err_handling_data + lcmocc.error = objects.ProblemDetails.from_dict(problem_details) @log.log @@ -123,6 +130,8 @@ class ConductorV2(object): inst = inst_utils.get_inst(context, lcmocc.vnfInstanceId) + user_script_err_handling_data = {} + # NOTE: error cannot happen to here basically. # if an error occurred lcmocc.operationState remains STARTING. # see the log of the tacker-conductor to investigate the cause @@ -177,7 +186,8 @@ class ConductorV2(object): try: self.vnflcm_driver.process(context, lcmocc, inst, grant_req, - grant, vnfd) + grant, vnfd, + user_script_err_handling_data) lcmocc.operationState = fields.LcmOperationStateType.COMPLETED # update inst and lcmocc at the same time @@ -190,7 +200,7 @@ class ConductorV2(object): except Exception as ex: LOG.exception("PROCESSING %s failed", lcmocc.operation) lcmocc.operationState = fields.LcmOperationStateType.FAILED_TEMP - self._set_lcmocc_error(lcmocc, ex) + self._set_lcmocc_error(lcmocc, ex, user_script_err_handling_data) lcmocc.update(context) # send notification COMPLETED or FAILED_TEMP @@ -212,6 +222,13 @@ class ConductorV2(object): inst = inst_utils.get_inst(context, lcmocc.vnfInstanceId) + if (lcmocc.obj_attr_is_set('error') and + lcmocc.error.obj_attr_is_set('userScriptErrHandlingData')): + user_script_err_handling_data = copy.deepcopy( + lcmocc.error.userScriptErrHandlingData) + else: + user_script_err_handling_data = {} + lcmocc.operationState = fields.LcmOperationStateType.PROCESSING lcmocc.update(context) # send notification PROCESSING @@ -230,7 +247,8 @@ class ConductorV2(object): self.vnflcm_driver.post_grant(context, lcmocc, inst, grant_req, grant, vnfd) self.vnflcm_driver.process(context, lcmocc, inst, grant_req, - grant, vnfd) + grant, vnfd, + user_script_err_handling_data) lcmocc.operationState = fields.LcmOperationStateType.COMPLETED lcmocc.error = None # clear error @@ -245,7 +263,7 @@ class ConductorV2(object): except Exception as ex: LOG.exception("PROCESSING %s failed", lcmocc.operation) lcmocc.operationState = fields.LcmOperationStateType.FAILED_TEMP - self._set_lcmocc_error(lcmocc, ex) + self._set_lcmocc_error(lcmocc, ex, user_script_err_handling_data) lcmocc.update(context) # grant_req and grant are already saved. they are not deleted # while operationState is FAILED_TEMP. @@ -269,6 +287,13 @@ class ConductorV2(object): inst = inst_utils.get_inst(context, lcmocc.vnfInstanceId) + if (lcmocc.obj_attr_is_set('error') and + lcmocc.error.obj_attr_is_set('userScriptErrHandlingData')): + user_script_err_handling_data = copy.deepcopy( + lcmocc.error.userScriptErrHandlingData) + else: + user_script_err_handling_data = {} + lcmocc.operationState = fields.LcmOperationStateType.ROLLING_BACK lcmocc.update(context) # send notification ROLLING_BACK @@ -283,9 +308,13 @@ class ConductorV2(object): self.vnflcm_driver.post_grant(context, lcmocc, inst, grant_req, grant, vnfd) self.vnflcm_driver.rollback(context, lcmocc, inst, grant_req, - grant, vnfd) + grant, vnfd, + user_script_err_handling_data) lcmocc.operationState = fields.LcmOperationStateType.ROLLED_BACK + if (lcmocc.obj_attr_is_set('error') and + lcmocc.error.obj_attr_is_set('userScriptErrHandlingData')): + del lcmocc.error.userScriptErrHandlingData with context.session.begin(subtransactions=True): lcmocc.update(context) # NOTE: Basically inst is not changed. But there is a case @@ -302,7 +331,7 @@ class ConductorV2(object): except Exception as ex: LOG.exception("ROLLING_BACK %s failed", lcmocc.operation) lcmocc.operationState = fields.LcmOperationStateType.FAILED_TEMP - self._set_lcmocc_error(lcmocc, ex) + self._set_lcmocc_error(lcmocc, ex, user_script_err_handling_data) lcmocc.update(context) # grant_req and grant are already saved. they are not deleted # while operationState is FAILED_TEMP. @@ -325,13 +354,17 @@ class ConductorV2(object): return inst = inst_utils.get_inst(context, lcmocc.vnfInstanceId) + + user_script_err_handling_data = {} + # send notification PROCESSING self.nfvo_client.send_lcmocc_notification(context, lcmocc, inst, self.endpoint) try: vnfd = self.nfvo_client.get_vnfd(context, inst.vnfdId) - self.vnflcm_driver.process(context, lcmocc, inst, None, None, vnfd) + self.vnflcm_driver.process(context, lcmocc, inst, None, None, vnfd, + user_script_err_handling_data) lcmocc.operationState = fields.LcmOperationStateType.COMPLETED # update inst and lcmocc at the same time with context.session.begin(subtransactions=True): @@ -340,7 +373,7 @@ class ConductorV2(object): except Exception as ex: LOG.exception("PROCESSING %s failed", lcmocc.operation) lcmocc.operationState = fields.LcmOperationStateType.FAILED_TEMP - self._set_lcmocc_error(lcmocc, ex) + self._set_lcmocc_error(lcmocc, ex, user_script_err_handling_data) lcmocc.update(context) # send notification COMPLETED or FAILED_TEMP diff --git a/tacker/sol_refactored/conductor/vnflcm_driver_v2.py b/tacker/sol_refactored/conductor/vnflcm_driver_v2.py index 89663917c..6fa6aef74 100644 --- a/tacker/sol_refactored/conductor/vnflcm_driver_v2.py +++ b/tacker/sol_refactored/conductor/vnflcm_driver_v2.py @@ -94,7 +94,8 @@ class VnfLcmDriverV2(object): method(context, lcmocc, inst, grant_req, grant, vnfd) def _exec_mgmt_driver_script(self, operation, flavour_id, req, inst, - grant_req, grant, vnfd, new_vnfd=None): + grant_req, grant, vnfd, + user_script_err_handling_data, new_vnfd=None): script = vnfd.get_interface_script(flavour_id, operation) if script is None: return @@ -108,7 +109,8 @@ class VnfLcmDriverV2(object): if grant_req is not None else None), 'grant_response': (grant.to_dict() if grant is not None else None), - 'tmp_csar_dir': tmp_csar_dir + 'tmp_csar_dir': tmp_csar_dir, + 'user_script_err_handling_data': user_script_err_handling_data } if new_vnfd: new_csar_dir = new_vnfd.make_tmp_csar_dir() @@ -122,21 +124,25 @@ class VnfLcmDriverV2(object): vnfd.remove_tmp_csar_dir(tmp_csar_dir) + try: + output = pickle.loads(out.stdout) + if isinstance(output, dict): + if 'vnf_instance' in output: + _inst = objects.VnfInstanceV2.from_dict( + output['vnf_instance']) + inst.__dict__.update(_inst.__dict__) + if 'user_script_err_handling_data' in output: + user_script_err_handling_data.update(output[ + 'user_script_err_handling_data']) + except EOFError: + pass + except pickle.UnpicklingError: + pass + if out.returncode != 0: LOG.debug("execute %s failed: %s", operation, out.stderr) msg = "{} failed: {}".format(operation, out.stderr) raise sol_ex.MgmtDriverExecutionFailed(sol_detail=msg) - else: - try: - output = pickle.loads(out.stdout) - if isinstance(output, dict) and 'vnf_instance' in output: - _inst = objects.VnfInstanceV2.from_dict( - output['vnf_instance']) - inst.__dict__.update(_inst.__dict__) - except EOFError: - pass - except pickle.UnpicklingError: - pass LOG.debug("execute %s of %s success.", operation, script) @@ -218,7 +224,8 @@ class VnfLcmDriverV2(object): else: return operation.lower() - def process(self, context, lcmocc, inst, grant_req, grant, vnfd): + def process(self, context, lcmocc, inst, grant_req, grant, vnfd, + user_script_err_handling_data): # save inst to use updating lcmocc after process done inst_saved = inst.obj_clone() @@ -234,8 +241,9 @@ class VnfLcmDriverV2(object): # NOTE: This is only the case that operation is MODIFY_INFO # and vnf instance is never instantiated after creation. flavour_id = None - self._exec_mgmt_driver_script(operation, - flavour_id, req, inst, grant_req, grant, vnfd) + self._exec_mgmt_driver_script( + operation, flavour_id, req, inst, grant_req, grant, vnfd, + user_script_err_handling_data) # main process method = getattr(self, "%s_%s" % (lcmocc.operation.lower(), 'process')) @@ -249,21 +257,44 @@ class VnfLcmDriverV2(object): context, req.vnfdId, all_contents=True) operation = "{}_end".format(self._script_method_name(lcmocc.operation)) self._exec_mgmt_driver_script( - operation, flavour_id, req, inst, - grant_req, grant, vnfd, new_vnfd=new_vnfd) + operation, flavour_id, req, inst, grant_req, grant, vnfd, + user_script_err_handling_data, new_vnfd=new_vnfd) self._make_inst_info_common(lcmocc, inst_saved, inst, vnfd) lcmocc_utils.update_lcmocc(lcmocc, inst_saved, inst) - def rollback(self, context, lcmocc, inst, grant_req, grant, vnfd): + def rollback(self, context, lcmocc, inst, grant_req, grant, vnfd, + user_script_err_handling_data): method = getattr(self, "%s_%s" % (lcmocc.operation.lower(), 'rollback'), None) - if method: - method(context, lcmocc, inst, grant_req, grant, vnfd) - else: + if not method: raise sol_ex.RollbackNotSupported(op=lcmocc.operation) + # perform preamble LCM script + req = lcmocc.operationParams + operation_name = self._script_method_name(lcmocc.operation) + operation = f"{operation_name}_rollback_start" + if lcmocc.operation == v2fields.LcmOperationType.INSTANTIATE: + flavour_id = req.flavourId + elif inst.obj_attr_is_set('instantiatedVnfInfo'): + flavour_id = inst.instantiatedVnfInfo.flavourId + else: + # NOTE: This is only the case that operation is MODIFY_INFO + # and vnf instance is never instantiated after creation. + flavour_id = None + self._exec_mgmt_driver_script( + operation, flavour_id, req, inst, grant_req, + grant, vnfd, user_script_err_handling_data) + + method(context, lcmocc, inst, grant_req, grant, vnfd) + + # perform postamble LCM script + operation = f"{operation_name}_rollback_end" + self._exec_mgmt_driver_script( + operation, flavour_id, req, inst, grant_req, + grant, vnfd, user_script_err_handling_data) + def _get_link_ports(self, inst_req): names = set() if inst_req.obj_attr_is_set('extVirtualLinks'): diff --git a/tacker/sol_refactored/mgmt_drivers/sample_script.py b/tacker/sol_refactored/mgmt_drivers/sample_script.py index db5260ef1..5b187d78c 100644 --- a/tacker/sol_refactored/mgmt_drivers/sample_script.py +++ b/tacker/sol_refactored/mgmt_drivers/sample_script.py @@ -20,12 +20,14 @@ import sys class SampleScript(object): - def __init__(self, req, inst, grant_req, grant, csar_dir): + def __init__(self, req, inst, grant_req, grant, csar_dir, + user_script_err_handling_data): self.req = req self.inst = inst self.grant_req = grant_req self.grant = grant self.csar_dir = csar_dir + self.user_script_err_handling_data = user_script_err_handling_data def instantiate_start(self): pass @@ -49,13 +51,23 @@ def main(): grant_req = script_dict['grant_request'] grant = script_dict['grant_response'] csar_dir = script_dict['tmp_csar_dir'] + user_script_err_handling_data = script_dict[ + 'user_script_err_handling_data'] - script = SampleScript(req, inst, grant_req, grant, csar_dir) + script = SampleScript(req, inst, grant_req, grant, csar_dir, + user_script_err_handling_data) try: getattr(script, operation)() except AttributeError: + output_dict = {} + sys.stdout.buffer.write(pickle.dumps(output_dict)) + sys.stdout.flush() raise Exception("{} is not included in the script.".format(operation)) + output_dict = {} + sys.stdout.buffer.write(pickle.dumps(output_dict)) + sys.stdout.flush() + if __name__ == "__main__": try: diff --git a/tacker/sol_refactored/mgmt_drivers/server_notification.py b/tacker/sol_refactored/mgmt_drivers/server_notification.py index c5ab5c024..90f688f8b 100644 --- a/tacker/sol_refactored/mgmt_drivers/server_notification.py +++ b/tacker/sol_refactored/mgmt_drivers/server_notification.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import copy import os import pickle import sys @@ -32,20 +33,81 @@ CONF = cfg.CONF class ServerNotificationMgmtDriver(object): - def __init__(self, req, inst, grant_req, grant, csar_dir): + def __init__(self, req, inst, grant_req, grant, csar_dir, + user_script_err_handling_data): self.req = req self.inst = inst self.grant_req = grant_req self.grant = grant self.csar_dir = csar_dir + self.user_script_err_handling_data = user_script_err_handling_data auth_handle = http_client.NoAuthHandle() self.client = http_client.HttpClient(auth_handle) CONF(project='tacker') rpc.init(CONF) self.rpc = conductor_rpc_v2.VnfLcmRpcApiV2() + def _merge_recent_register(self): + """Merge alarms information + + This method will compare the information in vnf_instance + and user_script_err_handling_data, merging the state of + user_script_err_handling_data with the state of vnf_instance + to make them consistent. + """ + + if self.user_script_err_handling_data.get('serverNotification'): + tmp_data = copy.deepcopy(self.user_script_err_handling_data) + rm_server_id = [] + for server_notify in tmp_data.get('serverNotification'): + server_id = server_notify.get('serverId') + res = [res for res in self.inst['instantiatedVnfInfo'][ + 'vnfcResourceInfo'] if res['computeResource'][ + 'resourceId'] == server_id] + if res: + alarm_id = server_notify.get('alarmId') + if alarm_id: + # Since there is registered alarm on the + # monitoring server side, add it to vnfcResourceInfo. + if 'metadata' not in res[0]: + res[0]['metadata'] = {} + res[0]['metadata']['server_notification'] = { + "alarmId": alarm_id} + else: + # Since there is no registered alarm on the + # monitoring server side, delete it from + # vnfcResourceInfo. + if res[0]['metadata'].get('server_notification'): + del res[0]['metadata']['server_notification'] + else: + # Since the alarm registration remains only on the + # monitoring server side, unregister it. + server_notifier_uri, _, tenant = self.get_params() + if server_notify.get('alarmId'): + self._request_unregister( + server_notifier_uri, tenant, server_id, + server_notify.get('alarmId')) + rm_server_id.append(server_id) + self.user_script_err_handling_data['serverNotification'] = [ + data for data in self.user_script_err_handling_data[ + 'serverNotification'] + if data['serverId'] not in rm_server_id + ] + + def _instantiate_scale_rollback(self): + if self.user_script_err_handling_data.get('serverNotification'): + tmp_data = copy.deepcopy(self.user_script_err_handling_data) + server_notifier_uri, _, tenant = self.get_params() + for server_notify in tmp_data.get('serverNotification'): + server_id = server_notify.get('serverId') + alarm_id = server_notify.get('alarmId') + if server_id and alarm_id: + self._request_unregister( + server_notifier_uri, tenant, server_id, alarm_id) + def make_output_dict(self): - return {'vnf_instance': self.inst} + return {'vnf_instance': self.inst, 'user_script_err_handling_data': + self.user_script_err_handling_data} def request_remove_timer(self, vnf_instance_id): self.rpc.server_notification_remove_timer( @@ -66,6 +128,22 @@ class ServerNotificationMgmtDriver(object): LOG.debug( "server_notification unregistration is processed: %d.", resp.status_code) + + if ('serverNotification' not in + self.user_script_err_handling_data): + self.user_script_err_handling_data[ + 'serverNotification'] = [] + rm_target = [data for data in + self.user_script_err_handling_data[ + 'serverNotification'] + if data.get('serverId') + and data['serverId'] == server_id] + if rm_target and rm_target[0].get('alarmId'): + del rm_target[0]['alarmId'] + else: + self.user_script_err_handling_data[ + 'serverNotification'].append({"serverId": server_id}) + except sol_ex.SolException as e: # Even if unregistration is failed for a single alarm_id, # Unregistration should be done for remaining alarm_ids. @@ -125,6 +203,20 @@ class ServerNotificationMgmtDriver(object): LOG.debug( "server_notification registration is processed: %d. " "alarm_id: %s", resp.status_code, res_body['alarm_id']) + + if 'serverNotification' not in self.user_script_err_handling_data: + self.user_script_err_handling_data['serverNotification'] = [] + add_target = [ + data for data in self.user_script_err_handling_data[ + 'serverNotification'] + if data.get('serverId') and data['serverId'] == server_id + ] + if add_target: + add_target[0]['alarmId'] = res_body['alarm_id'] + else: + self.user_script_err_handling_data['serverNotification'].append( + {'serverId': server_id, 'alarmId': res_body['alarm_id']}) + return {'alarmId': res_body['alarm_id'], 'serverId': server_id} def request_register(self): @@ -147,9 +239,7 @@ class ServerNotificationMgmtDriver(object): fault_id = None tenant = None additional_params = self.req.get('additionalParams', None) - if 'instantiatedVnfInfo' not in self.inst: - return (None, None, None) - vnf_info = self.inst['instantiatedVnfInfo'] + vnf_info = self.inst.get('instantiatedVnfInfo') if (additional_params and 'ServerNotifierUri' in additional_params and @@ -172,6 +262,7 @@ class ServerNotificationMgmtDriver(object): return (None, None, None) def terminate_start(self): + self._merge_recent_register() at_least_one_id_unregistered = self.request_unregister() if at_least_one_id_unregistered: self.request_remove_timer(self.inst['id']) @@ -184,6 +275,7 @@ class ServerNotificationMgmtDriver(object): def scale_start(self): if self.req['type'] != 'SCALE_IN': return + self._merge_recent_register() vnfc_res_ids = [res_def['resource']['resourceId'] for res_def in self.grant_req['removeResources'] if res_def.get('type', None) == 'COMPUTE'] @@ -192,6 +284,7 @@ class ServerNotificationMgmtDriver(object): return self.make_output_dict() def heal_start(self): + self._merge_recent_register() isall = ('additionalParams' in self.req and self.req['additionalParams'].get('all', False) and 'vnfcInstanceId' not in self.req) @@ -203,6 +296,7 @@ class ServerNotificationMgmtDriver(object): return self.make_output_dict() def instantiate_end(self): + self._merge_recent_register() self.request_register() server_notifier_uri, fault_id, _ = self.get_params() vnf_info = self.inst['instantiatedVnfInfo'] @@ -215,6 +309,7 @@ class ServerNotificationMgmtDriver(object): def scale_end(self): if self.req['type'] != 'SCALE_OUT': return + self._merge_recent_register() self.request_register() return self.make_output_dict() @@ -228,6 +323,14 @@ class ServerNotificationMgmtDriver(object): def terminate_end(self): pass + def instantiate_rollback_start(self): + self._instantiate_scale_rollback() + return self.make_output_dict() + + def scale_rollback_start(self): + self._instantiate_scale_rollback() + return self.make_output_dict() + def main(): script_dict = pickle.load(sys.stdin.buffer) @@ -238,12 +341,23 @@ def main(): grant_req = script_dict['grant_request'] grant = script_dict['grant_response'] csar_dir = script_dict['tmp_csar_dir'] + user_script_err_handling_data = script_dict[ + 'user_script_err_handling_data'] - script = ServerNotificationMgmtDriver( - req, inst, grant_req, grant, csar_dir) - output_dict = getattr(script, operation)() - sys.stdout.buffer.write(pickle.dumps(output_dict)) - sys.stdout.flush() + try: + script = ServerNotificationMgmtDriver( + req, inst, grant_req, grant, csar_dir, + user_script_err_handling_data) + output_dict = getattr(script, operation)() + sys.stdout.buffer.write(pickle.dumps(output_dict)) + sys.stdout.flush() + except Exception as mgmt_ex: + output_dict = { + "user_script_err_handling_data": user_script_err_handling_data + } + sys.stdout.buffer.write(pickle.dumps(output_dict)) + sys.stdout.flush() + raise common_ex.MgmtDriverOtherError(error_message=str(mgmt_ex)) if __name__ == "__main__": diff --git a/tacker/sol_refactored/objects/common/problem_details.py b/tacker/sol_refactored/objects/common/problem_details.py index 223d38f35..07b9f547c 100644 --- a/tacker/sol_refactored/objects/common/problem_details.py +++ b/tacker/sol_refactored/objects/common/problem_details.py @@ -31,4 +31,7 @@ class ProblemDetails(base.TackerObject, base.TackerObjectDictCompat): 'status': fields.IntegerField(nullable=False), 'detail': fields.StringField(nullable=False), 'instance': fields.UriField(nullable=True), + # NOTE: userScriptErrHandlingData is not defined in SOL003. + # It is original definition of Tacker. + 'userScriptErrHandlingData': fields.KeyValuePairsField(nullable=True), } diff --git a/tacker/tests/functional/sol_v2/test_server_notification.py b/tacker/tests/functional/sol_v2/test_server_notification.py index f7ad27fff..2f6c56037 100644 --- a/tacker/tests/functional/sol_v2/test_server_notification.py +++ b/tacker/tests/functional/sol_v2/test_server_notification.py @@ -21,6 +21,7 @@ from tacker.objects import fields from tacker.tests.functional.sol_v2_common import paramgen from tacker.tests.functional.sol_v2_common import test_vnflcm_basic_common +NUM = 0 TEST_COUNT = 0 RETRY_LIMIT = 10 RETRY_TIMEOUT = 3 @@ -34,6 +35,13 @@ def _make_alarm_id(header, body): return {'alarm_id': id} +def _return_alarm_id(header, body): + global NUM + NUM += 1 + id = f"alarm_id_{NUM}" + return {'alarm_id': id} + + @ddt.ddt class ServerNotificationTest(test_vnflcm_basic_common.CommonVnfLcmTest): @classmethod @@ -524,3 +532,1392 @@ class ServerNotificationTest(test_vnflcm_basic_common.CommonVnfLcmTest): def test_fault_notification_disabled(self): self.fault_notification_autoheal_disabled_test() + + def test_retry_instantiate(self): + """Test retry instantiate when error occurred after instantiate_end + + * About Test operations: + This test includes the following operations. + - 1. LCM-CreateV2 + - 2. LCM-InstantiateV2(error) + - 3. LCM-ShowV2 + - 4. LCM-Show-OpOccV2 + - 5. LCM-RetryV2 + - 6. LCM-ShowV2 + - 7. LCM-Show-OpOccV2 + - 8. LCM-RollbackV2 + - 9. LCM-DeleteV2 + """ + + # 1. LCM-CreateV2 + expected_inst_attrs = [ + 'id', 'vnfdId', 'vnfProvider', 'vnfProductName', + 'vnfSoftwareVersion', 'vnfdVersion', + 'instantiationState', '_links' + ] + create_req = paramgen.create_vnf_min(self.svn_id) + resp, body = self.create_vnf_instance(create_req) + self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + self.check_resp_body(body, expected_inst_attrs) + inst_id = body['id'] + + # check usageState of VNF Package + self.check_package_usage(self.svn_pkg, 'IN_USE') + + # 2. LCM-InstantiateV2(error) + server_notification_uri = ( + f'http://localhost:{self.get_server_port()}/server_notification') + self.set_server_callback( + 'POST', '/server_notification', status_code=200, + response_headers={"Content-Type": "application/json"}, + callback=_return_alarm_id) + global NUM + NUM = 0 + + instantiate_req = paramgen.instantiate_vnf_min() + instantiate_req['additionalParams'] = { + 'ServerNotifierUri': server_notification_uri, + 'ServerNotifierFaultID': ['1111', '1234'] + } + instantiate_req['vnfConfigurableProperties'] = { + 'isAutohealEnabled': False + } + + self.put_fail_file('instantiate_end') + resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_failed_temp(lcmocc_id) + + # 3. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('NOT_INSTANTIATED', body['instantiationState']) + + self.assertEqual(None, body.get('instantiatedVnfInfo')) + + # 4. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('FAILED_TEMP', body['operationState']) + self.assertEqual('INSTANTIATE', body['operation']) + + vm_infos = self.get_server_details(None) + vm_ids = {vm_info.get('id') for vm_info in vm_infos + if vm_info['name'] in ['VDU1', 'VDU2']} + alarm_ids = {'alarm_id_1', 'alarm_id_2'} + for data in (body['error']['userScriptErrHandlingData'] + ['serverNotification']): + self.assertIn(data['serverId'], vm_ids) + self.assertIn(data['alarmId'], alarm_ids) + self.assertEqual(2, len( + body['error']['userScriptErrHandlingData']['serverNotification'])) + + # 5. LCM-RetryV2 + resp, body = self.retry_lcmocc(lcmocc_id) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_delete(resp) + self.wait_lcmocc_failed_temp(lcmocc_id) + self.rm_fail_file('instantiate_end') + + # 6. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('NOT_INSTANTIATED', body['instantiationState']) + + self.assertEqual(None, body.get('instantiatedVnfInfo')) + + # 7. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('FAILED_TEMP', body['operationState']) + self.assertEqual('INSTANTIATE', body['operation']) + + vm_infos = self.get_server_details(None) + vm_ids = {vm_info.get('id') for vm_info in vm_infos + if vm_info['name'] in ['VDU1', 'VDU2']} + alarm_ids = {'alarm_id_1', 'alarm_id_2'} + for data in (body['error']['userScriptErrHandlingData'] + ['serverNotification']): + self.assertIn(data['serverId'], vm_ids) + self.assertIn(data['alarmId'], alarm_ids) + self.assertEqual(2, len( + body['error']['userScriptErrHandlingData']['serverNotification'])) + + # 8. LCM-RollbackV2 + resp, body = self.rollback_lcmocc(lcmocc_id) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_delete(resp) + self.wait_lcmocc_rolled_back(lcmocc_id) + + # 9. LCM-DeleteV2 + resp, body = self.exec_lcm_operation(self.delete_vnf_instance, inst_id) + self.assertEqual(204, resp.status_code) + + def test_retry_scale_in(self): + """Test retry scale-in when error occurred after scale_start + + * About Test operations: + This test includes the following operations. + - 1. LCM-CreateV2 + - 2. LCM-InstantiateV2 + - 3. LCM-ShowV2 + - 4. LCM-ScaleV2(in)(error) + - 5. LCM-ShowV2 + - 6. LCM-Show-OpOccV2 + - 7. LCM-RetryV2 + - 8. LCM-ShowV2 + - 9. LCM-Show-OpOccV2 + - 10. LCM-FailV2 + - 11. LCM-Show-OpOccV2 + - 12. LCM-TerminateV2 + - 13. LCM-DeleteV2 + """ + + # 1. LCM-CreateV2 + expected_inst_attrs = [ + 'id', 'vnfdId', 'vnfProvider', 'vnfProductName', + 'vnfSoftwareVersion', 'vnfdVersion', + 'instantiationState', '_links' + ] + create_req = paramgen.create_vnf_min(self.svn_id) + resp, body = self.create_vnf_instance(create_req) + self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + self.check_resp_body(body, expected_inst_attrs) + inst_id = body['id'] + + # check usageState of VNF Package + self.check_package_usage(self.svn_pkg, 'IN_USE') + + # 2. LCM-InstantiateV2 + server_notification_uri = ( + f'http://localhost:{self.get_server_port()}/server_notification') + self.set_server_callback( + 'POST', '/server_notification', status_code=200, + response_headers={"Content-Type": "application/json"}, + callback=_return_alarm_id) + global NUM + NUM = 0 + + instantiate_req = paramgen.instantiate_vnf_min() + instantiate_req['instantiationLevelId'] = "instantiation_level_2" + instantiate_req['additionalParams'] = { + 'ServerNotifierUri': server_notification_uri, + 'ServerNotifierFaultID': ['1111', '1234'] + } + instantiate_req['vnfConfigurableProperties'] = { + 'isAutohealEnabled': False + } + + resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # 3. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('INSTANTIATED', body['instantiationState']) + + metadata_infos = [{'serverId': res['computeResource']['resourceId'], + 'alarmId': res['metadata']['server_notification'][ + 'alarmId']} for res in + body['instantiatedVnfInfo']['vnfcResourceInfo']] + alarm_ids = {data['alarmId'] for data in metadata_infos} + expect_alarm_ids = {'alarm_id_1', 'alarm_id_2', + 'alarm_id_3', 'alarm_id_4'} + self.assertSetEqual(alarm_ids, expect_alarm_ids) + + # 4. LCM-ScaleV2(in)(error) + self.put_fail_file('scale_start') + scale_req = paramgen.scalein_vnf_min() + resp, body = self.scale_vnf_instance(inst_id, scale_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_failed_temp(lcmocc_id) + + # 5. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('INSTANTIATED', body['instantiationState']) + + metadata_infos = [{'serverId': res['computeResource']['resourceId'], + 'alarmId': res['metadata']['server_notification'][ + 'alarmId']} for res in + body['instantiatedVnfInfo']['vnfcResourceInfo']] + alarm_ids = {data['alarmId'] for data in metadata_infos} + expect_alarm_ids = {'alarm_id_1', 'alarm_id_2', + 'alarm_id_3', 'alarm_id_4'} + self.assertSetEqual(alarm_ids, expect_alarm_ids) + + # 6. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('FAILED_TEMP', body['operationState']) + self.assertEqual('SCALE', body['operation']) + + vm_infos = self.get_server_details(None) + vm_ids = {vm_info.get('id') for vm_info in vm_infos + if vm_info['name'] == 'VDU1'} + for data in (body['error']['userScriptErrHandlingData'] + ['serverNotification']): + self.assertIn(data['serverId'], vm_ids) + self.assertIsNone(data.get('alarmId')) + self.assertEqual(1, len( + body['error']['userScriptErrHandlingData']['serverNotification'])) + + # 7. LCM-RetryV2 + resp, body = self.retry_lcmocc(lcmocc_id) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_delete(resp) + self.wait_lcmocc_failed_temp(lcmocc_id) + self.rm_fail_file('scale_start') + + # 8. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('INSTANTIATED', body['instantiationState']) + + metadata_infos = [{'serverId': res['computeResource']['resourceId'], + 'alarmId': res['metadata']['server_notification'][ + 'alarmId']} for res in + body['instantiatedVnfInfo']['vnfcResourceInfo']] + alarm_ids = {data['alarmId'] for data in metadata_infos} + expect_alarm_ids = {'alarm_id_1', 'alarm_id_2', + 'alarm_id_3', 'alarm_id_4'} + self.assertSetEqual(alarm_ids, expect_alarm_ids) + + # 9. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('FAILED_TEMP', body['operationState']) + self.assertEqual('SCALE', body['operation']) + + vm_infos = self.get_server_details(None) + vm_ids = {vm_info.get('id') for vm_info in vm_infos + if vm_info['name'] == 'VDU1'} + for data in (body['error']['userScriptErrHandlingData'] + ['serverNotification']): + self.assertIn(data['serverId'], vm_ids) + self.assertIsNone(data.get('alarmId')) + self.assertEqual(1, len( + body['error']['userScriptErrHandlingData']['serverNotification'])) + + # 10. LCM-FailV2 + resp, body = self.fail_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + + # 11. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('FAILED', body['operationState']) + self.assertEqual('SCALE', body['operation']) + + vm_infos = self.get_server_details(None) + vm_ids = {vm_info.get('id') for vm_info in vm_infos + if vm_info['name'] == 'VDU1'} + for data in (body['error']['userScriptErrHandlingData'] + ['serverNotification']): + self.assertIn(data['serverId'], vm_ids) + self.assertIsNone(data.get('alarmId')) + self.assertEqual(1, len( + body['error']['userScriptErrHandlingData']['serverNotification'])) + + # 12. LCM-TerminateV2 + terminate_req = paramgen.terminate_vnf_min() + resp, body = self.terminate_vnf_instance(inst_id, terminate_req) + self.assertEqual(202, resp.status_code) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # 10. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('COMPLETED', body['operationState']) + self.assertEqual('TERMINATE', body['operation']) + + # 13. LCM-DeleteV2 + resp, body = self.exec_lcm_operation(self.delete_vnf_instance, inst_id) + self.assertEqual(204, resp.status_code) + + def test_retry_scale_out(self): + """Test retry scale-out when error occurred after scale_end + + * About Test operations: + This test includes the following operations. + - 1. LCM-CreateV2 + - 2. LCM-InstantiateV2 + - 3. LCM-ShowV2 + - 4. LCM-ScaleV2(out)(error) + - 5. LCM-ShowV2 + - 6. LCM-Show-OpOccV2 + - 7. LCM-RetryV2 + - 8. LCM-ShowV2 + - 9. LCM-Show-OpOccV2 + - 10. LCM-FailV2 + - 11. LCM-Show-OpOccV2 + - 12. LCM-TerminateV2 + - 13. LCM-DeleteV2 + """ + + # 1. LCM-CreateV2 + expected_inst_attrs = [ + 'id', 'vnfdId', 'vnfProvider', 'vnfProductName', + 'vnfSoftwareVersion', 'vnfdVersion', + 'instantiationState', '_links' + ] + create_req = paramgen.create_vnf_min(self.svn_id) + resp, body = self.create_vnf_instance(create_req) + self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + self.check_resp_body(body, expected_inst_attrs) + inst_id = body['id'] + + # check usageState of VNF Package + self.check_package_usage(self.svn_pkg, 'IN_USE') + + # 2. LCM-InstantiateV2 + server_notification_uri = ( + f'http://localhost:{self.get_server_port()}/server_notification') + self.set_server_callback( + 'POST', '/server_notification', status_code=200, + response_headers={"Content-Type": "application/json"}, + callback=_return_alarm_id) + global NUM + NUM = 0 + + instantiate_req = paramgen.instantiate_vnf_min() + instantiate_req['additionalParams'] = { + 'ServerNotifierUri': server_notification_uri, + 'ServerNotifierFaultID': ['1111', '1234'] + } + instantiate_req['vnfConfigurableProperties'] = { + 'isAutohealEnabled': False + } + + resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # 3. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('INSTANTIATED', body['instantiationState']) + + metadata_infos = [{'serverId': res['computeResource']['resourceId'], + 'alarmId': res['metadata']['server_notification'][ + 'alarmId']} for res in + body['instantiatedVnfInfo']['vnfcResourceInfo']] + alarm_ids = {data['alarmId'] for data in metadata_infos} + expect_alarm_ids = {'alarm_id_1', 'alarm_id_2'} + self.assertSetEqual(alarm_ids, expect_alarm_ids) + + # 4. LCM-ScaleV2(out)(error) + self.put_fail_file('scale_end') + scale_req = paramgen.scaleout_vnf_min() + resp, body = self.scale_vnf_instance(inst_id, scale_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_failed_temp(lcmocc_id) + + # 5. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('INSTANTIATED', body['instantiationState']) + + metadata_infos = [{'serverId': res['computeResource']['resourceId'], + 'alarmId': res['metadata']['server_notification'][ + 'alarmId']} for res in + body['instantiatedVnfInfo']['vnfcResourceInfo']] + alarm_ids = {data['alarmId'] for data in metadata_infos} + server_ids = {data['serverId'] for data in metadata_infos} + expect_alarm_ids = {'alarm_id_1', 'alarm_id_2'} + self.assertSetEqual(alarm_ids, expect_alarm_ids) + + # 6. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('FAILED_TEMP', body['operationState']) + self.assertEqual('SCALE', body['operation']) + + vm_infos = self.get_server_details(None) + vm_ids = {vm_info.get('id') for vm_info in vm_infos + if vm_info['name'] == 'VDU1'} + for data in (body['error']['userScriptErrHandlingData'] + ['serverNotification']): + self.assertIn(data['serverId'], vm_ids) + self.assertNotIn(data['serverId'], server_ids) + self.assertEqual('alarm_id_3', data['alarmId']) + self.assertEqual(1, len( + body['error']['userScriptErrHandlingData']['serverNotification'])) + + # 7. LCM-RetryV2 + resp, body = self.retry_lcmocc(lcmocc_id) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_delete(resp) + self.wait_lcmocc_failed_temp(lcmocc_id) + self.rm_fail_file('scale_end') + + # 8. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('INSTANTIATED', body['instantiationState']) + + metadata_infos = [{'serverId': res['computeResource']['resourceId'], + 'alarmId': res['metadata']['server_notification'][ + 'alarmId']} for res in + body['instantiatedVnfInfo']['vnfcResourceInfo']] + alarm_ids = {data['alarmId'] for data in metadata_infos} + server_ids = {data['serverId'] for data in metadata_infos} + expect_alarm_ids = {'alarm_id_1', 'alarm_id_2'} + self.assertSetEqual(alarm_ids, expect_alarm_ids) + + # 9. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('FAILED_TEMP', body['operationState']) + self.assertEqual('SCALE', body['operation']) + + vm_infos = self.get_server_details(None) + vm_ids = {vm_info.get('id') for vm_info in vm_infos + if vm_info['name'] == 'VDU1'} + for data in (body['error']['userScriptErrHandlingData'] + ['serverNotification']): + self.assertIn(data['serverId'], vm_ids) + self.assertNotIn(data['serverId'], server_ids) + self.assertEqual('alarm_id_3', data['alarmId']) + self.assertEqual(1, len( + body['error']['userScriptErrHandlingData']['serverNotification'])) + + # 10. LCM-FailV2 + resp, body = self.fail_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + + # 11. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('FAILED', body['operationState']) + self.assertEqual('SCALE', body['operation']) + + vm_infos = self.get_server_details(None) + vm_ids = {vm_info.get('id') for vm_info in vm_infos + if vm_info['name'] == 'VDU1'} + for data in (body['error']['userScriptErrHandlingData'] + ['serverNotification']): + self.assertIn(data['serverId'], vm_ids) + self.assertNotIn(data['serverId'], server_ids) + self.assertEqual('alarm_id_3', data['alarmId']) + self.assertEqual(1, len( + body['error']['userScriptErrHandlingData']['serverNotification'])) + + # 12. LCM-TerminateV2 + terminate_req = paramgen.terminate_vnf_min() + resp, body = self.terminate_vnf_instance(inst_id, terminate_req) + self.assertEqual(202, resp.status_code) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # 10. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('COMPLETED', body['operationState']) + self.assertEqual('TERMINATE', body['operation']) + + # 13. LCM-DeleteV2 + resp, body = self.exec_lcm_operation(self.delete_vnf_instance, inst_id) + self.assertEqual(204, resp.status_code) + + def test_retry_heal_all(self): + """Test retry heal(all) when error occurred after heal_end + + * About Test operations: + This test includes the following operations. + - 1. LCM-CreateV2 + - 2. LCM-InstantiateV2 + - 3. LCM-ShowV2 + - 4. LCM-HealV2(all, all=True)(error) + - 5. LCM-ShowV2 + - 6. LCM-Show-OpOccV2 + - 7. LCM-RetryV2 + - 8. LCM-ShowV2 + - 9. LCM-Show-OpOccV2 + - 10. LCM-FailV2 + - 11. LCM-Show-OpOccV2 + - 12. LCM-TerminateV2 + - 13. LCM-DeleteV2 + """ + + # 1. LCM-CreateV2 + expected_inst_attrs = [ + 'id', 'vnfdId', 'vnfProvider', 'vnfProductName', + 'vnfSoftwareVersion', 'vnfdVersion', + 'instantiationState', '_links' + ] + create_req = paramgen.create_vnf_min(self.svn_id) + resp, body = self.create_vnf_instance(create_req) + self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + self.check_resp_body(body, expected_inst_attrs) + inst_id = body['id'] + + # check usageState of VNF Package + self.check_package_usage(self.svn_pkg, 'IN_USE') + + # 2. LCM-InstantiateV2 + server_notification_uri = ( + f'http://localhost:{self.get_server_port()}/server_notification') + self.set_server_callback( + 'POST', '/server_notification', status_code=200, + response_headers={"Content-Type": "application/json"}, + callback=_return_alarm_id) + global NUM + NUM = 0 + + instantiate_req = paramgen.instantiate_vnf_min() + instantiate_req['additionalParams'] = { + 'ServerNotifierUri': server_notification_uri, + 'ServerNotifierFaultID': ['1111', '1234'] + } + instantiate_req['vnfConfigurableProperties'] = { + 'isAutohealEnabled': False + } + + resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # 3. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('INSTANTIATED', body['instantiationState']) + + metadata_infos = [{'serverId': res['computeResource']['resourceId'], + 'alarmId': res['metadata']['server_notification'][ + 'alarmId']} for res in + body['instantiatedVnfInfo']['vnfcResourceInfo']] + alarm_ids = {data['alarmId'] for data in metadata_infos} + expect_alarm_ids = {'alarm_id_1', 'alarm_id_2'} + self.assertSetEqual(alarm_ids, expect_alarm_ids) + + # 4. LCM-HealV2(all)(error) + self.put_fail_file('heal_end') + heal_req = paramgen.heal_vnf_all_max_with_parameter(True) + resp, body = self.heal_vnf_instance(inst_id, heal_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_failed_temp(lcmocc_id) + + # 5. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('INSTANTIATED', body['instantiationState']) + + metadata_infos = [{'serverId': res['computeResource']['resourceId'], + 'alarmId': res['metadata']['server_notification'][ + 'alarmId']} for res in + body['instantiatedVnfInfo']['vnfcResourceInfo']] + alarm_ids = {data['alarmId'] for data in metadata_infos} + server_ids = {data['serverId'] for data in metadata_infos} + expect_alarm_ids = {'alarm_id_1', 'alarm_id_2'} + self.assertSetEqual(alarm_ids, expect_alarm_ids) + + # 6. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('FAILED_TEMP', body['operationState']) + self.assertEqual('HEAL', body['operation']) + + vm_infos = self.get_server_details(None) + vm_ids = {vm_info.get('id') for vm_info in vm_infos + if vm_info['name'] in ['VDU1', 'VDU2']} + expect_alarm_ids = {'alarm_id_3', 'alarm_id_4'} + for data in (body['error']['userScriptErrHandlingData'] + ['serverNotification']): + if data['serverId'] in server_ids: + self.assertIsNone(data.get('alarmId')) + server_ids.remove(data['serverId']) + else: + self.assertIn(data['serverId'], vm_ids) + self.assertIn(data['alarmId'], expect_alarm_ids) + self.assertEqual(4, len( + body['error']['userScriptErrHandlingData']['serverNotification'])) + + # 7. LCM-RetryV2 + resp, body = self.retry_lcmocc(lcmocc_id) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_delete(resp) + self.wait_lcmocc_failed_temp(lcmocc_id) + self.rm_fail_file('heal_end') + + # 8. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('INSTANTIATED', body['instantiationState']) + + metadata_infos = [{'serverId': res['computeResource']['resourceId'], + 'alarmId': res['metadata']['server_notification'][ + 'alarmId']} for res in + body['instantiatedVnfInfo']['vnfcResourceInfo']] + alarm_ids = {data['alarmId'] for data in metadata_infos} + server_ids = {data['serverId'] for data in metadata_infos} + expect_alarm_ids = {'alarm_id_1', 'alarm_id_2'} + self.assertSetEqual(alarm_ids, expect_alarm_ids) + + # 9. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('FAILED_TEMP', body['operationState']) + self.assertEqual('HEAL', body['operation']) + + vm_infos = self.get_server_details(None) + vm_ids = {vm_info.get('id') for vm_info in vm_infos + if vm_info['name'] in ['VDU1', 'VDU2']} + expect_alarm_ids = {'alarm_id_5', 'alarm_id_6'} + for data in (body['error']['userScriptErrHandlingData'] + ['serverNotification']): + if data['serverId'] in server_ids: + self.assertIsNone(data.get('alarmId')) + server_ids.remove(data['serverId']) + else: + self.assertIn(data['serverId'], vm_ids) + self.assertIn(data['alarmId'], expect_alarm_ids) + self.assertEqual(4, len( + body['error']['userScriptErrHandlingData']['serverNotification'])) + + # 10. LCM-FailV2 + resp, body = self.fail_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + + # 11. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('FAILED', body['operationState']) + self.assertEqual('HEAL', body['operation']) + + server_ids = {data['serverId'] for data in metadata_infos} + vm_infos = self.get_server_details(None) + vm_ids = {vm_info.get('id') for vm_info in vm_infos + if vm_info['name'] in ['VDU1', 'VDU2']} + expect_alarm_ids = {'alarm_id_5', 'alarm_id_6'} + for data in (body['error']['userScriptErrHandlingData'] + ['serverNotification']): + if data['serverId'] in server_ids: + self.assertIsNone(data.get('alarmId')) + server_ids.remove(data['serverId']) + else: + self.assertIn(data['serverId'], vm_ids) + self.assertIn(data['alarmId'], expect_alarm_ids) + self.assertEqual(4, len( + body['error']['userScriptErrHandlingData']['serverNotification'])) + + # 12. LCM-TerminateV2 + terminate_req = paramgen.terminate_vnf_min() + resp, body = self.terminate_vnf_instance(inst_id, terminate_req) + self.assertEqual(202, resp.status_code) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # 10. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('COMPLETED', body['operationState']) + self.assertEqual('TERMINATE', body['operation']) + + # 13. LCM-DeleteV2 + resp, body = self.exec_lcm_operation(self.delete_vnf_instance, inst_id) + self.assertEqual(204, resp.status_code) + + def test_retry_heal_vnfc(self): + """Test retry terminate when error occurred after terminate_start + + * About Test operations: + This test includes the following operations. + - 1. LCM-CreateV2 + - 2. LCM-InstantiateV2 + - 3. LCM-ShowV2 + - 4. LCM-HealV2(vnfc)(error) + - 5. LCM-ShowV2 + - 6. LCM-Show-OpOccV2 + - 7. LCM-RetryV2 + - 8. LCM-ShowV2 + - 9. LCM-Show-OpOccV2 + - 10. LCM-FailV2 + - 11. LCM-Show-OpOccV2 + - 12. LCM-TerminateV2 + - 13. LCM-DeleteV2 + """ + + # 1. LCM-CreateV2 + expected_inst_attrs = [ + 'id', 'vnfdId', 'vnfProvider', 'vnfProductName', + 'vnfSoftwareVersion', 'vnfdVersion', + 'instantiationState', '_links' + ] + create_req = paramgen.create_vnf_min(self.svn_id) + resp, body = self.create_vnf_instance(create_req) + self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + self.check_resp_body(body, expected_inst_attrs) + inst_id = body['id'] + + # check usageState of VNF Package + self.check_package_usage(self.svn_pkg, 'IN_USE') + + # 2. LCM-InstantiateV2 + server_notification_uri = ( + f'http://localhost:{self.get_server_port()}/server_notification') + self.set_server_callback( + 'POST', '/server_notification', status_code=200, + response_headers={"Content-Type": "application/json"}, + callback=_return_alarm_id) + global NUM + NUM = 0 + + instantiate_req = paramgen.instantiate_vnf_min() + instantiate_req['additionalParams'] = { + 'ServerNotifierUri': server_notification_uri, + 'ServerNotifierFaultID': ['1111', '1234'] + } + instantiate_req['vnfConfigurableProperties'] = { + 'isAutohealEnabled': False + } + + resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # 3. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('INSTANTIATED', body['instantiationState']) + + metadata_infos = [{'serverId': res['computeResource']['resourceId'], + 'alarmId': res['metadata']['server_notification'][ + 'alarmId']} for res in + body['instantiatedVnfInfo']['vnfcResourceInfo']] + alarm_ids = {data['alarmId'] for data in metadata_infos} + expect_alarm_ids = {'alarm_id_1', 'alarm_id_2'} + self.assertSetEqual(alarm_ids, expect_alarm_ids) + + # 4. LCM-HealV2(vnfc)(error) + self.put_fail_file('heal_end') + vnfc_id = [data['id'] for data in body['instantiatedVnfInfo'][ + 'vnfcResourceInfo'] if data['vduId'] == 'VDU1'][0] + heal_req = paramgen.heal_vnf_vnfc_min('VDU1-' + vnfc_id) + resp, body = self.heal_vnf_instance(inst_id, heal_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_failed_temp(lcmocc_id) + + # 5. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('INSTANTIATED', body['instantiationState']) + + metadata_infos = [{'serverId': res['computeResource']['resourceId'], + 'alarmId': res['metadata']['server_notification'][ + 'alarmId']} for res in + body['instantiatedVnfInfo']['vnfcResourceInfo']] + alarm_ids = {data['alarmId'] for data in metadata_infos} + server_ids = {data['serverId'] for data in metadata_infos} + expect_alarm_ids = {'alarm_id_1', 'alarm_id_2'} + self.assertSetEqual(alarm_ids, expect_alarm_ids) + + # 6. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('FAILED_TEMP', body['operationState']) + self.assertEqual('HEAL', body['operation']) + + vm_infos = self.get_server_details(None) + vm_ids = {vm_info.get('id') for vm_info in vm_infos + if vm_info['name'] == 'VDU1'} + expect_alarm_ids = {'alarm_id_3'} + for data in (body['error']['userScriptErrHandlingData'] + ['serverNotification']): + if data['serverId'] in server_ids: + self.assertIsNone(data.get('alarmId')) + server_ids.remove(data['serverId']) + else: + self.assertIn(data['serverId'], vm_ids) + self.assertIn(data['alarmId'], expect_alarm_ids) + self.assertEqual(2, len( + body['error']['userScriptErrHandlingData']['serverNotification'])) + + # 7. LCM-RetryV2 + resp, body = self.retry_lcmocc(lcmocc_id) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_delete(resp) + self.wait_lcmocc_failed_temp(lcmocc_id) + self.rm_fail_file('heal_end') + + # 8. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('INSTANTIATED', body['instantiationState']) + + metadata_infos = [{'serverId': res['computeResource']['resourceId'], + 'alarmId': res['metadata']['server_notification'][ + 'alarmId']} for res in + body['instantiatedVnfInfo']['vnfcResourceInfo']] + alarm_ids = {data['alarmId'] for data in metadata_infos} + server_ids = {data['serverId'] for data in metadata_infos} + expect_alarm_ids = {'alarm_id_1', 'alarm_id_2'} + self.assertSetEqual(alarm_ids, expect_alarm_ids) + + # 9. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('FAILED_TEMP', body['operationState']) + self.assertEqual('HEAL', body['operation']) + + vm_infos = self.get_server_details(None) + vm_ids = {vm_info.get('id') for vm_info in vm_infos + if vm_info['name'] == 'VDU1'} + expect_alarm_ids = {'alarm_id_4'} + for data in (body['error']['userScriptErrHandlingData'] + ['serverNotification']): + if data['serverId'] in server_ids: + self.assertIsNone(data.get('alarmId')) + server_ids.remove(data['serverId']) + else: + self.assertIn(data['serverId'], vm_ids) + self.assertIn(data['alarmId'], expect_alarm_ids) + self.assertEqual(2, len( + body['error']['userScriptErrHandlingData']['serverNotification'])) + + # 10. LCM-FailV2 + resp, body = self.fail_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + + # 11. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('FAILED', body['operationState']) + self.assertEqual('HEAL', body['operation']) + + server_ids = {data['serverId'] for data in metadata_infos} + vm_infos = self.get_server_details(None) + vm_ids = {vm_info.get('id') for vm_info in vm_infos + if vm_info['name'] == 'VDU1'} + expect_alarm_ids = {'alarm_id_4'} + for data in (body['error']['userScriptErrHandlingData'] + ['serverNotification']): + if data['serverId'] in server_ids: + self.assertIsNone(data.get('alarmId')) + server_ids.remove(data['serverId']) + else: + self.assertIn(data['serverId'], vm_ids) + self.assertIn(data['alarmId'], expect_alarm_ids) + self.assertEqual(2, len( + body['error']['userScriptErrHandlingData']['serverNotification'])) + + # 12. LCM-TerminateV2 + terminate_req = paramgen.terminate_vnf_min() + resp, body = self.terminate_vnf_instance(inst_id, terminate_req) + self.assertEqual(202, resp.status_code) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # 10. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('COMPLETED', body['operationState']) + self.assertEqual('TERMINATE', body['operation']) + + # 13. LCM-DeleteV2 + resp, body = self.exec_lcm_operation(self.delete_vnf_instance, inst_id) + self.assertEqual(204, resp.status_code) + + def test_retry_terminate(self): + """Test retry terminate when error occurred after terminate_start + + * About Test operations: + This test includes the following operations. + - 1. LCM-CreateV2 + - 2. LCM-InstantiateV2 + - 3. LCM-ShowV2 + - 4. LCM-TerminateV2(error) + - 5. LCM-ShowV2 + - 6. LCM-Show-OpOccV2 + - 7. LCM-RetryV2 + - 8. LCM-ShowV2 + - 9. LCM-Show-OpOccV2 + - 10. LCM-FailV2 + - 11. LCM-Show-OpOccV2 + - 12. LCM-TerminateV2 + - 13. LCM-DeleteV2 + """ + + # 1. LCM-CreateV2 + expected_inst_attrs = [ + 'id', 'vnfdId', 'vnfProvider', 'vnfProductName', + 'vnfSoftwareVersion', 'vnfdVersion', + 'instantiationState', '_links' + ] + create_req = paramgen.create_vnf_min(self.svn_id) + resp, body = self.create_vnf_instance(create_req) + self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + self.check_resp_body(body, expected_inst_attrs) + inst_id = body['id'] + + # check usageState of VNF Package + self.check_package_usage(self.svn_pkg, 'IN_USE') + + # 2. LCM-InstantiateV2 + server_notification_uri = ( + f'http://localhost:{self.get_server_port()}/server_notification') + self.set_server_callback( + 'POST', '/server_notification', status_code=200, + response_headers={"Content-Type": "application/json"}, + callback=_return_alarm_id) + global NUM + NUM = 0 + + instantiate_req = paramgen.instantiate_vnf_min() + instantiate_req['additionalParams'] = { + 'ServerNotifierUri': server_notification_uri, + 'ServerNotifierFaultID': ['1111', '1234'] + } + instantiate_req['vnfConfigurableProperties'] = { + 'isAutohealEnabled': False + } + + resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # 3. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('INSTANTIATED', body['instantiationState']) + + metadata_infos = [{'serverId': res['computeResource']['resourceId'], + 'alarmId': res['metadata']['server_notification'][ + 'alarmId']} for res in + body['instantiatedVnfInfo']['vnfcResourceInfo']] + alarm_ids = {data['alarmId'] for data in metadata_infos} + expect_alarm_ids = {'alarm_id_1', 'alarm_id_2'} + self.assertSetEqual(alarm_ids, expect_alarm_ids) + + # 4. LCM-TerminateV2(error) + self.put_fail_file('terminate_start') + terminate_req = paramgen.terminate_vnf_min() + resp, body = self.terminate_vnf_instance(inst_id, terminate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_failed_temp(lcmocc_id) + + # 5. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('INSTANTIATED', body['instantiationState']) + + metadata_infos = [{'serverId': res['computeResource']['resourceId'], + 'alarmId': res['metadata']['server_notification'][ + 'alarmId']} for res in + body['instantiatedVnfInfo']['vnfcResourceInfo']] + alarm_ids = {data['alarmId'] for data in metadata_infos} + server_ids = {data['serverId'] for data in metadata_infos} + expect_alarm_ids = {'alarm_id_1', 'alarm_id_2'} + self.assertSetEqual(alarm_ids, expect_alarm_ids) + + # 6. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('FAILED_TEMP', body['operationState']) + self.assertEqual('TERMINATE', body['operation']) + + for data in (body['error']['userScriptErrHandlingData'] + ['serverNotification']): + self.assertIn(data['serverId'], server_ids) + server_ids.remove(data['serverId']) + self.assertIsNone(data.get('alarmId')) + self.assertEqual(2, len( + body['error']['userScriptErrHandlingData']['serverNotification'])) + + # 7. LCM-RetryV2 + resp, body = self.retry_lcmocc(lcmocc_id) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_delete(resp) + self.wait_lcmocc_failed_temp(lcmocc_id) + self.rm_fail_file('terminate_start') + + # 8. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('INSTANTIATED', body['instantiationState']) + + metadata_infos = [{'serverId': res['computeResource']['resourceId'], + 'alarmId': res['metadata']['server_notification'][ + 'alarmId']} for res in + body['instantiatedVnfInfo']['vnfcResourceInfo']] + alarm_ids = {data['alarmId'] for data in metadata_infos} + server_ids = {data['serverId'] for data in metadata_infos} + expect_alarm_ids = {'alarm_id_1', 'alarm_id_2'} + self.assertSetEqual(alarm_ids, expect_alarm_ids) + + # 9. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('FAILED_TEMP', body['operationState']) + self.assertEqual('TERMINATE', body['operation']) + + for data in (body['error']['userScriptErrHandlingData'] + ['serverNotification']): + self.assertIn(data['serverId'], server_ids) + server_ids.remove(data['serverId']) + self.assertIsNone(data.get('alarmId')) + self.assertEqual(2, len( + body['error']['userScriptErrHandlingData']['serverNotification'])) + + # 10. LCM-FailV2 + resp, body = self.fail_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + + # 11. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('FAILED', body['operationState']) + self.assertEqual('TERMINATE', body['operation']) + + server_ids = {data['serverId'] for data in metadata_infos} + for data in (body['error']['userScriptErrHandlingData'] + ['serverNotification']): + self.assertIn(data['serverId'], server_ids) + server_ids.remove(data['serverId']) + self.assertIsNone(data.get('alarmId')) + self.assertEqual(2, len( + body['error']['userScriptErrHandlingData']['serverNotification'])) + + # 12. LCM-TerminateV2 + terminate_req = paramgen.terminate_vnf_min() + resp, body = self.terminate_vnf_instance(inst_id, terminate_req) + self.assertEqual(202, resp.status_code) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # 10. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('COMPLETED', body['operationState']) + self.assertEqual('TERMINATE', body['operation']) + + # 13. LCM-DeleteV2 + resp, body = self.exec_lcm_operation(self.delete_vnf_instance, inst_id) + self.assertEqual(204, resp.status_code) + + def test_rollback_instantiate(self): + """Test rollback instantiate when error occurred after instantiate_end + + * About Test operations: + This test includes the following operations. + - 1. LCM-CreateV2 + - 2. LCM-InstantiateV2(error) + - 3. LCM-ShowV2 + - 4. LCM-Show-OpOccV2 + - 5. LCM-RollbackV2 + - 6. LCM-ShowV2 + - 7. LCM-Show-OpOccV2 + - 8. LCM-DeleteV2 + """ + + # 1. LCM-CreateV2 + expected_inst_attrs = [ + 'id', 'vnfdId', 'vnfProvider', 'vnfProductName', + 'vnfSoftwareVersion', 'vnfdVersion', + 'instantiationState', '_links' + ] + create_req = paramgen.create_vnf_min(self.svn_id) + resp, body = self.create_vnf_instance(create_req) + self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + self.check_resp_body(body, expected_inst_attrs) + inst_id = body['id'] + + # check usageState of VNF Package + self.check_package_usage(self.svn_pkg, 'IN_USE') + + # 2. LCM-InstantiateV2(error) + server_notification_uri = ( + f'http://localhost:{self.get_server_port()}/server_notification') + self.set_server_callback( + 'POST', '/server_notification', status_code=200, + response_headers={"Content-Type": "application/json"}, + callback=_return_alarm_id) + global NUM + NUM = 0 + + instantiate_req = paramgen.instantiate_vnf_min() + instantiate_req['additionalParams'] = { + 'ServerNotifierUri': server_notification_uri, + 'ServerNotifierFaultID': ['1111', '1234'] + } + instantiate_req['vnfConfigurableProperties'] = { + 'isAutohealEnabled': False + } + + self.put_fail_file('instantiate_end') + resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_failed_temp(lcmocc_id) + self.rm_fail_file('instantiate_end') + + # 3. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('NOT_INSTANTIATED', body['instantiationState']) + self.assertEqual(None, body.get('instantiatedVnfInfo')) + + # 4. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('FAILED_TEMP', body['operationState']) + self.assertEqual('INSTANTIATE', body['operation']) + + vm_infos = self.get_server_details(None) + vm_ids = {vm_info.get('id') for vm_info in vm_infos + if vm_info['name'] in ['VDU1', 'VDU2']} + alarm_ids = {'alarm_id_1', 'alarm_id_2'} + for data in (body['error']['userScriptErrHandlingData'] + ['serverNotification']): + self.assertIn(data['serverId'], vm_ids) + self.assertIn(data['alarmId'], alarm_ids) + self.assertEqual(2, len( + body['error']['userScriptErrHandlingData']['serverNotification'])) + + # 5. LCM-RollbackV2 + resp, body = self.rollback_lcmocc(lcmocc_id) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_delete(resp) + self.wait_lcmocc_rolled_back(lcmocc_id) + + # 6. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('NOT_INSTANTIATED', body['instantiationState']) + self.assertEqual(None, body.get('instantiatedVnfInfo')) + + # 7. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('ROLLED_BACK', body['operationState']) + self.assertEqual('INSTANTIATE', body['operation']) + + self.assertIsNone(body['error'].get('userScriptErrHandlingData')) + + # 8. LCM-DeleteV2 + resp, body = self.exec_lcm_operation(self.delete_vnf_instance, inst_id) + self.assertEqual(204, resp.status_code) + + def test_rollback_scale_out(self): + """Test rollback scale out when error occurred after scale_end + + * About Test operations: + This test includes the following operations. + - 1. LCM-CreateV2 + - 2. LCM-InstantiateV2 + - 3. LCM-ShowV2 + - 4. LCM-ScaleV2(out)(error) + - 5. LCM-ShowV2 + - 6. LCM-Show-OpOccV2 + - 7. LCM-RollbackV2 + - 8. LCM-ShowV2 + - 9. LCM-Show-OpOccV2 + - 10. LCM-TerminateV2 + - 11. LCM-DeleteV2 + """ + + # 1. LCM-CreateV2 + expected_inst_attrs = [ + 'id', 'vnfdId', 'vnfProvider', 'vnfProductName', + 'vnfSoftwareVersion', 'vnfdVersion', + 'instantiationState', '_links' + ] + create_req = paramgen.create_vnf_min(self.svn_id) + resp, body = self.create_vnf_instance(create_req) + self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + self.check_resp_body(body, expected_inst_attrs) + inst_id = body['id'] + + # check usageState of VNF Package + self.check_package_usage(self.svn_pkg, 'IN_USE') + + # 2. LCM-InstantiateV2 + server_notification_uri = ( + f'http://localhost:{self.get_server_port()}/server_notification') + self.set_server_callback( + 'POST', '/server_notification', status_code=200, + response_headers={"Content-Type": "application/json"}, + callback=_return_alarm_id) + global NUM + NUM = 0 + + instantiate_req = paramgen.instantiate_vnf_min() + instantiate_req['additionalParams'] = { + 'ServerNotifierUri': server_notification_uri, + 'ServerNotifierFaultID': ['1111', '1234'] + } + instantiate_req['vnfConfigurableProperties'] = { + 'isAutohealEnabled': False + } + + resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # 3. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('INSTANTIATED', body['instantiationState']) + + metadata_infos = [{'serverId': res['computeResource']['resourceId'], + 'alarmId': res['metadata']['server_notification'][ + 'alarmId']} for res in + body['instantiatedVnfInfo']['vnfcResourceInfo']] + alarm_ids = {data['alarmId'] for data in metadata_infos} + expect_alarm_ids = {'alarm_id_1', 'alarm_id_2'} + self.assertSetEqual(alarm_ids, expect_alarm_ids) + + # 4. LCM-ScaleV2(out)(error) + self.put_fail_file('scale_end') + scale_req = paramgen.scaleout_vnf_min() + resp, body = self.scale_vnf_instance(inst_id, scale_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_failed_temp(lcmocc_id) + self.rm_fail_file('scale_end') + + # 5. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('INSTANTIATED', body['instantiationState']) + + metadata_infos = [{'serverId': res['computeResource']['resourceId'], + 'alarmId': res['metadata']['server_notification'][ + 'alarmId']} for res in + body['instantiatedVnfInfo']['vnfcResourceInfo']] + alarm_ids = {data['alarmId'] for data in metadata_infos} + server_ids = {data['serverId'] for data in metadata_infos} + expect_alarm_ids = {'alarm_id_1', 'alarm_id_2'} + self.assertSetEqual(alarm_ids, expect_alarm_ids) + + # 6. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('FAILED_TEMP', body['operationState']) + self.assertEqual('SCALE', body['operation']) + + vm_infos = self.get_server_details(None) + vm_ids = {vm_info.get('id') for vm_info in vm_infos + if vm_info['name'] == 'VDU1'} + for data in (body['error']['userScriptErrHandlingData'] + ['serverNotification']): + self.assertIn(data['serverId'], vm_ids) + self.assertNotIn(data['serverId'], server_ids) + self.assertEqual('alarm_id_3', data['alarmId']) + self.assertEqual(1, len( + body['error']['userScriptErrHandlingData']['serverNotification'])) + + # 7. LCM-RollbackV2 + resp, body = self.rollback_lcmocc(lcmocc_id) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_delete(resp) + self.wait_lcmocc_rolled_back(lcmocc_id) + + # 8. LCM-ShowV2 + resp, body = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + self.check_resp_headers_in_get(resp) + self.assertEqual('INSTANTIATED', body['instantiationState']) + + metadata_infos = [{'serverId': res['computeResource']['resourceId'], + 'alarmId': res['metadata']['server_notification'][ + 'alarmId']} for res in + body['instantiatedVnfInfo']['vnfcResourceInfo']] + alarm_ids = {data['alarmId'] for data in metadata_infos} + expect_alarm_ids = {'alarm_id_1', 'alarm_id_2'} + self.assertSetEqual(alarm_ids, expect_alarm_ids) + + # 9. LCM-Show-OpOccV2 + resp, body = self.show_lcmocc(lcmocc_id) + self.assertEqual(200, resp.status_code) + self.assertEqual('ROLLED_BACK', body['operationState']) + self.assertEqual('SCALE', body['operation']) + + self.assertIsNone(body['error'].get('userScriptErrHandlingData')) + + # 10. LCM-TerminateV2 + terminate_req = paramgen.terminate_vnf_min() + resp, body = self.terminate_vnf_instance(inst_id, terminate_req) + self.assertEqual(202, resp.status_code) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # 11. LCM-DeleteV2 + resp, body = self.exec_lcm_operation(self.delete_vnf_instance, inst_id) + self.assertEqual(204, resp.status_code) diff --git a/tacker/tests/functional/sol_v2_common/base_v2.py b/tacker/tests/functional/sol_v2_common/base_v2.py index 92eb029d4..e7ca00415 100644 --- a/tacker/tests/functional/sol_v2_common/base_v2.py +++ b/tacker/tests/functional/sol_v2_common/base_v2.py @@ -161,6 +161,8 @@ class BaseSolV2Test(base_v2.BaseTackerTestV2): def get_server_details(self, server_name): path = "/servers/detail" resp, resp_body = self.nova_client.do_request(path, "GET") + if server_name is None: + return resp_body.get('servers') server_details = None for server in resp_body.get('servers'): diff --git a/tacker/tests/functional/sol_v2_common/samples/server_notification/contents/Definitions/v2_sample2_df_simple.yaml b/tacker/tests/functional/sol_v2_common/samples/server_notification/contents/Definitions/v2_sample2_df_simple.yaml index 496c9f333..095cfaf5e 100644 --- a/tacker/tests/functional/sol_v2_common/samples/server_notification/contents/Definitions/v2_sample2_df_simple.yaml +++ b/tacker/tests/functional/sol_v2_common/samples/server_notification/contents/Definitions/v2_sample2_df_simple.yaml @@ -60,6 +60,10 @@ topology_template: implementation: sample-script modify_information_start: implementation: sample-script + instantiate_rollback_start: + implementation: sample-script + scale_rollback_start: + implementation: sample-script artifacts: sample-script: description: Sample script diff --git a/tacker/tests/functional/sol_v2_common/samples/server_notification/contents/Definitions/v2_sample2_types.yaml b/tacker/tests/functional/sol_v2_common/samples/server_notification/contents/Definitions/v2_sample2_types.yaml index 8fff47c24..3bf199dde 100644 --- a/tacker/tests/functional/sol_v2_common/samples/server_notification/contents/Definitions/v2_sample2_types.yaml +++ b/tacker/tests/functional/sol_v2_common/samples/server_notification/contents/Definitions/v2_sample2_types.yaml @@ -6,6 +6,34 @@ imports: - etsi_nfv_sol001_common_types.yaml - etsi_nfv_sol001_vnfd_types.yaml +interface_types: + sample.test.Vnflcm: + derived_from: tosca.interfaces.nfv.Vnflcm + instantiate_start: + description: Invoked before instantiate + instantiate_end: + description: Invoked after instantiate + heal_start: + description: Invoked before heal + heal_end: + description: Invoked after heal + scale_start: + description: Invoked before scale + scale_end: + description: Invoked after scale + terminate_start: + description: Invoked before terminate + terminate_end: + description: Invoked after terminate + change_external_connectivity_start: + description: Invoked before change_external_connectivity + modify_information_start: + description: Invoked before modify_information + instantiate_rollback_start: + description: Invoked before instantiate_rollback + scale_rollback_start: + description: Invoked before scale_rollback + node_types: company.provider.VNF: derived_from: tosca.nodes.nfv.VNF @@ -52,4 +80,4 @@ node_types: capability: tosca.capabilities.nfv.VirtualLinkable interfaces: Vnflcm: - type: tosca.interfaces.nfv.Vnflcm \ No newline at end of file + type: sample.test.Vnflcm diff --git a/tacker/tests/functional/sol_v2_common/samples/server_notification/contents/Scripts/sample_script.py b/tacker/tests/functional/sol_v2_common/samples/server_notification/contents/Scripts/sample_script.py index c5ab5c024..cc387af52 100644 --- a/tacker/tests/functional/sol_v2_common/samples/server_notification/contents/Scripts/sample_script.py +++ b/tacker/tests/functional/sol_v2_common/samples/server_notification/contents/Scripts/sample_script.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +import copy +import functools import os import pickle import sys @@ -32,20 +34,88 @@ CONF = cfg.CONF class ServerNotificationMgmtDriver(object): - def __init__(self, req, inst, grant_req, grant, csar_dir): + def __init__(self, req, inst, grant_req, grant, csar_dir, + user_script_err_handling_data): self.req = req self.inst = inst self.grant_req = grant_req self.grant = grant self.csar_dir = csar_dir + self.user_script_err_handling_data = user_script_err_handling_data auth_handle = http_client.NoAuthHandle() self.client = http_client.HttpClient(auth_handle) CONF(project='tacker') rpc.init(CONF) self.rpc = conductor_rpc_v2.VnfLcmRpcApiV2() + def _fail(self, method): + if os.path.exists(f'/tmp/{method}'): + raise Exception(f'test {method} error') + + def __getattr__(self, name): + return functools.partial(self._fail, name) + + def _merge_recent_register(self): + """Merge alarms information + + This method will compare the information in vnf_instance + and user_script_err_handling_data, merging the state of + user_script_err_handling_data with the state of vnf_instance + to make them consistent. + """ + + if self.user_script_err_handling_data.get('serverNotification'): + tmp_data = copy.deepcopy(self.user_script_err_handling_data) + rm_server_id = [] + for server_notify in tmp_data.get('serverNotification'): + server_id = server_notify.get('serverId') + res = [res for res in self.inst['instantiatedVnfInfo'][ + 'vnfcResourceInfo'] if res['computeResource'][ + 'resourceId'] == server_id] + if res: + alarm_id = server_notify.get('alarmId') + if alarm_id: + # Since there is registered alarm on the + # monitoring server side, add it to vnfcResourceInfo. + if 'metadata' not in res[0]: + res[0]['metadata'] = {} + res[0]['metadata']['server_notification'] = { + "alarmId": alarm_id} + else: + # Since there is no registered alarm on the + # monitoring server side, delete it from + # vnfcResourceInfo. + if res[0]['metadata'].get('server_notification'): + del res[0]['metadata']['server_notification'] + else: + # Since the alarm registration remains only on the + # monitoring server side, unregister it. + server_notifier_uri, _, tenant = self.get_params() + if server_notify.get('alarmId'): + self._request_unregister( + server_notifier_uri, tenant, server_id, + server_notify.get('alarmId')) + rm_server_id.append(server_id) + self.user_script_err_handling_data['serverNotification'] = [ + data for data in self.user_script_err_handling_data[ + 'serverNotification'] + if data['serverId'] not in rm_server_id + ] + + def _instantiate_scale_rollback(self): + if self.user_script_err_handling_data.get('serverNotification'): + tmp_data = copy.deepcopy(self.user_script_err_handling_data) + server_notifier_uri, _, tenant = self.get_params() + for server_notify in tmp_data.get('serverNotification'): + server_id = server_notify.get('serverId') + alarm_id = server_notify.get('alarmId') + if server_id and alarm_id: + self._request_unregister( + server_notifier_uri, tenant, server_id, alarm_id) + def make_output_dict(self): - return {'vnf_instance': self.inst} + return {'vnf_instance': self.inst, 'user_script_err_handling_data': + self.user_script_err_handling_data} def request_remove_timer(self, vnf_instance_id): self.rpc.server_notification_remove_timer( @@ -66,6 +136,22 @@ class ServerNotificationMgmtDriver(object): LOG.debug( "server_notification unregistration is processed: %d.", resp.status_code) + + if ('serverNotification' not in + self.user_script_err_handling_data): + self.user_script_err_handling_data[ + 'serverNotification'] = [] + rm_target = [data for data in + self.user_script_err_handling_data[ + 'serverNotification'] + if data.get('serverId') + and data['serverId'] == server_id] + if rm_target and rm_target[0].get('alarmId'): + del rm_target[0]['alarmId'] + else: + self.user_script_err_handling_data[ + 'serverNotification'].append({"serverId": server_id}) + except sol_ex.SolException as e: # Even if unregistration is failed for a single alarm_id, # Unregistration should be done for remaining alarm_ids. @@ -125,6 +211,20 @@ class ServerNotificationMgmtDriver(object): LOG.debug( "server_notification registration is processed: %d. " "alarm_id: %s", resp.status_code, res_body['alarm_id']) + + if 'serverNotification' not in self.user_script_err_handling_data: + self.user_script_err_handling_data['serverNotification'] = [] + add_target = [ + data for data in self.user_script_err_handling_data[ + 'serverNotification'] + if data.get('serverId') and data['serverId'] == server_id + ] + if add_target: + add_target[0]['alarmId'] = res_body['alarm_id'] + else: + self.user_script_err_handling_data['serverNotification'].append( + {'serverId': server_id, 'alarmId': res_body['alarm_id']}) + return {'alarmId': res_body['alarm_id'], 'serverId': server_id} def request_register(self): @@ -147,9 +247,7 @@ class ServerNotificationMgmtDriver(object): fault_id = None tenant = None additional_params = self.req.get('additionalParams', None) - if 'instantiatedVnfInfo' not in self.inst: - return (None, None, None) - vnf_info = self.inst['instantiatedVnfInfo'] + vnf_info = self.inst.get('instantiatedVnfInfo') if (additional_params and 'ServerNotifierUri' in additional_params and @@ -172,6 +270,7 @@ class ServerNotificationMgmtDriver(object): return (None, None, None) def terminate_start(self): + self._merge_recent_register() at_least_one_id_unregistered = self.request_unregister() if at_least_one_id_unregistered: self.request_remove_timer(self.inst['id']) @@ -179,19 +278,25 @@ class ServerNotificationMgmtDriver(object): if ('metadata' in self.inst['instantiatedVnfInfo'] and key in self.inst['instantiatedVnfInfo']['metadata']): del self.inst['instantiatedVnfInfo']['metadata'][key] + if os.path.exists('/tmp/terminate_start'): + raise Exception('test terminate_start error') return self.make_output_dict() def scale_start(self): if self.req['type'] != 'SCALE_IN': return + self._merge_recent_register() vnfc_res_ids = [res_def['resource']['resourceId'] for res_def in self.grant_req['removeResources'] if res_def.get('type', None) == 'COMPUTE'] self.request_unregister( isall=False, vnfc_res_ids=vnfc_res_ids) + if os.path.exists('/tmp/scale_start'): + raise Exception('test scale_start error') return self.make_output_dict() def heal_start(self): + self._merge_recent_register() isall = ('additionalParams' in self.req and self.req['additionalParams'].get('all', False) and 'vnfcInstanceId' not in self.req) @@ -200,9 +305,12 @@ class ServerNotificationMgmtDriver(object): if res_def.get('type', None) == 'COMPUTE'] self.request_unregister( isall=isall, vnfc_res_ids=vnfc_res_ids) + if os.path.exists('/tmp/heal_start'): + raise Exception('test heal_start error') return self.make_output_dict() def instantiate_end(self): + self._merge_recent_register() self.request_register() server_notifier_uri, fault_id, _ = self.get_params() vnf_info = self.inst['instantiatedVnfInfo'] @@ -210,16 +318,23 @@ class ServerNotificationMgmtDriver(object): vnf_info['metadata'] = {} vnf_info['metadata']['ServerNotifierUri'] = server_notifier_uri vnf_info['metadata']['ServerNotifierFaultID'] = fault_id + if os.path.exists('/tmp/instantiate_end'): + raise Exception('test instantiate_end error') return self.make_output_dict() def scale_end(self): if self.req['type'] != 'SCALE_OUT': return + self._merge_recent_register() self.request_register() + if os.path.exists('/tmp/scale_end'): + raise Exception('test scale_end error') return self.make_output_dict() def heal_end(self): self.request_register() + if os.path.exists('/tmp/heal_end'): + raise Exception('test heal_end error') return self.make_output_dict() def instantiate_start(self): @@ -228,6 +343,18 @@ class ServerNotificationMgmtDriver(object): def terminate_end(self): pass + def instantiate_rollback_start(self): + self._instantiate_scale_rollback() + if os.path.exists('/tmp/instantiate_rollback_start'): + raise Exception('test instantiate_rollback_start error') + return self.make_output_dict() + + def scale_rollback_start(self): + self._instantiate_scale_rollback() + if os.path.exists('/tmp/scale_rollback_start'): + raise Exception('test scale_rollback_start error') + return self.make_output_dict() + def main(): script_dict = pickle.load(sys.stdin.buffer) @@ -238,12 +365,23 @@ def main(): grant_req = script_dict['grant_request'] grant = script_dict['grant_response'] csar_dir = script_dict['tmp_csar_dir'] + user_script_err_handling_data = script_dict[ + 'user_script_err_handling_data'] - script = ServerNotificationMgmtDriver( - req, inst, grant_req, grant, csar_dir) - output_dict = getattr(script, operation)() - sys.stdout.buffer.write(pickle.dumps(output_dict)) - sys.stdout.flush() + try: + script = ServerNotificationMgmtDriver( + req, inst, grant_req, grant, csar_dir, + user_script_err_handling_data) + output_dict = getattr(script, operation)() + sys.stdout.buffer.write(pickle.dumps(output_dict)) + sys.stdout.flush() + except Exception as mgmt_ex: + output_dict = { + "user_script_err_handling_data": user_script_err_handling_data + } + sys.stdout.buffer.write(pickle.dumps(output_dict)) + sys.stdout.flush() + raise common_ex.MgmtDriverOtherError(error_message=str(mgmt_ex)) if __name__ == "__main__": diff --git a/tacker/tests/unit/sol_refactored/conductor/test_conductor_v2.py b/tacker/tests/unit/sol_refactored/conductor/test_conductor_v2.py index 7bae7e0a1..5ed22802d 100644 --- a/tacker/tests/unit/sol_refactored/conductor/test_conductor_v2.py +++ b/tacker/tests/unit/sol_refactored/conductor/test_conductor_v2.py @@ -312,6 +312,8 @@ class TestConductorV2(db_base.SqlTestCase): # prepare lcmocc = self._create_inst_and_lcmocc( op_state=fields.LcmOperationStateType.FAILED_TEMP) + problem_details = {'userScriptErrHandlingData': {"key": "value"}} + lcmocc.error = objects.ProblemDetails.from_dict(problem_details) self._create_grant_req_and_grant(lcmocc) mocked_get_vnfd.return_value = mock.Mock() ex = sol_ex.StackOperationFailed(sol_detail="unit test", @@ -337,6 +339,7 @@ class TestConductorV2(db_base.SqlTestCase): # get lcmocc from DB to be sure lcmocc saved to DB lcmocc = lcmocc_utils.get_lcmocc(self.context, lcmocc.id) expected = ex.make_problem_details() + expected['userScriptErrHandlingData'] = {"key": "value"} self.assertEqual(expected, lcmocc.error.to_dict()) # check grant_req and grant remain @@ -353,6 +356,8 @@ class TestConductorV2(db_base.SqlTestCase): # prepare lcmocc = self._create_inst_and_lcmocc( op_state=fields.LcmOperationStateType.FAILED_TEMP) + problem_details = {'userScriptErrHandlingData': {"key": "value"}} + lcmocc.error = objects.ProblemDetails.from_dict(problem_details) self._create_grant_req_and_grant(lcmocc) mocked_get_vnfd.return_value = mock.Mock() @@ -386,6 +391,8 @@ class TestConductorV2(db_base.SqlTestCase): # prepare lcmocc = self._create_inst_and_lcmocc( op_state=fields.LcmOperationStateType.FAILED_TEMP) + problem_details = {'userScriptErrHandlingData': {"key": "value"}} + lcmocc.error = objects.ProblemDetails.from_dict(problem_details) self._create_grant_req_and_grant(lcmocc) mocked_get_vnfd.return_value = mock.Mock() ex = sol_ex.StackOperationFailed(sol_detail="unit test", @@ -412,6 +419,7 @@ class TestConductorV2(db_base.SqlTestCase): # get lcmocc from DB to be sure lcmocc saved to DB lcmocc = lcmocc_utils.get_lcmocc(self.context, lcmocc.id) expected = ex.make_problem_details() + expected['userScriptErrHandlingData'] = {"key": "value"} self.assertEqual(expected, lcmocc.error.to_dict()) # check grant_req and grant remain diff --git a/tacker/tests/unit/sol_refactored/conductor/test_vnflcm_driver_v2.py b/tacker/tests/unit/sol_refactored/conductor/test_vnflcm_driver_v2.py index a27e6cc05..f4e4d6cff 100644 --- a/tacker/tests/unit/sol_refactored/conductor/test_vnflcm_driver_v2.py +++ b/tacker/tests/unit/sol_refactored/conductor/test_vnflcm_driver_v2.py @@ -15,6 +15,8 @@ import copy from datetime import datetime import os +import pickle +import subprocess from unittest import mock from kubernetes import client @@ -2264,7 +2266,7 @@ class TestVnfLcmDriverV2(base.BaseTestCase): isCancelPending=False, operationParams=req_inst) self.driver.process( - self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1, {}) # other operation inst = objects.VnfInstanceV2( @@ -2293,7 +2295,7 @@ class TestVnfLcmDriverV2(base.BaseTestCase): operationParams=objects.TerminateVnfRequest( terminationType='FORCEFUL')) self.driver.process( - self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1, {}) # no flavour_id inst = objects.VnfInstanceV2( @@ -2319,10 +2321,11 @@ class TestVnfLcmDriverV2(base.BaseTestCase): operationParams=objects.VnfInfoModificationRequest() ) self.driver.process( - self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1, {}) + @mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'scale_rollback') @mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'instantiate_rollback') - def test_rollback(self, mock_inst_rollback): + def test_rollback(self, mock_inst_rollback, mock_scale_rollback): # instantiate req_inst = objects.InstantiateVnfRequest.from_dict( _inst_req_example) @@ -2353,7 +2356,7 @@ class TestVnfLcmDriverV2(base.BaseTestCase): isCancelPending=False, operationParams=req_inst) self.driver.rollback( - self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1, {}) # no rollback lcmocc = objects.VnfLcmOpOccV2( @@ -2370,7 +2373,74 @@ class TestVnfLcmDriverV2(base.BaseTestCase): terminationType='FORCEFUL')) self.assertRaises( sol_ex.RollbackNotSupported, self.driver.rollback, - self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1, {}) + + # mgmt rollback + req_scale = objects.ScaleVnfRequest( + type='SCALE_OUT', aspectId='aspect_id', numberOfSteps=1) + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.SCALE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req_scale) + self.driver.rollback( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1, {}) + inst.instantiatedVnfInfo = objects.VnfInstanceV2_InstantiatedVnfInfo( + flavourId='flavour_id') + self.driver.rollback( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1, {}) + + @mock.patch.object(pickle, 'loads') + @mock.patch.object(vnfd_utils.Vnfd, 'remove_tmp_csar_dir') + @mock.patch.object(subprocess, 'run') + @mock.patch.object(os.path, 'join') + @mock.patch.object(vnfd_utils.Vnfd, 'make_tmp_csar_dir') + @mock.patch.object(vnfd_utils.Vnfd, 'get_interface_script') + def test_exec_mgmt_driver_script_eof_error( + self, mock_script, mock_dir, mock_script_path, mock_out, + mock_remove, mock_output): + # instantiate + req_inst = objects.InstantiateVnfRequest.from_dict( + _inst_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='NOT_INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.INSTANTIATE + ) + grant = objects.GrantV1() + + mock_script.return_value = 'sample_script.py' + mock_dir.return_value = 'path' + mock_script_path.return_value = '../mgmt_drivers/sample_script.py' + mock_output.return_value = { + 'vnf_instance': { + 'id': 'id', 'vnfdId': 'vnfdId', 'vnfProvider': 'vnfProvider', + 'vnfProductName': 'vnfProductName', + 'vnfSoftwareVersion': 'vnfSoftwareVersion', + 'vnfdVersion': 'vnfdVersion', + 'instantiationState': 'INSTANTIATED'}, + 'user_script_err_handling_data': {'key': 'value'}} + user_script_err_handling_data = {} + self.assertRaises( + sol_ex.MgmtDriverExecutionFailed, + self.driver._exec_mgmt_driver_script, 'INSTANTIATE', 'flavour_id', + req_inst, inst, grant_req, grant, self.vnfd_1, + user_script_err_handling_data) def test_instantiate_grant_error(self): # instantiate diff --git a/tacker/tests/unit/sol_refactored/mgmt_drivers/fakes.py b/tacker/tests/unit/sol_refactored/mgmt_drivers/fakes.py index 6c5097b48..bea313b50 100644 --- a/tacker/tests/unit/sol_refactored/mgmt_drivers/fakes.py +++ b/tacker/tests/unit/sol_refactored/mgmt_drivers/fakes.py @@ -216,3 +216,103 @@ def fake_replica_set( name, image, conf_key, conf_name, sec_key, sec_name) ) ) + + +fault_notif_inst_example = { + "id": "c80f7afa-65f3-4be6-94ae-fdf438ac2d61", + "vnfInstanceName": "FaultNotification", + "vnfdId": "16ca1a07-2453-47f1-9f00-7ca2dce0a5ea", + "vnfProvider": "Company", + "vnfProductName": "Sample VNF", + "vnfSoftwareVersion": "1.0", + "vnfdVersion": "1.0", + "vimConnectionInfo": { + "vim1": { + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", + "vimId": 'vim id', + "interfaceInfo": {"endpoint": "http://localhost/identity/v3"}, + "accessInfo": { + "username": "nfv_user", + "region": "RegionOne", + "password": "devstack", + "project": "nfv", + "projectDomain": "Default", + "userDomain": "Default" + } + } + }, + "instantiationState": "INSTANTIATED", + "instantiatedVnfInfo": { + "flavourId": "simple", + "vnfState": "STARTED", + "vnfcResourceInfo": [{ + "id": "daemonset-vdu5-wh824", + "vduId": "VDU5", + "computeResource": { + "resourceId": "s0", + "vimLevelResourceType": "DaemonSet" + }, + "metadata": { + "server_notification": { + "alarmId": "xxx" + } + } + }, { + "id": "deployment2-vdu6-6f8c5c5ddb-9ptn9", + "vduId": "VDU6", + "computeResource": { + "resourceId": "s1", + "vimLevelResourceType": "Deployment" + } + }, { + "id": "env-test", + "vduId": "VDU3", + "computeResource": { + "resourceId": "s2", + "vimLevelResourceType": "Pod" + }, + "metadata": { + "server_notification": { + "alarmId": "a2" + } + } + }, { + "id": "env-test2", + "vduId": "VDU7", + "computeResource": { + "resourceId": "env-test2", + "vimLevelResourceType": "Pod" + }, + "metadata": {} + }], + "metadata": { + "ServerNotifierUri": "http://192.168.10.147:55555", + "ServerNotifierFaultID": ["1234"] + } + } +} + + +fault_notif_inst_req_example = { + "flavourId": "simple", + "instantiationLevelId": "instantiation_level_2", + "additionalParams": { + "ServerNotifierUri": "http://server_notifier:1234", + "ServerNotifierFaultID": ["1234"] + }, + "vimConnectionInfo": { + "vim1": { + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", + "vimId": 'vim id', + "interfaceInfo": {"endpoint": "http://localhost/identity/v3"}, + "accessInfo": { + "username": "nfv_user", + "region": "RegionOne", + "password": "devstack", + "project": "nfv", + "projectDomain": "Default", + "userDomain": "Default" + } + } + } +} diff --git a/tacker/tests/unit/sol_refactored/mgmt_drivers/test_server_notification.py b/tacker/tests/unit/sol_refactored/mgmt_drivers/test_server_notification.py new file mode 100644 index 000000000..e7e28ad22 --- /dev/null +++ b/tacker/tests/unit/sol_refactored/mgmt_drivers/test_server_notification.py @@ -0,0 +1,596 @@ +# Copyright (C) 2023 Fujitsu +# All Rights Reserved. +# +# 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. +import copy +import pickle +import sys +import webob + +from tacker.common import exceptions +from tacker.common import rpc +from tacker.sol_refactored.common import http_client +from tacker.sol_refactored.common import vnfd_utils +from tacker.sol_refactored.conductor import conductor_rpc_v2 +from tacker.sol_refactored.mgmt_drivers import ( + server_notification as mgmt_driver) +from tacker.sol_refactored import objects +from tacker.tests.unit import base +from tacker.tests.unit.sol_refactored.mgmt_drivers import fakes +from unittest import mock + + +SAMPLE_VNFD_ID = "16ca1a07-2453-47f1-9f00-7ca2dce0a5ea" + + +class TestServerNotification(base.TestCase): + + def setUp(self): + super(TestServerNotification, self).setUp() + objects.register_all() + self.vnfd = vnfd_utils.Vnfd(SAMPLE_VNFD_ID) + self.inst = copy.deepcopy(fakes.fault_notif_inst_example) + self.csar_dir = None + + @mock.patch.object(http_client.HttpClient, 'do_request') + @mock.patch.object(rpc, 'init') + @mock.patch.object(mgmt_driver, 'CONF') + @mock.patch.object(sys.stdout.buffer, 'write') + @mock.patch.object(pickle, 'load') + def test_instantiate_rollback_main( + self, mock_script, mock_write, mock_conf, mock_init, mock_resp): + user_script_err_handling_data = { + 'serverNotification': [ + {'serverId': 's0', 'alarmId': 'a0'}]} + req = objects.InstantiateVnfRequest.from_dict( + fakes.fault_notif_inst_req_example) + mock_script.return_value = { + "operation": "instantiate_rollback_start", + "request": req, + "vnf_instance": self.inst, + "grant_request": None, + "grant_response": None, + "tmp_csar_dir": self.csar_dir, + "user_script_err_handling_data": user_script_err_handling_data + } + resp = webob.Response() + resp.status_code = 202 + mock_resp.return_value = resp, {} + mgmt_driver.main() + err_handling_data_expected = { + 'serverNotification': [{'serverId': 's0'}]} + self.assertEqual( + err_handling_data_expected, user_script_err_handling_data) + + @mock.patch.object(http_client.HttpClient, 'do_request') + @mock.patch.object(rpc, 'init') + @mock.patch.object(mgmt_driver, 'CONF') + def test_instantiate_end( + self, mock_conf, mock_init, mock_resp): + user_script_err_handling_data = { + 'serverNotification': [ + {'serverId': 's0', 'alarmId': 'a0'}]} + resp = webob.Response() + resp.status_code = 202 + mock_resp.return_value = resp, {'alarm_id': 'alarm_id'} + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][2] + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][1] + req = objects.InstantiateVnfRequest.from_dict( + fakes.fault_notif_inst_req_example) + server_notification = mgmt_driver.ServerNotificationMgmtDriver( + req, self.inst, None, None, self.csar_dir, + user_script_err_handling_data) + server_notification.instantiate_end() + err_handling_data_expected = { + 'serverNotification': [ + {'serverId': 's0', 'alarmId': 'a0'}, + {'serverId': 'env-test2', 'alarmId': 'alarm_id'}]} + self.assertEqual( + err_handling_data_expected, user_script_err_handling_data) + self.assertEqual( + {'alarmId': 'a0'}, self.inst['instantiatedVnfInfo'][ + 'vnfcResourceInfo'][0]['metadata']['server_notification']) + self.assertEqual( + {'alarmId': 'alarm_id'}, + self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][1][ + 'metadata']['server_notification']) + + @mock.patch.object(http_client.HttpClient, 'do_request') + @mock.patch.object(rpc, 'init') + @mock.patch.object(mgmt_driver, 'CONF') + def test_merge_recent_register( + self, mock_conf, mock_init, mock_resp): + user_script_err_handling_data = { + 'serverNotification': [ + {'serverId': 's0', 'alarmId': 'a0'}, + {'serverId': 's1', 'alarmId': 'a1'}, + {'serverId': 's2'}, {'serverId': 's3', 'alarmId': 'a3'}]} + resp = webob.Response() + resp.status_code = 202 + mock_resp.return_value = resp, {'alarm_id': 'alarm_id'} + req = objects.InstantiateVnfRequest.from_dict( + fakes.fault_notif_inst_req_example) + server_notification = mgmt_driver.ServerNotificationMgmtDriver( + req, self.inst, None, None, self.csar_dir, + user_script_err_handling_data) + server_notification._merge_recent_register() + err_handling_data_expected = { + 'serverNotification': [ + {'serverId': 's0', 'alarmId': 'a0'}, + {'serverId': 's1', 'alarmId': 'a1'}, + {'serverId': 's2'}]} + self.assertEqual( + err_handling_data_expected, user_script_err_handling_data) + self.assertEqual( + {'alarmId': 'a0'}, self.inst['instantiatedVnfInfo'][ + 'vnfcResourceInfo'][0]['metadata']['server_notification']) + self.assertEqual( + {'alarmId': 'a1'}, self.inst['instantiatedVnfInfo'][ + 'vnfcResourceInfo'][1]['metadata']['server_notification']) + self.assertEqual( + None, self.inst['instantiatedVnfInfo'][ + 'vnfcResourceInfo'][2]['metadata'].get('server_notification')) + + @mock.patch.object(http_client.HttpClient, 'do_request') + @mock.patch.object(rpc, 'init') + @mock.patch.object(mgmt_driver, 'CONF') + def test_heal_start( + self, mock_conf, mock_init, mock_resp): + user_script_err_handling_data = { + 'serverNotification': [ + {'serverId': 's0', 'alarmId': 'a0'}]} + resp = webob.Response() + resp.status_code = 202 + mock_resp.return_value = resp, {'alarm_id': 'alarm_id'} + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][3] + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][2] + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][1] + req = objects.HealVnfRequest(additionalParams={"all": True}) + grant_req = objects.GrantRequestV1( + removeResources=[objects.ResourceDefinitionV1( + type='COMPUTE', resourceTemplateId='VDU1', + resource=objects.ResourceHandle( + resourceId="s0"))]) + grant = objects.GrantV1() + server_notification = mgmt_driver.ServerNotificationMgmtDriver( + req, self.inst, grant_req, grant, self.csar_dir, + user_script_err_handling_data) + server_notification.heal_start() + err_handling_data_expected = { + 'serverNotification': [ + {'serverId': 's0'}]} + self.assertEqual( + err_handling_data_expected, user_script_err_handling_data) + self.assertEqual( + None, self.inst['instantiatedVnfInfo'][ + 'vnfcResourceInfo'][0]['metadata'].get('server_notification')) + + @mock.patch.object(http_client.HttpClient, 'do_request') + @mock.patch.object(rpc, 'init') + @mock.patch.object(mgmt_driver, 'CONF') + def test_heal_end( + self, mock_conf, mock_init, mock_resp): + user_script_err_handling_data = { + 'serverNotification': [ + {'serverId': 's1', 'alarmId': 'a1'}]} + resp = webob.Response() + resp.status_code = 202 + mock_resp.return_value = resp, {'alarm_id': 'alarm_id'} + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][3] + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][2] + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][0] + req = objects.HealVnfRequest(additionalParams={"all": True}) + grant_req = objects.GrantRequestV1( + removeResources=[objects.ResourceDefinitionV1( + type='COMPUTE', resourceTemplateId='VDU1', + resource=objects.ResourceHandle( + resourceId="s1"))]) + grant = objects.GrantV1() + server_notification = mgmt_driver.ServerNotificationMgmtDriver( + req, self.inst, grant_req, grant, self.csar_dir, + user_script_err_handling_data) + server_notification.heal_end() + err_handling_data_expected = { + 'serverNotification': [ + {'serverId': 's1', 'alarmId': 'alarm_id'}]} + self.assertEqual( + err_handling_data_expected, user_script_err_handling_data) + self.assertEqual( + {'alarmId': 'alarm_id'}, self.inst['instantiatedVnfInfo'][ + 'vnfcResourceInfo'][0]['metadata'].get('server_notification')) + + @mock.patch.object(http_client.HttpClient, 'do_request') + @mock.patch.object(rpc, 'init') + @mock.patch.object(mgmt_driver, 'CONF') + def test_scale_start( + self, mock_conf, mock_init, mock_resp): + user_script_err_handling_data = { + 'serverNotification': [ + {'serverId': 's0', 'alarmId': 'a0'}]} + resp = webob.Response() + resp.status_code = 202 + mock_resp.return_value = resp, {'alarm_id': 'alarm_id'} + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][3] + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][2] + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][1] + req = objects.ScaleVnfRequest( + type='SCALE_IN', aspectId='aspect_id', numberOfSteps=1) + grant_req = objects.GrantRequestV1( + removeResources=[objects.ResourceDefinitionV1( + type='COMPUTE', resourceTemplateId='VDU1', + resource=objects.ResourceHandle( + resourceId="s0"))]) + grant = objects.GrantV1() + server_notification = mgmt_driver.ServerNotificationMgmtDriver( + req, self.inst, grant_req, grant, self.csar_dir, + user_script_err_handling_data) + server_notification.scale_start() + err_handling_data_expected = { + 'serverNotification': [ + {'serverId': 's0'}]} + self.assertEqual( + err_handling_data_expected, user_script_err_handling_data) + self.assertEqual( + None, self.inst['instantiatedVnfInfo'][ + 'vnfcResourceInfo'][0]['metadata'].get('server_notification')) + + @mock.patch.object(http_client.HttpClient, 'do_request') + @mock.patch.object(rpc, 'init') + @mock.patch.object(mgmt_driver, 'CONF') + def test_scale_end( + self, mock_conf, mock_init, mock_resp): + user_script_err_handling_data = { + 'serverNotification': [ + {'serverId': 's0', 'alarmId': 'a0'}]} + resp = webob.Response() + resp.status_code = 202 + mock_resp.return_value = resp, {'alarm_id': 'alarm_id'} + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][3] + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][2] + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][1] + req = objects.ScaleVnfRequest( + type='SCALE_OUT', aspectId='aspect_id', numberOfSteps=1) + grant_req = objects.GrantRequestV1( + removeResources=[objects.ResourceDefinitionV1( + type='COMPUTE', resourceTemplateId='VDU1', + resource=objects.ResourceHandle( + resourceId="s0"))]) + grant = objects.GrantV1() + server_notification = mgmt_driver.ServerNotificationMgmtDriver( + req, self.inst, grant_req, grant, self.csar_dir, + user_script_err_handling_data) + server_notification.scale_end() + err_handling_data_expected = { + 'serverNotification': [ + {'serverId': 's0', 'alarmId': 'a0'}]} + self.assertEqual( + err_handling_data_expected, user_script_err_handling_data) + self.assertEqual( + {'alarmId': 'a0'}, self.inst['instantiatedVnfInfo'][ + 'vnfcResourceInfo'][0]['metadata'].get('server_notification')) + + @mock.patch.object(conductor_rpc_v2.VnfLcmRpcApiV2, + 'server_notification_remove_timer') + @mock.patch.object(http_client.HttpClient, 'do_request') + @mock.patch.object(rpc, 'init') + @mock.patch.object(mgmt_driver, 'CONF') + def test_terminate_start( + self, mock_conf, mock_init, mock_resp, mock_timer): + user_script_err_handling_data = { + 'serverNotification': [ + {'serverId': 's0', 'alarmId': 'a0'}]} + resp = webob.Response() + resp.status_code = 202 + mock_resp.return_value = resp, {'alarm_id': 'alarm_id'} + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][3] + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][2] + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][1] + req = objects.ScaleVnfRequest( + type='SCALE_OUT', aspectId='aspect_id', numberOfSteps=1) + grant_req = objects.GrantRequestV1( + removeResources=[objects.ResourceDefinitionV1( + type='COMPUTE', resourceTemplateId='VDU1', + resource=objects.ResourceHandle( + resourceId="s0"))]) + grant = objects.GrantV1() + server_notification = mgmt_driver.ServerNotificationMgmtDriver( + req, self.inst, grant_req, grant, self.csar_dir, + user_script_err_handling_data) + server_notification.terminate_start() + err_handling_data_expected = { + 'serverNotification': [ + {'serverId': 's0'}]} + self.assertEqual( + err_handling_data_expected, user_script_err_handling_data) + self.assertEqual( + None, self.inst['instantiatedVnfInfo'][ + 'vnfcResourceInfo'][0]['metadata'].get('server_notification')) + + @mock.patch.object(http_client.HttpClient, 'do_request') + @mock.patch.object(rpc, 'init') + @mock.patch.object(mgmt_driver, 'CONF') + def test_scale_rollback_start( + self, mock_conf, mock_init, mock_resp): + user_script_err_handling_data = { + 'serverNotification': [ + {'serverId': 's0', 'alarmId': 'a0'}]} + resp = webob.Response() + resp.status_code = 202 + mock_resp.return_value = resp, {'alarm_id': 'alarm_id'} + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][3] + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][2] + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][1] + req = objects.ScaleVnfRequest( + type='SCALE_OUT', aspectId='aspect_id', numberOfSteps=1) + grant_req = objects.GrantRequestV1( + removeResources=[objects.ResourceDefinitionV1( + type='COMPUTE', resourceTemplateId='VDU1', + resource=objects.ResourceHandle( + resourceId="s0"))]) + grant = objects.GrantV1() + server_notification = mgmt_driver.ServerNotificationMgmtDriver( + req, self.inst, grant_req, grant, self.csar_dir, + user_script_err_handling_data) + server_notification.scale_rollback_start() + err_handling_data_expected = { + 'serverNotification': [ + {'serverId': 's0'}]} + self.assertEqual( + err_handling_data_expected, user_script_err_handling_data) + self.assertEqual( + {'alarmId': 'xxx'}, self.inst['instantiatedVnfInfo'][ + 'vnfcResourceInfo'][0]['metadata'].get('server_notification')) + + @mock.patch.object(sys.stdout.buffer, 'write') + @mock.patch.object(http_client.HttpClient, 'do_request') + @mock.patch.object(rpc, 'init') + @mock.patch.object(mgmt_driver, 'CONF') + @mock.patch.object(pickle, 'load') + def test_heal_end_main_error( + self, mock_script, mock_conf, mock_init, mock_resp, mock_write): + user_script_err_handling_data = None + resp = webob.Response() + resp.status_code = 400 + mock_resp.return_value = resp, None + req = objects.HealVnfRequest(additionalParams={"all": True}) + grant_req = objects.GrantRequestV1( + removeResources=[objects.ResourceDefinitionV1( + type='COMPUTE', resourceTemplateId='VDU1', + resource=objects.ResourceHandle( + resourceId="s1"))]) + grant = objects.GrantV1() + mock_script.return_value = { + "operation": "heal_end", + "request": req, + "vnf_instance": self.inst, + "grant_request": grant_req, + "grant_response": grant, + "tmp_csar_dir": self.csar_dir, + "user_script_err_handling_data": user_script_err_handling_data + } + self.assertRaises( + exceptions.MgmtDriverOtherError, mgmt_driver.main) + + @mock.patch.object(sys.stdout.buffer, 'write') + @mock.patch.object(rpc, 'init') + @mock.patch.object(mgmt_driver, 'CONF') + @mock.patch.object(pickle, 'load') + def test_instantiate_start( + self, mock_script, mock_conf, mock_init, mock_write): + user_script_err_handling_data = None + mock_script.return_value = { + "operation": "instantiate_start", + "request": None, + "vnf_instance": self.inst, + "grant_request": None, + "grant_response": None, + "tmp_csar_dir": self.csar_dir, + "user_script_err_handling_data": user_script_err_handling_data + } + mgmt_driver.main() + self.assertEqual(None, user_script_err_handling_data) + + @mock.patch.object(sys.stdout.buffer, 'write') + @mock.patch.object(rpc, 'init') + @mock.patch.object(mgmt_driver, 'CONF') + @mock.patch.object(pickle, 'load') + def test_terminate_end( + self, mock_script, mock_conf, mock_init, mock_write): + user_script_err_handling_data = None + mock_script.return_value = { + "operation": "terminate_end", + "request": None, + "vnf_instance": self.inst, + "grant_request": None, + "grant_response": None, + "tmp_csar_dir": self.csar_dir, + "user_script_err_handling_data": user_script_err_handling_data + } + mgmt_driver.main() + self.assertEqual(None, user_script_err_handling_data) + + @mock.patch.object(sys.stdout.buffer, 'write') + @mock.patch.object(rpc, 'init') + @mock.patch.object(mgmt_driver, 'CONF') + @mock.patch.object(pickle, 'load') + def test_scale_end_type( + self, mock_script, mock_conf, mock_init, mock_write): + user_script_err_handling_data = None + req = objects.ScaleVnfRequest( + type='SCALE_IN', aspectId='aspect_id', numberOfSteps=1) + mock_script.return_value = { + "operation": "scale_end", + "request": req, + "vnf_instance": self.inst, + "grant_request": None, + "grant_response": None, + "tmp_csar_dir": self.csar_dir, + "user_script_err_handling_data": user_script_err_handling_data + } + mgmt_driver.main() + self.assertEqual(None, user_script_err_handling_data) + + @mock.patch.object(sys.stdout.buffer, 'write') + @mock.patch.object(rpc, 'init') + @mock.patch.object(mgmt_driver, 'CONF') + @mock.patch.object(pickle, 'load') + def test_scale_start_type( + self, mock_script, mock_conf, mock_init, mock_write): + user_script_err_handling_data = None + req = objects.ScaleVnfRequest( + type='SCALE_OUT', aspectId='aspect_id', numberOfSteps=1) + mock_script.return_value = { + "operation": "scale_start", + "request": req, + "vnf_instance": self.inst, + "grant_request": None, + "grant_response": None, + "tmp_csar_dir": self.csar_dir, + "user_script_err_handling_data": user_script_err_handling_data + } + mgmt_driver.main() + self.assertEqual(None, user_script_err_handling_data) + + @mock.patch.object(http_client.HttpClient, 'do_request') + @mock.patch.object(rpc, 'init') + @mock.patch.object(mgmt_driver, 'CONF') + def test_instantiate_end_no_metadata( + self, mock_conf, mock_init, mock_resp): + user_script_err_handling_data = { + 'serverNotification': [ + {'serverId': 's0', 'alarmId': 'a0'}]} + resp = webob.Response() + resp.status_code = 202 + mock_resp.return_value = resp, {'alarm_id': 'alarm_id'} + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][2] + del self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][1] + del self.inst['instantiatedVnfInfo']['metadata'] + req = objects.InstantiateVnfRequest.from_dict( + fakes.fault_notif_inst_req_example) + server_notification = mgmt_driver.ServerNotificationMgmtDriver( + req, self.inst, None, None, self.csar_dir, + user_script_err_handling_data) + server_notification.instantiate_end() + err_handling_data_expected = { + 'serverNotification': [ + {'serverId': 's0', 'alarmId': 'a0'}, + {'serverId': 'env-test2', 'alarmId': 'alarm_id'}]} + self.assertEqual( + err_handling_data_expected, user_script_err_handling_data) + self.assertEqual( + {'alarmId': 'a0'}, self.inst['instantiatedVnfInfo'][ + 'vnfcResourceInfo'][0]['metadata']['server_notification']) + self.assertEqual( + {'alarmId': 'alarm_id'}, + self.inst['instantiatedVnfInfo']['vnfcResourceInfo'][1][ + 'metadata']['server_notification']) + + @mock.patch.object(rpc, 'init') + @mock.patch.object(mgmt_driver, 'CONF') + def test_get_params_none(self, mock_conf, mock_init): + user_script_err_handling_data = { + 'serverNotification': [ + {'serverId': 's0', 'alarmId': 'a0'}, + {'serverId': 's1', 'alarmId': 'a1'}, + {'serverId': 's2'}, {'serverId': 's3', 'alarmId': 'a3'}]} + req = objects.InstantiateVnfRequest.from_dict( + fakes.fault_notif_inst_req_example) + server_notification = mgmt_driver.ServerNotificationMgmtDriver( + req, self.inst, None, None, self.csar_dir, + user_script_err_handling_data) + + del self.inst['vimConnectionInfo'] + result1 = server_notification.get_params() + self.assertEqual((None, None, None), result1) + + del self.inst['instantiatedVnfInfo'] + result2 = server_notification.get_params() + self.assertEqual((None, None, None), result2) + + @mock.patch.object(http_client.HttpClient, 'do_request') + @mock.patch.object(rpc, 'init') + @mock.patch.object(mgmt_driver, 'CONF') + def test_request_register_no_server_notification( + self, mock_conf, mock_init, mock_resp): + vnfc_resource = {'computeResource': {'resourceId': 's0'}} + user_script_err_handling_data = {} + req = objects.InstantiateVnfRequest.from_dict( + fakes.fault_notif_inst_req_example) + resp = webob.Response() + resp.status_code = 202 + mock_resp.return_value = resp, {'alarm_id': 'alarm_id'} + server_notification = mgmt_driver.ServerNotificationMgmtDriver( + req, self.inst, None, None, self.csar_dir, + user_script_err_handling_data) + server_notification._request_register(vnfc_resource) + err_handling_data_expected = { + 'serverNotification': [ + {'serverId': 's0', 'alarmId': 'alarm_id'}]} + self.assertEqual( + err_handling_data_expected, user_script_err_handling_data) + + @mock.patch.object(mgmt_driver.ServerNotificationMgmtDriver, + '_request_unregister') + @mock.patch.object(mgmt_driver.ServerNotificationMgmtDriver, 'get_params') + @mock.patch.object(rpc, 'init') + @mock.patch.object(mgmt_driver, 'CONF') + def test_request_register_cancel( + self, mock_conf, mock_init, mock_params, mock_unregister): + rsc_list = [{ + 'computeResource': {'resourceId': 's0'}, + 'metadata': {'server_notification': {'alarmId': 'a0'}}}] + mock_params.return_value = (None, None, None) + user_script_err_handling_data = {} + req = objects.InstantiateVnfRequest.from_dict( + fakes.fault_notif_inst_req_example) + server_notification = mgmt_driver.ServerNotificationMgmtDriver( + req, self.inst, None, None, self.csar_dir, + user_script_err_handling_data) + server_notification.request_register_cancel(rsc_list) + self.assertEqual(1, mock_unregister.call_count) + + @mock.patch.object(http_client.HttpClient, 'do_request') + @mock.patch.object(rpc, 'init') + @mock.patch.object(mgmt_driver, 'CONF') + def test_request_unregister_400( + self, mock_conf, mock_init, mock_resp): + user_script_err_handling_data = {} + req = objects.InstantiateVnfRequest.from_dict( + fakes.fault_notif_inst_req_example) + resp = webob.Response() + resp.status_code = 400 + mock_resp.return_value = resp, {'alarm_id': 'alarm_id'} + server_notification = mgmt_driver.ServerNotificationMgmtDriver( + req, self.inst, None, None, self.csar_dir, + user_script_err_handling_data) + server_notification._request_unregister('', '', 's0', '') + self.assertEqual(1, mock_resp.call_count) + + @mock.patch.object(http_client.HttpClient, 'do_request') + @mock.patch.object(rpc, 'init') + @mock.patch.object(mgmt_driver, 'CONF') + def test_request_unregister_no_server_notification( + self, mock_conf, mock_init, mock_resp): + user_script_err_handling_data = {} + req = objects.InstantiateVnfRequest.from_dict( + fakes.fault_notif_inst_req_example) + resp = webob.Response() + resp.status_code = 200 + mock_resp.return_value = resp, {'alarm_id': 'alarm_id'} + server_notification = mgmt_driver.ServerNotificationMgmtDriver( + req, self.inst, None, None, self.csar_dir, + user_script_err_handling_data) + server_notification._request_unregister('', '', 's0', '') + self.assertEqual( + {'serverNotification': [{'serverId': 's0'}]}, + user_script_err_handling_data)