From 0403aa5839e4ae4822c06cb9068f733dd9ca8a75 Mon Sep 17 00:00:00 2001 From: Itsuro Oda Date: Thu, 28 Jul 2022 04:35:02 +0000 Subject: [PATCH] Support individual VNFc management using HOT Current Tacker's sample Base HOT uses OS::Heat::AutoScalingGroup for managing VNFc. There are some inconvenient situations that require rebooting all VMs at the same time. This patch introduces new userdata class, StandardUserData which enables individual VNFc management. Most modifications are closed in userdata class but some fixes are done in the main route which are originally necessary. Implements: blueprint individual-vnfc-management Change-Id: Ib14bcf0c00021f26878fc1960f48cb9f6aeaef43 --- ...dual-vnfc-management-7a942395ad7ca7de.yaml | 7 + .../infra_drivers/openstack/heat_utils.py | 2 +- .../infra_drivers/openstack/openstack.py | 213 ++++--- .../infra_drivers/openstack/userdata_main.py | 5 +- .../openstack/userdata_standard.py | 544 ++++++++++++++++++ .../infra_drivers/openstack/userdata_utils.py | 20 + .../sol_v2/test_individual_vnfc_mgmt.py | 433 ++++++++++++++ .../tests/functional/sol_v2_common/base_v2.py | 5 +- .../functional/sol_v2_common/paramgen.py | 194 +++++++ .../contents/BaseHOT/simple/nested/VDU1.yaml | 53 ++ .../contents/BaseHOT/simple/nested/VDU2.yaml | 69 +++ .../contents/BaseHOT/simple/sample3.yaml | 57 ++ .../Definitions/v2_sample3_df_simple.yaml | 357 ++++++++++++ .../Definitions/v2_sample3_top.vnfd.yaml | 31 + .../Definitions/v2_sample3_types.yaml | 55 ++ .../contents/Scripts/coordinate_vnf.py | 46 ++ .../contents/Scripts/sample_script.py | 68 +++ .../contents/TOSCA-Metadata/TOSCA.meta | 4 + .../samples/userdata_standard/pkggen.py | 83 +++ .../contents/BaseHOT/simple/nested/VDU1.yaml | 53 ++ .../contents/BaseHOT/simple/nested/VDU2.yaml | 69 +++ .../contents/BaseHOT/simple/sample4.yaml | 57 ++ .../Definitions/v2_sample4_df_simple.yaml | 357 ++++++++++++ .../Definitions/v2_sample4_top.vnfd.yaml | 31 + .../Definitions/v2_sample4_types.yaml | 55 ++ .../contents/Scripts/coordinate_vnf.py | 46 ++ .../contents/Scripts/sample_script.py | 68 +++ .../contents/TOSCA-Metadata/TOSCA.meta | 4 + .../userdata_standard_change_vnfpkg/pkggen.py | 52 ++ .../tests/functional/sol_v2_common/utils.py | 9 +- 30 files changed, 2961 insertions(+), 86 deletions(-) create mode 100644 releasenotes/notes/support-individual-vnfc-management-7a942395ad7ca7de.yaml create mode 100644 tacker/sol_refactored/infra_drivers/openstack/userdata_standard.py create mode 100644 tacker/tests/functional/sol_v2/test_individual_vnfc_mgmt.py create mode 100644 tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/BaseHOT/simple/nested/VDU1.yaml create mode 100644 tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/BaseHOT/simple/nested/VDU2.yaml create mode 100644 tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/BaseHOT/simple/sample3.yaml create mode 100644 tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/Definitions/v2_sample3_df_simple.yaml create mode 100644 tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/Definitions/v2_sample3_top.vnfd.yaml create mode 100644 tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/Definitions/v2_sample3_types.yaml create mode 100644 tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/Scripts/coordinate_vnf.py create mode 100644 tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/Scripts/sample_script.py create mode 100644 tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/TOSCA-Metadata/TOSCA.meta create mode 100644 tacker/tests/functional/sol_v2_common/samples/userdata_standard/pkggen.py create mode 100644 tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/BaseHOT/simple/nested/VDU1.yaml create mode 100644 tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/BaseHOT/simple/nested/VDU2.yaml create mode 100644 tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/BaseHOT/simple/sample4.yaml create mode 100644 tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/Definitions/v2_sample4_df_simple.yaml create mode 100644 tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/Definitions/v2_sample4_top.vnfd.yaml create mode 100644 tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/Definitions/v2_sample4_types.yaml create mode 100644 tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/Scripts/coordinate_vnf.py create mode 100644 tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/Scripts/sample_script.py create mode 100644 tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/TOSCA-Metadata/TOSCA.meta create mode 100644 tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/pkggen.py diff --git a/releasenotes/notes/support-individual-vnfc-management-7a942395ad7ca7de.yaml b/releasenotes/notes/support-individual-vnfc-management-7a942395ad7ca7de.yaml new file mode 100644 index 000000000..e3fbf7a4c --- /dev/null +++ b/releasenotes/notes/support-individual-vnfc-management-7a942395ad7ca7de.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Support individual VNFC management using OpenStack Heat. + New sample BaseHOT, corresponding UserData script, + and utility functions are added to UserData class + for v2 VNF Lifecycle management API. \ No newline at end of file diff --git a/tacker/sol_refactored/infra_drivers/openstack/heat_utils.py b/tacker/sol_refactored/infra_drivers/openstack/heat_utils.py index fbd65596b..cb34a5f85 100644 --- a/tacker/sol_refactored/infra_drivers/openstack/heat_utils.py +++ b/tacker/sol_refactored/infra_drivers/openstack/heat_utils.py @@ -94,7 +94,7 @@ class HeatClient(object): LOG.info("%s %s done.", operation, stack_name) raise loopingcall.LoopingCallDone() elif status in failed_status: - LOG.error("% %s failed.", operation, stack_name) + LOG.error("%s %s failed.", operation, stack_name) sol_title = "%s failed" % operation raise sol_ex.StackOperationFailed(sol_title=sol_title, sol_detail=status_reason) diff --git a/tacker/sol_refactored/infra_drivers/openstack/openstack.py b/tacker/sol_refactored/infra_drivers/openstack/openstack.py index c8e1f7507..14874f5e9 100644 --- a/tacker/sol_refactored/infra_drivers/openstack/openstack.py +++ b/tacker/sol_refactored/infra_drivers/openstack/openstack.py @@ -18,6 +18,7 @@ import json import os import pickle import subprocess +import yaml from dateutil import parser import eventlet @@ -67,6 +68,19 @@ def _make_combination_id(a, b): return '{}-{}'.format(a, b) +def _get_vdu_idx(vdu_with_idx): + part = vdu_with_idx.rpartition('-') + if part[1] == '': + return None + return int(part[2]) + + +def _vdu_with_idx(vdu, vdu_idx): + if vdu_idx is None: + return vdu + return f'{vdu}-{vdu_idx}' + + class Openstack(object): def __init__(self): @@ -75,7 +89,6 @@ class Openstack(object): def instantiate(self, req, inst, grant_req, grant, vnfd): # make HOT fields = self._make_hot(req, inst, grant_req, grant, vnfd) - LOG.debug("stack fields: %s", fields) # create or update stack vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo) @@ -113,21 +126,27 @@ class Openstack(object): stack_name = heat_utils.get_stack_name(inst) heat_client.delete_stack(stack_name) - def _update_nfv_dict(self, heat_client, stack_name, fields): - parameters = heat_client.get_parameters(stack_name) - LOG.debug("ORIG parameters: %s", parameters) - # NOTE: parameters['nfv'] is string - orig_nfv_dict = json.loads(parameters.get('nfv', '{}')) - if 'nfv' in fields['parameters']: + def _update_fields(self, heat_client, stack_name, fields): + if 'nfv' in fields.get('parameters', {}): + parameters = heat_client.get_parameters(stack_name) + LOG.debug("ORIG parameters: %s", parameters) + # NOTE: parameters['nfv'] is string + orig_nfv_dict = json.loads(parameters.get('nfv', '{}')) fields['parameters']['nfv'] = inst_utils.json_merge_patch( orig_nfv_dict, fields['parameters']['nfv']) - LOG.debug("NEW parameters: %s", fields['parameters']) + + if 'template' in fields: + orig_template = heat_client.get_template(stack_name) + template = inst_utils.json_merge_patch( + orig_template, yaml.safe_load(fields['template'])) + fields['template'] = yaml.safe_dump(template) + + LOG.debug("NEW fields: %s", fields) return fields def scale(self, req, inst, grant_req, grant, vnfd): # make HOT fields = self._make_hot(req, inst, grant_req, grant, vnfd) - LOG.debug("stack fields: %s", fields) vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo) heat_client = heat_utils.HeatClient(vim_info) @@ -139,17 +158,19 @@ class Openstack(object): if res_def.type == 'COMPUTE'] for vnfc in inst.instantiatedVnfInfo.vnfcResourceInfo: if vnfc.computeResource.resourceId in vnfc_res_ids: - if 'parent_stack_id' not in vnfc.metadata: + if 'parent_stack_id' in vnfc.metadata: + # AutoScalingGroup + heat_client.mark_unhealthy( + vnfc.metadata['parent_stack_id'], + vnfc.metadata['parent_resource_name']) + elif 'vdu_idx' not in vnfc.metadata: # It means definition of VDU in the BaseHOT # is inappropriate. raise sol_ex.UnexpectedParentResourceDefinition() - heat_client.mark_unhealthy( - vnfc.metadata['parent_stack_id'], - vnfc.metadata['parent_resource_name']) # update stack stack_name = heat_utils.get_stack_name(inst) - fields = self._update_nfv_dict(heat_client, stack_name, fields) + fields = self._update_fields(heat_client, stack_name, fields) heat_client.update_stack(stack_name, fields) # make instantiated_vnf_info @@ -158,6 +179,10 @@ class Openstack(object): def scale_rollback(self, req, inst, grant_req, grant, vnfd): # NOTE: rollback is supported for scale out only + # make HOT + fields = self._make_hot(req, inst, grant_req, grant, vnfd, + is_rollback=True) + vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo) heat_client = heat_utils.HeatClient(vim_info) stack_name = heat_utils.get_stack_name(inst) @@ -169,19 +194,18 @@ class Openstack(object): for res in heat_utils.get_server_reses(heat_reses): if res['physical_resource_id'] not in vnfc_ids: metadata = self._make_vnfc_metadata(res, heat_reses) - if 'parent_stack_id' not in metadata: + if 'parent_stack_id' in metadata: + # AutoScalingGroup + heat_client.mark_unhealthy( + metadata['parent_stack_id'], + metadata['parent_resource_name']) + elif 'vdu_idx' not in metadata: # It means definition of VDU in the BaseHOT # is inappropriate. raise sol_ex.UnexpectedParentResourceDefinition() - heat_client.mark_unhealthy( - metadata['parent_stack_id'], - metadata['parent_resource_name']) # update (put back) 'desired_capacity' parameter - fields = self._update_nfv_dict(heat_client, stack_name, - userdata_default.DefaultUserData.scale_rollback( - req, inst, grant_req, grant, vnfd.csar_dir)) - + fields = self._update_fields(heat_client, stack_name, fields) heat_client.update_stack(stack_name, fields) # NOTE: instantiatedVnfInfo is not necessary to update since it @@ -190,13 +214,12 @@ class Openstack(object): def change_ext_conn(self, req, inst, grant_req, grant, vnfd): # make HOT fields = self._make_hot(req, inst, grant_req, grant, vnfd) - LOG.debug("stack fields: %s", fields) # update stack vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo) heat_client = heat_utils.HeatClient(vim_info) stack_name = heat_utils.get_stack_name(inst) - fields = self._update_nfv_dict(heat_client, stack_name, fields) + fields = self._update_fields(heat_client, stack_name, fields) heat_client.update_stack(stack_name, fields) # make instantiated_vnf_info @@ -204,31 +227,33 @@ class Openstack(object): heat_client) def change_ext_conn_rollback(self, req, inst, grant_req, grant, vnfd): + # make HOT + fields = self._make_hot(req, inst, grant_req, grant, vnfd, + is_rollback=True) + + # update stack vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo) heat_client = heat_utils.HeatClient(vim_info) stack_name = heat_utils.get_stack_name(inst) - fields = self._update_nfv_dict(heat_client, stack_name, - userdata_default.DefaultUserData.change_ext_conn_rollback( - req, inst, grant_req, grant, vnfd.csar_dir)) + fields = self._update_fields(heat_client, stack_name, fields) heat_client.update_stack(stack_name, fields) # NOTE: it is necessary to re-create instantiatedVnfInfo because # ports may be changed. self._make_instantiated_vnf_info(req, inst, grant_req, grant, vnfd, - heat_client) + heat_client, is_rollback=True) def heal(self, req, inst, grant_req, grant, vnfd): # make HOT # NOTE: _make_hot() is called as other operations, but it returns - # empty 'nfv' dict by default. Therefore _update_nfv_dict() returns + # empty 'nfv' dict by default. Therefore _update_fields() returns # current heat parameters as is. fields = self._make_hot(req, inst, grant_req, grant, vnfd) - LOG.debug("stack fields: %s", fields) vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo) heat_client = heat_utils.HeatClient(vim_info) stack_name = heat_utils.get_stack_name(inst) - fields = self._update_nfv_dict(heat_client, stack_name, fields) + fields = self._update_fields(heat_client, stack_name, fields) # "re_create" is set to True only when SOL003 heal(without # vnfcInstanceId) and "all=True" in additionalParams. @@ -284,7 +309,6 @@ class Openstack(object): def change_vnfpkg(self, req, inst, grant_req, grant, vnfd): # make HOT fields = self._make_hot(req, inst, grant_req, grant, vnfd) - LOG.debug("stack fields: %s", fields) vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo) heat_client = heat_utils.HeatClient(vim_info) @@ -298,13 +322,35 @@ class Openstack(object): # update stack stack_name = heat_utils.get_stack_name(inst) - fields = self._update_nfv_dict(heat_client, stack_name, fields) + fields = self._update_fields(heat_client, stack_name, fields) heat_client.update_stack(stack_name, fields) # make instantiated_vnf_info self._make_instantiated_vnf_info(req, inst, grant_req, grant, vnfd, heat_client) + def _get_flavor_from_vdu_dict(self, vnfc, vdu_dict): + vdu_name = _vdu_with_idx(vnfc.vduId, vnfc.metadata.get('vdu_idx')) + return vdu_dict.get(vdu_name, {}).get('computeFlavourId') + + def _get_images_from_vdu_dict(self, vnfc, storage_infos, vdu_dict): + vdu_idx = vnfc.metadata.get('vdu_idx') + vdu_name = _vdu_with_idx(vnfc.vduId, vdu_idx) + vdu_names = [vdu_name] + if vnfc.obj_attr_is_set('storageResourceIds'): + vdu_names += [ + _vdu_with_idx(storage_info.virtualStorageDescId, vdu_idx) + for storage_info in storage_infos + if storage_info.id in vnfc.storageResourceIds + ] + images = {} + for vdu_name in vdu_names: + image = vdu_dict.get(vdu_name, {}).get('vcImageId') + if image: + images[vdu_name] = image + + return images + def _change_vnfpkg_rolling_update(self, req, inst, grant_req, grant, vnfd, fields, heat_client, is_rollback): if not grant_req.obj_attr_is_set('removeResources'): @@ -348,30 +394,25 @@ class Openstack(object): LOG.debug("stack fields: %s", vdu_fields) heat_client.update_stack(parent_stack_id, vdu_fields) else: - # handle single VM - # pickup 'vcImageId' and 'computeFlavourId' + # pickup 'vcImageId' and 'computeFlavourId' from vdu_dict + vdu_name = _vdu_with_idx(vnfc.vduId, + vnfc.metadata.get('vdu_idx')) params = {} - if vdu_dict[vnfc.vduId].get('computeFlavourId'): - params[vnfc.vduId] = {'computeFlavourId': - vdu_dict[vnfc.vduId]['computeFlavourId']} - vdu_names = [vnfc.vduId] - if vnfc.obj_attr_is_set('storageResourceIds'): - storage_infos = ( - inst.instantiatedVnfInfo.virtualStorageResourceInfo) - vdu_names += [ - storage_info.virtualStorageDescId - for storage_info in storage_infos - if storage_info.id in vnfc.storageResourceIds - ] - for vdu_name in vdu_names: - image = vdu_dict.get(vdu_name, {}).get('vcImageId') - if image: - params.setdefault(vdu_name, {}) - params[vdu_name]['vcImageId'] = image + flavor = self._get_flavor_from_vdu_dict(vnfc, vdu_dict) + if flavor: + params[vdu_name] = {'computeFlavourId': flavor} + storage_infos = ( + inst.instantiatedVnfInfo.virtualStorageResourceInfo + if vnfc.obj_attr_is_set('storageResourceIds') else []) + images = self._get_images_from_vdu_dict(vnfc, + storage_infos, vdu_dict) + for vdu_name, image in images.items(): + params.setdefault(vdu_name, {}) + params[vdu_name]['vcImageId'] = image update_nfv_dict = {'nfv': {'VDU': params}} update_fields = {'parameters': update_nfv_dict} - update_fields = self._update_nfv_dict(heat_client, stack_name, + update_fields = self._update_fields(heat_client, stack_name, update_fields) LOG.debug("stack fields: %s", update_fields) heat_client.update_stack(stack_name, update_fields) @@ -429,8 +470,9 @@ class Openstack(object): def change_vnfpkg_rollback(self, req, inst, grant_req, grant, vnfd, lcmocc): - fields = userdata_default.DefaultUserData.change_vnfpkg_rollback( - req, inst, grant_req, grant, vnfd.csar_dir) + # make HOT + fields = self._make_hot(req, inst, grant_req, grant, vnfd, + is_rollback=True) vim_info = inst_utils.select_vim_info(inst.vimConnectionInfo) heat_client = heat_utils.HeatClient(vim_info) @@ -444,15 +486,15 @@ class Openstack(object): # stack update stack_name = heat_utils.get_stack_name(inst) - fields = self._update_nfv_dict(heat_client, stack_name, fields) + fields = self._update_fields(heat_client, stack_name, fields) heat_client.update_stack(stack_name, fields) # NOTE: it is necessary to re-create instantiatedVnfInfo because # some resources may be changed. self._make_instantiated_vnf_info(req, inst, grant_req, grant, vnfd, - heat_client) + heat_client, is_rollback=True) - def _make_hot(self, req, inst, grant_req, grant, vnfd): + def _make_hot(self, req, inst, grant_req, grant, vnfd, is_rollback=False): if grant_req.operation == v2fields.LcmOperationType.INSTANTIATE: flavour_id = req.flavourId else: @@ -470,10 +512,12 @@ class Openstack(object): 'lcm-operation-user-data-class') if userdata is None and userdata_class is None: - LOG.debug("Processing default userdata %s", grant_req.operation) + operation = grant_req.operation.lower() + if is_rollback: + operation = operation + '_rollback' + LOG.debug("Processing default userdata %s", operation) # NOTE: objects used here are dict compat. - method = getattr(userdata_default.DefaultUserData, - grant_req.operation.lower()) + method = getattr(userdata_default.DefaultUserData, operation) fields = method(req, inst, grant_req, grant, vnfd.csar_dir) elif userdata is None or userdata_class is None: # Both must be specified. @@ -488,7 +532,8 @@ class Openstack(object): 'vnf_instance': inst.to_dict(), 'grant_request': grant_req.to_dict(), 'grant_response': grant.to_dict(), - 'tmp_csar_dir': tmp_csar_dir + 'tmp_csar_dir': tmp_csar_dir, + 'is_rollback': is_rollback } script_path = os.path.join( os.path.dirname(__file__), "userdata_main.py") @@ -510,6 +555,8 @@ class Openstack(object): fields['timeout_mins'] = ( CONF.v2_vnfm.openstack_vim_stack_create_timeout) + LOG.debug("stack fields: %s", fields) + return fields def _get_checked_reses(self, nodes, reses): @@ -895,9 +942,16 @@ class Openstack(object): } parent_res = heat_utils.get_parent_resource(server_res, heat_reses) if parent_res: - metadata['parent_stack_id'] = ( - heat_utils.get_resource_stack_id(parent_res)) - metadata['parent_resource_name'] = parent_res['resource_name'] + parent_parent_res = heat_utils.get_parent_resource(parent_res, + heat_reses) + if (parent_parent_res and + parent_parent_res['resource_type'] == + 'OS::Heat::AutoScalingGroup'): + metadata['parent_stack_id'] = ( + heat_utils.get_resource_stack_id(parent_res)) + metadata['parent_resource_name'] = parent_res['resource_name'] + else: + metadata['vdu_idx'] = _get_vdu_idx(parent_res['resource_name']) return metadata @@ -917,22 +971,16 @@ class Openstack(object): if k.startswith('image-'): metadata[k] = v else: - properties = nfv_dict['VDU'][vnfc_res_info.vduId] - metadata['flavor'] = properties['computeFlavourId'] - vdu_names = [vnfc_res_info.vduId] - if vnfc_res_info.obj_attr_is_set('storageResourceIds'): - vdu_names += [ - storage_info.virtualStorageDescId - for storage_info in storage_infos - if storage_info.id in vnfc_res_info.storageResourceIds - ] - for vdu_name in vdu_names: - image = nfv_dict['VDU'].get(vdu_name, {}).get('vcImageId') - if image: - metadata[f'image-{vdu_name}'] = image + # assume it is found + metadata['flavor'] = self._get_flavor_from_vdu_dict( + vnfc_res_info, nfv_dict['VDU']) + images = self._get_images_from_vdu_dict(vnfc_res_info, + storage_infos, nfv_dict['VDU']) + for vdu_name, image in images.items(): + metadata[f'image-{vdu_name}'] = image def _make_instantiated_vnf_info(self, req, inst, grant_req, grant, vnfd, - heat_client): + heat_client, is_rollback=False): # get heat resources stack_name = heat_utils.get_stack_name(inst) heat_reses = heat_client.get_resources(stack_name) @@ -1029,7 +1077,8 @@ class Openstack(object): flavour_id, req, grant) else: old_inst_vnf_info = inst.instantiatedVnfInfo - if op == v2fields.LcmOperationType.CHANGE_EXT_CONN: + if (op == v2fields.LcmOperationType.CHANGE_EXT_CONN and + not is_rollback): ext_vl_infos = self._make_ext_vl_info_from_req_and_inst( req, grant, old_inst_vnf_info, ext_cp_infos) else: @@ -1107,7 +1156,9 @@ class Openstack(object): # grant request. def _get_key(vnfc): - return parser.isoparse(vnfc.metadata['creation_time']) + vdu_idx = vnfc.metadata.get('vdu_idx', 0) + creation_time = parser.isoparse(vnfc.metadata['creation_time']) + return (vdu_idx, creation_time) sorted_vnfc_res_infos = sorted(vnfc_res_infos, key=_get_key, reverse=True) diff --git a/tacker/sol_refactored/infra_drivers/openstack/userdata_main.py b/tacker/sol_refactored/infra_drivers/openstack/userdata_main.py index 76acb4317..f3d2b0de8 100644 --- a/tacker/sol_refactored/infra_drivers/openstack/userdata_main.py +++ b/tacker/sol_refactored/infra_drivers/openstack/userdata_main.py @@ -39,7 +39,10 @@ def main(): module = importlib.import_module(class_module) klass = getattr(module, userdata_class) - method = getattr(klass, grant_req['operation'].lower()) + operation = grant_req['operation'].lower() + if script_dict['is_rollback']: + operation = operation + '_rollback' + method = getattr(klass, operation) stack_dict = method(req, inst, grant_req, grant, tmp_csar_dir) pickle.dump(stack_dict, sys.stdout.buffer) diff --git a/tacker/sol_refactored/infra_drivers/openstack/userdata_standard.py b/tacker/sol_refactored/infra_drivers/openstack/userdata_standard.py new file mode 100644 index 000000000..a01429485 --- /dev/null +++ b/tacker/sol_refactored/infra_drivers/openstack/userdata_standard.py @@ -0,0 +1,544 @@ +# Copyright (C) 2022 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. + +import copy +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 + + +def add_idx(name, index): + return f'{name}-{index}' + + +def rm_idx(name_idx): + return name_idx.rpartition('-')[0] + + +def add_idx_to_vdu_template(vdu_template, vdu_idx): + """Add index to the third element of get_param + + ex. input VDU template: + --- + VDU1: + type: VDU1.yaml + properties: + flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] } + image-VDU1: { get_param: [ nfv, VDU, VDU1, vcImageId ] } + net1: { get_param: [ nfv, CP, VDU1_CP1, network ] } + --- + + output VDU template: + --- + VDU1: + type: VDU1.yaml + properties: + flavor: { get_param: [ nfv, VDU, VDU1-1, computeFlavourId ] } + image-VDU1: { get_param: [ nfv, VDU, VDU1-1, vcImageId ] } + net1: { get_param: [ nfv, CP, VDU1_CP1-1, network ] } + --- + """ + res = copy.deepcopy(vdu_template) + for prop_value in res.get('properties', {}).values(): + get_param = prop_value.get('get_param') + if (get_param is not None and + isinstance(get_param, list) and len(get_param) >= 4): + get_param[2] = add_idx(get_param[2], vdu_idx) + return res + + +class StandardUserData(userdata_utils.AbstractUserData): + + @staticmethod + def instantiate(req, inst, grant_req, grant, 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'] + + # first modify VDU resources + poped_vdu = {} + vdu_idxes = {} + for vdu_name in vnfd.get_vdu_nodes(flavour_id).keys(): + poped_vdu[vdu_name] = top_hot.get('resources', {}).pop(vdu_name) + vdu_idxes[vdu_name] = 0 + + for res in grant_req['addResources']: + if res['type'] != 'COMPUTE': + continue + vdu_name = res['resourceTemplateId'] + if vdu_name not in poped_vdu: + continue + vdu_idx = vdu_idxes[vdu_name] + vdu_idxes[vdu_name] += 1 + res = add_idx_to_vdu_template(poped_vdu[vdu_name], vdu_idx) + top_hot['resources'][add_idx(vdu_name, vdu_idx)] = res + + nfv_dict = common_script_utils.init_nfv_dict(top_hot) + + vdus = nfv_dict.get('VDU', {}) + for vdu_name, vdu_value in vdus.items(): + vdu_name = rm_idx(vdu_name) + if 'computeFlavourId' in vdu_value: + vdu_value['computeFlavourId'] = ( + common_script_utils.get_param_flavor( + vdu_name, flavour_id, vnfd, grant)) + if 'vcImageId' in vdu_value: + vdu_value['vcImageId'] = common_script_utils.get_param_image( + vdu_name, flavour_id, vnfd, grant) + if 'locationConstraints' in vdu_value: + vdu_value['locationConstraints'] = ( + common_script_utils.get_param_zone( + vdu_name, grant_req, grant)) + + cps = nfv_dict.get('CP', {}) + for cp_name, cp_value in cps.items(): + cp_name = rm_idx(cp_name) + if 'network' in cp_value: + cp_value['network'] = common_script_utils.get_param_network( + cp_name, grant, req) + if 'fixed_ips' in cp_value: + ext_fixed_ips = common_script_utils.get_param_fixed_ips( + cp_name, grant, req) + fixed_ips = [] + for i in range(len(ext_fixed_ips)): + if i not in cp_value['fixed_ips']: + break + ips_i = cp_value['fixed_ips'][i] + if 'subnet' in ips_i: + ips_i['subnet'] = ext_fixed_ips[i].get('subnet') + if 'ip_address' in ips_i: + ips_i['ip_address'] = ext_fixed_ips[i].get( + 'ip_address') + fixed_ips.append(ips_i) + cp_value['fixed_ips'] = fixed_ips + + 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, + req['additionalParams']['nfv']) + if 'nfv' in grant.get('additionalParams', {}): + nfv_dict = inst_utils.json_merge_patch(nfv_dict, + grant['additionalParams']['nfv']) + + fields = { + 'template': yaml.safe_dump(top_hot), + 'parameters': {'nfv': nfv_dict}, + 'files': {} + } + for key, value in hot_dict.get('files', {}).items(): + fields['files'][key] = yaml.safe_dump(value) + + return fields + + @staticmethod + def scale(req, inst, grant_req, grant, tmp_csar_dir): + if req['type'] == 'SCALE_OUT': + return StandardUserData._scale_out(req, inst, grant_req, grant, + tmp_csar_dir) + else: + return StandardUserData._scale_in(req, inst, grant_req, grant, + tmp_csar_dir) + + @staticmethod + def _scale_out(req, inst, grant_req, grant, 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'] + + # first modify VDU resources + poped_vdu = {} + vdu_idxes = {} + for vdu_name in vnfd.get_vdu_nodes(flavour_id).keys(): + poped_vdu[vdu_name] = top_hot.get('resources', {}).pop(vdu_name) + vdu_idxes[vdu_name] = common_script_utils.get_current_capacity( + vdu_name, inst) + + for res in grant_req['addResources']: + if res['type'] != 'COMPUTE': + continue + vdu_name = res['resourceTemplateId'] + if vdu_name not in poped_vdu: + continue + vdu_idx = vdu_idxes[vdu_name] + vdu_idxes[vdu_name] += 1 + res = add_idx_to_vdu_template(poped_vdu[vdu_name], vdu_idx) + top_hot['resources'][add_idx(vdu_name, vdu_idx)] = res + + nfv_dict = common_script_utils.init_nfv_dict(top_hot) + + vdus = nfv_dict.get('VDU', {}) + for vdu_name, vdu_value in vdus.items(): + vdu_name = rm_idx(vdu_name) + if 'computeFlavourId' in vdu_value: + vdu_value['computeFlavourId'] = ( + common_script_utils.get_param_flavor( + vdu_name, flavour_id, vnfd, grant)) + if 'vcImageId' in vdu_value: + vdu_value['vcImageId'] = common_script_utils.get_param_image( + vdu_name, flavour_id, vnfd, grant) + if 'locationConstraints' in vdu_value: + vdu_value['locationConstraints'] = ( + common_script_utils.get_param_zone( + vdu_name, grant_req, grant)) + + cps = nfv_dict.get('CP', {}) + for cp_name, cp_value in cps.items(): + cp_name = rm_idx(cp_name) + if 'network' in cp_value: + cp_value['network'] = ( + common_script_utils.get_param_network_from_inst( + cp_name, inst)) + if 'fixed_ips' in cp_value: + 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']: + break + ips_i = cp_value['fixed_ips'][i] + if 'subnet' in ips_i: + ips_i['subnet'] = ext_fixed_ips[i].get('subnet') + if 'ip_address' in ips_i: + ips_i['ip_address'] = ext_fixed_ips[i].get( + 'ip_address') + fixed_ips.append(ips_i) + cp_value['fixed_ips'] = fixed_ips + + fields = { + 'template': yaml.safe_dump(top_hot), + 'parameters': {'nfv': nfv_dict} + } + + return fields + + @staticmethod + def _scale_in(req, inst, grant_req, grant, tmp_csar_dir): + vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir) + flavour_id = inst['instantiatedVnfInfo']['flavourId'] + + vdu_nodes = vnfd.get_vdu_nodes(flavour_id) + template = {'resources': {}} + vdus = {} + cps = {} + for res in grant_req['removeResources']: + if res['type'] != 'COMPUTE': + continue + for inst_vnfc in inst['instantiatedVnfInfo']['vnfcResourceInfo']: + if (inst_vnfc['computeResource']['resourceId'] == + res['resource']['resourceId']): + # must be found + vdu_idx = inst_vnfc['metadata']['vdu_idx'] + break + vdu_name = res['resourceTemplateId'] + template['resources'][add_idx(vdu_name, vdu_idx)] = None + vdus[add_idx(vdu_name, vdu_idx)] = None + + for str_name in vnfd.get_vdu_storages(vdu_nodes[vdu_name]): + # may overspec, but no problem + vdus[add_idx(str_name, vdu_idx)] = None + + for cp_name in vnfd.get_vdu_cps(flavour_id, vdu_name): + # may overspec, but no problem + cps[add_idx(cp_name, vdu_idx)] = None + + fields = { + 'template': yaml.safe_dump(template), + 'parameters': {'nfv': {'VDU': vdus, 'CP': cps}} + } + + return fields + + @staticmethod + def scale_rollback(req, inst, grant_req, grant, tmp_csar_dir): + vnfd = common_script_utils.get_vnfd(inst['vnfdId'], tmp_csar_dir) + flavour_id = inst['instantiatedVnfInfo']['flavourId'] + + vdu_nodes = vnfd.get_vdu_nodes(flavour_id) + vdu_idxes = {} + for vdu_name in vdu_nodes.keys(): + vdu_idxes[vdu_name] = common_script_utils.get_current_capacity( + vdu_name, inst) + + template = {'resources': {}} + vdus = {} + cps = {} + for res in grant_req['addResources']: + if res['type'] != 'COMPUTE': + continue + vdu_name = res['resourceTemplateId'] + vdu_idx = vdu_idxes[vdu_name] + vdu_idxes[vdu_name] += 1 + template['resources'][add_idx(vdu_name, vdu_idx)] = None + vdus[add_idx(vdu_name, vdu_idx)] = None + + for str_name in vnfd.get_vdu_storages(vdu_nodes[vdu_name]): + # may overspec, but no problem + vdus[add_idx(str_name, vdu_idx)] = None + + for cp_name in vnfd.get_vdu_cps(flavour_id, vdu_name): + # may overspec, but no problem + cps[add_idx(cp_name, vdu_idx)] = None + + fields = { + 'template': yaml.safe_dump(template), + 'parameters': {'nfv': {'VDU': vdus, 'CP': cps}} + } + + return fields + + @staticmethod + def change_ext_conn(req, inst, grant_req, grant, tmp_csar_dir): + # change_ext_conn is interested in 'CP' only. + # This method returns only 'CP' part in the 'nfv' dict from + # ChangeExtVnfConnectivityRequest. + # It is applied to json merge patch against the existing 'nfv' + # dict by the caller. + # NOTE: complete 'nfv' dict can not be made at the moment + # since InstantiateVnfRequest is necessary to make it. + + 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'] + + # first modify VDU resources + poped_vdu = {} + for vdu_name in vnfd.get_vdu_nodes(flavour_id).keys(): + poped_vdu[vdu_name] = top_hot.get('resources', {}).pop(vdu_name) + + for inst_vnfc in inst['instantiatedVnfInfo'].get( + 'vnfcResourceInfo', []): + vdu_idx = inst_vnfc['metadata'].get('vdu_idx') + if vdu_idx is None: + continue + vdu_name = inst_vnfc['vduId'] + res = add_idx_to_vdu_template(poped_vdu[vdu_name], vdu_idx) + top_hot['resources'][add_idx(vdu_name, vdu_idx)] = res + + nfv_dict = common_script_utils.init_nfv_dict(top_hot) + + cps = nfv_dict.get('CP', {}) + new_cps = {} + for cp_name_idx, cp_value in cps.items(): + cp_name = rm_idx(cp_name_idx) + if 'network' in cp_value: + network = common_script_utils.get_param_network( + cp_name, grant, req) + if network is None: + continue + new_cps.setdefault(cp_name_idx, {}) + new_cps[cp_name_idx]['network'] = network + if 'fixed_ips' in cp_value: + ext_fixed_ips = common_script_utils.get_param_fixed_ips( + cp_name, grant, req) + fixed_ips = [] + for i in range(len(ext_fixed_ips)): + if i not in cp_value['fixed_ips']: + break + ips_i = cp_value['fixed_ips'][i] + if 'subnet' in ips_i: + ips_i['subnet'] = ext_fixed_ips[i].get('subnet') + if 'ip_address' in ips_i: + ips_i['ip_address'] = ext_fixed_ips[i].get( + 'ip_address') + fixed_ips.append(ips_i) + new_cps.setdefault(cp_name_idx, {}) + new_cps[cp_name_idx]['fixed_ips'] = fixed_ips + + fields = {'parameters': {'nfv': {'CP': new_cps}}} + + return fields + + @staticmethod + def change_ext_conn_rollback(req, inst, grant_req, grant, 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'] + + # first modify VDU resources + poped_vdu = {} + for vdu_name in vnfd.get_vdu_nodes(flavour_id).keys(): + poped_vdu[vdu_name] = top_hot.get('resources', {}).pop(vdu_name) + + for inst_vnfc in inst['instantiatedVnfInfo'].get( + 'vnfcResourceInfo', []): + vdu_idx = inst_vnfc['metadata'].get('vdu_idx') + if vdu_idx is None: + continue + vdu_name = inst_vnfc['vduId'] + res = add_idx_to_vdu_template(poped_vdu[vdu_name], vdu_idx) + top_hot['resources'][add_idx(vdu_name, vdu_idx)] = res + + nfv_dict = common_script_utils.init_nfv_dict(top_hot) + + cps = nfv_dict.get('CP', {}) + new_cps = {} + for cp_name_idx, cp_value in cps.items(): + cp_name = rm_idx(cp_name_idx) + if 'network' in cp_value: + network = common_script_utils.get_param_network_from_inst( + cp_name, inst) + if network is None: + continue + new_cps.setdefault(cp_name_idx, {}) + new_cps[cp_name_idx]['network'] = network + if 'fixed_ips' in cp_value: + 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']: + break + ips_i = cp_value['fixed_ips'][i] + if 'subnet' in ips_i: + ips_i['subnet'] = ext_fixed_ips[i].get('subnet') + if 'ip_address' in ips_i: + ips_i['ip_address'] = ext_fixed_ips[i].get( + 'ip_address') + fixed_ips.append(ips_i) + new_cps.setdefault(cp_name_idx, {}) + new_cps[cp_name_idx]['fixed_ips'] = fixed_ips + + fields = {'parameters': {'nfv': {'CP': new_cps}}} + + return fields + + @staticmethod + def heal(req, inst, grant_req, grant, tmp_csar_dir): + # It is not necessary to change parameters at heal basically. + + fields = {'parameters': {'nfv': {}}} + + return fields + + @staticmethod + def change_vnfpkg(req, inst, grant_req, grant, tmp_csar_dir): + vnfd = common_script_utils.get_vnfd(grant_req['dstVnfdId'], + tmp_csar_dir) + flavour_id = inst['instantiatedVnfInfo']['flavourId'] + + hot_dict = vnfd.get_base_hot(flavour_id) + top_hot = hot_dict['template'] + + # first modify VDU resources + poped_vdu = {} + for vdu_name in vnfd.get_vdu_nodes(flavour_id).keys(): + poped_vdu[vdu_name] = top_hot.get('resources', {}).pop(vdu_name) + + for res in grant_req['removeResources']: + if res['type'] != 'COMPUTE': + continue + for inst_vnfc in inst['instantiatedVnfInfo']['vnfcResourceInfo']: + if (inst_vnfc['computeResource']['resourceId'] == + res['resource']['resourceId']): + # must be found + vdu_idx = inst_vnfc['metadata']['vdu_idx'] + break + vdu_name = res['resourceTemplateId'] + res = add_idx_to_vdu_template(poped_vdu[vdu_name], vdu_idx) + top_hot['resources'][add_idx(vdu_name, vdu_idx)] = res + + nfv_dict = common_script_utils.init_nfv_dict(top_hot) + + vdus = nfv_dict.get('VDU', {}) + for vdu_name, vdu_value in vdus.items(): + vdu_name = rm_idx(vdu_name) + if 'computeFlavourId' in vdu_value: + vdu_value['computeFlavourId'] = ( + common_script_utils.get_param_flavor( + vdu_name, flavour_id, vnfd, grant)) + if 'vcImageId' in vdu_value: + vdu_value['vcImageId'] = common_script_utils.get_param_image( + vdu_name, flavour_id, vnfd, grant) + if 'locationConstraints' in vdu_value: + vdu_value.pop('locationConstraints') + + fields = { + 'parameters': {'nfv': {'VDU': vdus}} + } + + return fields + + @staticmethod + def change_vnfpkg_rollback(req, inst, grant_req, grant, 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'] + + # first modify VDU resources + poped_vdu = {} + for vdu_name in vnfd.get_vdu_nodes(flavour_id).keys(): + poped_vdu[vdu_name] = top_hot.get('resources', {}).pop(vdu_name) + + for res in grant_req['removeResources']: + if res['type'] != 'COMPUTE': + continue + for inst_vnfc in inst['instantiatedVnfInfo']['vnfcResourceInfo']: + if (inst_vnfc['computeResource']['resourceId'] == + res['resource']['resourceId']): + # must be found + vdu_idx = inst_vnfc['metadata']['vdu_idx'] + break + vdu_name = res['resourceTemplateId'] + res = add_idx_to_vdu_template(poped_vdu[vdu_name], vdu_idx) + top_hot['resources'][add_idx(vdu_name, vdu_idx)] = res + + nfv_dict = common_script_utils.init_nfv_dict(top_hot) + + images = {} + flavors = {} + for inst_vnfc in inst['instantiatedVnfInfo']['vnfcResourceInfo']: + vdu_idx = inst_vnfc['metadata'].get('vdu_idx') + if vdu_idx is None: + continue + vdu_name = inst_vnfc['vduId'] + if vdu_name in flavors: + continue + for key, value in inst_vnfc['metadata'].items(): + if key == 'flavor': + flavors[add_idx(vdu_name, vdu_idx)] = value + elif key.startswith('image-'): + image_vdu = key.replace('image-', '') + images[image_vdu] = value + + vdus = nfv_dict.get('VDU', {}) + new_vdus = {} + for vdu_name, vdu_value in vdus.items(): + if 'computeFlavourId' in vdu_value: + new_vdus.setdefault(vdu_name, {}) + new_vdus[vdu_name]['computeFlavourId'] = flavors.get(vdu_name) + if 'vcImageId' in vdu_value: + new_vdus.setdefault(vdu_name, {}) + new_vdus[vdu_name]['vcImageId'] = images.get(vdu_name) + + fields = { + 'parameters': {'nfv': {'VDU': new_vdus}} + } + + return fields diff --git a/tacker/sol_refactored/infra_drivers/openstack/userdata_utils.py b/tacker/sol_refactored/infra_drivers/openstack/userdata_utils.py index 341449df4..b534fe3b8 100644 --- a/tacker/sol_refactored/infra_drivers/openstack/userdata_utils.py +++ b/tacker/sol_refactored/infra_drivers/openstack/userdata_utils.py @@ -44,12 +44,32 @@ class AbstractUserData(metaclass=abc.ABCMeta): def scale(req, inst, grant_req, grant, tmp_csar_dir): raise sol_ex.UserDataClassNotImplemented() + @staticmethod + @abc.abstractmethod + def scale_rollback(req, inst, grant_req, grant, tmp_csar_dir): + raise sol_ex.UserDataClassNotImplemented() + @staticmethod @abc.abstractmethod def change_ext_conn(req, inst, grant_req, grant, tmp_csar_dir): raise sol_ex.UserDataClassNotImplemented() + @staticmethod + @abc.abstractmethod + def change_ext_conn_rollback(req, inst, grant_req, grant, tmp_csar_dir): + raise sol_ex.UserDataClassNotImplemented() + @staticmethod @abc.abstractmethod def heal(req, inst, grant_req, grant, tmp_csar_dir): raise sol_ex.UserDataClassNotImplemented() + + @staticmethod + @abc.abstractmethod + def change_vnfpkg(req, inst, grant_req, grant, tmp_csar_dir): + raise sol_ex.UserDataClassNotImplemented() + + @staticmethod + @abc.abstractmethod + def change_vnfpkg_rollback(req, inst, grant_req, grant, tmp_csar_dir): + raise sol_ex.UserDataClassNotImplemented() diff --git a/tacker/tests/functional/sol_v2/test_individual_vnfc_mgmt.py b/tacker/tests/functional/sol_v2/test_individual_vnfc_mgmt.py new file mode 100644 index 000000000..08f963035 --- /dev/null +++ b/tacker/tests/functional/sol_v2/test_individual_vnfc_mgmt.py @@ -0,0 +1,433 @@ +# Copyright (C) 2022 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. + +import os +import time + +from tacker.tests.functional.sol_v2_common import paramgen +from tacker.tests.functional.sol_v2_common import test_vnflcm_basic_common + + +class IndividualVnfcMgmtTest(test_vnflcm_basic_common.CommonVnfLcmTest): + + @classmethod + def setUpClass(cls): + super(IndividualVnfcMgmtTest, cls).setUpClass() + cur_dir = os.path.dirname(__file__) + # tacker/tests/functional/sol_v2(here) + # /etc + 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)) + + # tacker/tests/functional/sol_v2(here) + # /sol_refactored + userdata_dir = os.path.join( + cur_dir, "../../../sol_refactored/infra_drivers/openstack") + userdata_file = "userdata_standard.py" + userdata_path = os.path.abspath( + os.path.join(userdata_dir, userdata_file)) + + # main vnf package for StandardUserData test + pkg_path_1 = os.path.join(cur_dir, + "../sol_v2_common/samples/userdata_standard") + cls.vnf_pkg_1, cls.vnfd_id_1 = cls.create_vnf_package( + pkg_path_1, image_path=image_path, userdata_path=userdata_path) + + # for change_vnfpkg test + pkg_path_2 = os.path.join(cur_dir, + "../sol_v2_common/samples/userdata_standard_change_vnfpkg") + cls.vnf_pkg_2, cls.vnfd_id_2 = cls.create_vnf_package( + pkg_path_2, image_path=image_path, userdata_path=userdata_path) + + @classmethod + def tearDownClass(cls): + super(IndividualVnfcMgmtTest, cls).tearDownClass() + cls.delete_vnf_package(cls.vnf_pkg_1) + cls.delete_vnf_package(cls.vnf_pkg_2) + + def setUp(self): + super().setUp() + + def _put_fail_file(self, operation): + with open(f'/tmp/{operation}', 'w'): + pass + + def _rm_fail_file(self, operation): + os.remove(f'/tmp/{operation}') + + def _get_vdu_indexes(self, inst, vdu): + return { + vnfc['metadata'].get('vdu_idx') + for vnfc in inst['instantiatedVnfInfo']['vnfcResourceInfo'] + if vnfc['vduId'] == vdu + } + + def _get_vnfc_by_vdu_index(self, inst, vdu, index): + for vnfc in inst['instantiatedVnfInfo']['vnfcResourceInfo']: + if (vnfc['vduId'] == vdu and + vnfc['metadata'].get('vdu_idx') == index): + return vnfc + + def _get_vnfc_id(self, inst, vdu, index): + vnfc = self._get_vnfc_by_vdu_index(inst, vdu, index) + return vnfc['id'] + + def _get_vnfc_cp_net_id(self, inst, vdu, index, cp): + vnfc = self._get_vnfc_by_vdu_index(inst, vdu, index) + for cp_info in vnfc['vnfcCpInfo']: + if cp_info['cpdId'] == cp: + # must be found + ext_cp_id = cp_info['vnfExtCpId'] + break + for ext_vl in inst['instantiatedVnfInfo']['extVirtualLinkInfo']: + for port in ext_vl['extLinkPorts']: + if port['cpInstanceId'] == ext_cp_id: + # must be found + return ext_vl['resourceHandle']['resourceId'] + + def _get_vnfc_image(self, inst, vdu, index): + vnfc = self._get_vnfc_by_vdu_index(inst, vdu, index) + for key, value in vnfc['metadata'].items(): + if key.startswith('image-'): + # must be found + return value + + def _delete_instance(self, inst_id): + for _ in range(3): + resp, body = self.delete_vnf_instance(inst_id) + if resp.status_code == 204: # OK + return + elif resp.status_code == 409: + # may happen. there is a bit time between lcmocc become + # COMPLETED and lock of terminate is freed. + time.sleep(3) + else: + break + self.assertTrue(False) + + def test_basic_operations(self): + """Test basic operations using StandardUserData + + * Note: + This test focuses whether StandardUserData works well. + This test does not check overall items of APIs at all. + + * About LCM operations: + This test includes the following operations. + - Create VNF instance + - 1. Instantiate VNF instance + - Show VNF instance / check + - 2. Scale out operation + - Show VNF instance / check + - 3. Heal operation + - Show VNF instance / check + - 4. Scale in operation + - Show VNF instance / check + - 5. Change_ext_conn operation + - Show VNF instance / check + - 6. Change_vnfpkg operation + - Show VNF instance / check + - Terminate VNF instance + - Delete VNF instance + """ + + net_ids = self.get_network_ids(['net0', 'net1', 'net_mgmt']) + subnet_ids = self.get_subnet_ids(['subnet0', 'subnet1']) + + # Create VNF instance + create_req = paramgen.sample3_create(self.vnfd_id_1) + resp, body = self.create_vnf_instance(create_req) + self.assertEqual(201, resp.status_code) + inst_id = body['id'] + + # 1. Instantiate VNF instance + instantiate_req = paramgen.sample3_instantiate( + net_ids, subnet_ids, self.auth_url) + resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) + self.assertEqual(202, resp.status_code) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # Show VNF instance + resp, inst_1 = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + + # check number of VDUs and indexes + self.assertEqual({0}, self._get_vdu_indexes(inst_1, 'VDU1')) + self.assertEqual({0}, self._get_vdu_indexes(inst_1, 'VDU2')) + + # 2. Scale out operation + scale_out_req = paramgen.sample3_scale_out() + resp, body = self.scale_vnf_instance(inst_id, scale_out_req) + self.assertEqual(202, resp.status_code) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # Show VNF instance + resp, inst_2 = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + + # check number of VDUs and indexes + self.assertEqual({0, 1, 2}, self._get_vdu_indexes(inst_2, 'VDU1')) + self.assertEqual({0}, self._get_vdu_indexes(inst_2, 'VDU2')) + + # 3. Heal operation + heal_req = paramgen.sample3_heal() + # pick up VDU1-1 to heal + vnfc_id = self._get_vnfc_id(inst_2, 'VDU1', 1) + heal_req['vnfcInstanceId'] = [f'VDU1-{vnfc_id}'] + resp, body = self.heal_vnf_instance(inst_id, heal_req) + self.assertEqual(202, resp.status_code) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # Show VNF instance + resp, inst_3 = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + + # check id of VDU1-1 is changed and other are not. + self.assertEqual(self._get_vnfc_id(inst_2, 'VDU1', 0), + self._get_vnfc_id(inst_3, 'VDU1', 0)) + self.assertNotEqual(self._get_vnfc_id(inst_2, 'VDU1', 1), + self._get_vnfc_id(inst_3, 'VDU1', 1)) + self.assertEqual(self._get_vnfc_id(inst_2, 'VDU1', 2), + self._get_vnfc_id(inst_3, 'VDU1', 2)) + self.assertEqual(self._get_vnfc_id(inst_2, 'VDU2', 0), + self._get_vnfc_id(inst_3, 'VDU2', 0)) + + # 4. Scale in operation + scale_in_req = paramgen.sample3_scale_in() + resp, body = self.scale_vnf_instance(inst_id, scale_in_req) + self.assertEqual(202, resp.status_code) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # Show VNF instance + resp, inst_4 = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + + # check VDU1-2 is removed. other are not changed. + self.assertEqual({0, 1}, self._get_vdu_indexes(inst_4, 'VDU1')) + self.assertEqual({0}, self._get_vdu_indexes(inst_4, 'VDU2')) + self.assertEqual(self._get_vnfc_id(inst_3, 'VDU1', 0), + self._get_vnfc_id(inst_4, 'VDU1', 0)) + self.assertEqual(self._get_vnfc_id(inst_3, 'VDU1', 1), + self._get_vnfc_id(inst_4, 'VDU1', 1)) + self.assertEqual(self._get_vnfc_id(inst_3, 'VDU2', 0), + self._get_vnfc_id(inst_4, 'VDU2', 0)) + + # 5. Change_ext_conn operation + change_ext_conn_req = paramgen.sample3_change_ext_conn(net_ids) + resp, body = self.change_ext_conn(inst_id, change_ext_conn_req) + self.assertEqual(202, resp.status_code) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # Show VNF instance + resp, inst_5 = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + + # check VDU1_CP1 is changed to net0 from net1. other are not changed. + self.assertEqual(net_ids['net1'], + self._get_vnfc_cp_net_id(inst_4, 'VDU1', 0, 'VDU1_CP1')) + self.assertEqual(net_ids['net1'], + self._get_vnfc_cp_net_id(inst_4, 'VDU1', 1, 'VDU1_CP1')) + self.assertEqual(net_ids['net1'], + self._get_vnfc_cp_net_id(inst_4, 'VDU2', 0, 'VDU2_CP1')) + self.assertEqual(net_ids['net0'], + self._get_vnfc_cp_net_id(inst_5, 'VDU1', 0, 'VDU1_CP1')) + self.assertEqual(net_ids['net0'], + self._get_vnfc_cp_net_id(inst_5, 'VDU1', 1, 'VDU1_CP1')) + self.assertEqual(net_ids['net1'], + self._get_vnfc_cp_net_id(inst_5, 'VDU2', 0, 'VDU2_CP1')) + + # 6. Change_vnfpkg operation + change_vnfpkg_req = paramgen.sample4_change_vnfpkg(self.vnfd_id_2) + resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req) + self.assertEqual(202, resp.status_code) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # Show VNF instance + resp, inst_6 = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + + # check vnfdId is changed + self.assertEqual(self.vnfd_id_2, inst_6['vnfdId']) + # check images are changed + self.assertNotEqual(self._get_vnfc_image(inst_5, 'VDU1', 0), + self._get_vnfc_image(inst_6, 'VDU1', 0)) + self.assertNotEqual(self._get_vnfc_image(inst_5, 'VDU1', 1), + self._get_vnfc_image(inst_6, 'VDU1', 1)) + self.assertNotEqual(self._get_vnfc_image(inst_5, 'VDU2', 0), + self._get_vnfc_image(inst_6, 'VDU2', 0)) + + # Terminate VNF instance + terminate_req = paramgen.sample4_terminate() + resp, body = self.terminate_vnf_instance(inst_id, terminate_req) + self.assertEqual(202, resp.status_code) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # Delete VNF instance + self._delete_instance(inst_id) + + def test_rollback_operations(self): + """Test rollback operations using StandardUserData + + * Note: + This test focuses whether StandardUserData works well. + This test does not check overall items of APIs at all. + + * About LCM operations: + This test includes the following operations. + - Create VNF instance + - Instantiate VNF instance + - Show VNF instance + - 1. Scale out operation => FAILED_TEMP + - Rollback + - Show VNF instance / check + - 2. Change_ext_conn operation => FAILED_TEMP + - Rollback + - Show VNF instance / check + - 3. Change_vnfpkg operation => FAILED_TEMP + - Rollback + - Show VNF instance / check + - Terminate VNF instance + - Delete VNF instance + """ + + net_ids = self.get_network_ids(['net0', 'net1', 'net_mgmt']) + subnet_ids = self.get_subnet_ids(['subnet0', 'subnet1']) + + # Create VNF instance + create_req = paramgen.sample3_create(self.vnfd_id_1) + resp, body = self.create_vnf_instance(create_req) + self.assertEqual(201, resp.status_code) + inst_id = body['id'] + + # Instantiate VNF instance + instantiate_req = paramgen.sample3_instantiate( + net_ids, subnet_ids, self.auth_url) + resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req) + self.assertEqual(202, resp.status_code) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # Show VNF instance + resp, inst_0 = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + + # 1. Scale out operation + self._put_fail_file('scale_end') + scale_out_req = paramgen.sample3_scale_out() + resp, body = self.scale_vnf_instance(inst_id, scale_out_req) + self.assertEqual(202, resp.status_code) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_failed_temp(lcmocc_id) + self._rm_fail_file('scale_end') + + # Rollback + resp, body = self.rollback_lcmocc(lcmocc_id) + self.assertEqual(202, resp.status_code) + self.wait_lcmocc_rolled_back(lcmocc_id) + + # Show VNF instance + resp, inst_1 = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + + # check number of vnfc and its id are not changed. + self.assertEqual(self._get_vdu_indexes(inst_0, 'VDU1'), + self._get_vdu_indexes(inst_1, 'VDU1')) + self.assertEqual(self._get_vdu_indexes(inst_0, 'VDU2'), + self._get_vdu_indexes(inst_1, 'VDU2')) + self.assertEqual(self._get_vnfc_id(inst_0, 'VDU1', 0), + self._get_vnfc_id(inst_1, 'VDU1', 0)) + self.assertEqual(self._get_vnfc_id(inst_0, 'VDU2', 0), + self._get_vnfc_id(inst_1, 'VDU2', 0)) + + # 2. Change_ext_conn operation + self._put_fail_file('change_external_connectivity_end') + change_ext_conn_req = paramgen.sample3_change_ext_conn(net_ids) + resp, body = self.change_ext_conn(inst_id, change_ext_conn_req) + self.assertEqual(202, resp.status_code) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_failed_temp(lcmocc_id) + self._rm_fail_file('change_external_connectivity_end') + + # Rollback + resp, body = self.rollback_lcmocc(lcmocc_id) + self.assertEqual(202, resp.status_code) + self.wait_lcmocc_rolled_back(lcmocc_id) + + # Show VNF instance + resp, inst_2 = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + + # check network of extVL cps are not changed. (i.e. net1) + self.assertEqual(net_ids['net1'], + self._get_vnfc_cp_net_id(inst_2, 'VDU1', 0, 'VDU1_CP1')) + self.assertEqual(net_ids['net1'], + self._get_vnfc_cp_net_id(inst_2, 'VDU2', 0, 'VDU2_CP1')) + + # 3. Change_vnfpkg operation + self._put_fail_file('change_vnfpkg') + change_vnfpkg_req = paramgen.sample4_change_vnfpkg(self.vnfd_id_2) + resp, body = self.change_vnfpkg(inst_id, change_vnfpkg_req) + self.assertEqual(202, resp.status_code) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_failed_temp(lcmocc_id) + self._rm_fail_file('change_vnfpkg') + + # Rollback + resp, body = self.rollback_lcmocc(lcmocc_id) + self.assertEqual(202, resp.status_code) + self.wait_lcmocc_rolled_back(lcmocc_id) + + # Show VNF instance + resp, inst_3 = self.show_vnf_instance(inst_id) + self.assertEqual(200, resp.status_code) + + # check vnfdId is not changed + self.assertEqual(self.vnfd_id_1, inst_3['vnfdId']) + # check images are not changed + self.assertEqual(self._get_vnfc_image(inst_2, 'VDU1', 0), + self._get_vnfc_image(inst_3, 'VDU1', 0)) + self.assertEqual(self._get_vnfc_image(inst_2, 'VDU2', 0), + self._get_vnfc_image(inst_3, 'VDU2', 0)) + + # Terminate VNF instance + terminate_req = paramgen.sample3_terminate() + resp, body = self.terminate_vnf_instance(inst_id, terminate_req) + self.assertEqual(202, resp.status_code) + + lcmocc_id = os.path.basename(resp.headers['Location']) + self.wait_lcmocc_complete(lcmocc_id) + + # Delete VNF instance + self._delete_instance(inst_id) diff --git a/tacker/tests/functional/sol_v2_common/base_v2.py b/tacker/tests/functional/sol_v2_common/base_v2.py index 28c9f9527..0edf84fbc 100644 --- a/tacker/tests/functional/sol_v2_common/base_v2.py +++ b/tacker/tests/functional/sol_v2_common/base_v2.py @@ -107,11 +107,12 @@ class BaseSolV2Test(base.BaseTestCase): @classmethod def create_vnf_package(cls, sample_path, user_data={}, - image_path=None, nfvo=False): + image_path=None, nfvo=False, userdata_path=None): vnfd_id = uuidutils.generate_uuid() tmp_dir = tempfile.mkdtemp() - utils.make_zip(sample_path, tmp_dir, vnfd_id, image_path) + utils.make_zip(sample_path, tmp_dir, vnfd_id, image_path, + userdata_path) zip_file_name = os.path.basename(os.path.abspath(sample_path)) + ".zip" zip_file_path = os.path.join(tmp_dir, zip_file_name) diff --git a/tacker/tests/functional/sol_v2_common/paramgen.py b/tacker/tests/functional/sol_v2_common/paramgen.py index 75bac1951..c8bd9f212 100644 --- a/tacker/tests/functional/sol_v2_common/paramgen.py +++ b/tacker/tests/functional/sol_v2_common/paramgen.py @@ -933,3 +933,197 @@ def change_vnfpkg(vnfd_id): }] } } + + +# sample3 is used for tests of StandardUserData +# +def sample3_create(vnfd_id): + return { + "vnfdId": vnfd_id, + "vnfInstanceName": "sample3", + "vnfInstanceDescription": "test for StandardUserData" + } + + +def sample3_terminate(): + return { + "terminationType": "FORCEFUL" + } + + +def sample3_instantiate(net_ids, subnet_ids, auth_url): + ext_vl_1 = { + "id": uuidutils.generate_uuid(), + "resourceId": net_ids['net1'], + "extCps": [ + { + "cpdId": "VDU1_CP1", + "cpConfig": { + "VDU1_CP2_1": { + "cpProtocolData": [{ + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [{ + "type": "IPV4", + "numDynamicAddresses": 1}]}}]} + } + }, + { + "cpdId": "VDU2_CP1", + "cpConfig": { + "VDU2_CP2_1": { + "cpProtocolData": [{ + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [{ + "type": "IPV4", + "numDynamicAddresses": 1, + "subnetId": subnet_ids['subnet1']}]}}]} + } + } + ] + } + + return { + "flavourId": "simple", + "instantiationLevelId": "instantiation_level_1", + "extVirtualLinks": [ext_vl_1], + "extManagedVirtualLinks": [ + { + "id": uuidutils.generate_uuid(), + "vnfVirtualLinkDescId": "internalVL1", + "resourceId": net_ids['net_mgmt'] + }, + ], + "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" + } + } + }, + "additionalParams": { + "lcm-operation-user-data": "./UserData/userdata_standard.py", + "lcm-operation-user-data-class": "StandardUserData" + } + } + + +def sample3_scale_out(): + return { + "type": "SCALE_OUT", + "aspectId": "VDU1_scale", + "numberOfSteps": 2, + "additionalParams": { + "lcm-operation-user-data": "./UserData/userdata_standard.py", + "lcm-operation-user-data-class": "StandardUserData" + } + } + + +def sample3_scale_in(): + return { + "type": "SCALE_IN", + "aspectId": "VDU1_scale", + "numberOfSteps": 1, + "additionalParams": { + "lcm-operation-user-data": "./UserData/userdata_standard.py", + "lcm-operation-user-data-class": "StandardUserData" + } + } + + +def sample3_heal(): + return { + "vnfcInstanceId": [], # should be filled + "additionalParams": { + "lcm-operation-user-data": "./UserData/userdata_standard.py", + "lcm-operation-user-data-class": "StandardUserData" + } + } + + +def sample3_change_ext_conn(net_ids): + return { + "extVirtualLinks": [ + { + "id": uuidutils.generate_uuid(), + "resourceId": net_ids['net0'], + "extCps": [ + { + "cpdId": "VDU1_CP1", + "cpConfig": { + "VDU1_CP2_1": { + "cpProtocolData": [{ + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [{ + "type": "IPV4", + "numDynamicAddresses": 1}]}}]} + } + } + ] + } + ], + "additionalParams": { + "lcm-operation-user-data": "./UserData/userdata_standard.py", + "lcm-operation-user-data-class": "StandardUserData" + } + } + + +# sample4 is for change_vnfpkg test of StandardUserData +# +def sample4_change_vnfpkg(vnfd_id): + return { + "vnfdId": vnfd_id, + "additionalParams": { + "upgrade_type": "RollingUpdate", + "lcm-operation-coordinate-new-vnf": "./Scripts/coordinate_vnf.py", + "lcm-operation-coordinate-old-vnf": "./Scripts/coordinate_vnf.py", + "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" + } + } + ], + "lcm-operation-user-data": "./UserData/userdata_standard.py", + "lcm-operation-user-data-class": "StandardUserData" + } + } + + +def sample4_terminate(): + return { + "terminationType": "FORCEFUL" + } diff --git a/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/BaseHOT/simple/nested/VDU1.yaml b/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/BaseHOT/simple/nested/VDU1.yaml new file mode 100644 index 000000000..c0fdb03bb --- /dev/null +++ b/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/BaseHOT/simple/nested/VDU1.yaml @@ -0,0 +1,53 @@ +heat_template_version: 2013-05-23 +description: 'VDU1 HOT for Sample VNF' + +parameters: + flavor: + type: string + image-VDU1: + type: string + net1: + type: string + net2: + type: string + net3: + type: string + affinity: + type: string + +resources: + VDU1: + type: OS::Nova::Server + properties: + flavor: { get_param: flavor } + name: VDU1 + image: { get_param: image-VDU1 } + networks: + - port: + get_resource: VDU1_CP1 +# replace the following line to Port ID when extmanagedVLs' Ports are +# specified in instantiatevnfrequest + - port: + get_resource: VDU1_CP2 + - port: + get_resource: VDU1_CP3 + scheduler_hints: + group: { get_param: affinity } + +# extVL without FixedIP or with numDynamicAddresses + VDU1_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: net1 } + +# CPs of internal VLs are deleted when extmanagedVLs and port are +# specified in instantiatevnfrequest + VDU1_CP2: + type: OS::Neutron::Port + properties: + network: { get_param: net2 } + + VDU1_CP3: + type: OS::Neutron::Port + properties: + network: { get_param: net3 } diff --git a/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/BaseHOT/simple/nested/VDU2.yaml b/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/BaseHOT/simple/nested/VDU2.yaml new file mode 100644 index 000000000..ee3415333 --- /dev/null +++ b/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/BaseHOT/simple/nested/VDU2.yaml @@ -0,0 +1,69 @@ +heat_template_version: 2013-05-23 +description: 'VDU2 HOT for Sample VNF' + +parameters: + flavor: + type: string + image-VDU2-VirtualStorage: + type: string + net1: + type: string + net2: + type: string + net3: + type: string + subnet1: + type: string + affinity: + type: string + +resources: + VDU2: + type: OS::Nova::Server + properties: + flavor: { get_param: flavor } + name: VDU2 + block_device_mapping_v2: [{"volume_id": { get_resource: VDU2-VirtualStorage }}] + networks: + - port: + get_resource: VDU2_CP1 +# replace the following line to Port ID when extmanagedVLs' Ports are +# specified in instantiatevnfrequest + - port: + get_resource: VDU2_CP2 + - port: + get_resource: VDU2_CP3 + scheduler_hints: + group: {get_param: affinity } + + VDU2-VirtualStorage: + type: OS::Cinder::Volume + properties: + image: { get_param: image-VDU2-VirtualStorage } + size: 1 + volume_type: { get_resource: multi } + multi: + type: OS::Cinder::VolumeType + properties: + name: VDU2-multi + metadata: { multiattach: " True" } + +# extVL with numDynamicAddresses and subnet + VDU2_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: net1 } + fixed_ips: + - subnet: { get_param: subnet1 } + +# CPs of internal VLs are deleted when extmanagedVLs and port are +# specified in instantiatevnfrequest + VDU2_CP2: + type: OS::Neutron::Port + properties: + network: { get_param: net2 } + + VDU2_CP3: + type: OS::Neutron::Port + properties: + network: { get_param: net3 } diff --git a/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/BaseHOT/simple/sample3.yaml b/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/BaseHOT/simple/sample3.yaml new file mode 100644 index 000000000..bad8030ac --- /dev/null +++ b/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/BaseHOT/simple/sample3.yaml @@ -0,0 +1,57 @@ +heat_template_version: 2013-05-23 +description: 'Simple Base HOT for Sample VNF' + +parameters: + nfv: + type: json + +resources: + VDU1: + type: VDU1.yaml + properties: + flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] } + image-VDU1: { get_param: [ nfv, VDU, VDU1, vcImageId ] } + net1: { get_param: [ nfv, CP, VDU1_CP1, network ] } + net2: { get_resource: internalVL1 } + net3: { get_resource: internalVL2 } + affinity: { get_resource: nfvi_node_affinity } + + VDU2: + type: VDU2.yaml + properties: + flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] } + image-VDU2-VirtualStorage: { get_param: [ nfv, VDU, VDU2-VirtualStorage, vcImageId ] } + net1: { get_param: [ nfv, CP, VDU2_CP1, network ] } + subnet1: { get_param: [nfv, CP, VDU2_CP1, fixed_ips, 0, subnet ]} + net2: { get_resource: internalVL1 } + net3: { get_resource: internalVL2 } + affinity: { get_resource: nfvi_node_affinity } + +# delete the following lines when extmanagedVLs are specified in instantiatevnfrequest + internalVL1: + type: OS::Neutron::Net + internalVL2: + type: OS::Neutron::Net + + internalVL1_subnet: + type: OS::Neutron::Subnet + properties: + ip_version: 4 + network: + get_resource: internalVL1 + cidr: 192.168.3.0/24 + internalVL2_subnet: + type: OS::Neutron::Subnet + properties: + ip_version: 4 + network: + get_resource: internalVL2 + cidr: 192.168.4.0/24 + + nfvi_node_affinity: + type: OS::Nova::ServerGroup + properties: + name: nfvi_node_affinity + policies: [ 'affinity' ] + +outputs: {} diff --git a/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/Definitions/v2_sample3_df_simple.yaml b/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/Definitions/v2_sample3_df_simple.yaml new file mode 100644 index 000000000..d3efc8796 --- /dev/null +++ b/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/Definitions/v2_sample3_df_simple.yaml @@ -0,0 +1,357 @@ +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 + - v2_sample3_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_start: + implementation: sample-script + instantiate_end: + implementation: sample-script + terminate_start: + implementation: sample-script + terminate_end: + implementation: sample-script + scale_start: + implementation: sample-script + scale_end: + implementation: sample-script + heal_start: + implementation: sample-script + heal_end: + implementation: sample-script + change_external_connectivity_start: + implementation: sample-script + change_external_connectivity_end: + implementation: sample-script + modify_information_start: + implementation: sample-script + modify_information_end: + implementation: sample-script + artifacts: + sample-script: + description: Sample script + type: tosca.artifacts.Implementation.Python + file: ../Scripts/sample_script.py + + 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: 12 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: 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 + + VDU1_CP2: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 1 + requirements: + - virtual_binding: VDU1 + - virtual_link: internalVL1 + + VDU1_CP3: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 2 + requirements: + - virtual_binding: VDU1 + - virtual_link: internalVL2 + + VDU2_CP1: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 0 + requirements: + - virtual_binding: VDU2 + + VDU2_CP2: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 1 + requirements: + - virtual_binding: VDU2 + - virtual_link: internalVL1 + + VDU2_CP3: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 2 + requirements: + - virtual_binding: VDU2 + - virtual_link: internalVL2 + + internalVL1: + type: tosca.nodes.nfv.VnfVirtualLink + properties: + connectivity_type: + layer_protocols: [ ipv4 ] + description: External Managed Virtual link in the VNF + vl_profile: + max_bitrate_requirements: + root: 1048576 + leaf: 1048576 + min_bitrate_requirements: + root: 1048576 + leaf: 1048576 + virtual_link_protocol_data: + - associated_layer_protocol: ipv4 + l3_protocol_data: + ip_version: ipv4 + cidr: 192.168.3.0/24 + + internalVL2: + type: tosca.nodes.nfv.VnfVirtualLink + properties: + connectivity_type: + layer_protocols: [ ipv4 ] + description: External Managed Virtual link in the VNF + vl_profile: + max_bitrate_requirements: + root: 1048576 + leaf: 1048576 + min_bitrate_requirements: + root: 1048576 + leaf: 1048576 + virtual_link_protocol_data: + - associated_layer_protocol: ipv4 + l3_protocol_data: + ip_version: ipv4 + cidr: 192.168.4.0/24 + + groups: + affinityOrAntiAffinityGroup1: + type: tosca.groups.nfv.PlacementGroup + members: [ VDU1, 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: 1 + 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: 1 + 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: 2 + 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 ] + + - internalVL1_instantiation_levels: + type: tosca.policies.nfv.VirtualLinkInstantiationLevels + properties: + levels: + instantiation_level_1: + bitrate_requirements: + root: 1048576 + leaf: 1048576 + instantiation_level_2: + bitrate_requirements: + root: 1048576 + leaf: 1048576 + targets: [ internalVL1 ] + + - internalVL2_instantiation_levels: + type: tosca.policies.nfv.VirtualLinkInstantiationLevels + properties: + levels: + instantiation_level_1: + bitrate_requirements: + root: 1048576 + leaf: 1048576 + instantiation_level_2: + bitrate_requirements: + root: 1048576 + leaf: 1048576 + targets: [ internalVL2 ] + + - policy_antiaffinity_group: + type: tosca.policies.nfv.AntiAffinityRule + targets: [ affinityOrAntiAffinityGroup1 ] + properties: + scope: nfvi_node diff --git a/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/Definitions/v2_sample3_top.vnfd.yaml b/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/Definitions/v2_sample3_top.vnfd.yaml new file mode 100644 index 000000000..af82904b3 --- /dev/null +++ b/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/Definitions/v2_sample3_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 + - v2_sample3_types.yaml + - v2_sample3_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_common/samples/userdata_standard/contents/Definitions/v2_sample3_types.yaml b/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/Definitions/v2_sample3_types.yaml new file mode 100644 index 000000000..8fff47c24 --- /dev/null +++ b/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/Definitions/v2_sample3_types.yaml @@ -0,0 +1,55 @@ +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 + - virtual_link_internal: + 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_common/samples/userdata_standard/contents/Scripts/coordinate_vnf.py b/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/Scripts/coordinate_vnf.py new file mode 100644 index 000000000..d7057da6d --- /dev/null +++ b/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/Scripts/coordinate_vnf.py @@ -0,0 +1,46 @@ +# Copyright (C) 2022 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. + +import os +import pickle +import sys + + +class FailScript(object): + def __init__(self, vnfc_param): + self.vnfc_param = vnfc_param + + def run(self): + operation = 'change_vnfpkg' + if self.vnfc_param['is_rollback']: + operation += '_rollback' + if os.path.exists(f'/tmp/{operation}'): + raise Exception(f'test {operation} error') + + +def main(): + vnfc_param = pickle.load(sys.stdin.buffer) + script = FailScript(vnfc_param) + script.run() + + +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_common/samples/userdata_standard/contents/Scripts/sample_script.py b/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/Scripts/sample_script.py new file mode 100644 index 000000000..cb98d4656 --- /dev/null +++ b/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/Scripts/sample_script.py @@ -0,0 +1,68 @@ +# Copyright (C) 2022 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. + +import functools +import os +import pickle +import sys + + +class FailScript(object): + """Define error method for each operation + + For example: + + def instantiate_start(self): + if os.path.exists('/tmp/instantiate_start') + raise Exception('test instantiate_start error') + """ + + def __init__(self, req, inst, grant_req, grant, csar_dir): + self.req = req + self.inst = inst + self.grant_req = grant_req + self.grant = grant + self.csar_dir = csar_dir + + def _fail(self, method): + if os.path.exists(f'/tmp/{method}'): + raise Exception(f'test {method} error') + + def __getattr__(self, name): + return functools.partial(self._fail, name) + + +def main(): + script_dict = pickle.load(sys.stdin.buffer) + + operation = script_dict['operation'] + 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'] + + script = FailScript(req, inst, grant_req, grant, csar_dir) + getattr(script, operation)() + + +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_common/samples/userdata_standard/contents/TOSCA-Metadata/TOSCA.meta b/tacker/tests/functional/sol_v2_common/samples/userdata_standard/contents/TOSCA-Metadata/TOSCA.meta new file mode 100644 index 000000000..c96ce5f5c --- /dev/null +++ b/tacker/tests/functional/sol_v2_common/samples/userdata_standard/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/v2_sample3_top.vnfd.yaml diff --git a/tacker/tests/functional/sol_v2_common/samples/userdata_standard/pkggen.py b/tacker/tests/functional/sol_v2_common/samples/userdata_standard/pkggen.py new file mode 100644 index 000000000..39e69bd56 --- /dev/null +++ b/tacker/tests/functional/sol_v2_common/samples/userdata_standard/pkggen.py @@ -0,0 +1,83 @@ +# Copyright (C) 2022 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. + +import json +import os +import shutil +import tempfile + +from oslo_utils import uuidutils + +from tacker.tests.functional.sol_v2_common import paramgen +from tacker.tests.functional.sol_v2_common import utils + + +zip_file_name = os.path.basename(os.path.abspath(".")) + '.zip' +tmp_dir = tempfile.mkdtemp() +vnfd_id = uuidutils.generate_uuid() + +# tacker/tests/etc... +# /functional/sol_v2_common/samples/smapleX +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) + +# tacker/sol_refactored/infra_drivers/openstack/userdata_standard.py +# /tests/functional/sol_v2_common/samples/smapleX +userdata_dir = "../../../../../sol_refactored/infra_drivers/openstack/" +userdata_file = "userdata_standard.py" +userdata_path = os.path.abspath(userdata_dir + userdata_file) + +utils.make_zip(".", tmp_dir, vnfd_id, image_path=image_path, + userdata_path=userdata_path) + +shutil.copy(os.path.join(tmp_dir, zip_file_name), ".") +shutil.rmtree(tmp_dir) + +create_req = paramgen.sample3_create(vnfd_id) +terminate_req = paramgen.sample3_terminate() + +net_ids = utils.get_network_ids(['net0', 'net1', 'net_mgmt']) +subnet_ids = utils.get_subnet_ids(['subnet0', 'subnet1']) + +instantiate_req = paramgen.sample3_instantiate( + net_ids, subnet_ids, "http://localhost/identity/v3") + +scale_out_req = paramgen.sample3_scale_out() +scale_in_req = paramgen.sample3_scale_in() +heal_req = paramgen.sample3_heal() +change_ext_conn_req = paramgen.sample3_change_ext_conn(net_ids) + +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", "w") as f: + f.write(json.dumps(instantiate_req, indent=2)) + +with open("scale_out_req", "w") as f: + f.write(json.dumps(scale_out_req, indent=2)) + +with open("scale_in_req", "w") as f: + f.write(json.dumps(scale_in_req, indent=2)) + +# NOTE: vnfcInstanceId should be filled by hand +with open("heal_req", "w") as f: + f.write(json.dumps(heal_req, indent=2)) + +with open("change_ext_conn_req", "w") as f: + f.write(json.dumps(change_ext_conn_req, indent=2)) diff --git a/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/BaseHOT/simple/nested/VDU1.yaml b/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/BaseHOT/simple/nested/VDU1.yaml new file mode 100644 index 000000000..c0fdb03bb --- /dev/null +++ b/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/BaseHOT/simple/nested/VDU1.yaml @@ -0,0 +1,53 @@ +heat_template_version: 2013-05-23 +description: 'VDU1 HOT for Sample VNF' + +parameters: + flavor: + type: string + image-VDU1: + type: string + net1: + type: string + net2: + type: string + net3: + type: string + affinity: + type: string + +resources: + VDU1: + type: OS::Nova::Server + properties: + flavor: { get_param: flavor } + name: VDU1 + image: { get_param: image-VDU1 } + networks: + - port: + get_resource: VDU1_CP1 +# replace the following line to Port ID when extmanagedVLs' Ports are +# specified in instantiatevnfrequest + - port: + get_resource: VDU1_CP2 + - port: + get_resource: VDU1_CP3 + scheduler_hints: + group: { get_param: affinity } + +# extVL without FixedIP or with numDynamicAddresses + VDU1_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: net1 } + +# CPs of internal VLs are deleted when extmanagedVLs and port are +# specified in instantiatevnfrequest + VDU1_CP2: + type: OS::Neutron::Port + properties: + network: { get_param: net2 } + + VDU1_CP3: + type: OS::Neutron::Port + properties: + network: { get_param: net3 } diff --git a/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/BaseHOT/simple/nested/VDU2.yaml b/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/BaseHOT/simple/nested/VDU2.yaml new file mode 100644 index 000000000..ee3415333 --- /dev/null +++ b/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/BaseHOT/simple/nested/VDU2.yaml @@ -0,0 +1,69 @@ +heat_template_version: 2013-05-23 +description: 'VDU2 HOT for Sample VNF' + +parameters: + flavor: + type: string + image-VDU2-VirtualStorage: + type: string + net1: + type: string + net2: + type: string + net3: + type: string + subnet1: + type: string + affinity: + type: string + +resources: + VDU2: + type: OS::Nova::Server + properties: + flavor: { get_param: flavor } + name: VDU2 + block_device_mapping_v2: [{"volume_id": { get_resource: VDU2-VirtualStorage }}] + networks: + - port: + get_resource: VDU2_CP1 +# replace the following line to Port ID when extmanagedVLs' Ports are +# specified in instantiatevnfrequest + - port: + get_resource: VDU2_CP2 + - port: + get_resource: VDU2_CP3 + scheduler_hints: + group: {get_param: affinity } + + VDU2-VirtualStorage: + type: OS::Cinder::Volume + properties: + image: { get_param: image-VDU2-VirtualStorage } + size: 1 + volume_type: { get_resource: multi } + multi: + type: OS::Cinder::VolumeType + properties: + name: VDU2-multi + metadata: { multiattach: " True" } + +# extVL with numDynamicAddresses and subnet + VDU2_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: net1 } + fixed_ips: + - subnet: { get_param: subnet1 } + +# CPs of internal VLs are deleted when extmanagedVLs and port are +# specified in instantiatevnfrequest + VDU2_CP2: + type: OS::Neutron::Port + properties: + network: { get_param: net2 } + + VDU2_CP3: + type: OS::Neutron::Port + properties: + network: { get_param: net3 } diff --git a/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/BaseHOT/simple/sample4.yaml b/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/BaseHOT/simple/sample4.yaml new file mode 100644 index 000000000..bad8030ac --- /dev/null +++ b/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/BaseHOT/simple/sample4.yaml @@ -0,0 +1,57 @@ +heat_template_version: 2013-05-23 +description: 'Simple Base HOT for Sample VNF' + +parameters: + nfv: + type: json + +resources: + VDU1: + type: VDU1.yaml + properties: + flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] } + image-VDU1: { get_param: [ nfv, VDU, VDU1, vcImageId ] } + net1: { get_param: [ nfv, CP, VDU1_CP1, network ] } + net2: { get_resource: internalVL1 } + net3: { get_resource: internalVL2 } + affinity: { get_resource: nfvi_node_affinity } + + VDU2: + type: VDU2.yaml + properties: + flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] } + image-VDU2-VirtualStorage: { get_param: [ nfv, VDU, VDU2-VirtualStorage, vcImageId ] } + net1: { get_param: [ nfv, CP, VDU2_CP1, network ] } + subnet1: { get_param: [nfv, CP, VDU2_CP1, fixed_ips, 0, subnet ]} + net2: { get_resource: internalVL1 } + net3: { get_resource: internalVL2 } + affinity: { get_resource: nfvi_node_affinity } + +# delete the following lines when extmanagedVLs are specified in instantiatevnfrequest + internalVL1: + type: OS::Neutron::Net + internalVL2: + type: OS::Neutron::Net + + internalVL1_subnet: + type: OS::Neutron::Subnet + properties: + ip_version: 4 + network: + get_resource: internalVL1 + cidr: 192.168.3.0/24 + internalVL2_subnet: + type: OS::Neutron::Subnet + properties: + ip_version: 4 + network: + get_resource: internalVL2 + cidr: 192.168.4.0/24 + + nfvi_node_affinity: + type: OS::Nova::ServerGroup + properties: + name: nfvi_node_affinity + policies: [ 'affinity' ] + +outputs: {} diff --git a/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/Definitions/v2_sample4_df_simple.yaml b/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/Definitions/v2_sample4_df_simple.yaml new file mode 100644 index 000000000..398f7dd8d --- /dev/null +++ b/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/Definitions/v2_sample4_df_simple.yaml @@ -0,0 +1,357 @@ +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 + - v2_sample4_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_start: + implementation: sample-script + instantiate_end: + implementation: sample-script + terminate_start: + implementation: sample-script + terminate_end: + implementation: sample-script + scale_start: + implementation: sample-script + scale_end: + implementation: sample-script + heal_start: + implementation: sample-script + heal_end: + implementation: sample-script + change_external_connectivity_start: + implementation: sample-script + change_external_connectivity_end: + implementation: sample-script + modify_information_start: + implementation: sample-script + modify_information_end: + implementation: sample-script + artifacts: + sample-script: + description: Sample script + type: tosca.artifacts.Implementation.Python + file: ../Scripts/sample_script.py + + 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: 12 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 + 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: 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: 12 GB + + VDU1_CP1: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 0 + requirements: + - virtual_binding: VDU1 + + VDU1_CP2: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 1 + requirements: + - virtual_binding: VDU1 + - virtual_link: internalVL1 + + VDU1_CP3: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 2 + requirements: + - virtual_binding: VDU1 + - virtual_link: internalVL2 + + VDU2_CP1: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 0 + requirements: + - virtual_binding: VDU2 + + VDU2_CP2: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 1 + requirements: + - virtual_binding: VDU2 + - virtual_link: internalVL1 + + VDU2_CP3: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 2 + requirements: + - virtual_binding: VDU2 + - virtual_link: internalVL2 + + internalVL1: + type: tosca.nodes.nfv.VnfVirtualLink + properties: + connectivity_type: + layer_protocols: [ ipv4 ] + description: External Managed Virtual link in the VNF + vl_profile: + max_bitrate_requirements: + root: 1048576 + leaf: 1048576 + min_bitrate_requirements: + root: 1048576 + leaf: 1048576 + virtual_link_protocol_data: + - associated_layer_protocol: ipv4 + l3_protocol_data: + ip_version: ipv4 + cidr: 192.168.3.0/24 + + internalVL2: + type: tosca.nodes.nfv.VnfVirtualLink + properties: + connectivity_type: + layer_protocols: [ ipv4 ] + description: External Managed Virtual link in the VNF + vl_profile: + max_bitrate_requirements: + root: 1048576 + leaf: 1048576 + min_bitrate_requirements: + root: 1048576 + leaf: 1048576 + virtual_link_protocol_data: + - associated_layer_protocol: ipv4 + l3_protocol_data: + ip_version: ipv4 + cidr: 192.168.4.0/24 + + groups: + affinityOrAntiAffinityGroup1: + type: tosca.groups.nfv.PlacementGroup + members: [ VDU1, 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: 1 + 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: 1 + 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: 2 + 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 ] + + - internalVL1_instantiation_levels: + type: tosca.policies.nfv.VirtualLinkInstantiationLevels + properties: + levels: + instantiation_level_1: + bitrate_requirements: + root: 1048576 + leaf: 1048576 + instantiation_level_2: + bitrate_requirements: + root: 1048576 + leaf: 1048576 + targets: [ internalVL1 ] + + - internalVL2_instantiation_levels: + type: tosca.policies.nfv.VirtualLinkInstantiationLevels + properties: + levels: + instantiation_level_1: + bitrate_requirements: + root: 1048576 + leaf: 1048576 + instantiation_level_2: + bitrate_requirements: + root: 1048576 + leaf: 1048576 + targets: [ internalVL2 ] + + - policy_antiaffinity_group: + type: tosca.policies.nfv.AntiAffinityRule + targets: [ affinityOrAntiAffinityGroup1 ] + properties: + scope: nfvi_node diff --git a/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/Definitions/v2_sample4_top.vnfd.yaml b/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/Definitions/v2_sample4_top.vnfd.yaml new file mode 100644 index 000000000..0e639d55b --- /dev/null +++ b/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/Definitions/v2_sample4_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 + - v2_sample4_types.yaml + - v2_sample4_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_common/samples/userdata_standard_change_vnfpkg/contents/Definitions/v2_sample4_types.yaml b/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/Definitions/v2_sample4_types.yaml new file mode 100644 index 000000000..0aac5c339 --- /dev/null +++ b/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/Definitions/v2_sample4_types.yaml @@ -0,0 +1,55 @@ +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 + - virtual_link_internal: + capability: tosca.capabilities.nfv.VirtualLinkable + interfaces: + Vnflcm: + type: tosca.interfaces.nfv.Vnflcm diff --git a/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/Scripts/coordinate_vnf.py b/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/Scripts/coordinate_vnf.py new file mode 100644 index 000000000..d7057da6d --- /dev/null +++ b/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/Scripts/coordinate_vnf.py @@ -0,0 +1,46 @@ +# Copyright (C) 2022 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. + +import os +import pickle +import sys + + +class FailScript(object): + def __init__(self, vnfc_param): + self.vnfc_param = vnfc_param + + def run(self): + operation = 'change_vnfpkg' + if self.vnfc_param['is_rollback']: + operation += '_rollback' + if os.path.exists(f'/tmp/{operation}'): + raise Exception(f'test {operation} error') + + +def main(): + vnfc_param = pickle.load(sys.stdin.buffer) + script = FailScript(vnfc_param) + script.run() + + +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_common/samples/userdata_standard_change_vnfpkg/contents/Scripts/sample_script.py b/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/Scripts/sample_script.py new file mode 100644 index 000000000..cb98d4656 --- /dev/null +++ b/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/Scripts/sample_script.py @@ -0,0 +1,68 @@ +# Copyright (C) 2022 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. + +import functools +import os +import pickle +import sys + + +class FailScript(object): + """Define error method for each operation + + For example: + + def instantiate_start(self): + if os.path.exists('/tmp/instantiate_start') + raise Exception('test instantiate_start error') + """ + + def __init__(self, req, inst, grant_req, grant, csar_dir): + self.req = req + self.inst = inst + self.grant_req = grant_req + self.grant = grant + self.csar_dir = csar_dir + + def _fail(self, method): + if os.path.exists(f'/tmp/{method}'): + raise Exception(f'test {method} error') + + def __getattr__(self, name): + return functools.partial(self._fail, name) + + +def main(): + script_dict = pickle.load(sys.stdin.buffer) + + operation = script_dict['operation'] + 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'] + + script = FailScript(req, inst, grant_req, grant, csar_dir) + getattr(script, operation)() + + +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_common/samples/userdata_standard_change_vnfpkg/contents/TOSCA-Metadata/TOSCA.meta b/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/contents/TOSCA-Metadata/TOSCA.meta new file mode 100644 index 000000000..c2d2a379e --- /dev/null +++ b/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/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/v2_sample4_top.vnfd.yaml diff --git a/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/pkggen.py b/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/pkggen.py new file mode 100644 index 000000000..387ad5f11 --- /dev/null +++ b/tacker/tests/functional/sol_v2_common/samples/userdata_standard_change_vnfpkg/pkggen.py @@ -0,0 +1,52 @@ +# Copyright (C) 2022 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. + +import json +import os +import shutil +import tempfile + +from oslo_utils import uuidutils + +from tacker.tests.functional.sol_v2_common import paramgen +from tacker.tests.functional.sol_v2_common import utils + + +zip_file_name = os.path.basename(os.path.abspath(".")) + '.zip' +tmp_dir = tempfile.mkdtemp() +vnfd_id = uuidutils.generate_uuid() + +# tacker/tests/etc... +# /functional/sol_v2_common/samples/sampleX +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) + +# tacker/sol_refactored/infra_drivers/openstack/userdata_standard.py +# /tests/functional/sol_v2_common/samples/sampleX +userdata_dir = "../../../../../sol_refactored/infra_drivers/openstack/" +userdata_file = "userdata_standard.py" +userdata_path = os.path.abspath(userdata_dir + userdata_file) + +utils.make_zip(".", tmp_dir, vnfd_id, image_path=image_path, + userdata_path=userdata_path) + +shutil.copy(os.path.join(tmp_dir, zip_file_name), ".") +shutil.rmtree(tmp_dir) + +change_vnfpkg_req = paramgen.sample4_change_vnfpkg(vnfd_id) + +with open("change_vnfpkg_req", "w") as f: + f.write(json.dumps(change_vnfpkg_req, indent=2)) diff --git a/tacker/tests/functional/sol_v2_common/utils.py b/tacker/tests/functional/sol_v2_common/utils.py index 607b3a9e9..2d5e602bb 100644 --- a/tacker/tests/functional/sol_v2_common/utils.py +++ b/tacker/tests/functional/sol_v2_common/utils.py @@ -23,7 +23,8 @@ import subprocess SAMPLE_VNFD_ID = "b1bb0ce7-ebca-4fa7-95ed-4840d7000000" -def make_zip(sample_dir, tmp_dir, vnfd_id, image_path=None): +def make_zip(sample_dir, tmp_dir, vnfd_id, image_path=None, + userdata_path=None): # NOTE: '.zip' will be added by shutil.make_archive zip_file_name = os.path.basename(os.path.abspath(sample_dir)) zip_file_path = os.path.join(tmp_dir, zip_file_name) @@ -53,6 +54,12 @@ def make_zip(sample_dir, tmp_dir, vnfd_id, image_path=None): os.makedirs(file_path) shutil.copy(image_path, file_path) + if userdata_path is not None: + # mkdir UserData/ and copy userdata_path into it + file_path = os.path.join(tmp_contents, "UserData") + os.makedirs(file_path) + shutil.copy(userdata_path, file_path) + shutil.make_archive(zip_file_path, "zip", tmp_contents)