diff --git a/.zuul.yaml b/.zuul.yaml index 180e23ab2..311daf7fa 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -121,6 +121,9 @@ $NEUTRON_DHCP_CONF: DEFAULT: enable_isolated_metadata: True + $CINDER_CONF: + lvmdriver-1: + image_volume_cache_enabled: False devstack_plugins: heat: https://opendev.org/openstack/heat networking-sfc: https://opendev.org/openstack/networking-sfc diff --git a/api-ref/source/v1/samples/vnflcm/change-ext-conn-request.json b/api-ref/source/v1/samples/vnflcm/change-ext-conn-request.json new file mode 100644 index 000000000..36e2a961e --- /dev/null +++ b/api-ref/source/v1/samples/vnflcm/change-ext-conn-request.json @@ -0,0 +1,69 @@ +{ + "extVirtualLinks": [ + { + "id": "ext-vl-uuid-VL1", + "resourceId": "neutron-network-uuid_VL1", + "extCps": [ + { + "cpdId": "CP1", + "cpConfig": [ + { + "cpProtocolData": [ + { + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [ + { + "type": "IPV4", + "numDynamicAddresses": 1, + "subnetId": "subnet-uuid" + } + ] + } + } + ] + } + ] + }, + { + "cpdId": "CP2", + "cpConfig": [ + { + "cpProtocolData": [ + { + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [ + { + "type": "IPV4", + "fixedAddresses": [ + "10.0.0.1" + ], + "subnetId": "subnet-uuid" + } + ] + } + } + ] + } + ] + } + ] + } + ], + "vimConnectionInfo": [ + { + "id": "vim-uuid", + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.v_2", + "vimConnectionId": "dummy-vimid", + "interfaceInfo": { + "key1": "value1", + "key2": "value2" + }, + "accessInfo": { + "key1": "value1", + "key2": "value2" + } + } + ] +} diff --git a/api-ref/source/v1/vnflcm.inc b/api-ref/source/v1/vnflcm.inc index 29cd1570e..ac5c5866b 100644 --- a/api-ref/source/v1/vnflcm.inc +++ b/api-ref/source/v1/vnflcm.inc @@ -674,6 +674,81 @@ Request Example .. literalinclude:: samples/vnflcm/modify-vnf-instance-request.json :language: javascript +Change External VNF Connectivity +================================ + +.. rest_method:: POST /vnflcm/v1/vnf_instances/{vnfInstanceId}/change_ext_conn + +The POST method changes the external connectivity of a VNF instance. + +This task resource represents the "Change external VNF connectivity" operation. +The client can use this resource to change the external connectivity of a VNF instance. + +Response Codes +-------------- + +.. rest_status_code:: success status.yaml + + - 202 + +.. rest_status_code:: error status.yaml + + - 400 + - 401 + - 403 + - 404 + - 409 + +Request Parameters +------------------ + +.. rest_parameters:: parameters_vnflcm.yaml + + - vnfInstanceId: vnf_instance_id + - extVirtualLinks: ext_virtual_links + - id: ext_virtual_links_id + - vimConnectionId: vim_connection_id + - resourceId: ext_virtual_links_resource_id + - extCps: ext_cps + - cpdId: cpd_id + - cpConfig: cp_config + - cpInstanceId: cp_instance_id + - linkPortId: link_port_id + - cpProtocolData: cp_protocol_data + - layerProtocol: layer_protocol + - ipOverEthernet: ip_over_ethernet + - macAddress: mac_address + - ipAddresses: ip_addresses + - type: ip_address_type + - fixedAddresses: fixed_addresses + - numDynamicAddresses: num_dynamic_addresses + - subnetId: subnet_id + - extLinkPorts: ext_link_ports + - id: ext_link_port_id + - resourceHandle: ext_link_port_resource_handle + - vimConnectionId: vim_connection_id + - resourceId: resource_handle_resource_id + - vimLevelResourceType: resource_handle_vim_level_resource_type + - vimConnectionInfo: vnf_instance_vim_connection_info + - id: vim_connection_info_id + - vimId: vim_connection_info_vim_id + - vimType: vim_connection_info_vim_type + - interfaceInfo: vim_connection_info_interface_info + - endpoint: vim_connection_info_interface_info_endpoint + - accessInfo: vim_connection_info_access_info + - username: vim_connection_info_access_info_username + - password: vim_connection_info_access_info_password + - region: vim_connection_info_access_info_region + - tenant: vim_connection_info_access_info_tenant + - additionalParams: vnf_instance_additional_params + + +Request Example +--------------- + +.. literalinclude:: samples/vnflcm/change-ext-conn-request.json + :language: javascript + Show VNF LCM operation occurrence ================================= diff --git a/tacker/api/schemas/vnf_lcm.py b/tacker/api/schemas/vnf_lcm.py index e7875854e..a74e495ed 100644 --- a/tacker/api/schemas/vnf_lcm.py +++ b/tacker/api/schemas/vnf_lcm.py @@ -274,3 +274,14 @@ scale = { 'required': ['type', 'aspectId'], 'additionalProperties': True, } + +change_ext_conn = { + 'type': 'object', + 'properties': { + 'extVirtualLinks': _extVirtualLinkData, + 'vimConnectionInfo': _vimConnectionInfo, + 'additionalParams': parameter_types.keyvalue_pairs, + }, + 'required': ['extVirtualLinks'], + 'additionalProperties': True, +} diff --git a/tacker/api/views/vnf_lcm.py b/tacker/api/views/vnf_lcm.py index fab34b39d..5739651db 100644 --- a/tacker/api/views/vnf_lcm.py +++ b/tacker/api/views/vnf_lcm.py @@ -60,6 +60,10 @@ class ViewBuilder(base.BaseViewBuilder): "heal": { "href": '/vnflcm/v1/vnf_instances/%s/heal' % vnf_instance.id + }, + "changeExtConn": { + "href": '/vnflcm/v1/vnf_instances/%s/change_ext_conn' + % vnf_instance.id } } diff --git a/tacker/api/vnflcm/v1/controller.py b/tacker/api/vnflcm/v1/controller.py index cc240757a..95f8a7a54 100644 --- a/tacker/api/vnflcm/v1/controller.py +++ b/tacker/api/vnflcm/v1/controller.py @@ -53,6 +53,7 @@ from tacker.extensions import vnfm from tacker import manager from tacker import objects from tacker.objects import fields +from tacker.objects.fields import ErrorPoint as EP from tacker.objects import vnf_lcm_op_occs as vnf_lcm_op_occs_obj from tacker.objects import vnf_lcm_subscriptions as subscription_obj from tacker.plugins.common import constants @@ -1577,6 +1578,49 @@ class VnfLcmController(wsgi.Controller): vnf_lcm_subscription, cast=False) return resp + @wsgi.response(http_client.ACCEPTED) + @wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN, + http_client.NOT_FOUND, http_client.CONFLICT)) + @validation.schema(vnf_lcm.change_ext_conn) + def change_ext_conn(self, request, id, body): + context = request.environ['tacker.context'] + context.can(vnf_lcm_policies.VNFLCM % 'change_ext_conn') + + vnf = self._get_vnf(context, id) + vnf_instance = self._get_vnf_instance(context, id) + if (vnf_instance.instantiation_state != + fields.VnfInstanceState.INSTANTIATED): + return self._make_problem_detail( + 'VNF is not instantiated', + 409, + title='VNF IS NOT INSTANTIATED') + vnf['before_error_point'] = EP.INITIAL + self._change_ext_conn(context, vnf_instance, vnf, body) + + def _change_ext_conn(self, context, vnf_instance, vnf, request_body): + req_body = utils.convert_camelcase_to_snakecase(request_body) + change_ext_conn_req = objects.ChangeExtConnRequest.obj_from_primitive( + req_body, context) + + # call notification process + if vnf['before_error_point'] == EP.INITIAL: + vnf_lcm_op_occs_id = self._notification_process( + context, + vnf_instance, + fields.LcmOccsOperationType.CHANGE_EXT_CONN, + change_ext_conn_req, + request_body) + else: + vnf_lcm_op_occs_id = vnf['vnf_lcm_op_occs_id'] + + # Call Conductor server. + self.rpc_api.change_ext_conn( + context, + vnf_instance, + vnf, + change_ext_conn_req, + vnf_lcm_op_occs_id) + def create_resource(): return wsgi.Resource(VnfLcmController()) diff --git a/tacker/api/vnflcm/v1/router.py b/tacker/api/vnflcm/v1/router.py index a6fafc98a..4f23c10f0 100644 --- a/tacker/api/vnflcm/v1/router.py +++ b/tacker/api/vnflcm/v1/router.py @@ -150,3 +150,8 @@ class VnflcmAPIRouter(wsgi.Router): methods = {"GET": "list_lcm_op_occs"} self._setup_route(mapper, "/vnf_lcm_op_occs", methods, controller, default_resource) + + # {apiRoot}/vnf_instances/{vnfInstanceId}/change_ext_conn resource + methods = {"POST": "change_ext_conn"} + self._setup_route(mapper, "/vnf_instances/{id}/change_ext_conn", + methods, controller, default_resource) diff --git a/tacker/common/exceptions.py b/tacker/common/exceptions.py index 5d14be80a..a145e3016 100644 --- a/tacker/common/exceptions.py +++ b/tacker/common/exceptions.py @@ -305,6 +305,16 @@ class VnfHealFailed(TackerException): message = _("Heal Vnf failed for vnf %(id)s, error: %(error)s") +class VnfChangeExtConnFailed(TackerException): + message = _("Change external connectivity failed " + "for vnf %(id)s, error: %(error)s") + + +class VnfChangeExtConnWaitFailed(TackerException): + message = _("Change external connectivity wait failed " + "for vnf %(id)s, error: %(error)s") + + class LockCreationFailed(TackerException): message = _('Unable to create lock. Coordination backend not started.') diff --git a/tacker/conductor/conductor_server.py b/tacker/conductor/conductor_server.py index f6d6fb796..a84e75fd1 100644 --- a/tacker/conductor/conductor_server.py +++ b/tacker/conductor/conductor_server.py @@ -59,6 +59,7 @@ from tacker.glance_store import store as glance_store from tacker import manager from tacker import objects from tacker.objects import fields +from tacker.objects.fields import ErrorPoint as EP from tacker.objects.vnf_package import VnfPackagesList from tacker.objects import vnfd as vnfd_db from tacker.objects import vnfd_attribute as vnfd_attribute_db @@ -109,7 +110,8 @@ _ACTIVE_STATUS = ('ACTIVE',) _PENDING_STATUS = ('PENDING_CREATE', 'PENDING_TERMINATE', 'PENDING_DELETE', - 'PENDING_HEAL') + 'PENDING_HEAL', + 'PENDING_CHANGE_EXT_CONN') _ERROR_STATUS = ('ERROR',) _ALL_STATUSES = _ACTIVE_STATUS + _INACTIVE_STATUS + _PENDING_STATUS + \ _ERROR_STATUS @@ -781,6 +783,30 @@ class Conductor(manager.Manager): format(error_msg)) raise exceptions.TackerException(message=error_msg) + @log.log + def _update_instantiated_vnf_info_change_ext_conn( + self, context, vnf_instance, change_ext_conn_req): + try: + vim_info = vnflcm_utils._get_vim(context, + vnf_instance.vim_connection_info) + vim_connection_info = \ + objects.VimConnectionInfo.obj_from_primitive( + vim_info, context) + + self.vnf_manager.invoke( + vim_connection_info.vim_type, 'post_change_ext_conn_vnf', + context=context, vnf_instance=vnf_instance, + vim_connection_info=vim_connection_info) + + vnflcm_utils._update_instantiated_vnf_info( + change_ext_conn_req, vnf_instance) + vnf_instance.instantiated_vnf_info.save() + except Exception as exp: + error_msg = \ + "Failed to update instantiation information for vnf {}: {}".\ + format(vnf_instance.id, encodeutils.exception_to_unicode(exp)) + raise exceptions.TackerException(message=error_msg) + @log.log def _add_additional_vnf_info(self, context, vnf_instance): '''this method adds misc info to 'vnf' table''' @@ -834,6 +860,104 @@ class Conductor(manager.Manager): {'zip': csar_path, 'folder': csar_zip_temp_path, 'uuid': vnf_pack.id}) + def _get_vnf_link_ports_by_vl(self, vnf_info, ext_vl_id, + resource_id): + results = [] + vnf_vl_resource_info = vnf_info.vnf_virtual_link_resource_info + for vnf_vl_res in vnf_vl_resource_info: + if ((vnf_vl_res.vnf_virtual_link_desc_id == ext_vl_id) and + (vnf_vl_res.network_resource.resource_id != resource_id)): + results.extend(vnf_vl_res.vnf_link_ports) + + return results + + def _get_vnf_link_ports_by_cp(self, vnf_info, cpd_id=None): + vnf_vl_resource_info = vnf_info.vnf_virtual_link_resource_info + vnfc_resource_info = vnf_info.vnfc_resource_info + + def _get_vnf_link_port(vnf_link_port_id): + for vnf_vl_res in vnf_vl_resource_info: + for vnf_link_port in vnf_vl_res.vnf_link_ports: + if vnf_link_port.id == vnf_link_port_id: + return vnf_link_port + + results = [] + for vnfc_resource in vnfc_resource_info: + for vnfc_cp_info in vnfc_resource.vnfc_cp_info: + if cpd_id == vnfc_cp_info.cpd_id: + results.append( + _get_vnf_link_port(vnfc_cp_info.vnf_link_port_id)) + + return results + + @grant_error_common + def _change_ext_conn_grant( + self, + context, + vnf_instance, + change_ext_conn_req, + vnf_lcm_op_occ_id): + if not self._get_grant_execute(): + return + + vnf_inf = vnf_instance.instantiated_vnf_info + + def _create_linkport_rd(linkport, cpd_id): + rh = linkport.resource_handle + rd = objects.ResourceDefinition() + rd.resource = objects.ResourceHandle() + rd.id = linkport.id + rd.type = constants.TYPE_LINKPORT + rd.resource_template_id = cpd_id + rd.resource.vim_connection_id = rh.vim_connection_id + rd.resource.resource_id = rh.resource_id + rd.resource.vim_level_resource_type = rh.vim_level_resource_type + return rd + + def _get_cpd_id(cp_instance_id): + vnfc_resource_info = vnf_inf.vnfc_resource_info + for vnfc_resource in vnfc_resource_info: + for vnfc_cp_info in vnfc_resource.vnfc_cp_info: + if cp_instance_id == vnfc_cp_info.id: + return vnfc_cp_info.cpd_id + + update_resources = dict() + # If network resource of the VirtualLink changed, get all LinkPort + # resource related to VirtualLink + for ext_vl in change_ext_conn_req.ext_virtual_links: + nw_changed_resources = self._get_vnf_link_ports_by_vl( + vnf_inf, ext_vl.id, ext_vl.resource_id) + LOG.debug('nw_changed_resources {}'.format(nw_changed_resources)) + if nw_changed_resources: + for resource in nw_changed_resources: + cpd_id = _get_cpd_id(resource.cp_instance_id) + update_resources[resource.resource_handle.resource_id] = \ + _create_linkport_rd(resource, cpd_id) + continue + # If network resource of the VirtualLink does not change, + # Searching vnfc_resource_info table by the cpd_id, if found, get + # LinkPort resource corresponding the CP. + # It does not check that the CP status updated or not. + for ext_cp in ext_vl.ext_cps: + cp_changed_resources = \ + self._get_vnf_link_ports_by_cp(vnf_inf, ext_cp.cpd_id) + LOG.debug('cp_changed_resources {}'.format( + cp_changed_resources)) + for resource in cp_changed_resources: + update_resources[resource.resource_handle.resource_id] = \ + _create_linkport_rd(resource, ext_cp.cpd_id) + + update_resources_list = list(update_resources.values()) + LOG.debug("Update Resources: %s", update_resources_list) + grant_request = self._make_grant_request( + context, + vnf_instance, + vnf_lcm_op_occ_id, + 'CHANGE_EXT_CONN', + False, + update_resources=update_resources_list) + return self._grant(context, grant_request) + def _grant(self, context, grant_request): LOG.info( "grant start grant_request[%s]" % @@ -855,7 +979,11 @@ class Conductor(manager.Manager): grant_obj.remove_resources): msg = "grant remove resource error" raise exceptions.ValidationError(detail=msg) - + if len( + grant_request.update_resources) != len( + grant_obj.update_resources): + msg = "grant update resource error" + raise exceptions.ValidationError(detail=msg) self._check_res_add_remove_rsc(context, grant_request, grant_obj) return grant_obj @@ -881,6 +1009,16 @@ class Conductor(manager.Manager): msg = "grant remove resource error" raise exceptions.ValidationError(detail=msg) + for update_resource in grant_request.update_resources: + match_flg = False + for rsc in grant_obj.update_resources: + if update_resource.id == rsc.resource_definition_id: + match_flg = True + break + if not match_flg: + msg = "grant update resource error" + raise exceptions.ValidationError(detail=msg) + @grant_error_common def _instantiate_grant(self, context, @@ -1307,6 +1445,7 @@ class Conductor(manager.Manager): is_automatic_invocation, add_resources=[], remove_resources=[], + update_resources=[], placement_constraints=[]): grant_request = objects.GrantRequest() grant_request.vnf_instance_id = vnf_instance.id @@ -1330,6 +1469,8 @@ class Conductor(manager.Manager): grant_request.add_resources = add_resources if remove_resources: grant_request.remove_resources = remove_resources + if update_resources: + grant_request.update_resources = update_resources if placement_constraints: grant_request.placement_constraints = placement_constraints @@ -1443,7 +1584,12 @@ class Conductor(manager.Manager): jsonutils.dumps(affected_resources_snake_case) changed_resource = objects.ResourceChanges.obj_from_primitive( resource_change_obj, context) + changed_ext_connectivity = \ + vnflcm_utils._get_changed_ext_connectivity( + old_vnf_instance=old_vnf_instance, + new_vnf_instance=vnf_instance) vnf_notif.resource_changes = changed_resource + vnf_notif.changed_ext_connectivity = changed_ext_connectivity vnf_notif.save() notification_data['affectedVnfcs'] = \ affected_resources.get('affectedVnfcs', []) @@ -1453,6 +1599,9 @@ class Conductor(manager.Manager): affected_resources.get('affectedVirtualStorages', []) notification_data['notificationStatus'] = \ fields.LcmOccsNotificationStatus.RESULT + notification_data['changedExtConnectivity'] = \ + utils.convert_snakecase_to_camelcase( + [i.to_dict() for i in changed_ext_connectivity]) if operation_state == \ fields.LcmOccsOperationState.FAILED_TEMP \ @@ -2035,6 +2184,113 @@ class Conductor(manager.Manager): self.vnflcm_driver.rollback_vnf(context, vnf_info, vnf_instance, operation_params) + @coordination.synchronized('{vnf_instance[id]}') + def change_ext_conn( + self, + context, + vnf_instance, + vnf_dict, + change_ext_conn_req, + vnf_lcm_op_occs_id): + """Perform change external VNF connectivity operation. + + This function will support changing external VNF connectivity + as defined in ETSI NFV SOL 002 and SOL 003, but now, you can + specify changing fixedAddresses or numDynamicAddresses in + ipAddresses attribute in extVirtualLinks. + + Note: + 1. Get grant from NFVO(if needed). + Request grant information is made from ExtVirtualLinkData + of ChangeExtConnRequest. If ExtVirtualLinkInfo is changed + from instantiated VNF, we inform VnfLinkPortInfo related + to that ExtVirtualLinkInfo. Also, we inform VnfLinkPortInfo + related to each individual VnfExtCpInfo. + 2. Call vnflcm_driver to change networks. + Invoke vnflcm_driver to perform change external VNF + connectivity. + 3. Update VNF information + Update InstantiatedVnfInfo as a post-processing. + + Args: + context (Context): context for security/db session. + vnf_instance (VnfInstance): Information object for VNF instance. + vnf_dict (dict): Container for error point indication. + change_ext_conn_req (ChangeExtConnRequest): + Request object of change external connectivity. + vnf_lcm_op_occs_id (uuid): self-explanatory :) + """ + if vnf_dict['before_error_point'] == EP.INITIAL: + self._change_ext_conn_grant( + context, + vnf_instance, + change_ext_conn_req, + vnf_lcm_op_occs_id) + + try: + old_vnf_instance = copy.deepcopy(vnf_instance) + # Update vnf_lcm_op_occs table and send notification "PROCESSING" + self._send_lcm_op_occ_notification( + context=context, + vnf_lcm_op_occs_id=vnf_lcm_op_occs_id, + old_vnf_instance=None, + vnf_instance=vnf_instance, + request_obj=change_ext_conn_req, + operation=fields.LcmOccsOperationType.CHANGE_EXT_CONN + ) + + vnf_dict['current_error_point'] = EP.NOTIFY_PROCESSING + if vnf_dict['before_error_point'] <= EP.NOTIFY_PROCESSING: + # update vnf status to PENDING_CHANGE_EXT_CONN + self._change_vnf_status(context, vnf_instance.id, + _ACTIVE_STATUS, 'PENDING_CHANGE_EXT_CONN') + + self.vnflcm_driver.change_ext_conn_vnf( + context, + vnf_instance, + vnf_dict, + change_ext_conn_req) + + vnf_dict['current_error_point'] = EP.NOTIFY_COMPLETED + self._update_instantiated_vnf_info_change_ext_conn( + context, vnf_instance, change_ext_conn_req) + # update vnf status to ACTIVE + self._update_vnf_attributes(context, vnf_instance, vnf_dict, + _PENDING_STATUS, _ACTIVE_STATUS) + # Update vnf_lcm_op_occs table and send notification "COMPLETED" + self._send_lcm_op_occ_notification( + context=context, + vnf_lcm_op_occs_id=vnf_lcm_op_occs_id, + old_vnf_instance=old_vnf_instance, + vnf_instance=vnf_instance, + request_obj=change_ext_conn_req, + operation=fields.LcmOccsOperationType.CHANGE_EXT_CONN, + operation_state=fields.LcmOccsOperationState.COMPLETED + ) + except Exception as e: + # update vnf_status to 'ERROR' and create event with 'ERROR' status + self._change_vnf_status(context, vnf_instance.id, + _ALL_STATUSES, constants.ERROR, str(e)) + + LOG.error('Failed to execute operation. error={}'.format(e)) + if vnf_dict['current_error_point'] in [EP.INTERNAL_PROCESSING, + EP.VNF_CONFIG_END]: + self._update_instantiated_vnf_info_change_ext_conn( + context, vnf_instance, change_ext_conn_req) + + # update vnf_lcm_op_occs and send notification "FAILED_TEMP" + self._send_lcm_op_occ_notification( + context=context, + vnf_lcm_op_occs_id=vnf_lcm_op_occs_id, + old_vnf_instance=old_vnf_instance, + vnf_instance=vnf_instance, + request_obj=change_ext_conn_req, + operation=fields.LcmOccsOperationType.CHANGE_EXT_CONN, + operation_state=fields.LcmOccsOperationState.FAILED_TEMP, + error=str(e), + error_point=vnf_dict['current_error_point'] + ) + 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 e64cb681c..70701389b 100644 --- a/tacker/conductor/conductorrpc/vnf_lcm_rpc.py +++ b/tacker/conductor/conductorrpc/vnf_lcm_rpc.py @@ -136,3 +136,25 @@ class VNFLcmRPCAPI(object): vnf_info=vnf_info, vnf_instance=vnf_instance, operation_params=operation_params) + + def change_ext_conn( + self, + context, + vnf_instance, + vnf_dict, + change_ext_conn_req, + vnf_lcm_op_occs_id, + 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, + 'change_ext_conn', + vnf_instance=vnf_instance, + vnf_dict=vnf_dict, + change_ext_conn_req=change_ext_conn_req, + vnf_lcm_op_occs_id=vnf_lcm_op_occs_id) diff --git a/tacker/extensions/vnfm.py b/tacker/extensions/vnfm.py index 69a3a8fd6..4cbdda1b4 100644 --- a/tacker/extensions/vnfm.py +++ b/tacker/extensions/vnfm.py @@ -87,6 +87,10 @@ class VNFHealWaitFailed(exceptions.TackerException): message = _('VNF Heal %(reason)s') +class VNFChangeExtConnWaitFailed(exceptions.TackerException): + message = _('VNF ChangeExtConn %(reason)s') + + class VNFDeleteFailed(exceptions.TackerException): message = _('%(reason)s') diff --git a/tacker/objects/__init__.py b/tacker/objects/__init__.py index 40c906178..6db053ef4 100644 --- a/tacker/objects/__init__.py +++ b/tacker/objects/__init__.py @@ -44,3 +44,4 @@ def register_all(): __import__('tacker.objects.grant') __import__('tacker.objects.grant_request') __import__('tacker.objects.vnfd_attribute') + __import__('tacker.objects.change_ext_conn_req') diff --git a/tacker/objects/change_ext_conn_req.py b/tacker/objects/change_ext_conn_req.py new file mode 100644 index 000000000..ad2cb876e --- /dev/null +++ b/tacker/objects/change_ext_conn_req.py @@ -0,0 +1,68 @@ +# 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. + +from oslo_log import log as logging + +from tacker import objects +from tacker.objects import base +from tacker.objects import fields + +LOG = logging.getLogger(__name__) + + +@base.TackerObjectRegistry.register +class ChangeExtConnRequest(base.TackerObject): + # Version 1.0: Initial version + VERSION = '1.0' + + fields = { + 'vim_connection_info': fields.ListOfObjectsField( + 'VimConnectionInfo', nullable=True, default=[]), + 'ext_virtual_links': fields.ListOfObjectsField( + 'ExtVirtualLinkData', nullable=False), + 'additional_params': fields.DictOfNullableField(nullable=True, + default={}) + } + + @classmethod + def obj_from_primitive(cls, primitive, context): + if 'tacker_object.name' in primitive: + obj_change_ext_conn_req = super( + ChangeExtConnRequest, cls).obj_from_primitive( + primitive, context) + else: + if 'vim_connection_info' in primitive.keys(): + obj_data = [objects.VimConnectionInfo._from_dict( + vim_conn) for vim_conn in primitive.get( + 'vim_connection_info', [])] + primitive.update({'vim_connection_info': obj_data}) + + if 'ext_virtual_links' in primitive.keys(): + obj_data = [objects.ExtVirtualLinkData.obj_from_primitive( + ext_vir_link, context) for ext_vir_link in primitive.get( + 'ext_virtual_links', [])] + primitive.update({'ext_virtual_links': obj_data}) + obj_change_ext_conn_req = ChangeExtConnRequest._from_dict( + primitive) + + return obj_change_ext_conn_req + + @classmethod + def _from_dict(cls, data_dict): + vim_connection_info = data_dict.get('vim_connection_info', []) + ext_virtual_links = data_dict.get('ext_virtual_links', []) + additional_params = data_dict.get('additional_params', {}) + + return cls( + vim_connection_info=vim_connection_info, + ext_virtual_links=ext_virtual_links, + additional_params=additional_params) diff --git a/tacker/objects/fields.py b/tacker/objects/fields.py index c1951e583..5c0aaa26f 100644 --- a/tacker/objects/fields.py +++ b/tacker/objects/fields.py @@ -233,8 +233,9 @@ class LcmOccsOperationType(BaseTackerEnum): TERMINATE = 'TERMINATE' HEAL = 'HEAL' SCALE = 'SCALE' + CHANGE_EXT_CONN = 'CHANGE_EXT_CONN' - ALL = (INSTANTIATE, TERMINATE, HEAL, SCALE) + ALL = (INSTANTIATE, TERMINATE, HEAL, SCALE, CHANGE_EXT_CONN) class LcmOccsNotificationStatus(BaseTackerEnum): diff --git a/tacker/objects/grant.py b/tacker/objects/grant.py index c62b3ef59..1501e0e7d 100644 --- a/tacker/objects/grant.py +++ b/tacker/objects/grant.py @@ -33,8 +33,12 @@ class Grant(base.TackerObject): 'GrantInfo', nullable=True, default=[]), 'remove_resources': fields.ListOfObjectsField( 'GrantInfo', nullable=True, default=[]), + 'update_resources': fields.ListOfObjectsField( + 'GrantInfo', nullable=True, default=[]), 'vim_assets': fields.ObjectField( - 'VimAssets', nullable=True) + 'VimAssets', nullable=True), + 'ext_virtual_links': fields.ListOfObjectsField( + 'ExtVirtualLinkData', nullable=True, default=[]), } @classmethod @@ -65,11 +69,20 @@ class Grant(base.TackerObject): remove_rsc) for remove_rsc in primitive.get( 'remove_resources', [])] primitive.update({'remove_resources': obj_data}) + if 'update_resources' in primitive.keys(): + obj_data = [GrantInfo._from_dict( + update_rsc) for update_rsc in primitive.get( + 'update_resources', [])] + primitive.update({'update_resources': obj_data}) if 'vim_assets' in primitive.keys(): obj_data = VimAssets.obj_from_primitive( primitive.get('vim_assets'), context) primitive.update({'vim_assets': obj_data}) - + if 'ext_virtual_links' in primitive.keys(): + obj_data = [objects.ExtVirtualLinkData.obj_from_primitive( + ext_vir_link, context) for ext_vir_link in primitive.get( + 'ext_virtual_links', [])] + primitive.update({'ext_virtual_links': obj_data}) obj_grant = Grant._from_dict(primitive) return obj_grant @@ -83,7 +96,9 @@ class Grant(base.TackerObject): zones = data_dict.get('zones', []) add_resources = data_dict.get('add_resources', []) remove_resources = data_dict.get('remove_resources', []) + update_resources = data_dict.get('update_resources', []) vim_assets = data_dict.get('vim_assets') + ext_virtual_links = data_dict.get('ext_virtual_links', []) obj = cls( id=id, @@ -93,7 +108,9 @@ class Grant(base.TackerObject): zones=zones, add_resources=add_resources, remove_resources=remove_resources, - vim_assets=vim_assets) + update_resources=update_resources, + vim_assets=vim_assets, + ext_virtual_links=ext_virtual_links) return obj diff --git a/tacker/objects/grant_request.py b/tacker/objects/grant_request.py index 088e7c13a..f3a69fcfd 100644 --- a/tacker/objects/grant_request.py +++ b/tacker/objects/grant_request.py @@ -35,6 +35,8 @@ class GrantRequest(base.TackerObject): 'ResourceDefinition', nullable=True, default=[]), 'remove_resources': fields.ListOfObjectsField( 'ResourceDefinition', nullable=True, default=[]), + 'update_resources': fields.ListOfObjectsField( + 'ResourceDefinition', nullable=True, default=[]), 'placement_constraints': fields.ListOfObjectsField( 'PlacementConstraint', nullable=True, default=[]), '_links': fields.ObjectField( @@ -57,11 +59,20 @@ class GrantRequest(base.TackerObject): remove_rsc) for remove_rsc in primitive.get( 'remove_resources', [])] primitive.update({'add_resources': obj_data}) + if 'update_resources' in primitive.keys(): + obj_data = [ResourceDefinition._from_dict( + update_rsc) for update_rsc in primitive.get( + 'update_resources', [])] + primitive.update({'update_resources': obj_data}) if 'placement_constraints' in primitive.keys(): obj_data = [PlacementConstraint._from_dict( place) for place in primitive.get( 'placement_constraints', [])] primitive.update({'add_resources': obj_data}) + if '_links' in primitive.keys(): + obj_data = Links._from_dict( + primitive.get('_links', {})) + primitive.update({'_links': obj_data}) obj_grant_req = GrantRequest._from_dict(primitive) return obj_grant_req @@ -76,6 +87,7 @@ class GrantRequest(base.TackerObject): is_automatic_invocation = data_dict.get('is_automatic_invocation') add_resources = data_dict.get('add_resources', []) remove_resources = data_dict.get('remove_resources', []) + update_resources = data_dict.get('update_resources', []) placement_constraints = data_dict.get('placement_constraints', []) links = data_dict.get('_links') @@ -88,6 +100,7 @@ class GrantRequest(base.TackerObject): is_automatic_invocation=is_automatic_invocation, add_resources=add_resources, remove_resources=remove_resources, + update_resources=update_resources, placement_constraints=placement_constraints, _links=links) return obj @@ -112,6 +125,12 @@ class GrantRequest(base.TackerObject): remove_resources_list.append(remove_resource.to_dict()) data.update({'remove_resources': remove_resources_list}) + if self.update_resources: + update_resources_list = [] + for update_resource in self.update_resources: + update_resources_list.append(update_resource.to_dict()) + + data.update({'update_resources': update_resources_list}) if self.placement_constraints: placement_constraints_list = [] for placement_constraint in self.placement_constraints: diff --git a/tacker/objects/vnf_lcm_op_occs.py b/tacker/objects/vnf_lcm_op_occs.py index 548501263..bb566b859 100644 --- a/tacker/objects/vnf_lcm_op_occs.py +++ b/tacker/objects/vnf_lcm_op_occs.py @@ -291,7 +291,8 @@ class VnfLcmOpOcc(base.TackerObject, base.TackerObjectDictCompat, changed_ext_conn = \ [objects.ExtVirtualLinkInfo.obj_from_primitive( chg_ext_conn, context) for chg_ext_conn in - db_vnf_lcm_op_occ['changed_ext_connectivity']] + jsonutils.loads( + db_vnf_lcm_op_occ['changed_ext_connectivity'])] vnf_lcm_op_occ_obj.changed_ext_connectivity = changed_ext_conn vnf_lcm_op_occ_obj._context = context diff --git a/tacker/plugins/common/constants.py b/tacker/plugins/common/constants.py index 3814a2d61..5c239915c 100644 --- a/tacker/plugins/common/constants.py +++ b/tacker/plugins/common/constants.py @@ -40,6 +40,7 @@ PENDING_SCALE_IN = "PENDING_SCALE_IN" PENDING_SCALE_OUT = "PENDING_SCALE_OUT" PENDING_HEAL = "PENDING_HEAL" PENDING_TERMINATE = "PENDING_TERMINATE" +PENDING_CHANGE_EXT_CONN = "PENDING_CHANGE_EXT_CONN" DEAD = "DEAD" ERROR = "ERROR" NACK = "NACK" diff --git a/tacker/policies/vnf_lcm.py b/tacker/policies/vnf_lcm.py index 5a8a8d307..83227a8bc 100644 --- a/tacker/policies/vnf_lcm.py +++ b/tacker/policies/vnf_lcm.py @@ -176,6 +176,18 @@ rules = [ } ] ), + policy.DocumentedRuleDefault( + name=VNFLCM % 'change_ext_conn', + check_str=base.RULE_ADMIN_OR_OWNER, + description="Change external VNF connectivity.", + operations=[ + { + 'method': 'POST', + 'path': + '/vnflcm/v1/vnf_instances/{vnfInstanceId}/change_ext_conn' + } + ] + ), ] diff --git a/tacker/releasenotes/notes/change_external_vnf_connectivity-444c580a01479f33.yaml b/tacker/releasenotes/notes/change_external_vnf_connectivity-444c580a01479f33.yaml new file mode 100644 index 000000000..b369c4a17 --- /dev/null +++ b/tacker/releasenotes/notes/change_external_vnf_connectivity-444c580a01479f33.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add REST APIs for Change External VNF Connectivity. + diff --git a/tacker/tests/etc/samples/etsi/nfv/test_inst_terminate_vnf_with_vnflcmnoop/Scripts/vnflcm_noop.py b/tacker/tests/etc/samples/etsi/nfv/test_inst_terminate_vnf_with_vnflcmnoop/Scripts/vnflcm_noop.py index ddf495c4b..bf75395b9 100644 --- a/tacker/tests/etc/samples/etsi/nfv/test_inst_terminate_vnf_with_vnflcmnoop/Scripts/vnflcm_noop.py +++ b/tacker/tests/etc/samples/etsi/nfv/test_inst_terminate_vnf_with_vnflcmnoop/Scripts/vnflcm_noop.py @@ -74,3 +74,17 @@ class VnflcmMgmtNoop(vnflcm_abstract_driver.VnflcmMgmtAbstractDriver): heal_vnf_request, grant, grant_request, **kwargs): pass + + @log.log + def change_external_connectivity_start( + self, context, vnf_instance, + change_ext_conn_request, grant, + grant_request, **kwargs): + pass + + @log.log + def change_external_connectivity_end( + self, context, vnf_instance, + change_ext_conn_request, grant, + grant_request, **kwargs): + pass diff --git a/tacker/tests/etc/samples/etsi/nfv/test_inst_terminate_vnf_with_vnflcmnoop/TOSCA-Metadata/TOSCA.meta b/tacker/tests/etc/samples/etsi/nfv/test_inst_terminate_vnf_with_vnflcmnoop/TOSCA-Metadata/TOSCA.meta index 6958095b0..d5e5012cc 100644 --- a/tacker/tests/etc/samples/etsi/nfv/test_inst_terminate_vnf_with_vnflcmnoop/TOSCA-Metadata/TOSCA.meta +++ b/tacker/tests/etc/samples/etsi/nfv/test_inst_terminate_vnf_with_vnflcmnoop/TOSCA-Metadata/TOSCA.meta @@ -9,4 +9,4 @@ Content-type: application/x-iso9066-image Name: Scripts/vnflcm_noop.py Content-Type: text/x-python Algorithm: SHA-256 -Hash: 63cfaf9963680ff864981d4db809c2ec175d78054157c0bcd43ac7a85973af10 +Hash: 950422e356c3eb6a7c92f424d975e2d3f295f480321bd91a97501045eddeab4a diff --git a/tacker/tests/functional/sol/vnflcm/base.py b/tacker/tests/functional/sol/vnflcm/base.py index 4b5ff4b12..8c3730a20 100644 --- a/tacker/tests/functional/sol/vnflcm/base.py +++ b/tacker/tests/functional/sol/vnflcm/base.py @@ -262,6 +262,9 @@ class BaseVnfLcmTest(base.BaseTackerTest): self.ext_link_ports = list() # Create external subnet in net1 self.ext_subnets = list() # Store ids for cleaning. + # Create external networks to change. + self.changed_ext_networks = list() + self.changed_ext_subnets = list() # Store ids for cleaning. networks = self.neutronclient().list_networks() for nw in networks.get('networks'): @@ -281,13 +284,26 @@ class BaseVnfLcmTest(base.BaseTackerTest): ext_mngd_net_id, _ = \ self._create_network("external_managed_internal_net") self.ext_mngd_networks.append(ext_mngd_net_id) + changed_ext_net_id, changed_ext_net_name = \ + self._create_network("changed_external_net") + self.changed_ext_networks.append(changed_ext_net_id) # Chack how many networks are created. networks = self.neutronclient().list_networks() for nw in networks.get('networks'): - if nw['name'] not in [ext_net_name]: + if nw['name'] not in [ext_net_name, changed_ext_net_name]: continue - self.ext_subnets.append(self._create_subnet(nw)) + + elif nw['name'] == ext_net_name: + self.ext_subnets.append( + self._create_subnet(nw, + cidr="22.22.1.0/24", + gateway="22.22.1.1")) + elif nw['name'] == changed_ext_net_name: + self.changed_ext_subnets.append( + self._create_subnet(nw, + cidr="22.22.2.0/24", + gateway="22.22.2.1")) @classmethod def _list_glance_image(cls, filter_name='cirros-0.4.0-x86_64-disk'): @@ -453,6 +469,16 @@ class BaseVnfLcmTest(base.BaseTackerTest): return resp, body + def _change_ext_conn_vnf_instance(self, vnf_instance_id, request_body): + url = os.path.join( + self.base_vnf_instances_url, + vnf_instance_id, + "change_ext_conn") + resp, body = self.http_client.do_request(url, "POST", + body=jsonutils.dumps(request_body)) + + return resp, body + def _rollback_op_occs(self, vnf_lcm_op_occs_id): rollback_url = os.path.join( self.base_vnf_lcm_op_occs_url, @@ -1140,18 +1166,18 @@ class BaseVnfLcmTest(base.BaseTackerTest): self.fail("Failed, create network=<%s>, %s" % (uniq_name, e)) - def _create_subnet(self, network): - cidr_prefix = "22.22.{}".format(str(len(self.ext_subnets))) + def _create_subnet(self, network, cidr, gateway): body = {'subnet': {'network_id': network['id'], 'name': "subnet-%s" % uuidutils.generate_uuid(), - 'cidr': "{}.0/24".format(cidr_prefix), + 'cidr': "{}".format(cidr), 'ip_version': 4, - 'gateway_ip': "{}.1".format(cidr_prefix), + 'gateway_ip': "{}".format(gateway), "enable_dhcp": True}} try: subnet = self.neutronclient().create_subnet(body=body)["subnet"] self.addCleanup(self._delete_subnet, subnet['id']) + print("Create subnet success, %s" % subnet['id'], flush=True) return subnet['id'] except Exception as e: self.fail("Failed, create subnet for net_id=<%s>, %s" % diff --git a/tacker/tests/functional/sol/vnflcm/fake_vnflcm.py b/tacker/tests/functional/sol/vnflcm/fake_vnflcm.py index cf0b1e33f..418edc9a2 100644 --- a/tacker/tests/functional/sol/vnflcm/fake_vnflcm.py +++ b/tacker/tests/functional/sol/vnflcm/fake_vnflcm.py @@ -40,7 +40,8 @@ class Subscription: "SCALE", "TERMINATE", "HEAL", - "MODIFY_INFO" + "MODIFY_INFO", + "CHANGE_EXT_CONN" ] }, "callbackUri": callback_uri @@ -373,3 +374,56 @@ class VnfInstances: "samplekey": "samplevalue" } } + + @staticmethod + def make_change_ext_conn_request_body( + tenant_id, + networks_id, + external_subnets_id): + + # set external subnet_id on vim. + ext_cps_vdu2_cp2 = { + "cpdId": "VDU2_CP2", + "cpConfig": [{ + "cpProtocolData": [{ + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [{ + "type": "IPV4", + "fixedAddresses": ["22.22.2.200"], + "subnetId": external_subnets_id[0] + }] + } + }] + }] + } + + ext_virtual_link_cp2 = { + "id": uuidsentinel.evl2_id, + "resourceId": networks_id[0], + "extCps": [ + ext_cps_vdu2_cp2 + ] + } + + data = { + "extVirtualLinks": [ + ext_virtual_link_cp2 + ], + "vimConnectionInfo": [{ + "id": uuidsentinel.vim_connection_id, + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.v_2", + "vimConnectionId": uuidsentinel.vim_connection_id, + "interfaceInfo": { + "endpoint": "http://127.0.0.1/identity" + }, + "accessInfo": { + "username": "nfv_user", + "region": "RegionOne", + "password": "devstack", + "tenant": tenant_id + } + }], + } + + return data diff --git a/tacker/tests/functional/sol/vnflcm/test_vnf_instance_with_user_data.py b/tacker/tests/functional/sol/vnflcm/test_vnf_instance_with_user_data.py index b6b771694..6c64644ff 100644 --- a/tacker/tests/functional/sol/vnflcm/test_vnf_instance_with_user_data.py +++ b/tacker/tests/functional/sol/vnflcm/test_vnf_instance_with_user_data.py @@ -1596,6 +1596,48 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest): expected_usage_state, vnf_package_info['usageState']) + def _get_fixed_ips(self, vnf_instance_id, request_body): + res_name = None + for extvirlink in request_body['extVirtualLinks']: + if 'extCps' not in extvirlink: + continue + for extcps in extvirlink['extCps']: + if 'cpdId' in extcps: + if res_name is None: + res_name = list() + res_name.append(extcps['cpdId']) + break + if res_name is None: + return [] + + stack = self._get_heat_stack(vnf_instance_id) + stack_id = stack.id + + stack_resource = self._get_heat_resource_list(stack_id, nested_depth=2) + + releations = dict() + for elmt in stack_resource: + if elmt.resource_type != 'OS::Neutron::Port': + continue + if elmt.resource_name not in res_name: + continue + releations[elmt.parent_resource] = elmt.resource_name + + details = list() + for (parent_name, resource_name) in releations.items(): + for elmt in stack_resource: + if parent_name != elmt.resource_name: + continue + detail_stack = self._get_heat_resource( + elmt.physical_resource_id, resource_name) + details.append(detail_stack) + + ans_list = list() + for detail in details: + ans_list.append(detail.attributes['fixed_ips']) + + return ans_list + def _assert_occ_show(self, resp, op_occs_info): self.assertEqual(200, resp.status_code) @@ -1684,3 +1726,146 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest): self.assertIsNotNone(_links.get('rollback').get('href')) if _links.get('grant') is not None: self.assertIsNotNone(_links.get('grant').get('href')) + + def test_inst_chgextconn_term(self): + """Test basic life cycle operations with sample VNFD. + + In this test case, we do following steps. + - Create subscription. + - Show subscriptions. + - Get list of subscriptions. + - Create VNF package. + - Upload VNF package. + - Create VNF instance. + - Instantiate VNF. + - Get list of VNF instances. + - Get VNF informations. + - Change External VNF Connectivity. + - Get opOccs informations. + - Terminate VNF + - Delete VNF + - Delete subscription + """ + # Create subscription and register it. + request_body = fake_vnflcm.Subscription.make_create_request_body( + 'http://localhost:{}{}'.format( + vnflcm_base.FAKE_SERVER_MANAGER.SERVER_PORT, + os.path.join(vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName))) + resp, response_body = self._register_subscription(request_body) + self.assertEqual(201, resp.status_code) + self.assert_http_header_location_for_subscription(resp.headers) + subscription_id = response_body.get('id') + self.addCleanup( + self._delete_subscription, + subscription_id) + + # Subscription show + resp, body = self._wait_show_subscription(subscription_id) + self.assert_subscription_show(resp, body) + + # Subscription list + resp, _ = self._list_subscription() + self.assertEqual(200, resp.status_code) + + # Pre Setting: Create vnf package. + sample_name = 'functional5' + csar_package_path = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + "../../../etc/samples/etsi/nfv", + sample_name)) + tempname, _ = vnflcm_base._create_csar_with_unique_vnfd_id( + csar_package_path) + # upload vnf package + vnf_package_id, vnfd_id = vnflcm_base._create_and_upload_vnf_package( + self.tacker_client, user_defined_data={ + "key": sample_name}, temp_csar_path=tempname) + + # Post Setting: Reserve deleting vnf package. + self.addCleanup(vnflcm_base._delete_vnf_package, self.tacker_client, + vnf_package_id) + + # Create vnf instance + resp, vnf_instance = self._create_vnf_instance_from_body( + fake_vnflcm.VnfInstances.make_create_request_body(vnfd_id)) + vnf_instance_id = vnf_instance['id'] + self._wait_lcm_done(vnf_instance_id=vnf_instance_id) + self.assert_create_vnf(resp, vnf_instance, vnf_package_id) + vnf_instance_name = vnf_instance['vnfInstanceName'] + self.addCleanup(self._delete_vnf_instance, vnf_instance_id) + + # Instantiate vnf instance + request_body = fake_vnflcm.VnfInstances.make_inst_request_body( + self.vim['tenant_id'], self.ext_networks, self.ext_mngd_networks, + self.ext_link_ports, self.ext_subnets) + resp, _ = self._instantiate_vnf_instance(vnf_instance_id, request_body) + self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id) + self.assert_instantiate_vnf(resp, vnf_instance_id, vnf_package_id) + + # List vnf instance + filter_expr = { + 'filter': "(eq,id,{});(eq,vnfInstanceName,{})".format( + vnf_instance_id, vnf_instance_name)} + resp, vnf_instances = self._list_vnf_instance(params=filter_expr) + self.assertEqual(200, resp.status_code) + self.assertEqual(1, len(vnf_instances)) + + # Show vnf instance + resp, vnf_instance = self._show_vnf_instance(vnf_instance_id) + self.assertEqual(200, resp.status_code) + + # Change external connectivity + request_body = \ + fake_vnflcm.VnfInstances.make_change_ext_conn_request_body( + self.vim['tenant_id'], self.changed_ext_networks, + self.changed_ext_subnets) + before_fixed_ips = self._get_fixed_ips(vnf_instance_id, request_body) + resp, _ = \ + self._change_ext_conn_vnf_instance(vnf_instance_id, request_body) + self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id) + after_fixed_ips = self._get_fixed_ips(vnf_instance_id, request_body) + self.assertNotEqual(before_fixed_ips, after_fixed_ips) + + callback_url = os.path.join( + vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName) + notify_mock_responses = vnflcm_base.FAKE_SERVER_MANAGER.get_history( + callback_url) + vnflcm_base.FAKE_SERVER_MANAGER.clear_history( + callback_url) + vnflcm_op_occ_id = notify_mock_responses[0].request_body.get( + 'vnfLcmOpOccId') + self.assertIsNotNone(vnflcm_op_occ_id) + vnflcm_base.FAKE_SERVER_MANAGER.clear_history(callback_url) + + # occ-show(chgextconn) + resp, op_occs_info = self._show_op_occs(vnflcm_op_occ_id) + self._assert_occ_show(resp, op_occs_info) + + # Terminate VNF + stack = self._get_heat_stack(vnf_instance_id) + resources_list = self._get_heat_resource_list(stack.id) + resource_name_list = [r.resource_name for r in resources_list] + glance_image_id_list = \ + self._get_glance_image_list_from_stack_resource( + stack.id, resource_name_list) + + terminate_req_body = fake_vnflcm.VnfInstances.make_term_request_body() + resp, _ = self._terminate_vnf_instance( + vnf_instance_id, terminate_req_body) + self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id) + self.assert_terminate_vnf(resp, vnf_instance_id, stack.id, + resource_name_list, glance_image_id_list, vnf_package_id) + + # Delete VNF + resp, _ = self._delete_vnf_instance(vnf_instance_id) + self._wait_lcm_done(vnf_instance_id=vnf_instance_id) + self.assert_delete_vnf(resp, vnf_instance_id, vnf_package_id) + + # Subscription delete + resp, response_body = self._delete_subscription(subscription_id) + self.assertEqual(204, resp.status_code) + + resp, _ = self._show_subscription(subscription_id) + self.assertEqual(404, resp.status_code) diff --git a/tacker/tests/functional/sol_separated_nfvo/vnflcm/fake_grant.py b/tacker/tests/functional/sol_separated_nfvo/vnflcm/fake_grant.py index b390446b0..2f812c760 100644 --- a/tacker/tests/functional/sol_separated_nfvo/vnflcm/fake_grant.py +++ b/tacker/tests/functional/sol_separated_nfvo/vnflcm/fake_grant.py @@ -76,6 +76,17 @@ class Grant: return res_remove_resources + @staticmethod + def _make_update_resources(req_update_resources): + res_update_resources = [] + for req_update_resource in req_update_resources: + res_update_resource = { + "resourceDefinitionId": req_update_resource['id'] + } + res_update_resources.append(res_update_resource) + + return res_update_resources + @staticmethod def _make_vim_assets(image_id, flavour_id="1"): # set m1.tiny="1" for flavour_id @@ -203,3 +214,17 @@ class Grant: request_body['removeResources']) return res + + @staticmethod + def make_change_ext_conn_response_body(request_body, tenant_id, image_id): + request_body = Grant._convert_body_to_dict(request_body) + res = Grant._make_response_template(request_body) + res["vimConnections"] = Grant._make_vim_connection_info(tenant_id) + res["zones"] = Grant.ZONES + if 'updateResources' in request_body.keys(): + res["updateResources"] = Grant._make_update_resources( + request_body['updateResources']) + res["vimAssets"] = Grant._make_vim_assets(image_id) + res["additionalParams"] = Grant.ADDITIONAL_PARAMS + + return res diff --git a/tacker/tests/functional/sol_separated_nfvo/vnflcm/test_vnf_instance_with_user_data_nfvo_separate.py b/tacker/tests/functional/sol_separated_nfvo/vnflcm/test_vnf_instance_with_user_data_nfvo_separate.py index 9767d83b0..b639a7fc8 100644 --- a/tacker/tests/functional/sol_separated_nfvo/vnflcm/test_vnf_instance_with_user_data_nfvo_separate.py +++ b/tacker/tests/functional/sol_separated_nfvo/vnflcm/test_vnf_instance_with_user_data_nfvo_separate.py @@ -23,7 +23,7 @@ from tacker.tests.functional.sol_separated_nfvo.vnflcm import fake_vnfpkgm class VnfLcmWithNfvoSeparator(vnflcm_base.BaseVnfLcmTest): - def _register_vnf_package_mock_response(self): + def _register_vnf_package_mock_response(self, package_dir="functional6"): """Prepare VNF package for test. Register VNF package response to fake NFVO server and Cleanups. @@ -32,7 +32,7 @@ class VnfLcmWithNfvoSeparator(vnflcm_base.BaseVnfLcmTest): Response: VNF Package information """ # Pre Setting: Create vnf package. - sample_name = "functional6" + sample_name = package_dir csar_package_path = os.path.abspath( os.path.join( os.path.dirname(__file__), @@ -92,6 +92,152 @@ class VnfLcmWithNfvoSeparator(vnflcm_base.BaseVnfLcmTest): return vnf_package_info + def test_inst_chgextconn_term(self): + """Test basic life cycle operations with sample VNFD with UserData. + + In this test case, we do following steps. + - Create subscription. + - Create VNF instance. + - Instantiate VNF. + - List VNF instances. + - Show VNF instance. + - Change External VNF Connectivity. + - Get opOccs information. + - Terminate VNF. + - Delete VNF. + - Delete subscription. + - Show subscription. + """ + vnf_package_info = self._register_vnf_package_mock_response( + package_dir="functional5") + glance_image = self._list_glance_image()[0] + + # Create subscription and register it. + request_body = fake_vnflcm.Subscription.make_create_request_body( + 'http://localhost:{}{}'.format( + vnflcm_base.FAKE_SERVER_MANAGER.SERVER_PORT, + os.path.join( + vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName))) + resp, response_body = self._register_subscription(request_body) + self.assertEqual(201, resp.status_code) + self.assert_http_header_location_for_subscription(resp.headers) + subscription_id = response_body.get('id') + self.addCleanup(self._delete_subscription, subscription_id) + + # Create vnf instance + resp, vnf_instance = self._create_vnf_instance_from_body( + fake_vnflcm.VnfInstances.make_create_request_body( + vnf_package_info['vnfdId'])) + vnf_instance_id = vnf_instance.get('id') + self._wait_lcm_done(vnf_instance_id=vnf_instance_id) + self._assert_create_vnf(resp, vnf_instance) + vnf_instance_name = vnf_instance['vnfInstanceName'] + self.addCleanup(self._delete_vnf_instance, vnf_instance_id) + + # Set Fake server response for Grant-Req(Instantiate) + vnflcm_base.FAKE_SERVER_MANAGER.set_callback('POST', + fake_grant.Grant.GRANT_REQ_PATH, status_code=201, + callback=lambda req_headers, + req_body: fake_grant.Grant.make_inst_response_body(req_body, + self.vim['tenant_id'], glance_image.id)) + + # Instantiate vnf instance + request_body = fake_vnflcm.VnfInstances.make_inst_request_body( + self.vim['tenant_id'], self.ext_networks, self.ext_mngd_networks, + self.ext_link_ports, self.ext_subnets) + resp, _ = self._instantiate_vnf_instance(vnf_instance_id, request_body) + self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id) + self._assert_instantiate_vnf(resp, vnf_instance_id) + + # List vnf instances + filter_expr = { + 'filter': "(eq,id,{});(eq,vnfInstanceName,{})".format( + vnf_instance_id, vnf_instance_name)} + resp, vnf_instances = self._list_vnf_instance(params=filter_expr) + self.assertEqual(200, resp.status_code) + self.assertEqual(1, len(vnf_instances)) + + # Show vnf instance + resp, vnf_instance = self._show_vnf_instance(vnf_instance_id) + self.assertEqual(200, resp.status_code) + + # Set Fake server response for Grant-Req(Chnage-ext-conn) + vnflcm_base.FAKE_SERVER_MANAGER.set_callback( + 'POST', + fake_grant.Grant.GRANT_REQ_PATH, + status_code=201, + callback=lambda req_headers, + req_body: fake_grant.Grant.make_change_ext_conn_response_body( + req_body, + self.vim['tenant_id'], + glance_image.id)) + + # Change external connectivity + request_body = \ + fake_vnflcm.VnfInstances.make_change_ext_conn_request_body( + self.vim['tenant_id'], self.changed_ext_networks, + self.changed_ext_subnets) + before_fixed_ips = self._get_fixed_ips(vnf_instance_id, request_body) + resp, _ = \ + self._change_ext_conn_vnf_instance(vnf_instance_id, request_body) + self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id) + after_fixed_ips = self._get_fixed_ips(vnf_instance_id, request_body) + self.assertNotEqual(before_fixed_ips, after_fixed_ips) + vnflcm_base.FAKE_SERVER_MANAGER.clear_history( + fake_grant.Grant.GRANT_REQ_PATH) + + # get vnflcm_op_occ_id + callback_url = os.path.join( + vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName) + notify_mock_responses = vnflcm_base.FAKE_SERVER_MANAGER.get_history( + callback_url) + vnflcm_base.FAKE_SERVER_MANAGER.clear_history( + callback_url) + + vnflcm_op_occ_id = notify_mock_responses[0].request_body.get( + 'vnfLcmOpOccId') + self.assertIsNotNone(vnflcm_op_occ_id) + + # occ-show(chgextconn) + resp, op_occs_info = self._show_op_occs(vnflcm_op_occ_id) + self._assert_occ_show(resp, op_occs_info) + + # Set Fake server response for Grant-Req(Terminate) + vnflcm_base.FAKE_SERVER_MANAGER.set_callback('POST', + fake_grant.Grant.GRANT_REQ_PATH, status_code=201, + callback=lambda req_headers, + req_body: fake_grant.Grant.make_term_response_body(req_body)) + + # Get stack informations to terminate. + stack = self._get_heat_stack(vnf_instance_id) + resources_list = self._get_heat_resource_list(stack.id) + resource_name_list = [r.resource_name for r in resources_list] + glance_image_id_list = self._get_glance_image_list_from_stack_resource( + stack.id, resource_name_list) + + # Terminate VNF + terminate_req_body = fake_vnflcm.VnfInstances.make_term_request_body() + resp, _ = self._terminate_vnf_instance(vnf_instance_id, + terminate_req_body) + self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id) + self._assert_terminate_vnf(resp, vnf_instance_id, stack.id, + resource_name_list, glance_image_id_list) + + # Delete VNF + resp, _ = self._delete_vnf_instance(vnf_instance_id) + self._wait_lcm_done(vnf_instance_id=vnf_instance_id) + self.assert_delete_vnf(resp, vnf_instance_id) + + # Delete Subscription + resp, response_body = self._delete_subscription(subscription_id) + self.assertEqual(204, resp.status_code) + + # Check subscription was deleted + resp, show_body = self._show_subscription(subscription_id) + self.assertEqual(404, resp.status_code) + def test_inst_heal_term(self): """Test basic life cycle operations with sample VNFD with UserData. @@ -315,3 +461,64 @@ class VnfLcmWithNfvoSeparator(vnflcm_base.BaseVnfLcmTest): self.assertEqual( '{} {}'.format(expected_auth_type, expected_token_value), actual_auth) + + def _assert_occ_show(self, resp, op_occs_info): + self.assertEqual(200, resp.status_code) + + # Only check required parameters. + self.assertIsNotNone(op_occs_info.get('id')) + self.assertIsNotNone(op_occs_info.get('operationState')) + self.assertIsNotNone(op_occs_info.get('stateEnteredTime')) + self.assertIsNotNone(op_occs_info.get('vnfInstanceId')) + self.assertIsNotNone(op_occs_info.get('operation')) + self.assertIsNotNone(op_occs_info.get('isAutomaticInvocation')) + self.assertIsNotNone(op_occs_info.get('isCancelPending')) + + _links = op_occs_info.get('_links') + self.assertIsNotNone(_links.get('self')) + self.assertIsNotNone(_links.get('self').get('href')) + self.assertIsNotNone(_links.get('vnfInstance')) + self.assertIsNotNone(_links.get('vnfInstance').get('href')) + self.assertIsNotNone(_links.get('grant')) + self.assertIsNotNone(_links.get('grant').get('href')) + + def _get_fixed_ips(self, vnf_instance_id, request_body): + res_name = None + for extvirlink in request_body['extVirtualLinks']: + if 'extCps' not in extvirlink: + continue + for extcps in extvirlink['extCps']: + if 'cpdId' in extcps: + if res_name is None: + res_name = list() + res_name.append(extcps['cpdId']) + break + self.assertTrue(res_name) + + stack = self._get_heat_stack(vnf_instance_id) + stack_id = stack.id + + stack_resource = self._get_heat_resource_list(stack_id, nested_depth=2) + + releations = dict() + for elmt in stack_resource: + if elmt.resource_type != 'OS::Neutron::Port': + continue + if elmt.resource_name not in res_name: + continue + releations[elmt.parent_resource] = elmt.resource_name + + details = list() + for (parent_name, resource_name) in releations.items(): + for elmt in stack_resource: + if parent_name != elmt.resource_name: + continue + detail_stack = self._get_heat_resource( + elmt.physical_resource_id, resource_name) + details.append(detail_stack) + + ans_list = list() + for detail in details: + ans_list.append(detail.attributes['fixed_ips']) + + return ans_list diff --git a/tacker/tests/unit/conductor/fakes.py b/tacker/tests/unit/conductor/fakes.py index d16839fc7..580983592 100644 --- a/tacker/tests/unit/conductor/fakes.py +++ b/tacker/tests/unit/conductor/fakes.py @@ -27,6 +27,7 @@ import zipfile from oslo_config import cfg +from tacker.common import utils as common_utils from tacker.db.db_sqlalchemy import models from tacker.objects import scale_vnf_request from tacker.tests import utils @@ -301,3 +302,250 @@ def scale_request(type, number_of_steps): scale_request = scale_vnf_request.ScaleVnfRequest(**scale_request_data) return scale_request + + +def get_instantiated_vnf_info(): + vnf_info = objects.vnf_instance.VnfInstance() + + def _get_instantiated_vnf_info_data(): + return { + "flavour_id": "simple", + "vnf_instance_id": uuidsentinel.vnf_instance_id, + "vnf_virtual_link_resource_info": + _get_virtual_link_resource_info(), + "vnfc_resource_info": _get_vnfc_resource_info(), + } + + def _get_virtual_link_resource_info(): + return [{ + "id": uuidsentinel.vnf_vl_resource_1, + "vnf_virtual_link_desc_id": uuidsentinel.ext_vl_1, + "network_resource": { + "vim_connection_id": None, + "resource_id": uuidsentinel.ext_vl_resource_1, + "vim_level_resource_type": "OS::Neutron::Net", + "deleted": False, + }, + "vnf_link_ports": [ + { + "id": uuidsentinel.vnf_link_port_1, + "resource_handle": { + "vim_connection_id": uuidsentinel.vim_connection_id, + "resource_id": uuidsentinel.vnf_vl1_link_port_1, + "vim_level_resource_type": "OS::Neutron::Port", + "deleted": False, + }, + "cp_instance_id": uuidsentinel.cp_instance_1, + "deleted": False, + }, + { + "id": uuidsentinel.vnf_link_port_2, + "resource_handle": { + "vim_connection_id": uuidsentinel.vim_connection_id, + "resource_id": uuidsentinel.vnf_vl1_link_port_2, + "vim_level_resource_type": "OS::Neutron::Port", + "deleted": False, + }, + "cp_instance_id": uuidsentinel.cp_instance_2, + "deleted": False, + }, + { + "id": uuidsentinel.vnf_link_port_5, + "resource_handle": { + "vim_connection_id": uuidsentinel.vim_connection_id, + "resource_id": uuidsentinel.vnf_vl1_link_port_5, + "vim_level_resource_type": "OS::Neutron::Port", + "deleted": False, + }, + "cp_instance_id": uuidsentinel.cp_instance_2, + "deleted": False, + }, + ], + }, + { + "id": uuidsentinel.vnf_vl_resource_2, + "vnf_virtual_link_desc_id": uuidsentinel.ext_vl_2, + "network_resource": { + "vim_connection_id": None, + "resource_id": uuidsentinel.ext_vl_resource_2, + "vim_level_resource_type": "OS::Neutron::Net", + "deleted": False, + }, + "vnf_link_ports": [ + { + "id": uuidsentinel.vnf_link_port_3, + "resource_handle": { + "vim_connection_id": uuidsentinel.vim_connection_id, + "resource_id": uuidsentinel.vnf_vl2_link_port_1, + "vim_level_resource_type": "OS::Neutron::Port", + "deleted": False, + }, + "cp_instance_id": uuidsentinel.cp_instance_3, + "deleted": False, + }, + { + "id": uuidsentinel.vnf_link_port_4, + "resource_handle": { + "vim_connection_id": uuidsentinel.vim_connection_id, + "resource_id": uuidsentinel.vnf_vl2_link_port_2, + "vim_level_resource_type": "OS::Neutron::Port", + "deleted": False, + }, + "cp_instance_id": uuidsentinel.cp_instance_4, + "deleted": False, + }, + { + "id": uuidsentinel.vnf_link_port_5, + "resource_handle": { + "vim_connection_id": uuidsentinel.vim_connection_id, + "resource_id": uuidsentinel.vnf_vl2_link_port_3, + "vim_level_resource_type": "OS::Neutron::Port", + "deleted": False, + }, + "cp_instance_id": uuidsentinel.cp_instance_5, + "deleted": False, + } + ] + }] + + def _get_vnfc_resource_info(): + return [{ + "id": uuidsentinel.vnfc_resource_1, + "vdu_id": "VDU1", + "compute_resource": { + "vim_connection_id": uuidsentinel.vim_connection_id, + "resource_id": uuidsentinel.uuid, + "vim_level_resource_type": "OS::Nova::Server", + "deleted": False, + }, + "storage_resource_ids": [], + "vnfc_cp_info": [{ + "id": uuidsentinel.cp_instance_1, + "cpd_id": "CP1", + "vnf_ext_cp_id": None, + "cp_protocol_info": [], + "vnf_link_port_id": uuidsentinel.vnf_link_port_1, + }, { + "id": uuidsentinel.cp_instance_2, + "cpd_id": "CP2", + "vnf_ext_cp_id": None, + "cp_protocol_info": [], + "vnf_link_port_id": uuidsentinel.vnf_link_port_2, + }], + "metadata": {}, + }, { + "id": uuidsentinel.vnfc_resource_2, + "vdu_id": "VDU2", + "compute_resource": { + "vim_connection_id": uuidsentinel.vim_connection_id, + "resource_id": uuidsentinel.uuid, + "vim_level_resource_type": "OS::Nova::Server", + "deleted": False, + }, + "storage_resource_ids": [], + "vnfc_cp_info": [{ + "id": uuidsentinel.cp_instance_3, + "cpd_id": "CP3", + "vnf_ext_cp_id": None, + "cp_protocol_info": [], + "vnf_link_port_id": uuidsentinel.vnf_link_port_3, + }, { + "id": uuidsentinel.cp_instance_4, + "cpd_id": "CP4", + "vnf_ext_cp_id": None, + "cp_protocol_info": [], + "vnf_link_port_id": uuidsentinel.vnf_link_port_4, + }, { + "id": uuidsentinel.cp_instance_5, + "cpd_id": "CP5", + "vnf_ext_cp_id": None, + "cp_protocol_info": [], + "vnf_link_port_id": uuidsentinel.vnf_link_port_5, + }], + "metadata": {}, + }] + + vnf_info.instantiated_vnf_info = \ + objects.InstantiatedVnfInfo.obj_from_primitive( + _get_instantiated_vnf_info_data(), None + ) + return vnf_info.instantiated_vnf_info + + +def get_change_ext_conn_request(): + change_ext_conn_req_body = { + "extVirtualLinks": [{ + "id": uuidsentinel.ext_vl_1, + "vimConnectionId": uuidsentinel.vim_connection_id, + "resourceId": uuidsentinel.ext_vl_resource_3, + "extCps": [{ + "cpdId": 'CP1', + "cpConfig": [{ + "cpInstanceId": uuidsentinel.uuid, + "cpProtocolData": [{ + "layerProtocol": 'IP_OVER_ETHERNET', + "ipOverEthernet": { + "ipAddresses": [{ + "type": "IPV4", + "fixedAddresses": ["10.0.0.1"], + "subnetId": uuidsentinel.uuid, + }] + } + }] + }] + }]}, { + "id": uuidsentinel.ext_vl_2, + "vimConnectionId": uuidsentinel.vim_connection_id, + "resourceId": uuidsentinel.ext_vl_resource_2, + "extCps": [{ + "cpdId": 'CP3', + "cpConfig": [{ + "cpInstanceId": uuidsentinel.uuid, + "cpProtocolData": [{ + "layerProtocol": 'IP_OVER_ETHERNET', + "ipOverEthernet": { + "ipAddresses": [{ + "type": "IPV4", + "fixedAddresses": ["10.0.0.2"], + "subnetId": uuidsentinel.uuid, + }] + } + }] + }]}, { + "cpdId": 'CP5', + "cpConfig": [{ + "cpInstanceId": uuidsentinel.uuid, + "cpProtocolData": [{ + "layerProtocol": 'IP_OVER_ETHERNET', + "ipOverEthernet": { + "ipAddresses": [{ + "type": "IPV4", + "numDynamicAddresses": 1, + "subnetId": uuidsentinel.uuid, + }] + } + }] + }] + }] + }], + "vimConnectionInfo": [{ + "id": uuidsentinel.vim_connection_id, + "vimId": uuidsentinel.uuid, + "vimType": 'openstack', + "interfaceInfo": {"key1": 'value1', "key2": 'value2'}, + "accessInfo": {"key1": 'value1', "key2": 'value2'}, + }], + } + + return change_ext_conn_req_body + + +def get_change_ext_conn_request_obj(): + """Return ChangeExtConnRequest Object + + obj_from_primitive() needs snake_case dictionary + """ + body = common_utils.convert_camelcase_to_snakecase( + get_change_ext_conn_request()) + return objects.ChangeExtConnRequest.obj_from_primitive( + body, None) diff --git a/tacker/tests/unit/conductor/test_conductor_server.py b/tacker/tests/unit/conductor/test_conductor_server.py index daa7ec576..eb7696ab4 100644 --- a/tacker/tests/unit/conductor/test_conductor_server.py +++ b/tacker/tests/unit/conductor/test_conductor_server.py @@ -3019,3 +3019,435 @@ class TestConductor(SqlTestCase, unit_base.FixturedTestCase): vnf_data["software_version"] = vnf_data.pop("vnf_software_version") vnf_data["descriptor_version"] = vnf_data.pop("vnfd_version") return vnf_data + + @mock.patch('tacker.conductor.conductor_server.Conductor.' + '_update_instantiated_vnf_info_change_ext_conn') + @mock.patch('tacker.conductor.conductor_server.Conductor.' + '_update_vnf_attributes') + @mock.patch('tacker.conductor.conductor_server.Conductor.' + '_change_vnf_status') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '.send_notification') + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch.object(conductor_server.Conductor, "_get_grant_execute") + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + def test_change_ext_conn( + self, + mock_vnf_by_id, + mock_exec, + mock_get_lock, + mock_save, + mock_send_notification, + mock_change_vnf_status, + mock_update_vnf_attributes, + mock_update_instantiated_vnf_info_change_ext_conn): + lcm_op_occs_data = fakes.get_lcm_op_occs_data() + vnf_lcm_op_occs_id = uuidsentinel.vnf_lcm_op_occs_id + mock_vnf_by_id.return_value = objects.VnfLcmOpOcc( + context=self.context, **lcm_op_occs_data) + vnf_package_vnfd = self._create_and_upload_vnf_package() + vnf_instance_data = fake_obj.get_vnf_instance_data( + vnf_package_vnfd.vnfd_id) + vnf_instance = objects.VnfInstance(context=self.context, + **vnf_instance_data) + + vnf_instance.create() + vnf_instance.instantiation_state = fields.VnfInstanceState.INSTANTIATED + vnf_instance.save() + vnf_instance.instantiated_vnf_info = fakes.get_instantiated_vnf_info() + vnf_dict = {"before_error_point": 0} + change_ext_conn_req = fakes.get_change_ext_conn_request_obj() + + # Test condition settings. + mock_exec.return_value = False + + self.conductor.change_ext_conn( + self.context, + vnf_instance, + vnf_dict, + change_ext_conn_req, + vnf_lcm_op_occs_id) + mock_change_vnf_status.assert_called_with(self.context, + mock.ANY, (constants.ACTIVE,), + constants.PENDING_CHANGE_EXT_CONN) + mock_update_vnf_attributes.assert_called_with(self.context, + mock.ANY, mock.ANY, mock.ANY, (constants.ACTIVE,)) + self.assertEqual( + mock_send_notification.call_args[0][1].get('operationState'), + 'PROCESSING') + + @mock.patch('tacker.conductor.conductor_server.Conductor.' + '_update_instantiated_vnf_info_change_ext_conn') + @mock.patch('tacker.conductor.conductor_server.Conductor.' + '_update_vnf_attributes') + @mock.patch('tacker.conductor.conductor_server.Conductor.' + '_change_vnf_status') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '.send_notification') + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + @mock.patch.object(conductor_server.Conductor, "_get_grant_execute") + @mock.patch.object(test_nfvo_client.GrantRequest, "grants") + def test_change_ext_conn_grant( + self, + mock_grants, + mock_exec, + mock_vnf_by_id, + mock_get_lock, + mock_save, + mock_send_notification, + mock_change_vnf_status, + mock_update_vnf_attributes, + mock_update_instantiated_vnf_info_change_ext_conn): + cfg.CONF.set_override( + 'base_url', + 'http://127.0.0.1:9990/grant/v1/grants', + group='connect_grant') + lcm_op_occs_data = fakes.get_lcm_op_occs_data() + vnf_lcm_op_occs_id = uuidsentinel.vnf_lcm_op_occs_id + mock_vnf_by_id.return_value = objects.VnfLcmOpOcc( + context=self.context, **lcm_op_occs_data) + vnf_package_vnfd = self._create_and_upload_vnf_package() + vnf_instance_data = fake_obj.get_vnf_instance_data( + vnf_package_vnfd.vnfd_id) + vnf_instance = objects.VnfInstance(context=self.context, + **vnf_instance_data) + vnf_instance.create() + vnf_instance.instantiation_state = fields.VnfInstanceState.INSTANTIATED + vnf_instance.save() + vnf_instance.instantiated_vnf_info = fakes.get_instantiated_vnf_info() + vnf_dict = {"before_error_point": 0} + change_ext_conn_req = fakes.get_change_ext_conn_request_obj() + vnf_virtual_link = ( + vnf_instance.instantiated_vnf_info.vnf_virtual_link_resource_info) + + # Test condition settings. + mock_exec.return_value = True + res_grant = dict() + res_grant['id'] = uuidsentinel.grant_id + res_grant['vnfInstanceId'] = vnf_instance.id + res_grant['vnfLcmOpOccId'] = vnf_lcm_op_occs_id + res_grant['updateResources'] = [ + { + 'resourceDefinitionId': + vnf_virtual_link[0].vnf_link_ports[0].id, + }, + { + 'resourceDefinitionId': + vnf_virtual_link[0].vnf_link_ports[1].id, + }, + { + 'resourceDefinitionId': + vnf_virtual_link[1].vnf_link_ports[0].id, + }, + { + 'resourceDefinitionId': + vnf_virtual_link[1].vnf_link_ports[2].id, + }, + ] + mock_grants.return_value = MockResponse(json_data=res_grant) + + self.conductor.change_ext_conn( + self.context, + vnf_instance, + vnf_dict, + change_ext_conn_req, + vnf_lcm_op_occs_id) + mock_change_vnf_status.assert_called_with(self.context, + mock.ANY, (constants.ACTIVE,), + constants.PENDING_CHANGE_EXT_CONN) + mock_update_vnf_attributes.assert_called_with(self.context, + mock.ANY, mock.ANY, mock.ANY, (constants.ACTIVE,)) + self.assertEqual( + mock_send_notification.call_args[0][1].get('operationState'), + 'PROCESSING') + + @mock.patch('tacker.conductor.conductor_server.Conductor.' + '_update_instantiated_vnf_info_change_ext_conn') + @mock.patch('tacker.conductor.conductor_server.Conductor.' + '_update_vnf_attributes') + @mock.patch('tacker.conductor.conductor_server.Conductor.' + '_change_vnf_status') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '.send_notification') + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch.object(conductor_server.Conductor, "_get_grant_execute") + @mock.patch.object(test_nfvo_client.GrantRequest, "grants") + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + def test_change_ext_conn_grant_exception_http_error( + self, + mock_vnf_by_id, + mock_grants, + mock_exec, + mock_get_lock, + mock_save, + mock_send_notification, + mock_change_vnf_status, + mock_update_vnf_attributes, + mock_update_instantiated_vnf_info_change_ext_conn): + lcm_op_occs_data = fakes.get_lcm_op_occs_data() + vnf_lcm_op_occs_id = uuidsentinel.vnf_lcm_op_occs_id + mock_vnf_by_id.return_value = objects.VnfLcmOpOcc( + context=self.context, **lcm_op_occs_data) + vnf_package_vnfd = self._create_and_upload_vnf_package() + vnf_instance_data = fake_obj.get_vnf_instance_data( + vnf_package_vnfd.vnfd_id) + vnf_instance = objects.VnfInstance(context=self.context, + **vnf_instance_data) + vnf_instance.create() + vnf_instance.instantiation_state = fields.VnfInstanceState.INSTANTIATED + vnf_instance.save() + vnf_instance.instantiated_vnf_info = fakes.get_instantiated_vnf_info() + vnf_dict = {"before_error_point": 0} + change_ext_conn_req = fakes.get_change_ext_conn_request_obj() + + # Test condition settings. + mock_exec.return_value = True + mock_grants.side_effect = ( + requests.exceptions.HTTPError("MockException")) + + self.assertRaises(requests.exceptions.HTTPError, + self.conductor.change_ext_conn, + self.context, vnf_instance, vnf_dict, + change_ext_conn_req, vnf_lcm_op_occs_id) + mock_change_vnf_status.assert_not_called() + mock_update_instantiated_vnf_info_change_ext_conn.assert_not_called() + self.assertEqual( + mock_send_notification.call_args[0][1].get('operationState'), + 'ROLLED_BACK') + + @mock.patch('tacker.conductor.conductor_server.Conductor.' + '_update_instantiated_vnf_info_change_ext_conn') + @mock.patch('tacker.conductor.conductor_server.Conductor.' + '_update_vnf_attributes') + @mock.patch('tacker.conductor.conductor_server.Conductor.' + '_change_vnf_status') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '.send_notification') + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch.object(conductor_server.Conductor, "_get_grant_execute") + @mock.patch.object(test_nfvo_client.GrantRequest, "grants") + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + def test_change_ext_conn_grant_exception_validation_error( + self, + mock_vnf_by_id, + mock_grants, + mock_exec, + mock_get_lock, + mock_save, + mock_send_notification, + mock_change_vnf_status, + mock_update_vnf_attributes, + mock_update_instantiated_vnf_info_change_ext_conn): + lcm_op_occs_data = fakes.get_lcm_op_occs_data() + vnf_lcm_op_occs_id = uuidsentinel.vnf_lcm_op_occs_id + mock_vnf_by_id.return_value = objects.VnfLcmOpOcc( + context=self.context, **lcm_op_occs_data) + vnf_package_vnfd = self._create_and_upload_vnf_package() + vnf_instance_data = fake_obj.get_vnf_instance_data( + vnf_package_vnfd.vnfd_id) + vnf_instance = objects.VnfInstance(context=self.context, + **vnf_instance_data) + vnf_instance.create() + vnf_instance.instantiation_state = fields.VnfInstanceState.INSTANTIATED + vnf_instance.save() + vnf_instance.instantiated_vnf_info = fakes.get_instantiated_vnf_info() + vnf_dict = {"before_error_point": 0} + change_ext_conn_req = fakes.get_change_ext_conn_request_obj() + + # Test condition settings. + mock_exec.return_value = True + res_upd_resource = [] + resource = { + 'resourceDefinitionId': uuidsentinel.rsc_dummy, + 'vimConnectionId': '1ffcd358-bee3-4bfb-bc3a-920db09f5da5', + } + res_upd_resource.append(resource) + res_grant = dict() + res_grant['id'] = uuidsentinel.grant_id + res_grant['vnfInstanceId'] = vnf_instance.id + res_grant['vnfLcmOpOccId'] = vnf_lcm_op_occs_id + res_grant['updateResources'] = [] + res_grant['updateResources'].extend(res_upd_resource) + mock_grants.return_value = MockResponse(json_data=res_grant) + + self.assertRaises(exceptions.ValidationError, + self.conductor.change_ext_conn, + self.context, vnf_instance, vnf_dict, + change_ext_conn_req, vnf_lcm_op_occs_id) + mock_change_vnf_status.assert_not_called() + mock_update_vnf_attributes.assert_not_called() + mock_update_instantiated_vnf_info_change_ext_conn.assert_not_called() + self.assertEqual( + mock_send_notification.call_args[0][1].get('operationState'), + 'ROLLED_BACK') + + @mock.patch('tacker.conductor.conductor_server.Conductor.' + '_change_vnf_status') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._update_vnf_attributes') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._update_instantiated_vnf_info_change_ext_conn') + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch.object(objects.LccnSubscriptionRequest, + 'vnf_lcm_subscriptions_get') + @mock.patch('tacker.vnflcm.utils._get_vnfd_dict') + @mock.patch('tacker.vnflcm.utils._convert_desired_capacity') + @mock.patch('tacker.conductor.conductor_server.LOG') + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + def test_change_ext_conn_failed_with_exception( + self, + mock_vnf_by_id, + mock_log, + mock_des, + mock_vnfd_dict, + mock_vnf_lcm_subscriptions_get, + mock_get_lock, + mock_save, + mock_update_vnf_info_change_ext_conn, + mock_update_vnf_attributes, + mock_change_vnf_status): + lcm_op_occs_data = fakes.get_lcm_op_occs_data() + mock_vnf_by_id.return_value = ( + objects.VnfLcmOpOcc(context=self.context, + **lcm_op_occs_data)) + + vnf_package_vnfd = self._create_and_upload_vnf_package() + vnf_instance_data = fake_obj.get_vnf_instance_data( + vnf_package_vnfd.vnfd_id) + vnf_instance = objects.VnfInstance(context=self.context, + **vnf_instance_data) + vnf_instance.create() + change_ext_conn_req = fakes.get_change_ext_conn_request_obj() + vnf_lcm_op_occs_id = uuidsentinel.vnf_lcm_op_occs_id + vnf_dict = {"before_error_point": 0, + "current_error_point": 6} + m_vnf_lcm_subscriptions = ( + [mock.MagicMock(**fakes.get_vnf_lcm_subscriptions())]) + mock_vnf_lcm_subscriptions_get.return_value = ( + m_vnf_lcm_subscriptions) + mock_update_vnf_info_change_ext_conn.side_effect = Exception + self.conductor.change_ext_conn( + self.context, + vnf_instance, + vnf_dict, + change_ext_conn_req, + vnf_lcm_op_occs_id) + mock_change_vnf_status.assert_called_with(self.context, + mock.ANY, mock.ANY, constants.ERROR, mock.ANY) + self.vnflcm_driver.change_ext_conn_vnf.assert_called_once_with( + self.context, vnf_instance, vnf_dict, change_ext_conn_req) + mock_update_vnf_info_change_ext_conn.assert_called_once() + + @mock.patch('tacker.conductor.conductor_server.Conductor' + '.send_notification') + @mock.patch('tacker.conductor.conductor_server.Conductor.' + '_change_vnf_status') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._update_vnf_attributes') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._update_instantiated_vnf_info_change_ext_conn') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._change_ext_conn_grant') + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch('tacker.vnflcm.utils._get_vnfd_dict') + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + def test_change_ext_conn_retry_error_point_1( + self, + mock_vnf_by_id, + mock_vnfd_dict, + mock_get_lock, + mock_save, + mock_change_ext_conn_grant, + mock_update_vnf_info_change_ext_conn, + mock_update_vnf_attributes, + mock_change_vnf_status, + mock_send_notification): + lcm_op_occs_data = fakes.get_lcm_op_occs_data() + vnf_lcm_op_occs_id = uuidsentinel.vnf_lcm_op_occs_id + mock_vnf_by_id.return_value = objects.VnfLcmOpOcc( + context=self.context, **lcm_op_occs_data) + vnf_package_vnfd = self._create_and_upload_vnf_package() + vnf_instance_data = fake_obj.get_vnf_instance_data( + vnf_package_vnfd.vnfd_id) + vnf_instance = objects.VnfInstance(context=self.context, + **vnf_instance_data) + vnf_instance.create() + vnf_instance.instantiation_state = fields.VnfInstanceState.INSTANTIATED + vnf_instance.save() + vnf_instance.instantiated_vnf_info = fakes.get_instantiated_vnf_info() + vnf_dict = {"before_error_point": 1} + change_ext_conn_req = fakes.get_change_ext_conn_request_obj() + + self.conductor.change_ext_conn( + self.context, + vnf_instance, + vnf_dict, + change_ext_conn_req, + vnf_lcm_op_occs_id) + self.vnflcm_driver.change_ext_conn_vnf.assert_called_once_with( + self.context, vnf_instance, vnf_dict, change_ext_conn_req) + mock_change_vnf_status.assert_called_with(self.context, + mock.ANY, (constants.ACTIVE,), + constants.PENDING_CHANGE_EXT_CONN) + self.assertEqual(mock_change_ext_conn_grant.call_count, 0) + mock_update_vnf_attributes.assert_called_once() + + @mock.patch('tacker.conductor.conductor_server.Conductor.' + 'send_notification') + @mock.patch('tacker.conductor.conductor_server.Conductor.' + '_change_vnf_status') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._update_vnf_attributes') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._update_instantiated_vnf_info_change_ext_conn') + @mock.patch('tacker.conductor.conductor_server.Conductor' + '._change_ext_conn_grant') + @mock.patch.object(objects.VnfLcmOpOcc, "save") + @mock.patch.object(coordination.Coordinator, 'get_lock') + @mock.patch('tacker.vnflcm.utils._get_vnfd_dict') + @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") + def test_change_ext_conn_retry_error_point_7( + self, + mock_vnf_by_id, + mock_vnfd_dict, + mock_get_lock, + mock_save, + mock_change_ext_conn_grant, + mock_update_vnf_info_change_ext_conn, + mock_update_vnf_attributes, + mock_change_vnf_status, + mock_send_notification): + lcm_op_occs_data = fakes.get_lcm_op_occs_data() + vnf_lcm_op_occs_id = uuidsentinel.vnf_lcm_op_occs_id + mock_vnf_by_id.return_value = objects.VnfLcmOpOcc( + context=self.context, **lcm_op_occs_data) + vnf_package_vnfd = self._create_and_upload_vnf_package() + vnf_instance_data = fake_obj.get_vnf_instance_data( + vnf_package_vnfd.vnfd_id) + vnf_instance = objects.VnfInstance(context=self.context, + **vnf_instance_data) + vnf_instance.create() + vnf_instance.instantiation_state = fields.VnfInstanceState.INSTANTIATED + vnf_instance.save() + vnf_instance.instantiated_vnf_info = fakes.get_instantiated_vnf_info() + vnf_dict = {"before_error_point": 7} + change_ext_conn_req = fakes.get_change_ext_conn_request_obj() + + self.conductor.change_ext_conn( + self.context, + vnf_instance, + vnf_dict, + change_ext_conn_req, + vnf_lcm_op_occs_id) + self.vnflcm_driver.change_ext_conn_vnf.assert_called_once_with( + self.context, vnf_instance, vnf_dict, change_ext_conn_req) + mock_change_vnf_status.assert_not_called() + self.assertEqual(mock_change_ext_conn_grant.call_count, 0) + mock_update_vnf_attributes.assert_called_once() diff --git a/tacker/tests/unit/objects/test_change_ext_conn_req.py b/tacker/tests/unit/objects/test_change_ext_conn_req.py new file mode 100644 index 000000000..d0d5233eb --- /dev/null +++ b/tacker/tests/unit/objects/test_change_ext_conn_req.py @@ -0,0 +1,178 @@ +# 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 + +from tacker import context +from tacker import objects +from tacker.tests.unit import base +from tacker.tests import uuidsentinel + + +class ChangeExtConnRequestTestCase(base.TestCase): + + def setUp(self): + super(ChangeExtConnRequestTestCase, self).setUp() + self.context = context.get_admin_context() + + def _get_change_ext_conn_request(self): + ext_vl_info = [{ + "id": "external_network", + "vim_connection_id": "6b0ff598-60d6-49b4-a907-a1111de52d92", + "resource_id": "dc67ee99-e963-44e2-a152-f0fb492eae76", + "ext_cps": [{ + "cpd_id": "CP1", + "cp_config": [{ + "cp_protocol_data": [{ + "ip_over_ethernet": { + "mac_address": "fa:16:3e:0d:6f:71"}, + "layer_protocol": "IP_OVER_ETHERNET"}]}]}, { + "cpd_id": "CP2", + "cp_config": [{ + "cp_protocol_data": [{ + "ip_over_ethernet": { + "ipaddresses": [{ + "type": "IPV4", + "fixed_addresses": [ + "10.0.0.1"], + "subnet_id": + "55f0fb3c-6a70-11eb-9439-0242ac130002"}]}, + "layer_protocol": "IP_OVER_ETHERNET"}]}]}, { + "cpd_id": "CP3", + "cp_config": [{ + "cp_protocol_data": [{ + "ip_over_ethernet": { + "ipaddresses": [{ + "type": "IPV4", + "num_dynamic_addresses": 1, + "subnet_id": + "3a7a37fc-6a92-11eb-9439-0242ac130002"}]}, + "layer_protocol": "IP_OVER_ETHERNET"}]}]}, { + "cpd_id": "CP4", + "cp_config": [{ + "cp_protocol_data": [{ + "layer_protocol": "IP_OVER_ETHERNET"}], + "link_port_id": + "413f4e46-21cf-41b1-be0f-de8d23f76cfe"}]}], + "ext_link_ports": [{ + "id": "413f4e46-21cf-41b1-be0f-de8d23f76cfe", + "resource_handle": { + "resource_id": "67f7e772-0d31-4087-bf4c-2576fadcbdb7", + "vim_connection_id": + "6b0ff598-60d6-49b4-a907-a1111de52d92", + "vim_level_resource_type": "LINKPORT"}}], }] + + vim_connection_info = [{ + "id": "6b0ff598-60d6-49b4-a907-a1111de52d92", + "vim_id": uuidsentinel.vim_id, + "vim_type": "ETSINFV.OPENSTACK_KEYSTONE.v_2", + "interface_info": { + "endpoint": "endpoint_value"}, + "access_info": { + "username": "username_value", + "password": "password_value", + "region": "region_value", + "tenant": "tenant_value"}}] + + change_ext_conn_data = { + 'ext_virtual_links': ext_vl_info, + 'vim_connection_info': vim_connection_info, + 'additional_params': {'key1': 'value1'}} + + return change_ext_conn_data + + def test_obj_from_primitive(self): + change_ext_conn_data = self._get_change_ext_conn_request() + change_ext_conn_req = objects.ChangeExtConnRequest.obj_from_primitive( + copy.deepcopy(change_ext_conn_data), self.context) + self._check_change_ext_conn_req(change_ext_conn_req, + change_ext_conn_data) + + def _check_change_ext_conn_req(self, obj, data): + + def _check_vim_connection_info(_obj, _data): + self.assertEqual(len(_obj), len(_data)) + for obj, data in zip(_obj, _data): + self.assertIsInstance(obj, objects.VimConnectionInfo) + + def _check_external_virtual_links(_obj, _data): + self.assertEqual(len(_obj), len(_data)) + for obj, data in zip(_obj, _data): + self.assertIsInstance(obj, objects.ExtVirtualLinkData) + self.assertEqual(obj.id, data.get('id')) + self.assertEqual(obj.vim_connection_id, + data.get('vim_connection_id')) + self.assertEqual(obj.resource_id, + data.get('resource_id')) + _check_ext_cps(obj.ext_cps, + data.get('ext_cps', [])) + _check_ext_link_ports(obj.ext_link_ports, + data.get('ext_link_ports', [])) + + def _check_ext_cps(_obj, _data): + self.assertEqual(len(_obj), len(_data)) + for obj, data in zip(_obj, _data): + self.assertIsInstance(obj, objects.VnfExtCpData) + self.assertEqual(obj.cpd_id, data.get('cpd_id')) + _check_ext_cp_config(obj.cp_config, + data.get('cp_config', [])) + + def _check_ext_cp_config(_obj, _data): + self.assertEqual(len(_obj), len(_data)) + for obj, data in zip(_obj, _data): + self.assertIsInstance(obj, objects.VnfExtCpConfig) + self.assertEqual(obj.cp_instance_id, + data.get('cp_instance_id')) + self.assertEqual(obj.link_port_id, + data.get('link_port_id')) + _check_cp_protocol_data(obj.cp_protocol_data, + data.get('cp_protocol_data', [])) + + def _check_cp_protocol_data(_obj, _data): + self.assertEqual(len(_obj), len(_data)) + for obj, data in zip(_obj, _data): + self.assertIsInstance(obj, objects.CpProtocolData) + self.assertEqual(obj.layer_protocol, + data.get('layer_protocol')) + if obj.ip_over_ethernet or data.get('ip_over_ethernet', None): + _check_ip_over_ethernet(obj.ip_over_ethernet, + data.get('ip_over_ethernet', None)) + + def _check_ip_over_ethernet(obj, data): + self.assertIsInstance(obj, objects.IpOverEthernetAddressData) + self.assertEqual(obj.mac_address, data.get('mac_address')) + _check_ip_addresses(obj.ip_addresses, + data.get('ip_addressest', [])) + + def _check_ip_addresses(_obj, _data): + self.assertEqual(len(_obj), len(_data)) + for obj, data in zip(_obj, _data): + self.assertIsInstance(obj, objects.IpAddressReq) + self.assertEqual(obj.type, data.get('type')) + self.assertEqual(obj.subnet_id, data.get('subnet_id')) + self.assertEqual(obj.num_dynamic_addresses, + data.get('num_dynamic_addresses')) + + def _check_ext_link_ports(_obj, _data): + self.assertEqual(len(_obj), len(_data)) + for obj, data in zip(_obj, _data): + self.assertIsInstance(obj, objects.ExtLinkPortData) + self.assertEqual(obj.id, data.get('id')) + self.assertIsInstance(obj.resource_handle, + objects.ResourceHandle) + + self.assertIsInstance(obj, objects.ChangeExtConnRequest) + _check_external_virtual_links(obj.ext_virtual_links, + data.get('ext_virtual_links')) + _check_vim_connection_info(obj.vim_connection_info, + data.get('vim_connection_info')) diff --git a/tacker/tests/unit/objects/test_grant.py b/tacker/tests/unit/objects/test_grant.py new file mode 100644 index 000000000..25d556b25 --- /dev/null +++ b/tacker/tests/unit/objects/test_grant.py @@ -0,0 +1,251 @@ +# 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 + +from tacker import context +from tacker import objects +from tacker.tests.unit import base +from tacker.tests import uuidsentinel + + +class GrantTestCase(base.TestCase): + + def setUp(self): + super(GrantTestCase, self).setUp() + self.context = context.get_admin_context() + + def _get_grant_data(self): + vim_connection_info = [{ + "id": "6b0ff598-60d6-49b4-a907-a1111de52d92", + "vim_id": uuidsentinel.vim_id, + "vim_type": "ETSINFV.OPENSTACK_KEYSTONE.v_2", + "interface_info": { + "endpoint": "endpoint_value"}, + "access_info": { + "username": "username_value", + "password": "password_value", + "region": "region_value", + "tenant": "tenant_value"}}] + + grant_info = [{ + "resource_definition_id": + "ff150558-6f45-11eb-9439-0242ac130002"}, { + "resource_definition_id": + "030df048-6f46-11eb-9439-0242ac130002"}] + + vim_compute_resource_flavour = [{ + "vim_connection_id": "6b0ff598-60d6-49b4-a907-a1111de52d92", + "vnfd_virtual_compute_desc_id": + "a813a1ea-6f47-11eb-9439-0242ac130002", + "vim_flavour_id": "c924aa12-b69d-41ee-91dd-7a26d92e6147"}] + + vim_software_image = [{ + "vim_connection_id": "6b0ff598-60d6-49b4-a907-a1111de52d92", + "vnfd_software_image_id": "49597d31-0aad-4c46-865d-ed7e60b3edc7", + "vim_software_image_id": "93738ce0-3ddb-4c13-9900-62b757561edd"}] + + ext_vl_info = [{ + "id": "external_network", + "vim_connection_id": "6b0ff598-60d6-49b4-a907-a1111de52d92", + "resource_id": "dc67ee99-e963-44e2-a152-f0fb492eae76", + "ext_cps": [{ + "cpd_id": "CP1", + "cp_config": [{ + "cp_protocol_data": [{ + "ip_over_ethernet": { + "mac_address": "fa:16:3e:0d:6f:71"}, + "layer_protocol": "IP_OVER_ETHERNET"}]}]}, { + "cpd_id": "CP2", + "cp_config": [{ + "cp_protocol_data": [{ + "ip_over_ethernet": { + "ipaddresses": [{ + "type": "IPV4", + "fixed_addresses": [ + "10.0.0.1"], + "subnet_id": + "55f0fb3c-6a70-11eb-9439-0242ac130002"}]}, + "layer_protocol": "IP_OVER_ETHERNET"}]}]}, { + "cpd_id": "CP3", + "cp_config": [{ + "cp_protocol_data": [{ + "ip_over_ethernet": { + "ipaddresses": [{ + "type": "IPV4", + "num_dynamic_addresses": 1, + "subnet_id": + "3a7a37fc-6a92-11eb-9439-0242ac130002"}]}, + "layer_protocol": "IP_OVER_ETHERNET"}]}]}, { + "cpd_id": "CP4", + "cp_config": [{ + "cp_protocol_data": [{ + "layer_protocol": "IP_OVER_ETHERNET"}], + "link_port_id": + "413f4e46-21cf-41b1-be0f-de8d23f76cfe"}]}], + "ext_link_ports": [{ + "id": "413f4e46-21cf-41b1-be0f-de8d23f76cfe", + "resource_handle": { + "resource_id": "67f7e772-0d31-4087-bf4c-2576fadcbdb7", + "vim_connection_id": + "6b0ff598-60d6-49b4-a907-a1111de52d92", + "vim_level_resource_type": "LINKPORT"}}], }] + + grant_data = { + "id": "3affab40-6f28-11eb-9439-0242ac130002", + "vnf_instance_id": "4d62c7c2-6f28-11eb-9439-0242ac130002", + "vnf_lcm_op_occ_id": "6e426236-6f28-11eb-9439-0242ac130002", + "vim_connections": vim_connection_info, + "update_resources": grant_info, + "vim_assets": { + "compute_resource_flavours": vim_compute_resource_flavour, + "software_images": vim_software_image, + }, + "ext_virtual_links": ext_vl_info, + "additional_params": {"key1": "value1"}, + "_links": { + "self": {"href": ""}, + "vnf_lcm_op_occ": {"href": ""}, + "vnf_instance": {"href": ""}, + } + } + + return grant_data + + def test_obj_from_primitive(self): + grant_data = self._get_grant_data() + grant = objects.Grant.obj_from_primitive( + copy.deepcopy(grant_data), self.context) + self._check_grant(grant, grant_data) + + def _check_grant(self, obj, data): + + def _check_vim_connection_info(_obj, _data): + self.assertEqual(len(_obj), len(_data)) + for obj, data in zip(_obj, _data): + self.assertIsInstance(obj, objects.VimConnectionInfo) + + def _check_update_resources(_obj, _data): + self.assertEqual(len(_obj), len(_data)) + for obj, data in zip(_obj, _data): + self.assertIsInstance(obj, objects.GrantInfo) + + def _check_vim_assets(obj, data): + self.assertIsInstance(obj, objects.VimAssets) + _check_compute_resource_flavours( + obj.compute_resource_flavours, + data.get('compute_resource_flavours', [])) + _check_software_images(obj.software_images, + data.get('software_images', [])) + + def _check_compute_resource_flavours(_obj, _data): + self.assertEqual(len(_obj), len(_data)) + for obj, data in zip(_obj, _data): + self.assertIsInstance(obj, + objects.VimComputeResourceFlavour) + self.assertEqual(obj.vim_connection_id, + data.get('vim_connection_id')) + self.assertEqual(obj.vnfd_virtual_compute_desc_id, + data.get('vnfd_virtual_compute_desc_id')) + self.assertEqual(obj.vim_flavour_id, + data.get('vim_flavour_id')) + + def _check_software_images(_obj, _data): + self.assertEqual(len(_obj), len(_data)) + for obj, data in zip(_obj, _data): + self.assertIsInstance(obj, objects.VimSoftwareImage) + self.assertEqual(obj.vim_connection_id, + data.get('vim_connection_id')) + self.assertEqual(obj.vnfd_software_image_id, + data.get('vnfd_software_image_id')) + self.assertEqual(obj.vim_software_image_id, + data.get('vim_software_image_id')) + + def _check_external_virtual_links(_obj, _data): + self.assertEqual(len(_obj), len(_data)) + for obj, data in zip(_obj, _data): + self.assertIsInstance(obj, objects.ExtVirtualLinkData) + self.assertEqual(obj.id, data.get('id')) + self.assertEqual(obj.vim_connection_id, + data.get('vim_connection_id')) + self.assertEqual(obj.resource_id, + data.get('resource_id')) + _check_ext_cps(obj.ext_cps, + data.get('ext_cps', [])) + _check_ext_link_ports(obj.ext_link_ports, + data.get('ext_link_ports', [])) + + def _check_ext_cps(_obj, _data): + self.assertEqual(len(_obj), len(_data)) + for obj, data in zip(_obj, _data): + self.assertIsInstance(obj, objects.VnfExtCpData) + self.assertEqual(obj.cpd_id, data.get('cpd_id')) + _check_ext_cp_config(obj.cp_config, + data.get('cp_config', [])) + + def _check_ext_cp_config(_obj, _data): + self.assertEqual(len(_obj), len(_data)) + for obj, data in zip(_obj, _data): + self.assertIsInstance(obj, objects.VnfExtCpConfig) + self.assertEqual(obj.cp_instance_id, + data.get('cp_instance_id')) + self.assertEqual(obj.link_port_id, + data.get('link_port_id')) + _check_cp_protocol_data(obj.cp_protocol_data, + data.get('cp_protocol_data', [])) + + def _check_cp_protocol_data(_obj, _data): + self.assertEqual(len(_obj), len(_data)) + for obj, data in zip(_obj, _data): + self.assertIsInstance(obj, objects.CpProtocolData) + self.assertEqual(obj.layer_protocol, + data.get('layer_protocol')) + if obj.ip_over_ethernet or data.get('ip_over_ethernet', None): + _check_ip_over_ethernet(obj.ip_over_ethernet, + data.get('ip_over_ethernet', None)) + + def _check_ip_over_ethernet(obj, data): + self.assertIsInstance(obj, objects.IpOverEthernetAddressData) + self.assertEqual(obj.mac_address, data.get('mac_address')) + _check_ip_addresses(obj.ip_addresses, + data.get('ip_addresses', [])) + + def _check_ip_addresses(_obj, _data): + self.assertEqual(len(_obj), len(_data)) + for obj, data in zip(_obj, _data): + self.assertIsInstance(obj, objects.IpAddressReq) + self.assertEqual(obj.type, data.get('type')) + self.assertEqual(obj.subnet_id, data.get('subnet_id')) + self.assertEqual(obj.num_dynamic_addresses, + data.get('num_dynamic_addresses')) + + def _check_ext_link_ports(_obj, _data): + self.assertEqual(len(_obj), len(_data)) + for obj, data in zip(_obj, _data): + self.assertIsInstance(obj, objects.ExtLinkPortData) + self.assertEqual(obj.id, data.get('id')) + self.assertIsInstance(obj.resource_handle, + objects.ResourceHandle) + + self.assertIsInstance(obj, objects.Grant) + self.assertEqual(obj.id, data.get('id')) + self.assertEqual(obj.vnf_instance_id, data.get('vnf_instance_id')) + self.assertEqual(obj.vnf_lcm_op_occ_id, data.get('vnf_lcm_op_occ_id')) + _check_vim_connection_info(obj.vim_connections, + data.get('vim_connections')) + _check_update_resources(obj.update_resources, + data.get('update_resources')) + _check_vim_assets(obj.vim_assets, + data.get('vim_assets', None)) + _check_external_virtual_links(obj.ext_virtual_links, + data.get('ext_virtual_links')) diff --git a/tacker/tests/unit/vnflcm/fakes.py b/tacker/tests/unit/vnflcm/fakes.py index b6c671163..41d1c2a4e 100644 --- a/tacker/tests/unit/vnflcm/fakes.py +++ b/tacker/tests/unit/vnflcm/fakes.py @@ -20,9 +20,11 @@ import os import webob from tacker.api.vnflcm.v1.router import VnflcmAPIRouter +from tacker.common import utils from tacker import context from tacker.db.db_sqlalchemy import models from tacker import objects +from tacker.objects.change_ext_conn_req import ChangeExtConnRequest from tacker.objects import fields from tacker.objects.instantiate_vnf_req import ExtManagedVirtualLinkData from tacker.objects.instantiate_vnf_req import ExtVirtualLinkData @@ -229,7 +231,10 @@ def _instantiated_vnf_links(vnf_instance_id): "terminate": {"href": "/vnflcm/v1/vnf_instances/%s/terminate" % vnf_instance_id}, "heal": {"href": "/vnflcm/v1/vnf_instances/%s/heal" % - vnf_instance_id}} + vnf_instance_id}, + "changeExtConn": {"href": + "/vnflcm/v1/vnf_instances/%s/change_ext_conn" % + vnf_instance_id}} return links @@ -1598,3 +1603,62 @@ def return_vnf_lcm_opoccs_list(): obj = objects.VnfLcmOpOcc(**vnf_lcm_op_occs) return [obj] + + +def get_change_ext_conn_request_body(): + change_ext_conn_req_body = { + "extVirtualLinks": [{ + "id": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa', + "vimConnectionId": '2b3beeff-d4a1-4dc7-a1f8-066f92cfcb75', + "resourceId": 'e08f5e67-55de-4c4a-815b-cf3f1e2bae04', + "extCps": [{ + "cpdId": 'VDU2_CP2', + "cpConfig": [{ + "cpInstanceId": '924d0ea7-786d-468b-bf45-65bfd483ee79', + "linkPortId": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa', + "cpProtocolData": [{ + "layerProtocol": 'IP_OVER_ETHERNET', + "ipOverEthernet": { + "macAddress": + 'fa:16:3e:11:11:11', + "ipAddresses": [{ + "type": "IPV4", + "fixedAddresses": ["22.22.1.20"], + "subnetId": + '497b7a75-6c10-4a74-85fa-83d498da2501' + }] + } + }] + }] + }], + "extLinkPorts": [{ + "id": 'decd78d2-993c-4112-9a8f-1ad54cade4d7', + "resourceHandle": { + "resourceId": 'cb602960-05ee-4e03-8fe2-ea0b64e08332', + "vimConnectionId": '2b3beeff-d4a1-4dc7-a1f8-066f92cfcb75', + "vimLevelResourceType": + 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa', + } + }], + "vimConnectionInfo": [{ + "id": '2b3beeff-d4a1-4dc7-a1f8-066f92cfcb75', + "vimId": 'f8c35bd0-4d67-4436-9f11-14b8a84c92aa', + "vimType": 'openstack', + "interfaceInfo": {"key1": 'value1', "key2": 'value2'}, + "accessInfo": {"key1": 'value1', "key2": 'value2'} + }], + }] + } + + return change_ext_conn_req_body + + +def get_change_ext_conn_request_obj(): + """Return ChangeExtConnRequest Object + + obj_from_primitive() needs snake_case dictionary + """ + body = utils.convert_camelcase_to_snakecase( + get_change_ext_conn_request_body()) + return ChangeExtConnRequest.obj_from_primitive( + body, context) diff --git a/tacker/tests/unit/vnflcm/test_controller.py b/tacker/tests/unit/vnflcm/test_controller.py index 33c41fcf1..117aa0288 100644 --- a/tacker/tests/unit/vnflcm/test_controller.py +++ b/tacker/tests/unit/vnflcm/test_controller.py @@ -3554,3 +3554,152 @@ class TestController(base.TestCase): mock_lcm_get_by_id.assert_called_once() mock_vnf_get_by_id.assert_called_once() + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': + test_nfvo_plugin.FakeVNFMPlugin()}) + @mock.patch('tacker.api.vnflcm.v1.controller.' + 'VnfLcmController._notification_process') + @mock.patch('tacker.api.vnflcm.v1.controller.' + 'VnfLcmController._get_vnf') + @mock.patch.object(objects.VnfInstance, "get_by_id") + @mock.patch.object(objects.VnfInstance, "save") + @mock.patch.object(vnf_lcm_rpc.VNFLcmRPCAPI, "change_ext_conn") + def test_change_ext_conn(self, mock_rpc, mock_save, + mock_vnf_by_id, mock_get_vnf, + mock_notification_process, + mock_get_service_plugins): + vnf_instance_obj = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + mock_vnf_by_id.return_value = vnf_instance_obj + mock_get_vnf.return_value = \ + self._get_dummy_vnf(vnf_id=vnf_instance_obj.id, status='ACTIVE') + + body = fakes.get_change_ext_conn_request_body() + req = fake_request.HTTPRequest.blank( + '/vnf_instances/%s/change_ext_conn' % uuidsentinel.vnf_instance_id) + req.body = jsonutils.dump_as_bytes(body) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + + resp = req.get_response(self.app) + + self.assertEqual(http_client.ACCEPTED, resp.status_code) + mock_rpc.assert_called_once() + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': + test_nfvo_plugin.FakeVNFMPlugin()}) + @mock.patch('tacker.api.vnflcm.v1.controller.' + 'VnfLcmController._get_vnf') + @mock.patch.object(objects.VnfInstance, "get_by_id") + def test_change_ext_conn_incorrect_instantiated_state( + self, mock_vnf_by_id, mock_get_vnf, + mock_get_service_plugins): + vnf_instance_obj = fakes.return_vnf_instance( + fields.VnfInstanceState.NOT_INSTANTIATED) + mock_vnf_by_id.return_value = vnf_instance_obj + + body = fakes.get_change_ext_conn_request_body() + req = fake_request.HTTPRequest.blank( + '/vnf_instances/%s/change_ext_conn' % uuidsentinel.vnf_instance_id) + req.body = jsonutils.dump_as_bytes(body) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + + resp = req.get_response(self.app) + + self.assertEqual(http_client.CONFLICT, resp.status_code) + expected_msg = ("VNF is not instantiated") + self.assertEqual(expected_msg, resp.json['detail']) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': + test_nfvo_plugin.FakeVNFMPlugin()}) + @ddt.data('HEAD', 'PUT', 'DELETE', 'PATCH', 'GET') + def test_change_ext_conn_invalid_http_method(self, method, + mock_get_service_plugins): + body = {} + req = fake_request.HTTPRequest.blank( + '/vnf_instances/%s/change_ext_conn' % uuidsentinel.vnf_instance_id) + req.body = jsonutils.dump_as_bytes(body) + req.headers['Content-Type'] = 'application/json' + req.method = method + + resp = req.get_response(self.app) + + self.assertEqual(http_client.METHOD_NOT_ALLOWED, resp.status_code) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': + test_nfvo_plugin.FakeVNFMPlugin()}) + @mock.patch('tacker.api.vnflcm.v1.controller.' + 'VnfLcmController._notification_process') + @mock.patch('tacker.api.vnflcm.v1.controller.' + 'VnfLcmController._get_vnf') + @mock.patch.object(objects.VnfInstance, "get_by_id") + @mock.patch.object(objects.VnfInstance, "save") + @mock.patch.object(vnf_lcm_rpc.VNFLcmRPCAPI, "change_ext_conn") + @ddt.data( + { + "type": "IPV4", + }, + { + "type": "IPV4", + "fixedAddresses": ["22.22.1.20"], + "numDynamicAddresses": 1, + "subnetId": '497b7a75-6c10-4a74-85fa-83d498da2501', + }, + { + "type": "IPV4", + "numDynamicAddresses": 0, + "subnetId": '497b7a75-6c10-4a74-85fa-83d498da2501', + }, + ) + def test_change_ext_conn_with_invalid_requests( + self, + ip_address, + mock_rpc, + mock_save, + mock_vnf_by_id, + mock_get_vnf, + mock_notification_process, + mock_get_service_plugins): + vnf_instance_obj = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + mock_vnf_by_id.return_value = vnf_instance_obj + mock_get_vnf.return_value = \ + self._get_dummy_vnf(vnf_id=vnf_instance_obj.id, status='ACTIVE') + + body = fakes.get_change_ext_conn_request_body() + body['extVirtualLinks'][0]['extCps'][0]['cpConfig'][0][ + 'cpProtocolData'][0]['ipOverEthernet'][ + 'ipAddresses'] = [ip_address] + req = fake_request.HTTPRequest.blank( + '/vnf_instances/%s/change_ext_conn' % uuidsentinel.vnf_instance_id) + req.body = jsonutils.dump_as_bytes(body) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + + resp = req.get_response(self.app) + + self.assertEqual( + resp.status_code, http_client.BAD_REQUEST) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': + test_nfvo_plugin.FakeVNFMPlugin()}) + def test_change_ext_conn_with_invalid_uuid(self, mock_get_service_plugins): + body = fakes.get_change_ext_conn_request_body() + req = fake_request.HTTPRequest.blank( + '/vnf_instances/%s/change_ext_conn' % constants.INVALID_UUID) + req.body = jsonutils.dump_as_bytes(body) + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + + resp = req.get_response(self.app) + + self.assertEqual(http_client.NOT_FOUND, resp.status_code) + self.assertEqual( + "Can not find requested vnf: %s" % constants.INVALID_UUID, + resp.json['itemNotFound']['message']) diff --git a/tacker/tests/unit/vnflcm/test_vnflcm_driver.py b/tacker/tests/unit/vnflcm/test_vnflcm_driver.py index 54b78c2a3..5cbc2b2a6 100644 --- a/tacker/tests/unit/vnflcm/test_vnflcm_driver.py +++ b/tacker/tests/unit/vnflcm/test_vnflcm_driver.py @@ -136,6 +136,14 @@ class FakeDriverManager(mock.Mock): raise InfraDriverException("post_heal_vnf failed") if 'get_rollback_ids' in args: return [], [], "" + if 'change_ext_conn_vnf' in args: + if self.fail_method_name and \ + self.fail_method_name == 'change_ext_conn_vnf': + raise InfraDriverException("change_ext_conn_vnf failed") + elif 'change_ext_conn_vnf_wait' in args: + if self.fail_method_name and \ + self.fail_method_name == 'change_ext_conn_vnf_wait': + raise InfraDriverException("change_ext_conn_vnf_wait failed") class FakeVimClient(mock.Mock): @@ -2900,3 +2908,348 @@ class TestVnflcmDriver(db_base.SqlTestCase): 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(VnfLcmDriver, + '_init_mgmt_driver_hash') + @mock.patch.object(objects.VnfInstance, "save") + @mock.patch('tacker.vnflcm.vnflcm_driver.LOG') + @mock.patch('tacker.vnflcm.utils._get_vnfd_dict') + @mock.patch('tacker.vnflcm.vnflcm_driver.VnfLcmDriver.' + '_load_vnf_interface') + def test_change_ext_conn_vnf(self, mock_vnf_interfaces, mock_vnfd_dict, + mock_log, mock_save, mock_init_hash, + mock_get_service_plugins): + mock_init_hash.return_value = { + "vnflcm_noop": "ffea638bfdbde3fb01f191bbe75b031859" + "b18d663b127100eb72b19eecd7ed51" + } + mock_vnf_interfaces.return_value = fakes.return_vnf_interfaces() + change_ext_conn_vnf_req = objects.ChangeExtConnRequest( + vim_connection_info=[], + ext_virtual_links=[], + additional_params={}) + + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + vnf_dict = {'before_error_point': 0, 'grant': None} + self._mock_vnf_manager() + driver = vnflcm_driver.VnfLcmDriver() + driver.change_ext_conn_vnf(self.context, + vnf_instance, + vnf_dict, + change_ext_conn_vnf_req) + self.assertEqual(4, self._vnf_manager.invoke.call_count) + + self.assertEqual(None, vnf_instance.task_state) + expected_msg = ("Request received for changing external " + "connectivity vnf '%s' is completed successfully") + mock_log.info.assert_called_with(expected_msg, + vnf_instance.id) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(VnfLcmDriver, + '_init_mgmt_driver_hash') + @mock.patch.object(objects.VnfInstance, "save") + @mock.patch('tacker.vnflcm.vnflcm_driver.LOG') + @mock.patch('tacker.vnflcm.utils._get_vnfd_dict') + @mock.patch('tacker.vnflcm.vnflcm_driver.VnfLcmDriver.' + '_load_vnf_interface') + def test_change_ext_conn_vnf_fail(self, mock_vnf_interfaces, + mock_vnfd_dict, mock_log, mock_save, mock_init_hash, + mock_get_service_plugins): + mock_init_hash.return_value = { + "vnflcm_noop": "ffea638bfdbde3fb01f191bbe75b031859" + "b18d663b127100eb72b19eecd7ed51" + } + mock_vnf_interfaces.return_value = fakes.return_vnf_interfaces() + change_ext_conn_vnf_req = objects.ChangeExtConnRequest( + vim_connection_info=[], + ext_virtual_links=[], + additional_params={}) + + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + vnf_dict = {'before_error_point': 0, 'grant': None} + + self._mock_vnf_manager(fail_method_name='change_ext_conn_vnf') + driver = vnflcm_driver.VnfLcmDriver() + self.assertRaises(exceptions.VnfChangeExtConnFailed, + driver.change_ext_conn_vnf, self.context, vnf_instance, + vnf_dict, change_ext_conn_vnf_req) + self.assertEqual(2, self._vnf_manager.invoke.call_count) + + self.assertEqual(fields.VnfInstanceTaskState.ERROR, + vnf_instance.task_state) + expected_msg = ("Failed to change external connectivity " + "vnf %(id)s in infra driver. " + "Error: %(error)s") + mock_log.error.assert_called_with(expected_msg, + {'id': vnf_instance.id, 'error': 'change_ext_conn_vnf failed'}) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(VnfLcmDriver, + '_init_mgmt_driver_hash') + @mock.patch.object(objects.VnfInstance, "save") + @mock.patch('tacker.vnflcm.vnflcm_driver.LOG') + @mock.patch('tacker.vnflcm.utils._get_vnfd_dict') + @mock.patch('tacker.vnflcm.vnflcm_driver.VnfLcmDriver.' + '_load_vnf_interface') + def test_change_ext_conn_vnf_wait_fail(self, mock_vnf_interfaces, + mock_vnfd_dict, mock_log, mock_save, mock_init_hash, + mock_get_service_plugins): + mock_init_hash.return_value = { + "vnflcm_noop": "ffea638bfdbde3fb01f191bbe75b031859" + "b18d663b127100eb72b19eecd7ed51" + } + mock_vnf_interfaces.return_value = fakes.return_vnf_interfaces() + change_ext_conn_vnf_req = objects.ChangeExtConnRequest( + vim_connection_info=[], + ext_virtual_links=[], + additional_params={}) + + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + vnf_dict = {'before_error_point': 0, 'grant': None} + + vnf_instance.instantiated_vnf_info.instance_id =\ + uuidsentinel.instance_id + self._mock_vnf_manager(fail_method_name='change_ext_conn_vnf_wait') + driver = vnflcm_driver.VnfLcmDriver() + self.assertRaises(exceptions.VnfChangeExtConnWaitFailed, + driver.change_ext_conn_vnf, self.context, vnf_instance, + vnf_dict, change_ext_conn_vnf_req) + self.assertEqual(3, self._vnf_manager.invoke.call_count) + + self.assertEqual( + fields.VnfInstanceTaskState.ERROR, + vnf_instance.task_state) + expected_msg = ('Failed to update vnf %(id)s resources for ' + 'instance %(instance)s. Error: %(error)s') + mock_log.error.assert_called_with(expected_msg, + {'id': vnf_instance.id, + 'instance': vnf_instance.instantiated_vnf_info.instance_id, + 'error': 'change_ext_conn_vnf_wait failed'}) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(VnfLcmDriver, + '_init_mgmt_driver_hash') + @mock.patch.object(objects.VnfInstance, "save") + @mock.patch('tacker.vnflcm.vnflcm_driver.LOG') + @mock.patch('tacker.vnflcm.utils._get_vnfd_dict') + @mock.patch('tacker.vnflcm.vnflcm_driver.VnfLcmDriver.' + '_load_vnf_interface') + def test_change_ext_conn_vnf_retry_error_point_2( + self, + mock_vnf_interfaces, + mock_vnfd_dict, + mock_log, + mock_save, + mock_init_hash, + mock_get_service_plugins): + mock_init_hash.return_value = { + "vnflcm_noop": "ffea638bfdbde3fb01f191bbe75b031859" + "b18d663b127100eb72b19eecd7ed51" + } + mock_vnf_interfaces.return_value = fakes.return_vnf_interfaces() + change_ext_conn_vnf_req = objects.ChangeExtConnRequest( + vim_connection_info=[], + ext_virtual_links=[], + additional_params={}) + + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + vnf_dict = {'before_error_point': 2, 'grant': None} + self._mock_vnf_manager() + driver = vnflcm_driver.VnfLcmDriver() + driver.change_ext_conn_vnf(self.context, + vnf_instance, + vnf_dict, + change_ext_conn_vnf_req) + + self.assertEqual(4, self._vnf_manager.invoke.call_count) + self.assertEqual(None, vnf_instance.task_state) + expected_msg = ("Request received for changing external " + "connectivity vnf '%s' is completed successfully") + mock_log.info.assert_called_with(expected_msg, + vnf_instance.id) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(VnfLcmDriver, + '_init_mgmt_driver_hash') + @mock.patch.object(objects.VnfInstance, "save") + @mock.patch('tacker.vnflcm.vnflcm_driver.LOG') + @mock.patch('tacker.vnflcm.utils._get_vnfd_dict') + @mock.patch('tacker.vnflcm.vnflcm_driver.VnfLcmDriver.' + '_load_vnf_interface') + def test_change_ext_conn_vnf_retry_error_point_3( + self, + mock_vnf_interfaces, + mock_vnfd_dict, + mock_log, + mock_save, + mock_init_hash, + mock_get_service_plugins): + mock_init_hash.return_value = { + "vnflcm_noop": "ffea638bfdbde3fb01f191bbe75b031859" + "b18d663b127100eb72b19eecd7ed51" + } + mock_vnf_interfaces.return_value = fakes.return_vnf_interfaces() + change_ext_conn_vnf_req = objects.ChangeExtConnRequest( + vim_connection_info=[], + ext_virtual_links=[], + additional_params={}) + + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + vnf_dict = {'before_error_point': 3, 'grant': None} + self._mock_vnf_manager() + driver = vnflcm_driver.VnfLcmDriver() + driver.change_ext_conn_vnf(self.context, + vnf_instance, + vnf_dict, + change_ext_conn_vnf_req) + + self.assertEqual(3, self._vnf_manager.invoke.call_count) + self.assertEqual(None, vnf_instance.task_state) + expected_msg = ("Request received for changing external " + "connectivity vnf '%s' is completed successfully") + mock_log.info.assert_called_with(expected_msg, + vnf_instance.id) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(VnfLcmDriver, + '_init_mgmt_driver_hash') + @mock.patch.object(objects.VnfInstance, "save") + @mock.patch('tacker.vnflcm.vnflcm_driver.LOG') + @mock.patch('tacker.vnflcm.utils._get_vnfd_dict') + @mock.patch('tacker.vnflcm.vnflcm_driver.VnfLcmDriver.' + '_load_vnf_interface') + def test_change_ext_conn_vnf_retry_error_point_4( + self, + mock_vnf_interfaces, + mock_vnfd_dict, + mock_log, + mock_save, + mock_init_hash, + mock_get_service_plugins): + mock_init_hash.return_value = { + "vnflcm_noop": "ffea638bfdbde3fb01f191bbe75b031859" + "b18d663b127100eb72b19eecd7ed51" + } + mock_vnf_interfaces.return_value = fakes.return_vnf_interfaces() + change_ext_conn_vnf_req = objects.ChangeExtConnRequest( + vim_connection_info=[], + ext_virtual_links=[], + additional_params={}) + + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + vnf_dict = {'before_error_point': 4, 'grant': None} + self._mock_vnf_manager() + driver = vnflcm_driver.VnfLcmDriver() + driver.change_ext_conn_vnf(self.context, + vnf_instance, + vnf_dict, + change_ext_conn_vnf_req) + + self.assertEqual(3, self._vnf_manager.invoke.call_count) + self.assertEqual(None, vnf_instance.task_state) + expected_msg = ("Request received for changing external " + "connectivity vnf '%s' is completed successfully") + mock_log.info.assert_called_with(expected_msg, + vnf_instance.id) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(VnfLcmDriver, + '_init_mgmt_driver_hash') + @mock.patch.object(objects.VnfInstance, "save") + @mock.patch('tacker.vnflcm.vnflcm_driver.LOG') + @mock.patch('tacker.vnflcm.utils._get_vnfd_dict') + @mock.patch('tacker.vnflcm.vnflcm_driver.VnfLcmDriver.' + '_load_vnf_interface') + def test_change_ext_conn_vnf_retry_error_point_5( + self, + mock_vnf_interfaces, + mock_vnfd_dict, + mock_log, + mock_save, + mock_init_hash, + mock_get_service_plugins): + mock_init_hash.return_value = { + "vnflcm_noop": "ffea638bfdbde3fb01f191bbe75b031859" + "b18d663b127100eb72b19eecd7ed51" + } + mock_vnf_interfaces.return_value = fakes.return_vnf_interfaces() + change_ext_conn_vnf_req = objects.ChangeExtConnRequest( + vim_connection_info=[], + ext_virtual_links=[], + additional_params={}) + + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + vnf_dict = {'before_error_point': 5, 'grant': None} + self._mock_vnf_manager() + driver = vnflcm_driver.VnfLcmDriver() + driver.change_ext_conn_vnf(self.context, + vnf_instance, + vnf_dict, + change_ext_conn_vnf_req) + + self.assertEqual(1, self._vnf_manager.invoke.call_count) + self.assertEqual(None, vnf_instance.task_state) + expected_msg = ("Request received for changing external " + "connectivity vnf '%s' is completed successfully") + mock_log.info.assert_called_with(expected_msg, + vnf_instance.id) + + @mock.patch.object(TackerManager, 'get_service_plugins', + return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(VnfLcmDriver, + '_init_mgmt_driver_hash') + @mock.patch.object(objects.VnfInstance, "save") + @mock.patch('tacker.vnflcm.vnflcm_driver.LOG') + @mock.patch('tacker.vnflcm.utils._get_vnfd_dict') + @mock.patch('tacker.vnflcm.vnflcm_driver.VnfLcmDriver.' + '_load_vnf_interface') + def test_change_ext_conn_vnf_retry_error_point_6( + self, + mock_vnf_interfaces, + mock_vnfd_dict, + mock_log, + mock_save, + mock_init_hash, + mock_get_service_plugins): + mock_init_hash.return_value = { + "vnflcm_noop": "ffea638bfdbde3fb01f191bbe75b031859" + "b18d663b127100eb72b19eecd7ed51" + } + mock_vnf_interfaces.return_value = fakes.return_vnf_interfaces() + change_ext_conn_vnf_req = objects.ChangeExtConnRequest( + vim_connection_info=[], + ext_virtual_links=[], + additional_params={}) + + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED) + vnf_dict = {'before_error_point': 6, 'grant': None} + self._mock_vnf_manager() + driver = vnflcm_driver.VnfLcmDriver() + driver.change_ext_conn_vnf(self.context, + vnf_instance, + vnf_dict, + change_ext_conn_vnf_req) + + self.assertEqual(1, self._vnf_manager.invoke.call_count) + self.assertEqual(None, vnf_instance.task_state) + expected_msg = ("Request received for changing external " + "connectivity vnf '%s' is completed successfully") + mock_log.info.assert_called_with(expected_msg, + vnf_instance.id) diff --git a/tacker/tests/unit/vnfm/infra_drivers/openstack/fixture_data/fixture_data_utils.py b/tacker/tests/unit/vnfm/infra_drivers/openstack/fixture_data/fixture_data_utils.py index ffa27d151..d80413aff 100644 --- a/tacker/tests/unit/vnfm/infra_drivers/openstack/fixture_data/fixture_data_utils.py +++ b/tacker/tests/unit/vnfm/infra_drivers/openstack/fixture_data/fixture_data_utils.py @@ -220,16 +220,84 @@ def get_vnfc_resource_info(vdu_id="VDU1", storage_resource_ids=None, return vnfc_resource_info +def _get_ext_link_port(ext_vl_port_id, cp_instance_id, + set_resource_id=False): + if set_resource_id: + resource_id = uuidsentinel.ext_virtual_link_port_resource_id + else: + resource_id = "" + + resource_handle = objects.ResourceHandle( + resource_id=resource_id, + vim_level_resource_type="OS::Neutron::Port") + + ext_vl_port = objects.ExtLinkPortInfo( + id=ext_vl_port_id, cp_instance_id=cp_instance_id, + resource_handle=resource_handle) + + return ext_vl_port + + +def get_ext_virtual_link_info(ext_virtual_link_id, desc_id="externalVL1", + set_resource_id=True): + + network_resource = objects.ResourceHandle( + resource_id=uuidsentinel.virtual_link_resource_id, + vim_level_resource_type="OS::Neutron::Network") + + ext_vl_link_port = _get_ext_link_port( + uuidsentinel.ext_vl_port_id, + cp_instance_id=uuidsentinel.cp_instance_id, + set_resource_id=set_resource_id) + + ext_vl_info = objects.ExtVirtualLinkInfo( + id=uuidsentinel.ext_virtual_link_id, + resource_handle=network_resource, + ext_link_ports=[ext_vl_link_port]) + + return ext_vl_info + + +def get_ext_cp_info(ext_cp_id, cpd_id='VDU1_CP1', ip_addresses=[]): + + ip_over_ethernet = objects.IpOverEthernetAddressInfo( + ip_addresses=ip_addresses) + + cp_protocol_info = objects.CpProtocolInfo( + layer_protocol="IP_OVER_ETHERNET", + ip_over_ethernet=ip_over_ethernet) + + ext_cp_info = objects.VnfExtCpInfo( + id=ext_cp_id, + cpd_id=cpd_id, + cp_protocol_info=[cp_protocol_info]) + + return ext_cp_info + + +def get_ip_address(ip_type='IPV4', subnet_id=None, is_dynamic=False, + addresses=[]): + ip_address = objects.IpAddress(type=ip_type, + subnet_id=subnet_id, + is_dynamic=is_dynamic, + addresses=addresses) + return ip_address + + def get_vnf_instantiated_info(flavour_id='simple', instantiation_level_id=None, vnfc_resource_info=None, virtual_storage_resource_info=None, vnf_virtual_link_resource_info=None, - ext_managed_virtual_link_info=None): + ext_managed_virtual_link_info=None, + ext_virtual_link_info=None, + ext_cp_info=None): vnfc_resource_info = vnfc_resource_info or [] vnf_virtual_link_resource_info = vnf_virtual_link_resource_info or [] virtual_storage_resource_info = virtual_storage_resource_info or [] ext_managed_virtual_link_info = ext_managed_virtual_link_info or [] + ext_virtual_link_info = ext_virtual_link_info or [] + ext_cp_info = ext_cp_info or [] inst_vnf_info = objects.InstantiatedVnfInfo(flavour_id=flavour_id, instantiation_level_id=instantiation_level_id, @@ -237,7 +305,9 @@ def get_vnf_instantiated_info(flavour_id='simple', vnfc_resource_info=vnfc_resource_info, vnf_virtual_link_resource_info=vnf_virtual_link_resource_info, virtual_storage_resource_info=virtual_storage_resource_info, - ext_managed_virtual_link_info=ext_managed_virtual_link_info) + ext_managed_virtual_link_info=ext_managed_virtual_link_info, + ext_virtual_link_info=ext_virtual_link_info, + ext_cp_info=ext_cp_info) return inst_vnf_info @@ -332,6 +402,162 @@ def get_grant_response_dict(): return grant_response_dict +def get_change_ext_conn_request(): + + def _get_ip_addresses(_type='IPV4', fixed_addrs=[], subnet_id=None): + if fixed_addrs and subnet_id: + return [{ + "type": _type, + "fixed_addresses": fixed_addrs, + "subnet_id": subnet_id, + }] + elif fixed_addrs and not subnet_id: + return [{ + "type": _type, + "fixed_addresses": fixed_addrs, + }] + elif not fixed_addrs and subnet_id: + return [{ + "type": _type, + "num_dynamic_addresses": 1, + "subnet_id": subnet_id, + }] + elif not fixed_addrs and not subnet_id: + return [{ + "type": _type, + "num_dynamic_addresses": 1, + }] + + def _get_ext_cp_info(cpd_id, ip_address): + return { + "cpd_id": cpd_id, + "cp_config": [{ + "cp_protocol_data": [{ + "layer_protocol": "IP_OVER_ETHERNET", + "ip_over_ethernet": { + "ip_addresses": ip_address + } + }] + }] + } + + def _get_request(): + ext_vl_info = [{ + "id": "external_network_1", + "vim_connection_id": uuidsentinel.vim_connection_id, + "resource_id": "nw-resource-id-1", + "ext_cps": [ + _get_ext_cp_info('VDU1_CP1', + _get_ip_addresses(fixed_addrs=["20.0.0.1"])), + _get_ext_cp_info('VDU1_CP2', + _get_ip_addresses( + fixed_addrs=["30.0.0.2"], + subnet_id="changed-subnet-id-1")), + _get_ext_cp_info('VDU1_CP3', + _get_ip_addresses(fixed_addrs=["10.0.0.1"])), + ]}, { + "id": "external_network_2", + "vim_connection_id": uuidsentinel.vim_connection_id, + "resource_id": "changed-nw-resource-id-2", + "ext_cps": [ + _get_ext_cp_info('VDU2_CP1', + _get_ip_addresses( + subnet_id="changed-subnet-id-2")), + _get_ext_cp_info('VDU2_CP2', _get_ip_addresses()) + ] + }] + + vim_connection_info = [{ + "id": uuidsentinel.vim_connection_id, + "vim_id": uuidsentinel.vim_id, + "vim_type": "ETSINFV.OPENSTACK_KEYSTONE.v_2", + "interface_info": { + "endpoint": "endpoint_value"}, + "access_info": { + "username": "username_value", + "password": "password_value", + "region": "region_value", + "tenant": "tenant_value"}}] + + change_ext_conn_data = { + 'ext_virtual_links': ext_vl_info, + 'vim_connection_info': vim_connection_info, + 'additional_params': {'key1': 'value1'}} + + return change_ext_conn_data + + change_ext_conn_data = _get_request() + change_ext_conn_req = objects.ChangeExtConnRequest.obj_from_primitive( + change_ext_conn_data, None) + + return change_ext_conn_req + + +def get_original_stack_param(): + stack_param = \ + {'nfv': { + 'VDU': { + 'VDU1': {'flavor': 'm1.tiny', 'image': 'None'}, + 'VirtualStorage': { + 'flavor': 'None', + 'image': 'cirros-0.4.0-x86_64-disk'}, + 'VDU2': { + 'flavor': 'm1.tiny', + 'image': 'cirros-0.4.0-x86_64-disk'}}, + 'CP': { + 'VDU1_CP1': { + 'network': 'nw-resource-id-1', + 'fixed_ips': [{'ip_address': '10.0.0.1'}]}, + 'VDU1_CP2': { + 'network': 'nw-resource-id-1', + 'fixed_ips': [{ + 'ip_address': '10.0.0.2', + 'subnet': 'subnet-id-2'}]}, + 'VDU2_CP1': { + 'network': 'nw-resource-id-2', + 'fixed_ips': [{ + 'subnet': 'subnet-id-2'}]}, + 'VDU2_CP2': {'network': 'nw-resource-id-2'}}}} + return stack_param + + +def get_expect_stack_param(): + stack_param = \ + {'nfv': { + 'VDU': { + 'VDU1': {'flavor': 'm1.tiny', 'image': 'None'}, + 'VirtualStorage': { + 'flavor': 'None', + 'image': 'cirros-0.4.0-x86_64-disk'}, + 'VDU2': { + 'flavor': 'm1.tiny', + 'image': 'cirros-0.4.0-x86_64-disk'}}, + 'CP': { + 'VDU1_CP1': { + 'network': 'nw-resource-id-1', + 'fixed_ips': [{'ip_address': '20.0.0.1'}]}, + 'VDU1_CP2': { + 'network': 'nw-resource-id-1', + 'fixed_ips': [{ + 'ip_address': '30.0.0.2', + 'subnet': 'changed-subnet-id-1'}]}, + 'VDU2_CP1': { + 'network': 'changed-nw-resource-id-2', + 'fixed_ips': [{ + 'subnet': 'changed-subnet-id-2'}]}, + 'VDU2_CP2': {'network': 'changed-nw-resource-id-2'}}}} + + return stack_param + + +def get_vnf_attribute_dict(): + vnf_attribute_dict = dict() + vnf_attribute_dict.update( + {'stack_param': str(get_original_stack_param())}) + + return vnf_attribute_dict + + def get_lcm_op_occs_object(operation="INSTANTIATE", error_point=0): vnf_lcm_op_occs = objects.VnfLcmOpOcc( 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 aeaa31ce9..6d710784d 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 @@ -2178,3 +2178,126 @@ class TestOpenStack(base.FixturedTestCase): None, self.context, vnf_dict, 'SP1', None, None) self.assertEqual('30435eb8-1472-4cbc-abbe-00b395165ce7', grp_id) + + @mock.patch('tacker.common.clients.OpenstackClients') + @mock.patch('tacker.vnflcm.utils.get_base_nest_hot_dict') + def test_change_ext_conn_vnf(self, + mock_get_base_hot_dict, + mock_mock_OpenstackClients_heat): + inst_vnf_info = fd_utils.get_vnf_instantiated_info() + + vnf_instance = fd_utils.get_vnf_instance_object( + instantiated_vnf_info=inst_vnf_info) + + nested_hot_dict = {'parameters': {'vnf': 'test'}} + mock_get_base_hot_dict.return_value = \ + self._read_file(), nested_hot_dict + + vnf_dict['vnfd'] = fd_utils.get_vnfd_dict() + vnf_dict['attributes'] = fd_utils.get_vnf_attribute_dict() + + vim_connection_info = fd_utils.get_vim_connection_info_object() + change_ext_conn_request = fd_utils.get_change_ext_conn_request() + + self.openstack.change_ext_conn_vnf( + self.context, vnf_instance, vnf_dict, + vim_connection_info, change_ext_conn_request) + + self.assertEqual( + str(fd_utils.get_expect_stack_param()), + vnf_dict['attributes']['stack_param']) + + def test_change_ext_conn_vnf_wait(self): + inst_vnf_info = fd_utils.get_vnf_instantiated_info() + + vnf_instance = fd_utils.get_vnf_instance_object( + instantiated_vnf_info=inst_vnf_info) + + vim_connection_info = fd_utils.get_vim_connection_info_object() + + # Mock various heat APIs that will be called by heatclient + # during the process of change_ext_conn_vnf_wait. + self._response_in_wait_until_stack_ready(["UPDATE_IN_PROGRESS", + "UPDATE_COMPLETE"]) + + stack = self.openstack.change_ext_conn_vnf_wait( + self.context, vnf_instance, vim_connection_info) + self.assertEqual('UPDATE_COMPLETE', stack.stack_status) + + def test_change_ext_conn_vnf_wait_fail(self): + inst_vnf_info = fd_utils.get_vnf_instantiated_info() + + vnf_instance = fd_utils.get_vnf_instance_object( + instantiated_vnf_info=inst_vnf_info) + + vim_connection_info = fd_utils.get_vim_connection_info_object() + + # Mock various heat APIs that will be called by heatclient + # during the process of change_ext_conn_vnf_wait. + self._response_in_wait_until_stack_ready(["UPDATE_IN_PROGRESS"]) + self.openstack.STACK_RETRIES = 1 + result = self.assertRaises(vnfm.VNFChangeExtConnWaitFailed, + self.openstack.change_ext_conn_vnf_wait, self.context, + vnf_instance, vim_connection_info) + + expected_msg = ("VNF ChangeExtConn action is not completed within 10 " + "seconds ""on stack %s") % inst_vnf_info.instance_id + self.assertIn(expected_msg, str(result)) + + def test_post_change_ext_conn_vnf(self): + v_s_resource_info = fd_utils.get_virtual_storage_resource_info( + desc_id="storage1", set_resource_id=False) + + storage_resource_ids = [v_s_resource_info.id] + vnfc_resource_info = fd_utils.get_vnfc_resource_info(vdu_id="VDU_VNF", + storage_resource_ids=storage_resource_ids, set_resource_id=False) + + v_l_resource_info = fd_utils.get_virtual_link_resource_info( + vnfc_resource_info.vnfc_cp_info[0].vnf_link_port_id, + vnfc_resource_info.vnfc_cp_info[0].id) + + inst_vnf_info = fd_utils.get_vnf_instantiated_info( + virtual_storage_resource_info=[v_s_resource_info], + vnf_virtual_link_resource_info=[v_l_resource_info], + vnfc_resource_info=[vnfc_resource_info]) + + vnf_instance = fd_utils.get_vnf_instance_object( + instantiated_vnf_info=inst_vnf_info) + + vim_connection_info = fd_utils.get_vim_connection_info_object() + resources = [{'resource_name': vnfc_resource_info.vdu_id, + 'resource_type': vnfc_resource_info.compute_resource. + vim_level_resource_type, + 'physical_resource_id': uuidsentinel.vdu_resource_id}, + {'resource_name': v_s_resource_info.virtual_storage_desc_id, + 'resource_type': v_s_resource_info.storage_resource. + vim_level_resource_type, + 'physical_resource_id': uuidsentinel.storage_resource_id}, + {'resource_name': vnfc_resource_info.vnfc_cp_info[0].cpd_id, + 'resource_type': inst_vnf_info.vnf_virtual_link_resource_info[0]. + vnf_link_ports[0].resource_handle.vim_level_resource_type, + 'physical_resource_id': uuidsentinel.cp1_resource_id}] + + self._responses_in_stack_list(inst_vnf_info.instance_id, + resources=resources) + self.openstack.post_change_ext_conn_vnf( + self.context, vnf_instance, vim_connection_info) + self.assertEqual(vnf_instance.instantiated_vnf_info. + vnfc_resource_info[0].metadata['stack_id'], + inst_vnf_info.instance_id) + + # Check if vnfc resource "VDU_VNF" is set with resource_id + self.assertEqual(uuidsentinel.vdu_resource_id, + vnf_instance.instantiated_vnf_info.vnfc_resource_info[0]. + compute_resource.resource_id) + + # Check if virtual storage resource "storage1" is set with resource_id + self.assertEqual(uuidsentinel.storage_resource_id, + vnf_instance.instantiated_vnf_info. + virtual_storage_resource_info[0].storage_resource.resource_id) + + # Check if virtual link port "CP1" is set with resource_id + self.assertEqual(uuidsentinel.cp1_resource_id, + vnf_instance.instantiated_vnf_info. + vnf_virtual_link_resource_info[0].vnf_link_ports[0]. + resource_handle.resource_id) diff --git a/tacker/vnflcm/utils.py b/tacker/vnflcm/utils.py index e0b0d1ffe..95f54f6d2 100644 --- a/tacker/vnflcm/utils.py +++ b/tacker/vnflcm/utils.py @@ -562,6 +562,22 @@ def _build_instantiated_vnf_info(vnfd_dict, instantiate_vnf_req, vnf_instance.instantiated_vnf_info = inst_vnf_info +def _update_instantiated_vnf_info(change_ext_conn_req, vnf_instance): + inst_vnf_info = vnf_instance.instantiated_vnf_info + tmp_insta_vnf_info = copy.deepcopy(inst_vnf_info) + + inst_vnf_info.ext_cp_info = _update_ext_cp_info(change_ext_conn_req, + inst_vnf_info=tmp_insta_vnf_info) + inst_vnf_info.ext_virtual_link_info = _update_ext_virtual_link_info( + change_ext_conn_req, inst_vnf_info=tmp_insta_vnf_info) + + inst_vnf_info.vnf_virtual_link_resource_info = \ + _update_vnf_virtual_link_resource_info(change_ext_conn_req, + inst_vnf_info) + + vnf_instance.instantiated_vnf_info = inst_vnf_info + + def _get_compute_nodes(vnfd_dict, instantiate_vnf_req): """Read the node templates and prepare VDU data in below format @@ -729,6 +745,40 @@ def _build_vnf_virtual_link_resource_info(node_templates, instantiate_vnf_req, return virtual_link_resource_info_list +def _update_vnf_virtual_link_resource_info(change_ext_conn_req, + inst_vnf_info): + def _update(change_ext_conn_req, vnf_vl_resource_info): + for ext_virtual_link in change_ext_conn_req.ext_virtual_links: + if (ext_virtual_link.id == + vnf_vl_resource_info.vnf_virtual_link_desc_id): + res_handle = objects.ResourceHandle() + res_handle.resource_id = ext_virtual_link.resource_id + nw_res = vnf_vl_resource_info.network_resource + res_handle.vim_connection_id = nw_res.vim_connection_id + res_handle.vim_level_resource_type = \ + nw_res.vim_level_resource_type + new_vnf_vl_resource_info = \ + objects.VnfVirtualLinkResourceInfo( + id=vnf_vl_resource_info.id, + vnf_virtual_link_desc_id=vnf_vl_resource_info. + vnf_virtual_link_desc_id, + network_resource=res_handle, + vnf_link_ports=vnf_vl_resource_info.vnf_link_ports) + return new_vnf_vl_resource_info + return None + + vnf_virtual_link_resource_list = [] + for vnf_vl_res_info in inst_vnf_info.vnf_virtual_link_resource_info: + updated_vnf_vl_res_info = \ + _update(change_ext_conn_req, vnf_vl_res_info) + if updated_vnf_vl_res_info: + vnf_virtual_link_resource_list.append(updated_vnf_vl_res_info) + else: + vnf_virtual_link_resource_list.append(vnf_vl_res_info) + + return vnf_virtual_link_resource_list + + def _build_vnf_cp_info(instantiate_vnf_req, cp_list): vnfc_cp_info_list = [] @@ -831,6 +881,34 @@ def _set_ext_cp_info(instantiate_vnf_req, inst_vnf_info=None): return ext_cp_info_list +def _update_ext_cp_info(change_ext_conn_req, inst_vnf_info): + + def _update(change_ext_conn_req, ext_cp_info): + for ext_virt_link in change_ext_conn_req.ext_virtual_links: + if not ext_virt_link.ext_cps: + continue + for ext_cp in ext_virt_link.ext_cps: + if ext_cp.cpd_id == ext_cp_info.cpd_id: + new_ext_cp_info = objects.VnfExtCpInfo( + id=ext_cp_info.id, + cpd_id=ext_cp.cpd_id, + cp_protocol_info=_set_cp_protocol_info(ext_cp), + associated_vnfc_cp_id=ext_cp_info. + associated_vnfc_cp_id) + return new_ext_cp_info + return None + + ext_cp_info_list = [] + for ext_cp_info in inst_vnf_info.ext_cp_info: + updated_ext_cp_info = _update(change_ext_conn_req, ext_cp_info) + if updated_ext_cp_info: + ext_cp_info_list.append(updated_ext_cp_info) + else: + ext_cp_info_list.append(ext_cp_info) + + return ext_cp_info_list + + def _get_ext_link_port_id(ext_virtual_link, cpd_id): if not ext_virtual_link.ext_link_ports: return @@ -926,6 +1004,38 @@ def _set_ext_virtual_link_info(instantiate_vnf_req, ext_cp_info): return ext_virtual_link_list +def _update_ext_virtual_link_info(change_ext_conn_req, inst_vnf_info): + + def _update(change_ext_conn_req, ext_virtual_link_info): + for ext_virtual_link in change_ext_conn_req.ext_virtual_links: + if ext_virtual_link.id == ext_virtual_link_info.id: + res_handle = objects.ResourceHandle() + res_handle.resource_id = ext_virtual_link.resource_id + new_ext_virtual_link_info = objects.ExtVirtualLinkInfo( + id=ext_virtual_link_info.id, + resource_handle=res_handle, + ext_link_ports=ext_virtual_link_info.ext_link_ports) + res_handle.vim_connection_id = \ + ext_virtual_link_info.resource_handle.vim_connection_id + new_ext_virtual_link_info = objects.ExtVirtualLinkInfo( + id=ext_virtual_link_info.id, + resource_handle=res_handle, + ext_link_ports=ext_virtual_link_info.ext_link_ports) + return new_ext_virtual_link_info + return None + + ext_virtual_link_list = [] + for ext_virtual_link_info in inst_vnf_info.ext_virtual_link_info: + updated_ext_virtual_link_info = \ + _update(change_ext_conn_req, ext_virtual_link_info) + if updated_ext_virtual_link_info: + ext_virtual_link_list.append(updated_ext_virtual_link_info) + else: + ext_virtual_link_list.append(ext_virtual_link_info) + + return ext_virtual_link_list + + def _set_ext_link_port(ext_virtual_links, ext_cp_info): ext_link_port_list = [] @@ -1152,3 +1262,54 @@ def get_target_vdu_def_dict(extract_policy_infos, aspect_id, tosca): vdu_def_dict[node_name] = node_value return vdu_def_dict + + +def _get_changed_ext_connectivity( + old_vnf_instance=None, new_vnf_instance=None): + + changed_ext_connectivities = [] + if not old_vnf_instance or not new_vnf_instance: + return changed_ext_connectivities + + old_vnf_vl_res_info = \ + old_vnf_instance.instantiated_vnf_info.\ + vnf_virtual_link_resource_info + new_vnf_vl_res_info = \ + new_vnf_instance.instantiated_vnf_info.\ + vnf_virtual_link_resource_info + + def _compare_vnf_link_ports(old_vnf_link_ports, + new_vnf_link_ports): + differed_vnf_link_ports = [] + for old_vnf_link_port in old_vnf_link_ports: + for new_vnf_link_port in new_vnf_link_ports: + if old_vnf_link_port.id == new_vnf_link_port.id: + if (old_vnf_link_port.resource_handle.resource_id != + new_vnf_link_port.resource_handle.resource_id): + differed_vnf_link_ports.append(new_vnf_link_port) + return differed_vnf_link_ports + + for old_vl_res in old_vnf_vl_res_info: + for new_vl_res in new_vnf_vl_res_info: + if old_vl_res.id == new_vl_res.id: + changed_ext_connectivity = objects.ExtVirtualLinkInfo( + id=new_vl_res.id, + resource_handle=new_vl_res.network_resource, + ext_link_ports=[]) + differed_vnf_link_ports = _compare_vnf_link_ports( + old_vl_res.vnf_link_ports, + new_vl_res.vnf_link_ports) + for link_port in differed_vnf_link_ports: + changed_ext_link_port = objects.ExtLinkPortInfo( + id=link_port.id, + resource_handle=link_port.resource_handle, + cp_instance_id=link_port.cp_instance_id) + changed_ext_connectivity.ext_link_ports.\ + append(changed_ext_link_port) + if changed_ext_connectivity.ext_link_ports: + changed_ext_connectivities.append( + changed_ext_connectivity) + + LOG.debug('changed_ext_connectivities: {}'.format( + changed_ext_connectivities)) + return changed_ext_connectivities diff --git a/tacker/vnflcm/vnflcm_driver.py b/tacker/vnflcm/vnflcm_driver.py index 7ef9ee80c..032e49dfe 100644 --- a/tacker/vnflcm/vnflcm_driver.py +++ b/tacker/vnflcm/vnflcm_driver.py @@ -1742,3 +1742,101 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver): vnf_instance, operation_params, vim_connection_info) + + def _change_ext_conn_vnf(self, context, vnf_instance, vnf_dict, + vim_connection_info, change_ext_conn_req): + inst_vnf_info = vnf_instance.instantiated_vnf_info + try: + self._vnf_manager.invoke( + vim_connection_info.vim_type, 'change_ext_conn_vnf', + context=context, vnf_instance=vnf_instance, vnf_dict=vnf_dict, + vim_connection_info=vim_connection_info, + change_ext_conn_req=change_ext_conn_req) + except Exception as exp: + with excutils.save_and_reraise_exception() as exc_ctxt: + exc_ctxt.reraise = False + LOG.error("Failed to change external connectivity " + "vnf %(id)s in infra driver. " + "Error: %(error)s", {"id": vnf_instance.id, "error": + encodeutils.exception_to_unicode(exp)}) + raise exceptions.VnfChangeExtConnFailed(id=vnf_instance.id, + error=encodeutils.exception_to_unicode(exp)) + vnf_dict['current_error_point'] = fields.ErrorPoint.POST_VIM_CONTROL + try: + self._vnf_manager.invoke( + vim_connection_info.vim_type, 'change_ext_conn_vnf_wait', + context=context, vnf_instance=vnf_instance, + vim_connection_info=vim_connection_info) + except Exception as exp: + LOG.error("Failed to update vnf %(id)s resources for instance " + "%(instance)s. Error: %(error)s", + {'id': vnf_instance.id, 'instance': + inst_vnf_info.instance_id, 'error': + encodeutils.exception_to_unicode(exp)}) + raise exceptions.VnfChangeExtConnWaitFailed( + id=vnf_instance.id, + error=encodeutils.exception_to_unicode(exp)) + + @log.log + @revert_to_error_task_state + def change_ext_conn_vnf( + self, + context, + vnf_instance, + vnf_dict, + change_ext_conn_req): + LOG.info("Request received for changing external connectivity " + "vnf '%s'", vnf_instance.id) + + vnfd_dict = vnflcm_utils._get_vnfd_dict( + context, vnf_instance.vnfd_id, + vnf_instance.instantiated_vnf_info.flavour_id) + + vnf_dict['current_error_point'] = EP.VNF_CONFIG_START + if vnf_dict['before_error_point'] <= EP.VNF_CONFIG_START: + # TODO(esto-aln): grant_request here is planned to pass + # as a parameter, however due to grant_request are not + # passed from conductor to vnflcm_driver, thus we put Null + # value to grant and grant_reqeust temporary. + # This part will be updated in next release. + self._mgmt_manager.invoke( + self._load_vnf_interface( + context, 'change_external_connectivity_start', + vnf_instance, vnfd_dict), + 'change_external_connectivity_start', context=context, + vnf_instance=vnf_instance, + change_ext_conn_request=change_ext_conn_req, + grant=vnf_dict.get('grant'), grant_request=None) + + vnf_dict['current_error_point'] = EP.PRE_VIM_CONTROL + if vnf_dict['before_error_point'] <= EP.POST_VIM_CONTROL: + vim_info = vnflcm_utils._get_vim(context, + vnf_instance.vim_connection_info) + + vim_connection_info = \ + objects.VimConnectionInfo.obj_from_primitive( + vim_info, context) + + self._change_ext_conn_vnf(context, vnf_instance, vnf_dict, + vim_connection_info, change_ext_conn_req) + + # Since there is no processing corresponding to + # EP.INTERNAL_PROCESSING, it transitions to EP.VNF_CONFIG_END. + vnf_dict['current_error_point'] = EP.VNF_CONFIG_END + if vnf_dict['before_error_point'] <= EP.VNF_CONFIG_END: + # TODO(esto-aln): grant_request here is planned to pass + # as a parameter, however due to grant_request are not + # passed from conductor to vnflcm_driver, thus we put Null + # value to grant and grant_reqeust temporary. + # This part will be updated in next release. + self._mgmt_manager.invoke( + self._load_vnf_interface( + context, 'change_external_connectivity_end', + vnf_instance, vnfd_dict), + 'change_external_connectivity_end', context=context, + vnf_instance=vnf_instance, + change_ext_conn_request=change_ext_conn_req, + grant=vnf_dict.get('grant'), grant_request=None) + + LOG.info("Request received for changing external connectivity " + "vnf '%s' is completed successfully", vnf_instance.id) diff --git a/tacker/vnfm/infra_drivers/abstract_driver.py b/tacker/vnfm/infra_drivers/abstract_driver.py index 34cbd098d..f97379e7e 100644 --- a/tacker/vnfm/infra_drivers/abstract_driver.py +++ b/tacker/vnfm/infra_drivers/abstract_driver.py @@ -131,3 +131,35 @@ class VnfAbstractDriver(extensions.PluginInterface, metaclass=abc.ABCMeta): parameters passed in the heal request """ pass + + @abc.abstractmethod + def change_ext_conn_vnf(self, context, vnf_instance, vnf_dict, + vim_connection_info, change_ext_conn_req): + """Change external VNF connectivity + + :param context: A RequestContext + :param vnf_instance: tacker.objects.VnfInstance to be changed + :param vnf_dict: + :param vim_connection_info: Credentials to initialize Vim connection + :param change_ext_conn_req: tacker.objects.ChangeExtconnRequest object + containing parameters passed in the + change_ext_conn request + """ + pass + + @abc.abstractmethod + def change_ext_conn_vnf_wait(self, context, vnf_instance, + vim_connection_info): + """Check vnf external connnectivity is changed successfully""" + pass + + @abc.abstractmethod + def post_change_ext_conn_vnf(self, context, vnf_instance, + vim_connection_info): + """Update resource information for each external VL/LINKPORT resources + + :param context: A RequestContext + :param vnf_instance: tacker.objects.VnfInstance to be changed + :param vim_connection_info: Credentials to initialize Vim connection + """ + pass diff --git a/tacker/vnfm/infra_drivers/kubernetes/kubernetes_driver.py b/tacker/vnfm/infra_drivers/kubernetes/kubernetes_driver.py index 90ab032e7..48bd1f490 100644 --- a/tacker/vnfm/infra_drivers/kubernetes/kubernetes_driver.py +++ b/tacker/vnfm/infra_drivers/kubernetes/kubernetes_driver.py @@ -2030,6 +2030,18 @@ class Kubernetes(abstract_driver.VnfAbstractDriver, finally: self.clean_authenticate_vim(auth_cred, file_descriptor) + def change_ext_conn_vnf(self, context, vnf_instance, vnf_dict, + vim_connection_info, change_ext_conn_req): + raise NotImplementedError() + + def change_ext_conn_vnf_wait(self, context, vnf_instance, + vim_connection_info): + raise NotImplementedError() + + def post_change_ext_conn_vnf(self, context, vnf_instance, + vim_connection_info): + raise NotImplementedError() + def get_scale_ids(self, plugin, context, diff --git a/tacker/vnfm/infra_drivers/noop.py b/tacker/vnfm/infra_drivers/noop.py index 6a19c2ef1..511582013 100644 --- a/tacker/vnfm/infra_drivers/noop.py +++ b/tacker/vnfm/infra_drivers/noop.py @@ -104,3 +104,15 @@ class VnfNoop(abstract_driver.VnfAbstractDriver): def post_heal_vnf(self, context, vnf_instance, vim_connection_info, heal_vnf_request): pass + + def change_ext_conn_vnf(self, context, vnf_instance, vnf_dict, + vim_connection_info, change_ext_conn_req): + pass + + def change_ext_conn_vnf_wait(self, context, vnf_instance, + vim_connection_info): + pass + + def post_change_ext_conn_vnf(self, context, vnf_instance, + vim_connection_info): + pass diff --git a/tacker/vnfm/infra_drivers/openstack/openstack.py b/tacker/vnfm/infra_drivers/openstack/openstack.py index 6c717214e..aaed19bd0 100644 --- a/tacker/vnfm/infra_drivers/openstack/openstack.py +++ b/tacker/vnfm/infra_drivers/openstack/openstack.py @@ -996,21 +996,6 @@ class OpenStack(abstract_driver.VnfAbstractDriver, vnfd_dict['instance_id'] = instance_id return instance_id - @log.log - def post_vnf_instantiation(self, context, vnf_instance, - vim_connection_info, instantiate_vnf_req): - inst_vnf_info = vnf_instance.instantiated_vnf_info - access_info = vim_connection_info.access_info - - heatclient = hc.HeatClient(access_info, - region_name=access_info.get('region')) - stack_resources = self._get_stack_resources( - inst_vnf_info.instance_id, heatclient) - - self._update_vnfc_resources(vnf_instance, stack_resources, - vim_connection_info) - self._update_vnfc_info(vnf_instance) - def _update_resource_handle(self, vnf_instance, resource_handle, stack_resources, resource_name, vim_connection_info): @@ -2033,3 +2018,101 @@ class OpenStack(abstract_driver.VnfAbstractDriver, return_rs_list = before_rs_list return return_list, return_rs_list, grp.physical_resource_id + + @log.log + def change_ext_conn_vnf(self, context, vnf_instance, vnf_dict, + vim_connection_info, change_ext_conn_req): + + base_hot_dict, nested_hot_dict = \ + vnflcm_utils.get_base_nest_hot_dict( + context, + vnf_instance.instantiated_vnf_info.flavour_id, + vnf_instance.vnfd_id) + stack_param = yaml.safe_load( + vnf_dict['attributes']['stack_param']) + + LOG.debug('before stack_param: {}'.format(stack_param)) + cp_param = stack_param['nfv']['CP'] + + for ext_virtual_link in change_ext_conn_req.ext_virtual_links: + for ext_cp in ext_virtual_link.ext_cps: + if ext_cp.cpd_id not in cp_param.keys(): + continue + cpd_id = ext_cp.cpd_id + network = cp_param[cpd_id].get('network', None) + if network and network != ext_virtual_link.resource_id: + cp_param[cpd_id].update( + dict(network=ext_virtual_link.resource_id)) + + try: + ip_addr = ext_cp.cp_config[0].cp_protocol_data[0].\ + ip_over_ethernet.ip_addresses[0] + except IndexError: + # If the element under ext_cp does not exist, + # and ip_addresses cannot get, do nothing + continue + + fixed_ips = dict() + updated_fixed_ips = [] + if ip_addr.fixed_addresses: + for address in ip_addr.fixed_addresses: + fixed_ips = dict(ip_address=address) + if ip_addr.subnet_id: + fixed_ips.update(dict(subnet=ip_addr.subnet_id)) + updated_fixed_ips.append(fixed_ips) + elif ip_addr.num_dynamic_addresses > 0: + if ip_addr.subnet_id: + fixed_ips.update(dict(subnet=ip_addr.subnet_id)) + updated_fixed_ips.append(fixed_ips) + if updated_fixed_ips: + cp_param[cpd_id].update( + dict(fixed_ips=updated_fixed_ips)) + + LOG.debug('after stack_param: {}'.format(stack_param)) + + access_info = vim_connection_info.access_info + heatclient = hc.HeatClient(access_info, + region_name=access_info.get('region')) + + # Update heat-stack with BaseHOT and parameters + self._update_stack_with_user_data( + heatclient, vnf_instance, base_hot_dict, nested_hot_dict, + stack_param, vnf_instance.instantiated_vnf_info.instance_id) + vnf_dict['attributes'].update({'stack_param': str(stack_param)}) + + @log.log + def change_ext_conn_vnf_wait(self, context, vnf_instance, + vim_connection_info): + access_info = vim_connection_info.access_info + region_name = access_info.get('region') + inst_vnf_info = vnf_instance.instantiated_vnf_info + stack = self._wait_until_stack_ready(inst_vnf_info.instance_id, + access_info, infra_cnst.STACK_UPDATE_IN_PROGRESS, + infra_cnst.STACK_UPDATE_COMPLETE, + vnfm.VNFChangeExtConnWaitFailed, + region_name=region_name) + return stack + + def _update_vnfc_resources_and_info(self, context, vnf_instance, + vim_connection_info): + inst_vnf_info = vnf_instance.instantiated_vnf_info + access_info = vim_connection_info.access_info + heatclient = hc.HeatClient(access_info, + region_name=access_info.get('region')) + stack_resources = self._get_stack_resources( + inst_vnf_info.instance_id, heatclient) + self._update_vnfc_resources(vnf_instance, stack_resources, + vim_connection_info) + self._update_vnfc_info(vnf_instance) + + @log.log + def post_vnf_instantiation(self, context, vnf_instance, + vim_connection_info, instantiate_vnf_req): + self._update_vnfc_resources_and_info( + context, vnf_instance, vim_connection_info) + + @log.log + def post_change_ext_conn_vnf(self, context, vnf_instance, + vim_connection_info): + self._update_vnfc_resources_and_info( + context, vnf_instance, vim_connection_info) diff --git a/tacker/vnfm/mgmt_drivers/vnflcm_abstract_driver.py b/tacker/vnfm/mgmt_drivers/vnflcm_abstract_driver.py index 462b37831..ba22f9300 100644 --- a/tacker/vnfm/mgmt_drivers/vnflcm_abstract_driver.py +++ b/tacker/vnfm/mgmt_drivers/vnflcm_abstract_driver.py @@ -83,3 +83,17 @@ class VnflcmMgmtAbstractDriver(metaclass=abc.ABCMeta): heal_vnf_request, grant, grant_request, **kwargs): pass + + @abc.abstractmethod + def change_external_connectivity_start( + self, context, vnf_instance, + change_ext_conn_request, grant, + grant_request, **kwargs): + pass + + @abc.abstractmethod + def change_external_connectivity_end( + self, context, vnf_instance, + change_ext_conn_request, grant, + grant_request, **kwargs): + pass diff --git a/tacker/vnfm/mgmt_drivers/vnflcm_noop.py b/tacker/vnfm/mgmt_drivers/vnflcm_noop.py index ddf495c4b..bf75395b9 100644 --- a/tacker/vnfm/mgmt_drivers/vnflcm_noop.py +++ b/tacker/vnfm/mgmt_drivers/vnflcm_noop.py @@ -74,3 +74,17 @@ class VnflcmMgmtNoop(vnflcm_abstract_driver.VnflcmMgmtAbstractDriver): heal_vnf_request, grant, grant_request, **kwargs): pass + + @log.log + def change_external_connectivity_start( + self, context, vnf_instance, + change_ext_conn_request, grant, + grant_request, **kwargs): + pass + + @log.log + def change_external_connectivity_end( + self, context, vnf_instance, + change_ext_conn_request, grant, + grant_request, **kwargs): + pass