diff --git a/tacker/tests/functional/sol/vnflcm/base.py b/tacker/tests/functional/sol/vnflcm/base.py index c8efe683b..5ff69371b 100644 --- a/tacker/tests/functional/sol/vnflcm/base.py +++ b/tacker/tests/functional/sol/vnflcm/base.py @@ -1034,13 +1034,8 @@ class BaseVnfLcmTest(base.BaseTackerTest): notify_mock_responses[0], 'VnfIdentifierDeletionNotification') - def assert_instantiate_vnf( - self, - resp, - vnf_instance_id, - http_client=None, - h_client=None, - fake_server_manager=None): + def assert_instantiate_vnf(self, resp, vnf_instance_id, http_client=None, + h_client=None, fake_server_manager=None, expected_show_res=None): if http_client is None: http_client = self.http_client if h_client is None: @@ -1083,14 +1078,12 @@ class BaseVnfLcmTest(base.BaseTackerTest): 'VnfLcmOperationOccurrenceNotification', 'COMPLETED') - def assert_heal_vnf( - self, - resp, - vnf_instance_id, - expected_stack_status='UPDATE_COMPLETE', - http_client=None, - h_client=None, - fake_server_manager=None): + if expected_show_res: + self._assert_show_res(vnf_instance, expected_show_res) + + def assert_heal_vnf(self, resp, vnf_instance_id, + expected_stack_status='UPDATE_COMPLETE', http_client=None, + h_client=None, fake_server_manager=None, expected_show_res=None): if http_client is None: http_client = self.http_client if h_client is None: @@ -1133,6 +1126,9 @@ class BaseVnfLcmTest(base.BaseTackerTest): 'VnfLcmOperationOccurrenceNotification', 'COMPLETED') + if expected_show_res: + self._assert_show_res(vnf_instance, expected_show_res) + def assert_terminate_vnf( self, resp, @@ -1195,17 +1191,10 @@ class BaseVnfLcmTest(base.BaseTackerTest): 'VnfLcmOperationOccurrenceNotification', 'COMPLETED') - def assert_scale_vnf( - self, - resp, - vnf_instance_id, - pre_stack_resource_list, - post_stack_resource_list, - scale_type='SCALE_OUT', - expected_stack_status='CREATE_COMPLETE', - http_client=None, - h_client=None, - fake_server_manager=None): + def assert_scale_vnf(self, resp, vnf_instance_id, pre_stack_resource_list, + post_stack_resource_list, scale_type='SCALE_OUT', + expected_stack_status='CREATE_COMPLETE', http_client=None, + h_client=None, fake_server_manager=None, expected_show_res=None): if http_client is None: http_client = self.http_client if h_client is None: @@ -1263,6 +1252,9 @@ class BaseVnfLcmTest(base.BaseTackerTest): 'VnfLcmOperationOccurrenceNotification', 'COMPLETED') + if expected_show_res: + self._assert_show_res(vnf_instance, expected_show_res) + def assert_rollback_vnf(self, resp, vnf_instance_id, fake_server_manager=None): self.assertEqual(202, resp.status_code) @@ -1523,3 +1515,21 @@ class BaseVnfLcmTest(base.BaseTackerTest): return resource_dict return resource_dict['VDU'][resource_name]['image'] + + def _assert_show_res(self, vnf_instance, expected_show_res): + # Check result of show vnf instance + if expected_show_res['len_vnfc_res_info']: + vnfc_res_info = vnf_instance.get('instantiatedVnfInfo', {}).get( + 'vnfcResourceInfo', []) + self.assertEqual(len(vnfc_res_info), + expected_show_res['len_vnfc_res_info']) + if expected_show_res['len_storage_res_info']: + storage_res_info = vnf_instance.get('instantiatedVnfInfo', {}).get( + 'virtualStorageResourceInfo', []) + self.assertEqual(len(storage_res_info), + expected_show_res['len_storage_res_info']) + if expected_show_res['len_vnfc_info']: + vnfc_info = vnf_instance.get('instantiatedVnfInfo', {}).get( + 'vnfcInfo', []) + self.assertEqual(len(vnfc_info), + expected_show_res['len_vnfc_info']) diff --git a/tacker/tests/functional/sol/vnflcm_userdata/test_vnf_instance_with_user_data.py b/tacker/tests/functional/sol/vnflcm_userdata/test_vnf_instance_with_user_data.py index 4f8b742f4..2cd5d21b1 100644 --- a/tacker/tests/functional/sol/vnflcm_userdata/test_vnf_instance_with_user_data.py +++ b/tacker/tests/functional/sol/vnflcm_userdata/test_vnf_instance_with_user_data.py @@ -111,6 +111,142 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest): - Instantiate VNF. - Get VNF informations. - Scale-Out VNF + - Scale-In(is_reverse) VNF + - Terminate VNF + - Delete VNF + - Delete subscription + """ + # Create subscription and register it. + callback_url = os.path.join(vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName) + request_body = fake_vnflcm.Subscription.make_create_request_body( + 'http://localhost:{}{}'.format( + vnflcm_base.FAKE_SERVER_MANAGER.SERVER_PORT, + callback_url)) + resp, response_body = self._register_subscription(request_body) + self.assertEqual(201, resp.status_code) + self.assert_http_header_location_for_subscription(resp.headers) + self.assert_notification_get(callback_url) + subscription_id = response_body.get('id') + self.addCleanup( + self._delete_subscription, + subscription_id) + + # Pre Setting: Create vnf package. + sample_name = 'functional5' + csar_package_path = self._sample_path(sample_name) + tempname, _ = vnflcm_base._create_csar_with_unique_vnfd_id( + csar_package_path) + # upload vnf package + vnf_package_id, vnfd_id = vnflcm_base._create_and_upload_vnf_package( + self.tacker_client, user_defined_data={ + "key": sample_name}, temp_csar_path=tempname) + + # Post Setting: Reserve deleting vnf package. + self.addCleanup(vnflcm_base._delete_vnf_package, self.tacker_client, + vnf_package_id) + + # Create vnf instance + resp, vnf_instance = self._create_vnf_instance_from_body( + fake_vnflcm.VnfInstances.make_create_request_body(vnfd_id)) + vnf_instance_id = vnf_instance['id'] + self._wait_lcm_done(vnf_instance_id=vnf_instance_id) + self.assert_create_vnf(resp, vnf_instance, vnf_package_id) + self.addCleanup(self._delete_vnf_instance, vnf_instance_id) + + # Instantiate vnf instance + request_body = fake_vnflcm.VnfInstances.\ + make_inst_request_body_include_num_dynamic( + self.vim['tenant_id'], self.ext_networks, + self.ext_mngd_networks, self.ext_link_ports, self.ext_subnets) + resp, _ = self._instantiate_vnf_instance(vnf_instance_id, request_body) + self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id) + expected_show_res = { + 'len_vnfc_res_info': 2, # = 1(VDU1) + 1(VDU2) + 'len_storage_res_info': 1, # = 1(VDU1) + 'len_vnfc_info': 2 # = 1(VDU1) + 1(VDU2) + } + self.assert_instantiate_vnf(resp, vnf_instance_id, vnf_package_id, + expected_show_res=expected_show_res) + + # Scale-out vnf instance + stack = self._get_heat_stack(vnf_instance_id) + pre_stack_resource_list = self._get_heat_resource_list(stack.id, 2) + + request_body = fake_vnflcm.VnfInstances.make_scale_request_body( + 'SCALE_OUT') + resp, _ = self._scale_vnf_instance(vnf_instance_id, request_body) + self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id) + + post_stack_resource_list = self._get_heat_resource_list(stack.id, 2) + expected_show_res = { + 'len_vnfc_res_info': 3, # = 2(VDU1) + 1(VDU2) + 'len_storage_res_info': 2, # = 2(VDU1) + 'len_vnfc_info': 3 # = 2(VDU1) + 1(VDU2) + } + self._assert_scale_vnf(resp, vnf_instance_id, vnf_package_id, + pre_stack_resource_list, post_stack_resource_list, + scale_type='SCALE_OUT', expected_stack_status='CREATE_COMPLETE', + expected_show_res=expected_show_res) + + # Scale-in vnf instance + stack = self._get_heat_stack(vnf_instance_id) + pre_stack_resource_list = self._get_heat_resource_list(stack.id, 2) + + request_body = (fake_vnflcm.VnfInstances + .make_reverse_scale_request_body('SCALE_IN')) + resp, _ = self._scale_vnf_instance(vnf_instance_id, request_body) + self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id) + + post_stack_resource_list = self._get_heat_resource_list(stack.id, 2) + expected_show_res = { + 'len_vnfc_res_info': 2, # = 1(VDU1) + 1(VDU2) + 'len_storage_res_info': 1, # = 1(VDU1) + 'len_vnfc_info': 2 # = 1(VDU1) + 1(VDU2) + } + self._assert_scale_vnf(resp, vnf_instance_id, vnf_package_id, + pre_stack_resource_list, post_stack_resource_list, + scale_type='SCALE_IN', expected_stack_status='UPDATE_COMPLETE', + expected_show_res=expected_show_res) + + # Terminate VNF + stack = self._get_heat_stack(vnf_instance_id) + resources_list = self._get_heat_resource_list(stack.id) + resource_name_list = [r.resource_name for r in resources_list] + glance_image_id_list = self._get_glance_image_list_from_stack_resource( + stack.id, resource_name_list) + + terminate_req_body = fake_vnflcm.VnfInstances.make_term_request_body() + resp, _ = self._terminate_vnf_instance( + vnf_instance_id, terminate_req_body) + self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id) + self.assert_terminate_vnf(resp, vnf_instance_id, stack.id, + resource_name_list, glance_image_id_list, vnf_package_id) + + # Delete VNF + resp, _ = self._delete_vnf_instance(vnf_instance_id) + self._wait_lcm_done(vnf_instance_id=vnf_instance_id) + self.assert_delete_vnf(resp, vnf_instance_id, vnf_package_id) + + # Subscription delete + resp, response_body = self._delete_subscription(subscription_id) + self.assertEqual(204, resp.status_code) + + resp, _ = self._show_subscription(subscription_id) + self.assertEqual(404, resp.status_code) + + def test_inst_scaling_heal(self): + """Test basic life cycle operations with sample VNFD. + + In this test case, we do following steps. + - Create subscription. + - Create VNF package. + - Upload VNF package. + - Create VNF instance. + - Instantiate VNF. + - Get VNF informations. + - Scale-Out VNF + - heal all VNF - Scale-In VNF - Terminate VNF - Delete VNF @@ -161,39 +297,66 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest): self.ext_mngd_networks, self.ext_link_ports, self.ext_subnets) resp, _ = self._instantiate_vnf_instance(vnf_instance_id, request_body) self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id) - self.assert_instantiate_vnf(resp, vnf_instance_id, vnf_package_id) - - # Show vnf instance - resp, vnf_instance = self._show_vnf_instance(vnf_instance_id) - self.assertEqual(200, resp.status_code) + expected_show_res = { + 'len_vnfc_res_info': 2, # = 1(VDU1) + 1(VDU2) + 'len_storage_res_info': 1, # = 1(VDU1) + 'len_vnfc_info': 2 # = 1(VDU1) + 1(VDU2) + } + self.assert_instantiate_vnf(resp, vnf_instance_id, vnf_package_id, + expected_show_res=expected_show_res) # Scale-out vnf instance stack = self._get_heat_stack(vnf_instance_id) pre_stack_resource_list = self._get_heat_resource_list(stack.id, 2) - request_body = fake_vnflcm.VnfInstances.make_scale_request_body( 'SCALE_OUT') resp, _ = self._scale_vnf_instance(vnf_instance_id, request_body) self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id) - post_stack_resource_list = self._get_heat_resource_list(stack.id, 2) + expected_show_res = { + 'len_vnfc_res_info': 3, # = 2(VDU1) + 1(VDU2) + 'len_storage_res_info': 2, # = 2(VDU1) + 'len_vnfc_info': 3 # = 2(VDU1) + 1(VDU2) + } self._assert_scale_vnf(resp, vnf_instance_id, vnf_package_id, pre_stack_resource_list, post_stack_resource_list, - scale_type='SCALE_OUT', expected_stack_status='CREATE_COMPLETE') + scale_type='SCALE_OUT', expected_stack_status='CREATE_COMPLETE', + expected_show_res=expected_show_res) + + # Heal vnf (do not specify vnfc_instace_id) + # pre check heat status. + self.assert_heat_stack_status(vnf_instance_id) + # Heal + request_body = fake_vnflcm.VnfInstances.make_heal_request_body() + resp, _ = self._heal_vnf_instance(vnf_instance_id, request_body) + self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id) + expected_show_res = { + 'len_vnfc_res_info': 3, # = 2(VDU1) + 1(VDU2) + 'len_storage_res_info': 2, # = 2(VDU1) + 'len_vnfc_info': 3 # = 2(VDU1) + 1(VDU2) + } + # post check heat status. + self.assert_heal_vnf(resp, vnf_instance_id, vnf_package_id, + expected_stack_status='CREATE_COMPLETE', + expected_show_res=expected_show_res) # Scale-in vnf instance stack = self._get_heat_stack(vnf_instance_id) pre_stack_resource_list = self._get_heat_resource_list(stack.id, 2) - request_body = (fake_vnflcm.VnfInstances - .make_reverse_scale_request_body('SCALE_IN')) + .make_scale_request_body('SCALE_IN')) resp, _ = self._scale_vnf_instance(vnf_instance_id, request_body) self._wait_lcm_done('COMPLETED', vnf_instance_id=vnf_instance_id) - post_stack_resource_list = self._get_heat_resource_list(stack.id, 2) + expected_show_res = { + 'len_vnfc_res_info': 2, # = 1(VDU1) + 1(VDU2) + 'len_storage_res_info': 1, # = 1(VDU1) + 'len_vnfc_info': 2 # = 1(VDU1) + 1(VDU2) + } self._assert_scale_vnf(resp, vnf_instance_id, vnf_package_id, pre_stack_resource_list, post_stack_resource_list, - scale_type='SCALE_IN', expected_stack_status='UPDATE_COMPLETE') + scale_type='SCALE_IN', expected_stack_status='CREATE_COMPLETE', + expected_show_res=expected_show_res) # Terminate VNF stack = self._get_heat_stack(vnf_instance_id) @@ -2186,25 +2349,20 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest): vnf_pkg_info, expected_usage_state=fields.PackageUsageStateType.NOT_IN_USE) - def assert_instantiate_vnf( - self, - resp, - vnf_instance_id, - vnf_pkg_id): - super().assert_instantiate_vnf(resp, vnf_instance_id) + def assert_instantiate_vnf(self, resp, vnf_instance_id, vnf_pkg_id, + expected_show_res=None): + super().assert_instantiate_vnf(resp, vnf_instance_id, + expected_show_res=expected_show_res) resp, vnf_pkg_info = vnflcm_base._show_vnf_package( self.tacker_client, vnf_pkg_id) self.assert_vnf_package_usage_state(vnf_pkg_info) - def assert_heal_vnf( - self, - resp, - vnf_instance_id, - vnf_pkg_id, - expected_stack_status='UPDATE_COMPLETE'): - super().assert_heal_vnf( - resp, vnf_instance_id, expected_stack_status=expected_stack_status) + def assert_heal_vnf(self, resp, vnf_instance_id, vnf_pkg_id, + expected_stack_status='UPDATE_COMPLETE', expected_show_res=None): + super().assert_heal_vnf(resp, vnf_instance_id, + expected_stack_status=expected_stack_status, + expected_show_res=expected_show_res) resp, vnf_pkg_info = vnflcm_base._show_vnf_package( self.tacker_client, vnf_pkg_id) @@ -2235,20 +2393,13 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest): resp, _ = self._show_subscription(sub_id) self.assertEqual(404, resp.status_code) - def _assert_scale_vnf( - self, - resp, - vnf_instance_id, - vnf_pkg_id, - pre_stack_resource_list, - post_stack_resource_list, - scale_type, expected_stack_status): - super().assert_scale_vnf( - resp, - vnf_instance_id, - pre_stack_resource_list, - post_stack_resource_list, - scale_type=scale_type, expected_stack_status=expected_stack_status) + def _assert_scale_vnf(self, resp, vnf_instance_id, vnf_pkg_id, + pre_stack_resource_list, post_stack_resource_list, scale_type, + expected_stack_status, expected_show_res=None): + super().assert_scale_vnf(resp, vnf_instance_id, + pre_stack_resource_list, post_stack_resource_list, + scale_type=scale_type, expected_stack_status=expected_stack_status, + expected_show_res=expected_show_res) resp, vnf_pkg_info = vnflcm_base._show_vnf_package( self.tacker_client, vnf_pkg_id) diff --git a/tacker/tests/unit/vnflcm/fakes.py b/tacker/tests/unit/vnflcm/fakes.py index 2dd53bf80..6ca7c69ae 100644 --- a/tacker/tests/unit/vnflcm/fakes.py +++ b/tacker/tests/unit/vnflcm/fakes.py @@ -721,6 +721,667 @@ def get_vnfd_dict(image_path=None): return vnfd_dict +def get_vnfd_dict_for_convert_desired_capacity(): + vnfd_dict = { + 'imports': [ + 'etsi_nfv_sol001_common_types.yaml', + 'etsi_nfv_sol001_vnfd_types.yaml', + 'helloworld3_types.yaml' + ], + 'description': 'Simple deployment flavour for Sample VNF', + 'topology_template': { + 'inputs': { + 'descriptor_id': { + 'type': 'string' + }, + 'flavour_description': { + 'type': 'string' + }, + 'software_version': { + 'type': 'string' + }, + 'flavour_id': { + 'type': 'string' + }, + 'descriptor_version': { + 'type': 'string' + }, + 'provider': { + 'type': 'string' + }, + 'vnfm_info': { + 'entry_schema': { + 'type': 'string' + }, + 'type': 'list' + }, + 'product_name': { + 'type': 'string' + } + }, + 'node_templates': { + 'VDU1': { + 'requirements': [ + { + 'virtual_storage': 'VirtualStorage' + } + ], + 'type': 'tosca.nodes.nfv.Vdu.Compute', + 'properties': { + 'vdu_profile': { + 'max_number_of_instances': 3, + 'min_number_of_instances': 1 + }, + 'description': 'VDU1 compute node', + 'name': 'VDU1' + }, + 'capabilities': { + 'virtual_compute': { + 'properties': { + 'requested_additional_capabilities': { + 'properties': { + 'requested_additional_capability_name': + 'm1.tiny', + 'target_performance_parameters': { + 'entry_schema': 'test' + }, + 'support_mandatory': 'true' + } + }, + 'virtual_cpu': { + 'num_virtual_cpu': 1 + }, + 'virtual_memory': { + 'virtual_mem_size': '512 MB' + }, + 'virtual_local_storage': [ + { + 'size_of_storage': '3 GB' + } + ] + } + } + } + }, + 'VirtualStorage': { + 'type': 'tosca.nodes.nfv.Vdu.VirtualBlockStorage', + 'properties': { + 'sw_image_data': { + 'name': 'cirros-0.5.2-x86_64-disk', + 'checksum': { + 'hash': 'fake hash', + 'algorithm': 'sha-256' + }, + 'min_ram': '256 MB', + 'disk_format': 'qcow2', + 'version': '0.5.2', + 'container_format': 'bare', + 'min_disk': '0 GB', + 'size': '12 GB' + }, + 'virtual_block_storage_data': { + 'size_of_storage': '1 GB', + 'rdma_enabled': 'true' + } + } + }, + 'VDU1_CP3': { + 'requirements': [ + { + 'virtual_binding': 'VDU1' + }, + { + 'virtual_link': 'internalVL1' + } + ], + 'type': 'tosca.nodes.nfv.VduCp', + 'properties': { + 'layer_protocols': [ + 'ipv4' + ], + 'order': 2 + } + }, + 'VDU2_CP5': { + 'requirements': [ + { + 'virtual_binding': 'VDU2' + }, + { + 'virtual_link': 'internalVL3' + } + ], + 'type': 'tosca.nodes.nfv.VduCp', + 'properties': { + 'layer_protocols': [ + 'ipv4' + ], + 'order': 4 + } + }, + 'VDU2_CP4': { + 'requirements': [ + { + 'virtual_binding': 'VDU2' + }, + { + 'virtual_link': 'internalVL2' + } + ], + 'type': 'tosca.nodes.nfv.VduCp', + 'properties': { + 'layer_protocols': [ + 'ipv4' + ], + 'order': 3 + } + }, + 'VDU2_CP1': { + 'requirements': [ + { + 'virtual_binding': 'VDU2' + } + ], + 'type': 'tosca.nodes.nfv.VduCp', + 'properties': { + 'layer_protocols': [ + 'ipv4' + ], + 'order': 0 + } + }, + 'VDU2_CP3': { + 'requirements': [ + { + 'virtual_binding': 'VDU2' + }, + { + 'virtual_link': 'internalVL1' + } + ], + 'type': 'tosca.nodes.nfv.VduCp', + 'properties': { + 'layer_protocols': [ + 'ipv4' + ], + 'order': 2 + } + }, + 'VDU2_CP2': { + 'requirements': [ + { + 'virtual_binding': 'VDU2' + } + ], + 'type': 'tosca.nodes.nfv.VduCp', + 'properties': { + 'layer_protocols': [ + 'ipv4' + ], + 'order': 1 + } + }, + 'internalVL1': { + 'type': 'tosca.nodes.nfv.VnfVirtualLink', + 'properties': { + 'connectivity_type': { + 'layer_protocols': [ + 'ipv4' + ] + }, + 'vl_profile': { + 'virtual_link_protocol_data': [ + { + 'l3_protocol_data': { + 'ip_version': 'ipv4', + 'cidr': '33.33.0.0/24' + }, + 'associated_layer_protocol': 'ipv4' + } + ], + 'min_bitrate_requirements': { + 'leaf': 1048576, + 'root': 1048576 + }, + 'max_bitrate_requirements': { + 'leaf': 1048576, + 'root': 1048576 + } + }, + 'description': + 'External Managed Virtual link in the VNF' + } + }, + 'internalVL2': { + 'type': 'tosca.nodes.nfv.VnfVirtualLink', + 'properties': { + 'connectivity_type': { + 'layer_protocols': [ + 'ipv4' + ] + }, + 'vl_profile': { + 'virtual_link_protocol_data': [ + { + 'l3_protocol_data': { + 'ip_version': 'ipv4', + 'cidr': '33.34.0.0/24' + }, + 'associated_layer_protocol': 'ipv4' + } + ], + 'min_bitrate_requirements': { + 'leaf': 1048576, + 'root': 1048576 + }, + 'max_bitrate_requirements': { + 'leaf': 1048576, + 'root': 1048576 + } + }, + 'description': + 'External Managed Virtual link in the VNF' + } + }, + 'VDU1_CP1': { + 'requirements': [ + { + 'virtual_binding': 'VDU1' + } + ], + 'type': 'tosca.nodes.nfv.VduCp', + 'properties': { + 'layer_protocols': [ + 'ipv4' + ], + 'order': 0 + } + }, + 'VDU1_CP2': { + 'requirements': [ + { + 'virtual_binding': 'VDU1' + } + ], + 'type': 'tosca.nodes.nfv.VduCp', + 'properties': { + 'layer_protocols': [ + 'ipv4' + ], + 'order': 1 + } + }, + 'VNF': { + 'interfaces': { + 'Vnflcm': { + 'instantiate': [], + 'modify_information_start': [], + 'modify_information_end': [], + 'terminate_start': [], + 'instantiate_start': [], + 'terminate': [], + 'instantiate_end': [], + 'terminate_end': [], + 'modify_information': [] + } + }, + 'type': 'company.provider.VNF', + 'properties': { + 'flavour_description': 'A simple flavour' + } + }, + 'VDU2': { + 'type': 'tosca.nodes.nfv.Vdu.Compute', + 'properties': { + 'vdu_profile': { + 'max_number_of_instances': 1, + 'min_number_of_instances': 1 + }, + 'description': 'VDU2 compute node', + 'sw_image_data': { + 'name': 'cirros-0.5.2-x86_64-disk', + 'checksum': { + 'hash': 'fake hash', + 'algorithm': 'sha-256' + }, + 'min_ram': '256 MB', + 'disk_format': 'qcow2', + 'version': '0.5.2', + 'container_format': 'bare', + 'min_disk': '0 GB', + 'size': '12 GB' + }, + 'name': 'VDU2' + }, + 'capabilities': { + 'virtual_compute': { + 'properties': { + 'requested_additional_capabilities': { + 'properties': { + 'requested_additional_capability_name': + 'm1.tiny', + 'target_performance_parameters': { + 'entry_schema': 'test' + }, + 'support_mandatory': 'true' + } + }, + 'virtual_cpu': { + 'num_virtual_cpu': 1 + }, + 'virtual_memory': { + 'virtual_mem_size': '512 MB' + }, + 'virtual_local_storage': [ + { + 'size_of_storage': '3 GB' + } + ] + } + } + } + }, + 'VDU1_CP4': { + 'requirements': [ + { + 'virtual_binding': 'VDU1' + }, + { + 'virtual_link': 'internalVL2' + } + ], + 'type': 'tosca.nodes.nfv.VduCp', + 'properties': { + 'layer_protocols': [ + 'ipv4' + ], + 'order': 3 + } + }, + 'VDU1_CP5': { + 'requirements': [ + { + 'virtual_binding': 'VDU1' + }, + { + 'virtual_link': 'internalVL3' + } + ], + 'type': 'tosca.nodes.nfv.VduCp', + 'properties': { + 'layer_protocols': [ + 'ipv4' + ], + 'order': 4 + } + }, + 'internalVL3': { + 'type': 'tosca.nodes.nfv.VnfVirtualLink', + 'properties': { + 'connectivity_type': { + 'layer_protocols': [ + 'ipv4' + ] + }, + 'vl_profile': { + 'virtual_link_protocol_data': [ + { + 'l3_protocol_data': { + 'ip_version': 'ipv4', + 'cidr': '33.35.0.0/24' + }, + 'associated_layer_protocol': 'ipv4' + } + ], + 'min_bitrate_requirements': { + 'leaf': 1048576, + 'root': 1048576 + }, + 'max_bitrate_requirements': { + 'leaf': 1048576, + 'root': 1048576 + } + }, + 'description': 'Internal Virtual link in the VNF' + } + } + }, + 'substitution_mappings': { + 'node_type': 'company.provider.VNF', + 'properties': { + 'flavour_id': 'simple' + } + }, + 'policies': [ + { + 'scaling_aspects': { + 'type': 'tosca.policies.nfv.ScalingAspects', + 'properties': { + 'aspects': { + 'VDU1_scale': { + 'max_scale_level': 2, + 'description': 'VDU1 scaling aspect', + 'name': 'VDU1_scale', + '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': { + 'deltas': { + 'delta_1': { + 'number_of_instances': 1 + } + }, + 'aspect': 'VDU1_scale' + }, + 'targets': [ + 'VDU1' + ] + } + }, + { + 'instantiation_levels': { + 'type': 'tosca.policies.nfv.InstantiationLevels', + 'properties': { + 'default_level': 'instantiation_level_1', + 'levels': { + 'instantiation_level_1': { + 'scale_info': { + 'VDU1_scale': { + 'scale_level': 0 + } + }, + 'description': 'Smallest size' + }, + 'instantiation_level_2': { + 'scale_info': { + 'VDU1_scale': { + 'scale_level': 2 + } + }, + 'description': 'Largest size' + } + } + } + } + }, + { + 'VDU1_instantiation_levels': { + 'type': 'tosca.policies.nfv.VduInstantiationLevels', + 'properties': { + 'levels': { + 'instantiation_level_1': { + 'number_of_instances': 1 + }, + 'instantiation_level_2': { + 'number_of_instances': 3 + } + } + }, + 'targets': [ + 'VDU1' + ] + } + }, + { + 'VDU2_instantiation_levels': { + 'type': 'tosca.policies.nfv.VduInstantiationLevels', + 'properties': { + 'levels': { + 'instantiation_level_1': { + 'number_of_instances': 1 + }, + 'instantiation_level_2': { + 'number_of_instances': 1 + } + } + }, + 'targets': [ + 'VDU2' + ] + } + }, + { + 'internalVL1_instantiation_levels': { + 'type': + 'tosca.policies.nfv.' + 'VirtualLinkInstantiationLevels', + 'properties': { + 'levels': { + 'instantiation_level_1': { + 'bitrate_requirements': { + 'leaf': 1048576, + 'root': 1048576 + } + }, + 'instantiation_level_2': { + 'bitrate_requirements': { + 'leaf': 1048576, + 'root': 1048576 + } + } + } + }, + 'targets': [ + 'internalVL1' + ] + } + }, + { + 'internalVL2_instantiation_levels': { + 'type': + 'tosca.policies.nfv.' + 'VirtualLinkInstantiationLevels', + 'properties': { + 'levels': { + 'instantiation_level_1': { + 'bitrate_requirements': { + 'leaf': 1048576, + 'root': 1048576 + } + }, + 'instantiation_level_2': { + 'bitrate_requirements': { + 'leaf': 1048576, + 'root': 1048576 + } + } + } + }, + 'targets': [ + 'internalVL2' + ] + } + }, + { + 'internalVL3_instantiation_levels': { + 'type': + 'tosca.policies.nfv.' + 'VirtualLinkInstantiationLevels', + 'properties': { + 'levels': { + 'instantiation_level_1': { + 'bitrate_requirements': { + 'leaf': 1048576, + 'root': 1048576 + } + }, + 'instantiation_level_2': { + 'bitrate_requirements': { + 'leaf': 1048576, + 'root': 1048576 + } + } + } + }, + 'targets': [ + 'internalVL3' + ] + } + }, + { + 'policy_antiaffinity_vdu1': { + 'type': 'tosca.policies.nfv.AntiAffinityRule', + 'properties': { + 'scope': 'zone' + }, + 'targets': [ + 'VDU1' + ] + } + }, + { + 'policy_antiaffinity_vdu2': { + 'type': 'tosca.policies.nfv.AntiAffinityRule', + 'properties': { + 'scope': 'zone' + }, + 'targets': [ + 'VDU2' + ] + } + } + ] + }, + 'tosca_definitions_version': 'tosca_simple_yaml_1_2' + } + + return vnfd_dict + + def get_dummy_vim_connection_info(): return {'access_info': { 'auth_url': 'fake/url', diff --git a/tacker/tests/unit/vnflcm/test_utils.py b/tacker/tests/unit/vnflcm/test_utils.py index cea1661e7..5b050feb0 100644 --- a/tacker/tests/unit/vnflcm/test_utils.py +++ b/tacker/tests/unit/vnflcm/test_utils.py @@ -14,9 +14,11 @@ # limitations under the License. import os +import shutil import ddt from oslo_config import cfg +import yaml from tacker.common import exceptions from tacker.objects import fields @@ -24,6 +26,7 @@ from tacker.objects.instantiate_vnf_req import InstantiateVnfRequest from tacker.objects.vim_connection import VimConnectionInfo from tacker.tests.unit import base from tacker.tests.unit.vnflcm import fakes +from tacker.tests import utils from tacker.tests import uuidsentinel from tacker.vnflcm import utils as vnflcm_utils @@ -140,3 +143,72 @@ class VnfLcmUtilsTestCase(base.TestCase): 'VduInitialDelta parameter from policies ' 'definition in VNFD.') self.assertEqual(expected_error, str(error)) + + def test_convert_desired_capacity_scale_status_none(self): + vnfd_dict = fakes.get_vnfd_dict_for_convert_desired_capacity() + + etsi_common_file_path = utils.test_etc_sample('etsi/nfv', + 'common/Definitions/etsi_nfv_sol001_common_types.yaml') + etsi_vnfd_file_path = utils.test_etc_sample('etsi/nfv', + 'common/Definitions/etsi_nfv_sol001_vnfd_types.yaml') + helloworld_path = utils.test_etc_sample('etsi/nfv', + 'common_artifact/Definitions/helloworld3_types.yaml') + + etsi_common_file_path_tmp = os.path.join( + os.path.dirname(__file__), 'etsi_nfv_sol001_common_types.yaml') + types_file_path_tmp = os.path.abspath( + os.path.join(os.path.dirname(__file__), 'types.yaml')) + + shutil.copy(etsi_common_file_path, etsi_common_file_path_tmp) + self.addCleanup(os.remove, etsi_common_file_path_tmp) + shutil.copy(helloworld_path, types_file_path_tmp) + self.addCleanup(os.remove, types_file_path_tmp) + + with open(types_file_path_tmp) as f: + data = yaml.safe_load(f) + data['imports'] = [etsi_common_file_path, etsi_vnfd_file_path] + with open(types_file_path_tmp, 'w', encoding='utf-8') as f: + yaml.dump(data, f) + + vnfd_dict['imports'] = [etsi_common_file_path, etsi_vnfd_file_path, + types_file_path_tmp] + + desired_capacity = vnflcm_utils._convert_desired_capacity( + 'instantiation_level_1', vnfd_dict, 'VDU1') + self.assertEqual(desired_capacity, 1) + + def test_convert_desired_capacity_scale_status_specified(self): + vnfd_dict = fakes.get_vnfd_dict_for_convert_desired_capacity() + + etsi_common_file_path = utils.test_etc_sample('etsi/nfv', + 'common/Definitions/etsi_nfv_sol001_common_types.yaml') + etsi_vnfd_file_path = utils.test_etc_sample('etsi/nfv', + 'common/Definitions/etsi_nfv_sol001_vnfd_types.yaml') + helloworld_path = utils.test_etc_sample('etsi/nfv', + 'common_artifact/Definitions/helloworld3_types.yaml') + + etsi_common_file_path_tmp = os.path.join( + os.path.dirname(__file__), 'etsi_nfv_sol001_common_types.yaml') + types_file_path_tmp = os.path.abspath( + os.path.join(os.path.dirname(__file__), 'types.yaml')) + + shutil.copy(etsi_common_file_path, etsi_common_file_path_tmp) + self.addCleanup(os.remove, etsi_common_file_path_tmp) + shutil.copy(helloworld_path, types_file_path_tmp) + self.addCleanup(os.remove, types_file_path_tmp) + + with open(types_file_path_tmp) as f: + data = yaml.safe_load(f) + data['imports'] = [etsi_common_file_path, etsi_vnfd_file_path] + with open(types_file_path_tmp, 'w', encoding='utf-8') as f: + yaml.dump(data, f) + + vnfd_dict['imports'] = [etsi_common_file_path, etsi_vnfd_file_path, + types_file_path_tmp] + + scale_status = [{'aspect_id': 'VDU1_scale', 'scale_level': 1}] + + desired_capacity = vnflcm_utils._convert_desired_capacity( + 'instantiation_level_1', vnfd_dict, 'VDU1', + scale_status=scale_status) + self.assertEqual(desired_capacity, 2) diff --git a/tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack_driver.py b/tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack_driver.py index ba1cfbee3..ff9cd1ea9 100644 --- a/tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack_driver.py +++ b/tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack_driver.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import copy import ddt import importlib import json @@ -327,6 +328,103 @@ class TestOpenStack(base.FixturedTestCase): grant_info=grant_info_test, vnf_instance=vnf_instance) + @mock.patch('tacker.vnfm.infra_drivers.openstack.openstack' + '.OpenStack._create_stack_with_user_data') + @mock.patch('tacker.tosca.utils.get_scale_group') + @mock.patch('tacker.vnfm.infra_drivers.openstack.openstack' + '.OpenStack._format_base_hot') + @mock.patch('tacker.vnflcm.utils._get_vnflcm_interface') + @mock.patch('tacker.vnflcm.utils.get_base_nest_hot_dict') + @mock.patch('tacker.common.clients.OpenstackClients') + def test_create_normal_with_scaling_group_and_scale_status( + self, mock_OpenstackClients_heat, mock_get_base_hot_dict, + mock_get_vnflcm_interface, mock_format_base_hot, + mock_get_scale_group, mock_create_stack_with_user_data): + vnf = utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + scaling_group=True, flavour='simple') + vnf['placement_attr'] = {'region_name': 'dummy_region'} + s_status = {'aspect_id': 'VDU1_scale', 'scale_level': 1} + vnf['scale_status'] = [objects.ScaleInfo(**s_status)] + vnf_package_path_test = self._nfv_sample( + 'user_data_sample_normal_scaling') + inst_req_info_test = type('', (), {}) + test_json = self._json_load( + 'instantiate_vnf_request_lcm_userdata.json') + inst_req_info_test.additional_params = test_json['additionalParams'] + inst_req_info_test.ext_virtual_links = None + inst_req_info_test.flavour_id = 'simple' + vnf_resource = type('', (), {}) + vnf_resource.resource_identifier = constants.INVALID_UUID + grant_info_test = {'vdu_name': {vnf_resource}} + nested_hot_dict = {'parameters': {'vnf': 'test'}} + base_hot_dict = self._read_file(scale_input_file=True) + mock_get_base_hot_dict.return_value = (base_hot_dict, nested_hot_dict) + mock_get_scale_group.return_value = { + 'scaleGroupDict': {'VDU1_scale': {'vdu': ['VDU1'], + 'num': 1, 'maxLevel': 2, 'initialNum': 1, + 'initialLevel': 0, 'default': 1}}} + vnf_instance = fd_utils.get_vnf_instance_object() + vnf['before_error_point'] = fields.ErrorPoint.PRE_VIM_CONTROL + self.openstack.create(self.plugin, self.context, vnf, + self.auth_attr, inst_req_info=inst_req_info_test, + vnf_package_path=vnf_package_path_test, + grant_info=grant_info_test, + vnf_instance=vnf_instance) + self.assertEqual(mock_create_stack_with_user_data.call_count, 1) + # Expect desired_capacity in base_hot_dict to be changed + # by specified scale_statue + (base_hot_dict['resources']['VDU1_scale']['properties'] + ['desired_capacity']) = 2 + mock_create_stack_with_user_data.assert_called_with( + mock.ANY, mock.ANY, base_hot_dict, mock.ANY, mock.ANY + ) + + @mock.patch('tacker.vnfm.infra_drivers.openstack.openstack' + '.OpenStack._create_stack_with_user_data') + @mock.patch('tacker.tosca.utils.get_scale_group') + @mock.patch('tacker.vnfm.infra_drivers.openstack.openstack' + '.OpenStack._format_base_hot') + @mock.patch('tacker.vnflcm.utils._get_vnflcm_interface') + @mock.patch('tacker.vnflcm.utils.get_base_nest_hot_dict') + @mock.patch('tacker.common.clients.OpenstackClients') + def test_create_normal_with_scaling_group_and_not_scale_status( + self, mock_OpenstackClients_heat, mock_get_base_hot_dict, + mock_get_vnflcm_interface, mock_format_base_hot, + mock_get_scale_group, mock_create_stack_with_user_data): + vnf = utils.get_dummy_vnf_etsi(instance_id=self.instance_uuid, + scaling_group=True, flavour='simple') + vnf['placement_attr'] = {'region_name': 'dummy_region'} + vnf_package_path_test = self._nfv_sample( + 'user_data_sample_normal_scaling') + inst_req_info_test = type('', (), {}) + test_json = self._json_load( + 'instantiate_vnf_request_lcm_userdata.json') + inst_req_info_test.additional_params = test_json['additionalParams'] + inst_req_info_test.ext_virtual_links = None + inst_req_info_test.flavour_id = 'simple' + vnf_resource = type('', (), {}) + vnf_resource.resource_identifier = constants.INVALID_UUID + grant_info_test = {'vdu_name': {vnf_resource}} + nested_hot_dict = {'parameters': {'vnf': 'test'}} + base_hot_dict = self._read_file(scale_input_file=True) + mock_get_base_hot_dict.return_value = (base_hot_dict, nested_hot_dict) + mock_get_scale_group.return_value = { + 'scaleGroupDict': {'VDU1_scale': {'vdu': ['VDU1'], + 'num': 1, 'maxLevel': 2, 'initialNum': 1, + 'initialLevel': 0, 'default': 1}}} + vnf_instance = fd_utils.get_vnf_instance_object() + vnf['before_error_point'] = fields.ErrorPoint.PRE_VIM_CONTROL + self.openstack.create(self.plugin, self.context, vnf, + self.auth_attr, inst_req_info=inst_req_info_test, + vnf_package_path=vnf_package_path_test, + grant_info=grant_info_test, + vnf_instance=vnf_instance) + self.assertEqual(mock_create_stack_with_user_data.call_count, 1) + # Expect desired_capacity in base_hot_dict to be unchanged + mock_create_stack_with_user_data.assert_called_with( + mock.ANY, mock.ANY, base_hot_dict, mock.ANY, mock.ANY + ) + @mock.patch('tacker.vnfm.infra_drivers.openstack.openstack' '.OpenStack._format_base_hot') @mock.patch('tacker.vnflcm.utils._get_vnflcm_interface') @@ -825,30 +923,45 @@ class TestOpenStack(base.FixturedTestCase): grant_info=grant_info_test, vnf_instance=vnf_instance) + @mock.patch('tacker.vnflcm.utils.get_vnfd_dict') @mock.patch('tacker.common.clients.OpenstackClients') - def test_create_heat_stack(self, mock_OpenstackClients_heat): + def test_create_heat_stack(self, mock_OpenstackClients_heat, + mock_vnfd_dict): vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) vnf['placement_attr'] = {'region_name': 'dummy_region'} vnf_package_path = None vnf['before_error_point'] = fields.ErrorPoint.PRE_VIM_CONTROL + mock_vnfd_dict.return_value = fakes.vnfd_dict_cnf() + vnf_instance = fd_utils.get_vnf_instance_object() + inst_req_info = fd_utils.get_instantiate_vnf_request() + self.assertIsNone(vnf.get('scale_status', None)) self.openstack.create(self.plugin, self.context, vnf, - self.auth_attr, vnf_package_path) + self.auth_attr, vnf_package_path, inst_req_info=inst_req_info, + grant_info=None, vnf_instance=vnf_instance) + self.assertIsNotNone(vnf.get('scale_status', None)) + @mock.patch('tacker.vnflcm.utils.get_vnfd_dict') @mock.patch('tacker.common.clients.OpenstackClients') @mock.patch('tacker.vnfm.infra_drivers.openstack.openstack' '.OpenStack._update_stack') @mock.patch.object(hc.HeatClient, "find_stack") def test_create_heat_stack_with_error_point_post_vim_control(self, mock_find_stack, mock_update_stack, - mock_OpenstackClients_heat): + mock_OpenstackClients_heat, mock_vnfd_dict): vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) vnf['placement_attr'] = {'region_name': 'dummy_region'} vnf_package_path = None vnf['before_error_point'] = fields.ErrorPoint.POST_VIM_CONTROL + mock_vnfd_dict.return_value = fakes.vnfd_dict_cnf() + vnf_instance = fd_utils.get_vnf_instance_object() + inst_req_info = fd_utils.get_instantiate_vnf_request() + self.assertIsNone(vnf.get('scale_status', None)) self.openstack.create(self.plugin, self.context, vnf, - self.auth_attr, vnf_package_path) + self.auth_attr, vnf_package_path, inst_req_info=inst_req_info, + grant_info=None, vnf_instance=vnf_instance) mock_find_stack.assert_called_once() mock_update_stack.assert_called_once() + self.assertIsNotNone(vnf.get('scale_status', None)) @mock.patch('tacker.common.clients.OpenstackClients') def test_create_userdata_none(self, mock_OpenstackClients_heat): @@ -1919,11 +2032,12 @@ class TestOpenStack(base.FixturedTestCase): mock_log.info.assert_called() self.assertEqual(delete_image_url.call_count, 2) + @mock.patch('tacker.vnflcm.utils.get_vnfd_dict') @mock.patch('tacker.vnfm.infra_drivers.openstack.translate_template.' 'TOSCAToHOT._get_unsupported_resource_props') @mock.patch.object(hc.HeatClient, "find_stack") def test_instantiate_vnf(self, mock_get_unsupported_resource_props, - mock_find_stack): + mock_find_stack, mock_vnfd_dict): vim_connection_info = fd_utils.get_vim_connection_info_object() inst_req_info = fd_utils.get_instantiate_vnf_request() grant_response = fd_utils.get_grant_response_dict() @@ -1939,7 +2053,7 @@ class TestOpenStack(base.FixturedTestCase): 'before_error_point': fields.ErrorPoint.PRE_VIM_CONTROL, 'status': '' } - + mock_vnfd_dict.return_value = fakes.vnfd_dict_cnf() instance_id = self.openstack.instantiate_vnf( self.context, vnf_instance, vnfd_dict, vim_connection_info, inst_req_info, grant_response, self.plugin) @@ -3320,8 +3434,8 @@ class TestOpenStack(base.FixturedTestCase): vnf_virtual_link_resource_info[0].vnf_link_ports[0]. resource_handle.resource_id) - @mock.patch.object(hc.HeatClient, "resource_get") - @mock.patch.object(hc.HeatClient, "resource_get_list") + @mock.patch.object(hc.HeatClient, 'resource_get') + @mock.patch.object(hc.HeatClient, 'resource_get_list') def test_scale_resource_update_scale_out_with_grant( self, mock_list, mock_resource): inst_vnf_info = fd_utils.get_vnf_instantiated_info() @@ -3332,11 +3446,11 @@ class TestOpenStack(base.FixturedTestCase): number_of_steps=1) vim_connection_info = fd_utils.get_vim_connection_info_object() vnf_info = {} - v_s_resource_info = fd_utils.\ - get_virtual_storage_resource_info_for_grant(desc_id="storage1") + v_s_resource_info = (fd_utils. + get_virtual_storage_resource_info_for_grant(desc_id='storage1')) storage_resource_ids = [v_s_resource_info.id] vnfc_resource_info = fd_utils.get_vnfc_resource_info_with_vnf_info( - vdu_id="workerNode", storage_resource_ids=storage_resource_ids) + vdu_id='workerNode', storage_resource_ids=storage_resource_ids) vnfc_resource_info_list = [] vnfc_resource_info_list.append(vnfc_resource_info) virtual_st_rsc_list = [] @@ -3422,16 +3536,25 @@ class TestOpenStack(base.FixturedTestCase): self.assertEqual( vnf_instance.instantiated_vnf_info.vnfc_resource_info[0].id, uuidsentinel.vnfc_resource_id) - return_vnfc_res = \ - vnf_instance.instantiated_vnf_info.vnfc_resource_info[0] + return_vnfc_res = ( + vnf_instance.instantiated_vnf_info.vnfc_resource_info[0]) self.assertEqual(return_vnfc_res.vnfc_cp_info[0].id, uuidsentinel.vnfc_cp_info_id) self.assertEqual(uuidsentinel.storage_id_1, vnf_instance.instantiated_vnf_info. virtual_storage_resource_info[0].id) + self.assertEqual( + len(vnf_instance.instantiated_vnf_info.vnfc_info), 1) + self.assertEqual( + return_vnfc_res.compute_resource.vim_level_resource_type, + 'OS::Nova::Server') + self.assertEqual( + (vnf_instance.instantiated_vnf_info. + virtual_storage_resource_info[0].storage_resource. + vim_level_resource_type), 'OS::Cinder::Volume') - @mock.patch.object(hc.HeatClient, "resource_get") - @mock.patch.object(hc.HeatClient, "resource_get_list") + @mock.patch.object(hc.HeatClient, 'resource_get') + @mock.patch.object(hc.HeatClient, 'resource_get_list') def test_scale_resource_update_scale_out(self, mock_list, mock_resource): inst_vnf_info = fd_utils.get_vnf_instantiated_info() vnf_instance = fd_utils.get_vnf_instance_object( @@ -3440,11 +3563,11 @@ class TestOpenStack(base.FixturedTestCase): aspect_id='worker_instance', number_of_steps=1) vim_connection_info = fd_utils.get_vim_connection_info_object() - v_s_resource_info = fd_utils. \ - get_virtual_storage_resource_info(desc_id="storage1") + v_s_resource_info = (fd_utils. + get_virtual_storage_resource_info(desc_id='storage1')) storage_resource_ids = [v_s_resource_info.id] vnfc_resource_info = fd_utils.get_vnfc_resource_info_with_vnf_info( - vdu_id="workerNode", storage_resource_ids=storage_resource_ids) + vdu_id='workerNode', storage_resource_ids=storage_resource_ids) vnfc_resource_info_list = [] vnfc_resource_info_list.append(vnfc_resource_info) virtual_st_rsc_list = [] @@ -3530,11 +3653,111 @@ class TestOpenStack(base.FixturedTestCase): self.assertNotEqual( vnf_instance.instantiated_vnf_info.vnfc_resource_info[0].id, uuidsentinel.vnfc_resource_id) - return_vnfc_res = \ - vnf_instance.instantiated_vnf_info.vnfc_resource_info[0] + return_vnfc_res = ( + vnf_instance.instantiated_vnf_info.vnfc_resource_info[0]) self.assertNotEqual(return_vnfc_res.vnfc_cp_info[0].id, uuidsentinel.vnfc_cp_info_id) self.assertNotEqual(uuidsentinel.storage_id, vnf_instance.instantiated_vnf_info. virtual_storage_resource_info[0].id) self.assertEqual(uuidsentinel.storage_id, v_s_resource_info.id) + self.assertEqual( + len(vnf_instance.instantiated_vnf_info.vnfc_info), 1) + self.assertEqual( + return_vnfc_res.compute_resource.vim_level_resource_type, + 'OS::Nova::Server') + self.assertEqual( + (vnf_instance.instantiated_vnf_info. + virtual_storage_resource_info[0].storage_resource. + vim_level_resource_type), 'OS::Cinder::Volume') + + @mock.patch.object(hc.HeatClient, 'resource_get_list') + def test_scale_resource_update_scale_in(self, mock_list): + inst_vnf_info = fd_utils.get_vnf_instantiated_info() + vnf_instance = fd_utils.get_vnf_instance_object( + instantiated_vnf_info=inst_vnf_info) + scale_vnf_request = objects.ScaleVnfRequest(type='SCALE_IN', + aspect_id='worker_instance', + number_of_steps=1) + vim_connection_info = fd_utils.get_vim_connection_info_object() + vnf_info = {'grant': 'test'} + + # instantiated_vnf_info - virtual_storage_resource_info + v_s_resource_info = (fd_utils. + get_virtual_storage_resource_info(desc_id='storage1')) + v_s_resource_info2 = copy.deepcopy(v_s_resource_info) + v_s_resource_info2.id = uuidsentinel.storage_id2 + v_s_resource_info2.storage_resource.resource_id = ( + uuidsentinel.storage_id2) + vnf_instance.instantiated_vnf_info.virtual_storage_resource_info = [ + v_s_resource_info, v_s_resource_info2] + + # instantiated_vnf_info - vnfc_resource_info + storage_resource_ids = [v_s_resource_info.id] + storage_resource_ids2 = [v_s_resource_info2.id] + vnfc_resource_info = fd_utils.get_vnfc_resource_info( + vdu_id='workerNode', storage_resource_ids=storage_resource_ids) + vnfc_resource_info2 = fd_utils.get_vnfc_resource_info( + vdu_id='workerNode', storage_resource_ids=storage_resource_ids2) + vnfc_resource_info2.id = uuidsentinel.vnfc_resource_id2 + vnfc_resource_info2.compute_resource.resource_id = ( + uuidsentinel.storage_resource_id2) + vnf_instance.instantiated_vnf_info.vnfc_resource_info = [ + vnfc_resource_info, vnfc_resource_info2] + + # instantiated_vnf_info - vnfc_info + vnfc_info = objects.VnfcInfo(id=uuidsentinel.vnfc_info_id, + vdu_id='workerNode', vnfc_state=fields.VnfcState.STARTED) + vnfc_info2 = copy.deepcopy(vnfc_info) + vnfc_info2.id = uuidsentinel.vnfc_info_id2 + vnf_instance.instantiated_vnf_info.vnfc_info = [ + vnfc_info, vnfc_info2] + + # heat resource + res_list1 = [] + resource1 = resources.Resource(None, { + 'resource_name': 'workerNode', + 'creation_time': '2020-01-01T00:00:00', + 'resource_type': 'OS::Nova::Server', + 'resource_status': 'CREATE_COMPLETE', + 'physical_resource_id': + vnfc_resource_info2.compute_resource.resource_id, + 'id': '1111' + }) + res_list1.append(resource1) + resource2 = resources.Resource(None, { + 'resource_name': 'storage1', + 'creation_time': '2020-01-01T00:00:00', + 'resource_type': 'OS::Cinder::Volume', + 'resource_status': 'CREATE_COMPLETE', + 'physical_resource_id': + v_s_resource_info2.storage_resource.resource_id, + 'id': '1111' + }) + res_list1.append(resource2) + mock_list.side_effect = [res_list1] + + # execute test target method + self.openstack.scale_resource_update( + context, vnf_instance, + scale_vnf_request, vnf_info, + vim_connection_info) + + self.assertEqual( + len(vnf_instance.instantiated_vnf_info.vnfc_resource_info), 1) + self.assertEqual( + (vnf_instance.instantiated_vnf_info.vnfc_resource_info[0]. + compute_resource.resource_id), + vnfc_resource_info2.compute_resource.resource_id) + self.assertEqual( + len(vnf_instance.instantiated_vnf_info. + virtual_storage_resource_info), 1) + self.assertEqual( + (vnf_instance.instantiated_vnf_info. + virtual_storage_resource_info[0].storage_resource.resource_id), + v_s_resource_info2.storage_resource.resource_id) + self.assertEqual( + len(vnf_instance.instantiated_vnf_info.vnfc_info), 1) + self.assertEqual( + vnf_instance.instantiated_vnf_info.vnfc_info[0].id, + uuidsentinel.vnfc_info_id2) diff --git a/tacker/vnflcm/utils.py b/tacker/vnflcm/utils.py index 19254ab6f..8fe676b05 100644 --- a/tacker/vnflcm/utils.py +++ b/tacker/vnflcm/utils.py @@ -549,15 +549,16 @@ def _get_vim_connection_info_from_vnf_req(vnf_instance, instantiate_vnf_req): def _build_instantiated_vnf_info(vnfd_dict, instantiate_vnf_req, - vnf_instance, vim_id): + vnf_instance, vim_id, scale_status=None): inst_vnf_info = vnf_instance.instantiated_vnf_info inst_vnf_info.vnf_state = fields.VnfOperationalStateType.STARTED node_templates = vnfd_dict.get( 'topology_template', {}).get('node_templates') - vnfc_resource_info, virtual_storage_resource_info = \ - _get_vnfc_resource_info(vnfd_dict, instantiate_vnf_req, vim_id) + vnfc_resource_info, virtual_storage_resource_info = ( + _get_vnfc_resource_info( + vnfd_dict, instantiate_vnf_req, vim_id, scale_status=scale_status)) inst_vnf_info.vnfc_resource_info = vnfc_resource_info @@ -599,7 +600,7 @@ def _update_instantiated_vnf_info(change_ext_conn_req, vnf_instance): vnf_instance.instantiated_vnf_info = inst_vnf_info -def _get_compute_nodes(vnfd_dict, instantiate_vnf_req): +def _get_compute_nodes(vnfd_dict, instantiate_vnf_req, scale_status=None): """Read the node templates and prepare VDU data in below format { @@ -619,7 +620,8 @@ def _get_compute_nodes(vnfd_dict, instantiate_vnf_req): continue desired_capacity = _convert_desired_capacity( - instantiate_vnf_req.instantiation_level_id, vnfd_dict, key) + instantiate_vnf_req.instantiation_level_id, vnfd_dict, key, + scale_status=scale_status) cp_list = _get_cp_for_vdu(key, node_templates) @@ -838,8 +840,10 @@ def _build_virtual_storage_info(virtual_storages): yield virtual_storage -def _get_vnfc_resource_info(vnfd_dict, instantiate_vnf_req, vim_id): - vdu_resources = _get_compute_nodes(vnfd_dict, instantiate_vnf_req) +def _get_vnfc_resource_info(vnfd_dict, instantiate_vnf_req, vim_id, + scale_status=None): + vdu_resources = _get_compute_nodes( + vnfd_dict, instantiate_vnf_req, scale_status=scale_status) vnfc_resource_info_list = [] virtual_storage_resource_info_list = [] @@ -1119,7 +1123,8 @@ def _build_ext_managed_virtual_link_info(instantiate_vnf_req, inst_vnf_info): return ext_managed_virtual_link_info -def _convert_desired_capacity(inst_level_id, vnfd_dict, vdu): +def _convert_desired_capacity( + inst_level_id, vnfd_dict, vdu, scale_status=None): aspect_delta_dict = {} aspect_vdu_dict = {} inst_level_dict = {} @@ -1166,7 +1171,12 @@ def _convert_desired_capacity(inst_level_id, vnfd_dict, vdu): initial_delta = vdu_delta_dict.get(vdu) if initial_delta is not None: - desired_capacity = initial_delta + delta_num * level_num + scale_level = level_num + if scale_status is not None: + for scale in scale_status: + if scale['aspect_id'] == aspect_id: + scale_level = scale['scale_level'] + desired_capacity = initial_delta + delta_num * scale_level return desired_capacity diff --git a/tacker/vnflcm/vnflcm_driver.py b/tacker/vnflcm/vnflcm_driver.py index 9345c5e7f..e32c0d594 100644 --- a/tacker/vnflcm/vnflcm_driver.py +++ b/tacker/vnflcm/vnflcm_driver.py @@ -897,7 +897,8 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver): else: vnflcm_utils._build_instantiated_vnf_info( vnfd_dict, instantiate_vnf_request, vnf_instance, - vim_connection_info.vim_id) + vim_connection_info.vim_id, + scale_status=vnf_dict.get('scale_status', None)) try: self._instantiate_vnf(context, vnf_instance, vnf_dict, diff --git a/tacker/vnfm/infra_drivers/openstack/openstack.py b/tacker/vnfm/infra_drivers/openstack/openstack.py index 487cc2ac6..b2975ea3f 100644 --- a/tacker/vnfm/infra_drivers/openstack/openstack.py +++ b/tacker/vnfm/infra_drivers/openstack/openstack.py @@ -52,6 +52,7 @@ from tacker.vnfm.infra_drivers.openstack import update_template as ut from tacker.vnfm.infra_drivers import scale_driver from tacker.vnfm.lcm_user_data.constants import USER_DATA_TIMEOUT from tacker.vnfm.lcm_user_data import utils as user_data_utils +from toscaparser import tosca_template eventlet.monkey_patch(time=True) @@ -251,17 +252,34 @@ class OpenStack(abstract_driver.VnfAbstractDriver, raise vnfm.LCMUserDataFailed(reason=error_reason) if scale_dict: - scale_status_list = [] - for name, value in scale_group_dict['scaleGroupDict'].items(): - key_name = name + '_desired_capacity' - if base_hot_dict.get('parameters') and \ - base_hot_dict['parameters'].get(key_name): - hot_param_dict[key_name] = value['default'] - scale_status = objects.ScaleInfo( - aspect_id=name, - scale_level=value['initialLevel']) - scale_status_list.append(scale_status) - vnf['scale_status'] = scale_status_list + if vnf.get('scale_status'): + # When instantiate by SOL003 heal after scale out, + # calculate `desired_capacity` using `scale_level` + # in scale_status + for name, value in ( + scale_group_dict['scaleGroupDict'].items()): + for scale in vnf.get('scale_status'): + if (scale['aspect_id'] == name and + name in base_hot_dict.get('resources') and + (base_hot_dict['resources'][name] + .get('properties').get('desired_capacity'))): + dc = (value['initialNum'] + + value['num'] * scale['scale_level']) + (base_hot_dict['resources'][name]['properties'] + ['desired_capacity']) = dc + else: + scale_status_list = [] + for name, value in ( + scale_group_dict['scaleGroupDict'].items()): + key_name = name + '_desired_capacity' + if base_hot_dict.get('parameters') and \ + base_hot_dict['parameters'].get(key_name): + hot_param_dict[key_name] = value['default'] + scale_status = objects.ScaleInfo( + aspect_id=name, + scale_level=value['initialLevel']) + scale_status_list.append(scale_status) + vnf['scale_status'] = scale_status_list if vnf.get('grant'): base_hot_dict, nested_hot_dict, hot_param_dict = \ self._setup_hot_for_grant_resources(vnf, vnf_instance, @@ -306,6 +324,30 @@ class OpenStack(abstract_driver.VnfAbstractDriver, elif user_data_path is None and user_data_class is None: LOG.info('Execute heat-translator and create heat-stack.') + + if not vnf.get('scale_status'): + # scale_status is not stored when instantiate operation + vnfd = vnflcm_utils.get_vnfd_dict(context, + vnf_instance.vnfd_id, inst_req_info.flavour_id) + tosca = tosca_template.ToscaTemplate( + parsed_params={}, a_file=False, yaml_dict_tpl=vnfd, + local_defs=tosca_utils.tosca_tmpl_local_defs()) + extract_policy_infos = ( + vnflcm_utils.get_extract_policy_infos(tosca)) + vnf['scale_status'] = [] + if inst_req_info.instantiation_level_id: + inst_level_id = inst_req_info.instantiation_level_id + else: + inst_level_id = ( + extract_policy_infos['default_inst_level_id']) + al_dict = (extract_policy_infos['inst_level_dict']. + get(inst_level_id)) + if al_dict: + vnf['scale_status'] = [ + objects.ScaleInfo( + aspect_id=aspect_id, scale_level=level_num) + for aspect_id, level_num in al_dict.items()] + tth = translate_template.TOSCAToHOT(vnf, heatclient, inst_req_info, grant_info) tth.generate_hot() @@ -1779,8 +1821,8 @@ class OpenStack(abstract_driver.VnfAbstractDriver, vim_connection_info.id resource.resource_id =\ rsc_info.physical_resource_id - resource.vim_level_resource_type = '\ - OS::Nova::Server' + resource.vim_level_resource_type = ( + 'OS::Nova::Server') vnfc_resource_info.compute_resource = resource vnfc_resource_info.metadata.update( {"stack_id": @@ -1794,6 +1836,11 @@ class OpenStack(abstract_driver.VnfAbstractDriver, storage_dict[vol['id']] = \ vnfc_resource_info.id vnfc_rscs.append(vnfc_resource_info) + vnfc = objects.VnfcInfo( + id=uuidutils.generate_uuid(), + vdu_id=rsc.resource_name, + vnfc_state=fields.VnfcState.STARTED) + inst_vnf_info.vnfc_info.append(vnfc) if len(vnfc_rscs) == 0: continue @@ -1901,8 +1948,8 @@ class OpenStack(abstract_driver.VnfAbstractDriver, resource.vim_connection_id =\ vim_connection_info.id resource.resource_id = rsc.physical_resource_id - resource.vim_level_resource_type = '\ - OS::Cinder::Volume' + resource.vim_level_resource_type = ( + 'OS::Cinder::Volume') virtual_storage_resource_info.\ storage_resource = resource inst_vnf_info.virtual_storage_resource_info.\ @@ -1932,10 +1979,15 @@ class OpenStack(abstract_driver.VnfAbstractDriver, after_port_list.append(rsc.physical_resource_id) LOG.debug("after_st_list %s", after_st_list) del_index = [] + del_vdu_id = {} for index, vnfc in enumerate( vnf_instance.instantiated_vnf_info.vnfc_resource_info): if vnfc.compute_resource.resource_id not in after_vnfcs_list: del_index.append(index) + if vnfc.vdu_id in del_vdu_id: + del_vdu_id[vnfc.vdu_id] += 1 + else: + del_vdu_id[vnfc.vdu_id] = 1 for ind in del_index[::-1]: vnf_instance.instantiated_vnf_info.vnfc_resource_info.pop(ind) @@ -1962,6 +2014,17 @@ class OpenStack(abstract_driver.VnfAbstractDriver, for ind in del_index[::-1]: vl.vnf_link_ports.pop(ind) + del_index = [] + # delete vnfc_info from earliest registerd + for index, vnfci in enumerate( + vnf_instance.instantiated_vnf_info.vnfc_info): + for vdu_id in del_vdu_id: + if vnfci.vdu_id == vdu_id and del_vdu_id[vdu_id] > 0: + del_index.append(index) + del_vdu_id[vdu_id] -= 1 + for ind in del_index[::-1]: + vnf_instance.instantiated_vnf_info.vnfc_info.pop(ind) + @log.log def scale_in_reverse(self, context, plugin, auth_attr, vnf_info, scale_vnf_request, region_name,