From 4543b961eb35a81c5cf3e65464bfa482364bd31c Mon Sep 17 00:00:00 2001 From: Aldinson Esto Date: Thu, 27 Aug 2020 21:00:12 +0900 Subject: [PATCH] Support Rollback of LCM Resource Supported rollback LCM operation Implements: blueprint support-etsi-nfv-specs Spec: https://specs.openstack.org/openstack/tacker-specs/specs/victoria/support-etsi-nfv-based-errorhandling.html Change-Id: Ic5701c6c1379ee6e08515bf3086da39d75160f82 --- api-ref/source/v1/vnflcm.inc | 33 + tacker/api/vnflcm/v1/controller.py | 84 ++ tacker/api/vnflcm/v1/router.py | 7 + tacker/conductor/conductor_server.py | 15 +- tacker/conductor/conductorrpc/vnf_lcm_rpc.py | 13 + tacker/db/vnfm/vnfm_db.py | 84 ++ tacker/policies/vnf_lcm.py | 11 + tacker/tests/unit/vnflcm/fakes.py | 246 ++++++ tacker/tests/unit/vnflcm/test_controller.py | 222 ++++- .../tests/unit/vnflcm/test_vnflcm_driver.py | 782 +++++++++++++++++- .../openstack/test_openstack_driver.py | 95 +++ tacker/tests/unit/vnfm/test_plugin.py | 51 ++ tacker/vnflcm/vnflcm_driver.py | 538 ++++++++++-- .../kubernetes/kubernetes_driver.py | 9 + .../vnfm/infra_drivers/openstack/openstack.py | 66 +- tacker/vnfm/infra_drivers/scale_driver.py | 10 + 16 files changed, 2210 insertions(+), 56 deletions(-) diff --git a/api-ref/source/v1/vnflcm.inc b/api-ref/source/v1/vnflcm.inc index ab8b5fc26..47a7d1211 100644 --- a/api-ref/source/v1/vnflcm.inc +++ b/api-ref/source/v1/vnflcm.inc @@ -777,6 +777,39 @@ Response Example .. literalinclude:: samples/vnflcm/show-vnflcm-operation-occurrence-response.json :language: javascript +Roll back a VNF lifecycle operation +=================================== + +.. rest_method:: POST /vnflcm/v1/vnf_lcm_op_occs/{vnfLcmOpOccId}/rollback + +The POST method initiates rolling back a VNF lifecycle operation if that operation has experienced a temporary failure, +i.e. the related "Individual VNF LCM operation occurrence" resource is in "FAILED_TEMP" state. + +In case of success of processing the asynchronous request, the "operationState" attribute in the representation of the +parent resource shall be changed to "ROLLING_BACK" and the applicable "start" notification +shall be emitted to indicate that rollback of the underlying VNF LCM operation occurrence is attempted. + +Response Codes +-------------- + +.. rest_status_code:: success status.yaml + + - 202 + +.. rest_status_code:: error status.yaml + + - 401 + - 403 + - 404 + - 409 + +Request Parameters +------------------ + +.. rest_parameters:: parameters_vnflcm.yaml + + - vnfLcmOpOccId: vnf_lcm_op_occ_id + Create a new subscription ========================= diff --git a/tacker/api/vnflcm/v1/controller.py b/tacker/api/vnflcm/v1/controller.py index 915a610ed..c4d49fb1d 100644 --- a/tacker/api/vnflcm/v1/controller.py +++ b/tacker/api/vnflcm/v1/controller.py @@ -1116,6 +1116,90 @@ class VnfLcmController(wsgi.Controller): return self._make_problem_detail( str(e), 500, title='Internal Server Error') + def _rollback( + self, + context, + vnf_info, + vnf_instance, + vnf_lcm_op_occs, + operation_params): + + self.rpc_api.rollback( + context, + vnf_info, + vnf_instance, + operation_params) + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs + + vnflcm_url = CONF.vnf_lcm.endpoint_url + \ + "/vnflcm/v1/vnf_lcm_op_occs/" + vnf_lcm_op_occs.id + res = webob.Response() + res.status_int = 202 + location = ('Location', vnflcm_url) + res.headerlist.append(location) + return res + + def _get_rollback_vnf(self, context, vnf_instance_id): + return self._vnfm_plugin.get_vnf(context, vnf_instance_id) + + @wsgi.response(http_client.ACCEPTED) + @wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN, + http_client.NOT_FOUND, http_client.CONFLICT)) + def rollback(self, request, id): + context = request.environ['tacker.context'] + context.can(vnf_lcm_policies.VNFLCM % 'rollback') + + try: + vnf_lcm_op_occs = objects.VnfLcmOpOcc.get_by_id(context, id) + if vnf_lcm_op_occs.operation_state != 'FAILED_TEMP': + return self._make_problem_detail( + 'OperationState IS NOT FAILED_TEMP', + 409, + title='OperationState IS NOT FAILED_TEMP') + + if vnf_lcm_op_occs.operation != 'INSTANTIATION' \ + and vnf_lcm_op_occs.operation != 'SCALE': + return self._make_problem_detail( + 'OPERATION IS NOT INSTANTIATION/SCALE', + 409, + title='OPERATION IS NOT INSTANTIATION/SCALE') + + operation_params = jsonutils.loads( + vnf_lcm_op_occs.operation_params) + + if vnf_lcm_op_occs.operation == 'SCALE' \ + and operation_params['type'] == 'SCALE_IN': + return self._make_problem_detail( + 'SCALE_IN CAN NOT ROLLBACK', 409, + title='SCALE_IN CAN NOT ROLLBACK') + + vnf_info = self._get_rollback_vnf( + context, vnf_lcm_op_occs.vnf_instance_id) + vnf_instance = self._get_vnf_instance( + context, vnf_lcm_op_occs.vnf_instance_id) + + vnf_lcm_op_occs.changed_info = None + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs + return self._rollback( + context, + vnf_info, + vnf_instance, + vnf_lcm_op_occs, + operation_params) + except vnfm.VNFNotFound as vnf_e: + return self._make_problem_detail( + str(vnf_e), 404, title='VNF NOT FOUND') + except exceptions.NotFound as occ_e: + return self._make_problem_detail( + str(occ_e), 404, title='VNF NOT FOUND') + except webob.exc.HTTPNotFound as inst_e: + return self._make_problem_detail( + str(inst_e), 404, title='VNF NOT FOUND') + except Exception as e: + LOG.error(traceback.format_exc()) + return self._make_problem_detail( + str(e), 500, title='Internal Server Error') + def _make_problem_detail( self, detail, diff --git a/tacker/api/vnflcm/v1/router.py b/tacker/api/vnflcm/v1/router.py index 2a9577e11..d2f2ed771 100644 --- a/tacker/api/vnflcm/v1/router.py +++ b/tacker/api/vnflcm/v1/router.py @@ -112,6 +112,13 @@ class VnflcmAPIRouter(wsgi.Router): "/vnf_instances/{id}/scale", methods, controller, default_resource) + # Allowed methods on + # {apiRoot}/vnflcm/v1/vnf_lcm_op_occs/{vnfLcmOpOccId}/rollback resource + methods = {"POST": "rollback"} + self._setup_route(mapper, + "/vnf_lcm_op_occs/{id}/rollback", + methods, controller, default_resource) + methods = {"GET": "subscription_list", "POST": "register_subscription"} self._setup_route(mapper, "/subscriptions", methods, controller, default_resource) diff --git a/tacker/conductor/conductor_server.py b/tacker/conductor/conductor_server.py index 70edf464a..de8d9f4de 100644 --- a/tacker/conductor/conductor_server.py +++ b/tacker/conductor/conductor_server.py @@ -1368,6 +1368,7 @@ class Conductor(manager.Manager): kwargs.get('is_automatic_invocation', False) error = kwargs.get('error', None) # Used for timing control when a failure occurs + error_point = kwargs.get('error_point', 0) if old_vnf_instance: vnf_instance_id = old_vnf_instance.id @@ -1381,6 +1382,7 @@ class Conductor(manager.Manager): vnf_notif = self._get_vnf_notify(context, vnf_lcm_op_occs_id) vnf_notif.operation_state = operation_state if operation_state == fields.LcmOccsOperationState.FAILED_TEMP: + vnf_notif.error_point = error_point error_details = objects.ProblemDetails( context=context, status=500, @@ -1543,6 +1545,8 @@ class Conductor(manager.Manager): instantiate_vnf, vnf_lcm_op_occs_id): + vnf_dict['error_point'] = 1 + self._instantiate_grant(context, vnf_instance, vnf_dict, @@ -1565,12 +1569,15 @@ class Conductor(manager.Manager): self._change_vnf_status(context, vnf_instance.id, _INACTIVE_STATUS, 'PENDING_CREATE') + vnf_dict['error_point'] = 3 self.vnflcm_driver.instantiate_vnf(context, vnf_instance, vnf_dict, instantiate_vnf) + vnf_dict['error_point'] = 5 self._build_instantiated_vnf_info(context, vnf_instance, instantiate_vnf_req=instantiate_vnf) + vnf_dict['error_point'] = 7 self._update_vnf_attributes(context, vnf_dict, _PENDING_STATUS, _ACTIVE_STATUS) self.vnflcm_driver._vnf_instance_update(context, vnf_instance, @@ -1604,7 +1611,8 @@ class Conductor(manager.Manager): vnf_instance=vnf_instance, request_obj=instantiate_vnf, operation_state=fields.LcmOccsOperationState.FAILED_TEMP, - error=str(ex) + error=str(ex), + error_point=vnf_dict['error_point'] ) @coordination.synchronized('{vnf_instance[id]}') @@ -1887,6 +1895,11 @@ class Conductor(manager.Manager): notification_data['changed_info'] = changed_info.to_dict() self.send_notification(context, notification_data) + @coordination.synchronized('{vnf_instance[id]}') + def rollback(self, context, vnf_info, vnf_instance, operation_params): + self.vnflcm_driver.rollback_vnf(context, vnf_info, + vnf_instance, operation_params) + def init(args, **kwargs): CONF(args=args, project='tacker', diff --git a/tacker/conductor/conductorrpc/vnf_lcm_rpc.py b/tacker/conductor/conductorrpc/vnf_lcm_rpc.py index 8d3f4cfcd..72e62664f 100644 --- a/tacker/conductor/conductorrpc/vnf_lcm_rpc.py +++ b/tacker/conductor/conductorrpc/vnf_lcm_rpc.py @@ -112,3 +112,16 @@ class VNFLcmRPCAPI(object): rpc_method = cctxt.cast if cast else cctxt.call return rpc_method(context, 'send_notification', notification=notification) + + def rollback(self, context, vnf_info, vnf_instance, + operation_params, cast=True): + serializer = objects_base.TackerObjectSerializer() + + client = rpc.get_client(self.target, version_cap=None, + serializer=serializer) + cctxt = client.prepare() + rpc_method = cctxt.cast if cast else cctxt.call + return rpc_method(context, 'rollback', + vnf_info=vnf_info, + vnf_instance=vnf_instance, + operation_params=operation_params) diff --git a/tacker/db/vnfm/vnfm_db.py b/tacker/db/vnfm/vnfm_db.py index 0ab8933df..8674cc7c7 100644 --- a/tacker/db/vnfm/vnfm_db.py +++ b/tacker/db/vnfm/vnfm_db.py @@ -865,3 +865,87 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin): update({ 'resource': placement_obj.resource, 'updated_at': timeutils.utcnow()})) + + def update_vnf_rollback_status_err(self, + context, + vnf_info): + vnf_lcm_op_occs = vnf_info['vnf_lcm_op_occ'] + if vnf_lcm_op_occs.operation == 'SCALE': + self._cos_db_plg.create_event( + context, res_id=vnf_info['id'], + res_type=constants.RES_TYPE_VNF, + res_state='ERROR', + evt_type=constants.RES_EVT_SCALE, + tstamp=timeutils.utcnow()) + else: + self._cos_db_plg.create_event( + context, res_id=vnf_info['id'], + res_type=constants.RES_TYPE_VNF, + res_state='ERROR', + evt_type=constants.RES_EVT_CREATE, + tstamp=timeutils.utcnow()) + + def _update_vnf_rollback_pre(self, + context, + vnf_info): + vnf_lcm_op_occs = vnf_info['vnf_lcm_op_occ'] + if vnf_lcm_op_occs.operation == 'SCALE': + self._cos_db_plg.create_event( + context, res_id=vnf_info['id'], + res_type=constants.RES_TYPE_VNF, + res_state='ROLL_BACK', + evt_type=constants.RES_EVT_SCALE, + tstamp=timeutils.utcnow()) + else: + self._cos_db_plg.create_event( + context, res_id=vnf_info['id'], + res_type=constants.RES_TYPE_VNF, + res_state='ROLL_BACK', + evt_type=constants.RES_EVT_CREATE, + tstamp=timeutils.utcnow()) + + def _update_vnf_rollback(self, + context, + vnf_info, + previous_statuses, + status, + vnf_instance=None, + vnf_lcm_op_occ=None): + with context.session.begin(subtransactions=True): + timestamp = timeutils.utcnow() + (self._model_query(context, VNF). + filter(VNF.id == vnf_info['id']). + filter(VNF.status == previous_statuses). + update({'status': status, + 'updated_at': timestamp})) + + dev_attrs = vnf_info.get('attributes', {}) + (context.session.query(VNFAttribute). + filter(VNFAttribute.vnf_id == vnf_info['id']). + filter(~VNFAttribute.key.in_(dev_attrs.keys())). + delete(synchronize_session='fetch')) + + vnf_lcm_op_occs = vnf_info['vnf_lcm_op_occ'] + if vnf_lcm_op_occs.operation == 'SCALE': + for (key, value) in dev_attrs.items(): + if 'vim_auth' not in key: + self._vnf_attribute_update_or_create( + context, vnf_info['id'], key, value) + self._cos_db_plg.create_event( + context, res_id=vnf_info['id'], + res_type=constants.RES_TYPE_VNF, + res_state=status, + evt_type=constants.RES_EVT_SCALE, + tstamp=timestamp) + else: + self._cos_db_plg.create_event( + context, res_id=vnf_info['id'], + res_type=constants.RES_TYPE_VNF, + res_state=status, + evt_type=constants.RES_EVT_CREATE, + tstamp=timestamp) + if vnf_lcm_op_occ: + vnf_lcm_op_occ.state_entered_time = timestamp + vnf_lcm_op_occ.save() + if vnf_instance: + vnf_instance.save() diff --git a/tacker/policies/vnf_lcm.py b/tacker/policies/vnf_lcm.py index 1b9b2d27b..ef2c7d22b 100644 --- a/tacker/policies/vnf_lcm.py +++ b/tacker/policies/vnf_lcm.py @@ -132,6 +132,17 @@ rules = [ } ] ), + policy.DocumentedRuleDefault( + name=VNFLCM % 'rollback', + check_str=base.RULE_ADMIN_OR_OWNER, + description="Rollback a VNF instance.", + operations=[ + { + 'method': 'POST', + 'path': '/vnflcm/v1/vnf_lcm_op_occs/{vnfLcmOpOccId}/rollback' + } + ] + ), ] diff --git a/tacker/tests/unit/vnflcm/fakes.py b/tacker/tests/unit/vnflcm/fakes.py index cda32fe6e..f1a81da43 100644 --- a/tacker/tests/unit/vnflcm/fakes.py +++ b/tacker/tests/unit/vnflcm/fakes.py @@ -836,6 +836,252 @@ def vnf_scale(): vim_id=uuidsentinel.vim_id) +def vnflcm_rollback(error_point=7): + return objects.VnfLcmOpOcc( + state_entered_time=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + start_time=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + vnf_instance_id=uuidsentinel.vnf_instance_id, + operation='SCALE', + operation_state='FAILED_TEMP', + is_automatic_invocation=False, + operation_params='{"type": "SCALE_OUT", "aspect_id": "SP1"}', + error_point=error_point, + id=constants.UUID, + created_at=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC)) + + +def vnflcm_rollback_insta(error_point=7): + return objects.VnfLcmOpOcc( + state_entered_time=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + start_time=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + vnf_instance_id=uuidsentinel.vnf_instance_id, + operation='INSTANTIATION', + operation_state='FAILED_TEMP', + is_automatic_invocation=False, + operation_params='{}', + error_point=error_point, + id=constants.UUID, + created_at=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC)) + + +def vnflcm_rollback_active(): + return objects.VnfLcmOpOcc( + state_entered_time=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + start_time=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + vnf_instance_id=uuidsentinel.vnf_instance_id, + operation='SCALE', + operation_state='ACTIVE', + is_automatic_invocation=False, + operation_params='{"type": "SCALE_OUT", "aspect_id": "SP1"}', + error_point=7, + id=constants.UUID, + created_at=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC)) + + +def vnflcm_rollback_ope(): + return objects.VnfLcmOpOcc( + state_entered_time=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + start_time=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + vnf_instance_id=uuidsentinel.vnf_instance_id, + operation='HEAL', + operation_state='FAILED_TEMP', + is_automatic_invocation=False, + operation_params='{}', + error_point=7, + id=constants.UUID, + created_at=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC)) + + +def vnflcm_rollback_scale_in(): + return objects.VnfLcmOpOcc( + state_entered_time=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + start_time=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC), + vnf_instance_id=uuidsentinel.vnf_instance_id, + operation='SCALE', + operation_state='FAILED_TEMP', + is_automatic_invocation=False, + operation_params='{"type": "SCALE_IN", "aspect_id": "SP1"}', + error_point=7, + id=constants.UUID, + created_at=datetime.datetime(2000, 1, 1, 1, 1, 1, + tzinfo=iso8601.UTC)) + + +def vnf_rollback(): + return tacker.db.vnfm.vnfm_db.VNF(id=constants.UUID, + vnfd_id=uuidsentinel.vnfd_id, + name='test', + status='ERROR', + vim_id=uuidsentinel.vim_id) + + +def vnf_dict(): + heat_temp = 'heat_template_version: ' + \ + '2013-05-23\ndescription: \'VNF Descriptor (TEST)' + \ + '\n\n \'\nparameters:\n current_num:\n type: ' + \ + 'number\n nfv:\n type: json\nresources:\n ' + \ + 'SP1_scale_out:\n type: OS::Heat::ScalingPolicy\n' + \ + ' properties:\n auto_scaling_group_id: ' + \ + '{get_resource: SP1_group}\n adjustment_type: ' + \ + 'change_in_capacity\n scaling_adjustment: 1\n ' + \ + 'SP1_group:\n type: OS::Heat::AutoScalingGroup\n ' + \ + 'properties:\n min_size: 0\n desired_capacity:' + \ + ' {get_param: current_num}\n resource:\n ' + \ + 'type: SP1_res.yaml\n properties:\n nfv:' + \ + ' {get_param: nfv}\n max_size: 3\n SP1_scale_in:\n' + \ + ' type: OS::Heat::ScalingPolicy\n properties:\n' + \ + ' auto_scaling_group_id: {get_resource: SP1_group}\n' + \ + ' adjustment_type: change_in_capacity\n ' + \ + 'scaling_adjustment: -1\noutputs: {}\n' + scale_g = '{\"scaleGroupDict\": { \"SP1\": { \"vdu\":' + \ + ' [\"VDU1\"], \"num\": 1, \"maxLevel\": 3, \"initialNum\":' + \ + ' 0, \"initialLevel\": 0, \"default\": 0 }}}' + vnfd = 'tosca_definitions_version: ' + \ + 'tosca_simple_yaml_1_2\n\ndescription: Simple deployment' + \ + ' flavour for Sample VNF\n\nimports:\n' + \ + ' - etsi_nfv_sol001_common_types.yaml\n' + \ + ' - etsi_nfv_sol001_vnfd_types.yaml\n\n' + \ + 'topology_template:\n node_templates:\n' + \ + ' VNF:\n type: nec.ossmano.VNF\n' + \ + ' properties:\n' + \ + ' flavour_description: A simple flavour\n' + \ + ' interfaces:\n Vnflcm:\n' + \ + ' scale_start: noop\n' + \ + ' scale: scale_standard\n' + \ + ' scale_end: noop\n artifacts:\n' + \ + ' hot:\n' + \ + ' type: tosca.artifacts.Implementation.nfv.Hot\n' + \ + ' file: ../Files/scale.yaml\n hot-nest:\n' + \ + ' type: tosca.artifacts.Implementation.nfv.Hot\n' + \ + ' file: ../Files/SP1_res.yaml\n' + \ + ' properties:\n nest: "True"\n\n' + \ + ' VDU1:\n type: tosca.nodes.nfv.Vdu.Compute\n' + \ + ' properties:\n name: VDU1\n' + \ + ' description: VDU1 compute node\n' + \ + ' vdu_profile:\n' + \ + ' min_number_of_instances: 1\n' + \ + ' max_number_of_instances: 3\n\n' + \ + ' capabilities:\n virtual_compute:\n' + \ + ' properties:\n virtual_memory:\n' + \ + ' virtual_mem_size: 512 MB\n' + \ + ' virtual_cpu:\n' + \ + ' num_virtual_cpu: 1\n' + \ + ' virtual_local_storage:\n' + \ + ' - size_of_storage: 1 GB\n' + \ + ' requirements:\n' + \ + ' - virtual_storage: VirtualStorage\n\n' + \ + ' VirtualStorage:\n' + \ + ' type: tosca.nodes.nfv.Vdu.VirtualBlockStorage\n' + \ + ' properties:\n virtual_block_storage_data:\n' + \ + ' size_of_storage: 1 GB\n' + \ + ' rdma_enabled: true\n sw_image_data:\n' + \ + ' name: VirtualStorage\n' + \ + ' version: \'0.4.0\'\n checksum:\n' + \ + ' algorithm: sha-512\n' + \ + ' hash: 6513f21e44aa3da349f248188a44bc304a3' + \ + '653a04122d8fb4535423c8e1d14cd6a153f735bb0982e2161b5b5' + \ + '186106570c17a9e58b64dd39390617cd5a350f78\n' + \ + ' container_format: bare\n' + \ + ' disk_format: qcow2\n' + \ + ' min_disk: 2 GB\n' + \ + ' min_ram: 256 MB\n' + \ + ' size: 1 GB\n\n CP1:\n' + \ + ' type: tosca.nodes.nfv.VduCp\n' + \ + ' properties:\n layer_protocols: [ ipv4 ]\n' + \ + ' order: 2\n requirements:\n' + \ + ' - virtual_binding: VDU1\n' + \ + ' - virtual_link: internalVL1\n\n' + \ + ' internalVL1:\n' + \ + ' type: tosca.nodes.nfv.VnfVirtualLink\n' + \ + ' properties:\n connectivity_type:\n' + \ + ' layer_protocols: [ ipv4 ]\n' + \ + ' description: Internal Virtual link in the VNF\n' + \ + ' vl_profile:\n' + \ + ' max_bitrate_requirements:\n' + \ + ' root: 1048576\n' + \ + ' leaf: 1048576\n' + \ + ' min_bitrate_requirements:\n' + \ + ' root: 1048576\n leaf: 1048576\n' + \ + ' virtual_link_protocol_data:\n' + \ + ' - associated_layer_protocol: ipv4\n' + \ + ' l3_protocol_data:\n' + \ + ' ip_version: ipv4\n' + \ + ' cidr: 33.33.0.0/24\n\n policies:\n' + \ + ' - scaling_aspects:\n' + \ + ' type: tosca.policies.nfv.ScalingAspects\n' + \ + ' properties:\n aspects:\n' + \ + ' SP1:\n name: SP1_aspect\n' + \ + ' description: SP1 scaling aspect\n' + \ + ' max_scale_level: 2\n' + \ + ' step_deltas:\n' + \ + ' - delta_1\n\n' + \ + ' - VDU1_initial_delta:\n' + \ + ' type: tosca.policies.nfv.VduInitialDelta\n' + \ + ' properties:\n initial_delta:\n' + \ + ' number_of_instances: 0\n' + \ + ' targets: [ VDU1 ]\n\n' + \ + ' - VDU1_scaling_aspect_deltas:\n' + \ + ' type: tosca.policies.nfv.VduScalingAspectDeltas\n' + \ + ' properties:\n aspect: SP1\n' + \ + ' deltas:\n delta_1:\n' + \ + ' number_of_instances: 1\n' + \ + ' targets: [ VDU1 ]\n\n' + \ + ' - instantiation_levels:\n' + \ + ' type: tosca.policies.nfv.InstantiationLevels\n' + \ + ' properties:\n levels:\n' + \ + ' instantiation_level_1:\n' + \ + ' description: Smallest size\n' + \ + ' scale_info:\n SP1:\n' + \ + ' scale_level: 0\n' + \ + ' instantiation_level_2:\n' + \ + ' description: Largest size\n' + \ + ' scale_info:\n SP1:\n' + \ + ' scale_level: 3\n' + \ + ' default_level: instantiation_level_1\n\n' + \ + ' - VDU1_instantiation_levels:\n' + \ + ' type: tosca.policies.nfv.VduInstantiationLevels\n' + \ + ' properties:\n levels:\n' + \ + ' instantiation_level_1:\n' + \ + ' number_of_instances: 0\n' + \ + ' instantiation_level_2:\n' + \ + ' number_of_instances: 3\n' + \ + ' targets: [ VDU1 ]\n' + vnf_dict = { + 'attributes': { + 'heat_template': heat_temp, + 'scale_group': scale_g + }, + 'status': 'ERROR', + 'vnfd_id': '576acf48-b9df-491d-a57c-342de660ec78', + 'tenant_id': '13d2ca8de70d48b2a2e0dbac2c327c0b', + 'vim_id': '3f41faa7-5630-47d2-9d4a-1216953c8887', + 'instance_id': 'd1121d3c-368b-4ac2-b39d-835aa3e4ccd8', + 'placement_attr': {'vim_name': 'openstack-vim'}, + 'id': 'a27fc58e-66ae-4031-bba4-efede318c60b', + 'name': 'vnf_create_1', + 'vnfd': { + 'attributes': { + 'vnfd_simple': vnfd + } + } + } + return vnf_dict + + class InjectContext(wsgi.Middleware): """Add a 'tacker.context' to WSGI environ.""" diff --git a/tacker/tests/unit/vnflcm/test_controller.py b/tacker/tests/unit/vnflcm/test_controller.py index 861f2f3f5..6e6ce1e42 100644 --- a/tacker/tests/unit/vnflcm/test_controller.py +++ b/tacker/tests/unit/vnflcm/test_controller.py @@ -2535,7 +2535,7 @@ class TestController(base.TestCase): @mock.patch.object(TackerManager, 'get_service_plugins', return_value={'VNFM': FakeVNFMPlugin()}) @mock.patch.object(objects.ScaleVnfRequest, "obj_from_primitive") - @mock.patch.object(tacker.db.vnfm.vnfm_db.VNFMPluginDb, "get_vnf") + @mock.patch.object(controller.VnfLcmController, "_get_rollback_vnf") @mock.patch.object(objects.VnfInstance, "get_by_id") @mock.patch.object(vnf_lcm_rpc.VNFLcmRPCAPI, "send_notification") @mock.patch.object(objects.VnfLcmOpOcc, "create") @@ -2594,3 +2594,223 @@ class TestController(base.TestCase): 'STARTING') self.assertEqual(mock_send_notification.call_args[0][1].get( 'isAutomaticInvocation'), 'False') + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + @mock.patch.object(controller.VnfLcmController, "_get_rollback_vnf") + @mock.patch.object(objects.VnfInstance, "get_by_id") + @mock.patch.object(vnf_lcm_rpc.VNFLcmRPCAPI, "rollback") + def test_rollback( + self, + mock_rollback, + mock_vnf_instance, + mock_get_vnf, + mock_lcm_by_id, + mock_get_service_plugins): + req = fake_request.HTTPRequest.blank( + '/vnf_lcm_op_occs/%s/rollback' % uuidsentinel.vnf_instance_id) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + + vnf_lcm_op_occs = fakes.vnflcm_rollback() + mock_lcm_by_id.return_value = vnf_lcm_op_occs + vnf_obj = fakes.vnf_rollback() + mock_get_vnf.return_value = vnf_obj + + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.NOT_INSTANTIATED, + task_state=fields.VnfInstanceTaskState.ERROR) + mock_vnf_instance.return_value = vnf_instance + + resp = req.get_response(self.app) + self.assertEqual(http_client.ACCEPTED, resp.status_code) + mock_rollback.assert_called_once() + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + @mock.patch.object(controller.VnfLcmController, "_get_rollback_vnf") + @mock.patch.object(objects.VnfInstance, "get_by_id") + @mock.patch.object(vnf_lcm_rpc.VNFLcmRPCAPI, "rollback") + def test_rollback_2( + self, + mock_rollback, + mock_vnf_instance, + mock_get_vnf, + mock_lcm_by_id, + mock_get_service_plugins): + req = fake_request.HTTPRequest.blank( + '/vnf_lcm_op_occs/%s/rollback' % uuidsentinel.vnf_instance_id) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + + vnf_lcm_op_occs = fakes.vnflcm_rollback_insta() + mock_lcm_by_id.return_value = vnf_lcm_op_occs + vnf_obj = fakes.vnf_rollback() + mock_get_vnf.return_value = vnf_obj + + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.NOT_INSTANTIATED, + task_state=fields.VnfInstanceTaskState.ERROR) + mock_vnf_instance.return_value = vnf_instance + + resp = req.get_response(self.app) + self.assertEqual(http_client.ACCEPTED, resp.status_code) + mock_rollback.assert_called_once() + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + def test_rollback_vnf_lcm_op_occs_access_error(self, + mock_get_service_plugins): + req = fake_request.HTTPRequest.blank( + '/vnf_lcm_op_occs/%s/rollback' % uuidsentinel.vnf_instance_id) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + + resp = req.get_response(self.app) + self.assertEqual(500, resp.status_code) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + def test_rollback_lcm_not_found(self, mock_lcm_by_id, + mock_get_service_plugins): + req = fake_request.HTTPRequest.blank( + '/vnf_lcm_op_occs/%s/rollback' % constants.INVALID_UUID) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + + mock_lcm_by_id.side_effect = exceptions.NotFound(resource='table', + name='vnf_lcm_op_occs') + + resp = req.get_response(self.app) + self.assertEqual(http_client.NOT_FOUND, resp.status_code) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + def test_rollback_not_failed_temp(self, mock_lcm_by_id, + mock_get_service_plugins): + req = fake_request.HTTPRequest.blank( + '/vnf_lcm_op_occs/%s/rollback' % uuidsentinel.vnf_instance_id) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + + vnf_lcm_op_occs = fakes.vnflcm_rollback_active() + mock_lcm_by_id.return_value = vnf_lcm_op_occs + + resp = req.get_response(self.app) + self.assertEqual(http_client.CONFLICT, resp.status_code) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id",) + def test_rollback_not_ope(self, mock_lcm_by_id, + mock_get_service_plugins): + req = fake_request.HTTPRequest.blank( + '/vnf_lcm_op_occs/%s/rollback' % uuidsentinel.vnf_instance_id) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + + vnf_lcm_op_occs = fakes.vnflcm_rollback_ope() + mock_lcm_by_id.return_value = vnf_lcm_op_occs + + resp = req.get_response(self.app) + self.assertEqual(http_client.CONFLICT, resp.status_code) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + def test_rollback_not_scale_in(self, mock_lcm_by_id, + mock_get_service_plugins): + req = fake_request.HTTPRequest.blank( + '/vnf_lcm_op_occs/%s/rollback' % uuidsentinel.vnf_instance_id) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + + vnf_lcm_op_occs = fakes.vnflcm_rollback_scale_in() + mock_lcm_by_id.return_value = vnf_lcm_op_occs + + resp = req.get_response(self.app) + self.assertEqual(http_client.CONFLICT, resp.status_code) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(controller.VnfLcmController, "_get_rollback_vnf") + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + def test_rollback_vnf_error(self, mock_lcm_by_id, mock_get_vnf, + mock_get_service_plugins): + req = fake_request.HTTPRequest.blank( + '/vnf_lcm_op_occs/%s/rollback' % uuidsentinel.vnf_instance_id) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + + vnf_lcm_op_occs = fakes.vnflcm_rollback_insta() + mock_lcm_by_id.return_value = vnf_lcm_op_occs + mock_get_vnf.side_effect = Exception("error") + + resp = req.get_response(self.app) + self.assertEqual(500, resp.status_code) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + @mock.patch.object(controller.VnfLcmController, "_get_rollback_vnf") + def test_rollback_vnf_not_found(self, mock_get_vnf, mock_lcm_by_id, + mock_get_service_plugins): + req = fake_request.HTTPRequest.blank( + '/vnf_lcm_op_occs/%s/rollback' % uuidsentinel.vnf_instance_id) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + + vnf_lcm_op_occs = fakes.vnflcm_rollback_insta() + mock_lcm_by_id.return_value = vnf_lcm_op_occs + mock_get_vnf.side_effect = vnfm.VNFNotFound( + vnf_id=uuidsentinel.vnf_instance_id) + + resp = req.get_response(self.app) + self.assertEqual(http_client.NOT_FOUND, resp.status_code) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + @mock.patch.object(controller.VnfLcmController, "_get_rollback_vnf") + def test_rollback_vnf_instance_error(self, mock_get_vnf, mock_lcm_by_id, + mock_get_service_plugins): + req = fake_request.HTTPRequest.blank( + '/vnf_lcm_op_occs/%s/rollback' % uuidsentinel.vnf_instance_id) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + + vnf_lcm_op_occs = fakes.vnflcm_rollback_insta() + mock_lcm_by_id.return_value = vnf_lcm_op_occs + vnf_obj = fakes.vnf_rollback() + mock_get_vnf.return_value = vnf_obj + + resp = req.get_response(self.app) + self.assertEqual(500, resp.status_code) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + @mock.patch.object(controller.VnfLcmController, "_get_rollback_vnf") + @mock.patch.object(objects.VnfInstance, "get_by_id") + def test_rollback_vnf_instance_not_found( + self, mock_vnf_instance, mock_get_vnf, mock_lcm_by_id, + mock_get_service_plugins): + req = fake_request.HTTPRequest.blank( + '/vnf_lcm_op_occs/%s/rollback' % uuidsentinel.vnf_instance_id) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + + vnf_lcm_op_occs = fakes.vnflcm_rollback_insta() + mock_lcm_by_id.return_value = vnf_lcm_op_occs + vnf_obj = fakes.vnf_rollback() + mock_get_vnf.return_value = vnf_obj + + mock_vnf_instance.side_effect = vnfm.VNFNotFound( + vnf_id=uuidsentinel.vnf_instance_id) + + resp = req.get_response(self.app) + self.assertEqual(http_client.NOT_FOUND, resp.status_code) diff --git a/tacker/tests/unit/vnflcm/test_vnflcm_driver.py b/tacker/tests/unit/vnflcm/test_vnflcm_driver.py index 6891f28c8..fafe72330 100644 --- a/tacker/tests/unit/vnflcm/test_vnflcm_driver.py +++ b/tacker/tests/unit/vnflcm/test_vnflcm_driver.py @@ -20,12 +20,16 @@ from unittest import mock import yaml from oslo_config import cfg +from oslo_serialization import jsonutils from oslo_utils import uuidutils +from heatclient.v1 import resources from tacker.common import driver_manager from tacker.common import exceptions from tacker.common import utils +from tacker.conductor.conductorrpc.vnf_lcm_rpc import VNFLcmRPCAPI from tacker import context +from tacker.db.common_services import common_services_db_plugin from tacker.manager import TackerManager from tacker import objects from tacker.objects import fields @@ -36,6 +40,9 @@ from tacker.tests.unit.vnflcm import fakes from tacker.tests import utils as test_utils from tacker.tests import uuidsentinel from tacker.vnflcm import vnflcm_driver +from tacker.vnfm.infra_drivers.openstack import heat_client +from tacker.vnfm.infra_drivers.openstack import openstack as opn +from tacker.vnfm import plugin from tacker.vnfm import vim_client @@ -125,6 +132,8 @@ class FakeDriverManager(mock.Mock): if self.fail_method_name and \ self.fail_method_name == 'post_heal_vnf': raise InfraDriverException("post_heal_vnf failed") + if 'get_rollback_ids' in args: + return [], [], "" class FakeVimClient(mock.Mock): @@ -308,7 +317,8 @@ class TestVnflcmDriver(db_base.SqlTestCase): self.assertEqual(expected_error % vnf_instance_obj.id, str(error)) self.assertEqual("NOT_INSTANTIATED", vnf_instance_obj.instantiation_state) - self.assertEqual(2, mock_vnf_instance_save.call_count) + # 2->1 reason: rollback_vnf_instantiated_resources deleted + self.assertEqual(1, mock_vnf_instance_save.call_count) self.assertEqual(2, self._vnf_manager.invoke.call_count) mock_final_vnf_dict.assert_called_once() @@ -356,9 +366,11 @@ class TestVnflcmDriver(db_base.SqlTestCase): self.assertEqual(expected_error % vnf_instance_obj.id, str(error)) self.assertEqual("NOT_INSTANTIATED", vnf_instance_obj.instantiation_state) - self.assertEqual(3, mock_vnf_instance_save.call_count) - self.assertEqual(5, self._vnf_manager.invoke.call_count) - mock_final_vnf_dict.assert_called_once() + # 3->1 reason: rollback_vnf_instantiated_resources deleted + self.assertEqual(1, mock_vnf_instance_save.call_count) + # 5->3 reason: rollback_vnf_instantiated_resources deleted + self.assertEqual(3, self._vnf_manager.invoke.call_count) + shutil.rmtree(fake_csar) @mock.patch('tacker.vnflcm.utils._make_final_vnf_dict') @@ -904,3 +916,765 @@ class TestVnflcmDriver(db_base.SqlTestCase): driver = vnflcm_driver.VnfLcmDriver() driver.scale(self.context, vnf_info, scale_vnf_request, vim_connection_info, scale_name_list, grp_id) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(VNFLcmRPCAPI, "send_notification") + @mock.patch.object(objects.VnfInstance, "save") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback_pre") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback") + def test_rollback_vnf_7( + self, + mock_update, + mock_up, + mock_insta_save, + mock_notification, + mock_lcm_save, + mock_get_service_plugins): + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_instance.instantiated_vnf_info.instance_id =\ + uuidsentinel.instance_id + vnf_lcm_op_occs = fakes.vnflcm_rollback_insta() + vnf_info = fakes.vnf_dict() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs + operation_params = jsonutils.loads(vnf_lcm_op_occs.operation_params) + + self._mock_vnf_manager() + driver = vnflcm_driver.VnfLcmDriver() + driver.rollback_vnf( + self.context, + vnf_info, + vnf_instance, + operation_params) + self.assertEqual(1, mock_lcm_save.call_count) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(VNFLcmRPCAPI, "send_notification") + @mock.patch.object(objects.VnfInstance, "save") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback_pre") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback") + def test_rollback_vnf_6( + self, + mock_update, + mock_up, + mock_insta_save, + mock_notification, + mock_lcm_save, + mock_get_service_plugins): + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_instance.instantiated_vnf_info.instance_id =\ + uuidsentinel.instance_id + vnf_lcm_op_occs = fakes.vnflcm_rollback_insta(error_point=6) + vnf_info = fakes.vnf_dict() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs + operation_params = jsonutils.loads(vnf_lcm_op_occs.operation_params) + + self._mock_vnf_manager() + driver = vnflcm_driver.VnfLcmDriver() + driver.rollback_vnf( + self.context, + vnf_info, + vnf_instance, + operation_params) + self.assertEqual(1, mock_lcm_save.call_count) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(VNFLcmRPCAPI, "send_notification") + @mock.patch.object(objects.VnfInstance, "save") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback_pre") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback") + def test_rollback_vnf_5( + self, + mock_update, + mock_up, + mock_insta_save, + mock_notification, + mock_lcm_save, + mock_get_service_plugins): + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_instance.instantiated_vnf_info.instance_id =\ + uuidsentinel.instance_id + vnf_lcm_op_occs = fakes.vnflcm_rollback_insta(error_point=5) + vnf_info = fakes.vnf_dict() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs + operation_params = jsonutils.loads(vnf_lcm_op_occs.operation_params) + + self._mock_vnf_manager() + driver = vnflcm_driver.VnfLcmDriver() + driver.rollback_vnf( + self.context, + vnf_info, + vnf_instance, + operation_params) + self.assertEqual(1, mock_lcm_save.call_count) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(VNFLcmRPCAPI, "send_notification") + @mock.patch.object(objects.VnfInstance, "save") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback_pre") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback") + def test_rollback_vnf_4( + self, + mock_update, + mock_up, + mock_insta_save, + mock_notification, + mock_lcm_save, + mock_get_service_plugins): + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_instance.instantiated_vnf_info.instance_id =\ + uuidsentinel.instance_id + vnf_lcm_op_occs = fakes.vnflcm_rollback_insta(error_point=4) + vnf_info = fakes.vnf_dict() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs + operation_params = jsonutils.loads(vnf_lcm_op_occs.operation_params) + + self._mock_vnf_manager() + driver = vnflcm_driver.VnfLcmDriver() + driver.rollback_vnf( + self.context, + vnf_info, + vnf_instance, + operation_params) + self.assertEqual(1, mock_lcm_save.call_count) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(VNFLcmRPCAPI, "send_notification") + @mock.patch.object(objects.VnfInstance, "save") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback_pre") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback") + def test_rollback_vnf_3( + self, + mock_update, + mock_up, + mock_insta_save, + mock_notification, + mock_lcm_save, + mock_get_service_plugins): + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_instance.instantiated_vnf_info.instance_id =\ + uuidsentinel.instance_id + vnf_lcm_op_occs = fakes.vnflcm_rollback_insta(error_point=3) + vnf_info = fakes.vnf_dict() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs + operation_params = jsonutils.loads(vnf_lcm_op_occs.operation_params) + + self._mock_vnf_manager() + driver = vnflcm_driver.VnfLcmDriver() + driver.rollback_vnf( + self.context, + vnf_info, + vnf_instance, + operation_params) + self.assertEqual(1, mock_lcm_save.call_count) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(VNFLcmRPCAPI, "send_notification") + @mock.patch.object(objects.VnfInstance, "save") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback_pre") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback") + def test_rollback_vnf_scale( + self, + mock_update, + mock_up, + mock_insta_save, + mock_notification, + mock_lcm_save, + mock_get_service_plugins): + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_instance.instantiated_vnf_info.instance_id =\ + uuidsentinel.instance_id + vnf_instance.instantiated_vnf_info.scale_status = [] + vnf_instance.instantiated_vnf_info.scale_status.append( + objects.ScaleInfo(aspect_id='SP1', scale_level=0)) + vnf_lcm_op_occs = fakes.vnflcm_rollback() + vnf_info = fakes.vnf_dict() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs + operation_params = jsonutils.loads(vnf_lcm_op_occs.operation_params) + + self._mock_vnf_manager() + driver = vnflcm_driver.VnfLcmDriver() + + driver.rollback_vnf( + self.context, + vnf_info, + vnf_instance, + operation_params) + self.assertEqual(1, mock_lcm_save.call_count) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(VNFLcmRPCAPI, "send_notification") + @mock.patch.object(objects.VnfInstance, "save") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback_pre") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback") + def test_rollback_vnf_scale_6( + self, + mock_update, + mock_up, + mock_insta_save, + mock_notification, + mock_lcm_save, + mock_get_service_plugins): + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_instance.instantiated_vnf_info.instance_id =\ + uuidsentinel.instance_id + vnf_instance.instantiated_vnf_info.scale_status = [] + vnf_instance.instantiated_vnf_info.scale_status.append( + objects.ScaleInfo(aspect_id='SP1', scale_level=0)) + vnf_lcm_op_occs = fakes.vnflcm_rollback(error_point=6) + vnf_info = fakes.vnf_dict() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs + operation_params = jsonutils.loads(vnf_lcm_op_occs.operation_params) + + self._mock_vnf_manager() + driver = vnflcm_driver.VnfLcmDriver() + + driver.rollback_vnf( + self.context, + vnf_info, + vnf_instance, + operation_params) + self.assertEqual(1, mock_lcm_save.call_count) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(VNFLcmRPCAPI, "send_notification") + @mock.patch.object(objects.VnfInstance, "save") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback_pre") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback") + def test_rollback_vnf_scale_5( + self, + mock_update, + mock_up, + mock_insta_save, + mock_notification, + mock_lcm_save, + mock_get_service_plugins): + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_instance.instantiated_vnf_info.instance_id =\ + uuidsentinel.instance_id + vnf_instance.instantiated_vnf_info.scale_status = [] + vnf_instance.instantiated_vnf_info.scale_status.append( + objects.ScaleInfo(aspect_id='SP1', scale_level=0)) + vnf_lcm_op_occs = fakes.vnflcm_rollback(error_point=5) + vnf_info = fakes.vnf_dict() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs + operation_params = jsonutils.loads(vnf_lcm_op_occs.operation_params) + + self._mock_vnf_manager() + driver = vnflcm_driver.VnfLcmDriver() + + driver.rollback_vnf( + self.context, + vnf_info, + vnf_instance, + operation_params) + self.assertEqual(1, mock_lcm_save.call_count) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(VNFLcmRPCAPI, "send_notification") + @mock.patch.object(objects.VnfInstance, "save") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback_pre") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback") + def test_rollback_vnf_scale_4( + self, + mock_update, + mock_up, + mock_insta_save, + mock_notification, + mock_lcm_save, + mock_get_service_plugins): + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_instance.instantiated_vnf_info.instance_id =\ + uuidsentinel.instance_id + vnf_instance.instantiated_vnf_info.scale_status = [] + vnf_instance.instantiated_vnf_info.scale_status.append( + objects.ScaleInfo(aspect_id='SP1', scale_level=0)) + vnf_lcm_op_occs = fakes.vnflcm_rollback(error_point=4) + vnf_info = fakes.vnf_dict() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs + operation_params = jsonutils.loads(vnf_lcm_op_occs.operation_params) + + self._mock_vnf_manager() + driver = vnflcm_driver.VnfLcmDriver() + + driver.rollback_vnf( + self.context, + vnf_info, + vnf_instance, + operation_params) + self.assertEqual(1, mock_lcm_save.call_count) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(VNFLcmRPCAPI, "send_notification") + @mock.patch.object(objects.VnfInstance, "save") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback_pre") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback") + def test_rollback_vnf_scale_3( + self, + mock_update, + mock_up, + mock_insta_save, + mock_notification, + mock_lcm_save, + mock_get_service_plugins): + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_instance.instantiated_vnf_info.instance_id =\ + uuidsentinel.instance_id + vnf_instance.instantiated_vnf_info.scale_status = [] + vnf_instance.instantiated_vnf_info.scale_status.append( + objects.ScaleInfo(aspect_id='SP1', scale_level=0)) + vnf_lcm_op_occs = fakes.vnflcm_rollback(error_point=3) + vnf_info = fakes.vnf_dict() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs + operation_params = jsonutils.loads(vnf_lcm_op_occs.operation_params) + + self._mock_vnf_manager() + driver = vnflcm_driver.VnfLcmDriver() + + driver.rollback_vnf( + self.context, + vnf_info, + vnf_instance, + operation_params) + self.assertEqual(1, mock_lcm_save.call_count) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "save") + def test_rollback_vnf_save_error(self, mock_lcm_save, + mock_get_service_plugins): + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_instance.instantiated_vnf_info.instance_id =\ + uuidsentinel.instance_id + vnf_lcm_op_occs = fakes.vnflcm_rollback_insta() + vnf_info = fakes.vnf_dict() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs + operation_params = jsonutils.loads(vnf_lcm_op_occs.operation_params) + mock_lcm_save.side_effect = exceptions.DBAccessError() + + self._mock_vnf_manager() + driver = vnflcm_driver.VnfLcmDriver() + self.assertRaises( + exceptions.DBAccessError, + driver.rollback_vnf, + self.context, + vnf_info, + vnf_instance, + operation_params) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(common_services_db_plugin.CommonServicesPluginDb, + "create_event") + @mock.patch.object(heat_client.HeatClient, "__init__") + @mock.patch.object(heat_client.HeatClient, "resource_get") + @mock.patch.object(heat_client.HeatClient, "resource_get_list") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback_pre") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback") + def test_rollback_vnf_scale_resource_error( + self, + mock_update, + mock_up, + mock_resource_get_list, + mock_resource_get, + mock_init, + mock_event, + mock_lcm_save, + mock_get_service_plugins): + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_instance.instantiated_vnf_info.instance_id =\ + uuidsentinel.instance_id + vnf_instance.instantiated_vnf_info.scale_status = [] + vnf_instance.instantiated_vnf_info.scale_status.append( + objects.ScaleInfo(aspect_id='SP1', scale_level=0)) + vnf_lcm_op_occs = fakes.vnflcm_rollback() + vnf_info = fakes.vnf_dict() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs + vnf_info['scale_level'] = 0 + operation_params = jsonutils.loads(vnf_lcm_op_occs.operation_params) + mock_init.return_value = None + mock_resource_get.side_effect = exceptions.DBAccessError() + + driver = vnflcm_driver.VnfLcmDriver() + self.assertRaises( + exceptions.DBAccessError, + driver.rollback_vnf, + self.context, + vnf_info, + vnf_instance, + operation_params) + self.assertEqual(2, mock_lcm_save.call_count) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(common_services_db_plugin.CommonServicesPluginDb, + "create_event") + @mock.patch.object(heat_client.HeatClient, "__init__") + @mock.patch.object(heat_client.HeatClient, "resource_get") + @mock.patch.object(heat_client.HeatClient, "resource_get_list") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback_pre") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback") + def test_rollback_vnf_scale_resource_list_error( + self, + mock_update, + mock_up, + mock_resource_list, + mock_resource_get, + mock_init, + mock_event, + mock_lcm_save, + mock_get_service_plugins): + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_instance.instantiated_vnf_info.instance_id =\ + uuidsentinel.instance_id + vnf_instance.instantiated_vnf_info.scale_status = [] + vnf_instance.instantiated_vnf_info.scale_status.append( + objects.ScaleInfo(aspect_id='SP1', scale_level=0)) + vnf_lcm_op_occs = fakes.vnflcm_rollback() + vnf_info = fakes.vnf_dict() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs + operation_params = jsonutils.loads(vnf_lcm_op_occs.operation_params) + mock_init.return_value = None + resource1 = resources.Resource(None, { + 'resource_name': 'SP1_group', + 'creation_time': '2020-01-01T00:00:00', + 'resource_status': 'CREATE_COMPLETE', + 'physical_resource_id': '30435eb8-1472-4cbc-abbe-00b395165ce7', + 'id': '1111' + }) + mock_resource_get.return_value = resource1 + mock_resource_list.side_effect = exceptions.DBAccessError() + + driver = vnflcm_driver.VnfLcmDriver() + self.assertRaises( + exceptions.DBAccessError, + driver.rollback_vnf, + self.context, + vnf_info, + vnf_instance, + operation_params) + self.assertEqual(2, mock_lcm_save.call_count) + self.assertEqual(2, mock_resource_list.call_count) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(common_services_db_plugin.CommonServicesPluginDb, + "create_event") + @mock.patch.object(heat_client.HeatClient, "__init__") + @mock.patch.object(plugin.VNFMMgmtMixin, "mgmt_call") + @mock.patch.object(opn.OpenStack, "delete") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback_pre") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback") + def test_rollback_vnf_delete_error( + self, + mock_update, + mock_up, + mock_delete, + mock_mgmt, + mock_init, + mock_event, + mock_lcm_save, + mock_get_service_plugins): + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_instance.instantiated_vnf_info.instance_id =\ + uuidsentinel.instance_id + vnf_lcm_op_occs = fakes.vnflcm_rollback_insta() + vnf_info = fakes.vnf_dict() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs + operation_params = jsonutils.loads(vnf_lcm_op_occs.operation_params) + mock_init.return_value = None + mock_delete.side_effect = exceptions.DBAccessError() + + driver = vnflcm_driver.VnfLcmDriver() + self.assertRaises( + exceptions.DBAccessError, + driver.rollback_vnf, + self.context, + vnf_info, + vnf_instance, + operation_params) + self.assertEqual(2, mock_lcm_save.call_count) + self.assertEqual(1, mock_delete.call_count) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(common_services_db_plugin.CommonServicesPluginDb, + "create_event") + @mock.patch.object(heat_client.HeatClient, "__init__") + @mock.patch.object(plugin.VNFMMgmtMixin, "mgmt_call") + @mock.patch.object(opn.OpenStack, "delete") + @mock.patch.object(opn.OpenStack, "delete_wait") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback_pre") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback") + def test_rollback_vnf_delete_wait_error( + self, + mock_update, + mock_up, + mock_delete_wait, + mock_delete, + mock_mgmt, + mock_init, + mock_event, + mock_lcm_save, + mock_get_service_plugins): + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_instance.instantiated_vnf_info.instance_id =\ + uuidsentinel.instance_id + vnf_lcm_op_occs = fakes.vnflcm_rollback_insta() + vnf_info = fakes.vnf_dict() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs + operation_params = jsonutils.loads(vnf_lcm_op_occs.operation_params) + mock_init.return_value = None + mock_delete_wait.side_effect = exceptions.DBAccessError() + + driver = vnflcm_driver.VnfLcmDriver() + self.assertRaises( + exceptions.DBAccessError, + driver.rollback_vnf, + self.context, + vnf_info, + vnf_instance, + operation_params) + self.assertEqual(2, mock_lcm_save.call_count) + self.assertEqual(1, mock_delete.call_count) + self.assertEqual(1, mock_delete_wait.call_count) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(common_services_db_plugin.CommonServicesPluginDb, + "create_event") + @mock.patch.object(heat_client.HeatClient, "__init__") + @mock.patch.object(heat_client.HeatClient, "resource_get") + @mock.patch.object(heat_client.HeatClient, "resource_get_list") + @mock.patch.object(opn.OpenStack, "get_rollback_ids") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_rollback_mgmt_call") + @mock.patch.object(opn.OpenStack, "scale_in_reverse") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback_pre") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback") + def test_rollback_vnf_scale_update_error( + self, + mock_update, + mock_up, + mock_scale, + mock_mgmt, + mock_resource_get, + mock_resource_get_list, + mock_resource, + mock_init, + mock_event, + mock_lcm_save, + mock_get_service_plugins): + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_instance.instantiated_vnf_info.instance_id =\ + uuidsentinel.instance_id + vnf_instance.instantiated_vnf_info.scale_status = [] + vnf_instance.instantiated_vnf_info.scale_status.append( + objects.ScaleInfo(aspect_id='SP1', scale_level=0)) + vnf_lcm_op_occs = fakes.vnflcm_rollback() + vnf_info = fakes.vnf_dict() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs + vnf_info['scale_level'] = 0 + operation_params = jsonutils.loads(vnf_lcm_op_occs.operation_params) + mock_init.return_value = None + mock_resource_get.return_value = ( + ['342bd357-7c4a-438c-9b5b-1f56702137d8'], + ['VDU1'], + '49c1cf71-abd4-4fb1-afb3-5f63f3b04246') + + mock_scale.side_effect = exceptions.DBAccessError() + + driver = vnflcm_driver.VnfLcmDriver() + self.assertRaises( + exceptions.DBAccessError, + driver.rollback_vnf, + self.context, + vnf_info, + vnf_instance, + operation_params) + self.assertEqual(2, mock_lcm_save.call_count) + self.assertEqual(1, mock_scale.call_count) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(common_services_db_plugin.CommonServicesPluginDb, + "create_event") + @mock.patch.object(heat_client.HeatClient, "__init__") + @mock.patch.object(heat_client.HeatClient, "resource_get") + @mock.patch.object(heat_client.HeatClient, "resource_get_list") + @mock.patch.object(opn.OpenStack, "get_rollback_ids") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_rollback_mgmt_call") + @mock.patch.object(opn.OpenStack, "scale_in_reverse") + @mock.patch.object(opn.OpenStack, "scale_update_wait") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback_pre") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback") + def test_rollback_vnf_scale_update_wait_error( + self, + mock_update, + mock_up, + mock_wait, + mock_scale, + mock_mgmt, + mock_resource_get, + mock_resource_get_list, + mock_resource, + mock_init, + mock_event, + mock_lcm_save, + mock_get_service_plugins): + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_instance.instantiated_vnf_info.instance_id =\ + uuidsentinel.instance_id + vnf_instance.instantiated_vnf_info.scale_status = [] + vnf_instance.instantiated_vnf_info.scale_status.append( + objects.ScaleInfo(aspect_id='SP1', scale_level=0)) + vnf_lcm_op_occs = fakes.vnflcm_rollback() + vnf_info = fakes.vnf_dict() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs + vnf_info['scale_level'] = 0 + operation_params = jsonutils.loads(vnf_lcm_op_occs.operation_params) + mock_init.return_value = None + mock_resource_get.return_value = ( + ['342bd357-7c4a-438c-9b5b-1f56702137d8'], + ['VDU1'], + '49c1cf71-abd4-4fb1-afb3-5f63f3b04246') + + mock_wait.side_effect = exceptions.DBAccessError() + + driver = vnflcm_driver.VnfLcmDriver() + self.assertRaises( + exceptions.DBAccessError, + driver.rollback_vnf, + self.context, + vnf_info, + vnf_instance, + operation_params) + self.assertEqual(2, mock_lcm_save.call_count) + self.assertEqual(1, mock_scale.call_count) + self.assertEqual(1, mock_wait.call_count) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(common_services_db_plugin.CommonServicesPluginDb, + "create_event") + @mock.patch.object(heat_client.HeatClient, "__init__") + @mock.patch.object(opn.OpenStack, "get_rollback_ids") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_rollback_mgmt_call") + @mock.patch.object(opn.OpenStack, "scale_in_reverse") + @mock.patch.object(opn.OpenStack, "scale_update_wait") + @mock.patch.object(opn.OpenStack, "scale_resource_update") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback_pre") + @mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback") + def test_rollback_vnf_scale_resource_get_error( + self, + mock_update, + mock_up, + mock_scale_resource, + mock_wait, + mock_scale, + mock_mgmt, + mock_resource_get, + mock_init, + mock_event, + mock_lcm_save, + mock_get_service_plugins): + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + + vnf_instance.instantiated_vnf_info.instance_id =\ + uuidsentinel.instance_id + vnf_instance.instantiated_vnf_info.scale_status = [] + vnf_instance.instantiated_vnf_info.scale_status.append( + objects.ScaleInfo(aspect_id='SP1', scale_level=0)) + vnf_lcm_op_occs = fakes.vnflcm_rollback() + vnf_info = fakes.vnf_dict() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs + vnf_info['scale_level'] = 0 + operation_params = jsonutils.loads(vnf_lcm_op_occs.operation_params) + mock_init.return_value = None + mock_resource_get.return_value = ( + ['342bd357-7c4a-438c-9b5b-1f56702137d8'], + ['VDU1'], + '49c1cf71-abd4-4fb1-afb3-5f63f3b04246') + resource1 = resources.Resource(None, { + 'resource_name': 'SP1_group', + 'creation_time': '2020-01-01T00:00:00', + 'resource_status': 'UPDATE_COMPLETE', + 'physical_resource_id': '30435eb8-1472-4cbc-abbe-00b395165ce7', + 'id': '1111' + }) + + mock_wait.return_value = resource1 + mock_scale_resource.side_effect = exceptions.DBAccessError() + + driver = vnflcm_driver.VnfLcmDriver() + self.assertRaises( + exceptions.DBAccessError, + driver.rollback_vnf, + self.context, + vnf_info, + vnf_instance, + operation_params) + self.assertEqual(2, mock_lcm_save.call_count) + self.assertEqual(1, mock_scale.call_count) + self.assertEqual(1, mock_wait.call_count) + self.assertEqual(2, mock_scale_resource.call_count) diff --git a/tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack_driver.py b/tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack_driver.py index f2a68b3b5..221d93903 100644 --- a/tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack_driver.py +++ b/tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack_driver.py @@ -35,6 +35,7 @@ from tacker.tests.common import helpers from tacker.tests import constants from tacker.tests.unit import base from tacker.tests.unit.db import utils +from tacker.tests.unit.vnflcm import fakes from tacker.tests.unit.vnfm.infra_drivers.openstack.fixture_data import client from tacker.tests.unit.vnfm.infra_drivers.openstack.fixture_data import \ fixture_data_utils as fd_utils @@ -1950,3 +1951,97 @@ class TestOpenStack(base.FixturedTestCase): scale_vnf_request=scale_vnf_request, region_name=None ) + + @mock.patch.object(hc.HeatClient, "resource_get") + @mock.patch.object(hc.HeatClient, "resource_get_list") + def test_get_rollback_ids(self, mock_list, mock_resource): + resource1 = resources.Resource(None, { + 'resource_name': 'SP1_group', + 'creation_time': '2020-01-01T00:00:00', + 'resource_status': 'CREATE_COMPLETE', + 'physical_resource_id': '30435eb8-1472-4cbc-abbe-00b395165ce7', + 'id': '1111' + }) + mock_resource.return_value = resource1 + res_list = [] + resource2 = resources.Resource(None, { + 'resource_name': 'aaaaaaaa', + 'creation_time': '2020-01-01T00:00:00', + 'resource_status': 'CREATE_COMPLETE', + 'physical_resource_id': '30435eb8-1472-4cbc-abbe-00b395165ce8', + 'id': '1111' + }) + res_list.append(resource2) + resource3 = resources.Resource(None, { + 'resource_name': 'bbbbbbbb', + 'creation_time': '2020-01-01T00:00:00', + 'resource_status': 'CREATE_COMPLETE', + 'physical_resource_id': '30435eb8-1472-4cbc-abbe-00b395165ce9', + 'id': '1111' + }) + res_list.append(resource3) + resource4 = resources.Resource(None, { + 'resource_name': 'cccccccc', + 'creation_time': '2020-01-01T00:00:01', + 'resource_status': 'CREATE_COMPLETE', + 'physical_resource_id': '30435eb8-1472-4cbc-abbe-00b395165ce0', + 'id': '1111' + }) + res_list.append(resource4) + mock_list.return_value = res_list + + vnf_dict = fakes.vnf_dict() + vnf_dict['res_num'] = 2 + + scale_id_list, scale_name_list, grp_id = \ + self.openstack.get_rollback_ids( + None, self.context, vnf_dict, 'SP1', None, None) + + self.assertEqual('30435eb8-1472-4cbc-abbe-00b395165ce7', grp_id) + + @mock.patch.object(hc.HeatClient, "resource_get") + @mock.patch.object(hc.HeatClient, "resource_get_list") + def test_get_rollback_ids_0(self, mock_list, mock_resource): + resource1 = resources.Resource(None, { + 'resource_name': 'SP1_group', + 'creation_time': '2020-01-01T00:00:00', + 'resource_status': 'CREATE_COMPLETE', + 'physical_resource_id': '30435eb8-1472-4cbc-abbe-00b395165ce7', + 'id': '1111' + }) + mock_resource.return_value = resource1 + res_list = [] + resource2 = resources.Resource(None, { + 'resource_name': 'aaaaaaaa', + 'creation_time': '2020-01-01T00:00:00', + 'resource_status': 'CREATE_COMPLETE', + 'physical_resource_id': '30435eb8-1472-4cbc-abbe-00b395165ce8', + 'id': '1111' + }) + res_list.append(resource2) + resource3 = resources.Resource(None, { + 'resource_name': 'bbbbbbbb', + 'creation_time': '2020-01-01T00:00:00', + 'resource_status': 'CREATE_COMPLETE', + 'physical_resource_id': '30435eb8-1472-4cbc-abbe-00b395165ce9', + 'id': '1111' + }) + res_list.append(resource3) + resource4 = resources.Resource(None, { + 'resource_name': 'cccccccc', + 'creation_time': '2020-01-01T00:00:01', + 'resource_status': 'CREATE_COMPLETE', + 'physical_resource_id': '30435eb8-1472-4cbc-abbe-00b395165ce0', + 'id': '1111' + }) + res_list.append(resource4) + mock_list.return_value = res_list + + vnf_dict = fakes.vnf_dict() + vnf_dict['res_num'] = 0 + + scale_id_list, scale_name_list, grp_id = \ + self.openstack.get_rollback_ids( + None, self.context, vnf_dict, 'SP1', None, None) + + self.assertEqual('30435eb8-1472-4cbc-abbe-00b395165ce7', grp_id) diff --git a/tacker/tests/unit/vnfm/test_plugin.py b/tacker/tests/unit/vnfm/test_plugin.py index b9f58d5b6..9757aec63 100644 --- a/tacker/tests/unit/vnfm/test_plugin.py +++ b/tacker/tests/unit/vnfm/test_plugin.py @@ -37,6 +37,7 @@ from tacker.plugins.common import constants from tacker.tests.unit.conductor import fakes from tacker.tests.unit.db import base as db_base from tacker.tests.unit.db import utils +from tacker.tests.unit.vnflcm import fakes as vnflcm_fakes from tacker.vnfm import monitor from tacker.vnfm import plugin @@ -1326,3 +1327,53 @@ class TestVNFMPlugin(db_base.SqlTestCase): self.vnfm_plugin.delete_placement_constraint( self.context, '7ddc38c3-a116-48b0-bfc1-68d7f306f467') + + def test_update_vnf_rollback_pre_scale(self): + vnf_info = {} + vnf_lcm_op_occ = vnflcm_fakes.vnflcm_rollback() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occ + vnf_info['id'] = uuidutils.generate_uuid() + self.vnfm_plugin._update_vnf_rollback_pre( + self.context, vnf_info) + + def test_update_vnf_rollback_pre_insta(self): + vnf_info = {} + vnf_lcm_op_occ = vnflcm_fakes.vnflcm_rollback_insta() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occ + vnf_info['id'] = uuidutils.generate_uuid() + self.vnfm_plugin._update_vnf_rollback_pre( + self.context, vnf_info) + + def test_update_vnf_rollback_scale(self): + vnf_info = {} + vnf_lcm_op_occ = vnflcm_fakes.vnflcm_rollback() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occ + vnf_info['id'] = uuidutils.generate_uuid() + self.vnfm_plugin._update_vnf_rollback( + self.context, vnf_info, + 'ERROR', 'ACTIVE') + + def test_update_vnf_rollback_insta(self): + vnf_info = {} + vnf_lcm_op_occ = vnflcm_fakes.vnflcm_rollback_insta() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occ + vnf_info['id'] = uuidutils.generate_uuid() + self.vnfm_plugin._update_vnf_rollback( + self.context, vnf_info, + 'ERROR', 'INACTIVE') + + def test_update_vnf_rollback_status_err_scale(self): + vnf_info = {} + vnf_lcm_op_occ = vnflcm_fakes.vnflcm_rollback() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occ + vnf_info['id'] = uuidutils.generate_uuid() + self.vnfm_plugin.update_vnf_rollback_status_err( + self.context, vnf_info) + + def test_update_vnf_rollback_status_err_insta(self): + vnf_info = {} + vnf_lcm_op_occ = vnflcm_fakes.vnflcm_rollback_insta() + vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occ + vnf_info['id'] = uuidutils.generate_uuid() + self.vnfm_plugin.update_vnf_rollback_status_err( + self.context, vnf_info) diff --git a/tacker/vnflcm/vnflcm_driver.py b/tacker/vnflcm/vnflcm_driver.py index 280db96e1..19ee1c586 100644 --- a/tacker/vnflcm/vnflcm_driver.py +++ b/tacker/vnflcm/vnflcm_driver.py @@ -47,52 +47,6 @@ LOG = logging.getLogger(__name__) CONF = cfg.CONF -@utils.expects_func_args('vnf_instance') -def rollback_vnf_instantiated_resources(function): - """Decorator to rollback resources created during vnf instantiation""" - - def _rollback_vnf(vnflcm_driver, context, vnf_instance): - vim_info = vnflcm_utils._get_vim(context, - vnf_instance.vim_connection_info) - - vim_connection_info = objects.VimConnectionInfo.obj_from_primitive( - vim_info, context) - - LOG.info("Rollback vnf %s", vnf_instance.id) - try: - vnflcm_driver._delete_vnf_instance_resources(context, vnf_instance, - vim_connection_info) - - if vnf_instance.instantiated_vnf_info: - vnf_instance.instantiated_vnf_info.reinitialize() - - vnflcm_driver._vnf_instance_update(context, vnf_instance, - vim_connection_info=[], task_state=None) - - LOG.info("Vnf %s rollback completed successfully", vnf_instance.id) - except Exception as ex: - LOG.error("Unable to rollback vnf instance " - "%s due to error: %s", vnf_instance.id, ex) - - @functools.wraps(function) - def decorated_function(self, context, *args, **kwargs): - try: - return function(self, context, *args, **kwargs) - except Exception as exp: - with excutils.save_and_reraise_exception(): - wrapped_func = safe_utils.get_wrapped_function(function) - keyed_args = inspect.getcallargs(wrapped_func, self, context, - *args, **kwargs) - vnf_instance = keyed_args['vnf_instance'] - LOG.error("Failed to instantiate vnf %(id)s. Error: %(error)s", - {"id": vnf_instance.id, - "error": six.text_type(exp)}) - - _rollback_vnf(self, context, vnf_instance) - - return decorated_function - - @utils.expects_func_args('vnf_info', 'vnf_instance', 'scale_vnf_request') def revert_to_error_scale(function): """Decorator to revert task_state to error on failure.""" @@ -218,6 +172,116 @@ def revert_to_error_task_state(function): return decorated_function +@utils.expects_func_args('vnf_info', 'vnf_instance', 'operation_params') +def revert_to_error_rollback(function): + """Decorator to revert task_state to error on failure.""" + + @functools.wraps(function) + def decorated_function(self, context, *args, **kwargs): + try: + return function(self, context, *args, **kwargs) + except Exception as ex: + with excutils.save_and_reraise_exception(): + wrapped_func = safe_utils.get_wrapped_function(function) + keyed_args = inspect.getcallargs(wrapped_func, self, context, + *args, **kwargs) + resource_changes = None + try: + vnf_info = keyed_args['vnf_info'] + vnf_instance = keyed_args['vnf_instance'] + operation_params = keyed_args['operation_params'] + vim_info = vnflcm_utils._get_vim(context, + vnf_instance.vim_connection_info) + vim_connection_info =\ + objects.VimConnectionInfo.obj_from_primitive( + vim_info, context) + vnf_lcm_op_occs = vnf_info['vnf_lcm_op_occ'] + if vnf_info.get('resource_changes'): + resource_changes = vnf_info.get('resource_changes') + else: + if vnf_lcm_op_occs.operation == 'SCALE': + scale_vnf_request =\ + objects.ScaleVnfRequest.obj_from_primitive( + operation_params, context=context) + scale_vnf_request_copy = \ + copy.deepcopy(scale_vnf_request) + scale_vnf_request_copy.type = 'SCALE_IN' + resource_changes = self._scale_resource_update( + context, + vnf_info, + vnf_instance, + scale_vnf_request_copy, + vim_connection_info, + error=True) + else: + resource_changes = self._term_resource_update( + context, + vnf_info, + vnf_instance) + except Exception as e: + LOG.warning(traceback.format_exc()) + LOG.warning("Failed to scale resource update " + "instance %(id)s. Error: %(error)s", + {"id": vnf_instance.id, "error": e}) + + try: + self._update_vnf_rollback_status_err(context, vnf_info) + except Exception as e: + LOG.warning("Failed to revert scale info for event " + "instance %(id)s. Error: %(error)s", + {"id": vnf_instance.id, "error": e}) + try: + self._vnf_instance_update(context, vnf_instance) + except Exception as e: + LOG.warning("Failed to revert instantiation info for vnf " + "instance %(id)s. Error: %(error)s", + {"id": vnf_instance.id, "error": e}) + problem = objects.ProblemDetails(status=500, + detail=str(ex)) + + try: + timestamp = datetime.utcnow() + vnf_lcm_op_occ = vnf_info['vnf_lcm_op_occ'] + vnf_lcm_op_occ.operation_state = 'FAILED_TEMP' + vnf_lcm_op_occ.state_entered_time = timestamp + if resource_changes: + vnf_lcm_op_occ.resource_changes = resource_changes + vnf_lcm_op_occ.error = problem + vnf_lcm_op_occ.save() + except Exception as e: + LOG.warning("Failed to update vnf_lcm_op_occ for vnf " + "instance %(id)s. Error: %(error)s", + {"id": vnf_instance.id, "error": e}) + + try: + notification = vnf_info['notification'] + notification['notificationStatus'] = 'RESULT' + notification['operationState'] = 'FAILED_TEMP' + notification['error'] = problem.to_dict() + if resource_changes: + resource_dict = resource_changes.to_dict() + if resource_dict.get('affected_vnfcs'): + notification['affectedVnfcs'] = \ + jsonutils.dump_as_bytes( + resource_dict.get('affected_vnfcs')) + if resource_dict.get('affected_virtual_links'): + notification['affectedVirtualLinks'] = \ + jsonutils.dump_as_bytes( + resource_dict.get( + 'affected_virtual_links')) + if resource_dict.get('affected_virtual_storages'): + notification['affectedVirtualStorages'] = \ + jsonutils.dump_as_bytes( + resource_dict.get( + 'affected_virtual_storages')) + self.rpc_api.sendNotification(context, notification) + except Exception as e: + LOG.warning("Failed to revert scale info for vnf " + "instance %(id)s. Error: %(error)s", + {"id": vnf_instance.id, "error": e}) + return decorated_function + + class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver): def __init__(self): @@ -321,7 +385,6 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver): error=encodeutils.exception_to_unicode(exp)) @log.log - @rollback_vnf_instantiated_resources def instantiate_vnf(self, context, vnf_instance, vnf_dict, instantiate_vnf_req): @@ -589,8 +652,8 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver): return scale_id_list, scale_name_list, grp_id def _get_node_template_for_vnf(self, vnfd_dict): - for node_template in vnfd_dict['topology_template']['\ - node_templates'].values(): + node_tmp = vnfd_dict['topology_template']['node_templates'] + for node_template in node_tmp.values(): LOG.debug("node_template %s", node_template) if not re.match('^tosca', node_template['type']): LOG.debug("VNF node_template %s", node_template) @@ -1007,3 +1070,380 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver): if i != scale_vnf_request.number_of_steps - 1: if cooldown: time.sleep(cooldown) + + def _term_resource_update(self, context, vnf_info, vnf_instance, + error=False): + if not vnf_instance.instantiated_vnf_info: + resource_changes = objects.ResourceChanges() + resource_changes.affected_vnfcs = [] + resource_changes.affected_virtual_links = [] + resource_changes.affected_virtual_storages = [] + vnf_info['resource_changes'] = resource_changes + return resource_changes + instantiated_vnf_before = copy.deepcopy( + vnf_instance.instantiated_vnf_info) + vnf_instance.instantiated_vnf_info.reinitialize() + if not error: + vnf_instance.vim_connection_info = [] + vnf_instance.task_state = None + LOG.debug( + "vnf_instance.instantiated_vnf_info %s", + vnf_instance.instantiated_vnf_info) + affected_vnfcs = [] + affected_virtual_storages = [] + affected_virtual_links = [] + for vnfc in instantiated_vnf_before.vnfc_resource_info: + vnfc_delete = True + for rsc in vnf_instance.instantiated_vnf_info.vnfc_resource_info: + if vnfc.compute_resource.resource_id == \ + rsc.compute_resource.resource_id: + vnfc_delete = False + break + if vnfc_delete: + affected_vnfc = objects.AffectedVnfc( + id=vnfc.id, + vdu_id=vnfc.vdu_id, + change_type='REMOVED', + compute_resource=vnfc.compute_resource) + affected_vnfcs.append(affected_vnfc) + + for st in instantiated_vnf_before.virtual_storage_resource_info: + st_delete = True + for rsc in \ + vnf_instance.instantiated_vnf_info.\ + virtual_storage_resource_info: + if st.storage_resource.resource_id == \ + rsc.storage_resource.resource_id: + st_delete = False + break + if st_delete: + affected_st = objects.AffectedVirtualStorage( + id=st.id, + virtual_storage_desc_id=st.virtual_storage_desc_id, + change_type='REMOVED', + storage_resource=st.storage_resource) + affected_virtual_storages.append(affected_st) + + for vl in instantiated_vnf_before.vnf_virtual_link_resource_info: + vm_delete = False + for rsc in \ + vnf_instance.instantiated_vnf_info.\ + vnf_virtual_link_resource_info: + if st.network_resource.resource_id == \ + rsc.network_resource.resource_id: + vm_delete = False + break + if vm_delete: + affected_vl = objects.AffectedVirtualLink( + id=vl.id, + vnf_virtual_link_desc_id=vl.vnf_virtual_link_desc_id, + change_type='REMOVED', + network_resource=vl.network_resource) + affected_virtual_links.append(affected_vl) + vnf_lcm_op_occs = vnf_info['vnf_lcm_op_occ'] + resource_changes = objects.ResourceChanges() + resource_changes.affected_vnfcs = [] + resource_changes.affected_virtual_links = [] + resource_changes.affected_virtual_storages = [] + if 'resource_changes' in vnf_lcm_op_occs \ + and vnf_lcm_op_occs.resource_changes: + if 'affected_vnfcs' in vnf_lcm_op_occs.resource_changes: + if len(vnf_lcm_op_occs.resource_changes.affected_vnfcs) > 0: + resource_changes.affected_vnfcs.extend( + vnf_lcm_op_occs.resource_changes.affected_vnfcs) + if 'affected_virtual_storages' in vnf_lcm_op_occs.resource_changes: + if len(vnf_lcm_op_occs.resource_changes. + affected_virtual_storages) > 0: + resource_changes.affected_virtual_storages.extend( + vnf_lcm_op_occs.resource_changes. + affected_virtual_storages) + if 'affected_virtual_links' in vnf_lcm_op_occs.resource_changes: + if len(vnf_lcm_op_occs.resource_changes. + affected_virtual_links) > 0: + resource_changes.affected_virtual_links.extend( + vnf_lcm_op_occs.resource_changes. + affected_virtual_links) + resource_changes.affected_vnfcs.extend(affected_vnfcs) + resource_changes.affected_virtual_storages.extend( + affected_virtual_storages) + resource_changes.affected_virtual_links.extend(affected_virtual_links) + + vnf_info['resource_changes'] = resource_changes + return resource_changes + + def _rollback_vnf_pre( + self, + context, + vnf_info, + vnf_instance, + operation_params, + vim_connection_info): + vnf_lcm_op_occs = vnf_info['vnf_lcm_op_occ'] + scale_id_list = [] + scale_name_list = [] + grp_id = None + self._update_vnf_rollback_pre(context, vnf_info) + if vnf_lcm_op_occs.operation == 'SCALE': + scaleGroupDict = jsonutils.loads( + vnf_info['attributes']['scale_group']) + cap_size = scaleGroupDict['scaleGroupDict'][operation_params + ['aspect_id']]['default'] + vnf_info['res_num'] = cap_size + scale_vnf_request = objects.ScaleVnfRequest.obj_from_primitive( + operation_params, context=context) + for scale in vnf_instance.instantiated_vnf_info.scale_status: + if scale_vnf_request.aspect_id == scale.aspect_id: + vnf_info['after_scale_level'] = scale.scale_level + break + if vnf_lcm_op_occs.operation == 'SCALE' \ + and vnf_lcm_op_occs.error_point >= 4: + scale_id_list, scale_name_list, grp_id = self._vnf_manager.invoke( + vim_connection_info.vim_type, + 'get_rollback_ids', + plugin=self, + context=context, + vnf_dict=vnf_info, + aspect_id=operation_params['aspect_id'], + auth_attr=vim_connection_info.access_info, + region_name=vim_connection_info.access_info.get('region_name') + ) + if vnf_lcm_op_occs.error_point == 7: + if vnf_lcm_op_occs.operation == 'SCALE': + vnfd_yaml = vnf_info['vnfd']['attributes'].\ + get('vnfd_' + + vnf_instance.instantiated_vnf_info.flavour_id, '') + vnfd_dict = yaml.safe_load(vnfd_yaml) + # mgmt_driver from vnfd + vnf_node = self._get_node_template_for_vnf(vnfd_dict) + if vnf_node and vnf_node.get('interfaces'): + if vnf_node['interfaces'].get('Vnflcm'): + if vnf_node['interfaces']['Vnflcm'].get('scale_start'): + vnf_info['vnfd']['mgmt_driver'] = \ + vnf_node['interfaces']['Vnflcm']['scale_start'] + vnf_info['action'] = 'in' + if len(scale_id_list) != 0 and vnf_info['vnfd'].get( + 'mgmt_driver'): + if len(scale_id_list) > 1: + stack_value = [] + stack_value = scale_id_list + else: + stack_value = scale_id_list[0] + kwargs = { + mgmt_constants.KEY_ACTION: + mgmt_constants.ACTION_SCALE_IN_VNF, + mgmt_constants.KEY_KWARGS: + {'vnf': vnf_info}, + mgmt_constants.KEY_SCALE: + stack_value, + } + self._rollback_mgmt_call(context, vnf_info, kwargs) + + else: + vnfd_yaml = vnf_info['vnfd']['attributes'].\ + get('vnfd_' + + vnf_instance.instantiated_vnf_info.flavour_id, '') + vnfd_dict = yaml.safe_load(vnfd_yaml) + # mgmt_driver from vnfd + vnf_node = self._get_node_template_for_vnf(vnfd_dict) + if vnf_node and vnf_node.get('interfaces'): + if vnf_node['interfaces'].get('Vnflcm'): + if vnf_node['interfaces']['Vnflcm'].get( + 'termination_start'): + vnf_info['vnfd']['mgmt_driver'] = vnf_node[ + 'interfaces']['Vnflcm']['termination_start'] + if len(scale_id_list) != 0 and vnf_info['vnfd'].get( + 'mgmt_driver'): + kwargs = { + mgmt_constants.KEY_ACTION: + mgmt_constants.ACTION_DELETE_VNF, + mgmt_constants.KEY_KWARGS: + {'vnf': vnf_info} + } + self._rollback_mgmt_call(context, vnf_info, kwargs) + vnf_lcm_op_occs.error_point = 6 + + return scale_name_list, grp_id + + def _rollback_vnf( + self, + context, + vnf_info, + vnf_instance, + operation_params, + vim_connection_info, + scale_name_list, + grp_id): + vnf_lcm_op_occs = vnf_info['vnf_lcm_op_occ'] + if vnf_lcm_op_occs.error_point >= 4: + if vnf_lcm_op_occs.operation == 'SCALE': + scale_vnf_request = objects.ScaleVnfRequest.obj_from_primitive( + operation_params, context=context) + self._vnf_manager.invoke( + vim_connection_info.vim_type, + 'scale_in_reverse', + plugin=self, + context=context, + auth_attr=vim_connection_info.access_info, + vnf_info=vnf_info, + scale_vnf_request=scale_vnf_request, + region_name=vim_connection_info.access_info.get( + 'region_name'), + scale_name_list=scale_name_list, + grp_id=grp_id) + self._vnf_manager.invoke( + vim_connection_info.vim_type, + 'scale_update_wait', + plugin=self, + context=context, + auth_attr=vim_connection_info.access_info, + vnf_info=vnf_info, + region_name=vim_connection_info.access_info.get( + 'region_name')) + + else: + instance_id = vnf_instance.instantiated_vnf_info.instance_id + access_info = vim_connection_info.access_info + self._vnf_manager.invoke(vim_connection_info.vim_type, + 'delete', plugin=self, context=context, + vnf_id=instance_id, auth_attr=access_info) + + self._vnf_manager.invoke(vim_connection_info.vim_type, + 'delete_wait', plugin=self, context=context, + vnf_id=instance_id, auth_attr=access_info) + + vnf_lcm_op_occs.error_point = 3 + + def _update_vnf_rollback_pre(self, context, vnf_info): + self._vnfm_plugin._update_vnf_rollback_pre(context, vnf_info) + + def _update_vnf_rollback(self, context, vnf_info, + vnf_instance, vnf_lcm_op_occs): + self._vnfm_plugin._update_vnf_rollback(context, vnf_info, + 'ERROR', + 'ACTIVE', + vnf_instance=vnf_instance, + vnf_lcm_op_occ=vnf_lcm_op_occs) + + def _update_vnf_rollback_status_err(self, context, vnf_info): + self._vnfm_plugin._update_vnf_rollback_status_err(context, vnf_info) + + def _rollback_mgmt_call(self, context, vnf_info, kwargs): + self._vnfm_plugin.mgmt_call(context, vnf_info, kwargs) + + def _rollback_vnf_post( + self, + context, + vnf_info, + vnf_instance, + operation_params, + vim_connection_info): + vnf_lcm_op_occs = vnf_info['vnf_lcm_op_occ'] + if vnf_lcm_op_occs.operation == 'SCALE': + scale_vnf_request = objects.ScaleVnfRequest.obj_from_primitive( + operation_params, context=context) + scale_vnf_request_copy = copy.deepcopy(scale_vnf_request) + scale_vnf_request_copy.type = 'SCALE_IN' + resource_changes = self._scale_resource_update(context, vnf_info, + vnf_instance, + scale_vnf_request_copy, + vim_connection_info) + + else: + resource_changes = self._term_resource_update( + context, vnf_info, vnf_instance) + + vnf_lcm_op_occs.error_point = 2 + + timestamp = datetime.utcnow() + vnf_lcm_op_occs.operation_state = 'ROLLED_BACK' + vnf_lcm_op_occs.state_entered_time = timestamp + vnf_lcm_op_occs.resource_changes = resource_changes + self._update_vnf_rollback(context, vnf_info, + vnf_instance, + vnf_lcm_op_occs) + notification = vnf_info['notification'] + notification['notificationStatus'] = 'RESULT' + notification['operationState'] = 'ROLLED_BACK' + resource_dict = resource_changes.to_dict() + if resource_dict.get('affected_vnfcs'): + notification['affectedVnfcs'] = resource_dict.get('affected_vnfcs') + if resource_dict.get('affected_virtual_links'): + notification['affectedVirtualLinks'] = \ + resource_dict.get('affected_virtual_links') + if resource_dict.get('affected_virtual_storages'): + notification['affectedVirtualStorages'] = \ + resource_dict.get('affected_virtual_storages') + self.rpc_api.send_notification(context, notification) + + @log.log + @revert_to_error_rollback + def rollback_vnf(self, context, vnf_info, vnf_instance, operation_params): + LOG.info("Request received for rollback vnf '%s'", vnf_instance.id) + vnf_lcm_op_occs = vnf_info['vnf_lcm_op_occ'] + if vnf_lcm_op_occs.operation == 'SCALE': + scale_vnf_request = objects.ScaleVnfRequest.obj_from_primitive( + operation_params, context=context) + for scale in vnf_instance.instantiated_vnf_info.scale_status: + if scale_vnf_request.aspect_id == scale.aspect_id: + vnf_info['after_scale_level'] = scale.scale_level + break + + timestamp = datetime.utcnow() + + vnf_lcm_op_occs.operation_state = 'ROLLING_BACK' + vnf_lcm_op_occs.state_entered_time = timestamp + LOG.debug("vnf_lcm_op_occs %s", vnf_lcm_op_occs) + + insta_url = CONF.vnf_lcm.endpoint_url + \ + "/vnflcm/v1/vnf_instances/" + \ + vnf_instance.id + vnflcm_url = CONF.vnf_lcm.endpoint_url + \ + "/vnflcm/v1/vnf_lcm_op_occs/" + \ + vnf_lcm_op_occs.id + notification = {} + notification['notificationType'] = \ + 'VnfLcmOperationOccurrenceNotification' + notification['vnfInstanceId'] = vnf_instance.id + notification['notificationStatus'] = 'START' + notification['operation'] = vnf_lcm_op_occs.operation + notification['operationState'] = 'ROLLING_BACK' + if vnf_lcm_op_occs.operation == 'SCALE': + notification['isAutomaticInvocation'] = \ + vnf_lcm_op_occs.is_automatic_invocation + else: + notification['isAutomaticInvocation'] = False + notification['vnfLcmOpOccId'] = vnf_lcm_op_occs.id + notification['_links'] = {} + notification['_links']['vnfInstance'] = {} + notification['_links']['vnfInstance']['href'] = insta_url + notification['_links']['vnfLcmOpOcc'] = {} + notification['_links']['vnfLcmOpOcc']['href'] = vnflcm_url + vnf_info['notification'] = notification + vnf_lcm_op_occs.save() + self.rpc_api.send_notification(context, notification) + + vim_info = vnflcm_utils._get_vim(context, + vnf_instance.vim_connection_info) + + vim_connection_info = objects.VimConnectionInfo.obj_from_primitive( + vim_info, context) + + scale_name_list, grp_id = self._rollback_vnf_pre( + context, vnf_info, vnf_instance, + operation_params, vim_connection_info) + + self._rollback_vnf( + context, + vnf_info, + vnf_instance, + operation_params, + vim_connection_info, + scale_name_list, + grp_id) + + self._rollback_vnf_post( + context, + vnf_info, + vnf_instance, + operation_params, + vim_connection_info) diff --git a/tacker/vnfm/infra_drivers/kubernetes/kubernetes_driver.py b/tacker/vnfm/infra_drivers/kubernetes/kubernetes_driver.py index 8917c0d37..3d759c8f8 100644 --- a/tacker/vnfm/infra_drivers/kubernetes/kubernetes_driver.py +++ b/tacker/vnfm/infra_drivers/kubernetes/kubernetes_driver.py @@ -1379,3 +1379,12 @@ class Kubernetes(abstract_driver.VnfAbstractDriver, vim_connection_info, del_list): pass + + def get_rollback_ids(self, + plugin, + context, + vnf_dict, + aspect_id, + auth_attr, + region_name): + pass diff --git a/tacker/vnfm/infra_drivers/openstack/openstack.py b/tacker/vnfm/infra_drivers/openstack/openstack.py index d199a6bf6..6bea16f1f 100644 --- a/tacker/vnfm/infra_drivers/openstack/openstack.py +++ b/tacker/vnfm/infra_drivers/openstack/openstack.py @@ -275,6 +275,7 @@ class OpenStack(abstract_driver.VnfAbstractDriver, for name, value in nested_hot_dict.items(): vnf['attributes'].update({name: self._format_base_hot(value)}) + vnf['error_point'] = 4 # Create heat-stack with BaseHOT and parameters stack = self._create_stack_with_user_data( heatclient, vnf, base_hot_dict, @@ -1537,7 +1538,7 @@ class OpenStack(abstract_driver.VnfAbstractDriver, 'parameters': paramDict, 'existing': True} heatclient.update(vnf_info['instance_id'], **stack_update_param) - stack_param = jsonutils.loads(vnf_info['attributes']['stack_param']) + stack_param = yaml.safe_load(vnf_info['attributes']['stack_param']) stack_param.update(paramDict) vnf_info['attributes'].update({'stack_param': str(paramDict)}) @@ -1842,3 +1843,66 @@ class OpenStack(abstract_driver.VnfAbstractDriver, vnf_info['removeResources'] = remove_resources vnf_info['affinity_list'] = [] vnf_info['placement_constraint_list'] = [] + + @log.log + def get_rollback_ids(self, plugin, context, + vnf_dict, + aspect_id, + auth_attr, + region_name): + heatclient = hc.HeatClient(auth_attr, region_name) + grp = heatclient.resource_get(vnf_dict['instance_id'], + aspect_id + '_group') + res_list = [] + for rsc in heatclient.resource_get_list(grp.physical_resource_id): + scale_rsc = heatclient.resource_get(grp.physical_resource_id, + rsc.resource_name) + if 'COMPLETE' in scale_rsc.resource_status \ + and 'INIT_COMPLETE' != scale_rsc.resource_status: + res_list.append(scale_rsc) + res_list = sorted( + res_list, + key=lambda x: (x.creation_time, x.resource_name) + ) + LOG.debug("res_list %s", res_list) + heat_template = vnf_dict['attributes']['heat_template'] + group_name = aspect_id + '_group' + + heat_resource = yaml.safe_load(heat_template) + group_temp = heat_resource['resources'][group_name] + group_prop = group_temp['properties'] + min_size = group_prop['min_size'] + + cap_size = vnf_dict['res_num'] + + if cap_size < min_size: + cap_size = min_size + + reversed_res_list = res_list[:cap_size] + LOG.debug("reversed_res_list reverse %s", reversed_res_list) + + # List of physical_resource_id before Rollback + before_list = [] + # List of physical_resource_ids remaining after Rollback + after_list = [] + # List of resource_name before Rollback + before_rs_list = [] + # List of resource_names left after Rollback + after_rs_list = [] + for rsc in res_list: + before_list.append(rsc.physical_resource_id) + before_rs_list.append(rsc.resource_name) + for rsc in reversed_res_list: + after_list.append(rsc.physical_resource_id) + after_rs_list.append(rsc.resource_name) + + # Make a list of the physical_resource_id and r + # esource_name of the VMs that will actually be deleted + if 0 < cap_size: + return_list = list(set(before_list) - set(after_list)) + return_rs_list = list(set(before_rs_list) - set(after_rs_list)) + else: + return_list = before_list + return_rs_list = before_rs_list + + return return_list, return_rs_list, grp.physical_resource_id diff --git a/tacker/vnfm/infra_drivers/scale_driver.py b/tacker/vnfm/infra_drivers/scale_driver.py index 1bb34adc7..78589779a 100644 --- a/tacker/vnfm/infra_drivers/scale_driver.py +++ b/tacker/vnfm/infra_drivers/scale_driver.py @@ -112,3 +112,13 @@ class VnfScaleAbstractDriver(extensions.PluginInterface): vim_connection_info, del_list): pass + + @abc.abstractmethod + def get_rollback_ids(self, + plugin, + context, + vnf_dict, + aspect_id, + auth_attr, + region_name): + pass