diff --git a/releasenotes/notes/add-v2-change-vnfpkg-api-7f993cf283a53aab.yaml b/releasenotes/notes/add-v2-change-vnfpkg-api-7f993cf283a53aab.yaml new file mode 100644 index 000000000..0d9b551ca --- /dev/null +++ b/releasenotes/notes/add-v2-change-vnfpkg-api-7f993cf283a53aab.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add Change Current VNF Package API based on ETSI NFV specifications. + Tacker supports a VNF upgrade using this API. + Currently, it only supports "RollingUpdate" out of several methods for + a VNF upgrade. + diff --git a/tacker/sol_refactored/api/policies/vnflcm_v2.py b/tacker/sol_refactored/api/policies/vnflcm_v2.py index f0ee7f71b..1e814c200 100644 --- a/tacker/sol_refactored/api/policies/vnflcm_v2.py +++ b/tacker/sol_refactored/api/policies/vnflcm_v2.py @@ -129,6 +129,15 @@ rules = [ 'path': VNF_INSTANCES_ID_PATH + '/change_ext_conn'} ] ), + policy.DocumentedRuleDefault( + name=POLICY_NAME.format('change_vnfpkg'), + check_str=RULE_ANY, + description="Change vnf package.", + operations=[ + {'method': 'POST', + 'path': VNF_INSTANCES_ID_PATH + '/change_vnfpkg'} + ] + ), # NOTE: add when the operation supported policy.DocumentedRuleDefault( name=POLICY_NAME.format('subscription_create'), diff --git a/tacker/sol_refactored/api/router.py b/tacker/sol_refactored/api/router.py index f1e215f10..36ae18502 100644 --- a/tacker/sol_refactored/api/router.py +++ b/tacker/sol_refactored/api/router.py @@ -40,6 +40,7 @@ class VnflcmAPIRouterV2(sol_wsgi.SolAPIRouter): ("/vnf_instances/{id}/terminate", {"POST": "terminate"}), ("/vnf_instances/{id}/scale", {"POST": "scale"}), ("/vnf_instances/{id}/change_ext_conn", {"POST": "change_ext_conn"}), + ("/vnf_instances/{id}/change_vnfpkg", {"POST": "change_vnfpkg"}), ("/api_versions", {"GET": "api_versions"}), ("/subscriptions", {"GET": "subscription_list", "POST": "subscription_create"}), diff --git a/tacker/sol_refactored/api/schemas/vnflcm_v2.py b/tacker/sol_refactored/api/schemas/vnflcm_v2.py index 30bcfc4d5..b6f41f4f4 100644 --- a/tacker/sol_refactored/api/schemas/vnflcm_v2.py +++ b/tacker/sol_refactored/api/schemas/vnflcm_v2.py @@ -77,6 +77,33 @@ TerminateVnfRequest_V200 = { 'additionalProperties': True, } +# SOL002 5.5.2.11a +# SOL003 5.5.2.11a +ChangeCurrentVnfPkgRequest_V200 = { + 'type': 'object', + 'properties': { + 'vnfdId': common_types.Identifier, + 'extVirtualLinks': { + 'type': 'array', + 'items': common_types.ExtVirtualLinkData}, + 'extManagedVirtualLinks': { + 'type': 'array', + 'items': common_types.ExtManagedVirtualLinkData}, + # NOTE: 'vimConnectionInfo' field supports only NFV-SOL 003 + 'vimConnectionInfo': { + 'type': 'object', + 'patternProperties': { + '^.*$': common_types.VimConnectionInfo + }, + }, + 'additionalParams': parameter_types.keyvalue_pairs, + 'extensions': parameter_types.keyvalue_pairs, + 'vnfConfigurableProperties': parameter_types.keyvalue_pairs + }, + 'required': ['vnfdId'], + 'additionalProperties': True, +} + # SOL003 5.5.2.5 ScaleVnfRequest_V200 = { 'type': 'object', @@ -263,6 +290,7 @@ _LifecycleChangeNotificationsFilter = { 'SCALE', 'SCALE_TO_LEVEL', 'CHANGE_FLAVOUR', + 'CHANGE_VNFPKG', 'TERMINATE', 'HEAL', 'OPERATE', diff --git a/tacker/sol_refactored/common/cinder_utils.py b/tacker/sol_refactored/common/cinder_utils.py new file mode 100644 index 000000000..1ccb8d8a3 --- /dev/null +++ b/tacker/sol_refactored/common/cinder_utils.py @@ -0,0 +1,45 @@ +# Copyright (C) 2022 Fujitsu +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +from openstack import connection +from openstack import exceptions as os_ex +from oslo_log import log as logging + + +LOG = logging.getLogger(__name__) + + +class CinderClient(object): + + def __init__(self, vim_info): + auth = dict( + auth_url=vim_info.interfaceInfo['endpoint'], + username=vim_info.accessInfo['username'], + password=vim_info.accessInfo['password'], + project_name=vim_info.accessInfo['project'], + user_domain_name=vim_info.accessInfo['userDomain'], + project_domain_name=vim_info.accessInfo['projectDomain'] + ) + self.conn = connection.Connection( + region_name=vim_info.accessInfo.get('region'), + auth=auth, + identity_interface='internal') + + def get_volume(self, volume_id): + try: + return self.conn.volume.get_volume(volume_id) + except os_ex.ResourceNotFound: + LOG.debug("volume %s not found.", volume_id) diff --git a/tacker/sol_refactored/common/common_script_utils.py b/tacker/sol_refactored/common/common_script_utils.py new file mode 100644 index 000000000..e33f0d671 --- /dev/null +++ b/tacker/sol_refactored/common/common_script_utils.py @@ -0,0 +1,279 @@ +# Copyright (C) 2022 Fujitsu +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from tacker.sol_refactored.common import exceptions as sol_ex +from tacker.sol_refactored.common import vnfd_utils + + +def get_vnfd(vnfd_id, csar_dir): + vnfd = vnfd_utils.Vnfd(vnfd_id) + vnfd.init_from_csar_dir(csar_dir) + return vnfd + + +def get_vdu_info(grant, inst, vnfd): + volume_name = '' + volume_size = '' + flavour_id = inst['instantiatedVnfInfo']['flavourId'] + vdu_nodes = vnfd.get_vdu_nodes(flavour_id) + storage_nodes = vnfd.get_storage_nodes(flavour_id) + vdu_info_dict = {} + for name, node in vdu_nodes.items(): + flavor = get_param_flavor(name, flavour_id, vnfd, grant) + image = get_param_image(name, flavour_id, vnfd, grant) + vdu_storage_names = vnfd.get_vdu_storages(node) + for vdu_storage_name in vdu_storage_names: + if storage_nodes[vdu_storage_name].get( + 'properties', {}).get('sw_image_data'): + image = get_param_image(vdu_storage_name, flavour_id, vnfd, + grant) + volume_name = vdu_storage_name + volume_size = storage_nodes[vdu_storage_name].get( + 'properties', {}).get( + 'virtual_block_storage_data', '').get( + 'size_of_storage', '' + ) + volume_size = volume_size.rstrip(' GB') + if not volume_size.isdigit(): + raise sol_ex.VmRunningFailed( + error_info='The volume size set in VNFD is invalid.') + break + + vdu_info_dict[name] = { + "flavor": flavor, + "image": image + } + + if volume_name: + vdu_info_dict[name]['volume_info'] = { + "volume_name": volume_name, + "volume_size": volume_size + } + return vdu_info_dict + + +def init_nfv_dict(hot_template): + get_params = [] + + def _get_get_param(prop): + if isinstance(prop, dict): + for key, value in prop.items(): + if key == 'get_param': + get_params.append(value) + else: + _get_get_param(value) + elif isinstance(prop, list): + for value in prop: + _get_get_param(value) + + for res in hot_template.get('resources', {}).values(): + _get_get_param(res.get('properties', {})) + + nfv = {} + + for param in get_params: + if (not isinstance(param, list) or len(param) < 4 or + param[0] != 'nfv'): + continue + parent = nfv + for item in param[1:-1]: + parent.setdefault(item, {}) + parent = parent[item] + parent[param[-1]] = None + + # TODO(YiFeng): enhance to handle list + # NOTE: List is not considered here and only 'fixed_ips' is treated as + # list in userdata_default.py at the moment. + # Note that if handling list is enhanced, userdata_default.py is + # necessary to modify. + return nfv + + +def get_param_flavor(vdu_name, flavour_id, vnfd, grant): + # try to get from grant + if 'vimAssets' in grant: + assets = grant['vimAssets'] + if 'computeResourceFlavours' in assets: + flavours = assets['computeResourceFlavours'] + for flavour in flavours: + if flavour['vnfdVirtualComputeDescId'] == vdu_name: + return flavour['vimFlavourId'] + + # if specified in VNFD, use it + # NOTE: if not found. parameter is set to None. + # may be error when stack create + return vnfd.get_compute_flavor(flavour_id, vdu_name) + + +def get_param_image(vdu_name, flavour_id, vnfd, grant): + # try to get from grant + if 'vimAssets' in grant: + assets = grant['vimAssets'] + if 'softwareImages' in assets: + images = assets['softwareImages'] + for image in images: + if image['vnfdSoftwareImageId'] == vdu_name: + return image['vimSoftwareImageId'] + + # if specified in VNFD, use it + # NOTE: if not found. parameter is set to None. + # may be error when stack create + sw_images = vnfd.get_sw_image(flavour_id) + for name, image in sw_images.items(): + if name == vdu_name: + return image + + +def get_param_zone(vdu_name, grant_req, grant): + if 'zones' not in grant or 'addResources' not in grant: + return + + for res in grant['addResources']: + if 'zoneId' not in res: + continue + for req_res in grant_req['addResources']: + if req_res['id'] == res['resourceDefinitionId']: + if req_res.get('resourceTemplateId') == vdu_name: + for zone in grant['zones']: + if zone['id'] == res['zoneId']: # must be found + return zone['zoneId'] + + +def get_current_capacity(vdu_name, inst): + count = 0 + inst_vnfcs = (inst.get('instantiatedVnfInfo', {}) + .get('vnfcResourceInfo', [])) + for inst_vnfc in inst_vnfcs: + if inst_vnfc['vduId'] == vdu_name: + count += 1 + + return count + + +def get_param_capacity(vdu_name, inst, grant_req): + # NOTE: refer grant_req here since interpretation of VNFD was done when + # making grant_req. + count = get_current_capacity(vdu_name, inst) + + add_reses = grant_req.get('addResources', []) + for res_def in add_reses: + if (res_def['type'] == 'COMPUTE' and + res_def['resourceTemplateId'] == vdu_name): + count += 1 + + rm_reses = grant_req.get('removeResources', []) + for res_def in rm_reses: + if (res_def['type'] == 'COMPUTE' and + res_def['resourceTemplateId'] == vdu_name): + count -= 1 + + return count + + +def _get_fixed_ips_from_extcp(extcp): + fixed_ips = [] + for cp_conf in extcp['cpConfig'].values(): + if 'cpProtocolData' not in cp_conf: + continue + for prot_data in cp_conf['cpProtocolData']: + if 'ipOverEthernet' not in prot_data: + continue + if 'ipAddresses' not in prot_data['ipOverEthernet']: + continue + for ip in prot_data['ipOverEthernet']['ipAddresses']: + data = {} + if 'fixedAddresses' in ip: + # pick up only one ip address + data['ip_address'] = str(ip['fixedAddresses'][0]) + if 'subnetId' in ip: + data['subnet'] = ip['subnetId'] + if data: + fixed_ips.append(data) + return fixed_ips + + +def get_param_network(cp_name, grant, req): + # see grant first then instantiateVnfRequest + vls = grant.get('extVirtualLinks', []) + req.get('extVirtualLinks', []) + for vl in vls: + for extcp in vl['extCps']: + if extcp['cpdId'] == cp_name: + return vl['resourceId'] + + +def get_param_fixed_ips(cp_name, grant, req): + # see grant first then instantiateVnfRequest + vls = grant.get('extVirtualLinks', []) + req.get('extVirtualLinks', []) + for vl in vls: + for extcp in vl['extCps']: + if extcp['cpdId'] == cp_name: + return _get_fixed_ips_from_extcp(extcp) + + +def get_param_network_from_inst(cp_name, inst): + for vl in inst['instantiatedVnfInfo'].get('extVirtualLinkInfo', []): + for extcp in vl.get('currentVnfExtCpData', []): + if extcp['cpdId'] == cp_name: + return vl['resourceHandle']['resourceId'] + + +def get_param_fixed_ips_from_inst(cp_name, inst): + for vl in inst['instantiatedVnfInfo'].get('extVirtualLinkInfo', []): + for extcp in vl.get('currentVnfExtCpData', []): + if extcp['cpdId'] == cp_name: + return _get_fixed_ips_from_extcp(extcp) + + +def apply_ext_managed_vls(hot_dict, req, grant): + # see grant first then instantiateVnfRequest + mgd_vls = (grant.get('extManagedVirtualLinks', []) + + req.get('extManagedVirtualLinks', [])) + + # NOTE: refer HOT only here, not refer VNFD. + # HOT and VNFD must be consistent. + + for mgd_vl in mgd_vls: + vl_name = mgd_vl['vnfVirtualLinkDescId'] + network_id = mgd_vl['resourceId'] + get_res = {'get_resource': vl_name} + + def _change(item): + if not isinstance(item, dict): + return + for key, value in item.items(): + if value == get_res: + item[key] = network_id + else: + _change(value) + + del_reses = [] + for res_name, res_data in hot_dict.get('resources', {}).items(): + # delete network definition + if res_name == vl_name: + del_reses.append(res_name) + + # delete subnet definition + if res_data['type'] == 'OS::Neutron::Subnet': + net = (res_data.get('properties', {}) + .get('network', {}) + .get('get_resource')) + if net == vl_name: + del_reses.append(res_name) + + # change '{get_resource: vl_name}' to network_id + _change(res_data) + + for res_name in del_reses: + hot_dict['resources'].pop(res_name) diff --git a/tacker/sol_refactored/common/exceptions.py b/tacker/sol_refactored/common/exceptions.py index e5beafa21..9952fe03a 100644 --- a/tacker/sol_refactored/common/exceptions.py +++ b/tacker/sol_refactored/common/exceptions.py @@ -114,6 +114,10 @@ class VnfInstanceNotFound(SolHttpError404): message = _("VnfInstance %(inst_id)s not found.") +class NotSupportUpgradeType(SolHttpError400): + message = _("not support upgrade_type %(upgrade_type)s") + + class VnfInstanceIsInstantiated(SolHttpError409): message = _("VnfInstance %(inst_id)s is instantiated.") @@ -122,6 +126,10 @@ class VnfInstanceIsNotInstantiated(SolHttpError409): message = _("VnfInstance %(inst_id)s isn't instantiated.") +class VnfInstanceIsNotChanged(SolHttpError409): + message = _("VnfInstance %(inst_id)s isn't changed.") + + class LccnSubscriptionNotFound(SolHttpError404): message = _("LccnSubscription %(subsc_id)s not found.") @@ -245,3 +253,24 @@ class DeltaMissingInVnfd(SolHttpError400): class ConductorProcessingError(SolException): title = 'Internal Server Error' message = _("Failure due to conductor processing error.") + + +class InvalidVolumeSize(SolHttpError400): + message = _("The volume size set in VNFD is invalid.") + + +class VduIdNotFound(SolHttpError404): + message = _("This vdu_id '%(vdu_id)s' does not exist" + " in current VnfInstance.") + + +class SshIpNotFoundException(SolHttpError404): + message = _("Ssh ip not found.") + + +class CoordinateVNFExecutionFailed(SolHttpError422): + message = _('CoordinateVNF execution failed.') + + +class VmRunningFailed(SolHttpError422): + message = _("VM is running incorrectly. Reason: '%(error_info)s'") diff --git a/tacker/sol_refactored/common/lcm_op_occ_utils.py b/tacker/sol_refactored/common/lcm_op_occ_utils.py index 2e62c9477..59516d1be 100644 --- a/tacker/sol_refactored/common/lcm_op_occ_utils.py +++ b/tacker/sol_refactored/common/lcm_op_occ_utils.py @@ -126,6 +126,8 @@ def _make_affected_vnfc(vnfc, change_type, strgs): changeType=change_type, computeResource=vnfc.computeResource ) + if vnfc.obj_attr_is_set('metadata'): + affected_vnfc.metadata = vnfc.metadata if vnfc.obj_attr_is_set('vnfcCpInfo'): cp_ids = [cp.id for cp in vnfc.vnfcCpInfo] affected_vnfc.affectedVnfcCpIds = cp_ids @@ -381,7 +383,15 @@ def update_lcmocc(lcmocc, inst_saved, inst): for strg in inst_info.virtualStorageResourceInfo if strg.id in added_strgs] - removed_vnfcs, added_vnfcs, _ = _calc_diff('vnfcResourceInfo') + removed_vnfcs, added_vnfcs, common_objs = _calc_diff('vnfcResourceInfo') + updated_vnfcs = [] + if lcmocc.operation == fields.LcmOperationType.CHANGE_VNFPKG: + updated_vnfcs = [ + obj.id for obj in inst_info.vnfcResourceInfo + if obj.metadata.get('current_vnfd_id') != inst_saved.vnfdId + and obj.id in common_objs and + obj.metadata.get('current_vnfd_id') is not None] + affected_vnfcs = [] if removed_vnfcs: affected_vnfcs += [ @@ -396,6 +406,11 @@ def update_lcmocc(lcmocc, inst_saved, inst): if vnfc.id in added_vnfcs ] + if updated_vnfcs: + affected_vnfcs += [_make_affected_vnfc(vnfc, 'MODIFIED', added_strgs) + for vnfc in inst_info.vnfcResourceInfo + if vnfc.id in updated_vnfcs] + removed_vls, added_vls, common_vls = _calc_diff( 'vnfVirtualLinkResourceInfo') affected_vls = [] @@ -491,6 +506,17 @@ def update_lcmocc(lcmocc, inst_saved, inst): lcmocc.changedExtConnectivity = chg_ext_conn +def get_inst_lcmocc(context, inst): + lcmoccs = objects.VnfLcmOpOccV2.get_by_filter( + context, vnfInstanceId=inst.id, + operationState=fields.LcmOperationStateType.COMPLETED, + operation=fields.LcmOperationType.INSTANTIATE) + inst_lcmocc = [inst_lcmocc for inst_lcmocc in lcmoccs + if inst_lcmocc.startTime == + max([lcmocc.startTime for lcmocc in lcmoccs])][0] + return inst_lcmocc + + def get_grant_req_and_grant(context, lcmocc): if lcmocc.operation == fields.LcmOperationType.MODIFY_INFO: return None, None diff --git a/tacker/sol_refactored/common/vnf_instance_utils.py b/tacker/sol_refactored/common/vnf_instance_utils.py index 5839f99cd..c39858d7e 100644 --- a/tacker/sol_refactored/common/vnf_instance_utils.py +++ b/tacker/sol_refactored/common/vnf_instance_utils.py @@ -49,6 +49,7 @@ def make_inst_links(inst, endpoint): links.scale = objects.Link(href=self_href + "/scale") links.heal = objects.Link(href=self_href + "/heal") links.changeExtConn = objects.Link(href=self_href + "/change_ext_conn") + links.changeVnfPkg = objects.Link(href=self_href + "/change_vnfpkg") # NOTE: add when the operation supported return links diff --git a/tacker/sol_refactored/common/vnfd_utils.py b/tacker/sol_refactored/common/vnfd_utils.py index b9fb6bd4a..a6d232b08 100644 --- a/tacker/sol_refactored/common/vnfd_utils.py +++ b/tacker/sol_refactored/common/vnfd_utils.py @@ -18,10 +18,10 @@ import io import os import shutil import tempfile -import yaml import zipfile from oslo_log import log as logging +import yaml from tacker.sol_refactored.common import exceptions as sol_ex diff --git a/tacker/sol_refactored/conductor/conductor_v2.py b/tacker/sol_refactored/conductor/conductor_v2.py index 07aec8512..7b85ac44a 100644 --- a/tacker/sol_refactored/conductor/conductor_v2.py +++ b/tacker/sol_refactored/conductor/conductor_v2.py @@ -14,6 +14,7 @@ # under the License. from oslo_log import log as logging +from oslo_utils import uuidutils from tacker.common import log from tacker import context as tacker_context @@ -103,8 +104,12 @@ class ConductorV2(object): self.endpoint) try: - vnfd = self.nfvo_client.get_vnfd(context, inst.vnfdId, - all_contents=True) + if lcmocc.operation == fields.LcmOperationType.CHANGE_VNFPKG: + vnfd = self.nfvo_client.get_vnfd( + context, lcmocc.operationParams.vnfdId, all_contents=True) + else: + vnfd = self.nfvo_client.get_vnfd(context, inst.vnfdId, + all_contents=True) # NOTE: perform grant exchange mainly but also perform # something to do at STATING phase ex. request check. @@ -237,20 +242,38 @@ class ConductorV2(object): try: vnfd = self.nfvo_client.get_vnfd(context, inst.vnfdId) - grant_req, grant = lcmocc_utils.get_grant_req_and_grant(context, - lcmocc) + grant_req, grant = lcmocc_utils.get_grant_req_and_grant( + context, lcmocc) self.vnflcm_driver.post_grant(context, lcmocc, inst, grant_req, grant, vnfd) - self.vnflcm_driver.rollback(context, lcmocc, inst, grant_req, - grant, vnfd) + if lcmocc.operation == fields.LcmOperationType.CHANGE_VNFPKG: + inst_lcmocc = lcmocc_utils.get_inst_lcmocc(context, inst) + inst_grant_req = objects.GrantRequestV1( + vnfInstanceId=inst.id, + vnfLcmOpOccId=inst_lcmocc.id, + operation=inst_lcmocc.operation, + isAutomaticInvocation=lcmocc.isAutomaticInvocation + ) + inst_grant = objects.GrantV1( + id=uuidutils.generate_uuid(), + vnfInstanceId=inst_grant_req.vnfInstanceId, + vnfLcmOpOccId=inst_grant_req.vnfLcmOpOccId + ) + self.vnflcm_driver.rollback( + context, lcmocc, inst, inst_grant_req, inst_grant, vnfd) + else: + self.vnflcm_driver.rollback(context, lcmocc, inst, grant_req, + grant, vnfd) lcmocc.operationState = fields.LcmOperationStateType.ROLLED_BACK with context.session.begin(subtransactions=True): lcmocc.update(context) # NOTE: Basically inst is not changed. But there is a case # that VIM resources may be changed while rollback. Only - # change_ext_conn_rollback at the moment. - if lcmocc.operation == fields.LcmOperationType.CHANGE_EXT_CONN: + # change_ext_conn_rollback and change_vnfpkg at the moment. + if lcmocc.operation in [ + fields.LcmOperationType.CHANGE_EXT_CONN, + fields.LcmOperationType.CHANGE_VNFPKG]: inst.update(context) # grant_req and grant are not necessary any more. if grant_req is not None: diff --git a/tacker/sol_refactored/conductor/vnflcm_driver_v2.py b/tacker/sol_refactored/conductor/vnflcm_driver_v2.py index 63dcee094..020036ad8 100644 --- a/tacker/sol_refactored/conductor/vnflcm_driver_v2.py +++ b/tacker/sol_refactored/conductor/vnflcm_driver_v2.py @@ -64,10 +64,13 @@ class VnfLcmDriverV2(object): grant_req = objects.GrantRequestV1( vnfInstanceId=inst.id, vnfLcmOpOccId=lcmocc.id, - vnfdId=inst.vnfdId, operation=lcmocc.operation, isAutomaticInvocation=lcmocc.isAutomaticInvocation ) + if lcmocc.operation == v2fields.LcmOperationType.CHANGE_VNFPKG: + grant_req.vnfdId = lcmocc.operationParams.get('vnfdId') + else: + grant_req.vnfdId = inst.vnfdId grant_req._links = objects.GrantRequestV1_Links( vnfLcmOpOcc=objects.Link( href=lcmocc_utils.lcmocc_href(lcmocc.id, self.endpoint)), @@ -953,6 +956,78 @@ class VnfLcmDriverV2(object): # only support openstack at the moment raise sol_ex.SolException(sol_detail='not support vim type') + def change_vnfpkg_grant(self, grant_req, req, inst, vnfd): + inst_info = inst.instantiatedVnfInfo + grant_req.flavourId = inst_info.flavourId + target_vdu_ids = [ + vdu_param.get('vdu_id') + for vdu_param in req.additionalParams.get('vdu_params', []) + ] + + if req.additionalParams.get('upgrade_type') == 'RollingUpdate': + update_reses = [] + add_reses = [] + remove_reses = [] + if inst_info.obj_attr_is_set('vnfcResourceInfo'): + for inst_vnc in inst_info.vnfcResourceInfo: + if inst_vnc.vduId in target_vdu_ids: + vdu_res_id = uuidutils.generate_uuid() + res_def = objects.ResourceDefinitionV1( + id=vdu_res_id, + type='COMPUTE', + resourceTemplateId=inst_vnc.vduId) + update_reses.append(res_def) + nodes = vnfd.get_vdu_nodes(inst_info.flavourId) + vdu_storage_names = vnfd.get_vdu_storages( + nodes[inst_vnc.vduId]) + for vdu_storage_name in vdu_storage_names: + res_def = objects.ResourceDefinitionV1( + id=_make_combination_id( + vdu_storage_name, vdu_res_id), + type='STORAGE', + resourceTemplateId=vdu_storage_name) + add_reses.append(res_def) + if inst_vnc.obj_attr_is_set('storageResourceIds'): + inst_stor_info = ( + inst_info.virtualStorageResourceInfo) + for str_info in inst_stor_info: + if str_info.id in inst_vnc.storageResourceIds: + res_def = objects.ResourceDefinitionV1( + id=uuidutils.generate_uuid(), + type='STORAGE', + resourceTemplateId=( + str_info.virtualStorageDescId), + resource=str_info.storageResource) + remove_reses.append(res_def) + if update_reses: + grant_req.updateResources = update_reses + + if add_reses: + grant_req.addResources = add_reses + + if remove_reses: + grant_req.removeResources = remove_reses + else: + # TODO(YiFeng): Blue-Green type will be supported in Zed release. + # not reach here at the moment + pass + + def change_vnfpkg_process( + self, context, lcmocc, inst, grant_req, grant, vnfd): + inst_saved = inst.obj_clone() + req = lcmocc.operationParams + vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo) + if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3': + driver = openstack.Openstack() + try: + driver.change_vnfpkg(req, inst, grant_req, grant, vnfd) + except Exception as ex: + lcmocc_utils.update_lcmocc(lcmocc, inst_saved, inst) + raise Exception from ex + else: + # only support openstack at the moment + raise sol_ex.SolException(sol_detail='not support vim type') + def change_ext_conn_rollback(self, context, lcmocc, inst, grant_req, grant, vnfd): req = lcmocc.operationParams @@ -963,3 +1038,15 @@ class VnfLcmDriverV2(object): else: # only support openstack at the moment raise sol_ex.SolException(sol_detail='not support vim type') + + def change_vnfpkg_rollback( + self, context, lcmocc, inst, grant_req, grant, vnfd): + vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo) + req = lcmocc.operationParams + driver = openstack.Openstack() + if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3': + driver.change_vnfpkg_rollback( + req, inst, grant_req, grant, vnfd, lcmocc) + else: + # only support openstack at the moment + raise sol_ex.SolException(sol_detail='not support vim type') diff --git a/tacker/sol_refactored/controller/vnflcm_v2.py b/tacker/sol_refactored/controller/vnflcm_v2.py index 6e627e2ea..0a1b44da8 100644 --- a/tacker/sol_refactored/controller/vnflcm_v2.py +++ b/tacker/sol_refactored/controller/vnflcm_v2.py @@ -379,6 +379,61 @@ class VnfLcmControllerV2(sol_wsgi.SolAPIController): return sol_wsgi.SolResponse(202, None, location=location) + @validator.schema(schema.ChangeCurrentVnfPkgRequest_V200, '2.0.0') + @coordinate.lock_vnf_instance('{id}') + def change_vnfpkg(self, request, id, body): + context = request.context + inst = inst_utils.get_inst(context, id) + vnfd_id = body['vnfdId'] + + if inst.instantiationState != 'INSTANTIATED': + raise sol_ex.VnfInstanceIsNotInstantiated(inst_id=id) + + lcmocc_utils.check_lcmocc_in_progress(context, id) + + pkg_info = self.nfvo_client.get_vnf_package_info_vnfd( + context, vnfd_id) + if pkg_info.operationalState != "ENABLED": + raise sol_ex.VnfdIdNotEnabled(vnfd_id=vnfd_id) + + additional_params = body.get('additionalParams') + if additional_params is None: + raise sol_ex.SolValidationError( + detail="Change Current VNF Package " + "operation must have 'additionalParams'") + upgrade_type = additional_params.get('upgrade_type', '') + if upgrade_type != 'RollingUpdate': + raise sol_ex.NotSupportUpgradeType(upgrade_type=upgrade_type) + if additional_params.get('vdu_params'): + vdu_ids = [vdu.get('vdu_id') for vdu in + additional_params.get('vdu_params')] + if None in vdu_ids: + raise sol_ex.SolValidationError( + detail="If you set vdu_params in additionalParams, you" + "must set vduId for each element.") + for vdu in additional_params.get('vdu_params'): + for attr in ['old_vnfc_param', 'new_vnfc_param']: + if vdu.get(attr): + vdu_keys = vdu.get(attr).keys() + if set(vdu_keys).difference(set( + ['cp_name', 'username', 'password'])): + raise sol_ex.SolValidationError( + detail=f"If you set {attr} in " + f"additionalParams, you must set" + f" 'cp_name', 'username' and" + f" 'password'") + + lcmocc = self._new_lcmocc(id, v2fields.LcmOperationType.CHANGE_VNFPKG, + body) + + lcmocc.create(context) + + self.conductor_rpc.start_lcm_op(context, lcmocc.id) + + location = lcmocc_utils.lcmocc_href(lcmocc.id, self.endpoint) + + return sol_wsgi.SolResponse(202, None, location=location) + @validator.schema(schema.LccnSubscriptionRequest_V200, '2.0.0') def subscription_create(self, request, body): context = request.context diff --git a/tacker/sol_refactored/infra_drivers/openstack/heat_utils.py b/tacker/sol_refactored/infra_drivers/openstack/heat_utils.py index 5ac9269ca..b0dfdea83 100644 --- a/tacker/sol_refactored/infra_drivers/openstack/heat_utils.py +++ b/tacker/sol_refactored/infra_drivers/openstack/heat_utils.py @@ -49,7 +49,7 @@ class HeatClient(object): self.wait_stack_create(fields["stack_name"]) def update_stack(self, stack_name, fields, wait=True): - path = "stacks/{}".format(stack_name) + path = f"stacks/{stack_name}" resp, body = self.client.do_request(path, "PATCH", expected_status=[202], body=fields) @@ -57,7 +57,7 @@ class HeatClient(object): self.wait_stack_update(stack_name) def delete_stack(self, stack_name, wait=True): - path = "stacks/{}".format(stack_name) + path = f"stacks/{stack_name}" resp, body = self.client.do_request(path, "DELETE", expected_status=[204, 404]) @@ -65,7 +65,7 @@ class HeatClient(object): self.wait_stack_delete(stack_name) def get_status(self, stack_name): - path = "stacks/{}".format(stack_name) + path = f"stacks/{stack_name}" resp, body = self.client.do_request(path, "GET", expected_status=[200, 404]) @@ -78,7 +78,7 @@ class HeatClient(object): def get_resources(self, stack_name): # NOTE: Because it is necessary to get nested stack info, it is # necessary to specify 'nested_depth=2'. - path = "stacks/{}/resources?nested_depth=2".format(stack_name) + path = f"stacks/{stack_name}/resources?nested_depth=2" resp, body = self.client.do_request(path, "GET", expected_status=[200]) @@ -134,23 +134,29 @@ class HeatClient(object): raise sol_ex.StackOperationFailed return body - def get_resource_info(self, stack_name, stack_id, resource_name): - path = f"stacks/{stack_name}/{stack_id}/resources/{resource_name}" + def get_resource_info(self, nested_stack_id, resource_name): + path = f"stacks/{nested_stack_id}/resources/{resource_name}" resp, body = self.client.do_request(path, "GET", expected_status=[200, 404]) if resp.status_code == 404: - return resp, None - return resp, body['resource'] + return None + return body['resource'] + + def get_resource_list(self, stack_id): + path = f"stacks/{stack_id}/resources" + resp, body = self.client.do_request(path, "GET", + expected_status=[200, 404]) + return body def get_parameters(self, stack_name): - path = "stacks/{}".format(stack_name) + path = f"stacks/{stack_name}" resp, body = self.client.do_request(path, "GET", expected_status=[200]) return body["stack"]["parameters"] def mark_unhealthy(self, stack_id, resource_name): - path = "stacks/{}/resources/{}".format(stack_id, resource_name) + path = f"stacks/{stack_id}/resources/{resource_name}" fields = { "mark_unhealthy": True, "resource_status_reason": "marked by tacker" @@ -159,14 +165,14 @@ class HeatClient(object): expected_status=[200], body=fields) def get_template(self, stack_name): - path = "stacks/{}/template".format(stack_name) + path = f"stacks/{stack_name}/template" resp, body = self.client.do_request(path, "GET", expected_status=[200]) return body def get_files(self, stack_name): - path = "stacks/{}/files".format(stack_name) + path = f"stacks/{stack_name}/files" resp, body = self.client.do_request(path, "GET", expected_status=[200]) @@ -205,9 +211,34 @@ def get_resource_stack_id(heat_res): return "{}/{}".format(items[-2], items[-1]) +def get_parent_nested_id(res): + for link in res.get('links', []): + if link['rel'] == 'nested': + items = link['href'].split('/') + return "{}/{}".format(items[-2], items[-1]) + + def get_parent_resource(heat_res, heat_reses): parent = heat_res.get('parent_resource') if parent: for res in heat_reses: if res['resource_name'] == parent: return res + + +def get_group_stack_id(heat_reses, vdu_id): + parent_resources = [heat_res for heat_res in heat_reses + if heat_res.get('resource_name') == vdu_id] + if parent_resources: + parent_resource = parent_resources[0].get('parent_resource') + else: + raise sol_ex.VduIdNotFound(vdu_id=vdu_id) + group_resource_name = [heat_res for heat_res in + heat_reses if + heat_res.get('resource_name') == + parent_resource][0].get('parent_resource') + group_stack_id = [heat_res for heat_res in + heat_reses if + heat_res.get('resource_name') == + group_resource_name][0].get('physical_resource_id') + return group_stack_id diff --git a/tacker/sol_refactored/infra_drivers/openstack/openstack.py b/tacker/sol_refactored/infra_drivers/openstack/openstack.py index dfacda85b..e2c368942 100644 --- a/tacker/sol_refactored/infra_drivers/openstack/openstack.py +++ b/tacker/sol_refactored/infra_drivers/openstack/openstack.py @@ -14,21 +14,25 @@ # under the License. -from dateutil import parser -import eventlet +import copy import json import os import pickle import subprocess +from dateutil import parser +import eventlet from oslo_log import log as logging from oslo_utils import uuidutils +import yaml +from tacker.sol_refactored.common import cinder_utils from tacker.sol_refactored.common import config from tacker.sol_refactored.common import exceptions as sol_ex from tacker.sol_refactored.common import vnf_instance_utils as inst_utils from tacker.sol_refactored.infra_drivers.openstack import heat_utils from tacker.sol_refactored.infra_drivers.openstack import userdata_default +from tacker.sol_refactored.nfvo import glance_utils from tacker.sol_refactored import objects from tacker.sol_refactored.objects.v2 import fields as v2fields @@ -292,6 +296,261 @@ class Openstack(object): self._make_instantiated_vnf_info(req, inst, grant_req, grant, vnfd, heat_reses) + def change_vnfpkg(self, req, inst, grant_req, grant, vnfd): + group_vdu_ids = [] + if req.additionalParams.get('upgrade_type') == 'RollingUpdate': + vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo) + heat_client = heat_utils.HeatClient(vim_info) + stack_name = heat_utils.get_stack_name(inst) + stack_id = heat_client.get_stack_resource( + stack_name)['stack']["id"] + vdu_infos = self._get_vdu_info(vnfd, grant, inst) + new_res_ids = {} + for vdu_param in req.additionalParams.get('vdu_params'): + vdu_id = vdu_param.get('vdu_id') + new_res_ids[vdu_id] = [] + body = heat_client.get_resource_info( + f"{stack_name}/{stack_id}", vdu_id) + if uuidutils.is_uuid_like( + vdu_infos[vdu_id]['image']): + vdu_image_id_flag = True + else: + vdu_image_id_flag = False + # The previous processing is to obtain the VM information under + # the current stack according to vduId, excluding the resources + # under nested. When the status_code is 404, it means that + # there is no VM under the stack, and the VM is created by + # `OS::Heat::AutoScalingGroup` by default. + if body is None: + # handle VM under `OS::Heat::AutoScalingGroup` + # In this case, the VM does not support changing from + # image creation to volume creation, or changing from + # volume creation to image creation. Because it cannot + # change VDU.yaml under nested directory. + heat_reses = heat_client.get_resources(stack_name) + group_stack_id = heat_utils.get_group_stack_id( + heat_reses, vdu_id) + templates = heat_client.get_template( + group_stack_id) + reses = heat_client.get_resource_list( + group_stack_id)['resources'] + + # update VM one by one + for res in reses: + templates['resources'][res.get('resource_name')][ + 'properties']['image'] = vdu_infos[ + vdu_id].get('image') + templates['resources'][res.get('resource_name')][ + 'properties']['flavor'] = vdu_infos[ + vdu_id].get('flavor') + fields = { + "stack_id": group_stack_id, + "template": templates + } + try: + heat_client.update_stack( + group_stack_id, fields) + except sol_ex.StackOperationFailed: + self._handle_exception( + res, new_res_ids, vdu_infos, + vdu_id, heat_client, req, inst, vnfd, + stack_name, get_res_flag=True) + self._get_new_res_info( + res.get('resource_name'), + new_res_ids[vdu_id], + heat_utils.get_parent_nested_id(res), + vdu_infos[vdu_id], vdu_id, + heat_client) + + # execute coordinate_vnf_script + try: + self._execute_coordinate_vnf_script( + req, inst, grant_req, grant, vnfd, + heat_utils.get_parent_nested_id(res), + heat_client, vdu_param, vim_info, + vdu_image_id_flag) + except (sol_ex.SshIpNotFoundException, + sol_ex.CoordinateVNFExecutionFailed) as ex: + self._handle_exception( + res, new_res_ids, vdu_infos, + vdu_id, heat_client, req, inst, vnfd, + stack_name, ex=ex) + group_vdu_ids.append(vdu_id) + else: + # handle single VM + res = { + "resource_name": stack_name, + "physical_resource_id": stack_id, + } + new_template = vnfd.get_base_hot( + inst.instantiatedVnfInfo.flavourId)['template'] + heat_parameter = self._update_vnf_template_and_parameter( + stack_id, vdu_infos, vdu_id, heat_client, new_template) + fields = { + "stack_id": stack_id, + "parameters": {"nfv": heat_parameter.get('nfv')}, + "template": yaml.safe_dump( + heat_parameter.get('templates')), + } + try: + heat_client.update_stack(stack_id, fields, wait=True) + except sol_ex.StackOperationFailed: + self._handle_exception( + res, new_res_ids, vdu_infos, + vdu_id, heat_client, req, inst, vnfd, + stack_name, get_res_flag=True) + self._get_new_res_info( + stack_name, + new_res_ids[vdu_id], + f'{stack_name}/{stack_id}', + vdu_infos[vdu_id], vdu_id, + heat_client) + + # execute coordinate_vnf_script + try: + self._execute_coordinate_vnf_script( + req, inst, grant_req, grant, vnfd, + f"{stack_name}/{stack_id}", + heat_client, vdu_param, vim_info, + vdu_image_id_flag, + operation='change_vnfpkg') + except (sol_ex.SshIpNotFoundException, + sol_ex.CoordinateVNFExecutionFailed) as ex: + self._handle_exception( + res, new_res_ids, vdu_infos, + vdu_id, heat_client, req, inst, vnfd, + stack_name, ex=ex) + + # Because external parameters are not updated after the image + # of the nested-VM is updated, scale will create the original + # VM again, so the overall update needs to be performed + # at the end. + fields = self._get_entire_stack_fields( + heat_client, stack_id, group_vdu_ids, vdu_infos) + try: + heat_client.update_stack(stack_id, fields) + except sol_ex.StackOperationFailed: + self._handle_exception( + res, new_res_ids, vdu_infos, + vdu_id, heat_client, req, inst, vnfd, + stack_name) + self._update_vnf_instantiated_info( + req, new_res_ids, inst, vnfd, + heat_client, stack_name) + inst.vnfdId = req.vnfdId + else: + # TODO(YiFeng): Blue-Green type will be supported in Zed release. + raise sol_ex.NotSupportUpgradeType( + upgrade_type=req.additionalParams.get('upgrade_type')) + + def change_vnfpkg_rollback( + self, req, inst, grant_req, grant, vnfd, lcmocc): + group_vdu_ids = [] + if req.additionalParams.get('upgrade_type') == 'RollingUpdate': + vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo) + heat_client = heat_utils.HeatClient(vim_info) + stack_name = heat_utils.get_stack_name(inst) + stack_id = heat_client.get_stack_resource( + stack_name)['stack']["id"] + vdu_infos = self._get_vdu_info(vnfd, grant, inst) + new_res_ids = {} + templates = {} + affected_vnfcs = [affected_vnfc for affected_vnfc in + lcmocc.resourceChanges.affectedVnfcs if + affected_vnfc.changeType in {'ADDED', 'MODIFIED'}] + for lcmocc_vnf in affected_vnfcs: + if new_res_ids.get(lcmocc_vnf.vduId) is None: + new_res_ids[lcmocc_vnf.vduId] = [] + image = vdu_infos[lcmocc_vnf.vduId]['image'] + if uuidutils.is_uuid_like(image): + vdu_image_id_flag = True + else: + vdu_image_id_flag = False + if lcmocc_vnf.metadata.get( + 'current_vnfd_id') is not inst.vnfdId: + parent_resource_name = lcmocc_vnf.metadata.get( + 'parent_resource_name') + if parent_resource_name: + vdu_stack_id = lcmocc_vnf.metadata.get( + 'stack_id') + if not templates.get(lcmocc_vnf.vduId): + templates[lcmocc_vnf.vduId] = {} + heat_reses = heat_client.get_resources(stack_name) + group_stack_id = heat_utils.get_group_stack_id( + heat_reses, lcmocc_vnf.vduId) + template = heat_client.get_template( + group_stack_id) + templates[lcmocc_vnf.vduId] = template + template['resources'][parent_resource_name][ + 'properties']['image'] = vdu_infos[ + lcmocc_vnf.vduId].get('image') + template['resources'][parent_resource_name][ + 'properties']['flavor'] = vdu_infos[ + lcmocc_vnf.vduId].get('flavor') + fields = { + "stack_id": group_stack_id, + "template": template + } + heat_client.update_stack( + group_stack_id, fields, wait=True) + self._get_new_res_info( + parent_resource_name, + new_res_ids[lcmocc_vnf.vduId], vdu_stack_id, + vdu_infos[lcmocc_vnf.vduId], + lcmocc_vnf.vduId, heat_client) + vdu_param = [vdu_param for vdu_param in + req.get('additionalParams').get( + 'vdu_params') + if vdu_param.get('vdu_id') + == lcmocc_vnf.vduId][0] + self._execute_coordinate_vnf_script( + req, inst, grant_req, grant, vnfd, + vdu_stack_id, + heat_client, vdu_param, vim_info, + vdu_image_id_flag, + operation="change_vnfpkg_rollback") + group_vdu_ids.append(lcmocc_vnf.vduId) + else: + vdu_stack_id = lcmocc_vnf.metadata.get('stack_id') + new_template = vnfd.get_base_hot( + inst.instantiatedVnfInfo.flavourId)['template'] + heat_parameter = ( + self._update_vnf_template_and_parameter( + stack_id, vdu_infos, + lcmocc_vnf.vduId, heat_client, new_template)) + fields = { + "stack_id": stack_id, + "parameters": {"nfv": heat_parameter.get('nfv')}, + "template": yaml.safe_dump( + heat_parameter.get('templates')), + } + heat_client.update_stack(stack_id, + fields, wait=True) + self._get_new_res_info( + stack_name, new_res_ids[lcmocc_vnf.vduId], + vdu_stack_id, vdu_infos[lcmocc_vnf.vduId], + lcmocc_vnf.vduId, heat_client) + vdu_param = [vdu_param for vdu_param in + req.get('additionalParams').get( + 'vdu_params') + if vdu_param.get('vdu_id') + == lcmocc_vnf.vduId][0] + self._execute_coordinate_vnf_script( + req, inst, grant_req, grant, vnfd, vdu_stack_id, + heat_client, vdu_param, vim_info, + vdu_image_id_flag, + operation="change_vnfpkg_rollback") + fields = self._get_entire_stack_fields( + heat_client, stack_id, group_vdu_ids, vdu_infos) + heat_client.update_stack(stack_id, fields, wait=True) + self._update_vnf_instantiated_info( + req, new_res_ids, inst, vnfd, + heat_client, stack_name, operation='change_vnfpkg_rollback') + else: + # TODO(YiFeng): Blue-Green type will be supported in Zed release. + raise sol_ex.NotSupportUpgradeType( + upgrade_type=req.additionalParams.get('upgrade_type')) + def _make_hot(self, req, inst, grant_req, grant, vnfd): if grant_req.operation == v2fields.LcmOperationType.INSTANTIATE: flavour_id = req.flavourId @@ -372,6 +631,60 @@ class Openstack(object): obj.maxAddress = range_data.maxAddress return obj + def _execute_coordinate_vnf_script( + self, req, inst, grant_req, grant, + vnfd, nested_stack_id, heat_client, vdu_param, + vim_info, vdu_image_id_flag, operation='change_vnfpkg'): + coordinate_vnf = None + coordinate_vnf_class = None + if req.obj_attr_is_set('additionalParams'): + if operation == 'change_vnfpkg': + coordinate_vnf = req.additionalParams.get( + 'lcm-operation-coordinate-new-vnf') + coordinate_vnf_class = req.additionalParams.get( + 'lcm-operation-coordinate-new-vnf-class') + else: + coordinate_vnf = req.additionalParams.get( + 'lcm-operation-coordinate-old-vnf') + coordinate_vnf_class = req.additionalParams.get( + 'lcm-operation-coordinate-old-vnf-class') + + if coordinate_vnf and coordinate_vnf_class: + if operation == 'change_vnfpkg': + ssh_ip = self._get_ssh_ip(nested_stack_id, + vdu_param.get('new_vnfc_param'), + heat_client) + else: + ssh_ip = self._get_ssh_ip(nested_stack_id, + vdu_param.get('old_vnfc_param'), + heat_client) + if not ssh_ip: + raise sol_ex.SshIpNotFoundException + image, flavor = self._get_current_vdu_image_and_flavor( + nested_stack_id, vdu_param.get('vdu_id'), + heat_client, vim_info, vdu_image_id_flag) + tmp_csar_dir = vnfd.make_tmp_csar_dir() + script_dict = { + "request": req.to_dict(), + "vnf_instance": inst.to_dict(), + "grant_request": grant_req.to_dict(), + "grant_response": grant.to_dict(), + "tmp_csar_dir": tmp_csar_dir, + "vdu_info": { + "ssh_ip": ssh_ip, + "new_image": image, + "new_flavor": flavor, + "vdu_param": vdu_param + } + } + script_path = os.path.join(tmp_csar_dir, coordinate_vnf) + out = subprocess.run(["python3", script_path], + input=pickle.dumps(script_dict), + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if out.returncode != 0: + LOG.error(out) + raise sol_ex.CoordinateVNFExecutionFailed + def _proto_data_to_info(self, proto_data): # make CpProtocolInfo (5.5.3.9b) from CpProtocolData (4.4.1.10b) proto_info = objects.CpProtocolInfoV2( @@ -463,6 +776,285 @@ class Openstack(object): return ext_vl + def _get_vdu_info(self, vnfd, grant, inst): + flavour_id = inst.instantiatedVnfInfo.flavourId + vdu_nodes = vnfd.get_vdu_nodes(flavour_id) + storage_nodes = vnfd.get_storage_nodes(flavour_id) + vdu_info_dict = {} + for name, node in vdu_nodes.items(): + flavor = self._get_param_flavor(vnfd, name, flavour_id, grant) + image = self._get_param_image(vnfd, name, flavour_id, grant) + vdu_storage_names = vnfd.get_vdu_storages(node) + volume_name = '' + volume_size = '' + for vdu_storage_name in vdu_storage_names: + if storage_nodes[vdu_storage_name].get( + 'properties').get('sw_image_data'): + image = self._get_param_image( + vnfd, vdu_storage_name, flavour_id, grant) + volume_name = vdu_storage_name + volume_size = storage_nodes[vdu_storage_name].get( + 'properties', {}).get( + 'virtual_block_storage_data', '').get( + 'size_of_storage', '' + ) + volume_size = volume_size.rstrip(' GB') + if not volume_size.isdigit(): + raise sol_ex.InvalidVolumeSize + break + + vdu_info_dict[name] = { + "flavor": flavor, + "image": image + } + + if volume_name: + vdu_info_dict[name]['volume_info'] = { + "volume_name": volume_name, + "volume_size": volume_size + } + return vdu_info_dict + + def _get_param_flavor(self, vnfd, vdu_name, flavour_id, grant): + # try to get from grant + if grant.obj_attr_is_set('vimAssets'): + assets = grant.vimAssets + if assets.obj_attr_is_set('computeResourceFlavours'): + flavours = assets.computeResourceFlavours + for flavour in flavours: + if flavour.vnfdVirtualComputeDescId == vdu_name: + return flavour.vimFlavourId + # if specified in VNFD, use it + # NOTE: if not found. parameter is set to None. + # may be error when stack create + return vnfd.get_compute_flavor(flavour_id, vdu_name) + + def _get_param_image(self, vnfd, vdu_name, flavour_id, grant): + # try to get from grant + if grant.obj_attr_is_set('vimAssets'): + assets = grant.vimAssets + if assets.obj_attr_is_set('softwareImages'): + images = assets.softwareImages + for image in images: + if image.vnfdSoftwareImageId == vdu_name: + return image.vimSoftwareImageId + + # if specified in VNFD, use it + # NOTE: if not found. parameter is set to None. + # may be error when stack create + sw_images = vnfd.get_sw_image(flavour_id) + for name, image in sw_images.items(): + if name == vdu_name: + return image + + return None + + def _get_current_vdu_image_and_flavor( + self, nested_stack_id, resource_name, + heat_client, vim_info, vdu_image_id_flag): + vdu_info = heat_client.get_resource_info( + nested_stack_id, resource_name) + if vdu_info.get('attributes').get('image'): + image = vdu_info.get('attributes').get('image').get('id') + if not vdu_image_id_flag: + glance_client = glance_utils.GlanceClient(vim_info) + image = glance_client.get_image(image).name + + else: + volume_ids = [volume.get('id') for volume in vdu_info.get( + 'attributes').get('os-extended-volumes:volumes_attached')] + cinder_client = cinder_utils.CinderClient(vim_info) + if vdu_image_id_flag: + image = [cinder_client.get_volume( + volume_id).volume_image_metadata.get('image_id') + for volume_id in volume_ids if cinder_client.get_volume( + volume_id).volume_image_metadata][0] + else: + image = [cinder_client.get_volume( + volume_id).volume_image_metadata.get('image_name') + for volume_id in volume_ids if cinder_client.get_volume( + volume_id).volume_image_metadata][0] + flavor_name = vdu_info.get('attributes').get('flavor').get( + 'original_name') + + return image, flavor_name + + def _get_new_res_info(self, parent_resource_name, vdu_infos, + stack_id, vnfd_info, vdu_id, heat_client): + new_res_infos = { + "stack_id": stack_id, + "parent_resource_name": parent_resource_name + } + nested_reses = heat_client.get_resource_list( + stack_id.split('/')[1])['resources'] + for nested_res in nested_reses: + if nested_res.get( + 'resource_type') == 'OS::Nova::Server' and nested_res.get( + 'resource_name') == vdu_id: + new_res_infos["vdu_id"] = nested_res.get( + 'physical_resource_id') + elif nested_res.get( + 'resource_type' + ) == 'OS::Cinder::Volume' and nested_res.get( + 'resource_name') == vnfd_info.get( + 'volume_info').get('volume_name'): + new_res_infos['volume_info'] = { + "volume_name": nested_res.get('resource_name'), + "volume_id": nested_res.get('physical_resource_id') + } + vdu_infos.append(new_res_infos) + + def _get_ssh_ip(self, nested_stack_id, vnfc_param, heat_client): + cp_name = vnfc_param.get('cp_name') + cp_info = heat_client.get_resource_info(nested_stack_id, cp_name) + if cp_info.get('attributes').get('floating_ip_address'): + ssh_ip = cp_info.get('attributes').get('floating_ip_address') + else: + ssh_ip = cp_info.get('attributes').get('fixed_ips')[0].get( + 'ip_address') + return ssh_ip + + def _update_vnf_instantiated_info( + self, req, new_res_ids, inst, vnfd, heat_client, stack_name, + operation='change_vnfpkg'): + instantiated_vnf_info = inst.instantiatedVnfInfo + vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo) + heat_reses = heat_client.get_resources(stack_name) + storage_reses = self._get_checked_reses( + vnfd.get_storage_nodes(inst.instantiatedVnfInfo.flavourId), + heat_utils.get_storage_reses(heat_reses)) + + # handle storage_info + def _res_to_handle(res): + return objects.ResourceHandle( + resourceId=res['physical_resource_id'], + vimLevelResourceType=res['resource_type'], + vimConnectionId=vim_info.vimId) + + storage_infos = [ + objects.VirtualStorageResourceInfoV2( + id=res_id, + virtualStorageDescId=res['resource_name'], + storageResource=_res_to_handle(res) + ) + for res_id, res in storage_reses.items() + ] + + # handle vnfc_resource_info + for vnfc_res in instantiated_vnf_info.vnfcResourceInfo: + if new_res_ids.get(vnfc_res.vduId): + new_res_info = [vnfc_info for vnfc_info + in new_res_ids.get(vnfc_res.vduId) + if vnfc_info['stack_id'] + == vnfc_res.metadata['stack_id']] + if not new_res_info: + continue + new_res_info = new_res_info[0] + current_vnfc = [] + if instantiated_vnf_info.obj_attr_is_set('vnfcInfo'): + current_vnfc = [ + vnfc for vnfc in instantiated_vnf_info.vnfcInfo + if vnfc.id == _make_combination_id( + vnfc_res.vduId, vnfc_res.id)][0] + vnfc_res.id = new_res_info.get('vdu_id') + vnfc_res.computeResource.resourceId = new_res_info.get( + 'vdu_id') + if current_vnfc: + current_vnfc.id = _make_combination_id( + vnfc_res.vduId, vnfc_res.id) + current_vnfc.vnfcResourceInfoId = vnfc_res.id + if operation == 'change_vnfpkg': + vnfc_res.metadata['current_vnfd_id'] = req.vnfdId + else: + vnfc_res.metadata['current_vnfd_id'] = inst.vnfdId + storage_ids = [ + storage_id for storage_id, storage_res in + storage_reses.items() + if (vnfc_res.vduId in storage_res.get( + 'required_by', []))] + if vnfc_res.metadata.get('parent_resource_name') != stack_name: + storage_ids = [ + storage_id for storage_id, storage_res in + storage_reses.items() + if (vnfc_res.vduId in storage_res.get( + 'required_by', []) and vnfc_res.metadata.get( + 'parent_resource_name') == storage_res.get( + 'parent_resource'))] + if storage_ids: + vnfc_res.storageResourceIds = storage_ids + else: + if vnfc_res.obj_attr_is_set('storageResourceIds'): + del vnfc_res.storageResourceIds + + if storage_infos: + instantiated_vnf_info.virtualStorageResourceInfo = storage_infos + else: + if instantiated_vnf_info.obj_attr_is_set( + 'virtualStorageResourceInfo'): + del instantiated_vnf_info.virtualStorageResourceInfo + + def _update_vnf_template_and_parameter( + self, stack_id, vdu_infos, vdu_id, heat_client, new_template): + vdu_info = vdu_infos[vdu_id] + volume_info = vdu_info.get('volume_info', {}) + base_templates = heat_client.get_template(stack_id) + old_parameter = heat_client.get_parameters(stack_id)['nfv'] + new_parameter = json.loads(copy.deepcopy(old_parameter)) + + # old VM(created by volume) -> new VM(created by volume) + if volume_info and 'image' not in base_templates[ + 'resources'][vdu_id]['properties']: + new_parameter['VDU'][vdu_id]['computeFlavourId'] = vdu_info.get( + 'flavor') + new_parameter['VDU'][volume_info.get('volume_name')][ + 'vcImageId'] = vdu_info.get('image') + + # old VM(created by volume) -> new VM(created by image) + elif vdu_info.get( + 'volume_info') is None and 'image' not in base_templates[ + 'resources'][vdu_id]['properties']: + # delete vdu's volume definition info + if len(base_templates['resources'][vdu_id]['properties'][ + 'block_device_mapping_v2']) > 1: + old_volumes = [name for name, value in + base_templates['resources'].items() + if value['type'] == 'OS::Cinder::Volume' + and value['properties']['image']] + for volume in base_templates['resources'][ + vdu_id]['properties']['block_device_mapping_v2']: + if volume['volume_id']['get_resource'] in old_volumes: + target_volume_name = volume['volume_id'][ + 'get_resource'] + else: + target_volume_name = base_templates['resources'][ + vdu_id]['properties']['block_device_mapping_v2'][0][ + 'volume_id']['get_resource'] + del new_parameter['VDU'][target_volume_name] + new_parameter['VDU'][vdu_id]['computeFlavourId'] = vdu_info.get( + 'flavor') + new_parameter['VDU'][vdu_id]['vcImageId'] = vdu_info.get('image') + + # old VM(created by image) -> new VM(created by volume) + elif volume_info and 'image' in base_templates[ + 'resources'][vdu_id]['properties']: + del new_parameter['VDU'][vdu_id]['vcImageId'] + new_parameter['VDU'][vdu_id]['computeFlavourId'] = vdu_info.get( + 'flavor') + new_parameter['VDU'][volume_info.get('volume_name')] = { + "vcImageId": vdu_infos[vdu_id].get('image') + } + # old VM(created by image) -> new VM(created by image) + else: + new_parameter['VDU'][vdu_id]['computeFlavourId'] = vdu_info.get( + 'flavor') + new_parameter['VDU'][vdu_id]['vcImageId'] = vdu_info.get('image') + + heat_parameter = { + "templates": new_template, + "nfv": new_parameter + } + return heat_parameter + def _make_ext_vl_info_from_req(self, req, grant, ext_cp_infos): # make extVirtualLinkInfo req_ext_vls = [] @@ -947,3 +1539,47 @@ class Openstack(object): inst_vnf_info.vnfcInfo = vnfc_infos inst.instantiatedVnfInfo = inst_vnf_info + + def _handle_exception( + self, res, new_res_ids, vdu_infos, + vdu_id, heat_client, req, inst, vnfd, + stack_name, ex=None, get_res_flag=False): + if get_res_flag: + if len(res.keys()) == 2: + par_stack_id = '{}/{}'.format( + res.get('resource_name'), + res.get('physical_resource_id')) + else: + par_stack_id = heat_utils.get_parent_nested_id(res) + self._get_new_res_info( + res.get('resource_name'), + new_res_ids[vdu_id], + par_stack_id, + vdu_infos[vdu_id], vdu_id, + heat_client) + self._update_vnf_instantiated_info( + req, new_res_ids, inst, + vnfd, heat_client, stack_name) + if ex: + raise ex + raise sol_ex.StackOperationFailed + + def _get_entire_stack_fields(self, heat_client, stack_id, + group_vdu_ids, vdu_infos): + parameter = json.loads(heat_client.get_parameters(stack_id)['nfv']) + for group_vdu_id in group_vdu_ids: + if not parameter['VDU'][group_vdu_id].get('vcImageId'): + volume_info = vdu_infos[ + group_vdu_id].get('volume_info') + parameter['VDU'][volume_info.get('volume_name')][ + 'vcImageId'] = vdu_infos[group_vdu_id].get( + 'image') + else: + parameter['VDU'][group_vdu_id][ + 'vcImageId'] = vdu_infos[group_vdu_id].get( + 'image') + fields = { + "stack_id": stack_id, + "parameters": {"nfv": parameter} + } + return fields diff --git a/tacker/sol_refactored/infra_drivers/openstack/userdata_default.py b/tacker/sol_refactored/infra_drivers/openstack/userdata_default.py index 3d387a374..1da6ac2a8 100644 --- a/tacker/sol_refactored/infra_drivers/openstack/userdata_default.py +++ b/tacker/sol_refactored/infra_drivers/openstack/userdata_default.py @@ -15,6 +15,7 @@ import yaml +from tacker.sol_refactored.common import common_script_utils from tacker.sol_refactored.common import vnf_instance_utils as inst_utils from tacker.sol_refactored.infra_drivers.openstack import userdata_utils @@ -23,39 +24,39 @@ class DefaultUserData(userdata_utils.AbstractUserData): @staticmethod def instantiate(req, inst, grant_req, grant, tmp_csar_dir): - vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir) + vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir) flavour_id = req['flavourId'] hot_dict = vnfd.get_base_hot(flavour_id) top_hot = hot_dict['template'] - nfv_dict = userdata_utils.init_nfv_dict(top_hot) + nfv_dict = common_script_utils.init_nfv_dict(top_hot) vdus = nfv_dict.get('VDU', {}) for vdu_name, vdu_value in vdus.items(): if 'computeFlavourId' in vdu_value: vdu_value['computeFlavourId'] = ( - userdata_utils.get_param_flavor( + common_script_utils.get_param_flavor( vdu_name, flavour_id, vnfd, grant)) if 'vcImageId' in vdu_value: - vdu_value['vcImageId'] = userdata_utils.get_param_image( + vdu_value['vcImageId'] = common_script_utils.get_param_image( vdu_name, flavour_id, vnfd, grant) if 'locationConstraints' in vdu_value: vdu_value['locationConstraints'] = ( - userdata_utils.get_param_zone( + common_script_utils.get_param_zone( vdu_name, grant_req, grant)) if 'desired_capacity' in vdu_value: vdu_value['desired_capacity'] = ( - userdata_utils.get_param_capacity( + common_script_utils.get_param_capacity( vdu_name, inst, grant_req)) cps = nfv_dict.get('CP', {}) for cp_name, cp_value in cps.items(): if 'network' in cp_value: - cp_value['network'] = userdata_utils.get_param_network( + cp_value['network'] = common_script_utils.get_param_network( cp_name, grant, req) if 'fixed_ips' in cp_value: - ext_fixed_ips = userdata_utils.get_param_fixed_ips( + ext_fixed_ips = common_script_utils.get_param_fixed_ips( cp_name, grant, req) fixed_ips = [] for i in range(len(ext_fixed_ips)): @@ -70,7 +71,7 @@ class DefaultUserData(userdata_utils.AbstractUserData): fixed_ips.append(ips_i) cp_value['fixed_ips'] = fixed_ips - userdata_utils.apply_ext_managed_vls(top_hot, req, grant) + common_script_utils.apply_ext_managed_vls(top_hot, req, grant) if 'nfv' in req.get('additionalParams', {}): nfv_dict = inst_utils.json_merge_patch(nfv_dict, @@ -98,19 +99,19 @@ class DefaultUserData(userdata_utils.AbstractUserData): # NOTE: complete 'nfv' dict can not be made at the moment # since InstantiateVnfRequest is necessary to make it. - vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir) + vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir) flavour_id = inst['instantiatedVnfInfo']['flavourId'] hot_dict = vnfd.get_base_hot(flavour_id) top_hot = hot_dict['template'] - nfv_dict = userdata_utils.init_nfv_dict(top_hot) + nfv_dict = common_script_utils.init_nfv_dict(top_hot) vdus = nfv_dict.get('VDU', {}) new_vdus = {} for vdu_name, vdu_value in vdus.items(): if 'desired_capacity' in vdu_value: - capacity = userdata_utils.get_param_capacity( + capacity = common_script_utils.get_param_capacity( vdu_name, inst, grant_req) new_vdus[vdu_name] = {'desired_capacity': capacity} @@ -125,19 +126,19 @@ class DefaultUserData(userdata_utils.AbstractUserData): # It is thought that it is suitable that this method defines # here since it is very likely to scale method above. - vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir) + vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir) flavour_id = inst['instantiatedVnfInfo']['flavourId'] hot_dict = vnfd.get_base_hot(flavour_id) top_hot = hot_dict['template'] - nfv_dict = userdata_utils.init_nfv_dict(top_hot) + nfv_dict = common_script_utils.init_nfv_dict(top_hot) vdus = nfv_dict.get('VDU', {}) new_vdus = {} for vdu_name, vdu_value in vdus.items(): if 'desired_capacity' in vdu_value: - capacity = userdata_utils.get_current_capacity( + capacity = common_script_utils.get_current_capacity( vdu_name, inst) new_vdus[vdu_name] = {'desired_capacity': capacity} @@ -155,25 +156,26 @@ class DefaultUserData(userdata_utils.AbstractUserData): # NOTE: complete 'nfv' dict can not be made at the moment # since InstantiateVnfRequest is necessary to make it. - vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir) + vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir) flavour_id = inst['instantiatedVnfInfo']['flavourId'] hot_dict = vnfd.get_base_hot(flavour_id) top_hot = hot_dict['template'] - nfv_dict = userdata_utils.init_nfv_dict(top_hot) + nfv_dict = common_script_utils.init_nfv_dict(top_hot) cps = nfv_dict.get('CP', {}) new_cps = {} for cp_name, cp_value in cps.items(): if 'network' in cp_value: - network = userdata_utils.get_param_network(cp_name, grant, req) + network = common_script_utils.get_param_network( + cp_name, grant, req) if network is None: continue new_cps.setdefault(cp_name, {}) new_cps[cp_name]['network'] = network if 'fixed_ips' in cp_value: - ext_fixed_ips = userdata_utils.get_param_fixed_ips( + ext_fixed_ips = common_script_utils.get_param_fixed_ips( cp_name, grant, req) fixed_ips = [] for i in range(len(ext_fixed_ips)): @@ -200,27 +202,28 @@ class DefaultUserData(userdata_utils.AbstractUserData): # It is thought that it is suitable that this method defines # here since it is very likely to scale method above. - vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir) + vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir) flavour_id = inst['instantiatedVnfInfo']['flavourId'] hot_dict = vnfd.get_base_hot(flavour_id) top_hot = hot_dict['template'] - nfv_dict = userdata_utils.init_nfv_dict(top_hot) + nfv_dict = common_script_utils.init_nfv_dict(top_hot) cps = nfv_dict.get('CP', {}) new_cps = {} for cp_name, cp_value in cps.items(): if 'network' in cp_value: - network = userdata_utils.get_param_network_from_inst( + network = common_script_utils.get_param_network_from_inst( cp_name, inst) if network is None: continue new_cps.setdefault(cp_name, {}) new_cps[cp_name]['network'] = network if 'fixed_ips' in cp_value: - ext_fixed_ips = userdata_utils.get_param_fixed_ips_from_inst( - cp_name, inst) + ext_fixed_ips = ( + common_script_utils.get_param_fixed_ips_from_inst( + cp_name, inst)) fixed_ips = [] for i in range(len(ext_fixed_ips)): if i not in cp_value['fixed_ips']: diff --git a/tacker/sol_refactored/infra_drivers/openstack/userdata_utils.py b/tacker/sol_refactored/infra_drivers/openstack/userdata_utils.py index 83e4d6bb5..341449df4 100644 --- a/tacker/sol_refactored/infra_drivers/openstack/userdata_utils.py +++ b/tacker/sol_refactored/infra_drivers/openstack/userdata_utils.py @@ -16,7 +16,6 @@ import abc from tacker.sol_refactored.common import exceptions as sol_ex -from tacker.sol_refactored.common import vnfd_utils class AbstractUserData(metaclass=abc.ABCMeta): @@ -54,224 +53,3 @@ class AbstractUserData(metaclass=abc.ABCMeta): @abc.abstractmethod def heal(req, inst, grant_req, grant, tmp_csar_dir): raise sol_ex.UserDataClassNotImplemented() - - -def get_vnfd(vnfd_id, csar_dir): - vnfd = vnfd_utils.Vnfd(vnfd_id) - vnfd.init_from_csar_dir(csar_dir) - return vnfd - - -def init_nfv_dict(hot_template): - get_params = [] - - def _get_get_param(prop): - if isinstance(prop, dict): - for key, value in prop.items(): - if key == 'get_param': - get_params.append(value) - else: - _get_get_param(value) - elif isinstance(prop, list): - for value in prop: - _get_get_param(value) - - for res in hot_template.get('resources', {}).values(): - _get_get_param(res.get('properties', {})) - - nfv = {} - - for param in get_params: - if (not isinstance(param, list) or len(param) < 4 or - param[0] != 'nfv'): - continue - parent = nfv - for item in param[1:-1]: - parent.setdefault(item, {}) - parent = parent[item] - parent[param[-1]] = None - - # TODO(oda-g): enhance to handle list - # NOTE: List is not considered here and only 'fixed_ips' is treated as - # list in userdata_default.py at the moment. - # Note that if handling list is enhanced, userdata_default.py is - # necessary to modify. - return nfv - - -def get_param_flavor(vdu_name, flavour_id, vnfd, grant): - # try to get from grant - if 'vimAssets' in grant: - assets = grant['vimAssets'] - if 'computeResourceFlavours' in assets: - flavours = assets['computeResourceFlavours'] - for flavour in flavours: - if flavour['vnfdVirtualComputeDescId'] == vdu_name: - return flavour['vimFlavourId'] - - # if specified in VNFD, use it - # NOTE: if not found. parameter is set to None. - # may be error when stack create - return vnfd.get_compute_flavor(flavour_id, vdu_name) - - -def get_param_image(vdu_name, flavour_id, vnfd, grant): - # try to get from grant - if 'vimAssets' in grant: - assets = grant['vimAssets'] - if 'softwareImages' in assets: - images = assets['softwareImages'] - for image in images: - if image['vnfdSoftwareImageId'] == vdu_name: - return image['vimSoftwareImageId'] - - # if specified in VNFD, use it - # NOTE: if not found. parameter is set to None. - # may be error when stack create - sw_images = vnfd.get_sw_image(flavour_id) - for name, image in sw_images.items(): - if name == vdu_name: - return image - - -def get_param_zone(vdu_name, grant_req, grant): - if 'zones' not in grant or 'addResources' not in grant: - return - - for res in grant['addResources']: - if 'zoneId' not in res: - continue - for req_res in grant_req['addResources']: - if req_res['id'] == res['resourceDefinitionId']: - if req_res.get('resourceTemplateId') == vdu_name: - for zone in grant['zones']: - if zone['id'] == res['zoneId']: # must be found - return zone['zoneId'] - - -def get_current_capacity(vdu_name, inst): - count = 0 - inst_vnfcs = (inst.get('instantiatedVnfInfo', {}) - .get('vnfcResourceInfo', [])) - for inst_vnfc in inst_vnfcs: - if inst_vnfc['vduId'] == vdu_name: - count += 1 - - return count - - -def get_param_capacity(vdu_name, inst, grant_req): - # NOTE: refer grant_req here since interpretation of VNFD was done when - # making grant_req. - count = get_current_capacity(vdu_name, inst) - - add_reses = grant_req.get('addResources', []) - for res_def in add_reses: - if (res_def['type'] == 'COMPUTE' and - res_def['resourceTemplateId'] == vdu_name): - count += 1 - - rm_reses = grant_req.get('removeResources', []) - for res_def in rm_reses: - if (res_def['type'] == 'COMPUTE' and - res_def['resourceTemplateId'] == vdu_name): - count -= 1 - - return count - - -def _get_fixed_ips_from_extcp(extcp): - fixed_ips = [] - for cp_conf in extcp['cpConfig'].values(): - if 'cpProtocolData' not in cp_conf: - continue - for prot_data in cp_conf['cpProtocolData']: - if 'ipOverEthernet' not in prot_data: - continue - if 'ipAddresses' not in prot_data['ipOverEthernet']: - continue - for ip in prot_data['ipOverEthernet']['ipAddresses']: - data = {} - if 'fixedAddresses' in ip: - # pick up only one ip address - data['ip_address'] = str(ip['fixedAddresses'][0]) - if 'subnetId' in ip: - data['subnet'] = ip['subnetId'] - if data: - fixed_ips.append(data) - return fixed_ips - - -def get_param_network(cp_name, grant, req): - # see grant first then instantiateVnfRequest - vls = grant.get('extVirtualLinks', []) + req.get('extVirtualLinks', []) - for vl in vls: - for extcp in vl['extCps']: - if extcp['cpdId'] == cp_name: - return vl['resourceId'] - - -def get_param_fixed_ips(cp_name, grant, req): - # see grant first then instantiateVnfRequest - vls = grant.get('extVirtualLinks', []) + req.get('extVirtualLinks', []) - for vl in vls: - for extcp in vl['extCps']: - if extcp['cpdId'] == cp_name: - return _get_fixed_ips_from_extcp(extcp) - - -def get_param_network_from_inst(cp_name, inst): - for vl in inst['instantiatedVnfInfo'].get('extVirtualLinkInfo', []): - for extcp in vl.get('currentVnfExtCpData', []): - if extcp['cpdId'] == cp_name: - return vl['resourceHandle']['resourceId'] - - -def get_param_fixed_ips_from_inst(cp_name, inst): - for vl in inst['instantiatedVnfInfo'].get('extVirtualLinkInfo', []): - for extcp in vl.get('currentVnfExtCpData', []): - if extcp['cpdId'] == cp_name: - return _get_fixed_ips_from_extcp(extcp) - - -def apply_ext_managed_vls(hot_dict, req, grant): - # see grant first then instantiateVnfRequest - mgd_vls = (grant.get('extManagedVirtualLinks', []) + - req.get('extManagedVirtualLinks', [])) - - # NOTE: refer HOT only here, not refer VNFD. - # HOT and VNFD must be consistent. - - for mgd_vl in mgd_vls: - vl_name = mgd_vl['vnfVirtualLinkDescId'] - network_id = mgd_vl['resourceId'] - get_res = {'get_resource': vl_name} - - def _change(item): - if not isinstance(item, dict): - return - for key, value in item.items(): - if value == get_res: - item[key] = network_id - else: - _change(value) - - del_reses = [] - for res_name, res_data in hot_dict.get('resources', {}).items(): - # delete network definition - if res_name == vl_name: - del_reses.append(res_name) - - # delete subnet definition - if res_data['type'] == 'OS::Neutron::Subnet': - net = (res_data.get('properties', {}) - .get('network', {}) - .get('get_resource')) - if net == vl_name: - del_reses.append(res_name) - - # change '{get_resource: vl_name}' to network_id - _change(res_data) - - for res_name in del_reses: - hot_dict['resources'].pop(res_name) diff --git a/tacker/sol_refactored/nfvo/local_nfvo.py b/tacker/sol_refactored/nfvo/local_nfvo.py index 7d6667b17..403461742 100644 --- a/tacker/sol_refactored/nfvo/local_nfvo.py +++ b/tacker/sol_refactored/nfvo/local_nfvo.py @@ -210,6 +210,52 @@ class LocalNfvo(object): softwareImages=vim_sw_images ) + def change_vnfpkg_grant(self, context, grant_req, grant_res): + attr_list = ['updateResources', 'addResources', 'removeResources'] + for attr in attr_list: + if grant_req.obj_attr_is_set(attr): + res_list = [] + for res_def in grant_req[attr]: + g_info = objects.GrantInfoV1(res_def.id) + res_list.append(g_info) + grant_res[attr] = res_list + vnfd = self.get_vnfd(context, grant_req.vnfdId) + sw_image_data = vnfd.get_sw_image_data(grant_req.flavourId) + target_vdu_ids = [res['resourceTemplateId'] for + res in grant_req['updateResources']] + vdu_nodes = {key: value for key, value in vnfd.get_vdu_nodes( + grant_req.flavourId).items() if key in target_vdu_ids} + target_storage_nodes = [] + for key, value in vdu_nodes.items(): + target_storage_nodes.extend(vnfd.get_vdu_storages(value)) + + vim_sw_images = [] + for res_id, sw_data in sw_image_data.items(): + if 'file' in sw_data and (res_id in target_storage_nodes or + res_id in target_vdu_ids): + vim_info = self._get_vim_info(context, grant_req) + if vim_info is None: + msg = "No VimConnectionInfo to create glance image" + LOG.exception(msg) + raise sol_ex.LocalNfvoGrantFailed(sol_detail=msg) + try: + image = self._glance_create_image( + vim_info, vnfd, sw_data, grant_req.vnfInstanceId) + except Exception: + msg = "glance image create failed" + LOG.exception(msg) + raise sol_ex.LocalNfvoGrantFailed(sol_detail=msg) + else: + image = sw_data['name'] + vim_sw_image = objects.VimSoftwareImageV1( + vnfdSoftwareImageId=res_id, + vimSoftwareImageId=image) + vim_sw_images.append(vim_sw_image) + if vim_sw_images: + grant_res.vimAssets = objects.GrantV1_VimAssets( + softwareImages=vim_sw_images + ) + def grant(self, context, grant_req): grant_res = objects.GrantV1( id=uuidutils.generate_uuid(), @@ -222,6 +268,9 @@ class LocalNfvo(object): if grant_req.operation == v2_fields.LcmOperationType.INSTANTIATE: self.instantiate_grant(context, grant_req, grant_res) + elif grant_req.operation == v2_fields.LcmOperationType.CHANGE_VNFPKG: + self.change_vnfpkg_grant(context, grant_req, grant_res) + endpoint = config.CONF.v2_vnfm.endpoint grant_res._links = objects.GrantV1_Links( vnfLcmOpOcc=objects.Link( @@ -312,8 +361,10 @@ class LocalNfvo(object): if vim_info is None: # never happen. just for code consistency. return - self._glance_delete_images(vim_info, inst.id) - elif lcmocc.operation == v2_fields.LcmOperationType.MODIFY_INFO: + if vim_info.vimType == 'ETSINFV.OPENSTACK_KEYSTONE.V_3': + self._glance_delete_images(vim_info, inst.id) + elif lcmocc.operation == v2_fields.LcmOperationType.MODIFY_INFO or ( + lcmocc.operation == v2_fields.LcmOperationType.CHANGE_VNFPKG): if (lcmocc.operationState == v2_fields.LcmOperationStateType.PROCESSING): # register vnfdId of vnf instance so that diff --git a/tacker/sol_refactored/objects/v2/change_current_vnf_pkg_request.py b/tacker/sol_refactored/objects/v2/change_current_vnf_pkg_request.py index fa48aa384..8baf4b9a2 100644 --- a/tacker/sol_refactored/objects/v2/change_current_vnf_pkg_request.py +++ b/tacker/sol_refactored/objects/v2/change_current_vnf_pkg_request.py @@ -28,13 +28,23 @@ class ChangeCurrentVnfPkgRequest(base.TackerObject, fields = { 'vnfdId': fields.StringField(nullable=False), + # NOTE: 'extVirtualLinks' is not supported. + # It can be specified but make no effect at all. 'extVirtualLinks': fields.ListOfObjectsField( 'ExtVirtualLinkData', nullable=True), + # NOTE: 'extManagedVirtualLinks' is not supported. + # It can be specified but make no effect at all. 'extManagedVirtualLinks': fields.ListOfObjectsField( 'ExtManagedVirtualLinkData', nullable=True), + # NOTE: 'vimConnectionInfo' is not supported. + # It can be specified but make no effect at all. 'vimConnectionInfo': fields.DictOfObjectsField( 'VimConnectionInfo', nullable=True), 'additionalParams': fields.KeyValuePairsField(nullable=True), + # NOTE: 'extensions' is not supported. + # It can be specified but make no effect at all. 'extensions': fields.KeyValuePairsField(nullable=True), + # NOTE: 'vnfConfigurableProperties' is not supported. + # It can be specified but make no effect at all. 'vnfConfigurableProperties': fields.KeyValuePairsField(nullable=True), } diff --git a/tacker/sol_refactored/test-tools/cli.py b/tacker/sol_refactored/test-tools/cli.py index ce224ba3e..ccc7e8a76 100644 --- a/tacker/sol_refactored/test-tools/cli.py +++ b/tacker/sol_refactored/test-tools/cli.py @@ -112,6 +112,12 @@ class Client(object): path, "POST", body=req_body, version="2.0.0") self.print(resp, body) + def change_vnfpkg(self, id, req_body): + path = self.path + '/' + id + '/change_vnfpkg' + resp, body = self.client.do_request( + path, "POST", body=req_body, version="2.0.0") + self.print(resp, body) + def retry(self, id): path = self.path + '/' + id + '/retry' resp, body = self.client.do_request(path, "POST", version="2.0.0") @@ -140,6 +146,7 @@ def usage(): print(" inst scale {id} body(path of content)") print(" inst heal {id} body(path of content)") print(" inst chg_ext_conn {id} body(path of content)") + print(" inst change_vnfpkg {id} body(path of content)") print(" subsc create body(path of content)") print(" subsc list [body(path of content)]") print(" subsc show {id}") @@ -166,7 +173,8 @@ if __name__ == '__main__': if resource == "inst": if action not in ["create", "list", "show", "delete", "update", - "inst", "term", "scale", "heal", "chg_ext_conn"]: + "inst", "term", "scale", "heal", "chg_ext_conn", + "change_vnfpkg"]: usage() client = Client("/vnflcm/v2/vnf_instances") elif resource == "subsc": @@ -224,6 +232,10 @@ if __name__ == '__main__': if len(sys.argv) != 5: usage() client.chg_ext_conn(sys.argv[3], get_body(sys.argv[4])) + elif action == "change_vnfpkg": + if len(sys.argv) != 5: + usage() + client.change_vnfpkg(sys.argv[3], get_body(sys.argv[4])) elif action == "retry": if len(sys.argv) != 4: usage() diff --git a/tacker/tests/functional/sol_v2/base_v2.py b/tacker/tests/functional/sol_v2/base_v2.py index f11ff0c43..d28277af7 100644 --- a/tacker/tests/functional/sol_v2/base_v2.py +++ b/tacker/tests/functional/sol_v2/base_v2.py @@ -76,6 +76,9 @@ class BaseSolV2Test(base.BaseTestCase): service_type='compute') cls.heat_client = heat_utils.HeatClient(vim_info) + cls.cinder_client = http_client.HttpClient( + auth, service_type='block-storage') + @classmethod def tearDownClass(cls): super(BaseSolV2Test, cls).tearDownClass() @@ -328,11 +331,21 @@ class BaseSolV2Test(base.BaseTestCase): return self.tacker_client.do_request( path, "POST", body=req_body, version="2.0.0") + def change_vnfpkg(self, inst_id, req_body): + path = "/vnflcm/v2/vnf_instances/{}/change_vnfpkg".format(inst_id) + return self.tacker_client.do_request( + path, "POST", body=req_body, version="2.0.0") + def terminate_vnf_instance(self, inst_id, req_body): path = "/vnflcm/v2/vnf_instances/{}/terminate".format(inst_id) return self.tacker_client.do_request( path, "POST", body=req_body, version="2.0.0") + def rollback(self, lcmocc_id): + path = "/vnflcm/v2/vnf_lcm_op_occs/{}/rollback".format(lcmocc_id) + return self.tacker_client.do_request( + path, "POST", version="2.0.0") + def wait_lcmocc_complete(self, lcmocc_id): # NOTE: It is not necessary to set timeout because the operation # itself set timeout and the state will become 'FAILED_TEMP'. @@ -476,3 +489,21 @@ class BaseSolV2Test(base.BaseTestCase): callback_url) self.assertEqual(1, len(notify_mock_responses)) self.assertEqual(204, notify_mock_responses[0].status_code) + + def get_current_vdu_image( + self, stack_id, stack_name, resource_name): + vdu_info = self.heat_client.get_resource_info( + f"{stack_name}/{stack_id}", resource_name) + if vdu_info.get('attributes').get('image'): + image_id = vdu_info.get('attributes').get('image').get('id') + else: + volume_ids = [volume.get('id') for volume in vdu_info.get( + 'attributes').get('os-extended-volumes:volumes_attached')] + for volume_id in volume_ids: + path = f"/volumes/{volume_id}" + resp, resp_body = self.cinder_client.do_request(path, "GET") + if resp_body['volume']['volume_image_metadata']: + image_id = resp_body['volume'][ + 'volume_image_metadata'].get('image_id') + + return image_id diff --git a/tacker/tests/functional/sol_v2/paramgen.py b/tacker/tests/functional/sol_v2/paramgen.py index a5d1b9898..e51c8acad 100644 --- a/tacker/tests/functional/sol_v2/paramgen.py +++ b/tacker/tests/functional/sol_v2/paramgen.py @@ -829,3 +829,109 @@ def change_ext_conn_min(net_ids, subnets): ext_vl_1 ] } + + +def change_vnfpkg_create(vnfd_id): + return { + "vnfdId": vnfd_id, + "vnfInstanceName": "vnf_change_vnfpkg", + "vnfInstanceDescription": "test_change_vnfpkg_from_image_to_image", + "metadata": {"dummy-key": "dummy-val"} + } + + +def change_vnfpkg_instantiate(net_ids, subnet_ids, auth_url, + flavor_id='simple'): + ext_vl_1 = { + "id": uuidutils.generate_uuid(), + "resourceId": net_ids['net0'], + "extCps": [ + { + "cpdId": "VDU1_CP1", + "cpConfig": { + "VDU1_CP1_1": { + "cpProtocolData": [{ + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [{ + "type": "IPV4", + "numDynamicAddresses": 1}]}}]} + } + }, + { + "cpdId": "VDU2_CP1", + "cpConfig": { + "VDU2_CP1_1": { + "cpProtocolData": [{ + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [{ + "type": "IPV4", + "fixedAddresses": ["10.10.0.101"]}]}}]} + } + } + ], + } + + return { + "flavourId": flavor_id, + "instantiationLevelId": "instantiation_level_1", + "extVirtualLinks": [ + ext_vl_1 + ], + "vimConnectionInfo": { + "vim1": { + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", + "vimId": uuidutils.generate_uuid(), + "interfaceInfo": {"endpoint": auth_url}, + "accessInfo": { + "username": "nfv_user", + "region": "RegionOne", + "password": "devstack", + "project": "nfv", + "projectDomain": "Default", + "userDomain": "Default" + } + } + }, + } + + +def change_vnfpkg(vnfd_id): + return { + "vnfdId": vnfd_id, + "additionalParams": { + "upgrade_type": "RollingUpdate", + "lcm-operation-coordinate-old-vnf": + "./Scripts/coordinate_old_vnf.py", + "lcm-operation-coordinate-old-vnf-class": "CoordinateOldVnf", + "lcm-operation-coordinate-new-vnf": + "./Scripts/coordinate_new_vnf.py", + "lcm-operation-coordinate-new-vnf-class": "CoordinateNewVnf", + "vdu_params": [{ + "vdu_id": "VDU1", + "old_vnfc_param": { + "cp_name": "VDU1_CP1", + "username": "ubuntu", + "password": "ubuntu" + }, + "new_vnfc_param": { + "cp_name": "VDU1_CP1", + "username": "ubuntu", + "password": "ubuntu" + } + }, { + "vdu_id": "VDU2", + "old_vnfc_param": { + "cp_name": "VDU2_CP1", + "username": "ubuntu", + "password": "ubuntu" + }, + "new_vnfc_param": { + "cp_name": "VDU2_CP1", + "username": "ubuntu", + "password": "ubuntu" + } + }] + } + } diff --git a/tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/UserData/userdata.py b/tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/UserData/userdata.py index 29283981a..f5604ff32 100644 --- a/tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/UserData/userdata.py +++ b/tacker/tests/functional/sol_v2/samples/basic_lcms_max/contents/UserData/userdata.py @@ -15,6 +15,7 @@ import yaml +from tacker.sol_refactored.common import common_script_utils from tacker.sol_refactored.common import vnf_instance_utils as inst_utils from tacker.sol_refactored.infra_drivers.openstack import userdata_utils @@ -47,39 +48,39 @@ class UserData(userdata_utils.AbstractUserData): link_port_ids.append(cp_conf['linkPortId']) return link_port_ids - vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir) + vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir) flavour_id = req['flavourId'] hot_dict = vnfd.get_base_hot(flavour_id) top_hot = hot_dict['template'] - nfv_dict = userdata_utils.init_nfv_dict(top_hot) + nfv_dict = common_script_utils.init_nfv_dict(top_hot) vdus = nfv_dict.get('VDU', {}) for vdu_name, vdu_value in vdus.items(): if 'computeFlavourId' in vdu_value: vdu_value['computeFlavourId'] = ( - userdata_utils.get_param_flavor( + common_script_utils.get_param_flavor( vdu_name, flavour_id, vnfd, grant)) if 'vcImageId' in vdu_value: - vdu_value['vcImageId'] = userdata_utils.get_param_image( + vdu_value['vcImageId'] = common_script_utils.get_param_image( vdu_name, flavour_id, vnfd, grant) if 'locationConstraints' in vdu_value: vdu_value['locationConstraints'] = ( - userdata_utils.get_param_zone( + common_script_utils.get_param_zone( vdu_name, grant_req, grant)) if 'desired_capacity' in vdu_value: vdu_value['desired_capacity'] = ( - userdata_utils.get_param_capacity( + common_script_utils.get_param_capacity( vdu_name, inst, grant_req)) cps = nfv_dict.get('CP', {}) for cp_name, cp_value in cps.items(): if 'network' in cp_value: - cp_value['network'] = userdata_utils.get_param_network( + cp_value['network'] = common_script_utils.get_param_network( cp_name, grant, req) if 'fixed_ips' in cp_value: - ext_fixed_ips = userdata_utils.get_param_fixed_ips( + ext_fixed_ips = common_script_utils.get_param_fixed_ips( cp_name, grant, req) fixed_ips = [] for i in range(len(ext_fixed_ips)): @@ -104,7 +105,7 @@ class UserData(userdata_utils.AbstractUserData): cp_value['port'] = _get_param_port( cp_name, grant, req).pop() - userdata_utils.apply_ext_managed_vls(top_hot, req, grant) + common_script_utils.apply_ext_managed_vls(top_hot, req, grant) if 'nfv' in req.get('additionalParams', {}): nfv_dict = inst_utils.json_merge_patch(nfv_dict, diff --git a/tacker/tests/functional/sol_v2/samples/scale_ng/contents/UserData/userdata.py b/tacker/tests/functional/sol_v2/samples/scale_ng/contents/UserData/userdata.py index 13b96c0f6..92f4784c9 100644 --- a/tacker/tests/functional/sol_v2/samples/scale_ng/contents/UserData/userdata.py +++ b/tacker/tests/functional/sol_v2/samples/scale_ng/contents/UserData/userdata.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 Nippon Telegraph and Telephone Corporation +# Copyright (C) 2022 Fujitsu # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -15,6 +15,7 @@ import yaml +from tacker.sol_refactored.common import common_script_utils from tacker.sol_refactored.common import vnf_instance_utils as inst_utils from tacker.sol_refactored.infra_drivers.openstack import userdata_utils @@ -47,39 +48,39 @@ class UserData(userdata_utils.AbstractUserData): link_port_ids.append(cp_conf['linkPortId']) return link_port_ids - vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir) + vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir) flavour_id = req['flavourId'] hot_dict = vnfd.get_base_hot(flavour_id) top_hot = hot_dict['template'] - nfv_dict = userdata_utils.init_nfv_dict(top_hot) + nfv_dict = common_script_utils.init_nfv_dict(top_hot) vdus = nfv_dict.get('VDU', {}) for vdu_name, vdu_value in vdus.items(): if 'computeFlavourId' in vdu_value: vdu_value['computeFlavourId'] = ( - userdata_utils.get_param_flavor( + common_script_utils.get_param_flavor( vdu_name, flavour_id, vnfd, grant)) if 'vcImageId' in vdu_value: - vdu_value['vcImageId'] = userdata_utils.get_param_image( + vdu_value['vcImageId'] = common_script_utils.get_param_image( vdu_name, flavour_id, vnfd, grant) if 'locationConstraints' in vdu_value: vdu_value['locationConstraints'] = ( - userdata_utils.get_param_zone( + common_script_utils.get_param_zone( vdu_name, grant_req, grant)) if 'desired_capacity' in vdu_value: vdu_value['desired_capacity'] = ( - userdata_utils.get_param_capacity( + common_script_utils.get_param_capacity( vdu_name, inst, grant_req)) cps = nfv_dict.get('CP', {}) for cp_name, cp_value in cps.items(): if 'network' in cp_value: - cp_value['network'] = userdata_utils.get_param_network( + cp_value['network'] = common_script_utils.get_param_network( cp_name, grant, req) if 'fixed_ips' in cp_value: - ext_fixed_ips = userdata_utils.get_param_fixed_ips( + ext_fixed_ips = common_script_utils.get_param_fixed_ips( cp_name, grant, req) fixed_ips = [] for i in range(len(ext_fixed_ips)): @@ -104,7 +105,7 @@ class UserData(userdata_utils.AbstractUserData): cp_value['port'] = _get_param_port( cp_name, grant, req).pop() - userdata_utils.apply_ext_managed_vls(top_hot, req, grant) + common_script_utils.apply_ext_managed_vls(top_hot, req, grant) if 'nfv' in req.get('additionalParams', {}): nfv_dict = inst_utils.json_merge_patch(nfv_dict, diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/BaseHOT/simple/base_hot_top.yaml b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/BaseHOT/simple/base_hot_top.yaml new file mode 100644 index 000000000..7f04e1c28 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/BaseHOT/simple/base_hot_top.yaml @@ -0,0 +1,54 @@ +heat_template_version: 2013-05-23 +description: 'Simple Base HOT for Sample VNF' + +parameters: + nfv: + type: json + +resources: + VDU1_scale: + type: OS::Heat::AutoScalingGroup + properties: + min_size: 1 + max_size: 3 + desired_capacity: { get_param: [ nfv, VDU, VDU1, desired_capacity ] } + resource: + type: base_hot_nested_VDU1.yaml + properties: + flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] } + image: { get_param: [ nfv, VDU, VDU1, vcImageId] } + net1: { get_param: [ nfv, CP, VDU1_CP1, network] } + + VDU1_scale_out: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: 1 + auto_scaling_group_id: + get_resource: VDU1_scale + adjustment_type: change_in_capacity + VDU1_scale_in: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: -1 + auto_scaling_group_id: + get_resource: VDU1_scale + adjustment_type: change_in_capacity + + VDU2: + type: OS::Nova::Server + properties: + flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] } + image: { get_param: [ nfv, VDU, VDU2, vcImageId] } + networks: + - port: + get_resource: VDU2_CP1 + + VDU2_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: [ nfv, CP, VDU2_CP1, network ] } + fixed_ips: + - ip_address: { get_param: [nfv, CP, VDU2_CP1, fixed_ips, 0, ip_address]} + + +outputs: {} diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/BaseHOT/simple/nested/base_hot_nested_VDU1.yaml b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/BaseHOT/simple/nested/base_hot_nested_VDU1.yaml new file mode 100644 index 000000000..14cd9a24d --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/BaseHOT/simple/nested/base_hot_nested_VDU1.yaml @@ -0,0 +1,27 @@ +heat_template_version: 2013-05-23 +description: 'VDU1 HOT for Sample VNF' + +parameters: + flavor: + type: string + image: + type: string + net1: + type: string + +resources: + VDU1: + type: OS::Nova::Server + properties: + flavor: { get_param: flavor } + name: VDU1 + image: { get_param: image } + networks: + - port: + get_resource: VDU1_CP1 + + VDU1_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: net1 } + diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/BaseHOT/volume/base_hot_top.yaml b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/BaseHOT/volume/base_hot_top.yaml new file mode 100644 index 000000000..1e6d90ccf --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/BaseHOT/volume/base_hot_top.yaml @@ -0,0 +1,53 @@ +heat_template_version: 2013-05-23 +description: 'Simple Base HOT for Sample VNF' + +parameters: + nfv: + type: json + +resources: + VDU1_scale: + type: OS::Heat::AutoScalingGroup + properties: + min_size: 1 + max_size: 3 + desired_capacity: { get_param: [ nfv, VDU, VDU1, desired_capacity ] } + resource: + type: base_hot_nested_VDU1.yaml + properties: + flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] } + image: { get_param: [ nfv, VDU, VDU1-VirtualStorage, vcImageId] } + net1: { get_param: [ nfv, CP, VDU1_CP1, network] } + + VDU1_scale_out: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: 1 + auto_scaling_group_id: + get_resource: VDU1_scale + adjustment_type: change_in_capacity + VDU1_scale_in: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: -1 + auto_scaling_group_id: + get_resource: VDU1_scale + adjustment_type: change_in_capacity + + VDU2: + type: OS::Nova::Server + properties: + flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] } + image: { get_param: [ nfv, VDU, VDU2, vcImageId] } + networks: + - port: + get_resource: VDU2_CP1 + + VDU2_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: [ nfv, CP, VDU2_CP1, network ] } + fixed_ips: + - ip_address: { get_param: [nfv, CP, VDU2_CP1, fixed_ips, 0, ip_address]} + +outputs: {} diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/BaseHOT/volume/nested/base_hot_nested_VDU1.yaml b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/BaseHOT/volume/nested/base_hot_nested_VDU1.yaml new file mode 100644 index 000000000..d3260f974 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/BaseHOT/volume/nested/base_hot_nested_VDU1.yaml @@ -0,0 +1,39 @@ +heat_template_version: 2013-05-23 +description: 'VDU1 HOT for Sample VNF' + +parameters: + flavor: + type: string + image: + type: string + net1: + type: string + +resources: + VDU1: + type: OS::Nova::Server + properties: + flavor: { get_param: flavor } + name: VDU1 + block_device_mapping_v2: [{"volume_id": { get_resource: VDU1-VirtualStorage }}] + networks: + - port: + get_resource: VDU1_CP1 + + VDU1_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: net1 } + + VDU1-VirtualStorage: + type: OS::Cinder::Volume + properties: + image: { get_param: image } + size: 4 + volume_type: { get_resource: multi } + multi: + type: OS::Cinder::VolumeType + properties: + name: { get_resource: VDU1_CP1 } + metadata: { multiattach: " True" } + diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/Definitions/change_vnf_pkg_new_image_df_simple.yaml b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/Definitions/change_vnf_pkg_new_image_df_simple.yaml new file mode 100644 index 000000000..fd35fe117 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/Definitions/change_vnf_pkg_new_image_df_simple.yaml @@ -0,0 +1,219 @@ +tosca_definitions_version: tosca_simple_yaml_1_2 + +description: Simple deployment flavour for Sample VNF + +imports: + - etsi_nfv_sol001_common_types.yaml + - etsi_nfv_sol001_vnfd_types.yaml + - change_vnf_pkg_types.yaml + +topology_template: + inputs: + descriptor_id: + type: string + descriptor_version: + type: string + provider: + type: string + product_name: + type: string + software_version: + type: string + vnfm_info: + type: list + entry_schema: + type: string + flavour_id: + type: string + flavour_description: + type: string + + substitution_mappings: + node_type: company.provider.VNF + properties: + flavour_id: simple + requirements: + virtual_link_external1_1: [ VDU1_CP1, virtual_link ] + virtual_link_external1_2: [ VDU2_CP1, virtual_link ] + + node_templates: + VNF: + type: company.provider.VNF + properties: + flavour_description: A simple flavour + interfaces: + Vnflcm: + instantiate: [] + instantiate_start: [] + instantiate_end: [] + terminate: [] + terminate_start: [] + terminate_end: [] + modify_information: [] + modify_information_start: [] + modify_information_end: [] + + VDU1: + type: tosca.nodes.nfv.Vdu.Compute + properties: + name: VDU1 + description: VDU1 compute node + vdu_profile: + min_number_of_instances: 1 + max_number_of_instances: 3 + sw_image_data: + name: VDU1-image + version: '0.5.2' + checksum: + algorithm: sha-256 + hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464 + container_format: bare + disk_format: qcow2 + min_disk: 0 GB + min_ram: 256 MB + size: 2 GB + capabilities: + virtual_compute: + properties: + requested_additional_capabilities: + properties: + requested_additional_capability_name: m1.tiny + support_mandatory: true + target_performance_parameters: + entry_schema: test + virtual_memory: + virtual_mem_size: 512 MB + virtual_cpu: + num_virtual_cpu: 1 + virtual_local_storage: + - size_of_storage: 3 GB + artifacts: + sw_image: + type: tosca.artifacts.nfv.SwImage + file: ../Files/images/cirros-0.5.2-x86_64-disk.img + + VDU2: + type: tosca.nodes.nfv.Vdu.Compute + properties: + name: VDU2 + description: VDU2 compute node + vdu_profile: + min_number_of_instances: 1 + max_number_of_instances: 1 + sw_image_data: + name: VDU2-image + version: '0.5.2' + checksum: + algorithm: sha-256 + hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464 + container_format: bare + disk_format: qcow2 + min_disk: 0 GB + min_ram: 256 MB + size: 2 GB + capabilities: + virtual_compute: + properties: + requested_additional_capabilities: + properties: + requested_additional_capability_name: m1.tiny + support_mandatory: true + target_performance_parameters: + entry_schema: test + virtual_memory: + virtual_mem_size: 512 MB + virtual_cpu: + num_virtual_cpu: 1 + virtual_local_storage: + - size_of_storage: 3 GB + artifacts: + sw_image: + type: tosca.artifacts.nfv.SwImage + file: ../Files/images/cirros-0.5.2-x86_64-disk.img + + VDU1_CP1: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 0 + requirements: + - virtual_binding: VDU1 + + VDU2_CP1: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 0 + requirements: + - virtual_binding: VDU2 + + policies: + - scaling_aspects: + type: tosca.policies.nfv.ScalingAspects + properties: + aspects: + VDU1_scale: + name: VDU1_scale + description: VDU1 scaling aspect + max_scale_level: 2 + step_deltas: + - delta_1 + + - VDU1_initial_delta: + type: tosca.policies.nfv.VduInitialDelta + properties: + initial_delta: + number_of_instances: 2 + targets: [ VDU1 ] + + - VDU2_initial_delta: + type: tosca.policies.nfv.VduInitialDelta + properties: + initial_delta: + number_of_instances: 1 + targets: [ VDU2 ] + + - VDU1_scaling_aspect_deltas: + type: tosca.policies.nfv.VduScalingAspectDeltas + properties: + aspect: VDU1_scale + deltas: + delta_1: + number_of_instances: 1 + targets: [ VDU1 ] + + - instantiation_levels: + type: tosca.policies.nfv.InstantiationLevels + properties: + levels: + instantiation_level_1: + description: Smallest size + scale_info: + VDU1_scale: + scale_level: 0 + instantiation_level_2: + description: Largest size + scale_info: + VDU1_scale: + scale_level: 2 + default_level: instantiation_level_1 + + - VDU1_instantiation_levels: + type: tosca.policies.nfv.VduInstantiationLevels + properties: + levels: + instantiation_level_1: + number_of_instances: 1 + instantiation_level_2: + number_of_instances: 3 + targets: [ VDU1 ] + + - VDU2_instantiation_levels: + type: tosca.policies.nfv.VduInstantiationLevels + properties: + levels: + instantiation_level_1: + number_of_instances: 1 + instantiation_level_2: + number_of_instances: 1 + targets: [ VDU2 ] diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/Definitions/change_vnf_pkg_new_image_df_volume.yaml b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/Definitions/change_vnf_pkg_new_image_df_volume.yaml new file mode 100644 index 000000000..d567bdec5 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/Definitions/change_vnf_pkg_new_image_df_volume.yaml @@ -0,0 +1,224 @@ +tosca_definitions_version: tosca_simple_yaml_1_2 + +description: Simple deployment flavour for Sample VNF + +imports: + - etsi_nfv_sol001_common_types.yaml + - etsi_nfv_sol001_vnfd_types.yaml + - change_vnf_pkg_types.yaml + +topology_template: + inputs: + descriptor_id: + type: string + descriptor_version: + type: string + provider: + type: string + product_name: + type: string + software_version: + type: string + vnfm_info: + type: list + entry_schema: + type: string + flavour_id: + type: string + flavour_description: + type: string + + substitution_mappings: + node_type: company.provider.VNF + properties: + flavour_id: volume + requirements: + virtual_link_external1_1: [ VDU1_CP1, virtual_link ] + virtual_link_external1_2: [ VDU2_CP1, virtual_link ] + + node_templates: + VNF: + type: company.provider.VNF + properties: + flavour_description: A simple flavour + interfaces: + Vnflcm: + instantiate: [] + instantiate_start: [] + instantiate_end: [] + terminate: [] + terminate_start: [] + terminate_end: [] + modify_information: [] + modify_information_start: [] + modify_information_end: [] + + VDU1: + type: tosca.nodes.nfv.Vdu.Compute + properties: + name: VDU1 + description: VDU1 compute node + vdu_profile: + min_number_of_instances: 1 + max_number_of_instances: 3 + capabilities: + virtual_compute: + properties: + requested_additional_capabilities: + properties: + requested_additional_capability_name: m1.tiny + support_mandatory: true + target_performance_parameters: + entry_schema: test + virtual_memory: + virtual_mem_size: 512 MB + virtual_cpu: + num_virtual_cpu: 1 + virtual_local_storage: + - size_of_storage: 3 GB + requirements: + - virtual_storage: VDU1-VirtualStorage + + VDU1-VirtualStorage: + type: tosca.nodes.nfv.Vdu.VirtualBlockStorage + properties: + virtual_block_storage_data: + size_of_storage: 4 GB + rdma_enabled: true + sw_image_data: + name: cirros-0.5.2-x86_64-disk + version: '0.5.2' + checksum: + algorithm: sha-256 + hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464 + container_format: bare + disk_format: qcow2 + min_disk: 0 GB + min_ram: 256 MB + size: 2 GB + + VDU2: + type: tosca.nodes.nfv.Vdu.Compute + properties: + name: VDU2 + description: VDU2 compute node + vdu_profile: + min_number_of_instances: 1 + max_number_of_instances: 1 + sw_image_data: + name: VDU2-image + version: '0.5.2' + checksum: + algorithm: sha-256 + hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464 + container_format: bare + disk_format: qcow2 + min_disk: 0 GB + min_ram: 256 MB + size: 2 GB + capabilities: + virtual_compute: + properties: + requested_additional_capabilities: + properties: + requested_additional_capability_name: m1.tiny + support_mandatory: true + target_performance_parameters: + entry_schema: test + virtual_memory: + virtual_mem_size: 512 MB + virtual_cpu: + num_virtual_cpu: 1 + virtual_local_storage: + - size_of_storage: 3 GB + artifacts: + sw_image: + type: tosca.artifacts.nfv.SwImage + file: ../Files/images/cirros-0.5.2-x86_64-disk.img + + VDU1_CP1: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 0 + requirements: + - virtual_binding: VDU1 + + VDU2_CP1: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 0 + requirements: + - virtual_binding: VDU2 + + policies: + - scaling_aspects: + type: tosca.policies.nfv.ScalingAspects + properties: + aspects: + VDU1_scale: + name: VDU1_scale + description: VDU1 scaling aspect + max_scale_level: 2 + step_deltas: + - delta_1 + + - VDU1_initial_delta: + type: tosca.policies.nfv.VduInitialDelta + properties: + initial_delta: + number_of_instances: 2 + targets: [ VDU1 ] + + - VDU2_initial_delta: + type: tosca.policies.nfv.VduInitialDelta + properties: + initial_delta: + number_of_instances: 1 + targets: [ VDU2 ] + + - VDU1_scaling_aspect_deltas: + type: tosca.policies.nfv.VduScalingAspectDeltas + properties: + aspect: VDU1_scale + deltas: + delta_1: + number_of_instances: 1 + targets: [ VDU1 ] + + - instantiation_levels: + type: tosca.policies.nfv.InstantiationLevels + properties: + levels: + instantiation_level_1: + description: Smallest size + scale_info: + VDU1_scale: + scale_level: 0 + instantiation_level_2: + description: Largest size + scale_info: + VDU1_scale: + scale_level: 2 + default_level: instantiation_level_1 + + - VDU1_instantiation_levels: + type: tosca.policies.nfv.VduInstantiationLevels + properties: + levels: + instantiation_level_1: + number_of_instances: 1 + instantiation_level_2: + number_of_instances: 3 + targets: [ VDU1 ] + + - VDU2_instantiation_levels: + type: tosca.policies.nfv.VduInstantiationLevels + properties: + levels: + instantiation_level_1: + number_of_instances: 1 + instantiation_level_2: + number_of_instances: 1 + targets: [ VDU2 ] diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/Definitions/change_vnf_pkg_top.vnfd.yaml b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/Definitions/change_vnf_pkg_top.vnfd.yaml new file mode 100644 index 000000000..29e972f05 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/Definitions/change_vnf_pkg_top.vnfd.yaml @@ -0,0 +1,31 @@ +tosca_definitions_version: tosca_simple_yaml_1_2 + +description: Sample VNF + +imports: + - etsi_nfv_sol001_common_types.yaml + - etsi_nfv_sol001_vnfd_types.yaml + - change_vnf_pkg_types.yaml + - change_vnf_pkg_new_image_df_simple.yaml + +topology_template: + inputs: + selected_flavour: + type: string + description: VNF deployment flavour selected by the consumer. It is provided in the API + + node_templates: + VNF: + type: company.provider.VNF + properties: + flavour_id: { get_input: selected_flavour } + descriptor_id: b1bb0ce7-ebca-4fa7-95ed-4840d7000000 + provider: Company + product_name: Sample VNF + software_version: '1.0' + descriptor_version: '1.0' + vnfm_info: + - Tacker + requirements: + #- virtual_link_external # mapped in lower-level templates + #- virtual_link_internal # mapped in lower-level templates diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/Definitions/change_vnf_pkg_types.yaml b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/Definitions/change_vnf_pkg_types.yaml new file mode 100644 index 000000000..d33c10c35 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/Definitions/change_vnf_pkg_types.yaml @@ -0,0 +1,53 @@ +tosca_definitions_version: tosca_simple_yaml_1_2 + +description: VNF type definition + +imports: + - etsi_nfv_sol001_common_types.yaml + - etsi_nfv_sol001_vnfd_types.yaml + +node_types: + company.provider.VNF: + derived_from: tosca.nodes.nfv.VNF + properties: + descriptor_id: + type: string + constraints: [ valid_values: [ b1bb0ce7-ebca-4fa7-95ed-4840d7000000 ] ] + default: b1bb0ce7-ebca-4fa7-95ed-4840d7000000 + descriptor_version: + type: string + constraints: [ valid_values: [ '1.0' ] ] + default: '1.0' + provider: + type: string + constraints: [ valid_values: [ 'Company' ] ] + default: 'Company' + product_name: + type: string + constraints: [ valid_values: [ 'Sample VNF' ] ] + default: 'Sample VNF' + software_version: + type: string + constraints: [ valid_values: [ '1.0' ] ] + default: '1.0' + vnfm_info: + type: list + entry_schema: + type: string + constraints: [ valid_values: [ Tacker ] ] + default: [ Tacker ] + flavour_id: + type: string + constraints: [ valid_values: [ simple,volume ] ] + default: simple + flavour_description: + type: string + default: "flavour" + requirements: + - virtual_link_external1: + capability: tosca.capabilities.nfv.VirtualLinkable + - virtual_link_external2: + capability: tosca.capabilities.nfv.VirtualLinkable + interfaces: + Vnflcm: + type: tosca.interfaces.nfv.Vnflcm \ No newline at end of file diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/Scripts/coordinate_new_vnf.py b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/Scripts/coordinate_new_vnf.py new file mode 100644 index 000000000..ca96ae51c --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/Scripts/coordinate_new_vnf.py @@ -0,0 +1,145 @@ +# Copyright (C) 2022 Fujitsu +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import os +import pickle +import sys +import time + +from oslo_log import log as logging +import paramiko + +from tacker.sol_refactored.common import common_script_utils +from tacker.sol_refactored.common import exceptions as sol_ex +from tacker.sol_refactored.common import vnfd_utils + +LOG = logging.getLogger(__name__) +CMD_TIMEOUT = 30 +SERVER_WAIT_COMPLETE_TIME = 60 +SSH_CONNECT_RETRY_COUNT = 4 + + +class SampleNewCoordinateVNFScript(object): + + def __init__(self, req, inst, grant_req, grant, csar_dir, vdu_info): + self.req = req + self.inst = inst + self.grant_req = grant_req + self.grant = grant + self.csar_dir = csar_dir + self.vdu_info = vdu_info + + def coordinate_vnf(self): + # check ssh connect and os version + """(YiFeng) Add comment to check ssh access + + The next part of code is to check connect VM via ssh. + Since the zuul's network cannot check this content, so + we comment this part of code. If you want to check them + in your local environment, please uncomment. + # user = self.vdu_info.get('vdu_param').get( + # 'new_vnfc_param').get('username') + # password = self.vdu_info.get('vdu_param').get( + # 'new_vnfc_param').get('password') + # host = self.vdu_info.get("ssh_ip") + # commander = self._init_commander( + # user, password, host, retry=SSH_CONNECT_RETRY_COUNT) + # ssh_command = 'cat /etc/os-release | grep PRETTY_NAME' + # result = self._execute_command(commander, host, ssh_command) + # os_version = result[0].replace('\n', '').split('=')[1] + # LOG.info('The os version of this new VM is %s', os_version) + """ + # check image and flavour updated successfully + vnfd = vnfd_utils.Vnfd(self.req.get('vnfdId')) + vnfd.init_from_csar_dir(self.csar_dir) + vdu_infos = common_script_utils.get_vdu_info( + self.grant, self.inst, vnfd) + + vdu_id = self.vdu_info.get('vdu_param').get('vdu_id') + image = vdu_infos.get(vdu_id, {}).get('image') + flavor = vdu_infos.get(vdu_id, {}).get('flavor') + if self.vdu_info.get('new_image') != image or self.vdu_info.get( + 'new_flavor') != flavor: + error = "The VM's image or flavour update failed'" + LOG.error(error) + raise sol_ex.VMRunningFailed(error) + + def _init_commander(self, user, password, host, retry): + while retry > 0: + try: + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect( + host, username=user, password=password) + LOG.info("Connected to %s", host) + return ssh + except paramiko.AuthenticationException as e: + LOG.error("Authentication failed when connecting to %s", + host) + raise sol_ex.VMRunningFailed(e) + except (paramiko.SSHException, + paramiko.ssh_exception.NoValidConnectionsError) as e: + LOG.debug(e) + retry -= 1 + if retry == 0: + LOG.error(e) + raise sol_ex.VMRunningFailed(e) + time.sleep(SERVER_WAIT_COMPLETE_TIME) + + def _execute_command(self, commander, host, command): + try: + stdin, stdout, stderr = commander.exec_command(command) + cmd_out = stdout.readlines() + cmd_err = stderr.readlines() + return_code = stdout.channel.recv_exit_status() + except paramiko.SSHException as e: + LOG.error("Command execution failed at %s. Giving up", host) + raise e + finally: + commander.close() + if return_code != 0: + error = cmd_err + raise sol_ex.VMRunningFailed(error_info=error) + result = "cmd: %s, stdout: %s, stderr: %s, return code: %s" % ( + command, cmd_out, cmd_err, return_code) + LOG.debug("Remote command execution result: %s", result) + return cmd_out + + +def main(): + operation = "coordinate_vnf" + script_dict = pickle.load(sys.stdin.buffer) + req = script_dict['request'] + inst = script_dict['vnf_instance'] + grant_req = script_dict['grant_request'] + grant = script_dict['grant_response'] + csar_dir = script_dict['tmp_csar_dir'] + vdu_info = script_dict['vdu_info'] + script = SampleNewCoordinateVNFScript( + req, inst, grant_req, grant, + csar_dir, vdu_info) + try: + getattr(script, operation)() + except Exception: + raise Exception + + +if __name__ == "__main__": + try: + main() + os._exit(0) + except Exception as ex: + sys.stderr.write(str(ex)) + sys.stderr.flush() + os._exit(1) diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/TOSCA-Metadata/TOSCA.meta b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/TOSCA-Metadata/TOSCA.meta new file mode 100644 index 000000000..79a1f6c6c --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/contents/TOSCA-Metadata/TOSCA.meta @@ -0,0 +1,4 @@ +TOSCA-Meta-File-Version: 1.0 +CSAR-Version: 1.1 +Created-by: Onboarding portal +Entry-Definitions: Definitions/change_vnf_pkg_top.vnfd.yaml diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/pkggen.py b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/pkggen.py new file mode 100644 index 000000000..539839b5a --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_image/pkggen.py @@ -0,0 +1,51 @@ +# Copyright (C) 2022 Fujitsu +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json +import os +import shutil +import tempfile + +from oslo_utils import uuidutils + +from tacker.tests.functional.sol_v2 import paramgen +from tacker.tests.functional.sol_v2 import utils + + +zip_file_name = os.path.basename(os.path.abspath(".")) + '.zip' +tmp_dir = tempfile.mkdtemp() +vnfd_id = uuidutils.generate_uuid() + +image_dir = "../../../../etc/samples/etsi/nfv/common/Files/images/" +image_file = "cirros-0.5.2-x86_64-disk.img" +image_path = os.path.abspath(image_dir + image_file) + +utils.make_zip(".", tmp_dir, vnfd_id, image_path) + +shutil.move(os.path.join(tmp_dir, zip_file_name), ".") +shutil.rmtree(tmp_dir) + +# if your sample is change VM from image to image +change_vnfpkg_req_from_image_to_image = paramgen.change_vnfpkg(vnfd_id) + +with open("change_vnfpkg_req_from_image_to_image", "w", encoding='utf-8') as f: + f.write(json.dumps(change_vnfpkg_req_from_image_to_image, indent=2)) + +# if your sample is change VM from volume to image +change_vnfpkg_req_from_volume_to_image = paramgen.change_vnfpkg(vnfd_id) +del change_vnfpkg_req_from_volume_to_image['additionalParams']['vdu_params'][0] + +with open("change_vnfpkg_req_from_volume", "w", encoding='utf-8') as f: + f.write(json.dumps(change_vnfpkg_req_from_volume_to_image, indent=2)) diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/BaseHOT/simple/base_hot_top.yaml b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/BaseHOT/simple/base_hot_top.yaml new file mode 100644 index 000000000..229edb2fa --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/BaseHOT/simple/base_hot_top.yaml @@ -0,0 +1,65 @@ +heat_template_version: 2013-05-23 +description: 'Simple Base HOT for Sample VNF' + +parameters: + nfv: + type: json + +resources: + VDU1_scale: + type: OS::Heat::AutoScalingGroup + properties: + min_size: 1 + max_size: 3 + desired_capacity: { get_param: [ nfv, VDU, VDU1, desired_capacity ] } + resource: + type: base_hot_nested_VDU1.yaml + properties: + flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] } + image: { get_param: [ nfv, VDU, VDU1, vcImageId ] } + net1: { get_param: [ nfv, CP, VDU1_CP1, network] } + + VDU1_scale_out: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: 1 + auto_scaling_group_id: + get_resource: VDU1_scale + adjustment_type: change_in_capacity + VDU1_scale_in: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: -1 + auto_scaling_group_id: + get_resource: VDU1_scale + adjustment_type: change_in_capacity + + VDU2: + type: OS::Nova::Server + properties: + flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] } + block_device_mapping_v2: [{"volume_id": { get_resource: VDU2-VirtualStorage }}] + networks: + - port: + get_resource: VDU2_CP1 + + VDU2_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: [ nfv, CP, VDU2_CP1, network ] } + fixed_ips: + - ip_address: { get_param: [nfv, CP, VDU2_CP1, fixed_ips, 0, ip_address]} + + VDU2-VirtualStorage: + type: OS::Cinder::Volume + properties: + image: { get_param: [ nfv, VDU, VDU2-VirtualStorage, vcImageId ] } + size: 4 + volume_type: { get_resource: multi } + multi: + type: OS::Cinder::VolumeType + properties: + name: VDU2-multi + metadata: { multiattach: " True" } + +outputs: {} diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/BaseHOT/simple/nested/base_hot_nested_VDU1.yaml b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/BaseHOT/simple/nested/base_hot_nested_VDU1.yaml new file mode 100644 index 000000000..14cd9a24d --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/BaseHOT/simple/nested/base_hot_nested_VDU1.yaml @@ -0,0 +1,27 @@ +heat_template_version: 2013-05-23 +description: 'VDU1 HOT for Sample VNF' + +parameters: + flavor: + type: string + image: + type: string + net1: + type: string + +resources: + VDU1: + type: OS::Nova::Server + properties: + flavor: { get_param: flavor } + name: VDU1 + image: { get_param: image } + networks: + - port: + get_resource: VDU1_CP1 + + VDU1_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: net1 } + diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/BaseHOT/volume/base_hot_top.yaml b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/BaseHOT/volume/base_hot_top.yaml new file mode 100644 index 000000000..6e614de01 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/BaseHOT/volume/base_hot_top.yaml @@ -0,0 +1,65 @@ +heat_template_version: 2013-05-23 +description: 'Simple Base HOT for Sample VNF' + +parameters: + nfv: + type: json + +resources: + VDU1_scale: + type: OS::Heat::AutoScalingGroup + properties: + min_size: 1 + max_size: 3 + desired_capacity: { get_param: [ nfv, VDU, VDU1, desired_capacity ] } + resource: + type: base_hot_nested_VDU1.yaml + properties: + flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] } + image: { get_param: [ nfv, VDU, VDU1-VirtualStorage, vcImageId ] } + net1: { get_param: [ nfv, CP, VDU1_CP1, network] } + + VDU1_scale_out: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: 1 + auto_scaling_group_id: + get_resource: VDU1_scale + adjustment_type: change_in_capacity + VDU1_scale_in: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: -1 + auto_scaling_group_id: + get_resource: VDU1_scale + adjustment_type: change_in_capacity + + VDU2: + type: OS::Nova::Server + properties: + flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] } + block_device_mapping_v2: [{"volume_id": { get_resource: VDU2-VirtualStorage }}] + networks: + - port: + get_resource: VDU2_CP1 + + VDU2_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: [ nfv, CP, VDU2_CP1, network ] } + fixed_ips: + - ip_address: { get_param: [nfv, CP, VDU2_CP1, fixed_ips, 0, ip_address]} + + VDU2-VirtualStorage: + type: OS::Cinder::Volume + properties: + image: { get_param: [ nfv, VDU, VDU2-VirtualStorage, vcImageId ] } + size: 4 + volume_type: { get_resource: multi } + multi: + type: OS::Cinder::VolumeType + properties: + name: VDU2-multi + metadata: { multiattach: " True" } + +outputs: {} diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/BaseHOT/volume/nested/base_hot_nested_VDU1.yaml b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/BaseHOT/volume/nested/base_hot_nested_VDU1.yaml new file mode 100644 index 000000000..d3260f974 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/BaseHOT/volume/nested/base_hot_nested_VDU1.yaml @@ -0,0 +1,39 @@ +heat_template_version: 2013-05-23 +description: 'VDU1 HOT for Sample VNF' + +parameters: + flavor: + type: string + image: + type: string + net1: + type: string + +resources: + VDU1: + type: OS::Nova::Server + properties: + flavor: { get_param: flavor } + name: VDU1 + block_device_mapping_v2: [{"volume_id": { get_resource: VDU1-VirtualStorage }}] + networks: + - port: + get_resource: VDU1_CP1 + + VDU1_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: net1 } + + VDU1-VirtualStorage: + type: OS::Cinder::Volume + properties: + image: { get_param: image } + size: 4 + volume_type: { get_resource: multi } + multi: + type: OS::Cinder::VolumeType + properties: + name: { get_resource: VDU1_CP1 } + metadata: { multiattach: " True" } + diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/Definitions/change_vnf_pkg_new_volume_df_simple.yaml b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/Definitions/change_vnf_pkg_new_volume_df_simple.yaml new file mode 100644 index 000000000..462d33686 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/Definitions/change_vnf_pkg_new_volume_df_simple.yaml @@ -0,0 +1,224 @@ +tosca_definitions_version: tosca_simple_yaml_1_2 + +description: Simple deployment flavour for Sample VNF + +imports: + - etsi_nfv_sol001_common_types.yaml + - etsi_nfv_sol001_vnfd_types.yaml + - change_vnf_pkg_types.yaml + +topology_template: + inputs: + descriptor_id: + type: string + descriptor_version: + type: string + provider: + type: string + product_name: + type: string + software_version: + type: string + vnfm_info: + type: list + entry_schema: + type: string + flavour_id: + type: string + flavour_description: + type: string + + substitution_mappings: + node_type: company.provider.VNF + properties: + flavour_id: simple + requirements: + virtual_link_external1_1: [ VDU1_CP1, virtual_link ] + virtual_link_external1_2: [ VDU2_CP1, virtual_link ] + + node_templates: + VNF: + type: company.provider.VNF + properties: + flavour_description: A simple flavour + interfaces: + Vnflcm: + instantiate: [] + instantiate_start: [] + instantiate_end: [] + terminate: [] + terminate_start: [] + terminate_end: [] + modify_information: [] + modify_information_start: [] + modify_information_end: [] + + VDU1: + type: tosca.nodes.nfv.Vdu.Compute + properties: + name: VDU1 + description: VDU1 compute node + vdu_profile: + min_number_of_instances: 1 + max_number_of_instances: 3 + sw_image_data: + name: cirros-0.5.2-x86_64-disk + version: '0.5.2' + checksum: + algorithm: sha-256 + hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464 + container_format: bare + disk_format: qcow2 + min_disk: 0 GB + min_ram: 256 MB + size: 2 GB + capabilities: + virtual_compute: + properties: + requested_additional_capabilities: + properties: + requested_additional_capability_name: m1.tiny + support_mandatory: true + target_performance_parameters: + entry_schema: test + virtual_memory: + virtual_mem_size: 512 MB + virtual_cpu: + num_virtual_cpu: 1 + virtual_local_storage: + - size_of_storage: 3 GB + + VDU2: + type: tosca.nodes.nfv.Vdu.Compute + properties: + name: VDU2 + description: VDU2 compute node + vdu_profile: + min_number_of_instances: 1 + max_number_of_instances: 1 + capabilities: + virtual_compute: + properties: + requested_additional_capabilities: + properties: + requested_additional_capability_name: ds2G + support_mandatory: true + target_performance_parameters: + entry_schema: test + virtual_memory: + virtual_mem_size: 2 GB + virtual_cpu: + num_virtual_cpu: 2 + virtual_local_storage: + - size_of_storage: 20 GB + requirements: + - virtual_storage: VDU2-VirtualStorage + + VDU2-VirtualStorage: + type: tosca.nodes.nfv.Vdu.VirtualBlockStorage + properties: + virtual_block_storage_data: + size_of_storage: 1 GB + rdma_enabled: true + sw_image_data: + name: VDU2-VirtualStorage-image + version: '0.5.2' + checksum: + algorithm: sha-256 + hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464 + container_format: bare + disk_format: qcow2 + min_disk: 0 GB + min_ram: 256 MB + size: 12 GB + artifacts: + sw_image: + type: tosca.artifacts.nfv.SwImage + file: ../Files/images/cirros-0.5.2-x86_64-disk.img + + VDU1_CP1: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 0 + requirements: + - virtual_binding: VDU1 + + VDU2_CP1: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 0 + requirements: + - virtual_binding: VDU2 + + policies: + - scaling_aspects: + type: tosca.policies.nfv.ScalingAspects + properties: + aspects: + VDU1_scale: + name: VDU1_scale + description: VDU1 scaling aspect + max_scale_level: 2 + step_deltas: + - delta_1 + + - VDU1_initial_delta: + type: tosca.policies.nfv.VduInitialDelta + properties: + initial_delta: + number_of_instances: 2 + targets: [ VDU1 ] + + - VDU2_initial_delta: + type: tosca.policies.nfv.VduInitialDelta + properties: + initial_delta: + number_of_instances: 1 + targets: [ VDU2 ] + + - VDU1_scaling_aspect_deltas: + type: tosca.policies.nfv.VduScalingAspectDeltas + properties: + aspect: VDU1_scale + deltas: + delta_1: + number_of_instances: 1 + targets: [ VDU1 ] + + - instantiation_levels: + type: tosca.policies.nfv.InstantiationLevels + properties: + levels: + instantiation_level_1: + description: Smallest size + scale_info: + VDU1_scale: + scale_level: 0 + instantiation_level_2: + description: Largest size + scale_info: + VDU1_scale: + scale_level: 2 + default_level: instantiation_level_1 + + - VDU1_instantiation_levels: + type: tosca.policies.nfv.VduInstantiationLevels + properties: + levels: + instantiation_level_1: + number_of_instances: 1 + instantiation_level_2: + number_of_instances: 3 + targets: [ VDU1 ] + + - VDU2_instantiation_levels: + type: tosca.policies.nfv.VduInstantiationLevels + properties: + levels: + instantiation_level_1: + number_of_instances: 1 + instantiation_level_2: + number_of_instances: 1 + targets: [ VDU2 ] diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/Definitions/change_vnf_pkg_new_volume_df_volume.yaml b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/Definitions/change_vnf_pkg_new_volume_df_volume.yaml new file mode 100644 index 000000000..2191b9b91 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/Definitions/change_vnf_pkg_new_volume_df_volume.yaml @@ -0,0 +1,237 @@ +tosca_definitions_version: tosca_simple_yaml_1_2 + +description: Simple deployment flavour for Sample VNF + +imports: + - etsi_nfv_sol001_common_types.yaml + - etsi_nfv_sol001_vnfd_types.yaml + - change_vnf_pkg_types.yaml + +topology_template: + inputs: + descriptor_id: + type: string + descriptor_version: + type: string + provider: + type: string + product_name: + type: string + software_version: + type: string + vnfm_info: + type: list + entry_schema: + type: string + flavour_id: + type: string + flavour_description: + type: string + + substitution_mappings: + node_type: company.provider.VNF + properties: + flavour_id: volume + requirements: + virtual_link_external1_1: [ VDU1_CP1, virtual_link ] + virtual_link_external1_2: [ VDU2_CP1, virtual_link ] + + node_templates: + VNF: + type: company.provider.VNF + properties: + flavour_description: A simple flavour + interfaces: + Vnflcm: + instantiate: [] + instantiate_start: [] + instantiate_end: [] + terminate: [] + terminate_start: [] + terminate_end: [] + modify_information: [] + modify_information_start: [] + modify_information_end: [] + + VDU1: + type: tosca.nodes.nfv.Vdu.Compute + properties: + name: VDU1 + description: VDU1 compute node + vdu_profile: + min_number_of_instances: 1 + max_number_of_instances: 3 + capabilities: + virtual_compute: + properties: + requested_additional_capabilities: + properties: + requested_additional_capability_name: m1.tiny + support_mandatory: true + target_performance_parameters: + entry_schema: test + virtual_memory: + virtual_mem_size: 512 MB + virtual_cpu: + num_virtual_cpu: 1 + virtual_local_storage: + - size_of_storage: 3 GB + requirements: + - virtual_storage: VDU1-VirtualStorage + + VDU1-VirtualStorage: + type: tosca.nodes.nfv.Vdu.VirtualBlockStorage + properties: + virtual_block_storage_data: + size_of_storage: 1 GB + rdma_enabled: true + sw_image_data: + name: VDU1-VirtualStorage-image + version: '0.5.2' + checksum: + algorithm: sha-256 + hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464 + container_format: bare + disk_format: qcow2 + min_disk: 0 GB + min_ram: 256 MB + size: 12 GB + artifacts: + sw_image: + type: tosca.artifacts.nfv.SwImage + file: ../Files/images/cirros-0.5.2-x86_64-disk.img + + VDU2: + type: tosca.nodes.nfv.Vdu.Compute + properties: + name: VDU2 + description: VDU2 compute node + vdu_profile: + min_number_of_instances: 1 + max_number_of_instances: 1 + capabilities: + virtual_compute: + properties: + requested_additional_capabilities: + properties: + requested_additional_capability_name: m1.tiny + support_mandatory: true + target_performance_parameters: + entry_schema: test + virtual_memory: + virtual_mem_size: 512 MB + virtual_cpu: + num_virtual_cpu: 1 + virtual_local_storage: + - size_of_storage: 3 GB + requirements: + - virtual_storage: VDU2-VirtualStorage + + VDU2-VirtualStorage: + type: tosca.nodes.nfv.Vdu.VirtualBlockStorage + properties: + virtual_block_storage_data: + size_of_storage: 1 GB + rdma_enabled: true + sw_image_data: + name: VDU2-VirtualStorage-image + version: '0.5.2' + checksum: + algorithm: sha-256 + hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464 + container_format: bare + disk_format: qcow2 + min_disk: 0 GB + min_ram: 256 MB + size: 12 GB + artifacts: + sw_image: + type: tosca.artifacts.nfv.SwImage + file: ../Files/images/cirros-0.5.2-x86_64-disk.img + + VDU1_CP1: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 0 + requirements: + - virtual_binding: VDU1 + + VDU2_CP1: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 0 + requirements: + - virtual_binding: VDU2 + + policies: + - scaling_aspects: + type: tosca.policies.nfv.ScalingAspects + properties: + aspects: + VDU1_scale: + name: VDU1_scale + description: VDU1 scaling aspect + max_scale_level: 2 + step_deltas: + - delta_1 + + - VDU1_initial_delta: + type: tosca.policies.nfv.VduInitialDelta + properties: + initial_delta: + number_of_instances: 2 + targets: [ VDU1 ] + + - VDU2_initial_delta: + type: tosca.policies.nfv.VduInitialDelta + properties: + initial_delta: + number_of_instances: 1 + targets: [ VDU2 ] + + - VDU1_scaling_aspect_deltas: + type: tosca.policies.nfv.VduScalingAspectDeltas + properties: + aspect: VDU1_scale + deltas: + delta_1: + number_of_instances: 1 + targets: [ VDU1 ] + + - instantiation_levels: + type: tosca.policies.nfv.InstantiationLevels + properties: + levels: + instantiation_level_1: + description: Smallest size + scale_info: + VDU1_scale: + scale_level: 0 + instantiation_level_2: + description: Largest size + scale_info: + VDU1_scale: + scale_level: 2 + default_level: instantiation_level_1 + + - VDU1_instantiation_levels: + type: tosca.policies.nfv.VduInstantiationLevels + properties: + levels: + instantiation_level_1: + number_of_instances: 1 + instantiation_level_2: + number_of_instances: 3 + targets: [ VDU1 ] + + - VDU2_instantiation_levels: + type: tosca.policies.nfv.VduInstantiationLevels + properties: + levels: + instantiation_level_1: + number_of_instances: 1 + instantiation_level_2: + number_of_instances: 1 + targets: [ VDU2 ] diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/Definitions/change_vnf_pkg_top.vnfd.yaml b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/Definitions/change_vnf_pkg_top.vnfd.yaml new file mode 100644 index 000000000..e0ae9c6ce --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/Definitions/change_vnf_pkg_top.vnfd.yaml @@ -0,0 +1,31 @@ +tosca_definitions_version: tosca_simple_yaml_1_2 + +description: Sample VNF + +imports: + - etsi_nfv_sol001_common_types.yaml + - etsi_nfv_sol001_vnfd_types.yaml + - change_vnf_pkg_types.yaml + - change_vnf_pkg_new_volume_df_simple.yaml + +topology_template: + inputs: + selected_flavour: + type: string + description: VNF deployment flavour selected by the consumer. It is provided in the API + + node_templates: + VNF: + type: company.provider.VNF + properties: + flavour_id: { get_input: selected_flavour } + descriptor_id: b1bb0ce7-ebca-4fa7-95ed-4840d7000000 + provider: Company + product_name: Sample VNF + software_version: '1.0' + descriptor_version: '1.0' + vnfm_info: + - Tacker + requirements: + #- virtual_link_external # mapped in lower-level templates + #- virtual_link_internal # mapped in lower-level templates diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/Definitions/change_vnf_pkg_types.yaml b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/Definitions/change_vnf_pkg_types.yaml new file mode 100644 index 000000000..d33c10c35 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/Definitions/change_vnf_pkg_types.yaml @@ -0,0 +1,53 @@ +tosca_definitions_version: tosca_simple_yaml_1_2 + +description: VNF type definition + +imports: + - etsi_nfv_sol001_common_types.yaml + - etsi_nfv_sol001_vnfd_types.yaml + +node_types: + company.provider.VNF: + derived_from: tosca.nodes.nfv.VNF + properties: + descriptor_id: + type: string + constraints: [ valid_values: [ b1bb0ce7-ebca-4fa7-95ed-4840d7000000 ] ] + default: b1bb0ce7-ebca-4fa7-95ed-4840d7000000 + descriptor_version: + type: string + constraints: [ valid_values: [ '1.0' ] ] + default: '1.0' + provider: + type: string + constraints: [ valid_values: [ 'Company' ] ] + default: 'Company' + product_name: + type: string + constraints: [ valid_values: [ 'Sample VNF' ] ] + default: 'Sample VNF' + software_version: + type: string + constraints: [ valid_values: [ '1.0' ] ] + default: '1.0' + vnfm_info: + type: list + entry_schema: + type: string + constraints: [ valid_values: [ Tacker ] ] + default: [ Tacker ] + flavour_id: + type: string + constraints: [ valid_values: [ simple,volume ] ] + default: simple + flavour_description: + type: string + default: "flavour" + requirements: + - virtual_link_external1: + capability: tosca.capabilities.nfv.VirtualLinkable + - virtual_link_external2: + capability: tosca.capabilities.nfv.VirtualLinkable + interfaces: + Vnflcm: + type: tosca.interfaces.nfv.Vnflcm \ No newline at end of file diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/Scripts/coordinate_new_vnf.py b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/Scripts/coordinate_new_vnf.py new file mode 100644 index 000000000..2ce689c33 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/Scripts/coordinate_new_vnf.py @@ -0,0 +1,145 @@ +# Copyright (C) 2022 Fujitsu +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import os +import pickle +import sys +import time + +from oslo_log import log as logging +import paramiko + +from tacker.sol_refactored.common import common_script_utils +from tacker.sol_refactored.common import exceptions as sol_ex +from tacker.sol_refactored.common import vnfd_utils + +LOG = logging.getLogger(__name__) +CMD_TIMEOUT = 30 +SERVER_WAIT_COMPLETE_TIME = 60 +SSH_CONNECT_RETRY_COUNT = 4 + + +class SampleNewCoordinateVNFScript(object): + + def __init__(self, req, inst, grant_req, grant, csar_dir, vdu_info): + self.req = req + self.inst = inst + self.grant_req = grant_req + self.grant = grant + self.csar_dir = csar_dir + self.vdu_info = vdu_info + + def coordinate_vnf(self): + # check ssh connect and os version + """(YiFeng) Add comment to check ssh access + + The next part of code is to check connect VM via ssh. + Since the zuul's network cannot check this content, so + we comment this part of code. If you want to check them + in your local environment, please uncomment. + user = self.vdu_info.get('vdu_param').get( + 'new_vnfc_param').get('username') + password = self.vdu_info.get('vdu_param').get( + 'new_vnfc_param').get('password') + host = self.vdu_info.get("ssh_ip") + commander = self._init_commander( + user, password, host, retry=SSH_CONNECT_RETRY_COUNT) + ssh_command = 'cat /etc/os-release | grep PRETTY_NAME' + result = self._execute_command(commander, host, ssh_command) + os_version = result[0].replace('\n', '').split('=')[1] + LOG.info('The os version of this new VM is %s', os_version) + """ + # check image and flavour updated successfully + vnfd = vnfd_utils.Vnfd(self.req.get('vnfdId')) + vnfd.init_from_csar_dir(self.csar_dir) + vdu_infos = common_script_utils.get_vdu_info( + self.grant, self.inst, vnfd) + + vdu_id = self.vdu_info.get('vdu_param').get('vdu_id') + image = vdu_infos.get(vdu_id, {}).get('image') + flavor = vdu_infos.get(vdu_id, {}).get('flavor') + if self.vdu_info.get('new_image') != image or self.vdu_info.get( + 'new_flavor') != flavor: + error = "The VM's image or flavour update failed'" + LOG.error(error) + raise sol_ex.VMRunningFailed(error) + + def _init_commander(self, user, password, host, retry): + while retry > 0: + try: + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect( + host, username=user, password=password) + LOG.info("Connected to %s", host) + return ssh + except paramiko.AuthenticationException as e: + LOG.error("Authentication failed when connecting to %s", + host) + raise sol_ex.VMRunningFailed(e) + except (paramiko.SSHException, + paramiko.ssh_exception.NoValidConnectionsError) as e: + LOG.debug(e) + retry -= 1 + if retry == 0: + LOG.error(e) + raise sol_ex.VMRunningFailed(e) + time.sleep(SERVER_WAIT_COMPLETE_TIME) + + def _execute_command(self, commander, host, command): + try: + stdin, stdout, stderr = commander.exec_command(command) + cmd_out = stdout.readlines() + cmd_err = stderr.readlines() + return_code = stdout.channel.recv_exit_status() + except paramiko.SSHException as e: + LOG.error("Command execution failed at %s. Giving up", host) + raise e + finally: + commander.close() + if return_code != 0: + error = cmd_err + raise sol_ex.VMRunningFailed(error_info=error) + result = "cmd: %s, stdout: %s, stderr: %s, return code: %s" % ( + command, cmd_out, cmd_err, return_code) + LOG.debug("Remote command execution result: %s", result) + return cmd_out + + +def main(): + operation = "coordinate_vnf" + script_dict = pickle.load(sys.stdin.buffer) + req = script_dict['request'] + inst = script_dict['vnf_instance'] + grant_req = script_dict['grant_request'] + grant = script_dict['grant_response'] + csar_dir = script_dict['tmp_csar_dir'] + vdu_info = script_dict['vdu_info'] + script = SampleNewCoordinateVNFScript( + req, inst, grant_req, grant, + csar_dir, vdu_info) + try: + getattr(script, operation)() + except Exception: + raise Exception + + +if __name__ == "__main__": + try: + main() + os._exit(0) + except Exception as ex: + sys.stderr.write(str(ex)) + sys.stderr.flush() + os._exit(1) diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/Scripts/error_coordinate_new_vnf.py b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/Scripts/error_coordinate_new_vnf.py new file mode 100644 index 000000000..6b7204214 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/Scripts/error_coordinate_new_vnf.py @@ -0,0 +1,67 @@ +# Copyright (C) 2022 Fujitsu +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import os +import pickle +import sys + +from oslo_log import log as logging + + +LOG = logging.getLogger(__name__) +CMD_TIMEOUT = 30 +SERVER_WAIT_COMPLETE_TIME = 60 +SSH_CONNECT_RETRY_COUNT = 4 + + +class SampleErrorNewCoordinateVNFScript(object): + + def __init__(self, req, inst, grant_req, grant, csar_dir, vdu_info): + self.req = req + self.inst = inst + self.grant_req = grant_req + self.grant = grant + self.csar_dir = csar_dir + self.vdu_info = vdu_info + + def coordinate_vnf(self): + raise Exception("ErrorNewCoordinateVNFScript") + + +def main(): + operation = "coordinate_vnf" + script_dict = pickle.load(sys.stdin.buffer) + req = script_dict['request'] + inst = script_dict['vnf_instance'] + grant_req = script_dict['grant_request'] + grant = script_dict['grant_response'] + csar_dir = script_dict['tmp_csar_dir'] + vdu_info = script_dict['vdu_info'] + script = SampleErrorNewCoordinateVNFScript( + req, inst, grant_req, grant, + csar_dir, vdu_info) + try: + getattr(script, operation)() + except Exception: + raise Exception + + +if __name__ == "__main__": + try: + main() + os._exit(0) + except Exception as ex: + sys.stderr.write(str(ex)) + sys.stderr.flush() + os._exit(1) diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/TOSCA-Metadata/TOSCA.meta b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/TOSCA-Metadata/TOSCA.meta new file mode 100644 index 000000000..79a1f6c6c --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/contents/TOSCA-Metadata/TOSCA.meta @@ -0,0 +1,4 @@ +TOSCA-Meta-File-Version: 1.0 +CSAR-Version: 1.1 +Created-by: Onboarding portal +Entry-Definitions: Definitions/change_vnf_pkg_top.vnfd.yaml diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/pkggen.py b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/pkggen.py new file mode 100644 index 000000000..c16af7cd5 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_new_volume/pkggen.py @@ -0,0 +1,54 @@ +# Copyright (C) 2022 Fujitsu +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json +import os +import shutil +import tempfile + +from oslo_utils import uuidutils + +from tacker.tests.functional.sol_v2 import paramgen +from tacker.tests.functional.sol_v2 import utils + + +zip_file_name = os.path.basename(os.path.abspath(".")) + '.zip' +tmp_dir = tempfile.mkdtemp() +vnfd_id = uuidutils.generate_uuid() + +image_dir = "../../../../etc/samples/etsi/nfv/common/Files/images/" +image_file = "cirros-0.5.2-x86_64-disk.img" +image_path = os.path.abspath(image_dir + image_file) + +utils.make_zip(".", tmp_dir, vnfd_id, image_path) + +shutil.move(os.path.join(tmp_dir, zip_file_name), ".") +shutil.rmtree(tmp_dir) + +# if your sample is change VM from image to volume +change_vnfpkg_req_from_image_to_volume = paramgen.change_vnfpkg(vnfd_id) +del change_vnfpkg_req_from_image_to_volume['additionalParams']['vdu_params'][0] + +with open("change_vnfpkg_req_from_image_to_volume", "w", + encoding='utf-8') as f: + f.write(json.dumps(change_vnfpkg_req_from_image_to_volume, indent=2)) + +# if your sample is change VM from volume to volume +change_vnfpkg_req_from_volume_to_volume = paramgen.change_vnfpkg(vnfd_id) +del change_vnfpkg_req_from_volume_to_volume[ + 'additionalParams']['vdu_params'][0] + +with open("change_vnfpkg_req_from_volume", "w", encoding='utf-8') as f: + f.write(json.dumps(change_vnfpkg_req_from_volume_to_volume, indent=2)) diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/contents/BaseHOT/simple/base_hot_top.yaml b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/contents/BaseHOT/simple/base_hot_top.yaml new file mode 100644 index 000000000..15960bfb3 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/contents/BaseHOT/simple/base_hot_top.yaml @@ -0,0 +1,53 @@ +heat_template_version: 2013-05-23 +description: 'Simple Base HOT for Sample VNF' + +parameters: + nfv: + type: json + +resources: + VDU1_scale: + type: OS::Heat::AutoScalingGroup + properties: + min_size: 1 + max_size: 3 + desired_capacity: { get_param: [ nfv, VDU, VDU1, desired_capacity ] } + resource: + type: base_hot_nested_VDU1.yaml + properties: + flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] } + image: { get_param: [ nfv, VDU, VDU1, vcImageId] } + net1: { get_param: [ nfv, CP, VDU1_CP1, network] } + + VDU1_scale_out: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: 1 + auto_scaling_group_id: + get_resource: VDU1_scale + adjustment_type: change_in_capacity + VDU1_scale_in: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: -1 + auto_scaling_group_id: + get_resource: VDU1_scale + adjustment_type: change_in_capacity + + VDU2: + type: OS::Nova::Server + properties: + flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] } + image: { get_param: [ nfv, VDU, VDU2, vcImageId] } + networks: + - port: + get_resource: VDU2_CP1 + + VDU2_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: [ nfv, CP, VDU2_CP1, network ] } + fixed_ips: + - ip_address: { get_param: [nfv, CP, VDU2_CP2, fixed_ips, 0, ip_address]} + +outputs: {} diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/contents/BaseHOT/simple/nested/base_hot_nested_VDU1.yaml b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/contents/BaseHOT/simple/nested/base_hot_nested_VDU1.yaml new file mode 100644 index 000000000..747cb47c9 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/contents/BaseHOT/simple/nested/base_hot_nested_VDU1.yaml @@ -0,0 +1,26 @@ +heat_template_version: 2013-05-23 +description: 'VDU1 HOT for Sample VNF' + +parameters: + flavor: + type: string + image: + type: string + net1: + type: string + +resources: + VDU1: + type: OS::Nova::Server + properties: + flavor: { get_param: flavor } + name: VDU1 + image: { get_param: image } + networks: + - port: + get_resource: VDU1_CP1 + + VDU1_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: net1 } diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/contents/Definitions/change_vnf_pkg_error_image_df_simple.yaml b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/contents/Definitions/change_vnf_pkg_error_image_df_simple.yaml new file mode 100644 index 000000000..95369ddf0 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/contents/Definitions/change_vnf_pkg_error_image_df_simple.yaml @@ -0,0 +1,219 @@ +tosca_definitions_version: tosca_simple_yaml_1_2 + +description: Simple deployment flavour for Sample VNF + +imports: + - etsi_nfv_sol001_common_types.yaml + - etsi_nfv_sol001_vnfd_types.yaml + - change_vnf_pkg_types.yaml + +topology_template: + inputs: + descriptor_id: + type: string + descriptor_version: + type: string + provider: + type: string + product_name: + type: string + software_version: + type: string + vnfm_info: + type: list + entry_schema: + type: string + flavour_id: + type: string + flavour_description: + type: string + + substitution_mappings: + node_type: company.provider.VNF + properties: + flavour_id: simple + requirements: + virtual_link_external1_1: [ VDU1_CP1, virtual_link ] + virtual_link_external1_2: [ VDU2_CP1, virtual_link ] + + node_templates: + VNF: + type: company.provider.VNF + properties: + flavour_description: A simple flavour + interfaces: + Vnflcm: + instantiate: [] + instantiate_start: [] + instantiate_end: [] + terminate: [] + terminate_start: [] + terminate_end: [] + modify_information: [] + modify_information_start: [] + modify_information_end: [] + + VDU1: + type: tosca.nodes.nfv.Vdu.Compute + properties: + name: VDU1 + description: VDU1 compute node + vdu_profile: + min_number_of_instances: 1 + max_number_of_instances: 3 + sw_image_data: + name: VDU1-image + version: '0.5.2' + checksum: + algorithm: sha-256 + hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464 + container_format: bare + disk_format: qcow2 + min_disk: 0 GB + min_ram: 256 MB + size: 2 GB + capabilities: + virtual_compute: + properties: + requested_additional_capabilities: + properties: + requested_additional_capability_name: m1.error + support_mandatory: true + target_performance_parameters: + entry_schema: test + virtual_memory: + virtual_mem_size: 512 MB + virtual_cpu: + num_virtual_cpu: 1 + virtual_local_storage: + - size_of_storage: 3 GB + artifacts: + sw_image: + type: tosca.artifacts.nfv.SwImage + file: ../Files/images/cirros-0.5.2-x86_64-disk.img + + VDU2: + type: tosca.nodes.nfv.Vdu.Compute + properties: + name: VDU2 + description: VDU2 compute node + vdu_profile: + min_number_of_instances: 1 + max_number_of_instances: 1 + sw_image_data: + name: VDU2-image + version: '0.5.2' + checksum: + algorithm: sha-256 + hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464 + container_format: bare + disk_format: qcow2 + min_disk: 0 GB + min_ram: 256 MB + size: 2 GB + capabilities: + virtual_compute: + properties: + requested_additional_capabilities: + properties: + requested_additional_capability_name: m1.tiny + support_mandatory: true + target_performance_parameters: + entry_schema: test + virtual_memory: + virtual_mem_size: 512 MB + virtual_cpu: + num_virtual_cpu: 1 + virtual_local_storage: + - size_of_storage: 3 GB + artifacts: + sw_image: + type: tosca.artifacts.nfv.SwImage + file: ../Files/images/cirros-0.5.2-x86_64-disk.img + + VDU1_CP1: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 0 + requirements: + - virtual_binding: VDU1 + + VDU2_CP1: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 0 + requirements: + - virtual_binding: VDU2 + + policies: + - scaling_aspects: + type: tosca.policies.nfv.ScalingAspects + properties: + aspects: + VDU1_scale: + name: VDU1_scale + description: VDU1 scaling aspect + max_scale_level: 2 + step_deltas: + - delta_1 + + - VDU1_initial_delta: + type: tosca.policies.nfv.VduInitialDelta + properties: + initial_delta: + number_of_instances: 2 + targets: [ VDU1 ] + + - VDU2_initial_delta: + type: tosca.policies.nfv.VduInitialDelta + properties: + initial_delta: + number_of_instances: 1 + targets: [ VDU2 ] + + - VDU1_scaling_aspect_deltas: + type: tosca.policies.nfv.VduScalingAspectDeltas + properties: + aspect: VDU1_scale + deltas: + delta_1: + number_of_instances: 1 + targets: [ VDU1 ] + + - instantiation_levels: + type: tosca.policies.nfv.InstantiationLevels + properties: + levels: + instantiation_level_1: + description: Smallest size + scale_info: + VDU1_scale: + scale_level: 0 + instantiation_level_2: + description: Largest size + scale_info: + VDU1_scale: + scale_level: 2 + default_level: instantiation_level_1 + + - VDU1_instantiation_levels: + type: tosca.policies.nfv.VduInstantiationLevels + properties: + levels: + instantiation_level_1: + number_of_instances: 1 + instantiation_level_2: + number_of_instances: 3 + targets: [ VDU1 ] + + - VDU2_instantiation_levels: + type: tosca.policies.nfv.VduInstantiationLevels + properties: + levels: + instantiation_level_1: + number_of_instances: 1 + instantiation_level_2: + number_of_instances: 1 + targets: [ VDU2 ] diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/contents/Definitions/change_vnf_pkg_top.vnfd.yaml b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/contents/Definitions/change_vnf_pkg_top.vnfd.yaml new file mode 100644 index 000000000..27ad34734 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/contents/Definitions/change_vnf_pkg_top.vnfd.yaml @@ -0,0 +1,31 @@ +tosca_definitions_version: tosca_simple_yaml_1_2 + +description: Sample VNF + +imports: + - etsi_nfv_sol001_common_types.yaml + - etsi_nfv_sol001_vnfd_types.yaml + - change_vnf_pkg_types.yaml + - change_vnf_pkg_error_image_df_simple.yaml + +topology_template: + inputs: + selected_flavour: + type: string + description: VNF deployment flavour selected by the consumer. It is provided in the API + + node_templates: + VNF: + type: company.provider.VNF + properties: + flavour_id: { get_input: selected_flavour } + descriptor_id: b1bb0ce7-ebca-4fa7-95ed-4840d7000000 + provider: Company + product_name: Sample VNF + software_version: '1.0' + descriptor_version: '1.0' + vnfm_info: + - Tacker + requirements: + #- virtual_link_external # mapped in lower-level templates + #- virtual_link_internal # mapped in lower-level templates diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/contents/Definitions/change_vnf_pkg_types.yaml b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/contents/Definitions/change_vnf_pkg_types.yaml new file mode 100644 index 000000000..f37634d74 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/contents/Definitions/change_vnf_pkg_types.yaml @@ -0,0 +1,53 @@ +tosca_definitions_version: tosca_simple_yaml_1_2 + +description: VNF type definition + +imports: + - etsi_nfv_sol001_common_types.yaml + - etsi_nfv_sol001_vnfd_types.yaml + +node_types: + company.provider.VNF: + derived_from: tosca.nodes.nfv.VNF + properties: + descriptor_id: + type: string + constraints: [ valid_values: [ b1bb0ce7-ebca-4fa7-95ed-4840d7000000 ] ] + default: b1bb0ce7-ebca-4fa7-95ed-4840d7000000 + descriptor_version: + type: string + constraints: [ valid_values: [ '1.0' ] ] + default: '1.0' + provider: + type: string + constraints: [ valid_values: [ 'Company' ] ] + default: 'Company' + product_name: + type: string + constraints: [ valid_values: [ 'Sample VNF' ] ] + default: 'Sample VNF' + software_version: + type: string + constraints: [ valid_values: [ '1.0' ] ] + default: '1.0' + vnfm_info: + type: list + entry_schema: + type: string + constraints: [ valid_values: [ Tacker ] ] + default: [ Tacker ] + flavour_id: + type: string + constraints: [ valid_values: [ simple ] ] + default: simple + flavour_description: + type: string + default: "flavour" + requirements: + - virtual_link_external1: + capability: tosca.capabilities.nfv.VirtualLinkable + - virtual_link_external2: + capability: tosca.capabilities.nfv.VirtualLinkable + interfaces: + Vnflcm: + type: tosca.interfaces.nfv.Vnflcm \ No newline at end of file diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/contents/Scripts/coordinate_new_vnf.py b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/contents/Scripts/coordinate_new_vnf.py new file mode 100644 index 000000000..33bd11a4d --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/contents/Scripts/coordinate_new_vnf.py @@ -0,0 +1,145 @@ +# Copyright (C) 2022 Fujitsu +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import os +import pickle +import sys +import time + +from oslo_log import log as logging +import paramiko + +from tacker.sol_refactored.common import common_script_utils +from tacker.sol_refactored.common import exceptions as sol_ex +from tacker.sol_refactored.common import vnfd_utils + +LOG = logging.getLogger(__name__) +CMD_TIMEOUT = 30 +SERVER_WAIT_COMPLETE_TIME = 60 +SSH_CONNECT_RETRY_COUNT = 4 + + +class SampleNewCoordinateVNFScript(object): + + def __init__(self, req, inst, grant_req, grant, csar_dir, vdu_info): + self.req = req + self.inst = inst + self.grant_req = grant_req + self.grant = grant + self.csar_dir = csar_dir + self.vdu_info = vdu_info + + def coordinate_vnf(self): + # check ssh connect and os version + """(YiFeng) Add comment to check ssh access + + The next part of code is to check connect VM via ssh. + Since the zuul's network cannot check this content, so + we comment this part of code. If you want to check them + in your local environment, please uncomment. + # user = self.vdu_info.get('vdu_param').get( + # 'new_vnfc_param').get('username') + # password = self.vdu_info.get('vdu_param').get( + # 'new_vnfc_param').get('password') + # host = self.vdu_info.get("ssh_ip"), + # commander = self._init_commander( + # user, password, host, retry=SSH_CONNECT_RETRY_COUNT) + # ssh_command = 'cat /etc/os-release | grep PRETTY_NAME' + # result = self._execute_command(commander, host, ssh_command) + # os_version = result.get_stdout()[0].replace('\n', '').split('=') + # LOG.info('The os version of this new VM is %s', os_version) + """ + # check image and flavour updated successfully + vnfd = vnfd_utils.Vnfd(self.req.get('vnfdId')) + vnfd.init_from_csar_dir(self.csar_dir) + vdu_infos = common_script_utils.get_vdu_info( + self.grant, self.inst, vnfd) + + vdu_id = self.vdu_info.get('vdu_param').get('vdu_id') + image = vdu_infos.get(vdu_id, {}).get('image') + flavor = vdu_infos.get(vdu_id, {}).get('flavor') + if self.vdu_info.get('new_image') != image or self.vdu_info.get( + 'new_flavor') != flavor: + error = "The VM's image or flavour update failed'" + LOG.error(error) + raise sol_ex.VMRunningFailed(error) + + def _init_commander(self, user, password, host, retry): + while retry > 0: + try: + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect( + host, username=user, password=password) + LOG.info("Connected to %s", host) + return ssh + except paramiko.AuthenticationException as e: + LOG.error("Authentication failed when connecting to %s", + host) + raise sol_ex.VMRunningFailed(e) + except (paramiko.SSHException, + paramiko.ssh_exception.NoValidConnectionsError) as e: + LOG.debug(e) + retry -= 1 + if retry == 0: + LOG.error(e) + raise sol_ex.VMRunningFailed(e) + time.sleep(SERVER_WAIT_COMPLETE_TIME) + + def _execute_command(self, commander, host, command): + try: + stdin, stdout, stderr = commander.exec_command(command) + cmd_out = stdout.readlines() + cmd_err = stderr.readlines() + return_code = stdout.channel.recv_exit_status() + except paramiko.SSHException as e: + LOG.error("Command execution failed at %s. Giving up", host) + raise e + finally: + commander.close() + if return_code != 0: + error = cmd_err + raise sol_ex.VMRunningFailed(error_info=error) + result = "cmd: %s, stdout: %s, stderr: %s, return code: %s" % ( + command, cmd_out, cmd_err, return_code) + LOG.debug("Remote command execution result: %s", result) + return cmd_out + + +def main(): + operation = "coordinate_vnf" + script_dict = pickle.load(sys.stdin.buffer) + req = script_dict['request'] + inst = script_dict['vnf_instance'] + grant_req = script_dict['grant_request'] + grant = script_dict['grant_response'] + csar_dir = script_dict['tmp_csar_dir'] + vdu_info = script_dict['vdu_info'] + script = SampleNewCoordinateVNFScript( + req, inst, grant_req, grant, + csar_dir, vdu_info) + try: + getattr(script, operation)() + except Exception: + raise Exception + + +if __name__ == "__main__": + try: + main() + os._exit(0) + except Exception as ex: + sys.stderr.write(str(ex)) + sys.stderr.flush() + os._exit(1) diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/contents/TOSCA-Metadata/TOSCA.meta b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/contents/TOSCA-Metadata/TOSCA.meta new file mode 100644 index 000000000..79a1f6c6c --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/contents/TOSCA-Metadata/TOSCA.meta @@ -0,0 +1,4 @@ +TOSCA-Meta-File-Version: 1.0 +CSAR-Version: 1.1 +Created-by: Onboarding portal +Entry-Definitions: Definitions/change_vnf_pkg_top.vnfd.yaml diff --git a/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/pkggen.py b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/pkggen.py new file mode 100644 index 000000000..1ec4712ad --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_change_vnf_pkg_with_update_failed/pkggen.py @@ -0,0 +1,46 @@ +# Copyright (C) 2022 Fujitsu +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json +import os +import shutil +import tempfile + +from oslo_utils import uuidutils + +from tacker.tests.functional.sol_v2 import paramgen +from tacker.tests.functional.sol_v2 import utils + + +zip_file_name = os.path.basename(os.path.abspath(".")) + '.zip' +tmp_dir = tempfile.mkdtemp() +vnfd_id = uuidutils.generate_uuid() + +image_dir = "../../../../etc/samples/etsi/nfv/common/Files/images/" +image_file = "cirros-0.5.2-x86_64-disk.img" +image_path = os.path.abspath(image_dir + image_file) + +utils.make_zip(".", tmp_dir, vnfd_id, image_path) + +shutil.move(os.path.join(tmp_dir, zip_file_name), ".") +shutil.rmtree(tmp_dir) + +# if your sample is change VM from image to image update failed +change_vnfpkg_req_update_failed = paramgen.change_vnfpkg(vnfd_id) +del change_vnfpkg_req_update_failed['additionalParams']['vdu_params'][1] + +with open("change_vnfpkg_req_update_failed", "w", + encoding='utf-8') as f: + f.write(json.dumps(change_vnfpkg_req_update_failed, indent=2)) diff --git a/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/BaseHOT/simple/base_hot_top.yaml b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/BaseHOT/simple/base_hot_top.yaml new file mode 100644 index 000000000..dddb6cc9d --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/BaseHOT/simple/base_hot_top.yaml @@ -0,0 +1,53 @@ +heat_template_version: 2013-05-23 +description: 'Simple Base HOT for Sample VNF' + +parameters: + nfv: + type: json + +resources: + VDU1_scale: + type: OS::Heat::AutoScalingGroup + properties: + min_size: 1 + max_size: 3 + desired_capacity: { get_param: [ nfv, VDU, VDU1, desired_capacity ] } + resource: + type: base_hot_nested_VDU1.yaml + properties: + flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] } + image: { get_param: [ nfv, VDU, VDU1, vcImageId] } + net1: { get_param: [ nfv, CP, VDU1_CP1, network] } + + VDU1_scale_out: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: 1 + auto_scaling_group_id: + get_resource: VDU1_scale + adjustment_type: change_in_capacity + VDU1_scale_in: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: -1 + auto_scaling_group_id: + get_resource: VDU1_scale + adjustment_type: change_in_capacity + + VDU2: + type: OS::Nova::Server + properties: + flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] } + image: { get_param: [ nfv, VDU, VDU2, vcImageId] } + networks: + - port: + get_resource: VDU2_CP1 + + VDU2_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: [ nfv, CP, VDU2_CP1, network ] } + fixed_ips: + - ip_address: { get_param: [nfv, CP, VDU2_CP1, fixed_ips, 0, ip_address]} + +outputs: {} diff --git a/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/BaseHOT/simple/nested/base_hot_nested_VDU1.yaml b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/BaseHOT/simple/nested/base_hot_nested_VDU1.yaml new file mode 100644 index 000000000..14cd9a24d --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/BaseHOT/simple/nested/base_hot_nested_VDU1.yaml @@ -0,0 +1,27 @@ +heat_template_version: 2013-05-23 +description: 'VDU1 HOT for Sample VNF' + +parameters: + flavor: + type: string + image: + type: string + net1: + type: string + +resources: + VDU1: + type: OS::Nova::Server + properties: + flavor: { get_param: flavor } + name: VDU1 + image: { get_param: image } + networks: + - port: + get_resource: VDU1_CP1 + + VDU1_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: net1 } + diff --git a/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/BaseHOT/volume/base_hot_top.yaml b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/BaseHOT/volume/base_hot_top.yaml new file mode 100644 index 000000000..6e614de01 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/BaseHOT/volume/base_hot_top.yaml @@ -0,0 +1,65 @@ +heat_template_version: 2013-05-23 +description: 'Simple Base HOT for Sample VNF' + +parameters: + nfv: + type: json + +resources: + VDU1_scale: + type: OS::Heat::AutoScalingGroup + properties: + min_size: 1 + max_size: 3 + desired_capacity: { get_param: [ nfv, VDU, VDU1, desired_capacity ] } + resource: + type: base_hot_nested_VDU1.yaml + properties: + flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] } + image: { get_param: [ nfv, VDU, VDU1-VirtualStorage, vcImageId ] } + net1: { get_param: [ nfv, CP, VDU1_CP1, network] } + + VDU1_scale_out: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: 1 + auto_scaling_group_id: + get_resource: VDU1_scale + adjustment_type: change_in_capacity + VDU1_scale_in: + type: OS::Heat::ScalingPolicy + properties: + scaling_adjustment: -1 + auto_scaling_group_id: + get_resource: VDU1_scale + adjustment_type: change_in_capacity + + VDU2: + type: OS::Nova::Server + properties: + flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] } + block_device_mapping_v2: [{"volume_id": { get_resource: VDU2-VirtualStorage }}] + networks: + - port: + get_resource: VDU2_CP1 + + VDU2_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: [ nfv, CP, VDU2_CP1, network ] } + fixed_ips: + - ip_address: { get_param: [nfv, CP, VDU2_CP1, fixed_ips, 0, ip_address]} + + VDU2-VirtualStorage: + type: OS::Cinder::Volume + properties: + image: { get_param: [ nfv, VDU, VDU2-VirtualStorage, vcImageId ] } + size: 4 + volume_type: { get_resource: multi } + multi: + type: OS::Cinder::VolumeType + properties: + name: VDU2-multi + metadata: { multiattach: " True" } + +outputs: {} diff --git a/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/BaseHOT/volume/nested/base_hot_nested_VDU1.yaml b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/BaseHOT/volume/nested/base_hot_nested_VDU1.yaml new file mode 100644 index 000000000..d3260f974 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/BaseHOT/volume/nested/base_hot_nested_VDU1.yaml @@ -0,0 +1,39 @@ +heat_template_version: 2013-05-23 +description: 'VDU1 HOT for Sample VNF' + +parameters: + flavor: + type: string + image: + type: string + net1: + type: string + +resources: + VDU1: + type: OS::Nova::Server + properties: + flavor: { get_param: flavor } + name: VDU1 + block_device_mapping_v2: [{"volume_id": { get_resource: VDU1-VirtualStorage }}] + networks: + - port: + get_resource: VDU1_CP1 + + VDU1_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: net1 } + + VDU1-VirtualStorage: + type: OS::Cinder::Volume + properties: + image: { get_param: image } + size: 4 + volume_type: { get_resource: multi } + multi: + type: OS::Cinder::VolumeType + properties: + name: { get_resource: VDU1_CP1 } + metadata: { multiattach: " True" } + diff --git a/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/Definitions/change_vnf_pkg_old_image_df_simple.yaml b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/Definitions/change_vnf_pkg_old_image_df_simple.yaml new file mode 100644 index 000000000..0fae91f24 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/Definitions/change_vnf_pkg_old_image_df_simple.yaml @@ -0,0 +1,211 @@ +tosca_definitions_version: tosca_simple_yaml_1_2 + +description: Simple deployment flavour for Sample VNF + +imports: + - etsi_nfv_sol001_common_types.yaml + - etsi_nfv_sol001_vnfd_types.yaml + - change_vnf_pkg_types.yaml + +topology_template: + inputs: + descriptor_id: + type: string + descriptor_version: + type: string + provider: + type: string + product_name: + type: string + software_version: + type: string + vnfm_info: + type: list + entry_schema: + type: string + flavour_id: + type: string + flavour_description: + type: string + + substitution_mappings: + node_type: company.provider.VNF + properties: + flavour_id: simple + requirements: + virtual_link_external1_1: [ VDU1_CP1, virtual_link ] + virtual_link_external1_2: [ VDU2_CP1, virtual_link ] + + node_templates: + VNF: + type: company.provider.VNF + properties: + flavour_description: A simple flavour + interfaces: + Vnflcm: + instantiate: [] + instantiate_start: [] + instantiate_end: [] + terminate: [] + terminate_start: [] + terminate_end: [] + modify_information: [] + modify_information_start: [] + modify_information_end: [] + + VDU1: + type: tosca.nodes.nfv.Vdu.Compute + properties: + name: VDU1 + description: VDU1 compute node + vdu_profile: + min_number_of_instances: 1 + max_number_of_instances: 3 + sw_image_data: + name: cirros-0.5.2-x86_64-disk + version: '0.5.2' + checksum: + algorithm: sha-256 + hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464 + container_format: bare + disk_format: qcow2 + min_disk: 0 GB + min_ram: 256 MB + size: 2 GB + capabilities: + virtual_compute: + properties: + requested_additional_capabilities: + properties: + requested_additional_capability_name: m1.tiny + support_mandatory: true + target_performance_parameters: + entry_schema: test + virtual_memory: + virtual_mem_size: 512 MB + virtual_cpu: + num_virtual_cpu: 1 + virtual_local_storage: + - size_of_storage: 3 GB + + VDU2: + type: tosca.nodes.nfv.Vdu.Compute + properties: + name: VDU2 + description: VDU2 compute node + vdu_profile: + min_number_of_instances: 1 + max_number_of_instances: 1 + sw_image_data: + name: cirros-0.5.2-x86_64-disk + version: '0.5.2' + checksum: + algorithm: sha-256 + hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464 + container_format: bare + disk_format: qcow2 + min_disk: 0 GB + min_ram: 256 MB + size: 2 GB + capabilities: + virtual_compute: + properties: + requested_additional_capabilities: + properties: + requested_additional_capability_name: m1.tiny + support_mandatory: true + target_performance_parameters: + entry_schema: test + virtual_memory: + virtual_mem_size: 512 MB + virtual_cpu: + num_virtual_cpu: 1 + virtual_local_storage: + - size_of_storage: 3 GB + + VDU1_CP1: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 0 + requirements: + - virtual_binding: VDU1 + + VDU2_CP1: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 0 + requirements: + - virtual_binding: VDU2 + + policies: + - scaling_aspects: + type: tosca.policies.nfv.ScalingAspects + properties: + aspects: + VDU1_scale: + name: VDU1_scale + description: VDU1 scaling aspect + max_scale_level: 2 + step_deltas: + - delta_1 + + - VDU1_initial_delta: + type: tosca.policies.nfv.VduInitialDelta + properties: + initial_delta: + number_of_instances: 2 + targets: [ VDU1 ] + + - VDU2_initial_delta: + type: tosca.policies.nfv.VduInitialDelta + properties: + initial_delta: + number_of_instances: 1 + targets: [ VDU2 ] + + - VDU1_scaling_aspect_deltas: + type: tosca.policies.nfv.VduScalingAspectDeltas + properties: + aspect: VDU1_scale + deltas: + delta_1: + number_of_instances: 1 + targets: [ VDU1 ] + + - instantiation_levels: + type: tosca.policies.nfv.InstantiationLevels + properties: + levels: + instantiation_level_1: + description: Smallest size + scale_info: + VDU1_scale: + scale_level: 0 + instantiation_level_2: + description: Largest size + scale_info: + VDU1_scale: + scale_level: 2 + default_level: instantiation_level_1 + + - VDU1_instantiation_levels: + type: tosca.policies.nfv.VduInstantiationLevels + properties: + levels: + instantiation_level_1: + number_of_instances: 1 + instantiation_level_2: + number_of_instances: 3 + targets: [ VDU1 ] + + - VDU2_instantiation_levels: + type: tosca.policies.nfv.VduInstantiationLevels + properties: + levels: + instantiation_level_1: + number_of_instances: 1 + instantiation_level_2: + number_of_instances: 1 + targets: [ VDU2 ] diff --git a/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/Definitions/change_vnf_pkg_old_volume_df_simple.yaml b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/Definitions/change_vnf_pkg_old_volume_df_simple.yaml new file mode 100644 index 000000000..c574cdba0 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/Definitions/change_vnf_pkg_old_volume_df_simple.yaml @@ -0,0 +1,229 @@ +tosca_definitions_version: tosca_simple_yaml_1_2 + +description: Simple deployment flavour for Sample VNF + +imports: + - etsi_nfv_sol001_common_types.yaml + - etsi_nfv_sol001_vnfd_types.yaml + - change_vnf_pkg_types.yaml + +topology_template: + inputs: + descriptor_id: + type: string + descriptor_version: + type: string + provider: + type: string + product_name: + type: string + software_version: + type: string + vnfm_info: + type: list + entry_schema: + type: string + flavour_id: + type: string + flavour_description: + type: string + + substitution_mappings: + node_type: company.provider.VNF + properties: + flavour_id: volume + requirements: + virtual_link_external1_1: [ VDU1_CP1, virtual_link ] + virtual_link_external1_2: [ VDU2_CP1, virtual_link ] + + node_templates: + VNF: + type: company.provider.VNF + properties: + flavour_description: A simple flavour + interfaces: + Vnflcm: + instantiate: [] + instantiate_start: [] + instantiate_end: [] + terminate: [] + terminate_start: [] + terminate_end: [] + modify_information: [] + modify_information_start: [] + modify_information_end: [] + + VDU1: + type: tosca.nodes.nfv.Vdu.Compute + properties: + name: VDU1 + description: VDU1 compute node + vdu_profile: + min_number_of_instances: 1 + max_number_of_instances: 3 + capabilities: + virtual_compute: + properties: + requested_additional_capabilities: + properties: + requested_additional_capability_name: m1.tiny + support_mandatory: true + target_performance_parameters: + entry_schema: test + virtual_memory: + virtual_mem_size: 512 MB + virtual_cpu: + num_virtual_cpu: 1 + virtual_local_storage: + - size_of_storage: 3 GB + requirements: + - virtual_storage: VDU1-VirtualStorage + + VDU1-VirtualStorage: + type: tosca.nodes.nfv.Vdu.VirtualBlockStorage + properties: + virtual_block_storage_data: + size_of_storage: 4 GB + rdma_enabled: true + sw_image_data: + name: cirros-0.5.2-x86_64-disk + version: '0.5.2' + checksum: + algorithm: sha-256 + hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464 + container_format: bare + disk_format: qcow2 + min_disk: 0 GB + min_ram: 256 MB + size: 2 GB + + VDU2: + type: tosca.nodes.nfv.Vdu.Compute + properties: + name: VDU2 + description: VDU2 compute node + vdu_profile: + min_number_of_instances: 1 + max_number_of_instances: 1 + capabilities: + virtual_compute: + properties: + requested_additional_capabilities: + properties: + requested_additional_capability_name: m1.tiny + support_mandatory: true + target_performance_parameters: + entry_schema: test + virtual_memory: + virtual_mem_size: 512 MB + virtual_cpu: + num_virtual_cpu: 1 + virtual_local_storage: + - size_of_storage: 3 GB + requirements: + - virtual_storage: VDU2-VirtualStorage + + VDU2-VirtualStorage: + type: tosca.nodes.nfv.Vdu.VirtualBlockStorage + properties: + virtual_block_storage_data: + size_of_storage: 4 GB + rdma_enabled: true + sw_image_data: + name: cirros-0.5.2-x86_64-disk + version: '0.5.2' + checksum: + algorithm: sha-256 + hash: 932fcae93574e242dc3d772d5235061747dfe537668443a1f0567d893614b464 + container_format: bare + disk_format: qcow2 + min_disk: 0 GB + min_ram: 256 MB + size: 2 GB + + VDU1_CP1: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 0 + requirements: + - virtual_binding: VDU1 + + VDU2_CP1: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 0 + requirements: + - virtual_binding: VDU2 + + policies: + - scaling_aspects: + type: tosca.policies.nfv.ScalingAspects + properties: + aspects: + VDU1_scale: + name: VDU1_scale + description: VDU1 scaling aspect + max_scale_level: 2 + step_deltas: + - delta_1 + + - VDU1_initial_delta: + type: tosca.policies.nfv.VduInitialDelta + properties: + initial_delta: + number_of_instances: 2 + targets: [ VDU1 ] + + - VDU2_initial_delta: + type: tosca.policies.nfv.VduInitialDelta + properties: + initial_delta: + number_of_instances: 1 + targets: [ VDU2 ] + + - VDU1_scaling_aspect_deltas: + type: tosca.policies.nfv.VduScalingAspectDeltas + properties: + aspect: VDU1_scale + deltas: + delta_1: + number_of_instances: 1 + targets: [ VDU1 ] + + - instantiation_levels: + type: tosca.policies.nfv.InstantiationLevels + properties: + levels: + instantiation_level_1: + description: Smallest size + scale_info: + VDU1_scale: + scale_level: 0 + instantiation_level_2: + description: Largest size + scale_info: + VDU1_scale: + scale_level: 2 + default_level: instantiation_level_1 + + - VDU1_instantiation_levels: + type: tosca.policies.nfv.VduInstantiationLevels + properties: + levels: + instantiation_level_1: + number_of_instances: 1 + instantiation_level_2: + number_of_instances: 3 + targets: [ VDU1 ] + + - VDU2_instantiation_levels: + type: tosca.policies.nfv.VduInstantiationLevels + properties: + levels: + instantiation_level_1: + number_of_instances: 1 + instantiation_level_2: + number_of_instances: 1 + targets: [ VDU2 ] diff --git a/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/Definitions/change_vnf_pkg_top.vnfd.yaml b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/Definitions/change_vnf_pkg_top.vnfd.yaml new file mode 100644 index 000000000..11be0200d --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/Definitions/change_vnf_pkg_top.vnfd.yaml @@ -0,0 +1,32 @@ +tosca_definitions_version: tosca_simple_yaml_1_2 + +description: Sample VNF + +imports: + - etsi_nfv_sol001_common_types.yaml + - etsi_nfv_sol001_vnfd_types.yaml + - change_vnf_pkg_types.yaml + - change_vnf_pkg_old_image_df_simple.yaml + - change_vnf_pkg_old_volume_df_simple.yaml + +topology_template: + inputs: + selected_flavour: + type: string + description: VNF deployment flavour selected by the consumer. It is provided in the API + + node_templates: + VNF: + type: company.provider.VNF + properties: + flavour_id: { get_input: selected_flavour } + descriptor_id: b1bb0ce7-ebca-4fa7-95ed-4840d7000000 + provider: Company + product_name: Sample VNF + software_version: '1.0' + descriptor_version: '1.0' + vnfm_info: + - Tacker + requirements: + #- virtual_link_external # mapped in lower-level templates + #- virtual_link_internal # mapped in lower-level templates diff --git a/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/Definitions/change_vnf_pkg_types.yaml b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/Definitions/change_vnf_pkg_types.yaml new file mode 100644 index 000000000..2934b6c7d --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/Definitions/change_vnf_pkg_types.yaml @@ -0,0 +1,53 @@ +tosca_definitions_version: tosca_simple_yaml_1_2 + +description: VNF type definition + +imports: + - etsi_nfv_sol001_common_types.yaml + - etsi_nfv_sol001_vnfd_types.yaml + +node_types: + company.provider.VNF: + derived_from: tosca.nodes.nfv.VNF + properties: + descriptor_id: + type: string + constraints: [ valid_values: [ b1bb0ce7-ebca-4fa7-95ed-4840d7000000 ] ] + default: b1bb0ce7-ebca-4fa7-95ed-4840d7000000 + descriptor_version: + type: string + constraints: [ valid_values: [ '1.0' ] ] + default: '1.0' + provider: + type: string + constraints: [ valid_values: [ 'Company' ] ] + default: 'Company' + product_name: + type: string + constraints: [ valid_values: [ 'Sample VNF' ] ] + default: 'Sample VNF' + software_version: + type: string + constraints: [ valid_values: [ '1.0' ] ] + default: '1.0' + vnfm_info: + type: list + entry_schema: + type: string + constraints: [ valid_values: [ Tacker ] ] + default: [ Tacker ] + flavour_id: + type: string + constraints: [ valid_values: [ simple, volume ] ] + default: simple + flavour_description: + type: string + default: "flavour" + requirements: + - virtual_link_external1: + capability: tosca.capabilities.nfv.VirtualLinkable + - virtual_link_external2: + capability: tosca.capabilities.nfv.VirtualLinkable + interfaces: + Vnflcm: + type: tosca.interfaces.nfv.Vnflcm \ No newline at end of file diff --git a/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/Scripts/coordinate_old_vnf.py b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/Scripts/coordinate_old_vnf.py new file mode 100644 index 000000000..c601d775d --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/Scripts/coordinate_old_vnf.py @@ -0,0 +1,145 @@ +# Copyright (C) 2022 Fujitsu +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import os +import pickle +import sys +import time + +from oslo_log import log as logging +import paramiko + +from tacker.sol_refactored.common import common_script_utils +from tacker.sol_refactored.common import exceptions as sol_ex +from tacker.sol_refactored.common import vnfd_utils + +LOG = logging.getLogger(__name__) +CMD_TIMEOUT = 30 +SERVER_WAIT_COMPLETE_TIME = 60 +SSH_CONNECT_RETRY_COUNT = 4 + + +class SampleOldCoordinateVNFScript(object): + + def __init__(self, req, inst, grant_req, grant, csar_dir, vdu_info): + self.req = req + self.inst = inst + self.grant_req = grant_req + self.grant = grant + self.csar_dir = csar_dir + self.vdu_info = vdu_info + + def coordinate_vnf(self): + # check ssh connect and os version + """(YiFeng) Add comment to check ssh access + + The next part of code is to check connect VM via ssh. + Since the zuul's network cannot check this content, so + we comment this part of code. If you want to check them + in your local environment, please uncomment. + # user = self.vdu_info.get('vdu_param').get( + # 'old_vnfc_param').get('username') + # password = self.vdu_info.get('vdu_param').get( + # 'old_vnfc_param').get('password') + # host = self.vdu_info.get("ssh_ip"), + # commander = self._init_commander( + # user, password, host, retry=SSH_CONNECT_RETRY_COUNT) + # ssh_command = 'cat /etc/os-release | grep PRETTY_NAME' + # result = self._execute_command(commander, host, ssh_command) + # os_version = result.get_stdout()[0].replace('\n', '').split('=') + # LOG.info('The os version of this new VM is %s', os_version) + """ + # check image and flavour updated successfully + vnfd = vnfd_utils.Vnfd(self.req.get('vnfdId')) + vnfd.init_from_csar_dir(self.csar_dir) + vdu_infos = common_script_utils.get_vdu_info( + self.grant, self.inst, vnfd) + + vdu_id = self.vdu_info.get('vdu_param').get('vdu_id') + image = vdu_infos.get(vdu_id, {}).get('image') + flavor = vdu_infos.get(vdu_id, {}).get('flavor') + if self.vdu_info.get('new_image') != image or self.vdu_info.get( + 'new_flavor') != flavor: + error = "The VM's image or flavour update failed'" + LOG.error(error) + raise sol_ex.VMRunningFailed(error) + + def _init_commander(self, user, password, host, retry): + while retry > 0: + try: + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect( + host, username=user, password=password) + LOG.info("Connected to %s", host) + return ssh + except paramiko.AuthenticationException as e: + LOG.error("Authentication failed when connecting to %s", + host) + raise sol_ex.VMRunningFailed(e) + except (paramiko.SSHException, + paramiko.ssh_exception.NoValidConnectionsError) as e: + LOG.debug(e) + retry -= 1 + if retry == 0: + LOG.error(e) + raise sol_ex.VMRunningFailed(e) + time.sleep(SERVER_WAIT_COMPLETE_TIME) + + def _execute_command(self, commander, host, command): + try: + stdin, stdout, stderr = commander.exec_command(command) + cmd_out = stdout.readlines() + cmd_err = stderr.readlines() + return_code = stdout.channel.recv_exit_status() + except paramiko.SSHException as e: + LOG.error("Command execution failed at %s. Giving up", host) + raise e + finally: + commander.close() + if return_code != 0: + error = cmd_err + raise sol_ex.VMRunningFailed(error_info=error) + result = "cmd: %s, stdout: %s, stderr: %s, return code: %s" % ( + command, cmd_out, cmd_err, return_code) + LOG.debug("Remote command execution result: %s", result) + return cmd_out + + +def main(): + operation = "coordinate_vnf" + script_dict = pickle.load(sys.stdin.buffer) + req = script_dict['request'] + inst = script_dict['vnf_instance'] + grant_req = script_dict['grant_request'] + grant = script_dict['grant_response'] + csar_dir = script_dict['tmp_csar_dir'] + vdu_info = script_dict['vdu_info'] + script = SampleOldCoordinateVNFScript( + req, inst, grant_req, grant, + csar_dir, vdu_info) + try: + getattr(script, operation)() + except Exception: + raise Exception + + +if __name__ == "__main__": + try: + main() + os._exit(0) + except Exception as ex: + sys.stderr.write(str(ex)) + sys.stderr.flush() + os._exit(1) diff --git a/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/TOSCA-Metadata/TOSCA.meta b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/TOSCA-Metadata/TOSCA.meta new file mode 100644 index 000000000..79a1f6c6c --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/contents/TOSCA-Metadata/TOSCA.meta @@ -0,0 +1,4 @@ +TOSCA-Meta-File-Version: 1.0 +CSAR-Version: 1.1 +Created-by: Onboarding portal +Entry-Definitions: Definitions/change_vnf_pkg_top.vnfd.yaml diff --git a/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/pkggen.py b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/pkggen.py new file mode 100644 index 000000000..be174f551 --- /dev/null +++ b/tacker/tests/functional/sol_v2/samples/test_instantiate_vnf_with_old_image_or_volume/pkggen.py @@ -0,0 +1,85 @@ +# Copyright (C) 2022 Fujitsu +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json +import os +import shutil +import tempfile + +from oslo_utils import uuidutils + +from tacker.tests.functional.sol_v2 import paramgen +from tacker.tests.functional.sol_v2 import utils + + +zip_file_name = os.path.basename(os.path.abspath(".")) + '.zip' +tmp_dir = tempfile.mkdtemp() +vnfd_id = uuidutils.generate_uuid() + +image_dir = "../../../../etc/samples/etsi/nfv/common/Files/images/" +image_file = "cirros-0.5.2-x86_64-disk.img" +image_path = os.path.abspath(image_dir + image_file) + +utils.make_zip(".", tmp_dir, vnfd_id, image_path) + +shutil.move(os.path.join(tmp_dir, zip_file_name), ".") +shutil.rmtree(tmp_dir) + +create_req = paramgen.change_vnfpkg_create(vnfd_id) +terminate_req = paramgen.terminate_vnf_min() + +net_ids = utils.get_network_ids(['net0']) +subnet_ids = utils.get_subnet_ids(['subnet0']) + +# if your sample is change VM from image to image +instantiate_req_from_image_to_image = paramgen.change_vnfpkg_instantiate( + net_ids, subnet_ids, "http://localhost/identity/v3") + +# if your sample is change VM from volume to image +instantiate_req_from_volume_to_image = paramgen.change_vnfpkg_instantiate( + net_ids, subnet_ids, "http://localhost/identity/v3", flavor_id='volume') + +# if your sample is change VM from image to volume +instantiate_req_from_image_to_volume = paramgen.change_vnfpkg_instantiate( + net_ids, subnet_ids, "http://localhost/identity/v3") + +# if your sample is change VM from volume to volume +instantiate_req_from_volume_to_volume = paramgen.change_vnfpkg_instantiate( + net_ids, subnet_ids, "http://localhost/identity/v3", flavor_id='volume') + +# if your sample is change VM from image to image update failed +instantiate_req_update_failed = paramgen.change_vnfpkg_instantiate( + net_ids, subnet_ids, "http://localhost/identity/v3") + +with open("create_req", "w") as f: + f.write(json.dumps(create_req, indent=2)) + +with open("terminate_req", "w") as f: + f.write(json.dumps(terminate_req, indent=2)) + +with open("instantiate_req_from_image_to_image", "w") as f: + f.write(json.dumps(instantiate_req_from_image_to_image, indent=2)) + +with open("instantiate_req_from_volume_to_image", "w") as f: + f.write(json.dumps(instantiate_req_from_volume_to_image, indent=2)) + +with open("instantiate_req_from_image_to_volume", "w") as f: + f.write(json.dumps(instantiate_req_from_image_to_volume, indent=2)) + +with open("instantiate_req_from_volume_to_volume", "w") as f: + f.write(json.dumps(instantiate_req_from_volume_to_volume, indent=2)) + +with open("instantiate_req_update_failed", "w") as f: + f.write(json.dumps(instantiate_req_update_failed, indent=2)) diff --git a/tacker/tests/functional/sol_v2/test_change_vnfpkg.py b/tacker/tests/functional/sol_v2/test_change_vnfpkg.py new file mode 100644 index 000000000..aba7772bc --- /dev/null +++ b/tacker/tests/functional/sol_v2/test_change_vnfpkg.py @@ -0,0 +1,644 @@ +# Copyright (C) 2022 Fujitsu +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import ddt +import os +import time + +from tacker.tests.functional.sol_v2 import base_v2 +from tacker.tests.functional.sol_v2 import paramgen + + +@ddt.ddt +class ChangeVnfPkgVnfLcmTest(base_v2.BaseSolV2Test): + + @classmethod + def setUpClass(cls): + super(ChangeVnfPkgVnfLcmTest, cls).setUpClass() + + cur_dir = os.path.dirname(__file__) + # tacker/tests/etc... + # /functional/sol_v2 + image_dir = os.path.join( + cur_dir, "../../etc/samples/etsi/nfv/common/Files/images") + image_file = "cirros-0.5.2-x86_64-disk.img" + image_path = os.path.abspath(os.path.join(image_dir, image_file)) + + change_vnfpkg_from_image_to_image_path = os.path.join( + cur_dir, "samples/test_instantiate_vnf_with_old_image_or_volume") + cls.vnf_pkg_1, cls.vnfd_id_1 = cls.create_vnf_package( + change_vnfpkg_from_image_to_image_path) + + change_vnfpkg_from_image_to_image_path_2 = os.path.join( + cur_dir, "samples/test_change_vnf_pkg_with_new_image") + cls.vnf_pkg_2, cls.vnfd_id_2 = cls.create_vnf_package( + change_vnfpkg_from_image_to_image_path_2, image_path=image_path) + + change_vnfpkg_from_image_to_volume_path = os.path.join( + cur_dir, "samples/test_change_vnf_pkg_with_new_volume") + cls.vnf_pkg_3, cls.vnfd_id_3 = cls.create_vnf_package( + change_vnfpkg_from_image_to_volume_path, image_path=image_path) + + change_vnfpkg_failed_in_update_path = os.path.join( + cur_dir, "samples/test_change_vnf_pkg_with_update_failed") + cls.vnf_pkg_4, cls.vnfd_id_4 = cls.create_vnf_package( + change_vnfpkg_failed_in_update_path, image_path=image_path) + + @classmethod + def tearDownClass(cls): + super(ChangeVnfPkgVnfLcmTest, cls).tearDownClass() + + cls.delete_vnf_package(cls.vnf_pkg_1) + cls.delete_vnf_package(cls.vnf_pkg_2) + cls.delete_vnf_package(cls.vnf_pkg_3) + cls.delete_vnf_package(cls.vnf_pkg_4) + + def setUp(self): + super(ChangeVnfPkgVnfLcmTest, self).setUp() + + def test_change_vnfpkg_from_image_to_image(self): + create_req = paramgen.change_vnfpkg_create(self.vnfd_id_1) + resp, body = self.create_vnf_instance(create_req) + expected_inst_attrs = [ + 'id', + 'vnfInstanceName', + 'vnfInstanceDescription', + 'vnfdId', + 'vnfProvider', + 'vnfProductName', + 'vnfSoftwareVersion', + 'vnfdVersion', + # 'vnfConfigurableProperties', # omitted + # 'vimConnectionInfo', # omitted + 'instantiationState', + # 'instantiatedVnfInfo', # omitted + 'metadata', + # 'extensions', # omitted + '_links' + ] + self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + self.check_resp_body(body, expected_inst_attrs) + inst_id = body['id'] + + net_ids = self.get_network_ids(['net0', 'net1', 'net_mgmt']) + subnet_ids = self.get_subnet_ids(['subnet0', 'subnet1']) + instantiate_req = paramgen.change_vnfpkg_instantiate( + net_ids, subnet_ids, self.auth_url) + resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + additional_inst_attrs = [ + 'vimConnectionInfo', + 'instantiatedVnfInfo' + ] + expected_inst_attrs.extend(additional_inst_attrs) + resp_1, body_1 = self.show_vnf_instance(inst_id) + stack_name = "vnf-{}".format(inst_id) + stack_id = self.heat_client.get_stack_resource(stack_name)['stack'][ + 'id'] + image_id_1 = self.get_current_vdu_image(stack_id, stack_name, 'VDU2') + old_vnfd_id = body_1['vnfdId'] + + self.assertEqual(200, resp_1.status_code) + self.check_resp_headers_in_get(resp_1) + self.check_resp_body(body_1, expected_inst_attrs) + + change_vnfpkg_req = paramgen.change_vnfpkg(self.vnfd_id_2) + resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + resp_2, body_2 = self.show_vnf_instance(inst_id) + image_id_2 = self.get_current_vdu_image(stack_id, stack_name, 'VDU2') + self.assertNotEqual(image_id_1, image_id_2) + + self.assertEqual(200, resp_2.status_code) + self.check_resp_headers_in_get(resp_2) + self.check_resp_body(body_2, expected_inst_attrs) + new_vnfd_id = [obj['metadata']['current_vnfd_id'] for obj in body_2[ + 'instantiatedVnfInfo']['vnfcResourceInfo']][0] + + self.assertNotEqual(old_vnfd_id, new_vnfd_id) + + terminate_req = paramgen.terminate_vnf_min() + resp, body = self.terminate_vnf_instance(inst_id, terminate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # wait a bit because there is a bit time lag between lcmocc DB + # update and terminate completion. + time.sleep(10) + + resp, body = self.delete_vnf_instance(inst_id) + self.assertEqual(204, resp.status_code) + self.check_resp_headers_in_delete(resp) + + def test_change_vnfpkg_from_image_to_volume(self): + create_req = paramgen.change_vnfpkg_create(self.vnfd_id_1) + resp, body = self.create_vnf_instance(create_req) + expected_inst_attrs = [ + 'id', + 'vnfInstanceName', + 'vnfInstanceDescription', + 'vnfdId', + 'vnfProvider', + 'vnfProductName', + 'vnfSoftwareVersion', + 'vnfdVersion', + # 'vnfConfigurableProperties', # omitted + # 'vimConnectionInfo', # omitted + 'instantiationState', + # 'instantiatedVnfInfo', # omitted + 'metadata', + # 'extensions', # omitted + '_links' + ] + self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + self.check_resp_body(body, expected_inst_attrs) + inst_id = body['id'] + + net_ids = self.get_network_ids(['net0', 'net1', 'net_mgmt']) + subnet_ids = self.get_subnet_ids(['subnet0', 'subnet1']) + instantiate_req = paramgen.change_vnfpkg_instantiate( + net_ids, subnet_ids, self.auth_url) + resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + additional_inst_attrs = [ + 'vimConnectionInfo', + 'instantiatedVnfInfo' + ] + expected_inst_attrs.extend(additional_inst_attrs) + resp_1, body_1 = self.show_vnf_instance(inst_id) + stack_name = "vnf-{}".format(inst_id) + stack_id = self.heat_client.get_stack_resource(stack_name)['stack'][ + 'id'] + image_id_1 = self.get_current_vdu_image(stack_id, stack_name, 'VDU2') + + self.assertEqual(200, resp_1.status_code) + self.check_resp_headers_in_get(resp_1) + self.check_resp_body(body_1, expected_inst_attrs) + resource_ids_1 = [obj['id'] for obj in body_1[ + 'instantiatedVnfInfo']['vnfcResourceInfo'] if obj[ + 'vduId'] == 'VDU2'][0] + + change_vnfpkg_req = paramgen.change_vnfpkg(self.vnfd_id_3) + del change_vnfpkg_req['additionalParams']['vdu_params'][0] + resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + resp_2, body_2 = self.show_vnf_instance(inst_id) + image_id_2 = self.get_current_vdu_image(stack_id, stack_name, 'VDU2') + storageResourceIds = [obj.get('storageResourceIds') for obj in body_2[ + 'instantiatedVnfInfo']['vnfcResourceInfo'] if obj[ + 'vduId'] == 'VDU2'] + self.assertIsNotNone(storageResourceIds) + resource_ids_2 = [obj['id'] for obj in body_2[ + 'instantiatedVnfInfo']['vnfcResourceInfo'] if obj[ + 'vduId'] == 'VDU2'][0] + self.assertNotEqual(resource_ids_1, resource_ids_2) + self.assertNotEqual(image_id_1, image_id_2) + + self.assertEqual(200, resp_2.status_code) + self.check_resp_headers_in_get(resp_2) + self.check_resp_body(body_2, expected_inst_attrs) + + terminate_req = paramgen.terminate_vnf_min() + resp, body = self.terminate_vnf_instance(inst_id, terminate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # wait a bit because there is a bit time lag between lcmocc DB + # update and terminate completion. + time.sleep(10) + + resp, body = self.delete_vnf_instance(inst_id) + self.assertEqual(204, resp.status_code) + self.check_resp_headers_in_delete(resp) + + def test_change_vnfpkg_from_volume_to_image(self): + create_req = paramgen.change_vnfpkg_create(self.vnfd_id_1) + resp, body = self.create_vnf_instance(create_req) + expected_inst_attrs = [ + 'id', + 'vnfInstanceName', + 'vnfInstanceDescription', + 'vnfdId', + 'vnfProvider', + 'vnfProductName', + 'vnfSoftwareVersion', + 'vnfdVersion', + # 'vnfConfigurableProperties', # omitted + # 'vimConnectionInfo', # omitted + 'instantiationState', + # 'instantiatedVnfInfo', # omitted + 'metadata', + # 'extensions', # omitted + '_links' + ] + self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + self.check_resp_body(body, expected_inst_attrs) + inst_id = body['id'] + + net_ids = self.get_network_ids(['net0', 'net1', 'net_mgmt']) + subnet_ids = self.get_subnet_ids(['subnet0', 'subnet1']) + instantiate_req = paramgen.change_vnfpkg_instantiate( + net_ids, subnet_ids, self.auth_url, flavor_id='volume') + resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + additional_inst_attrs = [ + 'vimConnectionInfo', + 'instantiatedVnfInfo' + ] + expected_inst_attrs.extend(additional_inst_attrs) + resp_1, body_1 = self.show_vnf_instance(inst_id) + stack_name = "vnf-{}".format(inst_id) + stack_id = self.heat_client.get_stack_resource(stack_name)['stack'][ + 'id'] + image_id_1 = self.get_current_vdu_image(stack_id, stack_name, 'VDU2') + storageResourceIds_1 = [ + obj.get('storageResourceIds') for obj in body_1[ + 'instantiatedVnfInfo']['vnfcResourceInfo'] + if obj['vduId'] == 'VDU2'] + resource_ids_1 = [obj['id'] for obj in body_1[ + 'instantiatedVnfInfo']['vnfcResourceInfo'] if obj[ + 'vduId'] == 'VDU2'][0] + self.assertIsNotNone(storageResourceIds_1) + + self.assertEqual(200, resp_1.status_code) + self.check_resp_headers_in_get(resp_1) + self.check_resp_body(body_1, expected_inst_attrs) + + change_vnfpkg_req = paramgen.change_vnfpkg(self.vnfd_id_2) + del change_vnfpkg_req['additionalParams']['vdu_params'][0] + resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + resp_2, body_2 = self.show_vnf_instance(inst_id) + image_id_2 = self.get_current_vdu_image(stack_id, stack_name, 'VDU2') + storageResourceIds_2 = [ + obj.get('storageResourceIds') for obj in body_2[ + 'instantiatedVnfInfo']['vnfcResourceInfo'] + if obj['vduId'] == 'VDU2'] + resource_ids_2 = [obj['id'] for obj in body_2[ + 'instantiatedVnfInfo']['vnfcResourceInfo'] if obj[ + 'vduId'] == 'VDU2'][0] + self.assertIsNone(storageResourceIds_2[0]) + self.assertNotEqual(image_id_1, image_id_2) + self.assertNotEqual(resource_ids_1, resource_ids_2) + + self.assertEqual(200, resp_2.status_code) + self.check_resp_headers_in_get(resp_2) + self.check_resp_body(body_2, expected_inst_attrs) + + terminate_req = paramgen.terminate_vnf_min() + resp, body = self.terminate_vnf_instance(inst_id, terminate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # wait a bit because there is a bit time lag between lcmocc DB + # update and terminate completion. + time.sleep(10) + + resp, body = self.delete_vnf_instance(inst_id) + self.assertEqual(204, resp.status_code) + self.check_resp_headers_in_delete(resp) + + def test_change_vnfpkg_from_volume_to_volume(self): + create_req = paramgen.change_vnfpkg_create(self.vnfd_id_1) + resp, body = self.create_vnf_instance(create_req) + expected_inst_attrs = [ + 'id', + 'vnfInstanceName', + 'vnfInstanceDescription', + 'vnfdId', + 'vnfProvider', + 'vnfProductName', + 'vnfSoftwareVersion', + 'vnfdVersion', + # 'vnfConfigurableProperties', # omitted + # 'vimConnectionInfo', # omitted + 'instantiationState', + # 'instantiatedVnfInfo', # omitted + 'metadata', + # 'extensions', # omitted + '_links' + ] + self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + self.check_resp_body(body, expected_inst_attrs) + inst_id = body['id'] + + net_ids = self.get_network_ids(['net0', 'net1', 'net_mgmt']) + subnet_ids = self.get_subnet_ids(['subnet0', 'subnet1']) + instantiate_req = paramgen.change_vnfpkg_instantiate( + net_ids, subnet_ids, self.auth_url, flavor_id='volume') + resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + additional_inst_attrs = [ + 'vimConnectionInfo', + 'instantiatedVnfInfo' + ] + expected_inst_attrs.extend(additional_inst_attrs) + resp_1, body_1 = self.show_vnf_instance(inst_id) + stack_name = "vnf-{}".format(inst_id) + stack_id = self.heat_client.get_stack_resource(stack_name)['stack'][ + 'id'] + image_id_1 = self.get_current_vdu_image(stack_id, stack_name, 'VDU2') + storageResourceId_1 = [ + obj.get('storageResourceIds') for obj in body_1[ + 'instantiatedVnfInfo']['vnfcResourceInfo'] + if obj['vduId'] == 'VDU2'] + resource_ids_1 = [obj['id'] for obj in body_1[ + 'instantiatedVnfInfo']['vnfcResourceInfo'] if obj[ + 'vduId'] == 'VDU2'][0] + + self.assertEqual(200, resp_1.status_code) + self.check_resp_headers_in_get(resp_1) + self.check_resp_body(body_1, expected_inst_attrs) + + change_vnfpkg_req = paramgen.change_vnfpkg(self.vnfd_id_3) + resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + resp_2, body_2 = self.show_vnf_instance(inst_id) + image_id_2 = self.get_current_vdu_image(stack_id, stack_name, 'VDU2') + storageResourceId_2 = [ + obj.get('storageResourceIds') for obj in body_2[ + 'instantiatedVnfInfo']['vnfcResourceInfo'] + if obj['vduId'] == 'VDU2'] + resource_ids_2 = [obj['id'] for obj in body_2[ + 'instantiatedVnfInfo']['vnfcResourceInfo'] if obj[ + 'vduId'] == 'VDU2'][0] + self.assertNotEqual(image_id_1, image_id_2) + self.assertNotEqual(storageResourceId_1, storageResourceId_2) + self.assertNotEqual(resource_ids_1, resource_ids_2) + + self.assertEqual(200, resp_2.status_code) + self.check_resp_headers_in_get(resp_2) + self.check_resp_body(body_2, expected_inst_attrs) + + terminate_req = paramgen.terminate_vnf_min() + resp, body = self.terminate_vnf_instance(inst_id, terminate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # wait a bit because there is a bit time lag between lcmocc DB + # update and terminate completion. + time.sleep(10) + + resp, body = self.delete_vnf_instance(inst_id) + self.assertEqual(204, resp.status_code) + self.check_resp_headers_in_delete(resp) + + def test_change_vnfpkg_failed_in_update(self): + create_req = paramgen.change_vnfpkg_create(self.vnfd_id_1) + resp, body = self.create_vnf_instance(create_req) + expected_inst_attrs = [ + 'id', + 'vnfInstanceName', + 'vnfInstanceDescription', + 'vnfdId', + 'vnfProvider', + 'vnfProductName', + 'vnfSoftwareVersion', + 'vnfdVersion', + # 'vnfConfigurableProperties', # omitted + # 'vimConnectionInfo', # omitted + 'instantiationState', + # 'instantiatedVnfInfo', # omitted + 'metadata', + # 'extensions', # omitted + '_links' + ] + self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + self.check_resp_body(body, expected_inst_attrs) + inst_id = body['id'] + + net_ids = self.get_network_ids(['net0', 'net1', 'net_mgmt']) + subnet_ids = self.get_subnet_ids(['subnet0', 'subnet1']) + instantiate_req = paramgen.change_vnfpkg_instantiate( + net_ids, subnet_ids, self.auth_url) + resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + additional_inst_attrs = [ + 'vimConnectionInfo', + 'instantiatedVnfInfo' + ] + expected_inst_attrs.extend(additional_inst_attrs) + resp_1, body_1 = self.show_vnf_instance(inst_id) + old_vnfd_id = body_1['vnfdId'] + + self.assertEqual(200, resp_1.status_code) + self.check_resp_headers_in_get(resp_1) + self.check_resp_body(body_1, expected_inst_attrs) + + change_vnfpkg_req = paramgen.change_vnfpkg(self.vnfd_id_4) + del change_vnfpkg_req['additionalParams']['vdu_params'][1] + resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_failed_temp(lcmocc_id) + + resp, body = self.rollback(lcmocc_id) + self.assertEqual(202, resp.status_code) + self.wait_lcmocc_rolled_back(lcmocc_id) + + resp_2, body_2 = self.show_vnf_instance(inst_id) + new_vnfd_id = [ + obj['metadata'].get('current_vnfd_id') for obj in body_2[ + 'instantiatedVnfInfo']['vnfcResourceInfo'] + if obj['metadata'].get('current_vnfd_id')][0] + self.assertEqual(old_vnfd_id, new_vnfd_id) + + self.assertEqual(200, resp_2.status_code) + self.check_resp_headers_in_get(resp_2) + self.check_resp_body(body_2, expected_inst_attrs) + + terminate_req = paramgen.terminate_vnf_min() + resp, body = self.terminate_vnf_instance(inst_id, terminate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # wait a bit because there is a bit time lag between lcmocc DB + # update and terminate completion. + time.sleep(10) + + resp, body = self.delete_vnf_instance(inst_id) + self.assertEqual(204, resp.status_code) + self.check_resp_headers_in_delete(resp) + + def test_change_vnfpkg_failed_with_error_coordinate_vnf(self): + create_req = paramgen.change_vnfpkg_create(self.vnfd_id_1) + resp, body = self.create_vnf_instance(create_req) + expected_inst_attrs = [ + 'id', + 'vnfInstanceName', + 'vnfInstanceDescription', + 'vnfdId', + 'vnfProvider', + 'vnfProductName', + 'vnfSoftwareVersion', + 'vnfdVersion', + # 'vnfConfigurableProperties', # omitted + # 'vimConnectionInfo', # omitted + 'instantiationState', + # 'instantiatedVnfInfo', # omitted + 'metadata', + # 'extensions', # omitted + '_links' + ] + self.assertEqual(201, resp.status_code) + self.check_resp_headers_in_create(resp) + self.check_resp_body(body, expected_inst_attrs) + inst_id = body['id'] + + net_ids = self.get_network_ids(['net0', 'net1', 'net_mgmt']) + subnet_ids = self.get_subnet_ids(['subnet0', 'subnet1']) + instantiate_req = paramgen.change_vnfpkg_instantiate( + net_ids, subnet_ids, self.auth_url, flavor_id='volume') + resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + additional_inst_attrs = [ + 'vimConnectionInfo', + 'instantiatedVnfInfo' + ] + expected_inst_attrs.extend(additional_inst_attrs) + resp_1, body_1 = self.show_vnf_instance(inst_id) + storageResourceId_1 = [ + obj.get('storageResourceIds') for obj in body_1[ + 'instantiatedVnfInfo']['vnfcResourceInfo'] + if obj['vduId'] == 'VDU2'] + resource_ids_1 = [obj['id'] for obj in body_1[ + 'instantiatedVnfInfo']['vnfcResourceInfo'] if obj[ + 'vduId'] == 'VDU2'][0] + + self.assertEqual(200, resp_1.status_code) + self.check_resp_headers_in_get(resp_1) + self.check_resp_body(body_1, expected_inst_attrs) + + change_vnfpkg_req = paramgen.change_vnfpkg(self.vnfd_id_3) + change_vnfpkg_req['additionalParams'][ + 'lcm-operation-coordinate-new-vnf' + ] = "./Scripts/error_coordinate_new_vnf.py" + change_vnfpkg_req['additionalParams'][ + 'lcm-operation-coordinate-new-vnf-class' + ] = "ErrorCoordinateNewVnf" + del change_vnfpkg_req['additionalParams']['vdu_params'][0] + resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_failed_temp(lcmocc_id) + + resp, body = self.rollback(lcmocc_id) + self.assertEqual(202, resp.status_code) + self.wait_lcmocc_rolled_back(lcmocc_id) + + resp_2, body_2 = self.show_vnf_instance(inst_id) + storageResourceId_2 = [ + obj.get('storageResourceIds') for obj in body_2[ + 'instantiatedVnfInfo']['vnfcResourceInfo'] + if obj['vduId'] == 'VDU2'] + resource_ids_2 = [obj['id'] for obj in body_2[ + 'instantiatedVnfInfo']['vnfcResourceInfo'] if obj[ + 'vduId'] == 'VDU2'][0] + + self.assertEqual(200, resp_2.status_code) + self.check_resp_headers_in_get(resp_2) + self.check_resp_body(body_2, expected_inst_attrs) + self.assertNotEqual(storageResourceId_1, storageResourceId_2) + self.assertNotEqual(resource_ids_1, resource_ids_2) + + terminate_req = paramgen.terminate_vnf_min() + resp, body = self.terminate_vnf_instance(inst_id, terminate_req) + self.assertEqual(202, resp.status_code) + self.check_resp_headers_in_operation_task(resp) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # wait a bit because there is a bit time lag between lcmocc DB + # update and terminate completion. + time.sleep(10) + + resp, body = self.delete_vnf_instance(inst_id) + self.assertEqual(204, resp.status_code) + self.check_resp_headers_in_delete(resp) diff --git a/tacker/tests/functional/sol_v2/test_vnflcm_basic.py b/tacker/tests/functional/sol_v2/test_vnflcm_basic.py index 81cdb5b88..ce38d1e55 100644 --- a/tacker/tests/functional/sol_v2/test_vnflcm_basic.py +++ b/tacker/tests/functional/sol_v2/test_vnflcm_basic.py @@ -762,15 +762,15 @@ class VnfLcmTest(base_v2.BaseSolV2Test): stack_name_1 = href.split("/")[7] break - _, port_info = self.heat_client.get_resource_info( - stack_name_1, stack_id_1, 'VDU1_CP1') + port_info = self.heat_client.get_resource_info( + f"{stack_name_1}/{stack_id_1}", 'VDU1_CP1') before_physical_resource_id_1 = port_info['physical_resource_id'] before_fixed_ips_1 = port_info['attributes']['fixed_ips'] stack_id_2 = self.heat_client.get_stack_resource(stack_name)['stack'][ 'id'] - _, port_info = self.heat_client.get_resource_info( - stack_name, stack_id_2, 'VDU2_CP2') + port_info = self.heat_client.get_resource_info( + f"{stack_name}/{stack_id_2}", 'VDU2_CP2') before_physical_resource_id_2 = port_info['physical_resource_id'] before_fixed_ips_2 = port_info['attributes']['fixed_ips'] @@ -782,15 +782,15 @@ class VnfLcmTest(base_v2.BaseSolV2Test): lcmocc_id = os.path.basename(resp.headers['Location']) self.wait_lcmocc_complete(lcmocc_id) - _, port_info = self.heat_client.get_resource_info( - stack_name_1, stack_id_1, 'VDU1_CP1') + port_info = self.heat_client.get_resource_info( + f"{stack_name_1}/{stack_id_1}", 'VDU1_CP1') after_physical_resource_id_1 = port_info['physical_resource_id'] after_fixed_ips_1 = port_info['attributes']['fixed_ips'] stack_id_2 = self.heat_client.get_stack_resource(stack_name)['stack'][ 'id'] - _, port_info = self.heat_client.get_resource_info( - stack_name, stack_id_2, 'VDU2_CP2') + port_info = self.heat_client.get_resource_info( + f"{stack_name}/{stack_id_2}", 'VDU2_CP2') after_physical_resource_id_2 = port_info['physical_resource_id'] after_fixed_ips_2 = port_info['attributes']['fixed_ips'] @@ -2859,8 +2859,8 @@ class VnfLcmTest(base_v2.BaseSolV2Test): # 14. Change external connectivity stack_id = self.heat_client.get_stack_resource(stack_name)['stack'][ 'id'] - _, port_info = self.heat_client.get_resource_info( - stack_name, stack_id, 'VDU2_CP2') + port_info = self.heat_client.get_resource_info( + f"{stack_name}/{stack_id}", 'VDU2_CP2') before_physical_resource_id = port_info['physical_resource_id'] before_fixed_ips = port_info['attributes']['fixed_ips'] @@ -2871,8 +2871,8 @@ class VnfLcmTest(base_v2.BaseSolV2Test): lcmocc_id = os.path.basename(resp.headers['Location']) self.wait_lcmocc_complete(lcmocc_id) - _, port_info = self.heat_client.get_resource_info( - stack_name, stack_id, 'VDU2_CP2') + port_info = self.heat_client.get_resource_info( + f"{stack_name}/{stack_id}", 'VDU2_CP2') after_physical_resource_id = port_info['physical_resource_id'] after_fixed_ips = port_info['attributes']['fixed_ips'] diff --git a/tacker/tests/unit/sol_refactored/infra_drivers/openstack/test_userdata_utils.py b/tacker/tests/unit/sol_refactored/common/test_common_script_utils.py similarity index 88% rename from tacker/tests/unit/sol_refactored/infra_drivers/openstack/test_userdata_utils.py rename to tacker/tests/unit/sol_refactored/common/test_common_script_utils.py index c3f84e7cf..5ceec1f17 100644 --- a/tacker/tests/unit/sol_refactored/infra_drivers/openstack/test_userdata_utils.py +++ b/tacker/tests/unit/sol_refactored/common/test_common_script_utils.py @@ -15,7 +15,7 @@ import os -from tacker.sol_refactored.infra_drivers.openstack import userdata_utils +from tacker.sol_refactored.common import common_script_utils from tacker.tests import base @@ -23,14 +23,14 @@ SAMPLE_VNFD_ID = "b1bb0ce7-ebca-4fa7-95ed-4840d7000000" SAMPLE_FLAVOUR_ID = "simple" -class TestUserDataUtils(base.BaseTestCase): +class TestCommontScriptUtils(base.BaseTestCase): def setUp(self): - super(TestUserDataUtils, self).setUp() + super(TestCommontScriptUtils, self).setUp() cur_dir = os.path.dirname(__file__) - sample_dir = os.path.join(cur_dir, "../..", "samples") + sample_dir = os.path.join(cur_dir, "..", "samples") - self.vnfd_1 = userdata_utils.get_vnfd(SAMPLE_VNFD_ID, + self.vnfd_1 = common_script_utils.get_vnfd(SAMPLE_VNFD_ID, os.path.join(sample_dir, "sample1")) def test_init_nfv_dict(self): @@ -54,7 +54,7 @@ class TestUserDataUtils(base.BaseTestCase): 'subnet': None}}} } } - result = userdata_utils.init_nfv_dict(top_hot) + result = common_script_utils.init_nfv_dict(top_hot) self.assertEqual(expected_result, result) def test_get_param_flavor(self): @@ -68,12 +68,14 @@ class TestUserDataUtils(base.BaseTestCase): } } - result = userdata_utils.get_param_flavor('VDU1', SAMPLE_FLAVOUR_ID, + result = common_script_utils.get_param_flavor( + 'VDU1', SAMPLE_FLAVOUR_ID, self.vnfd_1, grant) self.assertEqual(flavor, result) # if not exist in grant, get from VNFD - result = userdata_utils.get_param_flavor('VDU2', SAMPLE_FLAVOUR_ID, + result = common_script_utils.get_param_flavor( + 'VDU2', SAMPLE_FLAVOUR_ID, self.vnfd_1, grant) self.assertEqual('m1.tiny', result) @@ -90,7 +92,7 @@ class TestUserDataUtils(base.BaseTestCase): } } - result = userdata_utils.get_param_image('VDU2', SAMPLE_FLAVOUR_ID, + result = common_script_utils.get_param_image('VDU2', SAMPLE_FLAVOUR_ID, self.vnfd_1, grant) self.assertEqual(image_id, result) @@ -113,7 +115,7 @@ class TestUserDataUtils(base.BaseTestCase): ] } - result = userdata_utils.get_param_zone('VDU1', grant_req, grant) + result = common_script_utils.get_param_zone('VDU1', grant_req, grant) self.assertEqual('nova', result) def test_get_param_capacity(self): @@ -145,9 +147,11 @@ class TestUserDataUtils(base.BaseTestCase): } } - result = userdata_utils.get_param_capacity('VDU1', inst, grant_req) + result = common_script_utils.get_param_capacity( + 'VDU1', inst, grant_req) self.assertEqual(2, result) - result = userdata_utils.get_param_capacity('VDU2', inst, grant_req) + result = common_script_utils.get_param_capacity( + 'VDU2', inst, grant_req) self.assertEqual(1, result) def test_get_parama_network(self): @@ -167,7 +171,7 @@ class TestUserDataUtils(base.BaseTestCase): ] } - result = userdata_utils.get_param_network('VDU1_CP1', {}, req) + result = common_script_utils.get_param_network('VDU1_CP1', {}, req) self.assertEqual(res_id, result) def test_get_param_fixed_ips(self): @@ -207,7 +211,7 @@ class TestUserDataUtils(base.BaseTestCase): } expected_result = [{'ip_address': ip_address, 'subnet': subnet_id}] - result = userdata_utils.get_param_fixed_ips('VDU2_CP2', {}, req) + result = common_script_utils.get_param_fixed_ips('VDU2_CP2', {}, req) self.assertEqual(expected_result, result) def _inst_example_get_network_fixed_ips_from_inst(self): @@ -252,7 +256,8 @@ class TestUserDataUtils(base.BaseTestCase): def test_get_parama_network_from_inst(self): inst = self._inst_example_get_network_fixed_ips_from_inst() - result = userdata_utils.get_param_network_from_inst('VDU2_CP2', inst) + result = common_script_utils.get_param_network_from_inst( + 'VDU2_CP2', inst) self.assertEqual("ext_vl_res_id", result) def test_get_param_fixed_ips_from_inst(self): @@ -260,7 +265,8 @@ class TestUserDataUtils(base.BaseTestCase): expected_result = [{'ip_address': 'ip_address', 'subnet': 'subnet_id'}] - result = userdata_utils.get_param_fixed_ips_from_inst('VDU2_CP2', inst) + result = common_script_utils.get_param_fixed_ips_from_inst( + 'VDU2_CP2', inst) self.assertEqual(expected_result, result) def test_apply_ext_managed_vls(self): @@ -289,7 +295,7 @@ class TestUserDataUtils(base.BaseTestCase): self.assertIn(vl, top_hot['resources']) self.assertIn(vl_subnet, top_hot['resources']) - userdata_utils.apply_ext_managed_vls(top_hot, req, {}) + common_script_utils.apply_ext_managed_vls(top_hot, req, {}) # check after # replaced to resource id diff --git a/tacker/tests/unit/sol_refactored/common/test_lcm_op_occ_utils.py b/tacker/tests/unit/sol_refactored/common/test_lcm_op_occ_utils.py index e39235f52..abfc3a91d 100644 --- a/tacker/tests/unit/sol_refactored/common/test_lcm_op_occ_utils.py +++ b/tacker/tests/unit/sol_refactored/common/test_lcm_op_occ_utils.py @@ -914,6 +914,7 @@ _inst_info_example_4 = { "extVirtualLinkInfo": _inst_info_example_2["extVirtualLinkInfo"], # network resource is not changed but ports are re-receated. # this is for check of SOL003 all=True case. + "extManagedVirtualLinkInfo": [ { "id": "res_id_internalVL1", @@ -1099,6 +1100,257 @@ _inst_info_example_4 = { # "vnfcInfo": omitted } +# example_5 is update VDU1 and VDU2's info from example_1. +_inst_info_example_5 = { + # "flavourId", "vnfState", "scaleStatus", "maxScaleLevels" are omitted + # "extCpInfo": omitted + "extVirtualLinkInfo": [ + { + "id": "bbf0932a-6142-4ea8-93cd-8059dba594a1", + "resourceHandle": { + "resourceId": "3529d333-dbcc-4d93-9b64-210647712569" + }, + "extLinkPorts": [ + { + "id": "res_id_VDU2_CP1", + "resourceHandle": { + "vimConnectionId": "vim_connection_id", + "resourceId": "res_id_VDU2_CP1", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "cp-res_id_VDU2_CP1" + }, + { + "id": "res_id_VDU1_CP1_1", + "resourceHandle": { + "vimConnectionId": "vim_connection_id", + "resourceId": "res_id_VDU1_CP1_1", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "cp-res_id_VDU1_CP1_1" + } + ], + # "currentVnfExtCpData": omitted + }, + { + "id": "790949df-c7b3-4926-a559-3895412f1dfe", + "resourceHandle": { + "resourceId": "367e5b3b-34dc-47f2-85b8-c39e3272893a" + }, + "extLinkPorts": [ + { + "id": "res_id_VDU2_CP2", + "resourceHandle": { + "vimConnectionId": "vim_connection_id", + "resourceId": "res_id_VDU2_CP2", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "cp-res_id_VDU2_CP2" + }, + { + "id": "res_id_VDU1_CP2_1", + "resourceHandle": { + "vimConnectionId": "vim_connection_id", + "resourceId": "res_id_VDU1_CP2_1", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "cp-res_id_VDU1_CP2_1" + } + ], + # "currentVnfExtCpData": omitted + } + ], + "extManagedVirtualLinkInfo": [ + { + "id": "res_id_internalVL1", + "vnfVirtualLinkDescId": "internalVL1", + "networkResource": { + "resourceId": "res_id_internalVL1" + }, + "vnfLinkPorts": [ + { + "id": "res_id_VDU2_CP3", + "resourceHandle": { + "vimConnectionId": "vim_connection_id", + "resourceId": "res_id_VDU2_CP3", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "VDU2_CP3-res_id_VDU2", + "cpInstanceType": "VNFC_CP" + }, + { + "id": "res_id_VDU1_CP3_1", + "resourceHandle": { + "vimConnectionId": "vim_connection_id", + "resourceId": "res_id_VDU1_CP3_1", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "VDU1_CP3-res_id_VDU1_1", + "cpInstanceType": "VNFC_CP" + } + ] + } + ], + "vnfcResourceInfo": [ + { + "id": "res_id_VDU1_1_update", + "vduId": "VDU1", + "computeResource": { + "vimConnectionId": "vim_connection_id", + "resourceId": "res_id_VDU1_1_update", + "vimLevelResourceType": "OS::Nova::Server" + }, + "metadata": { + "current_vnfd_id": 'new_vnfd_id' + }, + "storageResourceIds": [ + "new_res_id_VirtualStorage_1" + ], + "vnfcCpInfo": [ + { + "id": "VDU1_CP1-res_id_VDU1_1", + "cpdId": "VDU1_CP1", + "vnfExtCpId": "cp-res_id_VDU1_CP1_1" + }, + { + "id": "VDU1_CP2-res_id_VDU1_1", + "cpdId": "VDU1_CP2", + "vnfExtCpId": "cp-res_id_VDU1_CP2_1" + }, + { + "id": "VDU1_CP3-res_id_VDU1_1", + "cpdId": "VDU1_CP3", + "vnfLinkPortId": "res_id_VDU1_CP3_1" + }, + { + "id": "VDU1_CP4-res_id_VDU1_1", + "cpdId": "VDU1_CP4", + "vnfLinkPortId": "res_id_VDU1_CP4_1" + }, + { + "id": "VDU1_CP5-res_id_VDU1_1", + "cpdId": "VDU1_CP5", + "vnfLinkPortId": "res_id_VDU1_CP5_1" + } + ], + }, + { + "id": "res_id_VDU2", + "vduId": "VDU2", + "computeResource": { + "vimConnectionId": "vim_connection_id", + "resourceId": "res_id_VDU2", + "vimLevelResourceType": "OS::Nova::Server" + }, + "metadata": { + "current_vnfd_id": 'new_vnfd_id' + }, + "vnfcCpInfo": [ + { + "id": "VDU2_CP1-res_id_VDU2", + "cpdId": "VDU2_CP1", + "vnfExtCpId": "cp-res_id_VDU2_CP1" + }, + { + "id": "VDU2_CP2-res_id_VDU2", + "cpdId": "VDU2_CP2", + "vnfExtCpId": "cp-res_id_VDU2_CP2" + }, + { + "id": "VDU2_CP3-res_id_VDU2", + "cpdId": "VDU2_CP3", + "vnfLinkPortId": "res_id_VDU2_CP3" + }, + { + "id": "VDU2_CP4-res_id_VDU2", + "cpdId": "VDU2_CP4", + "vnfLinkPortId": "res_id_VDU2_CP4" + }, + { + "id": "VDU2_CP5-res_id_VDU2", + "cpdId": "VDU2_CP5", + "vnfLinkPortId": "res_id_VDU2_CP5" + } + ], + } + ], + "vnfVirtualLinkResourceInfo": [ + { + "id": "res_id_internalVL3", + "vnfVirtualLinkDescId": "internalVL3", + "networkResource": { + "vimConnectionId": "vim_connection_id", + "resourceId": "res_id_internalVL3", + "vimLevelResourceType": "OS::Neutron::Net" + }, + "vnfLinkPorts": [ + { + "id": "res_id_VDU2_CP5", + "resourceHandle": { + "vimConnectionId": "vim_connection_id", + "resourceId": "res_id_VDU2_CP5", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "VDU2_CP5-res_id_VDU2", + "cpInstanceType": "VNFC_CP" + }, + { + "id": "res_id_VDU1_CP5_1", + "resourceHandle": { + "vimConnectionId": "vim_connection_id", + "resourceId": "res_id_VDU1_CP5_1", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "VDU1_CP5-res_id_VDU1_1", + "cpInstanceType": "VNFC_CP" + } + ] + }, + { + "id": "res_id_internalVL2", + "vnfVirtualLinkDescId": "internalVL2", + "networkResource": { + "vimConnectionId": "vim_connection_id", + "resourceId": "res_id_internalVL2", + "vimLevelResourceType": "OS::Neutron::Net" + }, + "vnfLinkPorts": [ + { + "id": "res_id_VDU2_CP4", + "resourceHandle": { + "vimConnectionId": "vim_connection_id", + "resourceId": "res_id_VDU2_CP4", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "VDU2_CP4-res_id_VDU2", + "cpInstanceType": "VNFC_CP" + }, + { + "id": "res_id_VDU1_CP4_1", + "resourceHandle": { + "vimConnectionId": "vim_connection_id", + "resourceId": "res_id_VDU1_CP4_1", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "VDU1_CP4-res_id_VDU1_1", + "cpInstanceType": "VNFC_CP" + } + ] + } + ], + "virtualStorageResourceInfo": [ + { + "id": "new_res_id_VirtualStorage_1", + "virtualStorageDescId": "VirtualStorage", + "storageResource": { + "vimConnectionId": "vim_connection_id", + "resourceId": "new_res_id_VirtualStorage_1", + "vimLevelResourceType": "OS::Cinder::Volume" + } + } + ], +} + # expected results _expected_resource_changes_instantiate = { "affectedVnfcs": [ @@ -1878,6 +2130,95 @@ _expected_resource_changes_heal = { } ] } +_expected_resource_changes_change_vnfpkg = { + "affectedVnfcs": [ + { + "id": "res_id_VDU1_1", + "vduId": "VDU1", + "changeType": "REMOVED", + "computeResource": { + "vimConnectionId": "vim_connection_id", + "resourceId": "res_id_VDU1_1", + "vimLevelResourceType": "OS::Nova::Server" + }, + "affectedVnfcCpIds": [ + "VDU1_CP1-res_id_VDU1_1", + "VDU1_CP2-res_id_VDU1_1", + "VDU1_CP3-res_id_VDU1_1", + "VDU1_CP4-res_id_VDU1_1", + "VDU1_CP5-res_id_VDU1_1" + ], + "removedStorageResourceIds": [ + "res_id_VirtualStorage_1" + ] + }, + { + "id": "res_id_VDU1_1_update", + "vduId": "VDU1", + "changeType": "ADDED", + "computeResource": { + "vimConnectionId": "vim_connection_id", + "resourceId": "res_id_VDU1_1_update", + "vimLevelResourceType": "OS::Nova::Server" + }, + "affectedVnfcCpIds": [ + "VDU1_CP1-res_id_VDU1_1", + "VDU1_CP2-res_id_VDU1_1", + "VDU1_CP3-res_id_VDU1_1", + "VDU1_CP4-res_id_VDU1_1", + "VDU1_CP5-res_id_VDU1_1" + ], + "addedStorageResourceIds": [ + "new_res_id_VirtualStorage_1" + ], + 'metadata': { + 'current_vnfd_id': 'new_vnfd_id' + } + }, + { + "id": "res_id_VDU2", + "vduId": "VDU2", + "changeType": "MODIFIED", + "computeResource": { + "vimConnectionId": "vim_connection_id", + "resourceId": "res_id_VDU2", + "vimLevelResourceType": "OS::Nova::Server" + }, + "affectedVnfcCpIds": [ + "VDU2_CP1-res_id_VDU2", + "VDU2_CP2-res_id_VDU2", + "VDU2_CP3-res_id_VDU2", + "VDU2_CP4-res_id_VDU2", + "VDU2_CP5-res_id_VDU2" + ], + 'metadata': { + 'current_vnfd_id': 'new_vnfd_id' + } + } + ], + "affectedVirtualStorages": [ + { + "id": "new_res_id_VirtualStorage_1", + "virtualStorageDescId": "VirtualStorage", + "changeType": "ADDED", + "storageResource": { + "vimConnectionId": "vim_connection_id", + "resourceId": "new_res_id_VirtualStorage_1", + "vimLevelResourceType": "OS::Cinder::Volume" + } + }, + { + "id": "res_id_VirtualStorage_1", + "virtualStorageDescId": "VirtualStorage", + "changeType": "REMOVED", + "storageResource": { + "vimConnectionId": "vim_connection_id", + "resourceId": "res_id_VirtualStorage_1", + "vimLevelResourceType": "OS::Cinder::Volume" + } + } + ] +} class TestLcmOpOccUtils(base.BaseTestCase): @@ -2061,3 +2402,25 @@ class TestLcmOpOccUtils(base.BaseTestCase): self.assertEqual( _expected_resource_changes_heal, self._sort_resource_changes(lcmocc['resourceChanges'])) + + def test_update_lcmocc_change_vnfpkg(self): + # prepare + inst_saved = objects.VnfInstanceV2() + inst_saved.instantiatedVnfInfo = ( + objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_example_1)) + inst_saved.vnfdId = 'old_vnfd_id' + inst = objects.VnfInstanceV2() + inst.instantiatedVnfInfo = ( + objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_example_5)) + lcmocc = objects.VnfLcmOpOccV2(operation='CHANGE_VNFPKG') + + # execute update_lcmocc + lcmocc_utils.update_lcmocc(lcmocc, inst_saved, inst) + + # check resourceChanges + lcmocc = lcmocc.to_dict() + self.assertEqual( + _expected_resource_changes_change_vnfpkg, + self._sort_resource_changes(lcmocc['resourceChanges'])) diff --git a/tacker/tests/unit/sol_refactored/conductor/test_conductor_v2.py b/tacker/tests/unit/sol_refactored/conductor/test_conductor_v2.py index 6ac44f47f..b8afc8fe8 100644 --- a/tacker/tests/unit/sol_refactored/conductor/test_conductor_v2.py +++ b/tacker/tests/unit/sol_refactored/conductor/test_conductor_v2.py @@ -70,6 +70,36 @@ class TestConductorV2(db_base.SqlTestCase): return lcmocc + def _change_vnfpkg_lcmocc( + self, op_state=fields.LcmOperationStateType.STARTING): + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=uuidutils.generate_uuid(), + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED' + ) + req = {"vnfdId": uuidutils.generate_uuid()} + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=op_state, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.CHANGE_VNFPKG, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + + inst.create(self.context) + lcmocc.create(self.context) + + return lcmocc + def _make_grant_req_and_grant(self, lcmocc): grant_req = objects.GrantRequestV1( # required fields @@ -129,6 +159,34 @@ class TestConductorV2(db_base.SqlTestCase): self.assertRaises(sol_ex.GrantRequestOrGrantNotFound, lcmocc_utils.get_grant_req_and_grant, self.context, lcmocc) + @mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'process') + @mock.patch.object(nfvo_client.NfvoClient, 'send_lcmocc_notification') + @mock.patch.object(nfvo_client.NfvoClient, 'get_vnfd') + @mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'grant') + def test_start_lcm_op_change_vnfpkg_completed( + self, mocked_grant, mocked_get_vnfd, + mocked_send_lcmocc_notification, mocked_process): + # prepare + lcmocc = self._change_vnfpkg_lcmocc() + mocked_get_vnfd.return_value = mock.Mock() + mocked_grant.return_value = self._make_grant_req_and_grant(lcmocc) + mocked_process.return_value = mock.Mock() + op_state = [] + + def _store_state(context, lcmocc, inst, endpoint): + op_state.append(lcmocc.operationState) + + mocked_send_lcmocc_notification.side_effect = _store_state + + # run start_lcm_op + self.conductor.start_lcm_op(self.context, lcmocc.id) + + # check operationState transition + self.assertEqual(3, mocked_send_lcmocc_notification.call_count) + self.assertEqual(fields.LcmOperationStateType.STARTING, op_state[0]) + self.assertEqual(fields.LcmOperationStateType.PROCESSING, op_state[1]) + self.assertEqual(fields.LcmOperationStateType.COMPLETED, op_state[2]) + @mock.patch.object(nfvo_client.NfvoClient, 'send_lcmocc_notification') @mock.patch.object(nfvo_client.NfvoClient, 'get_vnfd') @mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'grant') diff --git a/tacker/tests/unit/sol_refactored/conductor/test_vnflcm_driver_v2.py b/tacker/tests/unit/sol_refactored/conductor/test_vnflcm_driver_v2.py index 8fb1316cd..d96faf500 100644 --- a/tacker/tests/unit/sol_refactored/conductor/test_vnflcm_driver_v2.py +++ b/tacker/tests/unit/sol_refactored/conductor/test_vnflcm_driver_v2.py @@ -627,6 +627,29 @@ _modify_vnfc_info_example = { ] } +# change_vnfpkg example +_change_vnfpkg_example = { + "vnfdId": '61723406-6634-2fc0-060a-0b11104d2667', + "additionalParams": { + "upgrade_type": "RollingUpdate", + "lcm-operation-coordinate-old-vnf": "./Scripts/coordinate_old_vnf.py", + "lcm-operation-coordinate-old-vnf-class": "CoordinateOldVnf", + "lcm-operation-coordinate-new-vnf": "./Scripts/coordinate_new_vnf.py", + "lcm-operation-coordinate-new-vnf-class": "CoordinateNewVnf", + "vdu_params": [{ + "vdu_id": "VDU1", + "old_vnfc_param": { + "cp_name": "CP1", + "username": "ubuntu", + "password": "ubuntu"}, + "new_vnfc_param": { + "cp_name": "CP1", + "username": "ubuntu", + "password": "ubuntu"}, + }] + } +} + class TestVnfLcmDriverV2(base.BaseTestCase): @@ -1688,3 +1711,193 @@ class TestVnfLcmDriverV2(base.BaseTestCase): for key, value in check_reses.items(): for name, ids in value.items(): self.assertEqual(len(expected_res_ids[key][name]), len(ids)) + + @mock.patch.object(nfvo_client.NfvoClient, 'grant') + def test_change_vnfpkg_grant_update_reses(self, mocked_grant): + # prepare + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED' + ) + inst_info = objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_example) + inst.instantiatedVnfInfo = inst_info + req = objects.ChangeCurrentVnfPkgRequest.from_dict( + _change_vnfpkg_example) + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.PROCESSING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.CHANGE_VNFPKG, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + + mocked_grant.return_value = objects.GrantV1() + + # run change_vnfpkg_grant + grant_req, _ = self.driver.grant( + self.context, lcmocc, inst, self.vnfd_1) + + # check grant_req is constructed according to intention + grant_req = grant_req.to_dict() + expected_fixed_items = { + 'vnfInstanceId': inst.id, + 'vnfLcmOpOccId': lcmocc.id, + 'vnfdId': '61723406-6634-2fc0-060a-0b11104d2667', + 'operation': 'CHANGE_VNFPKG', + 'isAutomaticInvocation': False + } + for key, value in expected_fixed_items.items(): + self.assertEqual(value, grant_req[key]) + + update_reses = grant_req['updateResources'] + target_vdu_list = [ + vdu_param.get( + 'vdu_id') for vdu_param in req.additionalParams.get( + 'vdu_params')] + for i in range(len(update_reses)): + self.assertEqual('COMPUTE', update_reses[i]['type']) + for target_vdu in target_vdu_list: + self.assertEqual(target_vdu, + update_reses[i]['resourceTemplateId']) + + @mock.patch.object(nfvo_client.NfvoClient, 'grant') + def test_change_vnfpkg_grant_add_reses(self, mocked_grant): + # prepare + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED' + ) + inst_info = objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_example) + inst.instantiatedVnfInfo = inst_info + req = objects.ChangeCurrentVnfPkgRequest.from_dict( + _change_vnfpkg_example) + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.PROCESSING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.CHANGE_VNFPKG, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + + mocked_grant.return_value = objects.GrantV1() + + # run change_vnfpkg_grant + grant_req, _ = self.driver.grant( + self.context, lcmocc, inst, self.vnfd_1) + + # check grant_req is constructed according to intention + grant_req = grant_req.to_dict() + expected_fixed_items = { + 'vnfInstanceId': inst.id, + 'vnfLcmOpOccId': lcmocc.id, + 'vnfdId': '61723406-6634-2fc0-060a-0b11104d2667', + 'operation': 'CHANGE_VNFPKG', + 'isAutomaticInvocation': False + } + for key, value in expected_fixed_items.items(): + self.assertEqual(value, grant_req[key]) + + add_reses = grant_req['addResources'] + for inst_vnc in inst_info.vnfcResourceInfo: + nodes = self.vnfd_1.get_vdu_nodes(inst_info.flavourId) + vdu_storage_names = self.vnfd_1.get_vdu_storages( + nodes[inst_vnc.vduId]) + for i in range(len(add_reses)): + self.assertEqual('STORAGE', add_reses[i]['type']) + for vdu_storage_name in vdu_storage_names: + self.assertEqual(vdu_storage_name, + add_reses[i]['resourceTemplateId']) + + @mock.patch.object(nfvo_client.NfvoClient, 'grant') + def test_change_vnfpkg_grant_remove_reses(self, mocked_grant): + # prepare + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED' + ) + inst_info = objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_example) + inst.instantiatedVnfInfo = inst_info + req = objects.ChangeCurrentVnfPkgRequest.from_dict( + _change_vnfpkg_example) + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.PROCESSING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.CHANGE_VNFPKG, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + + mocked_grant.return_value = objects.GrantV1() + + # run change_vnfpkg_grant + grant_req, _ = self.driver.grant( + self.context, lcmocc, inst, self.vnfd_1) + + # check grant_req is constructed according to intention + grant_req = grant_req.to_dict() + expected_fixed_items = { + 'vnfInstanceId': inst.id, + 'vnfLcmOpOccId': lcmocc.id, + 'vnfdId': '61723406-6634-2fc0-060a-0b11104d2667', + 'operation': 'CHANGE_VNFPKG', + 'isAutomaticInvocation': False + } + for key, value in expected_fixed_items.items(): + self.assertEqual(value, grant_req[key]) + + remove_reses = grant_req['removeResources'] + inst_stor_info = inst_info.virtualStorageResourceInfo + check_reses = [] + for str_info in inst_stor_info: + check_res = { + 'resourceId': str_info.storageResource.resourceId, + 'vimLevelResourceType': + str_info.storageResource.vimLevelResourceType + } + check_reses.append(check_res) + for i in range(len(remove_reses)): + self.assertEqual('STORAGE', remove_reses[i]['type']) + self.assertEqual(str_info.virtualStorageDescId, + remove_reses[i]['resourceTemplateId']) + for j in range(len(check_reses)): + for k in range(len(remove_reses)): + if j == k: + self.assertEqual( + check_reses[j]['resourceId'], + remove_reses[j]['resource']['resourceId']) + self.assertEqual( + check_reses[j]['vimLevelResourceType'], + remove_reses[j]['resource']['vimLevelResourceType']) diff --git a/tacker/tests/unit/sol_refactored/controller/test_vnflcm_v2.py b/tacker/tests/unit/sol_refactored/controller/test_vnflcm_v2.py index 6651185ed..dea22580a 100644 --- a/tacker/tests/unit/sol_refactored/controller/test_vnflcm_v2.py +++ b/tacker/tests/unit/sol_refactored/controller/test_vnflcm_v2.py @@ -1,585 +1,673 @@ -# Copyright (C) 2021 Nippon Telegraph and Telephone Corporation -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from datetime import datetime -from unittest import mock - -from oslo_utils import uuidutils - -from tacker import context -from tacker.sol_refactored.api import api_version -from tacker.sol_refactored.common import exceptions as sol_ex -from tacker.sol_refactored.common import lcm_op_occ_utils as lcmocc_utils -from tacker.sol_refactored.controller import vnflcm_v2 -from tacker.sol_refactored.nfvo import nfvo_client -from tacker.sol_refactored import objects -from tacker.sol_refactored.objects.v2 import fields -from tacker.tests.unit.db import base as db_base - - -_change_ext_conn_req_example = { - "extVirtualLinks": [ - { - "id": "id_ext_vl_1", - "resourceId": "res_id_ext_vl_1", - "extCps": [ - { - "cpdId": "VDU2_CP2", - "cpConfig": { - "VDU2_CP2_1": { - "linkPortId": "link_port_id_VDU2_CP2" - } - } - } - ], - "extLinkPorts": [ - { - "id": "link_port_id_VDU2_CP2", - "resourceHandle": { - "resourceId": "res_id_VDU2_CP2" - } - } - ] - } - ] -} - - -class TestVnflcmV2(db_base.SqlTestCase): - - def setUp(self): - super(TestVnflcmV2, self).setUp() - objects.register_all() - self.controller = vnflcm_v2.VnfLcmControllerV2() - self.context = context.get_admin_context() - self.context.api_version = api_version.APIVersion("2.0.0") - self.request = mock.Mock() - self.request.context = self.context - - def _set_inst_and_lcmocc(self, inst_state, op_state): - inst = objects.VnfInstanceV2( - # required fields - id=uuidutils.generate_uuid(), - vnfdId=uuidutils.generate_uuid(), - vnfProvider='provider', - vnfProductName='product name', - vnfSoftwareVersion='software version', - vnfdVersion='vnfd version', - instantiationState=inst_state - ) - - req = {"flavourId": "simple"} # instantiate request - lcmocc = objects.VnfLcmOpOccV2( - # required fields - id=uuidutils.generate_uuid(), - operationState=op_state, - stateEnteredTime=datetime.utcnow(), - startTime=datetime.utcnow(), - vnfInstanceId=inst.id, - operation=fields.LcmOperationType.INSTANTIATE, - isAutomaticInvocation=False, - isCancelPending=False, - operationParams=req - ) - - return inst, lcmocc - - def _create_inst_and_lcmocc(self, inst_state, op_state): - inst, lcmocc = self._set_inst_and_lcmocc(inst_state, op_state) - - inst.create(self.context) - lcmocc.create(self.context) - - return inst.id, lcmocc.id - - @mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd') - def test_create_pkg_disabled(self, mocked_get_vnf_package_info_vnfd): - vnfd_id = uuidutils.generate_uuid() - pkg_info = objects.VnfPkgInfoV2( - id=uuidutils.generate_uuid(), - vnfdId=vnfd_id, - vnfProvider="provider", - vnfProductName="product", - vnfSoftwareVersion="software version", - vnfdVersion="vnfd version", - operationalState="DISABLED" - ) - mocked_get_vnf_package_info_vnfd.return_value = pkg_info - body = { - "vnfdId": vnfd_id, - "vnfInstanceName": "test", - "vnfInstanceDescription": "test" - } - self.assertRaises(sol_ex.VnfdIdNotEnabled, - self.controller.create, request=self.request, body=body) - - def test_delete_instantiated(self): - inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - - self.assertRaises(sol_ex.VnfInstanceIsInstantiated, - self.controller.delete, request=self.request, id=inst_id) - - def test_delete_lcmocc_in_progress(self): - inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', - fields.LcmOperationStateType.FAILED_TEMP) - - self.assertRaises(sol_ex.OtherOperationInProgress, - self.controller.delete, request=self.request, id=inst_id) - - def test_instantiate_instantiated(self): - inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - body = {"flavourId": "small"} - - self.assertRaises(sol_ex.VnfInstanceIsInstantiated, - self.controller.instantiate, request=self.request, id=inst_id, - body=body) - - def test_instantiate_lcmocc_in_progress(self): - inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', - fields.LcmOperationStateType.FAILED_TEMP) - body = {"flavourId": "small"} - - self.assertRaises(sol_ex.OtherOperationInProgress, - self.controller.instantiate, request=self.request, id=inst_id, - body=body) - - def test_terminate_not_instantiated(self): - inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - body = {"terminationType": "FORCEFUL"} - - self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated, - self.controller.terminate, request=self.request, id=inst_id, - body=body) - - def test_terminate_lcmocc_in_progress(self): - inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.FAILED_TEMP) - body = {"terminationType": "FORCEFUL"} - - self.assertRaises(sol_ex.OtherOperationInProgress, - self.controller.terminate, request=self.request, id=inst_id, - body=body) - - def test_invalid_subscripion(self): - body = { - "callbackUri": "http://127.0.0.1:6789/notification", - "authentication": { - "authType": ["BASIC"] - } - } - ex = self.assertRaises(sol_ex.InvalidSubscription, - self.controller.subscription_create, request=self.request, - body=body) - self.assertEqual("ParmasBasic must be specified.", ex.detail) - - body = { - "callbackUri": "http://127.0.0.1:6789/notification", - "authentication": { - "authType": ["OAUTH2_CLIENT_CREDENTIALS"] - } - } - ex = self.assertRaises(sol_ex.InvalidSubscription, - self.controller.subscription_create, request=self.request, - body=body) - self.assertEqual("paramsOauth2ClientCredentials must be specified.", - ex.detail) - - body = { - "callbackUri": "http://127.0.0.1:6789/notification", - "authentication": { - "authType": ["TLS_CERT"] - } - } - ex = self.assertRaises(sol_ex.InvalidSubscription, - self.controller.subscription_create, request=self.request, - body=body) - self.assertEqual("'TLS_CERT' is not supported at the moment.", - ex.detail) - - def test_retry_not_failed_temp(self): - _, lcmocc_id = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - - self.assertRaises(sol_ex.LcmOpOccNotFailedTemp, - self.controller.lcm_op_occ_retry, request=self.request, - id=lcmocc_id) - - def test_rollback_not_failed_temp(self): - _, lcmocc_id = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - - self.assertRaises(sol_ex.LcmOpOccNotFailedTemp, - self.controller.lcm_op_occ_rollback, request=self.request, - id=lcmocc_id) - - def test_fail_not_failed_temp(self): - _, lcmocc_id = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - - self.assertRaises(sol_ex.LcmOpOccNotFailedTemp, - self.controller.lcm_op_occ_fail, request=self.request, - id=lcmocc_id) - - def _prepare_db_for_fail(self): - inst, lcmocc = self._set_inst_and_lcmocc('NOT_INSTANTIATED', - fields.LcmOperationStateType.FAILED_TEMP) - - inst.create(self.context) - lcmocc.create(self.context) - - grant_req = objects.GrantRequestV1( - # required fields - vnfInstanceId=lcmocc.vnfInstanceId, - vnfLcmOpOccId=lcmocc.id, - vnfdId=uuidutils.generate_uuid(), - operation=lcmocc.operation, - isAutomaticInvocation=lcmocc.isAutomaticInvocation - ) - - grant = objects.GrantV1( - # required fields - id=uuidutils.generate_uuid(), - vnfInstanceId=lcmocc.vnfInstanceId, - vnfLcmOpOccId=lcmocc.id - ) - - grant_req.create(self.context) - grant.create(self.context) - lcmocc.grantId = grant.id - lcmocc.update(self.context) - - return lcmocc - - @mock.patch.object(nfvo_client.NfvoClient, 'send_lcmocc_notification') - def test_lcm_op_occ_fail(self, mocked_send_lcmocc_notification): - # prepare - lcmocc = self._prepare_db_for_fail() - - op_state = [] - - def _store_state(context, lcmocc, inst, endpoint): - op_state.append(lcmocc.operationState) - - mocked_send_lcmocc_notification.side_effect = _store_state - - # run lcm_op_occ_fail - self.controller.lcm_op_occ_fail(self.request, lcmocc.id) - - # check operationstate - self.assertEqual(1, mocked_send_lcmocc_notification.call_count) - self.assertEqual(fields.LcmOperationStateType.FAILED, op_state[0]) - - # check grant_req and grant are deleted - self.assertRaises(sol_ex.GrantRequestOrGrantNotFound, - lcmocc_utils.get_grant_req_and_grant, self.context, lcmocc) - - def test_scale_not_instantiated(self): - inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - body = {"aspectId": "aspect_1", "type": "SCALE_OUT"} - - self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated, - self.controller.scale, request=self.request, id=inst_id, - body=body) - - def test_scale_lcmocc_in_progress(self): - inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.FAILED_TEMP) - body = {"aspectId": "aspect_1", "type": "SCALE_OUT"} - - self.assertRaises(sol_ex.OtherOperationInProgress, - self.controller.scale, request=self.request, id=inst_id, - body=body) - - def _prepare_db_for_scale_param_check(self, scale_status, - max_scale_levels): - inst = objects.VnfInstanceV2( - # required fields - id=uuidutils.generate_uuid(), - vnfdId=uuidutils.generate_uuid(), - vnfProvider='provider', - vnfProductName='product name', - vnfSoftwareVersion='software version', - vnfdVersion='vnfd version', - instantiationState='INSTANTIATED' - ) - inst.instantiatedVnfInfo = objects.VnfInstanceV2_InstantiatedVnfInfo( - flavourId='small', - vnfState='STARTED', - scaleStatus=scale_status, - maxScaleLevels=max_scale_levels - ) - inst.create(self.context) - - return inst.id - - def test_scale_invalid_aspect_id(self): - scale_status = [ - objects.ScaleInfoV2( - aspectId="aspect_2", - scaleLevel=0 - ) - ] - max_scale_levels = [ - objects.ScaleInfoV2( - aspectId="aspect_2", - scaleLevel=3 - ) - ] - inst_id = self._prepare_db_for_scale_param_check(scale_status, - max_scale_levels) - body = {"aspectId": "aspect_1", "type": "SCALE_OUT"} - - self.assertRaises(sol_ex.InvalidScaleAspectId, - self.controller.scale, request=self.request, id=inst_id, - body=body) - - def test_scale_invalid_number_of_steps(self): - scale_status = [ - objects.ScaleInfoV2( - aspectId="aspect_1", - scaleLevel=1 - ) - ] - max_scale_levels = [ - objects.ScaleInfoV2( - aspectId="aspect_1", - scaleLevel=3 - ) - ] - inst_id = self._prepare_db_for_scale_param_check(scale_status, - max_scale_levels) - body = {"aspectId": "aspect_1", "type": "SCALE_OUT", - "numberOfSteps": 3} - - self.assertRaises(sol_ex.InvalidScaleNumberOfSteps, - self.controller.scale, request=self.request, id=inst_id, - body=body) - - body = {"aspectId": "aspect_1", "type": "SCALE_IN", - "numberOfSteps": 2} - - self.assertRaises(sol_ex.InvalidScaleNumberOfSteps, - self.controller.scale, request=self.request, id=inst_id, - body=body) - - def test_update_lcmocc_in_progress(self): - inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.FAILED_TEMP) - - body = {"vnfInstanceDescription": "example1"} - - self.assertRaises(sol_ex.OtherOperationInProgress, - self.controller.update, request=self.request, id=inst_id, - body=body) - - def test_update_vim_connection_info_not_instantiated(self): - inst = objects.VnfInstanceV2( - # required fields - id=uuidutils.generate_uuid(), - vnfdId=uuidutils.generate_uuid(), - vnfProvider='provider', - vnfProductName='product name', - vnfSoftwareVersion='software version', - vnfdVersion='vnfd version', - instantiationState='NOT_INSTANTIATED' - ) - inst.create(self.context) - - body = { - "vimConnectionInfo": { - "vim1": { - "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", - "vimId": "c36428ef-3071-4b74-8d9a-f39c4dd30065", - "interfaceInfo": { - "endpoint": "http://localhost/identity/v3" - } - } - } - } - self.assertRaises(sol_ex.SolValidationError, - self.controller.update, request=self.request, id=inst.id, - body=body) - - @mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd') - def test_update_vnf_package_disabled(self, - mocked_get_vnf_package_info_vnfd): - inst = objects.VnfInstanceV2( - # required fields - id=uuidutils.generate_uuid(), - vnfdId=uuidutils.generate_uuid(), - vnfProvider='provider', - vnfProductName='product name', - vnfSoftwareVersion='software version', - vnfdVersion='vnfd version', - instantiationState='INSTANTIATED' - ) - inst.create(self.context) - - req_vnfd_id = uuidutils.generate_uuid() - pkg_info = objects.VnfPkgInfoV2( - id=uuidutils.generate_uuid(), - vnfdId=req_vnfd_id, - vnfProvider="provider_1", - vnfProductName="product_1", - vnfSoftwareVersion="software version", - vnfdVersion="vnfd version", - operationalState="DISABLED" - ) - - mocked_get_vnf_package_info_vnfd.return_value = pkg_info - - body = {"vnfdId": req_vnfd_id} - - self.assertRaises(sol_ex.VnfdIdNotEnabled, - self.controller.update, request=self.request, id=inst.id, - body=body) - - def test_update_not_exist_vnfc_info_id(self): - inst = objects.VnfInstanceV2( - # required fields - id=uuidutils.generate_uuid(), - vnfdId=uuidutils.generate_uuid(), - vnfProvider='provider', - vnfProductName='product name', - vnfSoftwareVersion='software version', - vnfdVersion='vnfd version', - instantiationState='INSTANTIATED' - ) - inst.instantiatedVnfInfo = objects.VnfInstanceV2_InstantiatedVnfInfo( - flavourId='small', - vnfState='STARTED', - vnfcInfo=[ - objects.VnfcInfoV2( - id="VDU1-vnfc_res_info_id_VDU1", - vduId="VDU1", - vnfcResourceInfoId="vnfc_res_info_id_VDU1", - vnfcState="STARTED" - ) - ] - ) - inst.create(self.context) - - body = { - "vnfcInfoModifications": [ - { - "id": "VDU2-vnfc_res_info_id_VDU2", - "vnfcConfigurableProperties": {"key": "value"} - } - ] - } - - self.assertRaises(sol_ex.SolValidationError, - self.controller.update, request=self.request, id=inst.id, - body=body) - - def test_change_ext_conn_not_instantiated(self): - inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - - self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated, - self.controller.change_ext_conn, request=self.request, id=inst_id, - body=_change_ext_conn_req_example) - - def test_change_ext_conn_lcmocc_in_progress(self): - inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.FAILED_TEMP) - - self.assertRaises(sol_ex.OtherOperationInProgress, - self.controller.change_ext_conn, request=self.request, id=inst_id, - body=_change_ext_conn_req_example) - - def test_heal_not_instantiated(self): - inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - body = {"cause": "Healing VNF instance"} - - self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated, - self.controller.heal, request=self.request, id=inst_id, - body=body) - - def test_heal_lcmocc_in_progress(self): - inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.FAILED_TEMP) - body = {"cause": "Healing VNF instance"} - - self.assertRaises(sol_ex.OtherOperationInProgress, - self.controller.heal, request=self.request, id=inst_id, - body=body) - - def _prepare_db_for_heal_param_check(self): - inst = objects.VnfInstanceV2( - # required fields - id=uuidutils.generate_uuid(), - vnfdId=uuidutils.generate_uuid(), - vnfProvider='provider', - vnfProductName='product name', - vnfSoftwareVersion='software version', - vnfdVersion='vnfd version', - instantiationState='INSTANTIATED' - ) - inst.instantiatedVnfInfo = objects.VnfInstanceV2_InstantiatedVnfInfo( - flavourId='small', - vnfState='STARTED', - vnfcInfo=[ - objects.VnfcInfoV2( - id="VDU2-vnfc_res_info_id_VDU2", - vduId="VDU2", - vnfcResourceInfoId="vnfc_res_info_id_VDU2", - vnfcState="STARTED" - ), - objects.VnfcInfoV2( - id="VDU1-vnfc_res_info_id_VDU1", - vduId="VDU1", - vnfcResourceInfoId="vnfc_res_info_id_VDU1", - vnfcState="STARTED" - ), - objects.VnfcInfoV2( - id="VDU1-vnfc_res_info_id_VDU2", - vduId="VDU1", - vnfcResourceInfoId="vnfc_res_info_id_VDU2", - vnfcState="STARTED" - ), - ] - ) - inst.create(self.context) - - return inst.id - - def test_heal_invalid_additional_params(self): - inst_id = self._prepare_db_for_heal_param_check() - body = { - "additionalParams": {"all": "string"} - } - - self.assertRaises(sol_ex.SolValidationError, - self.controller.heal, request=self.request, id=inst_id, - body=body) - - def test_heal_invalid_vnfcinstance_id(self): - inst_id = self._prepare_db_for_heal_param_check() - body = { - "vnfcInstanceId": [ - "VDU2-vnfc_res_info_id_VDU2", - "VDU2-vnfc_res_info_id_VDU1" - ] - } - - self.assertRaises(sol_ex.SolValidationError, - self.controller.heal, request=self.request, id=inst_id, - body=body) +# Copyright (C) 2021 Nippon Telegraph and Telephone Corporation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from datetime import datetime +from unittest import mock + +from oslo_utils import uuidutils + +from tacker import context +from tacker.sol_refactored.api import api_version +from tacker.sol_refactored.common import exceptions as sol_ex +from tacker.sol_refactored.common import lcm_op_occ_utils as lcmocc_utils +from tacker.sol_refactored.controller import vnflcm_v2 +from tacker.sol_refactored.nfvo import nfvo_client +from tacker.sol_refactored import objects +from tacker.sol_refactored.objects.v2 import fields +from tacker.tests.unit.db import base as db_base + + +_change_ext_conn_req_example = { + "extVirtualLinks": [ + { + "id": "id_ext_vl_1", + "resourceId": "res_id_ext_vl_1", + "extCps": [ + { + "cpdId": "VDU2_CP2", + "cpConfig": { + "VDU2_CP2_1": { + "linkPortId": "link_port_id_VDU2_CP2" + } + } + } + ], + "extLinkPorts": [ + { + "id": "link_port_id_VDU2_CP2", + "resourceHandle": { + "resourceId": "res_id_VDU2_CP2" + } + } + ] + } + ] +} + + +class TestVnflcmV2(db_base.SqlTestCase): + + def setUp(self): + super(TestVnflcmV2, self).setUp() + objects.register_all() + self.controller = vnflcm_v2.VnfLcmControllerV2() + self.context = context.get_admin_context() + self.context.api_version = api_version.APIVersion("2.0.0") + self.request = mock.Mock() + self.request.context = self.context + + def _set_inst_and_lcmocc(self, inst_state, op_state): + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=uuidutils.generate_uuid(), + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState=inst_state + ) + + req = {"flavourId": "simple"} # instantiate request + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=op_state, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.INSTANTIATE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req + ) + + return inst, lcmocc + + def _create_inst_and_lcmocc(self, inst_state, op_state): + inst, lcmocc = self._set_inst_and_lcmocc(inst_state, op_state) + + inst.create(self.context) + lcmocc.create(self.context) + + return inst.id, lcmocc.id + + @mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd') + def test_create_pkg_disabled(self, mocked_get_vnf_package_info_vnfd): + vnfd_id = uuidutils.generate_uuid() + pkg_info = objects.VnfPkgInfoV2( + id=uuidutils.generate_uuid(), + vnfdId=vnfd_id, + vnfProvider="provider", + vnfProductName="product", + vnfSoftwareVersion="software version", + vnfdVersion="vnfd version", + operationalState="DISABLED" + ) + mocked_get_vnf_package_info_vnfd.return_value = pkg_info + body = { + "vnfdId": vnfd_id, + "vnfInstanceName": "test", + "vnfInstanceDescription": "test" + } + self.assertRaises(sol_ex.VnfdIdNotEnabled, + self.controller.create, request=self.request, body=body) + + @mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd') + def test_change_vnfpkg_pkg_disabled(self, + mocked_get_vnf_package_info_vnfd): + vnfd_id = uuidutils.generate_uuid() + inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + body = {"vnfdId": vnfd_id} + pkg_info = objects.VnfPkgInfoV2( + id=uuidutils.generate_uuid(), + vnfdId=vnfd_id, + vnfProvider="provider", + vnfProductName="product", + vnfSoftwareVersion="software version", + vnfdVersion="vnfd version", + operationalState="DISABLED" + ) + mocked_get_vnf_package_info_vnfd.return_value = pkg_info + self.assertRaises(sol_ex.VnfdIdNotEnabled, + self.controller.change_vnfpkg, request=self.request, id=inst_id, + body=body) + + @mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd') + def test_change_vnfpkg_pkg_no_additional_params( + self, mocked_get_vnf_package_info_vnfd): + vnfd_id = uuidutils.generate_uuid() + inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + body = {"vnfdId": vnfd_id} + pkg_info = objects.VnfPkgInfoV2( + id=uuidutils.generate_uuid(), + vnfdId=vnfd_id, + vnfProvider="provider", + vnfProductName="product", + vnfSoftwareVersion="software version", + vnfdVersion="vnfd version", + operationalState="ENABLED" + ) + mocked_get_vnf_package_info_vnfd.return_value = pkg_info + self.assertRaises(sol_ex.SolValidationError, + self.controller.change_vnfpkg, request=self.request, id=inst_id, + body=body) + + @mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd') + def test_change_vnfpkg_pkg_upgrade_type( + self, mocked_get_vnf_package_info_vnfd): + vnfd_id = uuidutils.generate_uuid() + inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + body = { + "vnfdId": vnfd_id, + "additionalParams": { + "upgrade_type": "BuleGreen" + } + } + pkg_info = objects.VnfPkgInfoV2( + id=uuidutils.generate_uuid(), + vnfdId=vnfd_id, + vnfProvider="provider", + vnfProductName="product", + vnfSoftwareVersion="software version", + vnfdVersion="vnfd version", + operationalState="ENABLED" + ) + mocked_get_vnf_package_info_vnfd.return_value = pkg_info + self.assertRaises(sol_ex.NotSupportUpgradeType, + self.controller.change_vnfpkg, request=self.request, id=inst_id, + body=body) + + def test_delete_instantiated(self): + inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + + self.assertRaises(sol_ex.VnfInstanceIsInstantiated, + self.controller.delete, request=self.request, id=inst_id) + + def test_delete_lcmocc_in_progress(self): + inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', + fields.LcmOperationStateType.FAILED_TEMP) + + self.assertRaises(sol_ex.OtherOperationInProgress, + self.controller.delete, request=self.request, id=inst_id) + + def test_instantiate_instantiated(self): + inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + body = {"flavourId": "small"} + + self.assertRaises(sol_ex.VnfInstanceIsInstantiated, + self.controller.instantiate, request=self.request, id=inst_id, + body=body) + + def test_instantiate_lcmocc_in_progress(self): + inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', + fields.LcmOperationStateType.FAILED_TEMP) + body = {"flavourId": "small"} + + self.assertRaises(sol_ex.OtherOperationInProgress, + self.controller.instantiate, request=self.request, id=inst_id, + body=body) + + def test_change_vnfpkg_not_instantiated(self): + vnfd_id = uuidutils.generate_uuid() + inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + body = {"vnfdId": vnfd_id} + + self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated, + self.controller.change_vnfpkg, request=self.request, id=inst_id, + body=body) + + def test_change_vnfpkg_lcmocc_in_progress(self): + vnfd_id = uuidutils.generate_uuid() + inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.FAILED_TEMP) + body = {"vnfdId": vnfd_id} + + self.assertRaises(sol_ex.OtherOperationInProgress, + self.controller.change_vnfpkg, request=self.request, id=inst_id, + body=body) + + def test_terminate_not_instantiated(self): + inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + body = {"terminationType": "FORCEFUL"} + + self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated, + self.controller.terminate, request=self.request, id=inst_id, + body=body) + + def test_terminate_lcmocc_in_progress(self): + inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.FAILED_TEMP) + body = {"terminationType": "FORCEFUL"} + + self.assertRaises(sol_ex.OtherOperationInProgress, + self.controller.terminate, request=self.request, id=inst_id, + body=body) + + def test_invalid_subscripion(self): + body = { + "callbackUri": "http://127.0.0.1:6789/notification", + "authentication": { + "authType": ["BASIC"] + } + } + ex = self.assertRaises(sol_ex.InvalidSubscription, + self.controller.subscription_create, request=self.request, + body=body) + self.assertEqual("ParmasBasic must be specified.", ex.detail) + + body = { + "callbackUri": "http://127.0.0.1:6789/notification", + "authentication": { + "authType": ["OAUTH2_CLIENT_CREDENTIALS"] + } + } + ex = self.assertRaises(sol_ex.InvalidSubscription, + self.controller.subscription_create, request=self.request, + body=body) + self.assertEqual("paramsOauth2ClientCredentials must be specified.", + ex.detail) + + body = { + "callbackUri": "http://127.0.0.1:6789/notification", + "authentication": { + "authType": ["TLS_CERT"] + } + } + ex = self.assertRaises(sol_ex.InvalidSubscription, + self.controller.subscription_create, request=self.request, + body=body) + self.assertEqual("'TLS_CERT' is not supported at the moment.", + ex.detail) + + def test_retry_not_failed_temp(self): + _, lcmocc_id = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + + self.assertRaises(sol_ex.LcmOpOccNotFailedTemp, + self.controller.lcm_op_occ_retry, request=self.request, + id=lcmocc_id) + + def test_rollback_not_failed_temp(self): + _, lcmocc_id = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + + self.assertRaises(sol_ex.LcmOpOccNotFailedTemp, + self.controller.lcm_op_occ_rollback, request=self.request, + id=lcmocc_id) + + def test_fail_not_failed_temp(self): + _, lcmocc_id = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + + self.assertRaises(sol_ex.LcmOpOccNotFailedTemp, + self.controller.lcm_op_occ_fail, request=self.request, + id=lcmocc_id) + + def _prepare_db_for_fail(self): + inst, lcmocc = self._set_inst_and_lcmocc('NOT_INSTANTIATED', + fields.LcmOperationStateType.FAILED_TEMP) + + inst.create(self.context) + lcmocc.create(self.context) + + grant_req = objects.GrantRequestV1( + # required fields + vnfInstanceId=lcmocc.vnfInstanceId, + vnfLcmOpOccId=lcmocc.id, + vnfdId=uuidutils.generate_uuid(), + operation=lcmocc.operation, + isAutomaticInvocation=lcmocc.isAutomaticInvocation + ) + + grant = objects.GrantV1( + # required fields + id=uuidutils.generate_uuid(), + vnfInstanceId=lcmocc.vnfInstanceId, + vnfLcmOpOccId=lcmocc.id + ) + + grant_req.create(self.context) + grant.create(self.context) + lcmocc.grantId = grant.id + lcmocc.update(self.context) + + return lcmocc + + @mock.patch.object(nfvo_client.NfvoClient, 'send_lcmocc_notification') + def test_lcm_op_occ_fail(self, mocked_send_lcmocc_notification): + # prepare + lcmocc = self._prepare_db_for_fail() + + op_state = [] + + def _store_state(context, lcmocc, inst, endpoint): + op_state.append(lcmocc.operationState) + + mocked_send_lcmocc_notification.side_effect = _store_state + + # run lcm_op_occ_fail + self.controller.lcm_op_occ_fail(self.request, lcmocc.id) + + # check operationstate + self.assertEqual(1, mocked_send_lcmocc_notification.call_count) + self.assertEqual(fields.LcmOperationStateType.FAILED, op_state[0]) + + # check grant_req and grant are deleted + self.assertRaises(sol_ex.GrantRequestOrGrantNotFound, + lcmocc_utils.get_grant_req_and_grant, self.context, lcmocc) + + def test_scale_not_instantiated(self): + inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + body = {"aspectId": "aspect_1", "type": "SCALE_OUT"} + + self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated, + self.controller.scale, request=self.request, id=inst_id, + body=body) + + def test_scale_lcmocc_in_progress(self): + inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.FAILED_TEMP) + body = {"aspectId": "aspect_1", "type": "SCALE_OUT"} + + self.assertRaises(sol_ex.OtherOperationInProgress, + self.controller.scale, request=self.request, id=inst_id, + body=body) + + def _prepare_db_for_scale_param_check(self, scale_status, + max_scale_levels): + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=uuidutils.generate_uuid(), + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED' + ) + inst.instantiatedVnfInfo = objects.VnfInstanceV2_InstantiatedVnfInfo( + flavourId='small', + vnfState='STARTED', + scaleStatus=scale_status, + maxScaleLevels=max_scale_levels + ) + inst.create(self.context) + + return inst.id + + def test_scale_invalid_aspect_id(self): + scale_status = [ + objects.ScaleInfoV2( + aspectId="aspect_2", + scaleLevel=0 + ) + ] + max_scale_levels = [ + objects.ScaleInfoV2( + aspectId="aspect_2", + scaleLevel=3 + ) + ] + inst_id = self._prepare_db_for_scale_param_check(scale_status, + max_scale_levels) + body = {"aspectId": "aspect_1", "type": "SCALE_OUT"} + + self.assertRaises(sol_ex.InvalidScaleAspectId, + self.controller.scale, request=self.request, id=inst_id, + body=body) + + def test_scale_invalid_number_of_steps(self): + scale_status = [ + objects.ScaleInfoV2( + aspectId="aspect_1", + scaleLevel=1 + ) + ] + max_scale_levels = [ + objects.ScaleInfoV2( + aspectId="aspect_1", + scaleLevel=3 + ) + ] + inst_id = self._prepare_db_for_scale_param_check(scale_status, + max_scale_levels) + body = {"aspectId": "aspect_1", "type": "SCALE_OUT", + "numberOfSteps": 3} + + self.assertRaises(sol_ex.InvalidScaleNumberOfSteps, + self.controller.scale, request=self.request, id=inst_id, + body=body) + + body = {"aspectId": "aspect_1", "type": "SCALE_IN", + "numberOfSteps": 2} + + self.assertRaises(sol_ex.InvalidScaleNumberOfSteps, + self.controller.scale, request=self.request, id=inst_id, + body=body) + + def test_update_lcmocc_in_progress(self): + inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.FAILED_TEMP) + + body = {"vnfInstanceDescription": "example1"} + + self.assertRaises(sol_ex.OtherOperationInProgress, + self.controller.update, request=self.request, id=inst_id, + body=body) + + def test_update_vim_connection_info_not_instantiated(self): + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=uuidutils.generate_uuid(), + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='NOT_INSTANTIATED' + ) + inst.create(self.context) + + body = { + "vimConnectionInfo": { + "vim1": { + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", + "vimId": "c36428ef-3071-4b74-8d9a-f39c4dd30065", + "interfaceInfo": { + "endpoint": "http://localhost/identity/v3" + } + } + } + } + self.assertRaises(sol_ex.SolValidationError, + self.controller.update, request=self.request, id=inst.id, + body=body) + + @mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd') + def test_update_vnf_package_disabled(self, + mocked_get_vnf_package_info_vnfd): + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=uuidutils.generate_uuid(), + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED' + ) + inst.create(self.context) + + req_vnfd_id = uuidutils.generate_uuid() + pkg_info = objects.VnfPkgInfoV2( + id=uuidutils.generate_uuid(), + vnfdId=req_vnfd_id, + vnfProvider="provider_1", + vnfProductName="product_1", + vnfSoftwareVersion="software version", + vnfdVersion="vnfd version", + operationalState="DISABLED" + ) + + mocked_get_vnf_package_info_vnfd.return_value = pkg_info + + body = {"vnfdId": req_vnfd_id} + + self.assertRaises(sol_ex.VnfdIdNotEnabled, + self.controller.update, request=self.request, id=inst.id, + body=body) + + def test_update_not_exist_vnfc_info_id(self): + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=uuidutils.generate_uuid(), + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED' + ) + inst.instantiatedVnfInfo = objects.VnfInstanceV2_InstantiatedVnfInfo( + flavourId='small', + vnfState='STARTED', + vnfcInfo=[ + objects.VnfcInfoV2( + id="VDU1-vnfc_res_info_id_VDU1", + vduId="VDU1", + vnfcResourceInfoId="vnfc_res_info_id_VDU1", + vnfcState="STARTED" + ) + ] + ) + inst.create(self.context) + + body = { + "vnfcInfoModifications": [ + { + "id": "VDU2-vnfc_res_info_id_VDU2", + "vnfcConfigurableProperties": {"key": "value"} + } + ] + } + + self.assertRaises(sol_ex.SolValidationError, + self.controller.update, request=self.request, id=inst.id, + body=body) + + def test_change_ext_conn_not_instantiated(self): + inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + + self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated, + self.controller.change_ext_conn, request=self.request, id=inst_id, + body=_change_ext_conn_req_example) + + def test_change_ext_conn_lcmocc_in_progress(self): + inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.FAILED_TEMP) + + self.assertRaises(sol_ex.OtherOperationInProgress, + self.controller.change_ext_conn, request=self.request, id=inst_id, + body=_change_ext_conn_req_example) + + def test_heal_not_instantiated(self): + inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + body = {"cause": "Healing VNF instance"} + + self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated, + self.controller.heal, request=self.request, id=inst_id, + body=body) + + def test_heal_lcmocc_in_progress(self): + inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.FAILED_TEMP) + body = {"cause": "Healing VNF instance"} + + self.assertRaises(sol_ex.OtherOperationInProgress, + self.controller.heal, request=self.request, id=inst_id, + body=body) + + def _prepare_db_for_heal_param_check(self): + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=uuidutils.generate_uuid(), + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED' + ) + inst.instantiatedVnfInfo = objects.VnfInstanceV2_InstantiatedVnfInfo( + flavourId='small', + vnfState='STARTED', + vnfcInfo=[ + objects.VnfcInfoV2( + id="VDU2-vnfc_res_info_id_VDU2", + vduId="VDU2", + vnfcResourceInfoId="vnfc_res_info_id_VDU2", + vnfcState="STARTED" + ), + objects.VnfcInfoV2( + id="VDU1-vnfc_res_info_id_VDU1", + vduId="VDU1", + vnfcResourceInfoId="vnfc_res_info_id_VDU1", + vnfcState="STARTED" + ), + objects.VnfcInfoV2( + id="VDU1-vnfc_res_info_id_VDU2", + vduId="VDU1", + vnfcResourceInfoId="vnfc_res_info_id_VDU2", + vnfcState="STARTED" + ), + ] + ) + inst.create(self.context) + + return inst.id + + def test_heal_invalid_additional_params(self): + inst_id = self._prepare_db_for_heal_param_check() + body = { + "additionalParams": {"all": "string"} + } + + self.assertRaises(sol_ex.SolValidationError, + self.controller.heal, request=self.request, id=inst_id, + body=body) + + def test_heal_invalid_vnfcinstance_id(self): + inst_id = self._prepare_db_for_heal_param_check() + body = { + "vnfcInstanceId": [ + "VDU2-vnfc_res_info_id_VDU2", + "VDU2-vnfc_res_info_id_VDU1" + ] + } + + self.assertRaises(sol_ex.SolValidationError, + self.controller.heal, request=self.request, id=inst_id, + body=body) diff --git a/tacker/tests/unit/sol_refactored/infra_drivers/openstack/test_openstack.py b/tacker/tests/unit/sol_refactored/infra_drivers/openstack/test_openstack.py index abfb89569..9cd918153 100644 --- a/tacker/tests/unit/sol_refactored/infra_drivers/openstack/test_openstack.py +++ b/tacker/tests/unit/sol_refactored/infra_drivers/openstack/test_openstack.py @@ -15,10 +15,18 @@ import copy import os +import requests +import subprocess + +from datetime import datetime +from oslo_utils import uuidutils +from unittest import mock from tacker import context from tacker.sol_refactored.common import vnfd_utils +from tacker.sol_refactored.infra_drivers.openstack import heat_utils from tacker.sol_refactored.infra_drivers.openstack import openstack +from tacker.sol_refactored.nfvo import glance_utils from tacker.sol_refactored import objects from tacker.sol_refactored.objects.v2 import fields from tacker.tests import base @@ -35,6 +43,21 @@ _vim_connection_info_example = { # "accessInfo": omitted } +_vim_connection_info_for_change_vnfpkg = { + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", + "vimId": uuidutils.generate_uuid(), + "interfaceInfo": {"endpoint": "http://127.0.0.1/identity"}, + "accessInfo": { + "username": "nfv_user", + "region": "RegionOne", + "password": "devstack", + "project": "nfv", + "projectDomain": "Default", + "userDomain": "Default" + } + +} + _instantiate_req_example = { "flavourId": SAMPLE_FLAVOUR_ID, "extVirtualLinks": [ @@ -219,6 +242,54 @@ _change_ext_conn_req_example = { ] } +_change_vnfpkg_example = { + "vnfdId": uuidutils.generate_uuid(), + "additionalParams": { + "upgrade_type": "RollingUpdate", + "lcm-operation-coordinate-old-vnf": "Scripts/coordinate_old_vnf.py", + "lcm-operation-coordinate-old-vnf-class": "CoordinateOldVnf", + "lcm-operation-coordinate-new-vnf": "Scripts/coordinate_new_vnf.py", + "lcm-operation-coordinate-new-vnf-class": "CoordinateNewVnf", + "vdu_params": [{ + "vdu_id": "VDU1", + "old_vnfc_param": { + "cp_name": "CP1", + "username": "ubuntu", + "password": "ubuntu" + }, + "new_vnfc_param": { + "cp_name": "CP1", + "username": "ubuntu", + "password": "ubuntu" + }, + }] + } +} + +_change_vnfpkg_example_2 = { + "vnfdId": uuidutils.generate_uuid(), + "additionalParams": { + "upgrade_type": "RollingUpdate", + "lcm-operation-coordinate-old-vnf": "Scripts/coordinate_old_vnf.py", + "lcm-operation-coordinate-old-vnf-class": "CoordinateOldVnf", + "lcm-operation-coordinate-new-vnf": "Scripts/coordinate_new_vnf.py", + "lcm-operation-coordinate-new-vnf-class": "CoordinateNewVnf", + "vdu_params": [{ + "vdu_id": "VDU2", + "old_vnfc_param": { + "cp_name": "CP1", + "username": "ubuntu", + "password": "ubuntu" + }, + "new_vnfc_param": { + "cp_name": "CP1", + "username": "ubuntu", + "password": "ubuntu" + }, + }] + } +} + # heat resources examples # NOTE: # - following attributes which are not related to tests are omitted. @@ -234,6 +305,7 @@ _href = "".join((_url, _stack_id)) _stack_id_VDU1_scale = ( "vnf-768c24d2-2ea6-4225-b1c7-79e42abfbde6-VDU1_scale_group-dv4kv7qtcwhw/" "53ee92b6-8193-4df5-90f7-2738e61fba2c") + _href_VDU1_scale = "".join((_url, _stack_id_VDU1_scale)) _stack_id_VDU1_1 = ( @@ -785,6 +857,375 @@ _heat_reses_example = ( _heat_reses_example_change_ext_conn = ( _heat_reses_example_base + _heat_reses_example_cps_after) +# change vnfpkg before inst info +_inst_info_example = { + "flavourId": "simple", + "vnfState": "STARTED", + "extCpInfo": [ + { + "id": "90561570-264c-4472-b84f-1fff98513475", + "cpdId": "VDU2_CP1", + "cpConfigId": "VDU2_CP1_1", + # "cpProtocolInfo": omitted + "extLinkPortId": "ac27c99b-73c8-4e91-b730-90deade72af4", + "associatedVnfcCpId": "be955786-a0c7-4b61-8cd8-9bb8bcb1c6e3" + }, + { + "id": "f9f4b4b2-50e2-4c73-b89b-e0665e65ffbe", + "cpdId": "VDU2_CP2", + "cpConfigId": "VDU2_CP2_1", + # "cpProtocolInfo": omitted + "extLinkPortId": "12567a13-9fbd-4803-ad9f-d94ced266cd8", + "associatedVnfcCpId": "c54fa2fc-185a-49a7-bb89-f30f7c3be6a4" + }, + { + "id": "05474d0b-a1f7-4be5-b57e-ef6873e1f3b6", + "cpdId": "VDU1_CP2", + "cpConfigId": "VDU1_CP2_1", + # "cpProtocolInfo": omitted + "extLinkPortId": "aa6646da-2e59-4de9-9b72-c62e7c4d9142", + "associatedVnfcCpId": "fdbb289f-87c8-40d0-bf06-da07b41ba124" + }, + { + "id": "42ede9a6-c2b8-4c0d-a337-26342ffb236c", + "cpdId": "VDU1_CP1", + "cpConfigId": "VDU1_CP1_1", + # "cpProtocolInfo": omitted + "extLinkPortId": "efd0eb4e-4e55-4ac8-8b9b-403ec79faf2d", + "associatedVnfcCpId": "235f920c-8b49-4894-9c36-73f5a3b9f74d" + }, + { + "id": "4f0ab1ad-b7de-482f-a69b-1093c71d2ceb", + "cpdId": "VDU1_CP2", + "cpConfigId": "VDU1_CP2_1", + # "cpProtocolInfo": omitted + "extLinkPortId": "a6c4c043-e082-4873-a871-02467af66224", + "associatedVnfcCpId": "e23d970d-9ea9-4c26-9d67-8f244383ea3c" + }, + { + "id": "7a7fa30f-a303-4856-bc8b-b836cb682892", + "cpdId": "VDU1_CP1", + "cpConfigId": "VDU1_CP1_1", + # "cpProtocolInfo": omitted + "extLinkPortId": "f58df4d9-08ff-41b7-ab73-95ebfb8103c4", + "associatedVnfcCpId": "259c5895-7be6-4bed-8a94-221c41b3d08f" + } + ], + "extVirtualLinkInfo": [ + { + "id": "137bdf0b-835c-43f0-b0d2-5c002599118a", + "resourceHandle": { + "resourceId": "6f97f400-2861-482a-ba78-65b652aaf8fc" + }, + "extLinkPorts": [ + { + "id": "ac27c99b-73c8-4e91-b730-90deade72af4", + "resourceHandle": { + # "vimConnectionId": omitted + "resourceId": "res_id_VDU2_CP1", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "90561570-264c-4472-b84f-1fff98513475" + }, + { + "id": "efd0eb4e-4e55-4ac8-8b9b-403ec79faf2d", + "resourceHandle": { + # "vimConnectionId": omitted + "resourceId": "res_id_VDU1_1_CP1", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "42ede9a6-c2b8-4c0d-a337-26342ffb236c" + }, + { + "id": "f58df4d9-08ff-41b7-ab73-95ebfb8103c4", + "resourceHandle": { + # "vimConnectionId": omitted + "resourceId": "res_id_VDU1_2_CP1", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "7a7fa30f-a303-4856-bc8b-b836cb682892" + } + ], + # "currentVnfExtCpData": omitted + }, + { + "id": "d8141a5a-6b6e-4dab-9bf5-158f23a617d7", + "resourceHandle": { + "resourceId": "02bc95e0-3d43-4d11-83b8-f7b15d8661a9" + }, + "extLinkPorts": [ + { + "id": "12567a13-9fbd-4803-ad9f-d94ced266cd8", + "resourceHandle": { + # "vimConnectionId": omitted + "resourceId": "res_id_VDU2_CP2", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "f9f4b4b2-50e2-4c73-b89b-e0665e65ffbe" + }, + { + "id": "aa6646da-2e59-4de9-9b72-c62e7c4d9142", + "resourceHandle": { + # "vimConnectionId": omitted + "resourceId": "res_id_VDU1_1_CP2", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "05474d0b-a1f7-4be5-b57e-ef6873e1f3b6" + }, + { + "id": "a6c4c043-e082-4873-a871-02467af66224", + "resourceHandle": { + # "vimConnectionId": omitted + "resourceId": "res_id_VDU1_2_CP2", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "4f0ab1ad-b7de-482f-a69b-1093c71d2ceb" + } + ], + # "currentVnfExtCpData": omitted + } + ], + "extManagedVirtualLinkInfo": [ + { + "id": "bad53df7-f1fa-482d-91b1-caec382aeec2", + "vnfVirtualLinkDescId": "internalVL1", + "networkResource": { + "resourceId": "56730009-169c-4f96-8141-828acf1ee067" + }, + "vnfLinkPorts": [ + { + "id": "74f387fe-6355-4af3-adc7-cdb507d5fa5f", + "resourceHandle": { + # "vimConnectionId": omitted + "resourceId": "res_id_VDU2_CP3", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "5b0b336b-c207-4fa8-8b41-a5ad87d85cd0", + "cpInstanceType": "VNFC_CP" + }, + { + "id": "4064ec55-b862-4527-a911-8752d3aa765a", + "resourceHandle": { + # "vimConnectionId": omitted + "resourceId": "res_id_VDU1_1_CP3", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "b0732fb7-a42a-4077-aebc-d22b67b64f13", + "cpInstanceType": "VNFC_CP" + }, + { + "id": "f3c9e62d-0f31-4a36-bd99-eecd8def0871", + "resourceHandle": { + # "vimConnectionId": omitted + "resourceId": "res_id_VDU1_2_CP3", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "9c65f67e-feb2-447c-b0e7-a4f896185b4f", + "cpInstanceType": "VNFC_CP" + } + ] + } + ], + "vnfcResourceInfo": [ + { + "id": "res_id_VDU1_2", + "vduId": "VDU1", + "computeResource": { + "vimConnectionId": "vim_id_1", + "resourceId": "res_id_VDU1_2", + "vimLevelResourceType": "OS::Nova::Server" + }, + "storageResourceIds": [ + "res_id_VirtualStorage_2" + ], + "vnfcCpInfo": [ + { + "id": "VDU1_CP1-res_id_VDU1_2", + "cpdId": "VDU1_CP1", + "vnfExtCpId": "cp-res_id_VDU1_CP1_2" + }, + { + "id": "VDU1_CP2-res_id_VDU1_2", + "cpdId": "VDU1_CP2", + "vnfExtCpId": "cp-res_id_VDU1_CP2_2" + }, + { + "id": "VDU1_CP3-res_id_VDU1_2", + "cpdId": "VDU1_CP3", + "vnfLinkPortId": "res_id_VDU1_CP3_2" + }, + { + "id": "VDU1_CP4-res_id_VDU1_2", + "cpdId": "VDU1_CP4", + "vnfLinkPortId": "res_id_VDU1_CP4_2" + }, + { + "id": "VDU1_CP5-res_id_VDU1_2", + "cpdId": "VDU1_CP5", + "vnfLinkPortId": "res_id_VDU1_CP5_2" + } + ], + "metadata": { + "creation_time": "2021-12-10T01:03:49Z", + "stack_id": "vnf-d8962b72-6dac-4eb5-a8c4-8c7a2abaefb7-" + "VDU1_scale_group-2zmsxtwtsj7n-" + "fkwryhyv6qbr-qoemdwxw7o5c/" + "d7aeba20-1b00-4bff-b050-6b42a262c84d", + "parent_resource_name": "fkwryhyv6qbr" + } + }, + { + "id": "res_id_VDU2_1", + "vduId": "VDU2", + "computeResource": { + "vimConnectionId": "vim_id_1", + "resourceId": "res_id_VDU2_1", + "vimLevelResourceType": "OS::Nova::Server" + }, + "storageResourceIds": [ + "res_id_VirtualStorage_2" + ], + "vnfcCpInfo": [ + { + "id": "VDU2_CP1-res_id_VDU2_1", + "cpdId": "VDU2_CP1", + "vnfExtCpId": "cp-res_id_VDU2_CP1_1" + }, + { + "id": "VDU2_CP2-res_id_VDU2_1", + "cpdId": "VDU2_CP2", + "vnfExtCpId": "cp-res_id_VDU2_CP2_1" + }, + { + "id": "VDU2_CP3-res_id_VDU2_1", + "cpdId": "VDU2_CP3", + "vnfLinkPortId": "res_id_VDU2_CP3_1" + }, + { + "id": "VDU2_CP4-res_id_VDU2_1", + "cpdId": "VDU2_CP4", + "vnfLinkPortId": "res_id_VDU2_CP4_1" + }, + { + "id": "VDU2_CP5-res_id_VDU2_1", + "cpdId": "VDU2_CP5", + "vnfLinkPortId": "res_id_VDU2_CP5_1" + } + ], + "metadata": { + "creation_time": "2021-12-10T00:41:43Z", + "stack_id": 'vnf-d7aeba20-1b00-4bff-b050-6b42a262c84d/' + 'd7aeba20-1b00-4bff-b050-6b42a262c84d' + } + }, + ], + "vnfVirtualLinkResourceInfo": [ + { + "id": "18bd0111-d5e1-4aa3-b2d8-5b89833c6351", + "vnfVirtualLinkDescId": "internalVL3", + "networkResource": { + # "vimConnectionId": omitted + "resourceId": "res_id_internalVL3", + "vimLevelResourceType": "OS::Neutron::Net" + }, + "vnfLinkPorts": [ + { + "id": "4dd7cadd-b9a1-484f-b2f2-1ff50ef0d90f", + "resourceHandle": { + # "vimConnectionId": omitted + "resourceId": "res_id_VDU2_CP5", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "39a7d895-3b19-4330-b6ec-ae3557ea9c01", + "cpInstanceType": "VNFC_CP" + }, + { + "id": "ace663cd-431b-402a-b2ae-d0824c996edb", + "resourceHandle": { + # "vimConnectionId": omitted + "resourceId": "res_id_VDU1_1_CP5", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "33194d65-ecd6-48d9-8ef7-c15ce9fef46c", + "cpInstanceType": "VNFC_CP" + }, + { + "id": "e0f98917-70ff-4f79-8747-9d7fc22827a4", + "resourceHandle": { + # "vimConnectionId": omitted + "resourceId": "res_id_VDU1_2_CP5", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "c9112298-61eb-4bba-b285-ed3419593b1b", + "cpInstanceType": "VNFC_CP" + } + ] + }, + { + "id": "047aa313-b591-4529-aa98-cb8ce2b82e28", + "vnfVirtualLinkDescId": "internalVL2", + "networkResource": { + # "vimConnectionId": omitted + "resourceId": "res_id_internalVL2", + "vimLevelResourceType": "OS::Neutron::Net" + }, + "vnfLinkPorts": [ + { + "id": "8e01813f-35fc-4a35-8f64-0da08a45ea21", + "resourceHandle": { + # "vimConnectionId": omitted + "resourceId": "res_id_VDU2_CP4", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "2cb2b3a8-a7a0-41da-b3b8-4b82f576b090", + "cpInstanceType": "VNFC_CP" + }, + { + "id": "1666a0f7-6a34-474e-87a2-07fb0c30ecdb", + "resourceHandle": { + # "vimConnectionId": omitted + "resourceId": "res_id_VDU1_1_CP4", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "a49f8fb8-6fd9-4e9f-a6dd-0d268e51c83c", + "cpInstanceType": "VNFC_CP" + }, + { + "id": "bce3159b-caca-45b7-8bb7-88015e951e56", + "resourceHandle": { + # "vimConnectionId": omitted + "resourceId": "res_id_VDU1_2_CP4", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "0716ac20-612a-4ac2-8c87-d83be31dd4b5", + "cpInstanceType": "VNFC_CP" + } + ] + } + ], + "virtualStorageResourceInfo": [ + { + "id": "2135b13c-e630-4700-8f8d-85b6e48f7871", + "virtualStorageDescId": "VirtualStorage", + "storageResource": { + # "vimConnectionId": omitted + "resourceId": "res_id_VirtualStorage_1", + "vimLevelResourceType": "OS::Cinder::Volume" + } + }, + { + "id": "739f7012-7973-485b-b34f-b006bc336150", + "virtualStorageDescId": "VirtualStorage", + "storageResource": { + # "vimConnectionId": omitted + "resourceId": "res_id_VirtualStorage_2", + "vimLevelResourceType": "OS::Cinder::Volume" + } + } + ], + # "vnfcInfo": omitted +} + # expected results (other than change_ext_conn) _expected_inst_info = { "flavourId": "simple", @@ -1785,6 +2226,381 @@ _expected_inst_info_change_ext_conn = { "vnfcInfo": _expected_inst_info["vnfcInfo"] } +mock_resource = { + 'resources': [{ + 'updated_time': '2021-12-27T02:53:29Z', + 'creation_time': '2021-12-27T02:53:29Z', + 'logical_resource_id': 'VDU1', + 'resource_name': 'VDU1', + 'physical_resource_id': 'e79ebeaf-1b26-4ff9-9895-f4c78a8a39a6', + 'resource_status': 'CREATE_COMPLETE', + 'resource_status_reason': 'state changed', + 'resource_type': 'OS::Nova::Server', + 'required_by': [], + 'parent_resource': 'fkwryhyv6qbr' + }, { + 'updated_time': '2021-12-27T03:16:02Z', + 'creation_time': '2021-12-27T02:53:27Z', + 'logical_resource_id': 'fkwryhyv6qbr', + 'resource_name': 'fkwryhyv6qbr', + 'physical_resource_id': 'd7aeba20-1b00-4bff-b050-6b42a262c84d', + 'resource_status': 'UPDATE_COMPLETE', + 'resource_status_reason': 'state changed', + 'resource_type': 'VDU1.yaml', + 'required_by': [], + 'parent_resource': 'VDU1_scale_group' + }, { + 'updated_time': '2021-12-27T03:16:01Z', + 'creation_time': '2021-12-27T02:53:20Z', + 'logical_resource_id': 'VDU1_scale_group', + 'resource_name': 'VDU1_scale_group', + 'physical_resource_id': '53ba8388-287d-411e-93c9-bd27cec8d0ec', + 'resource_status': 'UPDATE_COMPLETE', + 'resource_status_reason': 'state changed', + 'resource_type': 'OS::Heat::AutoScalingGroup', + 'required_by': ['VDU1_scale_in', 'VDU1_scale_out'] + }] +} + +mock_resource_template = { + 'heat_template_version': '2015-04-30', 'resources': { + 'fkwryhyv6qbr': {'type': 'VDU1.yaml', 'properties': { + 'flavor': 'm1.tiny', + 'image': 'cirros-0.5.2-x86_64-disk', + 'net1': '9b243768-1193-414b-b7c5-b56dfa765da4', + 'net2': '094e43b4-056c-49ce-8203-c7cd955003a6', + 'net3': '31451e60-ef7b-42f2-a4c7-2ca67d6c5caf', + 'net4': 'dfc1c440-50b0-442f-bcd4-bd090b3272a5', + 'net5': '2b2ecd2d-1f53-4b29-9c9d-7855c2fee7e3', + 'subnet': '1368ba79-5710-4ef6-b481-829fa22711c7'}}}, + 'outputs': {'refs_map': {'value': {'fkwryhyv6qbr': { + 'get_resource': 'fkwryhyv6qbr'}}}}} + +mock_resource_template_2 = { + 'heat_template_version': '2015-04-30', + 'description': 'Simple Base HOT for Sample VNF', + 'parameters': {'nfv': {'type': 'json'}}, + 'resources': {'VDU2': { + 'type': 'OS::Nova::Server', + 'properties': { + 'flavor': {'get_param': [ + 'nfv', 'VDU', 'VDU2', 'computeFlavourId']}, + 'image': {'get_param': [ + 'nfv', 'VDU', 'VDU2', 'vcImageId']}, + 'networks': [ + {'port': {'get_resource': 'VDU2_CP1'}}, + {'port': {'get_resource': 'VDU2_CP2'}}, + {'port': {'get_resource': 'VDU2_CP3'}}, + {'port': {'get_resource': 'VDU2_CP4'}}, + {'port': {'get_resource': 'VDU2_CP5'}}], + 'scheduler_hints': { + 'group': { + 'get_resource': 'nfvi_node_affinity'}}}} + } +} + +mock_resource_template_3 = { + 'heat_template_version': '2015-04-30', 'resources': {'VDU2': { + 'type': 'VDU2.yaml', + 'properties': {'flavor': 'm1.tiny', + 'image': 'cirros-0.5.2-x86_64-disk', + 'net1': '9b243768-1193-414b-b7c5-b56dfa765da4', + 'net2': '094e43b4-056c-49ce-8203-c7cd955003a6', + 'net3': '31451e60-ef7b-42f2-a4c7-2ca67d6c5caf', + 'net4': 'dfc1c440-50b0-442f-bcd4-bd090b3272a5', + 'net5': '2b2ecd2d-1f53-4b29-9c9d-7855c2fee7e3', + 'subnet': '1368ba79-5710-4ef6-b481-829fa22711c7', + 'block_device_mapping_v2': ''}}}, + 'outputs': {'refs_map': {'value': {'VDU2': {'get_resource': 'VDU2'}}}}} + +mock_resource_list = { + 'resources': [{ + 'updated_time': '2021-12-27T03:16:02Z', + 'creation_time': '2021-12-27T02:53:27Z', + 'logical_resource_id': 'fkwryhyv6qbr', + 'resource_name': 'fkwryhyv6qbr', + 'physical_resource_id': 'd7aeba20-1b00-4bff-b050-6b42a262c84d', + 'resource_status': 'UPDATE_COMPLETE', + 'resource_status_reason': 'state changed', + 'resource_type': 'VDU1.yaml', + 'links': [{ + 'href': 'http://192.168.10.115/heat-api/v1/' + '11ee4693b37c4b7995ab2ae331e9adf3/stacks/' + 'vnf-d7aeba20-1b00-4bff-b050-6b42a262c84d/' + 'd7aeba20-1b00-4bff-b050-6b42a262c84d/resources/' + 'fkwryhyv6qbr', + 'rel': 'self' + }, { + 'href': 'http://192.168.10.115/heat-api/v1/' + '11ee4693b37c4b7995ab2ae331e9adf3/stacks/' + 'vnf-d7aeba20-1b00-4bff-b050-6b42a262c84d/' + 'd7aeba20-1b00-4bff-b050-6b42a262c84d', + 'rel': 'stack' + }, { + 'href': 'http://192.168.10.115/heat-api/v1/' + '11ee4693b37c4b7995ab2ae331e9adf3/stacks/' + 'vnf-d8962b72-6dac-4eb5-a8c4-8c7a2abaefb7-' + 'VDU1_scale_group-2zmsxtwtsj7n-fkwryhyv6qbr-qoemdwxw7o5c/' + 'd7aeba20-1b00-4bff-b050-6b42a262c84d', + 'rel': 'nested' + }], + 'required_by': [], + 'parent_resource': 'VDU1_scale_group' + } + ] +} + +mock_resource_list_2 = { + 'resources': [ + { + 'updated_time': '2021-12-27T02:53:29Z', + 'creation_time': '2021-12-27T02:53:29Z', + 'logical_resource_id': 'VDU1', + 'resource_name': 'VDU1', + 'physical_resource_id': 'e79ebeaf-1b26-4ff9-9895-f4c78a8a39a6', + 'resource_status': 'CREATE_COMPLETE', + 'resource_status_reason': 'state changed', + 'resource_type': 'OS::Nova::Server', + 'links': [{ + 'href': 'http://192.168.10.115/heat-api/v1/' + '11ee4693b37c4b7995ab2ae331e9adf3/stacks/' + 'vnf-d8962b72-6dac-4eb5-a8c4-8c7a2abaefb7-' + 'VDU1_scale_group-2zmsxtwtsj7n-fkwryhyv6qbr-' + 'qoemdwxw7o5c/d7aeba20-1b00-4bff-b050-6b42a262c84d' + '/resources/VDU1', + 'rel': 'self' + }, { + 'href': 'http://192.168.10.115/heat-api/v1/' + '11ee4693b37c4b7995ab2ae331e9adf3/stacks/' + 'vnf-d8962b72-6dac-4eb5-a8c4-8c7a2abaefb7-' + 'VDU1_scale_group-2zmsxtwtsj7n-fkwryhyv6qbr-' + 'qoemdwxw7o5c/d7aeba20-1b00-4bff-b050-6b42a262c84d', + 'rel': 'stack' + }], + + 'required_by': [], + 'parent_resource': 'fkwryhyv6qbr' + }, { + 'updated_time': '2021-12-27T02:53:29Z', + 'creation_time': '2021-12-27T02:53:29Z', + 'logical_resource_id': 'VDU1_CP4', + 'resource_name': 'VDU1_CP4', + 'physical_resource_id': 'c625b32c-5b2e-4366-824e-0921f4efd954', + 'resource_status': 'CREATE_COMPLETE', + 'resource_status_reason': 'state changed', + 'resource_type': 'OS::Neutron::Port', + 'required_by': ['VDU1'], + 'parent_resource': 'fkwryhyv6qbr' + }, { + 'updated_time': '2021-12-27T02:53:29Z', + 'creation_time': '2021-12-27T02:53:29Z', + 'logical_resource_id': 'VDU1_CP1', + 'resource_name': 'VDU1_CP1', + 'physical_resource_id': 'd13bab44-a728-4a3d-9c85-b70d77b5c7f4', + 'resource_status': 'CREATE_COMPLETE', + 'resource_status_reason': 'state changed', + 'resource_type': 'OS::Neutron::Port', + 'required_by': ['VDU1'], + 'parent_resource': 'fkwryhyv6qbr' + }, { + 'updated_time': '2021-12-27T02:53:29Z', + 'creation_time': '2021-12-27T02:53:29Z', + 'logical_resource_id': 'VDU1_CP3', + 'resource_name': 'VDU1_CP3', + 'physical_resource_id': 'a231fd59-42e3-4636-a93d-695976ce03ef', + 'resource_status': 'CREATE_COMPLETE', + 'resource_status_reason': 'state changed', + 'resource_type': 'OS::Neutron::Port', + 'required_by': ['VDU1'], + 'parent_resource': 'fkwryhyv6qbr' + }, { + 'updated_time': '2021-12-27T02:53:29Z', + 'creation_time': '2021-12-27T02:53:29Z', + 'logical_resource_id': 'VDU1_CP5', + 'resource_name': 'VDU1_CP5', + 'physical_resource_id': '8703f963-2e00-4931-8bce-ddbcaf1205e4', + 'resource_status': 'CREATE_COMPLETE', + 'resource_status_reason': 'state changed', + 'resource_type': 'OS::Neutron::Port', + 'required_by': ['VDU1'], + 'parent_resource': 'fkwryhyv6qbr' + }, { + 'updated_time': '2021-12-27T02:53:29Z', + 'creation_time': '2021-12-27T02:53:29Z', + 'logical_resource_id': 'VirtualStorage', + 'resource_name': 'VirtualStorage', + 'physical_resource_id': '29809bc8-4a3d-412e-b6e2-5122995faccc', + 'resource_status': 'CREATE_COMPLETE', + 'resource_status_reason': 'state changed', + 'resource_type': 'OS::Cinder::Volume', + 'required_by': ['VDU1'], + 'parent_resource': 'fkwryhyv6qbr' + }, { + 'updated_time': '2021-12-27T02:53:29Z', + 'creation_time': '2021-12-27T02:53:29Z', + 'logical_resource_id': 'VDU1_CP2', + 'resource_name': 'VDU1_CP2', + 'physical_resource_id': '963ba654-e3f1-4b78-8187-756dbd043916', + 'resource_status': 'CREATE_COMPLETE', + 'resource_status_reason': 'state changed', + 'resource_type': 'OS::Neutron::Port', + 'required_by': ['VDU1'], + 'parent_resource': 'fkwryhyv6qbr' + } + ] +} + + +mock_resource_list_3 = { + 'resources': [ + { + 'updated_time': '2021-12-27T02:53:29Z', + 'creation_time': '2021-12-27T02:53:29Z', + 'logical_resource_id': 'VDU2', + 'resource_name': 'VDU2', + 'physical_resource_id': 'res_id_VDU2_1', + 'resource_status': 'CREATE_COMPLETE', + 'resource_status_reason': 'state changed', + 'resource_type': 'OS::Nova::Server', + 'links': [{ + 'href': 'http://192.168.10.115/heat-api/v1/' + '11ee4693b37c4b7995ab2ae331e9adf3/stacks/' + 'vnf-d8962b72-6dac-4eb5-a8c4-8c7a2abaefb7/' + 'ddea2e52-532a-491c-b8fc-9c759e35fd72/resources/VDU2', + 'rel': 'self' + }, { + 'href': 'http://192.168.10.115/heat-api/v1/' + '11ee4693b37c4b7995ab2ae331e9adf3/stacks/' + 'vnf-d8962b72-6dac-4eb5-a8c4-8c7a2abaefb7/' + 'ddea2e52-532a-491c-b8fc-9c759e35fd72', + 'rel': 'stack' + }], + 'required_by': [] + }, { + 'updated_time': '2021-12-27T02:53:29Z', + 'creation_time': '2021-12-27T02:53:29Z', + 'logical_resource_id': 'VDU2_CP4', + 'resource_name': 'VDU2_CP4', + 'physical_resource_id': 'c625b32c-5b2e-4366-824e-0921f4efd954', + 'resource_status': 'CREATE_COMPLETE', + 'resource_status_reason': 'state changed', + 'resource_type': 'OS::Neutron::Port', + 'links': [{ + 'href': 'http://192.168.10.115/heat-api/v1/' + '11ee4693b37c4b7995ab2ae331e9adf3/stacks/' + 'vnf-d8962b72-6dac-4eb5-a8c4-8c7a2abaefb7/' + 'ddea2e52-532a-491c-b8fc-9c759e35fd72/resources/' + 'VDU2_CP4', + 'rel': 'self' + }, { + 'href': 'http://192.168.10.115/heat-api/v1/' + '11ee4693b37c4b7995ab2ae331e9adf3/stacks/' + 'vnf-d8962b72-6dac-4eb5-a8c4-8c7a2abaefb7/' + 'ddea2e52-532a-491c-b8fc-9c759e35fd72', + 'rel': 'stack' + }], + 'required_by': ['VDU2'] + }, { + 'updated_time': '2021-12-27T02:53:29Z', + 'creation_time': '2021-12-27T02:53:29Z', + 'logical_resource_id': 'VDU2_CP1', + 'resource_name': 'VDU2_CP1', + 'physical_resource_id': 'd13bab44-a728-4a3d-9c85-b70d77b5c7f4', + 'resource_status': 'CREATE_COMPLETE', + 'resource_status_reason': 'state changed', + 'resource_type': 'OS::Neutron::Port', + 'links': [{ + 'href': 'http://192.168.10.115/heat-api/v1/' + '11ee4693b37c4b7995ab2ae331e9adf3/stacks/' + 'vnf-d8962b72-6dac-4eb5-a8c4-8c7a2abaefb7/' + 'ddea2e52-532a-491c-b8fc-9c759e35fd72/resources/' + 'VDU2_CP1', + 'rel': 'self' + }, { + 'href': 'http://192.168.10.115/heat-api/v1/' + '11ee4693b37c4b7995ab2ae331e9adf3/stacks/' + 'vnf-d8962b72-6dac-4eb5-a8c4-8c7a2abaefb7/' + 'ddea2e52-532a-491c-b8fc-9c759e35fd72', + 'rel': 'stack' + }], + 'required_by': ['VDU2'] + }, { + 'updated_time': '2021-12-27T02:53:29Z', + 'creation_time': '2021-12-27T02:53:29Z', + 'logical_resource_id': 'VDU2_CP3', + 'resource_name': 'VDU2_CP3', + 'physical_resource_id': 'a231fd59-42e3-4636-a93d-695976ce03ef', + 'resource_status': 'CREATE_COMPLETE', + 'resource_status_reason': 'state changed', + 'resource_type': 'OS::Neutron::Port', + 'links': [{ + 'href': 'http://192.168.10.115/heat-api/v1/' + '11ee4693b37c4b7995ab2ae331e9adf3/stacks/' + 'vnf-d8962b72-6dac-4eb5-a8c4-8c7a2abaefb7/' + 'ddea2e52-532a-491c-b8fc-9c759e35fd72/resources/' + 'VDU2_CP3', + 'rel': 'self' + }, { + 'href': 'http://192.168.10.115/heat-api/v1/' + '11ee4693b37c4b7995ab2ae331e9adf3/stacks/' + 'vnf-d8962b72-6dac-4eb5-a8c4-8c7a2abaefb7/' + 'ddea2e52-532a-491c-b8fc-9c759e35fd72', + 'rel': 'stack' + }], + + 'required_by': ['VDU2'] + }, { + 'updated_time': '2021-12-27T02:53:29Z', + 'creation_time': '2021-12-27T02:53:29Z', + 'logical_resource_id': 'VDU2_CP5', + 'resource_name': 'VDU2_CP5', + 'physical_resource_id': '8703f963-2e00-4931-8bce-ddbcaf1205e4', + 'resource_status': 'CREATE_COMPLETE', + 'resource_status_reason': 'state changed', + 'resource_type': 'OS::Neutron::Port', + 'links': [{ + 'href': 'http://192.168.10.115/heat-api/v1/' + '11ee4693b37c4b7995ab2ae331e9adf3/stacks/' + 'vnf-d8962b72-6dac-4eb5-a8c4-8c7a2abaefb7/' + 'ddea2e52-532a-491c-b8fc-9c759e35fd72/resources/' + 'VDU2_CP5', + 'rel': 'self' + }, { + 'href': 'http://192.168.10.115/heat-api/v1/' + '11ee4693b37c4b7995ab2ae331e9adf3/stacks/' + 'vnf-d8962b72-6dac-4eb5-a8c4-8c7a2abaefb7/' + 'ddea2e52-532a-491c-b8fc-9c759e35fd72', + 'rel': 'stack' + }], + 'required_by': ['VDU2'] + }, { + 'updated_time': '2021-12-27T02:53:29Z', + 'creation_time': '2021-12-27T02:53:29Z', + 'logical_resource_id': 'VDU2_CP2', + 'resource_name': 'VDU2_CP2', + 'physical_resource_id': '963ba654-e3f1-4b78-8187-756dbd043916', + 'resource_status': 'CREATE_COMPLETE', + 'resource_status_reason': 'state changed', + 'resource_type': 'OS::Neutron::Port', + 'links': [{ + 'href': 'http://192.168.10.115/heat-api/v1/' + '11ee4693b37c4b7995ab2ae331e9adf3/stacks/' + 'vnf-d8962b72-6dac-4eb5-a8c4-8c7a2abaefb7/' + 'ddea2e52-532a-491c-b8fc-9c759e35fd72/resources/' + 'VDU2_CP2', + 'rel': 'self' + }, { + 'href': 'http://192.168.10.115/heat-api/v1/' + '11ee4693b37c4b7995ab2ae331e9adf3/stacks/' + 'vnf-d8962b72-6dac-4eb5-a8c4-8c7a2abaefb7/' + 'ddea2e52-532a-491c-b8fc-9c759e35fd72', + 'rel': 'stack' + }], + 'required_by': ['VDU2'] + } + ] +} + class TestOpenstack(base.BaseTestCase): @@ -1935,3 +2751,395 @@ class TestOpenstack(base.BaseTestCase): # check result = inst.to_dict()["instantiatedVnfInfo"] self._check_inst_info(_expected_inst_info_change_ext_conn, result) + + @mock.patch.object(heat_utils.HeatClient, 'get_parameters') + @mock.patch.object(subprocess, 'run') + @mock.patch.object(glance_utils.GlanceClient, 'get_image') + @mock.patch.object(heat_utils.HeatClient, 'update_stack') + @mock.patch.object(heat_utils.HeatClient, 'get_resource_list') + @mock.patch.object(heat_utils.HeatClient, 'get_template') + @mock.patch.object(heat_utils.HeatClient, 'get_resources') + @mock.patch.object(heat_utils.HeatClient, 'get_resource_info') + @mock.patch.object(heat_utils.HeatClient, 'get_stack_resource') + def test_change_vnfpkg_404( + self, mocked_get_stack_resource, mocked_get_resource_info, + mocked_get_resources, mocked_get_template, + mocked_get_resource_list, mocked_update_stack, + mocked_get_image, mocked_run, mocked_get_parameters): + # prepare + req = objects.ChangeCurrentVnfPkgRequest.from_dict( + _change_vnfpkg_example) + inst_info = objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_example) + vim_info = { + "vim1": objects.VimConnectionInfo.from_dict( + _vim_connection_info_for_change_vnfpkg) + } + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED' + ) + inst.vimConnectionInfo = vim_info + inst.instantiatedVnfInfo = inst_info + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.CHANGE_VNFPKG + ) + grant = objects.GrantV1() + stack_body = {'stack': {'id': uuidutils.generate_uuid(), + 'name': 'test'}} + stack_body_2 = {'stack': { + 'stack_name': + 'vnf-d8962b72-6dac-4eb5-a8c4-8c7a2abaefb7-VDU1_scale_group'}} + mocked_get_stack_resource.side_effect = [stack_body, stack_body_2] + + body_2 = {"attributes": {"floating_ip_address": "192.168.0.1"}} + body_3 = {"attributes": { + "image": {"id": "image-1.0.0-x86_64-disk"}, + "flavor": {"original_name": "m1.tiny"} + } + } + mocked_get_resource_info.side_effect = [None, + body_2, body_3] + mocked_get_resources.side_effect = [mock_resource['resources'], + _heat_reses_example] + mocked_get_template.return_value = mock_resource_template + mocked_get_resource_list.side_effect = [mock_resource_list, + mock_resource_list_2] + mocked_update_stack.return_value = mock.Mock() + resp_image = requests.Response() + resp_image.name = "image-1.0.0-x86_64-disk" + mocked_get_image.return_value = resp_image + out = requests.Response() + out.returncode = 0 + mocked_run.return_value = out + parameter = { + 'nfv': '{"VDU":{"VDU1":{"vcImageId":""},' + '"VDU2":{"vcImageId":""},' + '"VirtualStorage":{"vcImageId":""}}}' + } + mocked_get_parameters.return_value = parameter + # execute change_vnfpkg + self.driver.change_vnfpkg(req, inst, grant_req, grant, + self.vnfd_1) + + # check + for vnfc_res in inst.instantiatedVnfInfo.vnfcResourceInfo: + if vnfc_res.vduId == "VDU1": + self.assertEqual(vnfc_res.id, + 'e79ebeaf-1b26-4ff9-9895-f4c78a8a39a6') + self.assertEqual(vnfc_res.computeResource.resourceId, + 'e79ebeaf-1b26-4ff9-9895-f4c78a8a39a6') + self.assertIn('current_vnfd_id', vnfc_res.metadata) + + @mock.patch.object(heat_utils.HeatClient, 'get_resources') + @mock.patch.object(subprocess, 'run') + @mock.patch.object(glance_utils.GlanceClient, 'get_image') + @mock.patch.object(heat_utils.HeatClient, 'get_resource_list') + @mock.patch.object(heat_utils.HeatClient, 'update_stack') + @mock.patch.object(heat_utils.HeatClient, 'get_parameters') + @mock.patch.object(heat_utils.HeatClient, 'get_template') + @mock.patch.object(heat_utils.HeatClient, 'get_resource_info') + @mock.patch.object(heat_utils.HeatClient, 'get_stack_resource') + def test_change_vnfpkg_200( + self, mocked_get_stack_resource, mocked_get_resource_info, + mocked_get_template, mocked_get_parameters, + mocked_update_stack, mocked_get_resource_list, mocked_get_image, + mocked_run, mocked_get_resources): + # prepare + req = objects.ChangeCurrentVnfPkgRequest.from_dict( + _change_vnfpkg_example_2) + inst_info = objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_example) + vim_info = { + "vim1": objects.VimConnectionInfo.from_dict( + _vim_connection_info_for_change_vnfpkg) + } + inst = objects.VnfInstanceV2( + # required fields + id="d7aeba20-1b00-4bff-b050-6b42a262c84d", + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED' + ) + inst.vimConnectionInfo = vim_info + inst.instantiatedVnfInfo = inst_info + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.CHANGE_VNFPKG + ) + grant = objects.GrantV1() + stack_body = {'stack': {'id': "d7aeba20-1b00-4bff-b050-6b42a262c84d", + 'name': 'test'}} + stack_body_2 = {'stack': { + 'stack_name': + 'vnf-d8962b72-6dac-4eb5-a8c4-8c7a2abaefb7-VDU1_scale_group'}} + mocked_get_stack_resource.side_effect = [stack_body, stack_body_2] + + body = {"resource": "resource"} + body_2 = {"attributes": {"floating_ip_address": "192.168.0.1"}} + body_3 = {"attributes": { + "image": {"id": "image-1.0.0-x86_64-disk"}, + "flavor": {"original_name": "m1.tiny"} + } + } + mocked_get_resource_info.side_effect = [body['resource'], body_2, + body_3] + mocked_get_resources.side_effect = [mock_resource['resources'], + _heat_reses_example] + mocked_get_template.return_value = mock_resource_template_2 + mocked_get_resource_list.return_value = mock_resource_list_3 + mocked_update_stack.return_value = mock.Mock() + resp_image = requests.Response() + resp_image.name = "image-1.0.0-x86_64-disk" + mocked_get_image.return_value = resp_image + out = requests.Response() + out.returncode = 0 + mocked_run.return_value = out + parameter = { + 'nfv': '{"VDU":{"VDU1":{"vcImageId":""},' + '"VDU2":{"vcImageId":""},' + '"VirtualStorage":{"vcImageId":""}}}' + } + mocked_get_parameters.return_value = parameter + # execute change_vnfpkg + self.driver.change_vnfpkg(req, inst, grant_req, grant, + self.vnfd_1) + + # check + for vnfc_res in inst.instantiatedVnfInfo.vnfcResourceInfo: + if vnfc_res.vduId == "VDU2": + self.assertEqual(vnfc_res.id, + 'res_id_VDU2_1') + self.assertEqual(vnfc_res.computeResource.resourceId, + 'res_id_VDU2_1') + self.assertIn('current_vnfd_id', vnfc_res.metadata) + + @mock.patch.object(heat_utils.HeatClient, 'get_parameters') + @mock.patch.object(subprocess, 'run') + @mock.patch.object(glance_utils.GlanceClient, 'get_image') + @mock.patch.object(heat_utils.HeatClient, 'get_resource_info') + @mock.patch.object(heat_utils.HeatClient, 'get_resource_list') + @mock.patch.object(heat_utils.HeatClient, 'update_stack') + @mock.patch.object(heat_utils.HeatClient, 'get_template') + @mock.patch.object(heat_utils.HeatClient, 'get_resources') + @mock.patch.object(heat_utils.HeatClient, 'get_stack_resource') + def test_change_vnfpkg_rollback( + self, mocked_get_stack_resource, mocked_get_resources, + mocked_get_template, mocked_update_stack, + mocked_get_resource_list, mocked_get_resource_info, + mocked_get_image, mocked_run, mocked_get_parameters): + req = objects.ChangeCurrentVnfPkgRequest.from_dict( + _change_vnfpkg_example) + inst_info = objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_example) + vim_info = { + "vim1": objects.VimConnectionInfo.from_dict( + _vim_connection_info_for_change_vnfpkg) + } + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED' + ) + inst.vimConnectionInfo = vim_info + inst.instantiatedVnfInfo = inst_info + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.CHANGE_VNFPKG + ) + grant = objects.GrantV1() + + affected_vnfcs = objects.AffectedVnfcV2( + id=uuidutils.generate_uuid(), + vduId='VDU1', + vnfdId=SAMPLE_VNFD_ID, + changeType='ADDED', + metadata={ + "creation_time": "2021-12-10T01:03:49Z", + "stack_id": "vnf-d8962b72-6dac-4eb5-a8c4-8c7a2abaefb7-" + "VDU1_scale_group-2zmsxtwtsj7n-" + "fkwryhyv6qbr-qoemdwxw7o5c/" + "d7aeba20-1b00-4bff-b050-6b42a262c84d", + "parent_resource_name": "fkwryhyv6qbr" + } + ) + resource_change = objects.VnfLcmOpOccV2_ResourceChanges( + affectedVnfcs=[affected_vnfcs] + ) + + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.FAILED_TEMP, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.CHANGE_VNFPKG, + resourceChanges=resource_change, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + + stack_body = {'stack': {'id': uuidutils.generate_uuid(), + 'name': 'test'}} + stack_body_2 = {'stack': { + 'stack_name': + 'vnf-d8962b72-6dac-4eb5-a8c4-8c7a2abaefb7-VDU1_scale_group'}} + mocked_get_stack_resource.side_effect = [stack_body, stack_body_2] + + body = {"attributes": {"floating_ip_address": "192.168.0.1"}} + body_2 = {"attributes": { + "image": {"id": "image-1.0.0-x86_64-disk"}, + "flavor": {"original_name": "m1.tiny"} + } + } + mocked_get_resource_info.side_effect = [body, body_2] + mocked_get_resources.side_effect = [mock_resource['resources'], + _heat_reses_example] + mocked_get_template.return_value = mock_resource_template + mocked_get_resource_list.return_value = mock_resource_list_2 + mocked_update_stack.return_value = mock.Mock() + resp_image = requests.Response() + resp_image.name = "image-1.0.0-x86_64-disk" + mocked_get_image.return_value = resp_image + out = requests.Response() + out.returncode = 0 + mocked_run.return_value = out + parameter = { + 'nfv': '{"VDU":{"VDU1":{"vcImageId":""},' + '"VDU2":{"vcImageId":""},' + '"VirtualStorage":{"vcImageId":""}}}' + } + mocked_get_parameters.return_value = parameter + self.driver.change_vnfpkg_rollback(req, inst, grant_req, grant, + self.vnfd_1, lcmocc) + # check + for vnfc_res in inst.instantiatedVnfInfo.vnfcResourceInfo: + if vnfc_res.vduId == "VDU1": + self.assertEqual(vnfc_res.id, + 'e79ebeaf-1b26-4ff9-9895-f4c78a8a39a6') + self.assertEqual(vnfc_res.computeResource.resourceId, + 'e79ebeaf-1b26-4ff9-9895-f4c78a8a39a6') + self.assertIn('current_vnfd_id', vnfc_res.metadata) + + @mock.patch.object(heat_utils.HeatClient, 'get_resources') + @mock.patch.object(subprocess, 'run') + @mock.patch.object(glance_utils.GlanceClient, 'get_image') + @mock.patch.object(heat_utils.HeatClient, 'get_resource_info') + @mock.patch.object(heat_utils.HeatClient, 'get_resource_list') + @mock.patch.object(heat_utils.HeatClient, 'update_stack') + @mock.patch.object(heat_utils.HeatClient, 'get_parameters') + @mock.patch.object(heat_utils.HeatClient, 'get_template') + @mock.patch.object(heat_utils.HeatClient, 'get_stack_resource') + def test_change_vnfpkg_rollback_same( + self, mocked_get_stack_resource, mocked_get_template, + mocked_get_parameters, mocked_update_stack, + mocked_get_resource_list, mocked_get_resource_info, + mocked_get_image, mocked_run, mocked_get_resources): + req = objects.ChangeCurrentVnfPkgRequest.from_dict( + _change_vnfpkg_example_2) + inst_info = objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_example) + vim_info = { + "vim1": objects.VimConnectionInfo.from_dict( + _vim_connection_info_for_change_vnfpkg) + } + inst = objects.VnfInstanceV2( + # required fields + id="d7aeba20-1b00-4bff-b050-6b42a262c84d", + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED' + ) + inst.vimConnectionInfo = vim_info + inst.instantiatedVnfInfo = inst_info + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.CHANGE_VNFPKG + ) + grant = objects.GrantV1() + + affected_vnfcs = objects.AffectedVnfcV2( + id=uuidutils.generate_uuid(), + vduId='VDU2', + vnfdId=SAMPLE_VNFD_ID, + changeType='MODIFIED', + metadata={ + "creation_time": "2021-12-10T01:03:49Z", + "stack_id": 'vnf-d7aeba20-1b00-4bff-b050-6b42a262c84d/' + 'd7aeba20-1b00-4bff-b050-6b42a262c84d' + } + ) + resource_change = objects.VnfLcmOpOccV2_ResourceChanges( + affectedVnfcs=[affected_vnfcs] + ) + + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.FAILED_TEMP, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.CHANGE_VNFPKG, + resourceChanges=resource_change, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + + stack_body = {'stack': {'id': 'd7aeba20-1b00-4bff-b050-6b42a262c84d', + 'name': 'test'}} + stack_body_2 = {'stack': { + 'stack_name': + 'vnf-d7aeba20-1b00-4bff-b050-6b42a262c84d-VDU1_scale_group'}} + mocked_get_stack_resource.side_effect = [stack_body, stack_body_2] + + body = {"attributes": {"floating_ip_address": "192.168.0.1"}} + body_2 = {"attributes": { + "image": { + "id": "image-1.0.0-x86_64-disk"}, + "flavor": {"original_name": "m1.tiny"} + } + } + mocked_get_resource_info.side_effect = [body, body_2] + mocked_get_resources.side_effect = [mock_resource['resources'], + _heat_reses_example] + mocked_get_template.return_value = mock_resource_template_3 + mocked_get_resource_list.return_value = mock_resource_list_3 + mocked_update_stack.return_value = mock.Mock() + resp_image = requests.Response() + resp_image.name = "image-1.0.0-x86_64-disk" + mocked_get_image.return_value = resp_image + out = requests.Response() + out.returncode = 0 + mocked_run.return_value = out + parameter = { + 'nfv': '{"VDU":{"VDU1":{"vcImageId":""},' + '"VDU2":{"vcImageId":""},' + '"VirtualStorage":{"vcImageId":""}}}' + } + mocked_get_parameters.return_value = parameter + self.driver.change_vnfpkg_rollback(req, inst, grant_req, grant, + self.vnfd_1, lcmocc) + # check + for vnfc_res in inst.instantiatedVnfInfo.vnfcResourceInfo: + if vnfc_res.vduId == "VDU2": + self.assertEqual(vnfc_res.id, + 'res_id_VDU2_1') + self.assertEqual(vnfc_res.computeResource.resourceId, + 'res_id_VDU2_1') + self.assertIn('current_vnfd_id', vnfc_res.metadata) diff --git a/tacker/tests/unit/sol_refactored/samples/sample1/UserData/userdata_default.py b/tacker/tests/unit/sol_refactored/samples/sample1/UserData/userdata_default.py index 114f5aac6..841aaa289 100644 --- a/tacker/tests/unit/sol_refactored/samples/sample1/UserData/userdata_default.py +++ b/tacker/tests/unit/sol_refactored/samples/sample1/UserData/userdata_default.py @@ -15,6 +15,7 @@ import yaml +from tacker.sol_refactored.common import common_script_utils from tacker.sol_refactored.common import vnf_instance_utils as inst_utils from tacker.sol_refactored.infra_drivers.openstack import userdata_utils @@ -23,39 +24,39 @@ class DefaultUserData(userdata_utils.AbstractUserData): @staticmethod def instantiate(req, inst, grant_req, grant, tmp_csar_dir): - vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir) + vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir) flavour_id = req['flavourId'] hot_dict = vnfd.get_base_hot(flavour_id) top_hot = hot_dict['template'] - nfv_dict = userdata_utils.init_nfv_dict(top_hot) + nfv_dict = common_script_utils.init_nfv_dict(top_hot) vdus = nfv_dict.get('VDU', {}) for vdu_name, vdu_value in vdus.items(): if 'computeFlavourId' in vdu_value: vdu_value['computeFlavourId'] = ( - userdata_utils.get_param_flavor( + common_script_utils.get_param_flavor( vdu_name, flavour_id, vnfd, grant)) if 'vcImageId' in vdu_value: - vdu_value['vcImageId'] = userdata_utils.get_param_image( + vdu_value['vcImageId'] = common_script_utils.get_param_image( vdu_name, flavour_id, vnfd, grant) if 'locationConstraints' in vdu_value: vdu_value['locationConstraints'] = ( - userdata_utils.get_param_zone( + common_script_utils.get_param_zone( vdu_name, grant_req, grant)) if 'desired_capacity' in vdu_value: vdu_value['desired_capacity'] = ( - userdata_utils.get_param_capacity( + common_script_utils.get_param_capacity( vdu_name, inst, grant_req)) cps = nfv_dict.get('CP', {}) for cp_name, cp_value in cps.items(): if 'network' in cp_value: - cp_value['network'] = userdata_utils.get_param_network( + cp_value['network'] = common_script_utils.get_param_network( cp_name, grant, req) if 'fixed_ips' in cp_value: - ext_fixed_ips = userdata_utils.get_param_fixed_ips( + ext_fixed_ips = common_script_utils.get_param_fixed_ips( cp_name, grant, req) fixed_ips = [] for i in range(len(ext_fixed_ips)): @@ -70,7 +71,7 @@ class DefaultUserData(userdata_utils.AbstractUserData): fixed_ips.append(ips_i) cp_value['fixed_ips'] = fixed_ips - userdata_utils.apply_ext_managed_vls(top_hot, req, grant) + common_script_utils.apply_ext_managed_vls(top_hot, req, grant) if 'nfv' in req.get('additionalParams', {}): nfv_dict = inst_utils.json_merge_patch(nfv_dict, @@ -98,19 +99,19 @@ class DefaultUserData(userdata_utils.AbstractUserData): # NOTE: complete 'nfv' dict can not be made at the moment # since InstantiateVnfRequest is necessary to make it. - vnfd = userdata_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir) + vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir) flavour_id = inst['instantiatedVnfInfo']['flavourId'] hot_dict = vnfd.get_base_hot(flavour_id) top_hot = hot_dict['template'] - nfv_dict = userdata_utils.init_nfv_dict(top_hot) + nfv_dict = common_script_utils.init_nfv_dict(top_hot) vdus = nfv_dict.get('VDU', {}) new_vdus = {} for vdu_name, vdu_value in vdus.items(): if 'desired_capacity' in vdu_value: - capacity = userdata_utils.get_param_capacity( + capacity = common_script_utils.get_param_capacity( vdu_name, inst, grant_req) new_vdus[vdu_name] = {'desired_capacity': capacity}