diff --git a/tacker/sol_refactored/common/subscription_utils.py b/tacker/sol_refactored/common/subscription_utils.py index aa973165c..f4d68d700 100644 --- a/tacker/sol_refactored/common/subscription_utils.py +++ b/tacker/sol_refactored/common/subscription_utils.py @@ -171,7 +171,7 @@ def match_inst_subsc_filter(inst_filter, inst): return False if inst_filter.obj_attr_is_set('vnfInstanceNames'): - if inst.vnfInstanceNames not in inst_filter.vnfInstanceNames: + if inst.vnfInstanceName not in inst_filter.vnfInstanceNames: return False return True diff --git a/tacker/sol_refactored/common/vnfd_utils.py b/tacker/sol_refactored/common/vnfd_utils.py index c8457d20f..fbcd9f418 100644 --- a/tacker/sol_refactored/common/vnfd_utils.py +++ b/tacker/sol_refactored/common/vnfd_utils.py @@ -77,7 +77,10 @@ class Vnfd(object): def delete(self): if self.csar_dir_is_tmp: - shutil.rmtree(self.csar_dir) + try: + shutil.rmtree(self.csar_dir) + except Exception: + LOG.exception("rmtree %s failed", self.csar_dir) def get_vnfd_flavour(self, flavour_id): if flavour_id in self.vnfd_flavours: @@ -336,6 +339,8 @@ class Vnfd(object): def get_interface_script(self, flavour_id, operation): vnfd = self.get_vnfd_flavour(flavour_id) + if not vnfd: + return nodes = (vnfd.get('topology_template', {}) .get('node_templates', {})) for node in nodes.values(): diff --git a/tacker/tests/unit/sol_refactored/common/test_common_script_utils.py b/tacker/tests/unit/sol_refactored/common/test_common_script_utils.py index 5ceec1f17..57c0276fe 100644 --- a/tacker/tests/unit/sol_refactored/common/test_common_script_utils.py +++ b/tacker/tests/unit/sol_refactored/common/test_common_script_utils.py @@ -39,7 +39,9 @@ class TestCommontScriptUtils(base.BaseTestCase): expected_result = { 'VDU': { - 'VDU1': {'computeFlavourId': None}, + 'VDU1': {'computeFlavourId': None, + 'desired_capacity': None, + 'locationConstraints': None}, 'VirtualStorage': {'vcImageId': None}, 'VDU2': {'computeFlavourId': None, 'vcImageId': None} }, @@ -56,6 +58,9 @@ class TestCommontScriptUtils(base.BaseTestCase): } result = common_script_utils.init_nfv_dict(top_hot) self.assertEqual(expected_result, result) + self.assertIsNone(result['CP'].get('VDU1_CP6')) + self.assertIsNone(result['CP'].get('VDU1_CP7')) + self.assertIsNone(result['CP'].get('VDU1_CP8')) def test_get_param_flavor(self): flavor = 'm1.large' @@ -79,6 +84,24 @@ class TestCommontScriptUtils(base.BaseTestCase): self.vnfd_1, grant) self.assertEqual('m1.tiny', result) + def test_get_param_flavor_no_compute_resource_flavours(self): + grant = { + 'vimAssets': { + } + } + + # if not exist in grant, get from VNFD + result = common_script_utils.get_param_flavor( + 'VDU1', SAMPLE_FLAVOUR_ID, + self.vnfd_1, grant) + self.assertEqual('m1.tiny', result) + + # if not exist in grant, get from VNFD + result = common_script_utils.get_param_flavor( + 'VDU2', SAMPLE_FLAVOUR_ID, + self.vnfd_1, grant) + self.assertEqual('m1.tiny', result) + def test_get_param_image(self): image_id = 'f30e149d-b3c7-497a-8b19-a092bc81e47b' grant = { @@ -96,6 +119,33 @@ class TestCommontScriptUtils(base.BaseTestCase): self.vnfd_1, grant) self.assertEqual(image_id, result) + def test_get_param_image_no_software_images(self): + grant = { + 'vimAssets': { + } + } + + result = common_script_utils.get_param_image('VDU2', SAMPLE_FLAVOUR_ID, + self.vnfd_1, grant) + self.assertEqual('VDU2-image', result) + + def test_get_param_image_no_match_image(self): + image_id = 'f30e149d-b3c7-497a-8b19-a092bc81e47b' + grant = { + 'vimAssets': { + 'softwareImages': [ + {'vnfdSoftwareImageId': 'VDU3', + 'vimSoftwareImageId': image_id}, + {'vnfdSoftwareImageId': 'VirtualStorage', + 'vimSoftwareImageId': 'image-1.0.0-x86_64-disk'} + ] + } + } + + result = common_script_utils.get_param_image('VDU2', SAMPLE_FLAVOUR_ID, + self.vnfd_1, grant) + self.assertEqual('VDU2-image', result) + def test_get_param_zone(self): grant_req = { 'addResources': [ @@ -118,6 +168,75 @@ class TestCommontScriptUtils(base.BaseTestCase): result = common_script_utils.get_param_zone('VDU1', grant_req, grant) self.assertEqual('nova', result) + def test_get_param_zone_no_zones(self): + grant_req = { + 'addResources': [ + {'id': 'dd60c89a-29a2-43bc-8cff-a534515523df', + 'type': 'COMPUTE', 'resourceTemplateId': 'VDU1'} + ] + } + grant = { + 'addResources': [ + {'resourceDefinitionId': + 'dd60c89a-29a2-43bc-8cff-a534515523df', + 'zoneId': '717f6ae9-3094-46b6-b070-89ede8337571'} + ] + } + + common_script_utils.get_param_zone('VDU1', grant_req, grant) + + def test_get_param_zone_no_add_resources(self): + grant_req = { + 'addResources': [ + {'id': 'dd60c89a-29a2-43bc-8cff-a534515523df', + 'type': 'COMPUTE', 'resourceTemplateId': 'VDU1'} + ] + } + grant = { + 'zones': [ + {'id': '717f6ae9-3094-46b6-b070-89ede8337571', + 'zoneId': 'nova'} + ] + } + + common_script_utils.get_param_zone('VDU1', grant_req, grant) + + def test_get_param_zone_no_zone_id(self): + grant_req = { + 'addResources': [ + {'id': 'dd60c89a-29a2-43bc-8cff-a534515523df', + 'type': 'COMPUTE', 'resourceTemplateId': 'VDU1'}, + {'id': 'e3ac628c-29a4-2878-b4a2-29aa685dcd70', + 'type': 'COMPUTE', 'resourceTemplateId': 'VDU1'}, + {'id': '36628ed5-6821-6f55-8c99-cbab0890fc71', + 'type': 'COMPUTE', 'resourceTemplateId': 'VDU2'}, + {'id': 'eed6860c-e9b2-ef79-5deb-89dee39785ec', + 'type': 'COMPUTE', 'resourceTemplateId': 'VDU3'}, + ] + } + grant = { + 'zones': [ + {'id': '717f6ae9-3094-46b6-b070-89ede8337571', + 'zoneId': 'nova'} + ], + 'addResources': [ + {'resourceDefinitionId': 'eed6860c-e9b2-' + 'ef79-5deb-89dee39785ec'}, + {'resourceDefinitionId': 'e3ac628c-29a4-' + '2878-b4a2-29aa685dcd70', + 'zoneId': '99171a93-d0b2-d2cd-83c1-b0694a3f771b'}, + {'resourceDefinitionId': '36628ed5-6821-' + '6f55-8c99-cbab0890fc71', + 'zoneId': '717f6ae9-3094-46b6-b070-89ede8337571'}, + {'resourceDefinitionId': 'dd60c89a-29a2-' + '43bc-8cff-a534515523df', + 'zoneId': '717f6ae9-3094-46b6-b070-89ede8337571'} + ] + } + + result = common_script_utils.get_param_zone('VDU1', grant_req, grant) + self.assertEqual('nova', result) + def test_get_param_capacity(self): # test get_current_capacity at the same time grant_req = { @@ -214,6 +333,81 @@ class TestCommontScriptUtils(base.BaseTestCase): result = common_script_utils.get_param_fixed_ips('VDU2_CP2', {}, req) self.assertEqual(expected_result, result) + def test_get_param_fixed_ips_other_cases(self): + ip_address = "10.10.1.101" + subnet_id = "9defebca-3e9c-4bd2-9fa0-c4210c56ece6" + ext_cp = { + "cpdId": "VDU2_CP2", + "cpConfig": { + "VDU2_CP2_1": { + "cpProtocolData": [ + { + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [ + { + "type": "IPV4", + "fixedAddresses": [ + ip_address + ], + "subnetId": subnet_id + } + ] + } + } + ] + }, + "VDU2_CP2_2": { + }, + "VDU2_CP2_3": { + "cpProtocolData": [ + { + "layerProtocol": "IP_OVER_ETHERNET" + } + ] + }, + "VDU2_CP2_4": { + "cpProtocolData": [ + { + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": {} + } + ] + }, + "VDU2_CP2_5": { + "cpProtocolData": [ + { + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [ + { + "type": "IPV4" + } + ] + } + } + ] + }, + + } + } + req = { + "extVirtualLinks": [ + { + "id": "8b49f4b6-1ff9-4a03-99cf-ff445b788436", + "resourceId": "4c54f742-5f1d-4287-bb81-37bf2e6ddc3e", + "extCps": [ext_cp] + } + ] + } + expected_result = [{'ip_address': ip_address, 'subnet': subnet_id}] + + # no vls + common_script_utils.get_param_fixed_ips('VDU2_CP2', {}, {}) + # with other cases + result = common_script_utils.get_param_fixed_ips('VDU2_CP2', {}, req) + self.assertEqual(expected_result, result) + def _inst_example_get_network_fixed_ips_from_inst(self): ext_cp = { "cpdId": "VDU2_CP2", diff --git a/tacker/tests/unit/sol_refactored/common/test_lcm_op_occ_utils.py b/tacker/tests/unit/sol_refactored/common/test_lcm_op_occ_utils.py index 5f082ac4c..c42702808 100644 --- a/tacker/tests/unit/sol_refactored/common/test_lcm_op_occ_utils.py +++ b/tacker/tests/unit/sol_refactored/common/test_lcm_op_occ_utils.py @@ -12,8 +12,10 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +from unittest import mock from tacker import context +from tacker.sol_refactored.common import exceptions as sol_ex from tacker.sol_refactored.common import lcm_op_occ_utils as lcmocc_utils from tacker.sol_refactored import objects from tacker.sol_refactored.objects.v2 import fields @@ -2108,6 +2110,31 @@ _expected_resource_changes_change_vnfpkg = { ] } +# lcmocc_info_example +_error = { + 'status': 1, + 'detail': 'error' +} +_lcmocc_modify_value = { + 'id': 'test-1', + 'vnfInstanceId': 'instance-1', + 'operation': 'MODIFY_INFO', + 'operationState': 'PROCESSING', + 'isAutomaticInvocation': False, + 'startTime': '2021-01-22 13:41:03+00:00' +} +_lcmocc_inst_value = { + 'id': 'test-2', + 'vnfInstanceId': 'instance-2', + 'operation': 'INSTANTIATE', + 'operationState': 'COMPLETED', + 'isAutomaticInvocation': False, + 'startTime': '2021-01-23 13:41:03+00:00', + 'resourceChanges': _expected_resource_changes_instantiate, + 'changedInfo': _expected_changedInfo + +} + class TestLcmOpOccUtils(base.BaseTestCase): @@ -2312,3 +2339,149 @@ class TestLcmOpOccUtils(base.BaseTestCase): self.assertEqual( _expected_resource_changes_change_vnfpkg, self._sort_resource_changes(lcmocc['resourceChanges'])) + + @mock.patch.object(objects.base.TackerPersistentObject, 'get_by_id') + def test_get_lcmocc(self, mock_lcmocc): + mock_lcmocc.return_value = objects.VnfLcmOpOccV2( + operation='INSTANTIATE') + expected_result = objects.VnfLcmOpOccV2( + operation='INSTANTIATE') + + result = lcmocc_utils.get_lcmocc(context, 'lcmocc_id') + self.assertEqual(expected_result.operation, result.operation) + + @mock.patch.object(objects.base.TackerPersistentObject, 'get_by_id') + def test_get_lcmocc_error(self, mock_lcmocc): + mock_lcmocc.return_value = None + self.assertRaises( + sol_ex.VnfLcmOpOccNotFound, + lcmocc_utils.get_lcmocc, context, 'lcmocc_id') + + @mock.patch.object(objects.base.TackerPersistentObject, 'get_all') + def test_get_lcmocc_all(self, mock_lcmocc): + mock_lcmocc.return_value = [objects.VnfLcmOpOccV2( + operation='INSTANTIATE')] + expected_result = [objects.VnfLcmOpOccV2( + operation='INSTANTIATE')] + + result = lcmocc_utils.get_lcmocc_all(context) + self.assertEqual(expected_result[0].operation, result[0].operation) + + def test_make_lcmocc_links(self): + lcmocc = objects.VnfLcmOpOccV2( + id='test-1', vnfInstanceId='instance-1', operation='INSTANTIATE') + endpoint = 'http://127.0.0.1:9890' + + expected_result = objects.VnfLcmOpOccV2_Links() + expected_result.self = objects.Link( + href=f'{endpoint}/vnflcm/v2/vnf_lcm_op_occs/{lcmocc.id}') + expected_result.vnfInstance = objects.Link( + href=f'{endpoint}/vnflcm/v2/vnf_instances/{lcmocc.vnfInstanceId}') + expected_result.retry = objects.Link( + href=f'{endpoint}/vnflcm/v2/vnf_lcm_op_occs/{lcmocc.id}/retry') + expected_result.rollback = objects.Link( + href=f'{endpoint}/vnflcm/v2/vnf_lcm_op_occs/{lcmocc.id}/rollback') + expected_result.fail = objects.Link( + href=f'{endpoint}/vnflcm/v2/vnf_lcm_op_occs/{lcmocc.id}/fail') + + result = lcmocc_utils.make_lcmocc_links(lcmocc, endpoint) + self.assertEqual(expected_result.self.href, result.self.href) + self.assertEqual(expected_result.vnfInstance.href, + result.vnfInstance.href) + self.assertEqual(expected_result.retry.href, result.retry.href) + self.assertEqual(expected_result.rollback.href, result.rollback.href) + self.assertEqual(expected_result.fail.href, result.fail.href) + + def test_make_lcmocc_notif_data(self): + subsc_modify = objects.LccnSubscriptionV2( + id='sub-1', verbosity='SHORT') + subsc_inst = objects.LccnSubscriptionV2( + id='sub-1', verbosity='FULL') + lcmocc_modify = objects.VnfLcmOpOccV2.from_dict(_lcmocc_modify_value) + lcmocc_modify.error = objects.ProblemDetails.from_dict(_error) + lcmocc_inst = objects.VnfLcmOpOccV2.from_dict(_lcmocc_inst_value) + endpoint = 'http://127.0.0.1:9890' + + # execute modify lcmocc + modify_result = lcmocc_utils.make_lcmocc_notif_data( + subsc_modify, lcmocc_modify, endpoint) + # execute inst lcmocc + inst_result = lcmocc_utils.make_lcmocc_notif_data( + subsc_inst, lcmocc_inst, endpoint) + + self.assertEqual('START', modify_result.notificationStatus) + self.assertEqual('error', modify_result.error.detail) + self.assertIsNotNone(inst_result.affectedVnfcs) + self.assertIsNotNone(inst_result.changedInfo) + + @mock.patch.object(objects.base.TackerPersistentObject, 'get_by_filter') + def test_get_inst_lcmocc(self, mock_value): + inst = objects.VnfInstanceV2(id='test-instance') + value_1 = { + 'id': 'test-1', + 'vnfInstanceId': 'instance-1', + 'operation': 'INSTANTIATE', + 'operationState': 'COMPLETED', + 'startTime': '2021-01-22 13:41:03+00:00' + } + value_2 = { + 'id': 'test-2', + 'vnfInstanceId': 'instance-2', + 'operation': 'INSTANTIATE', + 'operationState': 'COMPLETED', + 'startTime': '2021-01-23 13:41:03+00:00' + } + mock_value.return_value = [ + objects.VnfLcmOpOccV2.from_dict(value_1), + objects.VnfLcmOpOccV2.from_dict(value_2) + ] + expected_result = objects.VnfLcmOpOccV2.from_dict(value_2) + result = lcmocc_utils.get_inst_lcmocc(context, inst) + self.assertEqual(expected_result.id, result.id) + + @mock.patch.object(objects.base.TackerPersistentObject, 'get_by_filter') + @mock.patch.object(objects.base.TackerPersistentObject, 'get_by_id') + def test_get_grant_req_and_grant(self, mock_grant, mock_grant_reqs): + lcmocc_modify = objects.VnfLcmOpOccV2.from_dict(_lcmocc_modify_value) + lcmocc_inst = objects.VnfLcmOpOccV2.from_dict(_lcmocc_inst_value) + + # execute modify lcmocc + modify_grant_req, modify_grant = lcmocc_utils.get_grant_req_and_grant( + context, lcmocc_modify) + self.assertIsNone(modify_grant_req) + self.assertIsNone(modify_grant) + + # execute inst lcmocc + lcmocc_inst.grantId = 'grant-1' + mock_grant_reqs.return_value = [objects.GrantRequestV1( + vnfInstanceId='inst-1', vnfLcmOpOccId='lcmocc-1', + vnfdId='vnfd-1', operation='INSTANTIATE', + isAutomaticInvocation=False)] + mock_grant.return_value = objects.GrantV1( + id='grant-1', vnfInstanceId='inst-1', vnfLcmOpOccId='lcmocc-1') + inst_grant_req, inst_grant = lcmocc_utils.get_grant_req_and_grant( + context, lcmocc_inst) + self.assertEqual('inst-1', inst_grant_req.vnfInstanceId) + self.assertEqual('grant-1', inst_grant.id) + + @mock.patch.object(objects.base.TackerPersistentObject, + 'get_by_filter') + @mock.patch.object(objects.base.TackerPersistentObject, 'get_by_id') + def test_get_grant_req_and_grant_error(self, mock_grant_reqs, mock_grant): + lcmocc_modify = objects.VnfLcmOpOccV2.from_dict(_lcmocc_modify_value) + lcmocc_inst = objects.VnfLcmOpOccV2.from_dict(_lcmocc_inst_value) + + # execute modify lcmocc + modify_grant_req, modify_grant = lcmocc_utils.get_grant_req_and_grant( + context, lcmocc_modify) + self.assertIsNone(modify_grant_req) + self.assertIsNone(modify_grant) + + # execute inst lcmocc + lcmocc_inst.grantId = 'grant-1' + mock_grant_reqs.return_value = None + mock_grant.return_value = objects.GrantV1( + id='grant-1', vnfInstanceId='inst-1', vnfLcmOpOccId='lcmocc-1') + self.assertRaises( + sol_ex.GrantRequestOrGrantNotFound, + lcmocc_utils.get_grant_req_and_grant, context, lcmocc_inst) diff --git a/tacker/tests/unit/sol_refactored/common/test_subscription_utils.py b/tacker/tests/unit/sol_refactored/common/test_subscription_utils.py new file mode 100644 index 000000000..7db76f3ae --- /dev/null +++ b/tacker/tests/unit/sol_refactored/common/test_subscription_utils.py @@ -0,0 +1,383 @@ +# Copyright (C) 2022 FUJITSU +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import requests +from unittest import mock + +from oslo_utils import uuidutils + +from tacker import context +from tacker.sol_refactored.api import api_version +from tacker.sol_refactored.common import exceptions as sol_ex +from tacker.sol_refactored.common import http_client +from tacker.sol_refactored.common import subscription_utils as subsc_utils +from tacker.sol_refactored import objects +from tacker.tests import base + + +class TestSubscriptionUtils(base.BaseTestCase): + + def setUp(self): + super(TestSubscriptionUtils, self).setUp() + objects.register_all() + self.context = context.get_admin_context() + self.context.api_version = api_version.APIVersion('2.0.0') + + @mock.patch.object(objects.base.TackerPersistentObject, 'get_by_id') + def test_get_subsc(self, mock_subsc): + mock_subsc.return_value = objects.LccnSubscriptionV2(id='subsc-1') + + result = subsc_utils.get_subsc(context, 'subsc-1') + self.assertEqual('subsc-1', result.id) + + @mock.patch.object(objects.base.TackerPersistentObject, 'get_by_id') + def test_get_subsc_error(self, mock_subsc): + mock_subsc.return_value = None + self.assertRaises( + sol_ex.LccnSubscriptionNotFound, + subsc_utils.get_subsc, context, 'subsc-1') + + @mock.patch.object(objects.base.TackerPersistentObject, 'get_all') + def test_get_subsc_all(self, mock_subsc): + mock_subsc.return_value = [objects.LccnSubscriptionV2(id='subsc-1')] + + result = subsc_utils.get_subsc_all(context) + self.assertEqual('subsc-1', result[0].id) + + @mock.patch.object(http_client.HttpClient, 'do_request') + def test_send_notification(self, mock_resp): + subsc_no_auth = objects.LccnSubscriptionV2( + id='sub-1', verbosity='SHORT', + callbackUri='http://127.0.0.1/callback') + notif_data_no_auth = objects.VnfLcmOperationOccurrenceNotificationV2( + id=uuidutils.generate_uuid() + ) + resp_no_auth = requests.Response() + resp_no_auth.status_code = 204 + mock_resp.return_value = (resp_no_auth, None) + + # execute no_auth + subsc_utils.send_notification(subsc_no_auth, notif_data_no_auth) + + subsc_basic_auth = objects.LccnSubscriptionV2( + id='sub-2', verbosity='SHORT', + callbackUri='http://127.0.0.1/callback', + authentication=objects.SubscriptionAuthentication( + paramsBasic=objects.SubscriptionAuthentication_ParamsBasic( + userName='test', password='test'))) + + # execute basic_auth + subsc_utils.send_notification(subsc_basic_auth, notif_data_no_auth) + + subsc_oauth2 = objects.LccnSubscriptionV2( + id='sub-3', verbosity='SHORT', + callbackUri='http://127.0.0.1/callback', + authentication=objects.SubscriptionAuthentication( + paramsOauth2ClientCredentials=( + objects.SubscriptionAuthentication_ParamsOauth2( + clientId='test', clientPassword='test', + tokenEndpoint='http://127.0.0.1/token')))) + + # execute oauth2 + subsc_utils.send_notification(subsc_oauth2, notif_data_no_auth) + + @mock.patch.object(http_client.HttpClient, 'do_request') + def test_send_notification_error_code(self, mock_resp): + subsc_no_auth = objects.LccnSubscriptionV2( + id='sub-1', verbosity='SHORT', + callbackUri='http://127.0.0.1/callback') + notif_data_no_auth = objects.VnfLcmOperationOccurrenceNotificationV2( + id=uuidutils.generate_uuid() + ) + resp_no_auth = requests.Response() + resp_no_auth.status_code = 200 + mock_resp.return_value = (resp_no_auth, None) + + # execute no_auth + subsc_utils.send_notification(subsc_no_auth, notif_data_no_auth) + + @mock.patch.object(http_client.HttpClient, 'do_request') + def test_send_notification_error(self, mock_resp): + subsc_no_auth = objects.LccnSubscriptionV2( + id='sub-1', verbosity='SHORT', + callbackUri='http://127.0.0.1/callback') + notif_data_no_auth = objects.VnfLcmOperationOccurrenceNotificationV2( + id=uuidutils.generate_uuid() + ) + resp_no_auth = Exception() + mock_resp.return_value = (resp_no_auth, None) + + # execute no_auth + subsc_utils.send_notification(subsc_no_auth, notif_data_no_auth) + + @mock.patch.object(http_client.HttpClient, 'do_request') + def test_test_notification(self, mock_resp): + subsc_no_auth = objects.LccnSubscriptionV2( + id='sub-1', verbosity='SHORT', + callbackUri='http://127.0.0.1/callback') + + resp_no_auth = requests.Response() + resp_no_auth.status_code = 204 + mock_resp.return_value = (resp_no_auth, None) + + # execute no_auth + subsc_utils.test_notification(subsc_no_auth) + + @mock.patch.object(http_client.HttpClient, 'do_request') + def test_test_notification_error_code(self, mock_resp): + subsc_no_auth = objects.LccnSubscriptionV2( + id='sub-1', verbosity='SHORT', + callbackUri='http://127.0.0.1/callback') + resp_no_auth = requests.Response() + resp_no_auth.status_code = 200 + mock_resp.return_value = (resp_no_auth, None) + + # execute no_auth + self.assertRaises(sol_ex.TestNotificationFailed, + subsc_utils.test_notification, subsc_no_auth) + + @mock.patch.object(http_client.HttpClient, 'do_request') + def test_test_notification_error(self, mock_resp): + subsc_no_auth = objects.LccnSubscriptionV2( + id='sub-1', verbosity='SHORT', + callbackUri='http://127.0.0.1/callback') + resp_no_auth = Exception() + mock_resp.return_value = (resp_no_auth, None) + + # execute no_auth + self.assertRaises(sol_ex.TestNotificationFailed, + subsc_utils.test_notification, subsc_no_auth) + + def test_match_version(self): + inst = objects.VnfInstanceV2( + id='test-instance', vnfSoftwareVersion='1.1.1', vnfdVersion='1.2') + version_mismatch = ( + objects._VnfProductsFromProviders_VnfProducts_Versions( + vnfSoftwareVersion='1.1.2')) + result_1 = subsc_utils.match_version(version_mismatch, inst) + self.assertEqual(False, result_1) + + version_match = ( + objects._VnfProductsFromProviders_VnfProducts_Versions( + vnfSoftwareVersion='1.1.1')) + result_2 = subsc_utils.match_version(version_match, inst) + self.assertEqual(True, result_2) + + version_vnfd = ( + objects._VnfProductsFromProviders_VnfProducts_Versions( + vnfSoftwareVersion='1.1.1', vnfdVersions=['1.2'])) + result_3 = subsc_utils.match_version(version_vnfd, inst) + self.assertEqual(True, result_3) + + def test_match_products_per_provider(self): + inst = objects.VnfInstanceV2( + id='test-instance', vnfProvider='company', + vnfSoftwareVersion='1.1.1', vnfdVersion='1.2', + vnfProductName='test') + products_mismatch = objects._VnfProductsFromProviders( + vnfProvider='test') + result_1 = subsc_utils.match_products_per_provider( + products_mismatch, inst) + self.assertEqual(False, result_1) + + products_vnfproducts_no_exist = objects._VnfProductsFromProviders( + vnfProvider='company') + result_2 = subsc_utils.match_products_per_provider( + products_vnfproducts_no_exist, inst) + self.assertEqual(True, result_2) + + products_vnf_mismatch = objects._VnfProductsFromProviders( + vnfProvider='company', vnfProducts=[ + objects._VnfProductsFromProviders_VnfProducts( + vnfProductName='error'), + objects._VnfProductsFromProviders_VnfProducts( + vnfProductName='test', versions=[ + objects._VnfProductsFromProviders_VnfProducts_Versions( + vnfSoftwareVersion='1.1.2' + )]) + ]) + result_3 = subsc_utils.match_products_per_provider( + products_vnf_mismatch, inst) + self.assertEqual(False, result_3) + + products_vnf_match_with_no_versions = ( + objects._VnfProductsFromProviders( + vnfProvider='company', vnfProducts=[ + objects._VnfProductsFromProviders_VnfProducts( + vnfProductName='test')])) + + result_4 = subsc_utils.match_products_per_provider( + products_vnf_match_with_no_versions, inst) + self.assertEqual(True, result_4) + + products_vnf_match_with_versions = objects._VnfProductsFromProviders( + vnfProvider='company', vnfProducts=[ + objects._VnfProductsFromProviders_VnfProducts( + vnfProductName='test', versions=[ + objects._VnfProductsFromProviders_VnfProducts_Versions( + vnfSoftwareVersion='1.1.1' + )]) + ]) + + result_5 = subsc_utils.match_products_per_provider( + products_vnf_match_with_versions, inst) + self.assertEqual(True, result_5) + + def test_match_inst_subsc_filter(self): + inst = objects.VnfInstanceV2( + id='test-instance', vnfProvider='company', + vnfSoftwareVersion='1.1.1', vnfdVersion='1.2', + vnfProductName='test', vnfdId='vnfdid-1', + vnfInstanceName='test') + inst_filter_mismatch_vnfdid = objects.VnfInstanceSubscriptionFilter( + vnfdIds=['error']) + result_1 = subsc_utils.match_inst_subsc_filter( + inst_filter_mismatch_vnfdid, inst) + self.assertEqual(False, result_1) + + products_vnfproducts_no_exist = objects._VnfProductsFromProviders( + vnfProvider='company') + inst_filter_match_products = objects.VnfInstanceSubscriptionFilter( + vnfProductsFromProviders=[products_vnfproducts_no_exist]) + result_2 = subsc_utils.match_inst_subsc_filter( + inst_filter_match_products, inst) + self.assertEqual(True, result_2) + + products_mismatch = objects._VnfProductsFromProviders( + vnfProvider='test') + inst_filter_mismatch_products = objects.VnfInstanceSubscriptionFilter( + vnfProductsFromProviders=[products_mismatch]) + result_3 = subsc_utils.match_inst_subsc_filter( + inst_filter_mismatch_products, inst) + self.assertEqual(False, result_3) + + inst_filter_mismatch_inst_id = objects.VnfInstanceSubscriptionFilter( + vnfInstanceIds=['instanceid-2']) + result_4 = subsc_utils.match_inst_subsc_filter( + inst_filter_mismatch_inst_id, inst) + self.assertEqual(False, result_4) + + inst_filter_mismatch_inst_name = objects.VnfInstanceSubscriptionFilter( + vnfInstanceNames=['instance_name-2']) + result_5 = subsc_utils.match_inst_subsc_filter( + inst_filter_mismatch_inst_name, inst) + self.assertEqual(False, result_5) + + @mock.patch.object(objects.base.TackerPersistentObject, 'get_all') + def test_get_inst_create_subscs(self, mock_subscs): + inst = objects.VnfInstanceV2(id='test-instance') + mock_subscs.return_value = [objects.LccnSubscriptionV2(id='subsc-1')] + result = subsc_utils.get_inst_create_subscs(context, inst) + + self.assertEqual('subsc-1', result[0].id) + + @mock.patch.object(objects.base.TackerPersistentObject, 'get_all') + def test_get_inst_delete_subscs(self, mock_subscs): + inst = objects.VnfInstanceV2(id='test-instance') + mock_subscs.return_value = [objects.LccnSubscriptionV2(id='subsc-1')] + result = subsc_utils.get_inst_delete_subscs(context, inst) + + self.assertEqual('subsc-1', result[0].id) + + @mock.patch.object(objects.base.TackerPersistentObject, 'get_all') + def test_get_lcmocc_subscs(self, mock_subscs): + inst = objects.VnfInstanceV2(id='test-instance') + lcmocc = objects.VnfLcmOpOccV2(operationState='COMPLETED', + operation='INSTANTIATE') + mock_subscs.return_value = [objects.LccnSubscriptionV2(id='subsc-1')] + result = subsc_utils.get_lcmocc_subscs(context, lcmocc, inst) + + self.assertEqual('subsc-1', result[0].id) + + @mock.patch.object(objects.base.TackerPersistentObject, 'get_all') + def test_get_matched_subscs(self, mock_subscs): + inst = objects.VnfInstanceV2(id='test-instance', vnfProvider='company') + notif_type = 'VnfLcmOperationOccurrenceNotification' + op_type = 'INSTANTIATE' + op_status = 'COMPLETED' + + subscs_no_fileter = objects.LccnSubscriptionV2(id='subsc-1') + + products_vnfproducts_no_exist = objects._VnfProductsFromProviders( + vnfProvider='company') + inst_filter_match_products = objects.VnfInstanceSubscriptionFilter( + vnfProductsFromProviders=[products_vnfproducts_no_exist]) + subscs_filter_match = objects.LccnSubscriptionV2( + id='subsc-2', + filter=objects.LifecycleChangeNotificationsFilterV2( + vnfInstanceSubscriptionFilter=inst_filter_match_products)) + + products_mismatch = objects._VnfProductsFromProviders( + vnfProvider='test') + inst_filter_mismatch_products = objects.VnfInstanceSubscriptionFilter( + vnfProductsFromProviders=[products_mismatch]) + subscs_filter_mismatch = objects.LccnSubscriptionV2( + id='subsc-3', + filter=objects.LifecycleChangeNotificationsFilterV2( + vnfInstanceSubscriptionFilter=inst_filter_mismatch_products)) + + subscs_noti_type_match = objects.LccnSubscriptionV2( + id='subsc-4', filter=objects.LifecycleChangeNotificationsFilterV2( + notificationTypes=['VnfLcmOperationOccurrenceNotification'])) + subscs_noti_type_mismatch = objects.LccnSubscriptionV2( + id='subsc-5', filter=objects.LifecycleChangeNotificationsFilterV2( + notificationTypes=['VnfIdentifierCreationNotification'])) + + subscs_op_type_match = objects.LccnSubscriptionV2( + id='subsc-6', filter=objects.LifecycleChangeNotificationsFilterV2( + operationTypes=['INSTANTIATE'])) + subscs_op_type_mismatch = objects.LccnSubscriptionV2( + id='subsc-7', filter=objects.LifecycleChangeNotificationsFilterV2( + operationTypes=['TERMINATE'])) + + subscs_op_status_match = objects.LccnSubscriptionV2( + id='subsc-8', filter=objects.LifecycleChangeNotificationsFilterV2( + operationStatus=['COMPLETED'])) + subscs_op_status_mismatch = objects.LccnSubscriptionV2( + id='subsc-9', filter=objects.LifecycleChangeNotificationsFilterV2( + operationStatus=['FAILED_TEMP'])) + + mock_subscs.return_value = [ + subscs_no_fileter, subscs_filter_match, subscs_filter_mismatch, + subscs_noti_type_match, subscs_noti_type_mismatch, + subscs_op_type_match, subscs_op_type_mismatch, + subscs_op_status_match, subscs_op_status_mismatch] + + result = subsc_utils.get_matched_subscs( + context, inst, notif_type, op_type, op_status) + + expected_ids = ['subsc-1', 'subsc-2', 'subsc-4', 'subsc-6', 'subsc-8'] + + result_ids = [sub.id for sub in result] + self.assertEqual(expected_ids, result_ids) + + def test_make_create_inst_notif_data(self): + subsc = objects.LccnSubscriptionV2(id='subsc-1') + inst = objects.VnfInstanceV2(id='test-instance') + endpoint = 'http://127.0.0.1:9890' + + result = subsc_utils.make_create_inst_notif_data(subsc, inst, endpoint) + + self.assertEqual('subsc-1', result.subscriptionId) + self.assertEqual('test-instance', result.vnfInstanceId) + + def test_make_delete_inst_notif_data(self): + subsc = objects.LccnSubscriptionV2(id='subsc-1') + inst = objects.VnfInstanceV2(id='test-instance') + endpoint = 'http://127.0.0.1:9890' + + result = subsc_utils.make_delete_inst_notif_data(subsc, inst, endpoint) + + self.assertEqual('subsc-1', result.subscriptionId) + self.assertEqual('test-instance', result.vnfInstanceId) diff --git a/tacker/tests/unit/sol_refactored/common/test_vim_utils.py b/tacker/tests/unit/sol_refactored/common/test_vim_utils.py new file mode 100644 index 000000000..68df17712 --- /dev/null +++ b/tacker/tests/unit/sol_refactored/common/test_vim_utils.py @@ -0,0 +1,114 @@ +# Copyright (C) 2022 FUJITSU +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from unittest import mock + +from tacker import context +from tacker.sol_refactored.api import api_version +from tacker.sol_refactored.common import exceptions as sol_ex +from tacker.sol_refactored.common import vim_utils +from tacker.sol_refactored import objects +from tacker.tests import base +from tacker.vnfm import vim_client + +_vim_openstack = { + "placement_attr": { + "regions": ['RegionOne'] + }, + "vim_auth": { + "username": "nfv_user", + "password": "devstack", + "project_name": "nfv", + "project_domain_name": "Default", + "user_domain_name": "Default", + "auth_url": "http://127.0.0.1/identity" + }, + "vim_type": "openstack", + "vim_id": "openstack-1" +} +_vim_kubernetes_bearer_token = { + "placement_attr": { + "regions": ['RegionOne'] + }, + "vim_auth": { + "username": None, + "password": None, + "bearer_token": "test_token", + "auth_url": "https://127.0.0.1:6443", + "ssl_ca_cert": "test_ssl" + }, + "vim_type": "kubernetes", + "vim_id": "kubernetes-1" +} +_vim_kubernetes_user = { + "vim_auth": { + "username": "admin", + "password": "admin", + "auth_url": "https://127.0.0.1:6443" + }, + "vim_type": "kubernetes", + "vim_id": "kubernetes-2" +} + + +class TestVimUtils(base.BaseTestCase): + + def setUp(self): + super(TestVimUtils, self).setUp() + objects.register_all() + self.context = context.get_admin_context() + self.context.api_version = api_version.APIVersion('2.0.0') + + @mock.patch.object(vim_client.VimClient, 'get_vim') + def test_get_default_vim(self, mock_vim): + mock_vim.return_value = _vim_openstack + result = vim_utils.get_default_vim(context) + + self.assertEqual('openstack-1', result.vimId) + + @mock.patch.object(vim_client.VimClient, 'get_vim') + def test_get_default_vim_error(self, mock_vim): + mock_vim.return_value = Exception + vim_utils.get_default_vim(context) + + @mock.patch.object(vim_client.VimClient, 'get_vim') + def test_get_vim(self, mock_vim): + mock_vim.return_value = _vim_kubernetes_bearer_token + result = vim_utils.get_vim(context, 'kubernetes-1') + + self.assertEqual('kubernetes-1', result.vimId) + + @mock.patch.object(vim_client.VimClient, 'get_vim') + def test_get_vim_error(self, mock_vim): + mock_vim.return_value = Exception + self.assertRaises( + sol_ex.VimNotFound, vim_utils.get_vim, context, 'test') + + def test_vim_to_conn_info(self): + vim_openstack = _vim_openstack + vim_kubernetes_1 = _vim_kubernetes_bearer_token + vim_kubernetes_2 = _vim_kubernetes_user + + result_1 = vim_utils.vim_to_conn_info(vim_openstack) + self.assertEqual('openstack-1', result_1.vimId) + + result_2 = vim_utils.vim_to_conn_info(vim_kubernetes_1) + self.assertEqual('kubernetes-1', result_2.vimId) + + result_3 = vim_utils.vim_to_conn_info(vim_kubernetes_2) + self.assertEqual('kubernetes-2', result_3.vimId) + + self.assertRaises( + sol_ex.SolException, vim_utils.vim_to_conn_info, + {'vim_type': 'test', 'vim_auth': 'test'}) diff --git a/tacker/tests/unit/sol_refactored/common/test_vnf_instance_utils.py b/tacker/tests/unit/sol_refactored/common/test_vnf_instance_utils.py index 68018a0b3..99204dc72 100644 --- a/tacker/tests/unit/sol_refactored/common/test_vnf_instance_utils.py +++ b/tacker/tests/unit/sol_refactored/common/test_vnf_instance_utils.py @@ -12,23 +12,31 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +from unittest import mock +from tacker import context +from tacker.sol_refactored.common import exceptions as sol_ex from tacker.sol_refactored.common import vnf_instance_utils as inst_utils +from tacker.sol_refactored import objects from tacker.tests import base class TestVnfInstanceUtils(base.BaseTestCase): + def setUp(self): + super(TestVnfInstanceUtils, self).setUp() + objects.register_all() + self.context = context.get_admin_context() def test_json_merge_patch(self): # patch is not dict. - target = {"key1", "value1"} + target = {"key1": "value1"} patch = "text" result = inst_utils.json_merge_patch(target, patch) self.assertEqual(patch, result) # target is not dict. target = "text" - patch = {"key1", "value1"} + patch = {"key1": "value1"} result = inst_utils.json_merge_patch(target, patch) self.assertEqual(patch, result) @@ -65,3 +73,24 @@ class TestVnfInstanceUtils(base.BaseTestCase): } result = inst_utils.json_merge_patch(target, patch) self.assertEqual(expected_result, result) + + @mock.patch.object(objects.base.TackerPersistentObject, 'get_by_id') + def test_get_inst(self, mock_inst): + mock_inst.return_value = objects.VnfInstanceV2(id='inst-1') + + result = inst_utils.get_inst(context, 'inst-1') + self.assertEqual('inst-1', result.id) + + @mock.patch.object(objects.base.TackerPersistentObject, 'get_by_id') + def test_get_inst_error(self, mock_inst): + mock_inst.return_value = None + self.assertRaises( + sol_ex.VnfInstanceNotFound, + inst_utils.get_inst, context, 'inst-1') + + @mock.patch.object(objects.base.TackerPersistentObject, 'get_all') + def test_get_inst_all(self, mock_inst): + mock_inst.return_value = [objects.VnfInstanceV2(id='inst-1')] + + result = inst_utils.get_inst_all(context) + self.assertEqual('inst-1', result[0].id) diff --git a/tacker/tests/unit/sol_refactored/common/test_vnfd_utils.py b/tacker/tests/unit/sol_refactored/common/test_vnfd_utils.py index d59fcb286..765f4c384 100644 --- a/tacker/tests/unit/sol_refactored/common/test_vnfd_utils.py +++ b/tacker/tests/unit/sol_refactored/common/test_vnfd_utils.py @@ -12,8 +12,13 @@ # 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 shutil +import tempfile + +from oslo_log import log as logging +from oslo_utils import uuidutils +import yaml from tacker.sol_refactored.common import exceptions as sol_ex from tacker.sol_refactored.common import vnfd_utils @@ -22,6 +27,7 @@ from tacker.tests import base SAMPLE_VNFD_ID = "b1bb0ce7-ebca-4fa7-95ed-4840d7000000" SAMPLE_FLAVOUR_ID = "simple" +LOG = logging.getLogger() class TestVnfd(base.BaseTestCase): @@ -179,3 +185,126 @@ class TestVnfd(base.BaseTestCase): result = self.vnfd_1.get_max_scale_level(SAMPLE_FLAVOUR_ID, 'VDU1_scale') self.assertEqual(2, result) + + def test_init_from_zip_file(self): + vnfd_id = uuidutils.generate_uuid() + tmp_dir = tempfile.mkdtemp() + cur_dir = os.path.dirname(__file__) + sample_path = os.path.join(cur_dir, "../samples/sample1") + shutil.make_archive(tmp_dir, 'zip', root_dir=sample_path) + + vnfd = vnfd_utils.Vnfd(vnfd_id) + with open(f'{tmp_dir}.zip', "rb") as f: + content = f.read() + vnfd.init_from_zip_file(content) + with open(f'{sample_path}/TOSCA-Metadata/TOSCA.meta', 'r') as f: + tosca_content = yaml.safe_load(f.read()) + with open(f'{sample_path}/Definitions/' + f'ut_sample1_df_simple.yaml', 'r') as f: + definition_content = yaml.safe_load(f.read()) + self.assertEqual(tosca_content, vnfd.tosca_meta) + self.assertEqual(True, vnfd.csar_dir_is_tmp) + self.assertEqual( + definition_content, vnfd.definitions['ut_sample1_df_simple.yaml']) + + def test_init_vnfd_error(self): + vnfd_id = uuidutils.generate_uuid() + vnfd = vnfd_utils.Vnfd(vnfd_id) + vnfd.csar_dir = 'test' + self.assertRaises(sol_ex.InvalidVnfdFormat, vnfd.init_vnfd) + + def test_delete(self): + os.mkdir("/tmp/test") + vnfd_id = uuidutils.generate_uuid() + vnfd = vnfd_utils.Vnfd(vnfd_id) + vnfd.csar_dir_is_tmp = True + vnfd.csar_dir = "/tmp/test" + vnfd.delete() + result = os.path.isdir(vnfd.csar_dir) + self.assertEqual(False, result) + + def test_get_vnfd_properties(self): + vnfd_id = uuidutils.generate_uuid() + vnfd = vnfd_utils.Vnfd(vnfd_id) + expected_result = { + 'vnfConfigurableProperties': {}, + 'extensions': {}, + 'metadata': {} + } + result = vnfd.get_vnfd_properties() + self.assertEqual(expected_result, result) + + def test_get_base_hot_abnormal(self): + flavour_id = 'simple' + vnfd_id = uuidutils.generate_uuid() + vnfd = vnfd_utils.Vnfd(vnfd_id) + vnfd.csar_dir = '/test/' + base_hot_result = vnfd.get_base_hot(flavour_id) + self.assertEqual({}, base_hot_result) + + flavour_id = 'error' + base_hot_result = self.vnfd_1.get_base_hot(flavour_id) + self.assertIsNotNone(base_hot_result['template']) + + def test_remove_tmp_csar_dir(self): + os.mkdir("/tmp/test") + vnfd_id = uuidutils.generate_uuid() + vnfd = vnfd_utils.Vnfd(vnfd_id) + tmp_dir = "/tmp/test" + vnfd.remove_tmp_csar_dir(tmp_dir) + result = os.path.isdir(tmp_dir) + self.assertEqual(False, result) + + def test_remove_tmp_csar_dir_error(self): + vnfd_id = uuidutils.generate_uuid() + vnfd = vnfd_utils.Vnfd(vnfd_id) + log_name = "tacker.sol_refactored.common.vnfd_utils" + with self.assertLogs(logger=log_name, level=logging.DEBUG) as cm: + vnfd.remove_tmp_csar_dir('test') + + msg = (f'ERROR:{log_name}:rmtree test failed') + self.assertIn(f'{msg}', cm.output[0].split('\n')[0]) + + def test_get_policy_values_by_type(self): + result = self.vnfd_1.get_policy_values_by_type( + 'error', 'tosca.policies.nfv.AntiAffinityRule') + self.assertEqual('nfvi_node', result[0]['properties']['scope']) + + def test_get_vdu_num_none(self): + result = self.vnfd_1.get_vdu_num('error', 'VDU1', 'default') + self.assertEqual(0, result) + + def test_get_affinity_targets(self): + result = self.vnfd_1.get_affinity_targets('error') + expected_result = [(['VDU3'], 'zone')] + self.assertEqual(expected_result, result) + + def test_get_interface_script_abnormal(self): + result = self.vnfd_1.get_interface_script('error', 'instantiate_start') + self.assertEqual(None, result) + + result = self.vnfd_1.get_interface_script('error', 'instantiate_end') + self.assertEqual(None, result) + + self.vnfd_1.get_interface_script('error', 'instantiate_end') + self.assertRaises( + sol_ex.SolHttpError422, self.vnfd_1.get_interface_script, + 'error', 'terminate_start') + + def test_get_scale_vdu_and_num_abnormal(self): + result = self.vnfd_1.get_scale_vdu_and_num('error', 'VDU1_scale') + self.assertEqual({}, result) + + def test_get_scale_info_from_inst_level_abnormal(self): + result = self.vnfd_1.get_scale_info_from_inst_level('error', 'default') + self.assertEqual({}, result) + + def test_get_max_scale_level_abnormal(self): + result = self.vnfd_1.get_max_scale_level('error', 'VDU1_scale') + self.assertEqual(0, result) + + def test_get_vnf_artifact_files_manifest(self): + result = self.vnfd_1.get_vnf_artifact_files() + expected_result = ['Scripts/install.sh', + 'Files/kubernetes/deployment.yaml'] + self.assertEqual(expected_result, result) diff --git a/tacker/tests/unit/sol_refactored/conductor/test_conductor_v2.py b/tacker/tests/unit/sol_refactored/conductor/test_conductor_v2.py index b8afc8fe8..87886a7da 100644 --- a/tacker/tests/unit/sol_refactored/conductor/test_conductor_v2.py +++ b/tacker/tests/unit/sol_refactored/conductor/test_conductor_v2.py @@ -39,8 +39,9 @@ class TestConductorV2(db_base.SqlTestCase): self.conductor = conductor_v2.ConductorV2() self.context = context.get_admin_context() - def _create_inst_and_lcmocc(self, - op_state=fields.LcmOperationStateType.STARTING): + def _create_inst_and_lcmocc( + self, op_state=fields.LcmOperationStateType.STARTING, + is_change_vnfpkg=False): inst = objects.VnfInstanceV2( # required fields id=uuidutils.generate_uuid(), @@ -65,6 +66,9 @@ class TestConductorV2(db_base.SqlTestCase): isCancelPending=False, operationParams=req) + if is_change_vnfpkg: + lcmocc.operation = fields.LcmOperationType.CHANGE_VNFPKG + inst.create(self.context) lcmocc.create(self.context) @@ -530,3 +534,98 @@ class TestConductorV2(db_base.SqlTestCase): lcmocc = lcmocc_utils.get_lcmocc(self.context, lcmocc.id) expected = ex.make_problem_details() self.assertEqual(expected, lcmocc.error.to_dict()) + + def test_start_lcm_op_abnormal(self): + # prepare + lcmocc = self._create_inst_and_lcmocc( + op_state=fields.LcmOperationStateType.PROCESSING) + + result = self.conductor.start_lcm_op(self.context, lcmocc.id) + self.assertEqual(None, result) + + @mock.patch.object(nfvo_client.NfvoClient, 'send_lcmocc_notification') + @mock.patch.object(nfvo_client.NfvoClient, 'get_vnfd') + @mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'post_grant') + @mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'process') + def test_retry_lcm_op_abnormal(self, mocked_process, mocked_post_grant, + mocked_get_vnfd, mocked_send_lcmocc_notification): + # operation state incorrect + lcmocc = self._create_inst_and_lcmocc( + op_state=fields.LcmOperationStateType.PROCESSING) + result = self.conductor.retry_lcm_op(self.context, lcmocc.id) + self.assertEqual(None, result) + + # operation is change_vnfpkg + # prepare + lcmocc = self._create_inst_and_lcmocc( + op_state=fields.LcmOperationStateType.FAILED_TEMP) + lcmocc.operation = fields.LcmOperationType.CHANGE_VNFPKG + lcmocc.operationParams = objects.ChangeCurrentVnfPkgRequest( + vnfdId='test-vnfdid') + self._create_grant_req_and_grant(lcmocc) + mocked_get_vnfd.return_value = mock.Mock() + + op_state = [] + + def _store_state(context, lcmocc, inst, endpoint): + op_state.append(lcmocc.operationState) + + mocked_send_lcmocc_notification.side_effect = _store_state + + # run retry_lcm_op + self.conductor.retry_lcm_op(self.context, lcmocc.id) + + # check operationState transition + self.assertEqual(2, mocked_send_lcmocc_notification.call_count) + self.assertEqual(fields.LcmOperationStateType.PROCESSING, op_state[0]) + self.assertEqual(fields.LcmOperationStateType.COMPLETED, op_state[1]) + + # check grant_req and grant are deleted + self.assertRaises(sol_ex.GrantRequestOrGrantNotFound, + lcmocc_utils.get_grant_req_and_grant, self.context, lcmocc) + + @mock.patch.object(nfvo_client.NfvoClient, 'send_lcmocc_notification') + @mock.patch.object(nfvo_client.NfvoClient, 'get_vnfd') + @mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'post_grant') + @mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'rollback') + def test_rollback_lcm_op_abnormal(self, mocked_rollback, + mocked_post_grant, mocked_get_vnfd, + mocked_send_lcmocc_notification): + # operation state incorrect + lcmocc = self._create_inst_and_lcmocc( + op_state=fields.LcmOperationStateType.PROCESSING) + result = self.conductor.rollback_lcm_op(self.context, lcmocc.id) + self.assertEqual(None, result) + + # operation is change_vnfpkg + lcmocc = self._create_inst_and_lcmocc( + op_state=fields.LcmOperationStateType.FAILED_TEMP, + is_change_vnfpkg=True) + self._create_grant_req_and_grant(lcmocc) + mocked_get_vnfd.return_value = mock.Mock() + + op_state = [] + + def _store_state(context, lcmocc, inst, endpoint): + op_state.append(lcmocc.operationState) + + mocked_send_lcmocc_notification.side_effect = _store_state + + # run rollback_lcm_op + self.conductor.rollback_lcm_op(self.context, lcmocc.id) + + # check operationState transition + self.assertEqual(2, mocked_send_lcmocc_notification.call_count) + self.assertEqual(fields.LcmOperationStateType.ROLLING_BACK, + op_state[0]) + self.assertEqual(fields.LcmOperationStateType.ROLLED_BACK, op_state[1]) + + # check grant_req and grant are deleted + self.assertRaises(sol_ex.GrantRequestOrGrantNotFound, + lcmocc_utils.get_grant_req_and_grant, self.context, lcmocc) + + def test_modify_vnfinfo_abnormal(self): + lcmocc = self._create_inst_and_lcmocc() + + result = self.conductor.modify_vnfinfo(self.context, lcmocc.id) + self.assertEqual(None, result) diff --git a/tacker/tests/unit/sol_refactored/conductor/test_vnflcm_driver_v2.py b/tacker/tests/unit/sol_refactored/conductor/test_vnflcm_driver_v2.py index e61aa2a7a..6a0972244 100644 --- a/tacker/tests/unit/sol_refactored/conductor/test_vnflcm_driver_v2.py +++ b/tacker/tests/unit/sol_refactored/conductor/test_vnflcm_driver_v2.py @@ -12,7 +12,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - +import copy from datetime import datetime import os from unittest import mock @@ -20,9 +20,13 @@ from unittest import mock from oslo_utils import uuidutils from tacker import context +from tacker.sol_refactored.common import exceptions as sol_ex +from tacker.sol_refactored.common import lcm_op_occ_utils as lcmocc_utils +from tacker.sol_refactored.common import vim_utils from tacker.sol_refactored.common import vnfd_utils from tacker.sol_refactored.conductor import vnflcm_driver_v2 from tacker.sol_refactored.infra_drivers.kubernetes import kubernetes +from tacker.sol_refactored.infra_drivers.openstack import openstack from tacker.sol_refactored.nfvo import nfvo_client from tacker.sol_refactored import objects from tacker.sol_refactored.objects.v2 import fields @@ -187,7 +191,8 @@ _ext_vl_3 = { ] } _change_ext_conn_req_example = { - "extVirtualLinks": [_ext_vl_3] + "extVirtualLinks": [_ext_vl_3], + "additionalParams": {"key": "value"} } # instantiatedVnfInfo example for terminate/scale grant test @@ -198,6 +203,7 @@ _change_ext_conn_req_example = { _inst_info_example = { "flavourId": "simple", "vnfState": "STARTED", + 'scaleStatus': [], "extCpInfo": [ { "id": "90561570-264c-4472-b84f-1fff98513475", @@ -397,6 +403,11 @@ _inst_info_example = { "id": "39a7d895-3b19-4330-b6ec-ae3557ea9c01", "cpdId": "VDU2_CP5", "vnfLinkPortId": "4dd7cadd-b9a1-484f-b2f2-1ff50ef0d90f" + }, + { + "id": "315a938a-b194-0c20-6398-ba92cce39c18", + "cpdId": "VDU2_CP6", + "vnfLinkPortId": "6dafdda1-db6c-492e-6dc2-4e5d323d6f98" } ] }, @@ -474,6 +485,15 @@ _inst_info_example = { "vnfLinkPortId": "e0f98917-70ff-4f79-8747-9d7fc22827a4" } ] + }, + { + "id": "vnfc_res_no_cp_info", + "vduId": "VDU1", + "computeResource": { + # "vimConnectionId": omitted + "resourceId": "res_id_VDU1_3", + "vimLevelResourceType": "OS::Nova::Server" + } } ], "vnfVirtualLinkResourceInfo": [ @@ -754,7 +774,7 @@ _change_cnf_vnfpkg_example = { "Files/new_kubernetes/new_deployment.yaml" ], "vdu_params": [{ - "vduId": "VDU1" + "vdu_id": "VDU1" }] } } @@ -946,12 +966,12 @@ class TestVnfLcmDriverV2(base.BaseTestCase): 'LINKPORT': {'VDU1_CP1': [], 'VDU1_CP2': [], 'VDU1_CP3': [], 'VDU1_CP4': [], 'VDU1_CP5': [], 'VDU2_CP1': [], 'VDU2_CP2': [], 'VDU2_CP3': [], - 'VDU2_CP4': [], 'VDU2_CP5': []}, + 'VDU2_CP4': [], 'VDU2_CP5': [], 'VDU2_CP6': []}, 'VL': {'internalVL2': [], 'internalVL3': []} } expected_res_ids = { 'COMPUTE': { - 'VDU1': ['res_id_VDU1_1', 'res_id_VDU1_2'], + 'VDU1': ['res_id_VDU1_1', 'res_id_VDU1_2', 'res_id_VDU1_3'], 'VDU2': ['res_id_VDU2'] }, 'STORAGE': { @@ -968,7 +988,8 @@ class TestVnfLcmDriverV2(base.BaseTestCase): 'VDU2_CP2': ['res_id_VDU2_CP2'], 'VDU2_CP3': ['res_id_VDU2_CP3'], 'VDU2_CP4': ['res_id_VDU2_CP4'], - 'VDU2_CP5': ['res_id_VDU2_CP5'] + 'VDU2_CP5': ['res_id_VDU2_CP5'], + 'VDU2_CP6': [] }, 'VL': { 'internalVL2': ['res_id_internalVL2'], @@ -976,8 +997,9 @@ class TestVnfLcmDriverV2(base.BaseTestCase): } } for res in rm_reses: - check_reses[res['type']][res['resourceTemplateId']].append( - res['resource']['resourceId']) + if res.get('resource', {}).get('resourceId'): + check_reses[res['type']][res['resourceTemplateId']].append( + res['resource']['resourceId']) for key, value in check_reses.items(): for name, ids in value.items(): @@ -1723,7 +1745,7 @@ class TestVnfLcmDriverV2(base.BaseTestCase): } expected_res_ids = { 'COMPUTE': { - 'VDU1': ['res_id_VDU1_1', 'res_id_VDU1_2'], + 'VDU1': ['res_id_VDU1_1', 'res_id_VDU1_2', 'res_id_VDU1_3'], 'VDU2': ['res_id_VDU2'] } } @@ -1782,12 +1804,12 @@ class TestVnfLcmDriverV2(base.BaseTestCase): 'LINKPORT': {'VDU1_CP1': [], 'VDU1_CP2': [], 'VDU1_CP3': [], 'VDU1_CP4': [], 'VDU1_CP5': [], 'VDU2_CP1': [], 'VDU2_CP2': [], 'VDU2_CP3': [], - 'VDU2_CP4': [], 'VDU2_CP5': []}, + 'VDU2_CP4': [], 'VDU2_CP5': [], 'VDU2_CP6': []}, 'VL': {'internalVL2': [], 'internalVL3': []} } expected_res_ids = { 'COMPUTE': { - 'VDU1': ['res_id_VDU1_1', 'res_id_VDU1_2'], + 'VDU1': ['res_id_VDU1_1', 'res_id_VDU1_2', 'res_id_VDU1_3'], 'VDU2': ['res_id_VDU2'] }, 'STORAGE': { @@ -1804,7 +1826,8 @@ class TestVnfLcmDriverV2(base.BaseTestCase): 'VDU2_CP2': ['res_id_VDU2_CP2'], 'VDU2_CP3': ['res_id_VDU2_CP3'], 'VDU2_CP4': ['res_id_VDU2_CP4'], - 'VDU2_CP5': ['res_id_VDU2_CP5'] + 'VDU2_CP5': ['res_id_VDU2_CP5'], + 'VDU2_CP6': [] }, 'VL': { 'internalVL2': ['res_id_internalVL2'], @@ -1812,8 +1835,9 @@ class TestVnfLcmDriverV2(base.BaseTestCase): } } for res in rm_reses: - check_reses[res['type']][res['resourceTemplateId']].append( - res['resource']['resourceId']) + if res.get('resource', {}).get('resourceId'): + check_reses[res['type']][res['resourceTemplateId']].append( + res['resource']['resourceId']) for key, value in check_reses.items(): for name, ids in value.items(): @@ -1827,13 +1851,14 @@ class TestVnfLcmDriverV2(base.BaseTestCase): 'LINKPORT': {'VDU1_CP1': [], 'VDU1_CP2': [], 'VDU1_CP3': [], 'VDU1_CP4': [], 'VDU1_CP5': [], 'VDU2_CP1': [], 'VDU2_CP2': [], 'VDU2_CP3': [], - 'VDU2_CP4': [], 'VDU2_CP5': []}, + 'VDU2_CP4': [], 'VDU2_CP5': [], 'VDU2_CP6': []}, 'VL': {'internalVL2': [], 'internalVL3': []} } for res in add_reses: - check_reses[res['type']][res['resourceTemplateId']].append( - res['id']) - + if res.get('id'): + check_reses[res['type']][res['resourceTemplateId']].append( + res['id']) + expected_res_ids['LINKPORT']['VDU2_CP6'] = ['res_id_VDU2_CP6'] for key, value in check_reses.items(): for name, ids in value.items(): self.assertEqual(len(expected_res_ids[key][name]), len(ids)) @@ -1897,7 +1922,7 @@ class TestVnfLcmDriverV2(base.BaseTestCase): } expected_res_ids = { 'COMPUTE': { - 'VDU1': ['res_id_VDU1_1', 'res_id_VDU1_2'], + 'VDU1': ['res_id_VDU1_1', 'res_id_VDU1_2', 'res_id_VDU1_3'], 'VDU2': ['res_id_VDU2'] }, 'STORAGE': { @@ -2099,3 +2124,1155 @@ class TestVnfLcmDriverV2(base.BaseTestCase): mock_vnfd.return_value = self.vnfd_2 self.driver.change_vnfpkg_process( self.context, lcmocc, inst, grant_req, grant, self.vnfd_3) + + # no lcm-kubernetes-def-files in req + del req.additionalParams['lcm-kubernetes-def-files'] + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.CHANGE_VNFPKG, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + self.driver.change_vnfpkg_process( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_3) + + def test_post_grant(self): + # prepare + req_inst = objects.InstantiateVnfRequest.from_dict( + _inst_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='NOT_INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.INSTANTIATE + ) + grant = objects.GrantV1() + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.INSTANTIATE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req_inst) + self.driver.post_grant( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + self.assertEqual(_inst_req_example['vimConnectionInfo']['vim1'], + inst.vimConnectionInfo['vim1'].to_dict()) + + @mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'instantiate_process') + @mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'terminate_process') + @mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'modify_info_process') + @mock.patch.object(lcmocc_utils, 'update_lcmocc') + def test_process(self, mock_update_lcmocc, mock_modify, + mock_terminate, mock_process): + # instantiate + req_inst = objects.InstantiateVnfRequest.from_dict( + _inst_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='NOT_INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.INSTANTIATE + ) + grant = objects.GrantV1() + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.INSTANTIATE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req_inst) + self.driver.process( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + + # other operation + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED' + ) + inst_info = objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_example) + inst.instantiatedVnfInfo = inst_info + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.TERMINATE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=objects.TerminateVnfRequest( + terminationType='FORCEFUL')) + self.driver.process( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + + # no flavour_id + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED' + ) + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.MODIFY_INFO, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=objects.VnfInfoModificationRequest() + ) + self.driver.process( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + + @mock.patch.object(vnflcm_driver_v2.VnfLcmDriverV2, 'instantiate_rollback') + def test_rollback(self, mock_inst_rollback): + # instantiate + req_inst = objects.InstantiateVnfRequest.from_dict( + _inst_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='NOT_INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.INSTANTIATE + ) + grant = objects.GrantV1() + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.INSTANTIATE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req_inst) + self.driver.rollback( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + + # no rollback + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.TERMINATE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=objects.TerminateVnfRequest( + terminationType='FORCEFUL')) + self.assertRaises( + sol_ex.RollbackNotSupported, self.driver.rollback, + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + + def test_instantiate_grant_error(self): + # instantiate + req_inst = objects.InstantiateVnfRequest.from_dict( + _inst_req_example) + req_inst.flavourId = 'test' + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='NOT_INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.INSTANTIATE + ) + self.assertRaises( + sol_ex.FlavourIdNotFound, self.driver.instantiate_grant, + grant_req, req_inst, inst, self.vnfd_1) + + @mock.patch.object(vim_utils, 'get_vim') + def test_instantiate_post_grant(self, mock_vim): + # instantiate + req_inst = objects.InstantiateVnfRequest.from_dict( + _inst_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='NOT_INSTANTIATED' + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.INSTANTIATE + ) + vim_connection_info = { + "vim1": { + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", + "vimId": uuidutils.generate_uuid(), + "interfaceInfo": {"endpoint": "http://localhost/identity/v3"}, + "accessInfo": { + "username": "nfv_user", + "region": "RegionOne", + "password": "devstack", + "project": "nfv", + "projectDomain": "Default", + "userDomain": "Default" + } + }, + "vim2": { + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", + "vimId": uuidutils.generate_uuid() + } + } + grant = objects.GrantV1( + vimConnectionInfo={ + "vim1": objects.VimConnectionInfo.from_dict( + vim_connection_info['vim1']), + "vim2": objects.VimConnectionInfo.from_dict( + vim_connection_info['vim2']) + } + ) + mock_vim.return_value = objects.VimConnectionInfo.from_dict( + vim_connection_info['vim2']) + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.INSTANTIATE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req_inst) + self.driver.instantiate_post_grant( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + self.assertEqual(2, len(inst.vimConnectionInfo.keys())) + + @mock.patch.object(vim_utils, 'get_default_vim') + def test_instantiate_post_grant_default(self, mock_vim): + # instantiate + mock_vim.return_value = objects.VimConnectionInfo.from_dict( + _inst_req_example['vimConnectionInfo']['vim1']) + expected_result = _inst_req_example[ + 'vimConnectionInfo']['vim1']['vimId'] + new_inst_req = copy.deepcopy(_inst_req_example) + del new_inst_req['vimConnectionInfo'] + req_inst = objects.InstantiateVnfRequest.from_dict( + new_inst_req) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='NOT_INSTANTIATED' + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.INSTANTIATE + ) + grant = objects.GrantV1() + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.INSTANTIATE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req_inst) + self.driver.instantiate_post_grant( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + self.assertEqual( + expected_result, inst.vimConnectionInfo["default"]['vimId']) + + @mock.patch.object(vim_utils, 'get_default_vim') + def test_instantiate_post_grant_error(self, mock_vim): + # instantiate + mock_vim.return_value = None + new_inst_req = copy.deepcopy(_inst_req_example) + del new_inst_req['vimConnectionInfo'] + req_inst = objects.InstantiateVnfRequest.from_dict( + new_inst_req) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='NOT_INSTANTIATED' + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.INSTANTIATE + ) + grant = objects.GrantV1() + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.INSTANTIATE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req_inst) + self.assertRaises( + sol_ex.NoVimConnectionInfo, self.driver.instantiate_post_grant, + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + + @mock.patch.object(openstack.Openstack, 'instantiate') + @mock.patch.object(kubernetes.Kubernetes, 'instantiate') + def test_instantiate_process(self, mock_kubernetes, mock_openstack): + # openstack + req_inst = objects.InstantiateVnfRequest.from_dict( + _inst_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='NOT_INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.INSTANTIATE + ) + grant = objects.GrantV1() + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.INSTANTIATE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req_inst) + self.driver.instantiate_process( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + self.assertEqual('INSTANTIATED', inst.instantiationState) + + # kubernetes + req_inst = objects.InstantiateVnfRequest.from_dict( + _inst_cnf_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='NOT_INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo + ) + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.INSTANTIATE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req_inst) + self.driver.instantiate_process( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_2) + self.assertEqual('INSTANTIATED', inst.instantiationState) + + # error + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='NOT_INSTANTIATED', + vimConnectionInfo={'vim1': objects.VimConnectionInfo( + vimType='error')} + ) + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.INSTANTIATE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req_inst) + self.assertRaises( + sol_ex.SolException, self.driver.instantiate_process, + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + + @mock.patch.object(openstack.Openstack, 'instantiate_rollback') + def test_instantiate_rollback(self, mock_openstack): + # openstack + req_inst = objects.InstantiateVnfRequest.from_dict( + _inst_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='NOT_INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.INSTANTIATE + ) + grant = objects.GrantV1() + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.INSTANTIATE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req_inst) + self.driver.instantiate_rollback( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + self.assertEqual('NOT_INSTANTIATED', inst.instantiationState) + + # other type + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='NOT_INSTANTIATED', + vimConnectionInfo={'vim1': objects.VimConnectionInfo( + vimType='error')} + ) + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.INSTANTIATE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req_inst) + self.assertRaises( + sol_ex.SolException, self.driver.instantiate_process, + self.context, lcmocc, inst, grant_req, grant, self.vnfd_2) + + @mock.patch.object(openstack.Openstack, 'terminate') + @mock.patch.object(kubernetes.Kubernetes, 'terminate') + def test_terminate_process(self, mock_kubernetes, mock_openstack): + # openstack + req_inst = objects.InstantiateVnfRequest.from_dict(_inst_req_example) + req = objects.TerminateVnfRequest(terminationType='FORCEFUL') + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo, + instantiatedVnfInfo=( + objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_example)) + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.TERMINATE + ) + grant = objects.GrantV1() + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.TERMINATE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + self.driver.terminate_process( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + self.assertEqual('NOT_INSTANTIATED', inst.instantiationState) + self.assertEqual({}, inst.vimConnectionInfo) + + # kubernetes + req_inst = objects.InstantiateVnfRequest.from_dict( + _inst_cnf_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo, + instantiatedVnfInfo=( + objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_cnf_example)) + ) + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.TERMINATE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + self.driver.terminate_process( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_2) + self.assertEqual('NOT_INSTANTIATED', inst.instantiationState) + self.assertEqual({}, inst.vimConnectionInfo) + + # error + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo={'vim1': objects.VimConnectionInfo( + vimType='error')} + ) + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.TERMINATE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + self.assertRaises( + sol_ex.SolException, self.driver.terminate_process, + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + + def test_scale_grant(self): + req = objects.ScaleVnfRequest( + type='SCALE_OUT', aspectId='error', numberOfSteps=1) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + instantiatedVnfInfo=( + objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_example)) + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.SCALE + ) + self.assertRaises( + sol_ex.InvalidScaleAspectId, self.driver.scale_grant, + grant_req, req, inst, self.vnfd_1) + + # normal + req = objects.ScaleVnfRequest( + type='SCALE_OUT', aspectId='VDU1_scale', numberOfSteps=1, + additionalParams={"key": "value"}) + self.driver.scale_grant(grant_req, req, inst, self.vnfd_1) + self.assertEqual('value', grant_req.additionalParams['key']) + + @mock.patch.object(openstack.Openstack, 'scale') + def test_scale_process(self, mock_openstack): + # openstack + req_inst = objects.InstantiateVnfRequest.from_dict( + _inst_req_example) + req = objects.ScaleVnfRequest( + type='SCALE_OUT', aspectId='VDU1_scale', numberOfSteps=1) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo, + instantiatedVnfInfo=( + objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_example)) + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.SCALE + ) + grant = objects.GrantV1() + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.SCALE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + self.driver.scale_process( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + + # other type + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo={'vim1': objects.VimConnectionInfo( + vimType='error')} + ) + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.SCALE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + self.assertRaises( + sol_ex.SolException, self.driver.scale_process, + self.context, lcmocc, inst, grant_req, grant, self.vnfd_2) + + @mock.patch.object(openstack.Openstack, 'scale_rollback') + def test_scale_rollback(self, mock_openstack): + # openstack + req_inst = objects.InstantiateVnfRequest.from_dict( + _inst_req_example) + req = objects.ScaleVnfRequest( + type='SCALE_OUT', aspectId='VDU1_scale', numberOfSteps=1) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo, + instantiatedVnfInfo=( + objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_example)) + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.SCALE + ) + grant = objects.GrantV1() + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.SCALE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + self.driver.scale_rollback( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + + # 'SCALE_IN' + req = objects.ScaleVnfRequest( + type='SCALE_IN', aspectId='VDU1_scale', numberOfSteps=1) + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.SCALE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + self.assertRaises( + sol_ex.RollbackNotSupported, self.driver.scale_rollback, + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + + # other type + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo={'vim1': objects.VimConnectionInfo( + vimType='error')} + ) + req = objects.ScaleVnfRequest( + type='SCALE_OUT', aspectId='VDU1_scale', numberOfSteps=1) + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.SCALE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + self.assertRaises( + sol_ex.SolException, self.driver.scale_rollback, + self.context, lcmocc, inst, grant_req, grant, self.vnfd_2) + + @mock.patch.object(openstack.Openstack, 'heal') + def test_heal_process(self, mock_heal): + # openstack + req_inst = objects.InstantiateVnfRequest.from_dict( + _inst_req_example) + req = objects.HealVnfRequest() + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo, + instantiatedVnfInfo=( + objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_example)) + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.HEAL + ) + grant = objects.GrantV1() + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.HEAL, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + self.driver.heal_process( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + + # other type + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo={'vim1': objects.VimConnectionInfo( + vimType='error')} + ) + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.HEAL, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + self.assertRaises( + sol_ex.SolException, self.driver.heal_process, + self.context, lcmocc, inst, grant_req, grant, self.vnfd_2) + + @mock.patch.object(openstack.Openstack, 'change_ext_conn') + def test_change_ext_conn_process(self, mock_change_ext_conn): + # openstack + req_inst = objects.InstantiateVnfRequest.from_dict( + _inst_req_example) + req = objects.ChangeExtVnfConnectivityRequest.from_dict( + _change_ext_conn_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo, + instantiatedVnfInfo=( + objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_example)) + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.CHANGE_EXT_CONN + ) + grant = objects.GrantV1() + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.CHANGE_EXT_CONN, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + self.driver.change_ext_conn_process( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + + # other type + req_inst = objects.InstantiateVnfRequest.from_dict( + _inst_cnf_req_example) + req = objects.ChangeExtVnfConnectivityRequest( + vimConnectionInfo=req_inst.vimConnectionInfo) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo + ) + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.CHANGE_EXT_CONN, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + self.assertRaises( + sol_ex.SolException, self.driver.change_ext_conn_process, + self.context, lcmocc, inst, grant_req, grant, self.vnfd_2) + + @mock.patch.object(nfvo_client.NfvoClient, 'grant') + def test_cnf_change_vnfpkg_grant(self, mock_grant): + # prepare + req_inst = objects.InstantiateVnfRequest.from_dict( + _inst_cnf_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=CNF_SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo, + metadata={'lcm-kubernetes-def-files': [ + 'Files/kubernetes/deployment.yaml']} + ) + inst_info = objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_cnf_example) + inst.instantiatedVnfInfo = inst_info + + req = objects.ChangeCurrentVnfPkgRequest.from_dict( + _change_cnf_vnfpkg_example) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.CHANGE_VNFPKG + ) + self.driver.change_vnfpkg_grant(grant_req, req, inst, self.vnfd_3) + self.assertEqual(2, len(grant_req.addResources)) + self.assertEqual(2, len(grant_req.removeResources)) + + @mock.patch.object(openstack.Openstack, 'change_vnfpkg') + def test_change_vnfpkg_process(self, mock_change_vnfpkg): + # openstack + req_inst = objects.InstantiateVnfRequest.from_dict(_inst_req_example) + req = objects.ChangeCurrentVnfPkgRequest.from_dict( + _change_vnfpkg_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo, + instantiatedVnfInfo=( + objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_example)) + ) + grant_req = objects.GrantRequestV1( + dstVnfdId=SAMPLE_VNFD_ID, + operation=fields.LcmOperationType.CHANGE_VNFPKG + ) + grant = objects.GrantV1() + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.CHANGE_VNFPKG, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + self.driver.change_vnfpkg_process( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + + # error + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo={'vim1': objects.VimConnectionInfo( + vimType='error')} + ) + self.assertRaises( + sol_ex.SolException, self.driver.change_vnfpkg_process, + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + + @mock.patch.object(openstack.Openstack, 'change_ext_conn_rollback') + def test_change_ext_conn_rollback(self, mock_change_ext_conn_rollback): + # openstack + req_inst = objects.InstantiateVnfRequest.from_dict( + _inst_req_example) + req = objects.ChangeExtVnfConnectivityRequest() + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo, + instantiatedVnfInfo=( + objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_example)) + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.CHANGE_EXT_CONN + ) + grant = objects.GrantV1() + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.CHANGE_EXT_CONN, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + self.driver.change_ext_conn_rollback( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + + # other type + req_inst = objects.InstantiateVnfRequest.from_dict( + _inst_cnf_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo + ) + self.assertRaises( + sol_ex.SolException, self.driver.change_ext_conn_rollback, + self.context, lcmocc, inst, grant_req, grant, self.vnfd_2) + + @mock.patch.object(openstack.Openstack, 'change_vnfpkg_rollback') + @mock.patch.object(kubernetes.Kubernetes, 'change_vnfpkg_rollback') + def test_change_vnfpkg_rollback(self, mock_cnf_change_vnfpkg_rollback, + mock_change_vnfpkg_rollback): + # openstack + req_inst = objects.InstantiateVnfRequest.from_dict( + _inst_req_example) + req = objects.ChangeCurrentVnfPkgRequest() + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo, + instantiatedVnfInfo=( + objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_example)) + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.CHANGE_VNFPKG + ) + grant = objects.GrantV1() + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.STARTING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.CHANGE_VNFPKG, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + self.driver.change_vnfpkg_rollback( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) + + # kubernetes + req_inst = objects.InstantiateVnfRequest.from_dict( + _inst_cnf_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo + ) + self.driver.change_vnfpkg_rollback( + self.context, lcmocc, inst, grant_req, grant, self.vnfd_2) + + # other type + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo={'vim1': objects.VimConnectionInfo( + vimType='error')} + ) + self.assertRaises( + sol_ex.SolException, self.driver.change_vnfpkg_rollback, + self.context, lcmocc, inst, grant_req, grant, self.vnfd_1) diff --git a/tacker/tests/unit/sol_refactored/controller/test_vnflcm_v2.py b/tacker/tests/unit/sol_refactored/controller/test_vnflcm_v2.py index f4a3cae59..a17b1364d 100644 --- a/tacker/tests/unit/sol_refactored/controller/test_vnflcm_v2.py +++ b/tacker/tests/unit/sol_refactored/controller/test_vnflcm_v2.py @@ -1,673 +1,1245 @@ -# Copyright (C) 2021 Nippon Telegraph and Telephone Corporation -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from datetime import datetime -from unittest import mock - -from oslo_utils import uuidutils - -from tacker import context -from tacker.sol_refactored.api import api_version -from tacker.sol_refactored.common import exceptions as sol_ex -from tacker.sol_refactored.common import lcm_op_occ_utils as lcmocc_utils -from tacker.sol_refactored.controller import vnflcm_v2 -from tacker.sol_refactored.nfvo import nfvo_client -from tacker.sol_refactored import objects -from tacker.sol_refactored.objects.v2 import fields -from tacker.tests.unit.db import base as db_base - - -_change_ext_conn_req_example = { - "extVirtualLinks": [ - { - "id": "id_ext_vl_1", - "resourceId": "res_id_ext_vl_1", - "extCps": [ - { - "cpdId": "VDU2_CP2", - "cpConfig": { - "VDU2_CP2_1": { - "linkPortId": "link_port_id_VDU2_CP2" - } - } - } - ], - "extLinkPorts": [ - { - "id": "link_port_id_VDU2_CP2", - "resourceHandle": { - "resourceId": "res_id_VDU2_CP2" - } - } - ] - } - ] -} - - -class TestVnflcmV2(db_base.SqlTestCase): - - def setUp(self): - super(TestVnflcmV2, self).setUp() - objects.register_all() - self.controller = vnflcm_v2.VnfLcmControllerV2() - self.context = context.get_admin_context() - self.context.api_version = api_version.APIVersion("2.0.0") - self.request = mock.Mock() - self.request.context = self.context - - def _set_inst_and_lcmocc(self, inst_state, op_state): - inst = objects.VnfInstanceV2( - # required fields - id=uuidutils.generate_uuid(), - vnfdId=uuidutils.generate_uuid(), - vnfProvider='provider', - vnfProductName='product name', - vnfSoftwareVersion='software version', - vnfdVersion='vnfd version', - instantiationState=inst_state - ) - - req = {"flavourId": "simple"} # instantiate request - lcmocc = objects.VnfLcmOpOccV2( - # required fields - id=uuidutils.generate_uuid(), - operationState=op_state, - stateEnteredTime=datetime.utcnow(), - startTime=datetime.utcnow(), - vnfInstanceId=inst.id, - operation=fields.LcmOperationType.INSTANTIATE, - isAutomaticInvocation=False, - isCancelPending=False, - operationParams=req - ) - - return inst, lcmocc - - def _create_inst_and_lcmocc(self, inst_state, op_state): - inst, lcmocc = self._set_inst_and_lcmocc(inst_state, op_state) - - inst.create(self.context) - lcmocc.create(self.context) - - return inst.id, lcmocc.id - - @mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd') - def test_create_pkg_disabled(self, mocked_get_vnf_package_info_vnfd): - vnfd_id = uuidutils.generate_uuid() - pkg_info = objects.VnfPkgInfoV2( - id=uuidutils.generate_uuid(), - vnfdId=vnfd_id, - vnfProvider="provider", - vnfProductName="product", - vnfSoftwareVersion="software version", - vnfdVersion="vnfd version", - operationalState="DISABLED" - ) - mocked_get_vnf_package_info_vnfd.return_value = pkg_info - body = { - "vnfdId": vnfd_id, - "vnfInstanceName": "test", - "vnfInstanceDescription": "test" - } - self.assertRaises(sol_ex.VnfdIdNotEnabled, - self.controller.create, request=self.request, body=body) - - @mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd') - def test_change_vnfpkg_pkg_disabled(self, - mocked_get_vnf_package_info_vnfd): - vnfd_id = uuidutils.generate_uuid() - inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - body = {"vnfdId": vnfd_id} - pkg_info = objects.VnfPkgInfoV2( - id=uuidutils.generate_uuid(), - vnfdId=vnfd_id, - vnfProvider="provider", - vnfProductName="product", - vnfSoftwareVersion="software version", - vnfdVersion="vnfd version", - operationalState="DISABLED" - ) - mocked_get_vnf_package_info_vnfd.return_value = pkg_info - self.assertRaises(sol_ex.VnfdIdNotEnabled, - self.controller.change_vnfpkg, request=self.request, id=inst_id, - body=body) - - @mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd') - def test_change_vnfpkg_pkg_no_additional_params( - self, mocked_get_vnf_package_info_vnfd): - vnfd_id = uuidutils.generate_uuid() - inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - body = {"vnfdId": vnfd_id} - pkg_info = objects.VnfPkgInfoV2( - id=uuidutils.generate_uuid(), - vnfdId=vnfd_id, - vnfProvider="provider", - vnfProductName="product", - vnfSoftwareVersion="software version", - vnfdVersion="vnfd version", - operationalState="ENABLED" - ) - mocked_get_vnf_package_info_vnfd.return_value = pkg_info - self.assertRaises(sol_ex.SolValidationError, - self.controller.change_vnfpkg, request=self.request, id=inst_id, - body=body) - - @mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd') - def test_change_vnfpkg_pkg_upgrade_type( - self, mocked_get_vnf_package_info_vnfd): - vnfd_id = uuidutils.generate_uuid() - inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - body = { - "vnfdId": vnfd_id, - "additionalParams": { - "upgrade_type": "BuleGreen" - } - } - pkg_info = objects.VnfPkgInfoV2( - id=uuidutils.generate_uuid(), - vnfdId=vnfd_id, - vnfProvider="provider", - vnfProductName="product", - vnfSoftwareVersion="software version", - vnfdVersion="vnfd version", - operationalState="ENABLED" - ) - mocked_get_vnf_package_info_vnfd.return_value = pkg_info - self.assertRaises(sol_ex.NotSupportUpgradeType, - self.controller.change_vnfpkg, request=self.request, id=inst_id, - body=body) - - def test_delete_instantiated(self): - inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - - self.assertRaises(sol_ex.VnfInstanceIsInstantiated, - self.controller.delete, request=self.request, id=inst_id) - - def test_delete_lcmocc_in_progress(self): - inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', - fields.LcmOperationStateType.FAILED_TEMP) - - self.assertRaises(sol_ex.OtherOperationInProgress, - self.controller.delete, request=self.request, id=inst_id) - - def test_instantiate_instantiated(self): - inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - body = {"flavourId": "small"} - - self.assertRaises(sol_ex.VnfInstanceIsInstantiated, - self.controller.instantiate, request=self.request, id=inst_id, - body=body) - - def test_instantiate_lcmocc_in_progress(self): - inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', - fields.LcmOperationStateType.FAILED_TEMP) - body = {"flavourId": "small"} - - self.assertRaises(sol_ex.OtherOperationInProgress, - self.controller.instantiate, request=self.request, id=inst_id, - body=body) - - def test_change_vnfpkg_not_instantiated(self): - vnfd_id = uuidutils.generate_uuid() - inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - body = {"vnfdId": vnfd_id} - - self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated, - self.controller.change_vnfpkg, request=self.request, id=inst_id, - body=body) - - def test_change_vnfpkg_lcmocc_in_progress(self): - vnfd_id = uuidutils.generate_uuid() - inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.FAILED_TEMP) - body = {"vnfdId": vnfd_id} - - self.assertRaises(sol_ex.OtherOperationInProgress, - self.controller.change_vnfpkg, request=self.request, id=inst_id, - body=body) - - def test_terminate_not_instantiated(self): - inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - body = {"terminationType": "FORCEFUL"} - - self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated, - self.controller.terminate, request=self.request, id=inst_id, - body=body) - - def test_terminate_lcmocc_in_progress(self): - inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.FAILED_TEMP) - body = {"terminationType": "FORCEFUL"} - - self.assertRaises(sol_ex.OtherOperationInProgress, - self.controller.terminate, request=self.request, id=inst_id, - body=body) - - def test_invalid_subscripion(self): - body = { - "callbackUri": "http://127.0.0.1:6789/notification", - "authentication": { - "authType": ["BASIC"] - } - } - ex = self.assertRaises(sol_ex.InvalidSubscription, - self.controller.subscription_create, request=self.request, - body=body) - self.assertEqual("ParamsBasic must be specified.", ex.detail) - - body = { - "callbackUri": "http://127.0.0.1:6789/notification", - "authentication": { - "authType": ["OAUTH2_CLIENT_CREDENTIALS"] - } - } - ex = self.assertRaises(sol_ex.InvalidSubscription, - self.controller.subscription_create, request=self.request, - body=body) - self.assertEqual("paramsOauth2ClientCredentials must be specified.", - ex.detail) - - body = { - "callbackUri": "http://127.0.0.1:6789/notification", - "authentication": { - "authType": ["TLS_CERT"] - } - } - ex = self.assertRaises(sol_ex.InvalidSubscription, - self.controller.subscription_create, request=self.request, - body=body) - self.assertEqual("'TLS_CERT' is not supported at the moment.", - ex.detail) - - def test_retry_not_failed_temp(self): - _, lcmocc_id = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - - self.assertRaises(sol_ex.LcmOpOccNotFailedTemp, - self.controller.lcm_op_occ_retry, request=self.request, - id=lcmocc_id) - - def test_rollback_not_failed_temp(self): - _, lcmocc_id = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - - self.assertRaises(sol_ex.LcmOpOccNotFailedTemp, - self.controller.lcm_op_occ_rollback, request=self.request, - id=lcmocc_id) - - def test_fail_not_failed_temp(self): - _, lcmocc_id = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - - self.assertRaises(sol_ex.LcmOpOccNotFailedTemp, - self.controller.lcm_op_occ_fail, request=self.request, - id=lcmocc_id) - - def _prepare_db_for_fail(self): - inst, lcmocc = self._set_inst_and_lcmocc('NOT_INSTANTIATED', - fields.LcmOperationStateType.FAILED_TEMP) - - inst.create(self.context) - lcmocc.create(self.context) - - grant_req = objects.GrantRequestV1( - # required fields - vnfInstanceId=lcmocc.vnfInstanceId, - vnfLcmOpOccId=lcmocc.id, - vnfdId=uuidutils.generate_uuid(), - operation=lcmocc.operation, - isAutomaticInvocation=lcmocc.isAutomaticInvocation - ) - - grant = objects.GrantV1( - # required fields - id=uuidutils.generate_uuid(), - vnfInstanceId=lcmocc.vnfInstanceId, - vnfLcmOpOccId=lcmocc.id - ) - - grant_req.create(self.context) - grant.create(self.context) - lcmocc.grantId = grant.id - lcmocc.update(self.context) - - return lcmocc - - @mock.patch.object(nfvo_client.NfvoClient, 'send_lcmocc_notification') - def test_lcm_op_occ_fail(self, mocked_send_lcmocc_notification): - # prepare - lcmocc = self._prepare_db_for_fail() - - op_state = [] - - def _store_state(context, lcmocc, inst, endpoint): - op_state.append(lcmocc.operationState) - - mocked_send_lcmocc_notification.side_effect = _store_state - - # run lcm_op_occ_fail - self.controller.lcm_op_occ_fail(self.request, lcmocc.id) - - # check operationstate - self.assertEqual(1, mocked_send_lcmocc_notification.call_count) - self.assertEqual(fields.LcmOperationStateType.FAILED, op_state[0]) - - # check grant_req and grant are deleted - self.assertRaises(sol_ex.GrantRequestOrGrantNotFound, - lcmocc_utils.get_grant_req_and_grant, self.context, lcmocc) - - def test_scale_not_instantiated(self): - inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - body = {"aspectId": "aspect_1", "type": "SCALE_OUT"} - - self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated, - self.controller.scale, request=self.request, id=inst_id, - body=body) - - def test_scale_lcmocc_in_progress(self): - inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.FAILED_TEMP) - body = {"aspectId": "aspect_1", "type": "SCALE_OUT"} - - self.assertRaises(sol_ex.OtherOperationInProgress, - self.controller.scale, request=self.request, id=inst_id, - body=body) - - def _prepare_db_for_scale_param_check(self, scale_status, - max_scale_levels): - inst = objects.VnfInstanceV2( - # required fields - id=uuidutils.generate_uuid(), - vnfdId=uuidutils.generate_uuid(), - vnfProvider='provider', - vnfProductName='product name', - vnfSoftwareVersion='software version', - vnfdVersion='vnfd version', - instantiationState='INSTANTIATED' - ) - inst.instantiatedVnfInfo = objects.VnfInstanceV2_InstantiatedVnfInfo( - flavourId='small', - vnfState='STARTED', - scaleStatus=scale_status, - maxScaleLevels=max_scale_levels - ) - inst.create(self.context) - - return inst.id - - def test_scale_invalid_aspect_id(self): - scale_status = [ - objects.ScaleInfoV2( - aspectId="aspect_2", - scaleLevel=0 - ) - ] - max_scale_levels = [ - objects.ScaleInfoV2( - aspectId="aspect_2", - scaleLevel=3 - ) - ] - inst_id = self._prepare_db_for_scale_param_check(scale_status, - max_scale_levels) - body = {"aspectId": "aspect_1", "type": "SCALE_OUT"} - - self.assertRaises(sol_ex.InvalidScaleAspectId, - self.controller.scale, request=self.request, id=inst_id, - body=body) - - def test_scale_invalid_number_of_steps(self): - scale_status = [ - objects.ScaleInfoV2( - aspectId="aspect_1", - scaleLevel=1 - ) - ] - max_scale_levels = [ - objects.ScaleInfoV2( - aspectId="aspect_1", - scaleLevel=3 - ) - ] - inst_id = self._prepare_db_for_scale_param_check(scale_status, - max_scale_levels) - body = {"aspectId": "aspect_1", "type": "SCALE_OUT", - "numberOfSteps": 3} - - self.assertRaises(sol_ex.InvalidScaleNumberOfSteps, - self.controller.scale, request=self.request, id=inst_id, - body=body) - - body = {"aspectId": "aspect_1", "type": "SCALE_IN", - "numberOfSteps": 2} - - self.assertRaises(sol_ex.InvalidScaleNumberOfSteps, - self.controller.scale, request=self.request, id=inst_id, - body=body) - - def test_update_lcmocc_in_progress(self): - inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.FAILED_TEMP) - - body = {"vnfInstanceDescription": "example1"} - - self.assertRaises(sol_ex.OtherOperationInProgress, - self.controller.update, request=self.request, id=inst_id, - body=body) - - def test_update_vim_connection_info_not_instantiated(self): - inst = objects.VnfInstanceV2( - # required fields - id=uuidutils.generate_uuid(), - vnfdId=uuidutils.generate_uuid(), - vnfProvider='provider', - vnfProductName='product name', - vnfSoftwareVersion='software version', - vnfdVersion='vnfd version', - instantiationState='NOT_INSTANTIATED' - ) - inst.create(self.context) - - body = { - "vimConnectionInfo": { - "vim1": { - "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", - "vimId": "c36428ef-3071-4b74-8d9a-f39c4dd30065", - "interfaceInfo": { - "endpoint": "http://localhost/identity/v3" - } - } - } - } - self.assertRaises(sol_ex.SolValidationError, - self.controller.update, request=self.request, id=inst.id, - body=body) - - @mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd') - def test_update_vnf_package_disabled(self, - mocked_get_vnf_package_info_vnfd): - inst = objects.VnfInstanceV2( - # required fields - id=uuidutils.generate_uuid(), - vnfdId=uuidutils.generate_uuid(), - vnfProvider='provider', - vnfProductName='product name', - vnfSoftwareVersion='software version', - vnfdVersion='vnfd version', - instantiationState='INSTANTIATED' - ) - inst.create(self.context) - - req_vnfd_id = uuidutils.generate_uuid() - pkg_info = objects.VnfPkgInfoV2( - id=uuidutils.generate_uuid(), - vnfdId=req_vnfd_id, - vnfProvider="provider_1", - vnfProductName="product_1", - vnfSoftwareVersion="software version", - vnfdVersion="vnfd version", - operationalState="DISABLED" - ) - - mocked_get_vnf_package_info_vnfd.return_value = pkg_info - - body = {"vnfdId": req_vnfd_id} - - self.assertRaises(sol_ex.VnfdIdNotEnabled, - self.controller.update, request=self.request, id=inst.id, - body=body) - - def test_update_not_exist_vnfc_info_id(self): - inst = objects.VnfInstanceV2( - # required fields - id=uuidutils.generate_uuid(), - vnfdId=uuidutils.generate_uuid(), - vnfProvider='provider', - vnfProductName='product name', - vnfSoftwareVersion='software version', - vnfdVersion='vnfd version', - instantiationState='INSTANTIATED' - ) - inst.instantiatedVnfInfo = objects.VnfInstanceV2_InstantiatedVnfInfo( - flavourId='small', - vnfState='STARTED', - vnfcInfo=[ - objects.VnfcInfoV2( - id="VDU1-vnfc_res_info_id_VDU1", - vduId="VDU1", - vnfcResourceInfoId="vnfc_res_info_id_VDU1", - vnfcState="STARTED" - ) - ] - ) - inst.create(self.context) - - body = { - "vnfcInfoModifications": [ - { - "id": "VDU2-vnfc_res_info_id_VDU2", - "vnfcConfigurableProperties": {"key": "value"} - } - ] - } - - self.assertRaises(sol_ex.SolValidationError, - self.controller.update, request=self.request, id=inst.id, - body=body) - - def test_change_ext_conn_not_instantiated(self): - inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - - self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated, - self.controller.change_ext_conn, request=self.request, id=inst_id, - body=_change_ext_conn_req_example) - - def test_change_ext_conn_lcmocc_in_progress(self): - inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.FAILED_TEMP) - - self.assertRaises(sol_ex.OtherOperationInProgress, - self.controller.change_ext_conn, request=self.request, id=inst_id, - body=_change_ext_conn_req_example) - - def test_heal_not_instantiated(self): - inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', - fields.LcmOperationStateType.COMPLETED) - body = {"cause": "Healing VNF instance"} - - self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated, - self.controller.heal, request=self.request, id=inst_id, - body=body) - - def test_heal_lcmocc_in_progress(self): - inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', - fields.LcmOperationStateType.FAILED_TEMP) - body = {"cause": "Healing VNF instance"} - - self.assertRaises(sol_ex.OtherOperationInProgress, - self.controller.heal, request=self.request, id=inst_id, - body=body) - - def _prepare_db_for_heal_param_check(self): - inst = objects.VnfInstanceV2( - # required fields - id=uuidutils.generate_uuid(), - vnfdId=uuidutils.generate_uuid(), - vnfProvider='provider', - vnfProductName='product name', - vnfSoftwareVersion='software version', - vnfdVersion='vnfd version', - instantiationState='INSTANTIATED' - ) - inst.instantiatedVnfInfo = objects.VnfInstanceV2_InstantiatedVnfInfo( - flavourId='small', - vnfState='STARTED', - vnfcInfo=[ - objects.VnfcInfoV2( - id="VDU2-vnfc_res_info_id_VDU2", - vduId="VDU2", - vnfcResourceInfoId="vnfc_res_info_id_VDU2", - vnfcState="STARTED" - ), - objects.VnfcInfoV2( - id="VDU1-vnfc_res_info_id_VDU1", - vduId="VDU1", - vnfcResourceInfoId="vnfc_res_info_id_VDU1", - vnfcState="STARTED" - ), - objects.VnfcInfoV2( - id="VDU1-vnfc_res_info_id_VDU2", - vduId="VDU1", - vnfcResourceInfoId="vnfc_res_info_id_VDU2", - vnfcState="STARTED" - ), - ] - ) - inst.create(self.context) - - return inst.id - - def test_heal_invalid_additional_params(self): - inst_id = self._prepare_db_for_heal_param_check() - body = { - "additionalParams": {"all": "string"} - } - - self.assertRaises(sol_ex.SolValidationError, - self.controller.heal, request=self.request, id=inst_id, - body=body) - - def test_heal_invalid_vnfcinstance_id(self): - inst_id = self._prepare_db_for_heal_param_check() - body = { - "vnfcInstanceId": [ - "VDU2-vnfc_res_info_id_VDU2", - "VDU2-vnfc_res_info_id_VDU1" - ] - } - - self.assertRaises(sol_ex.SolValidationError, - self.controller.heal, request=self.request, id=inst_id, - body=body) +# Copyright (C) 2021 Nippon Telegraph and Telephone Corporation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from datetime import datetime +import requests +from unittest import mock + +from oslo_utils import uuidutils + +from tacker import context +from tacker.sol_refactored.api import api_version +from tacker.sol_refactored.common import config +from tacker.sol_refactored.common import exceptions as sol_ex +from tacker.sol_refactored.common import lcm_op_occ_utils as lcmocc_utils +from tacker.sol_refactored.common import subscription_utils as subsc_utils +from tacker.sol_refactored.common import vim_utils +from tacker.sol_refactored.common import vnf_instance_utils as inst_utils +from tacker.sol_refactored.common import vnfd_utils +from tacker.sol_refactored.conductor import conductor_rpc_v2 +from tacker.sol_refactored.controller import vnflcm_v2 +from tacker.sol_refactored.nfvo import nfvo_client +from tacker.sol_refactored import objects +from tacker.sol_refactored.objects.v2 import fields +from tacker.tests.unit.db import base as db_base + + +_change_ext_conn_req_example = { + "extVirtualLinks": [ + { + "id": "id_ext_vl_1", + "resourceId": "res_id_ext_vl_1", + "extCps": [ + { + "cpdId": "VDU2_CP2", + "cpConfig": { + "VDU2_CP2_1": { + "linkPortId": "link_port_id_VDU2_CP2" + } + } + } + ], + "extLinkPorts": [ + { + "id": "link_port_id_VDU2_CP2", + "resourceHandle": { + "resourceId": "res_id_VDU2_CP2" + } + } + ] + } + ] +} + +_vim_connection_info_example = { + "vimId": "vim_id_1", + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", + "interfaceInfo": {"endpoint": "http://127.0.0.1/identity"}, + "accessInfo": { + "username": "nfv_user", + "region": "RegionOne", + "password": "devstack", + "project": "nfv", + "projectDomain": "Default", + "userDomain": "Default" + } +} + +_inst_req_example = { + "flavourId": "simple", + "vimConnectionInfo": { + "vim1": _vim_connection_info_example + } +} + +_inst_cnf_req_example = { + "flavourId": "simple", + "additionalParams": { + "lcm-kubernetes-def-files": [ + "Files/kubernetes/deployment.yaml", + "Files/kubernetes/namespace.yaml", + "Files/kubernetes/pod.yaml", + ], + "namespace": "curry" + }, + "vimConnectionInfo": { + "vim1": { + "vimType": "kubernetes", + "vimId": uuidutils.generate_uuid(), + "interfaceInfo": {"endpoint": "https://127.0.0.1:6443"}, + "accessInfo": { + "bearer_token": "secret_token", + "region": "RegionOne" + } + } + } +} + +SAMPLE_VNFD_ID = "b1bb0ce7-ebca-4fa7-95ed-4840d7000000" + +CONF = config.CONF + + +class TestVnflcmV2(db_base.SqlTestCase): + + def setUp(self): + super(TestVnflcmV2, self).setUp() + objects.register_all() + self.controller = vnflcm_v2.VnfLcmControllerV2() + self.context = context.get_admin_context() + self.context.api_version = api_version.APIVersion("2.0.0") + self.request = mock.Mock() + self.request.context = self.context + self.vnfd_1 = vnfd_utils.Vnfd(SAMPLE_VNFD_ID) + + def _set_inst_and_lcmocc(self, inst_state, op_state): + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=uuidutils.generate_uuid(), + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState=inst_state + ) + + req = {"flavourId": "simple"} # instantiate request + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=op_state, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.INSTANTIATE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req + ) + + return inst, lcmocc + + def _create_inst_and_lcmocc(self, inst_state, op_state): + inst, lcmocc = self._set_inst_and_lcmocc(inst_state, op_state) + + inst.create(self.context) + lcmocc.create(self.context) + + return inst.id, lcmocc.id + + @mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd') + def test_create_pkg_disabled(self, mocked_get_vnf_package_info_vnfd): + vnfd_id = uuidutils.generate_uuid() + pkg_info = objects.VnfPkgInfoV2( + id=uuidutils.generate_uuid(), + vnfdId=vnfd_id, + vnfProvider="provider", + vnfProductName="product", + vnfSoftwareVersion="software version", + vnfdVersion="vnfd version", + operationalState="DISABLED" + ) + mocked_get_vnf_package_info_vnfd.return_value = pkg_info + body = { + "vnfdId": vnfd_id, + "vnfInstanceName": "test", + "vnfInstanceDescription": "test" + } + self.assertRaises(sol_ex.VnfdIdNotEnabled, + self.controller.create, request=self.request, body=body) + + @mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd') + @mock.patch.object(nfvo_client.NfvoClient, 'get_vnfd') + @mock.patch.object(nfvo_client.NfvoClient, 'send_inst_create_notification') + @mock.patch.object(vnfd_utils.Vnfd, 'get_vnfd_properties') + def test_create_201( + self, mock_prop, mock_send, mock_vnfd, + mocked_get_vnf_package_info_vnfd): + vnfd_id = uuidutils.generate_uuid() + pkg_info = objects.VnfPkgInfoV2( + id=uuidutils.generate_uuid(), + vnfdId=vnfd_id, + vnfProvider="provider", + vnfProductName="product", + vnfSoftwareVersion="software version", + vnfdVersion="vnfd version", + operationalState="ENABLED" + ) + mocked_get_vnf_package_info_vnfd.return_value = pkg_info + mock_vnfd.return_value = self.vnfd_1 + mock_prop.return_value = { + 'vnfConfigurableProperties': {"test": "test"}, + 'extensions': {"test": "test"}, + 'metadata': {} + } + body = { + "vnfdId": vnfd_id, + "vnfInstanceName": "test", + "vnfInstanceDescription": "test", + "metadata": {"key": "value"} + } + result = self.controller.create(request=self.request, body=body) + self.assertEqual(201, result.status) + + @mock.patch.object(inst_utils, 'get_inst_all') + def test_index(self, mock_inst): + request = requests.Request() + request.context = self.context + request.GET = {'filter': f'(eq,vnfdId,{SAMPLE_VNFD_ID})'} + mock_inst.return_value = [objects.VnfInstanceV2( + id='inst-1', vnfdId=SAMPLE_VNFD_ID, + instantiationState='NOT_INSTANTIATED')] + + result = self.controller.index(request) + self.assertEqual(200, result.status) + + # no filter + request.GET = {} + result = self.controller.index(request) + self.assertEqual(200, result.status) + + @mock.patch.object(inst_utils, 'get_inst') + def test_show(self, mock_inst): + request = requests.Request() + request.context = self.context + mock_inst.return_value = objects.VnfInstanceV2( + id='inst-1', vnfdId=SAMPLE_VNFD_ID, + instantiationState='NOT_INSTANTIATED') + result = self.controller.show(request, 'inst-1') + self.assertEqual(200, result.status) + + @mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd') + def test_change_vnfpkg_pkg_disabled(self, + mocked_get_vnf_package_info_vnfd): + vnfd_id = uuidutils.generate_uuid() + inst_id = self._prepare_db_for_change_vnfpkg_param() + body = {"vnfdId": vnfd_id} + pkg_info = objects.VnfPkgInfoV2( + id=uuidutils.generate_uuid(), + vnfdId=vnfd_id, + vnfProvider="provider", + vnfProductName="product", + vnfSoftwareVersion="software version", + vnfdVersion="vnfd version", + operationalState="DISABLED" + ) + mocked_get_vnf_package_info_vnfd.return_value = pkg_info + self.assertRaises(sol_ex.VnfdIdNotEnabled, + self.controller.change_vnfpkg, request=self.request, id=inst_id, + body=body) + + @mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd') + def test_change_vnfpkg_pkg_no_additional_params( + self, mocked_get_vnf_package_info_vnfd): + vnfd_id = uuidutils.generate_uuid() + inst_id = self._prepare_db_for_change_vnfpkg_param() + body = {"vnfdId": vnfd_id} + pkg_info = objects.VnfPkgInfoV2( + id=uuidutils.generate_uuid(), + vnfdId=vnfd_id, + vnfProvider="provider", + vnfProductName="product", + vnfSoftwareVersion="software version", + vnfdVersion="vnfd version", + operationalState="ENABLED" + ) + mocked_get_vnf_package_info_vnfd.return_value = pkg_info + self.assertRaises(sol_ex.SolValidationError, + self.controller.change_vnfpkg, request=self.request, id=inst_id, + body=body) + + @mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd') + def test_change_vnfpkg_pkg_upgrade_type( + self, mocked_get_vnf_package_info_vnfd): + vnfd_id = uuidutils.generate_uuid() + inst_id = self._prepare_db_for_change_vnfpkg_param() + body = { + "vnfdId": vnfd_id, + "additionalParams": { + "upgrade_type": "BuleGreen" + } + } + pkg_info = objects.VnfPkgInfoV2( + id=uuidutils.generate_uuid(), + vnfdId=vnfd_id, + vnfProvider="provider", + vnfProductName="product", + vnfSoftwareVersion="software version", + vnfdVersion="vnfd version", + operationalState="ENABLED" + ) + mocked_get_vnf_package_info_vnfd.return_value = pkg_info + self.assertRaises(sol_ex.NotSupportUpgradeType, + self.controller.change_vnfpkg, request=self.request, id=inst_id, + body=body) + + @mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd') + def test_change_vnfpkg_pkg_no_vduId( + self, mocked_get_vnf_package_info_vnfd): + vnfd_id = uuidutils.generate_uuid() + inst_id = self._prepare_db_for_change_vnfpkg_param() + body = { + "vnfdId": vnfd_id, + "additionalParams": { + "upgrade_type": "RollingUpdate", + "vdu_params": [{}] + } + } + pkg_info = objects.VnfPkgInfoV2( + id=uuidutils.generate_uuid(), + vnfdId=vnfd_id, + vnfProvider="provider", + vnfProductName="product", + vnfSoftwareVersion="software version", + vnfdVersion="vnfd version", + operationalState="ENABLED" + ) + mocked_get_vnf_package_info_vnfd.return_value = pkg_info + self.assertRaises(sol_ex.SolValidationError, + self.controller.change_vnfpkg, request=self.request, + id=inst_id, + body=body) + + @mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd') + def test_change_vnfpkg_pkg_no_username( + self, mocked_get_vnf_package_info_vnfd): + vnfd_id = uuidutils.generate_uuid() + inst_id = self._prepare_db_for_change_vnfpkg_param() + body = { + "vnfdId": vnfd_id, + "additionalParams": { + "upgrade_type": "RollingUpdate", + "lcm-operation-coordinate-new-vnf": "test", + "vdu_params": [{ + "vdu_id": "VDU1", + "new_vnfc_param": { + "passoword": "test", + "cp_name": "VDU1_CP1" + } + }] + } + } + pkg_info = objects.VnfPkgInfoV2( + id=uuidutils.generate_uuid(), + vnfdId=vnfd_id, + vnfProvider="provider", + vnfProductName="product", + vnfSoftwareVersion="software version", + vnfdVersion="vnfd version", + operationalState="ENABLED" + ) + mocked_get_vnf_package_info_vnfd.return_value = pkg_info + self.assertRaises(sol_ex.SolValidationError, + self.controller.change_vnfpkg, request=self.request, + id=inst_id, + body=body) + + @mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd') + @mock.patch.object(conductor_rpc_v2.VnfLcmRpcApiV2, 'start_lcm_op') + def test_change_vnfpkg_pkg_202( + self, mock_start, mocked_get_vnf_package_info_vnfd): + vnfd_id = uuidutils.generate_uuid() + inst_id = self._prepare_db_for_change_vnfpkg_param() + body = { + "vnfdId": vnfd_id, + "additionalParams": { + "upgrade_type": "RollingUpdate", + "vdu_params": [{ + "vdu_id": "VDU1", + "new_vnfc_param": { + "username": "test", + "password": "test", + "cp_name": "VDU1_CP1" + }, + "old_vnfc_param": { + "username": "test", + "password": "test", + "cp_name": "VDU1_CP1" + } + }] + } + } + pkg_info = objects.VnfPkgInfoV2( + id=uuidutils.generate_uuid(), + vnfdId=vnfd_id, + vnfProvider="provider", + vnfProductName="product", + vnfSoftwareVersion="software version", + vnfdVersion="vnfd version", + operationalState="ENABLED" + ) + mocked_get_vnf_package_info_vnfd.return_value = pkg_info + result = self.controller.change_vnfpkg( + request=self.request, id=inst_id, body=body) + self.assertEqual(202, result.status) + + def _prepare_db_for_change_vnfpkg_param(self): + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=uuidutils.generate_uuid(), + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED' + ) + inst.instantiatedVnfInfo = objects.VnfInstanceV2_InstantiatedVnfInfo( + flavourId='small', + vnfState='STARTED', + vnfcResourceInfo=[ + objects.VnfcResourceInfoV2( + id="VDU1-vnfc_res_info_id_VDU1", + vduId="VDU1" + ) + ] + ) + inst.vimConnectionInfo = { + "vim1": objects.VimConnectionInfo.from_dict( + _vim_connection_info_example)} + inst.create(self.context) + return inst.id + + def test_delete_instantiated(self): + inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + + self.assertRaises(sol_ex.VnfInstanceIsInstantiated, + self.controller.delete, request=self.request, id=inst_id) + + def test_delete_lcmocc_in_progress(self): + inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', + fields.LcmOperationStateType.FAILED_TEMP) + + self.assertRaises(sol_ex.OtherOperationInProgress, + self.controller.delete, request=self.request, id=inst_id) + + @mock.patch.object(nfvo_client.NfvoClient, 'send_inst_delete_notification') + def test_delete_204(self, mock_delete): + inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + result = self.controller.delete(self.request, id=inst_id) + self.assertEqual(204, result.status) + + def test_instantiate_instantiated(self): + inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + body = {"flavourId": "small"} + + self.assertRaises(sol_ex.VnfInstanceIsInstantiated, + self.controller.instantiate, request=self.request, id=inst_id, + body=body) + + def test_instantiate_lcmocc_in_progress(self): + inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', + fields.LcmOperationStateType.FAILED_TEMP) + body = {"flavourId": "small"} + + self.assertRaises(sol_ex.OtherOperationInProgress, + self.controller.instantiate, request=self.request, id=inst_id, + body=body) + + @mock.patch.object(vim_utils, 'get_vim') + @mock.patch.object(conductor_rpc_v2.VnfLcmRpcApiV2, 'start_lcm_op') + def test_instantiate_202(self, mock_start, mock_vim): + inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + body = { + "flavourId": "small", + "vimConnectionInfo": { + "vim1": { + "vimId": "vim_id_1", + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3" + } + } + } + mock_vim.return_value = objects.VimConnectionInfo.from_dict( + _vim_connection_info_example) + result = self.controller.instantiate( + request=self.request, id=inst_id, body=body) + self.assertEqual(202, result.status) + + def test_change_vnfpkg_not_instantiated(self): + vnfd_id = uuidutils.generate_uuid() + inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + body = {"vnfdId": vnfd_id} + + self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated, + self.controller.change_vnfpkg, request=self.request, id=inst_id, + body=body) + + def test_change_vnfpkg_lcmocc_in_progress(self): + vnfd_id = uuidutils.generate_uuid() + inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.FAILED_TEMP) + body = {"vnfdId": vnfd_id} + + self.assertRaises(sol_ex.OtherOperationInProgress, + self.controller.change_vnfpkg, request=self.request, id=inst_id, + body=body) + + def test_terminate_not_instantiated(self): + inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + body = {"terminationType": "FORCEFUL"} + + self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated, + self.controller.terminate, request=self.request, id=inst_id, + body=body) + + def test_terminate_lcmocc_in_progress(self): + inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.FAILED_TEMP) + body = {"terminationType": "FORCEFUL"} + + self.assertRaises(sol_ex.OtherOperationInProgress, + self.controller.terminate, request=self.request, id=inst_id, + body=body) + + @mock.patch.object(conductor_rpc_v2.VnfLcmRpcApiV2, 'start_lcm_op') + def test_terminate_202(self, mock_start): + inst_id, _ = self._create_inst_and_lcmocc( + 'INSTANTIATED', fields.LcmOperationStateType.COMPLETED) + body = {"terminationType": "FORCEFUL"} + + result = self.controller.terminate(request=self.request, id=inst_id, + body=body) + self.assertEqual(202, result.status) + + def test_invalid_subscripion(self): + body = { + "callbackUri": "http://127.0.0.1:6789/notification", + "authentication": { + "authType": ["BASIC"] + } + } + ex = self.assertRaises(sol_ex.InvalidSubscription, + self.controller.subscription_create, request=self.request, + body=body) + self.assertEqual("ParamsBasic must be specified.", ex.detail) + + body = { + "callbackUri": "http://127.0.0.1:6789/notification", + "authentication": { + "authType": ["OAUTH2_CLIENT_CREDENTIALS"] + } + } + ex = self.assertRaises(sol_ex.InvalidSubscription, + self.controller.subscription_create, request=self.request, + body=body) + self.assertEqual("paramsOauth2ClientCredentials must be specified.", + ex.detail) + + body = { + "callbackUri": "http://127.0.0.1:6789/notification", + "authentication": { + "authType": ["TLS_CERT"] + } + } + ex = self.assertRaises(sol_ex.InvalidSubscription, + self.controller.subscription_create, request=self.request, + body=body) + self.assertEqual("'TLS_CERT' is not supported at the moment.", + ex.detail) + + @mock.patch.object(subsc_utils, 'test_notification') + def test_subscription_create_201(self, mock_test): + body = { + "callbackUri": "http://127.0.0.1:6789/notification", + "authentication": { + "authType": ["BASIC", "OAUTH2_CLIENT_CREDENTIALS"], + "paramsBasic": { + "userName": "test", + "password": "test" + }, + "paramsOauth2ClientCredentials": { + "clientId": "test", + "clientPassword": "test", + "tokenEndpoint": "https://127.0.0.1/token" + } + }, + "filter": { + "operationTypes": [fields.LcmOperationType.INSTANTIATE] + } + } + result = self.controller.subscription_create( + request=self.request, body=body) + self.assertEqual(201, result.status) + + @mock.patch.object(subsc_utils, 'get_subsc_all') + def test_subscription_list(self, mock_subsc): + request = requests.Request() + request.context = self.context + request.GET = { + 'filter': '(eq,callbackUri,http://127.0.0.1:6789/notification)'} + lccn_subsc = { + "id": "subsc-1", + "callbackUri": "http://127.0.0.1:6789/notification" + } + mock_subsc.return_value = [ + objects.LccnSubscriptionV2.from_dict(lccn_subsc)] + + result = self.controller.subscription_list(request) + self.assertEqual(200, result.status) + + # no filter + request.GET = {} + result = self.controller.subscription_list(request) + self.assertEqual(200, result.status) + + @mock.patch.object(subsc_utils, 'get_subsc') + def test_subscription_show(self, mock_subsc): + mock_subsc.return_value = objects.LccnSubscriptionV2(id='subsc-1') + result = self.controller.subscription_show( + request=self.request, id='subsc-1') + self.assertEqual(200, result.status) + + @mock.patch.object(subsc_utils, 'get_subsc') + def test_subscription_delete(self, mock_subsc): + mock_subsc.return_value = objects.LccnSubscriptionV2(id='subsc-1') + result = self.controller.subscription_delete( + request=self.request, id='subsc-1') + self.assertEqual(204, result.status) + + @mock.patch.object(lcmocc_utils, 'get_lcmocc_all') + def test_lcm_op_occ_list(self, mock_lcmocc): + request = requests.Request() + request.context = self.context + request.GET = { + 'filter': f'(eq,operation,' + f'{fields.LcmOperationType.INSTANTIATE})'} + mock_lcmocc.return_value = [objects.VnfLcmOpOccV2( + id='lcmocc-1', operation='INSTANTIATE', vnfInstanceId='inst-1')] + + result = self.controller.lcm_op_occ_list(request) + self.assertEqual(200, result.status) + + # no filter + request.GET = {} + result = self.controller.lcm_op_occ_list(request) + self.assertEqual(200, result.status) + + @mock.patch.object(lcmocc_utils, 'get_lcmocc') + def test_lcm_op_occ_show(self, mock_lcmocc): + mock_lcmocc.return_value = objects.VnfLcmOpOccV2( + id='lcmocc-1', operation='INSTANTIATE', vnfInstanceId='inst-1') + result = self.controller.lcm_op_occ_show( + request=self.request, id='lcmocc-1') + self.assertEqual(200, result.status) + + def test_retry_not_failed_temp(self): + _, lcmocc_id = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + + self.assertRaises(sol_ex.LcmOpOccNotFailedTemp, + self.controller.lcm_op_occ_retry, request=self.request, + id=lcmocc_id) + + @mock.patch.object(lcmocc_utils, 'get_lcmocc') + @mock.patch.object(vim_utils, 'get_default_vim') + @mock.patch.object(conductor_rpc_v2.VnfLcmRpcApiV2, 'retry_lcm_op') + @mock.patch.object(inst_utils, 'get_inst') + def test_retry_202(self, mock_inst, mock_retry, mock_vim, mock_lcmocc): + # instantiate with vimConnectionInfo + req = objects.InstantiateVnfRequest.from_dict( + _inst_req_example) + mock_lcmocc.return_value = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.FAILED_TEMP, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId='inst-1', + operation=fields.LcmOperationType.INSTANTIATE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + result = self.controller.lcm_op_occ_retry( + request=self.request, id=mock_lcmocc.return_value.id) + self.assertEqual(202, result.status) + + # instantiate with no vimConnectionInfo + del mock_lcmocc.return_value.operationParams.vimConnectionInfo + mock_vim.return_value = objects.VimConnectionInfo.from_dict( + _vim_connection_info_example) + result = self.controller.lcm_op_occ_retry( + request=self.request, id=mock_lcmocc.return_value.id) + self.assertEqual(202, result.status) + + # other operation + mock_lcmocc.return_value = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.FAILED_TEMP, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId='inst-1', + operation=fields.LcmOperationType.HEAL, + isAutomaticInvocation=False, + isCancelPending=False) + mock_inst.return_value = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo={"vim1": mock_vim.return_value} + ) + result = self.controller.lcm_op_occ_retry( + request=self.request, id=mock_lcmocc.return_value.id) + self.assertEqual(202, result.status) + + def test_rollback_not_failed_temp(self): + _, lcmocc_id = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + + self.assertRaises(sol_ex.LcmOpOccNotFailedTemp, + self.controller.lcm_op_occ_rollback, request=self.request, + id=lcmocc_id) + + @mock.patch.object(lcmocc_utils, 'get_lcmocc') + @mock.patch.object(vim_utils, 'get_default_vim') + @mock.patch.object(conductor_rpc_v2.VnfLcmRpcApiV2, 'retry_lcm_op') + @mock.patch.object(inst_utils, 'get_inst') + def test_rollback_202(self, mock_inst, mock_retry, mock_vim, mock_lcmocc): + # instantiate with vimConnectionInfo + req = objects.InstantiateVnfRequest.from_dict( + _inst_req_example) + mock_lcmocc.return_value = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.FAILED_TEMP, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId='inst-1', + operation=fields.LcmOperationType.INSTANTIATE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + result = self.controller.lcm_op_occ_rollback( + request=self.request, id=mock_lcmocc.return_value.id) + self.assertEqual(202, result.status) + + # instantiate with no vimConnectionInfo + del mock_lcmocc.return_value.operationParams.vimConnectionInfo + mock_vim.return_value = objects.VimConnectionInfo.from_dict( + _vim_connection_info_example) + result = self.controller.lcm_op_occ_rollback( + request=self.request, id=mock_lcmocc.return_value.id) + self.assertEqual(202, result.status) + + # other operation + mock_lcmocc.return_value = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.FAILED_TEMP, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId='inst-1', + operation=fields.LcmOperationType.HEAL, + isAutomaticInvocation=False, + isCancelPending=False) + mock_inst.return_value = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo={"vim1": mock_vim.return_value} + ) + result = self.controller.lcm_op_occ_rollback( + request=self.request, id=mock_lcmocc.return_value.id) + self.assertEqual(202, result.status) + + def test_fail_not_failed_temp(self): + _, lcmocc_id = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + + self.assertRaises(sol_ex.LcmOpOccNotFailedTemp, + self.controller.lcm_op_occ_fail, request=self.request, + id=lcmocc_id) + + def test_lcm_op_occ_delete(self): + _, lcmocc_id = self._create_inst_and_lcmocc( + 'INSTANTIATED', fields.LcmOperationStateType.FAILED_TEMP) + # True + CONF.v2_vnfm.test_enable_lcm_op_occ_delete = True + result = self.controller.lcm_op_occ_delete( + request=self.request, id=lcmocc_id) + self.assertEqual(204, result.status) + + # False + CONF.v2_vnfm.test_enable_lcm_op_occ_delete = False + self.assertRaises( + sol_ex.MethodNotAllowed, self.controller.lcm_op_occ_delete, + request=self.request, + id=lcmocc_id) + + def _prepare_db_for_fail(self): + inst, lcmocc = self._set_inst_and_lcmocc('NOT_INSTANTIATED', + fields.LcmOperationStateType.FAILED_TEMP) + + inst.create(self.context) + lcmocc.create(self.context) + + grant_req = objects.GrantRequestV1( + # required fields + vnfInstanceId=lcmocc.vnfInstanceId, + vnfLcmOpOccId=lcmocc.id, + vnfdId=uuidutils.generate_uuid(), + operation=lcmocc.operation, + isAutomaticInvocation=lcmocc.isAutomaticInvocation + ) + + grant = objects.GrantV1( + # required fields + id=uuidutils.generate_uuid(), + vnfInstanceId=lcmocc.vnfInstanceId, + vnfLcmOpOccId=lcmocc.id + ) + + grant_req.create(self.context) + grant.create(self.context) + lcmocc.grantId = grant.id + lcmocc.update(self.context) + + return lcmocc + + @mock.patch.object(nfvo_client.NfvoClient, 'send_lcmocc_notification') + def test_lcm_op_occ_fail(self, mocked_send_lcmocc_notification): + # prepare + lcmocc = self._prepare_db_for_fail() + + op_state = [] + + def _store_state(context, lcmocc, inst, endpoint): + op_state.append(lcmocc.operationState) + + mocked_send_lcmocc_notification.side_effect = _store_state + + # run lcm_op_occ_fail + self.controller.lcm_op_occ_fail(self.request, lcmocc.id) + + # check operationstate + self.assertEqual(1, mocked_send_lcmocc_notification.call_count) + self.assertEqual(fields.LcmOperationStateType.FAILED, op_state[0]) + + # check grant_req and grant are deleted + self.assertRaises(sol_ex.GrantRequestOrGrantNotFound, + lcmocc_utils.get_grant_req_and_grant, self.context, lcmocc) + + def test_scale_not_instantiated(self): + inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + body = {"aspectId": "aspect_1", "type": "SCALE_OUT"} + + self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated, + self.controller.scale, request=self.request, id=inst_id, + body=body) + + def test_scale_lcmocc_in_progress(self): + inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.FAILED_TEMP) + body = {"aspectId": "aspect_1", "type": "SCALE_OUT"} + + self.assertRaises(sol_ex.OtherOperationInProgress, + self.controller.scale, request=self.request, id=inst_id, + body=body) + + def _prepare_db_for_scale_param_check(self, scale_status, + max_scale_levels): + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=uuidutils.generate_uuid(), + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED' + ) + inst.instantiatedVnfInfo = objects.VnfInstanceV2_InstantiatedVnfInfo( + flavourId='small', + vnfState='STARTED', + scaleStatus=scale_status, + maxScaleLevels=max_scale_levels + ) + inst.create(self.context) + + return inst.id + + def test_scale_invalid_aspect_id(self): + scale_status = [ + objects.ScaleInfoV2( + aspectId="aspect_2", + scaleLevel=0 + ) + ] + max_scale_levels = [ + objects.ScaleInfoV2( + aspectId="aspect_2", + scaleLevel=3 + ) + ] + inst_id = self._prepare_db_for_scale_param_check(scale_status, + max_scale_levels) + body = {"aspectId": "aspect_1", "type": "SCALE_OUT"} + + self.assertRaises(sol_ex.InvalidScaleAspectId, + self.controller.scale, request=self.request, id=inst_id, + body=body) + + def test_scale_invalid_number_of_steps(self): + scale_status = [ + objects.ScaleInfoV2( + aspectId="aspect_1", + scaleLevel=1 + ) + ] + max_scale_levels = [ + objects.ScaleInfoV2( + aspectId="aspect_1", + scaleLevel=3 + ) + ] + inst_id = self._prepare_db_for_scale_param_check(scale_status, + max_scale_levels) + body = {"aspectId": "aspect_1", "type": "SCALE_OUT", + "numberOfSteps": 3} + + self.assertRaises(sol_ex.InvalidScaleNumberOfSteps, + self.controller.scale, request=self.request, id=inst_id, + body=body) + + body = {"aspectId": "aspect_1", "type": "SCALE_IN", + "numberOfSteps": 2} + + self.assertRaises(sol_ex.InvalidScaleNumberOfSteps, + self.controller.scale, request=self.request, id=inst_id, + body=body) + + @mock.patch.object(conductor_rpc_v2.VnfLcmRpcApiV2, 'start_lcm_op') + def test_scale_202(self, mock_start): + scale_status = [ + objects.ScaleInfoV2( + aspectId="aspect_1", + scaleLevel=1 + ) + ] + max_scale_levels = [ + objects.ScaleInfoV2( + aspectId="aspect_1", + scaleLevel=3 + ) + ] + inst_id = self._prepare_db_for_scale_param_check(scale_status, + max_scale_levels) + body = {"aspectId": "aspect_1", "type": "SCALE_OUT", + "numberOfSteps": 1} + + result = self.controller.scale(request=self.request, id=inst_id, + body=body) + self.assertEqual(202, result.status) + + def test_update_lcmocc_in_progress(self): + inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.FAILED_TEMP) + + body = {"vnfInstanceDescription": "example1"} + + self.assertRaises(sol_ex.OtherOperationInProgress, + self.controller.update, request=self.request, id=inst_id, + body=body) + + def test_update_vim_connection_info_not_instantiated(self): + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=uuidutils.generate_uuid(), + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='NOT_INSTANTIATED' + ) + inst.create(self.context) + + body = { + "vimConnectionInfo": { + "vim1": { + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", + "vimId": "c36428ef-3071-4b74-8d9a-f39c4dd30065", + "interfaceInfo": { + "endpoint": "http://localhost/identity/v3" + } + } + } + } + self.assertRaises(sol_ex.SolValidationError, + self.controller.update, request=self.request, id=inst.id, + body=body) + + @mock.patch.object(nfvo_client.NfvoClient, 'get_vnf_package_info_vnfd') + def test_update_vnf_package_disabled(self, + mocked_get_vnf_package_info_vnfd): + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=uuidutils.generate_uuid(), + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED' + ) + inst.create(self.context) + + req_vnfd_id = uuidutils.generate_uuid() + pkg_info = objects.VnfPkgInfoV2( + id=uuidutils.generate_uuid(), + vnfdId=req_vnfd_id, + vnfProvider="provider_1", + vnfProductName="product_1", + vnfSoftwareVersion="software version", + vnfdVersion="vnfd version", + operationalState="DISABLED" + ) + + mocked_get_vnf_package_info_vnfd.return_value = pkg_info + + body = {"vnfdId": req_vnfd_id} + + self.assertRaises(sol_ex.VnfdIdNotEnabled, + self.controller.update, request=self.request, id=inst.id, + body=body) + + def test_update_not_exist_vnfc_info_id(self): + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=uuidutils.generate_uuid(), + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED' + ) + inst.instantiatedVnfInfo = objects.VnfInstanceV2_InstantiatedVnfInfo( + flavourId='small', + vnfState='STARTED', + vnfcInfo=[ + objects.VnfcInfoV2( + id="VDU1-vnfc_res_info_id_VDU1", + vduId="VDU1", + vnfcResourceInfoId="vnfc_res_info_id_VDU1", + vnfcState="STARTED" + ) + ] + ) + inst.create(self.context) + + body = { + "vnfcInfoModifications": [ + { + "id": "VDU2-vnfc_res_info_id_VDU2", + "vnfcConfigurableProperties": {"key": "value"} + } + ] + } + + self.assertRaises(sol_ex.SolValidationError, + self.controller.update, request=self.request, id=inst.id, + body=body) + + @mock.patch.object(conductor_rpc_v2.VnfLcmRpcApiV2, 'modify_vnfinfo') + def test_update_202(self, mock_modify): + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=uuidutils.generate_uuid(), + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED' + ) + inst.instantiatedVnfInfo = objects.VnfInstanceV2_InstantiatedVnfInfo( + flavourId='small', + vnfState='STARTED', + vnfcInfo=[ + objects.VnfcInfoV2( + id="VDU1-vnfc_res_info_id_VDU1", + vduId="VDU1", + vnfcResourceInfoId="vnfc_res_info_id_VDU1", + vnfcState="STARTED" + ) + ] + ) + inst.create(self.context) + + body = { + "vnfcInfoModifications": [ + { + "id": "VDU1-vnfc_res_info_id_VDU1", + "vnfcConfigurableProperties": {"key": "value"} + } + ] + } + result = self.controller.update( + request=self.request, id=inst.id, body=body) + self.assertEqual(202, result.status) + + def test_change_ext_conn_not_instantiated(self): + inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + + self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated, + self.controller.change_ext_conn, request=self.request, id=inst_id, + body=_change_ext_conn_req_example) + + def test_change_ext_conn_lcmocc_in_progress(self): + inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.FAILED_TEMP) + + self.assertRaises(sol_ex.OtherOperationInProgress, + self.controller.change_ext_conn, request=self.request, id=inst_id, + body=_change_ext_conn_req_example) + + @mock.patch.object(conductor_rpc_v2.VnfLcmRpcApiV2, 'start_lcm_op') + def test_change_ext_conn_202(self, mock_start): + inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + + result = self.controller.change_ext_conn( + request=self.request, id=inst_id, + body=_change_ext_conn_req_example) + self.assertEqual(202, result.status) + + def test_heal_not_instantiated(self): + inst_id, _ = self._create_inst_and_lcmocc('NOT_INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + body = {"cause": "Healing VNF instance"} + + self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated, + self.controller.heal, request=self.request, id=inst_id, + body=body) + + def test_heal_lcmocc_in_progress(self): + inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.FAILED_TEMP) + body = {"cause": "Healing VNF instance"} + + self.assertRaises(sol_ex.OtherOperationInProgress, + self.controller.heal, request=self.request, id=inst_id, + body=body) + + @mock.patch.object(conductor_rpc_v2.VnfLcmRpcApiV2, 'start_lcm_op') + def test_heal_202(self, mock_start): + inst_id, _ = self._create_inst_and_lcmocc('INSTANTIATED', + fields.LcmOperationStateType.COMPLETED) + body = {"cause": "Healing VNF instance"} + + result = self.controller.heal(request=self.request, id=inst_id, + body=body) + self.assertEqual(202, result.status) + + def _prepare_db_for_heal_param_check(self): + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=uuidutils.generate_uuid(), + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED' + ) + inst.instantiatedVnfInfo = objects.VnfInstanceV2_InstantiatedVnfInfo( + flavourId='small', + vnfState='STARTED', + vnfcInfo=[ + objects.VnfcInfoV2( + id="VDU2-vnfc_res_info_id_VDU2", + vduId="VDU2", + vnfcResourceInfoId="vnfc_res_info_id_VDU2", + vnfcState="STARTED" + ), + objects.VnfcInfoV2( + id="VDU1-vnfc_res_info_id_VDU1", + vduId="VDU1", + vnfcResourceInfoId="vnfc_res_info_id_VDU1", + vnfcState="STARTED" + ), + objects.VnfcInfoV2( + id="VDU1-vnfc_res_info_id_VDU2", + vduId="VDU1", + vnfcResourceInfoId="vnfc_res_info_id_VDU2", + vnfcState="STARTED" + ), + ] + ) + inst.create(self.context) + + return inst.id + + def test_heal_invalid_additional_params(self): + inst_id = self._prepare_db_for_heal_param_check() + body = { + "additionalParams": {"all": "string"} + } + + self.assertRaises(sol_ex.SolValidationError, + self.controller.heal, request=self.request, id=inst_id, + body=body) + + def test_heal_invalid_vnfcinstance_id(self): + inst_id = self._prepare_db_for_heal_param_check() + body = { + "vnfcInstanceId": [ + "VDU2-vnfc_res_info_id_VDU2", + "VDU2-vnfc_res_info_id_VDU1" + ] + } + + self.assertRaises(sol_ex.SolValidationError, + self.controller.heal, request=self.request, id=inst_id, + body=body) diff --git a/tacker/tests/unit/sol_refactored/infra_drivers/openstack/test_openstack.py b/tacker/tests/unit/sol_refactored/infra_drivers/openstack/test_openstack.py index b27c5d774..1e4d3ad3d 100644 --- a/tacker/tests/unit/sol_refactored/infra_drivers/openstack/test_openstack.py +++ b/tacker/tests/unit/sol_refactored/infra_drivers/openstack/test_openstack.py @@ -21,6 +21,8 @@ from oslo_utils import uuidutils from unittest import mock from tacker import context +from tacker.sol_refactored.common import config +from tacker.sol_refactored.common import exceptions as sol_ex from tacker.sol_refactored.common import vnfd_utils from tacker.sol_refactored.infra_drivers.openstack import openstack from tacker.sol_refactored import objects @@ -35,13 +37,6 @@ SAMPLE_FLAVOUR_ID = "simple" _vim_connection_info_example = { "vimId": "vim_id_1", "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", - # "interfaceInfo": omitted - # "accessInfo": omitted -} - -_vim_connection_info_for_change_vnfpkg = { - "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", - "vimId": uuidutils.generate_uuid(), "interfaceInfo": {"endpoint": "http://127.0.0.1/identity"}, "accessInfo": { "username": "nfv_user", @@ -51,7 +46,6 @@ _vim_connection_info_for_change_vnfpkg = { "projectDomain": "Default", "userDomain": "Default" } - } _instantiate_req_example = { @@ -2639,6 +2633,110 @@ mock_resource_list_3 = { ] } +_heat_parameters = { + 'VDU': { + 'VDU1': { + 'desired_capacity': 1, + 'computeFlavourId': 'm1.tiny', + 'locationConstraints': None + }, + 'VirtualStorage': { + 'vcImageId': 'image-1.0.0-x86_64-disk' + }, + 'VDU2': { + 'computeFlavourId': 'm1.small', + 'vcImageId': 'image-VDU2' + } + }, + 'CP': { + 'VDU1_CP1': { + 'network': 'res_id_ext_vl_1' + }, + 'VDU1_CP2': { + 'network': 'res_id_id_ext_vl_2', + 'fixed_ips': [{ + 'subnet': 'res_id_subnet_1' + }] + }, + 'VDU2_CP1': { + 'network': 'res_id_ext_vl_1', + 'fixed_ips': [{ + 'ip_address': '10.10.0.102' + }] + }, + 'VDU2_CP2': { + 'network': 'res_id_id_ext_vl_2', + 'fixed_ips': [] + } + } +} + +_grant_req_example = { + 'operation': 'SCALE', + 'removeResources': [{ + 'id': 'ed52e40b-922f-4e01-bc30-d4bf80029280', + 'type': 'COMPUTE', + 'resourceTemplateId': 'VDU1', + 'resource': { + 'resourceId': 'res_id_VDU1_1', + 'vimLevelResourceType': 'OS::Nova::Server' + } + }, { + 'id': 'VDU1_CP1-ed52e40b-922f-4e01-bc30-d4bf80029280', + 'type': 'LINKPORT', + 'resourceTemplateId': 'VDU1_CP1', + 'resource': { + 'resourceId': 'res_id_VDU1_1_CP1', + 'vimLevelResourceType': 'OS::Neutron::Port' + } + }, { + 'id': 'VDU1_CP2-ed52e40b-922f-4e01-bc30-d4bf80029280', + 'type': 'LINKPORT', + 'resourceTemplateId': 'VDU1_CP2', + 'resource': { + 'resourceId': 'res_id_VDU1_1_CP2', + 'vimLevelResourceType': 'OS::Neutron::Port' + } + }, { + 'id': 'VDU1_CP3-ed52e40b-922f-4e01-bc30-d4bf80029280', + 'type': 'LINKPORT', + 'resourceTemplateId': 'VDU1_CP3', + 'resource': { + 'resourceId': 'res_id_VDU1_1_CP3', + 'vimLevelResourceType': 'OS::Neutron::Port' + } + }, { + 'id': 'VDU1_CP4-ed52e40b-922f-4e01-bc30-d4bf80029280', + 'type': 'LINKPORT', + 'resourceTemplateId': 'VDU1_CP4', + 'resource': { + 'resourceId': 'res_id_VDU1_1_CP4', + 'vimLevelResourceType': 'OS::Neutron::Port' + } + }, { + 'id': 'VDU1_CP5-ed52e40b-922f-4e01-bc30-d4bf80029280', + 'type': 'LINKPORT', + 'resourceTemplateId': 'VDU1_CP5', + 'resource': { + 'resourceId': 'res_id_VDU1_1_CP5', + 'vimLevelResourceType': 'OS::Neutron::Port' + } + }, { + 'id': 'VirtualStorage-ed52e40b-922f-4e01-bc30-d4bf80029280', + 'type': 'STORAGE', + 'resourceTemplateId': 'VirtualStorage', + 'resource': { + 'resourceId': 'res_id_VirtualStorage_1', + 'vimLevelResourceType': 'OS::Cinder::Volume' + } + }], + 'additionalParams': { + 'key': 'value' + } +} + +CONF = config.CONF + class TestOpenstack(base.BaseTestCase): @@ -2647,6 +2745,7 @@ class TestOpenstack(base.BaseTestCase): objects.register_all() self.driver = openstack.Openstack() self.context = context.get_admin_context() + CONF.v2_vnfm.default_graceful_termination_timeout = 0 cur_dir = os.path.dirname(__file__) sample_dir = os.path.join(cur_dir, "../..", "samples") @@ -2811,3 +2910,353 @@ class TestOpenstack(base.BaseTestCase): # check result = inst.to_dict()["instantiatedVnfInfo"] self._check_inst_info(_expected_inst_info_change_ext_conn, result) + + @mock.patch.object(openstack.heat_utils.HeatClient, 'get_status') + @mock.patch.object(openstack.heat_utils.HeatClient, 'create_stack') + @mock.patch.object(openstack.heat_utils.HeatClient, 'update_stack') + @mock.patch.object(openstack.heat_utils.HeatClient, 'get_resources') + @mock.patch.object(openstack.heat_utils.HeatClient, 'get_parameters') + @mock.patch.object(openstack.heat_utils.HeatClient, 'get_template') + def test_instantiate(self, mock_template, mock_parameters, mock_resources, + mock_update_stack, mock_create_stack, mock_status): + # prepare + req = objects.InstantiateVnfRequest.from_dict(_instantiate_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req.vimConnectionInfo + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.INSTANTIATE + ) + grant = objects.GrantV1() + mock_status.return_value = (None, 'test') + mock_resources.return_value = _heat_reses_example + mock_parameters.return_value = _heat_get_parameters_example + mock_template.return_value = _heat_get_template_example + # execute + self.driver.instantiate(req, inst, grant_req, grant, self.vnfd_1) + mock_create_stack.assert_called_once() + + mock_status.return_value = ('Create_Failed', 'test') + # execute + self.driver.instantiate(req, inst, grant_req, grant, self.vnfd_1) + mock_update_stack.assert_called_once() + + @mock.patch.object(openstack.heat_utils.HeatClient, 'get_status') + @mock.patch.object(openstack.heat_utils.HeatClient, 'delete_stack') + def test_instantiate_rollback(self, mock_delete_stack, mock_status): + # prepare + req = objects.InstantiateVnfRequest.from_dict(_instantiate_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req.vimConnectionInfo + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.INSTANTIATE + ) + grant = objects.GrantV1() + mock_status.return_value = (None, 'test') + # execute + self.driver.instantiate_rollback( + req, inst, grant_req, grant, self.vnfd_1) + mock_delete_stack.assert_not_called() + + mock_status.return_value = ('Create_Failed', 'test') + # execute + self.driver.instantiate_rollback( + req, inst, grant_req, grant, self.vnfd_1) + mock_delete_stack.assert_called_once() + + @mock.patch.object(openstack.heat_utils.HeatClient, 'delete_stack') + def test_terminate(self, mock_delete_stack): + # prepare + req_inst = objects.InstantiateVnfRequest.from_dict( + _instantiate_req_example) + req = objects.TerminateVnfRequest( + terminationType='GRACEFUL', gracefulTerminationTimeout=0) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo, + instantiatedVnfInfo=( + objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _inst_info_example)) + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.TERMINATE + ) + grant = objects.GrantV1() + # graceful + self.driver.terminate(req, inst, grant_req, grant, self.vnfd_1) + self.assertEqual(1, mock_delete_stack.call_count) + + # graceful with no time + req = objects.TerminateVnfRequest(terminationType='GRACEFUL') + self.driver.terminate(req, inst, grant_req, grant, self.vnfd_1) + self.assertEqual(2, mock_delete_stack.call_count) + + # forceful + req = objects.TerminateVnfRequest(terminationType='FORCEFUL') + self.driver.terminate(req, inst, grant_req, grant, self.vnfd_1) + self.assertEqual(3, mock_delete_stack.call_count) + + @mock.patch.object(openstack.heat_utils.HeatClient, 'update_stack') + @mock.patch.object(openstack.heat_utils.HeatClient, 'get_resources') + @mock.patch.object(openstack.heat_utils.HeatClient, 'get_parameters') + @mock.patch.object(openstack.heat_utils.HeatClient, 'mark_unhealthy') + @mock.patch.object(openstack.heat_utils.HeatClient, 'get_template') + def test_scale(self, mock_template, mock_unhealthy, mock_parameters, + mock_reses, mock_stack): + # prepare + req_inst = objects.InstantiateVnfRequest.from_dict( + _instantiate_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo, + instantiatedVnfInfo=( + objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _expected_inst_info_vnfc_updated)) + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.SCALE + ) + grant = objects.GrantV1() + mock_parameters.return_value = {'nfv': json.dumps(_heat_parameters)} + mock_template.return_value = _heat_get_template_example + + # scale-out + req = objects.ScaleVnfRequest( + type='SCALE_OUT', aspectId='VDU1_scale', numberOfSteps=1) + mock_reses.return_value = _heat_reses_example + self.driver.scale(req, inst, grant_req, grant, self.vnfd_1) + + # check + result = inst.to_dict()["instantiatedVnfInfo"] + self._check_inst_info(_expected_inst_info_vnfc_updated, result) + + # scale-in + req = objects.ScaleVnfRequest( + type='SCALE_IN', aspectId='VDU1_scale', numberOfSteps=1) + grant_req = objects.GrantRequestV1.from_dict(_grant_req_example) + mock_reses.return_value = _heat_reses_example + self.driver.scale(req, inst, grant_req, grant, self.vnfd_1) + + # check + result = inst.to_dict()["instantiatedVnfInfo"] + self._check_inst_info(_expected_inst_info_vnfc_updated, result) + + # error + req = objects.ScaleVnfRequest( + type='SCALE_IN', aspectId='VDU1_scale', numberOfSteps=1) + grant_req = objects.GrantRequestV1.from_dict(_grant_req_example) + del inst.instantiatedVnfInfo.vnfcResourceInfo[1]['metadata'][ + 'parent_stack_id'] + self.assertRaises( + sol_ex.UnexpectedParentResourceDefinition, self.driver.scale, + req, inst, grant_req, grant, self.vnfd_1) + + @mock.patch.object(openstack.heat_utils.HeatClient, 'update_stack') + @mock.patch.object(openstack.heat_utils.HeatClient, 'get_resources') + @mock.patch.object(openstack.heat_utils.HeatClient, 'get_parameters') + @mock.patch.object(openstack.heat_utils.HeatClient, 'mark_unhealthy') + def test_scale_rollback( + self, mock_unhealthy, mock_parameters, + mock_reses, mock_stack): + # prepare + req_inst = objects.InstantiateVnfRequest.from_dict( + _instantiate_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo, + instantiatedVnfInfo=( + objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _expected_inst_info_vnfc_updated)) + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.SCALE + ) + grant = objects.GrantV1() + mock_parameters.return_value = {'nfv': json.dumps(_heat_parameters)} + + # scale-out + req = objects.ScaleVnfRequest( + type='SCALE_OUT', aspectId='VDU1_scale', numberOfSteps=1) + mock_reses.return_value = _heat_reses_example + mock_reses.return_value[13]['physical_resource_id'] = ( + 'res_id_VDU1_1_new') + self.driver.scale_rollback(req, inst, grant_req, grant, self.vnfd_1) + # check + result = inst.to_dict()["instantiatedVnfInfo"] + self._check_inst_info(_expected_inst_info_vnfc_updated, result) + + # error + del mock_reses.return_value[13]['parent_resource'] + self.assertRaises( + sol_ex.UnexpectedParentResourceDefinition, + self.driver.scale_rollback, req, inst, + grant_req, grant, self.vnfd_1) + + @mock.patch.object(openstack.heat_utils.HeatClient, 'update_stack') + @mock.patch.object(openstack.heat_utils.HeatClient, 'get_resources') + @mock.patch.object(openstack.heat_utils.HeatClient, 'get_parameters') + @mock.patch.object(openstack.heat_utils.HeatClient, 'get_template') + def test_change_ext_conn(self, mock_template, mock_parameters, + mock_reses, mock_stack): + req_inst = objects.InstantiateVnfRequest.from_dict( + _instantiate_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo, + instantiatedVnfInfo=( + objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _expected_inst_info_change_ext_conn)) + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.CHANGE_EXT_CONN + ) + grant = objects.GrantV1() + req = objects.ChangeExtVnfConnectivityRequest.from_dict( + _change_ext_conn_req_example) + mock_parameters.return_value = {'nfv': json.dumps(_heat_parameters)} + mock_reses.return_value = _heat_reses_example_change_ext_conn + mock_template.return_value = _heat_get_template_example + self.driver.change_ext_conn(req, inst, grant_req, grant, self.vnfd_1) + + # check + result = inst.to_dict()["instantiatedVnfInfo"] + self._check_inst_info(_expected_inst_info_change_ext_conn, result) + + @mock.patch.object(openstack.heat_utils.HeatClient, 'update_stack') + @mock.patch.object(openstack.heat_utils.HeatClient, 'get_resources') + @mock.patch.object(openstack.heat_utils.HeatClient, 'get_parameters') + @mock.patch.object(openstack.heat_utils.HeatClient, 'get_template') + def test_change_ext_conn_rollback( + self, mock_template, mock_parameters, mock_reses, mock_stack): + req_inst = objects.InstantiateVnfRequest.from_dict( + _instantiate_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo, + instantiatedVnfInfo=( + objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _expected_inst_info_change_ext_conn)) + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.CHANGE_EXT_CONN + ) + grant = objects.GrantV1() + req = objects.ChangeExtVnfConnectivityRequest.from_dict( + _change_ext_conn_req_example) + mock_parameters.return_value = {'nfv': json.dumps(_heat_parameters)} + mock_reses.return_value = _heat_reses_example_change_ext_conn + mock_template.return_value = _heat_get_template_example + self.driver.change_ext_conn_rollback( + req, inst, grant_req, grant, self.vnfd_1) + + # check + result = inst.to_dict()["instantiatedVnfInfo"] + self._check_inst_info(_expected_inst_info_change_ext_conn, result) + + @mock.patch.object(openstack.heat_utils.HeatClient, 'update_stack') + @mock.patch.object(openstack.heat_utils.HeatClient, 'mark_unhealthy') + @mock.patch.object(openstack.heat_utils.HeatClient, 'get_resources') + @mock.patch.object(openstack.heat_utils.HeatClient, 'delete_stack') + @mock.patch.object(openstack.heat_utils.HeatClient, 'create_stack') + @mock.patch.object(openstack.heat_utils.HeatClient, 'get_parameters') + @mock.patch.object(openstack.heat_utils.HeatClient, 'get_template') + @mock.patch.object(openstack.heat_utils.HeatClient, 'get_files') + def test_heal(self, mock_files, mock_template, + mock_parameters, mock_create, mock_delete, + mock_reses, mock_unhealthy, mock_update): + req_inst = objects.InstantiateVnfRequest.from_dict( + _instantiate_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo, + instantiatedVnfInfo=( + objects.VnfInstanceV2_InstantiatedVnfInfo.from_dict( + _expected_inst_info_vnfc_updated)) + ) + grant_req = objects.GrantRequestV1( + operation=fields.LcmOperationType.HEAL + ) + grant = objects.GrantV1() + mock_parameters.return_value = {'nfv': json.dumps(_heat_parameters)} + mock_template.return_value = _heat_get_template_example + + # re-create + req = objects.HealVnfRequest( + additionalParams={ + "all": True + } + ) + mock_reses.return_value = _heat_reses_example + self.driver.heal(req, inst, grant_req, grant, self.vnfd_1) + # check + result = inst.to_dict()["instantiatedVnfInfo"] + self._check_inst_info(_expected_inst_info_vnfc_updated, result) + + # no re-create + grant_req = objects.GrantRequestV1.from_dict(_grant_req_example) + req = objects.HealVnfRequest() + mock_reses.return_value = _heat_reses_example + self.driver.heal(req, inst, grant_req, grant, self.vnfd_1) + + # check + result = inst.to_dict()["instantiatedVnfInfo"] + self._check_inst_info(_expected_inst_info_vnfc_updated, result) diff --git a/tacker/tests/unit/sol_refactored/infra_drivers/openstack/test_userdata_default.py b/tacker/tests/unit/sol_refactored/infra_drivers/openstack/test_userdata_default.py new file mode 100644 index 000000000..3d13de71b --- /dev/null +++ b/tacker/tests/unit/sol_refactored/infra_drivers/openstack/test_userdata_default.py @@ -0,0 +1,763 @@ +# Copyright (C) 2022 FUJITSU +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import os + +from oslo_utils import uuidutils + +from tacker import context +from tacker.sol_refactored.api import api_version +from tacker.sol_refactored.infra_drivers.openstack import userdata_default +from tacker.sol_refactored import objects +from tacker.sol_refactored.objects.v2 import fields +from tacker.tests import base + + +SAMPLE_VNFD_ID = "b1bb0ce7-ebca-4fa7-95ed-4840d7000000" +# InstantiateVnfRequest example +_ext_vl_1 = { + "id": uuidutils.generate_uuid(), + "resourceId": 'net0_id', + "extCps": [ + { + "cpdId": "VDU1_CP1", + "cpConfig": { + "VDU1_CP1_1": { + "cpProtocolData": [{ + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [{ + "type": "IPV4", + "numDynamicAddresses": 1}]}}]} + } + }, + { + "cpdId": "VDU2_CP1", + "cpConfig": { + "VDU2_CP1_1": { + "cpProtocolData": [{ + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [{ + "type": "IPV4", + "fixedAddresses": ["10.10.0.101"]}]}}]} + } + } + ], +} +_ext_vl_2 = { + "id": uuidutils.generate_uuid(), + "resourceId": 'net1_id', + "extCps": [ + { + "cpdId": "VDU1_CP2", + "cpConfig": { + "VDU1_CP2_1": { + "cpProtocolData": [{ + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [{ + "type": "IPV4", + "numDynamicAddresses": 1, + "subnetId": 'subnet1_id'}]}}]} + } + }, + { + "cpdId": "VDU2_CP2", + "cpConfig": { + "VDU2_CP2_1": { + "cpProtocolData": [{ + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [{ + "type": "IPV4", + "fixedAddresses": ["10.10.1.101"], + "subnetId": 'subnet1_id'}]}}]} + } + } + ] +} +_inst_req_example = { + "flavourId": "simple", + "instantiationLevelId": "instantiation_level_2", + "extVirtualLinks": [ + _ext_vl_1, + _ext_vl_2 + ], + "extManagedVirtualLinks": [ + { + "id": uuidutils.generate_uuid(), + "vnfVirtualLinkDescId": "internalVL1", + "resourceId": 'net_mgmt_id' + }, + ], + "vimConnectionInfo": { + "vim1": { + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", + "vimId": uuidutils.generate_uuid(), + "interfaceInfo": {"endpoint": "http://localhost/identity/v3"}, + "accessInfo": { + "username": "nfv_user", + "region": "RegionOne", + "password": "devstack", + "project": "nfv", + "projectDomain": "Default", + "userDomain": "Default" + } + } + } +} + +_scale_req_example = { + 'type': 'SCALE_OUT', + 'aspectId': 'VDU1_scale', + 'numberOfSteps': 1 +} + +_change_ext_conn_req_example = { + "extVirtualLinks": [ + { + "id": "id_ext_vl_3", + "resourceId": "res_id_ext_vl_3", + "extCps": [ + { + "cpdId": "VDU2_CP1", + "cpConfig": { + "VDU2_CP1_1": { + "cpProtocolData": [ + { + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [ + { + "type": "IPV4", + "fixedAddresses": [ + "20.10.0.102" + ] + } + ] + } + } + ] + } + } + } + ] + }, + { + "id": "id_ext_vl_4", + "resourceId": "res_id_id_ext_vl_4", + "extCps": [ + { + "cpdId": "VDU1_CP2", + "cpConfig": { + "VDU1_CP2_1": { + "cpProtocolData": [ + { + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [ + { + "type": "IPV4", + "numDynamicAddresses": 1, + "subnetId": "res_id_subnet_4" + } + ] + } + } + ] + } + } + }, + { + "cpdId": "VDU2_CP2", + "cpConfig": { + "VDU2_CP2_1": { + "linkPortId": "link_port_id_VDU2_CP2_modified" + } + } + } + ], + "extLinkPorts": [ + { + "id": "link_port_id_VDU2_CP2_modified", + "resourceHandle": { + "resourceId": "res_id_VDU2_CP2_modified" + } + } + ] + } + ] +} + +_inst_info_example = { + "flavourId": "simple", + "vnfState": "STARTED", + 'scaleStatus': [], + "extCpInfo": [ + { + "id": "90561570-264c-4472-b84f-1fff98513475", + "cpdId": "VDU2_CP1", + "cpConfigId": "VDU2_CP1_1", + "extLinkPortId": "ac27c99b-73c8-4e91-b730-90deade72af4", + "associatedVnfcCpId": "be955786-a0c7-4b61-8cd8-9bb8bcb1c6e3" + }, + { + "id": "f9f4b4b2-50e2-4c73-b89b-e0665e65ffbe", + "cpdId": "VDU2_CP2", + "cpConfigId": "VDU2_CP2_1", + "extLinkPortId": "12567a13-9fbd-4803-ad9f-d94ced266cd8", + "associatedVnfcCpId": "c54fa2fc-185a-49a7-bb89-f30f7c3be6a4" + }, + { + "id": "05474d0b-a1f7-4be5-b57e-ef6873e1f3b6", + "cpdId": "VDU1_CP2", + "cpConfigId": "VDU1_CP2_1", + "extLinkPortId": "aa6646da-2e59-4de9-9b72-c62e7c4d9142", + "associatedVnfcCpId": "fdbb289f-87c8-40d0-bf06-da07b41ba124" + }, + { + "id": "42ede9a6-c2b8-4c0d-a337-26342ffb236c", + "cpdId": "VDU1_CP1", + "cpConfigId": "VDU1_CP1_1", + "extLinkPortId": "efd0eb4e-4e55-4ac8-8b9b-403ec79faf2d", + "associatedVnfcCpId": "235f920c-8b49-4894-9c36-73f5a3b9f74d" + }, + { + "id": "4f0ab1ad-b7de-482f-a69b-1093c71d2ceb", + "cpdId": "VDU1_CP2", + "cpConfigId": "VDU1_CP2_1", + "extLinkPortId": "a6c4c043-e082-4873-a871-02467af66224", + "associatedVnfcCpId": "e23d970d-9ea9-4c26-9d67-8f244383ea3c" + }, + { + "id": "7a7fa30f-a303-4856-bc8b-b836cb682892", + "cpdId": "VDU1_CP1", + "cpConfigId": "VDU1_CP1_1", + "extLinkPortId": "f58df4d9-08ff-41b7-ab73-95ebfb8103c4", + "associatedVnfcCpId": "259c5895-7be6-4bed-8a94-221c41b3d08f" + } + ], + "extVirtualLinkInfo": [ + { + "id": "137bdf0b-835c-43f0-b0d2-5c002599118a", + "resourceHandle": { + "resourceId": "6f97f400-2861-482a-ba78-65b652aaf8fc" + }, + "extLinkPorts": [ + { + "id": "ac27c99b-73c8-4e91-b730-90deade72af4", + "resourceHandle": { + "resourceId": "res_id_VDU2_CP1", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "90561570-264c-4472-b84f-1fff98513475" + }, + { + "id": "efd0eb4e-4e55-4ac8-8b9b-403ec79faf2d", + "resourceHandle": { + "resourceId": "res_id_VDU1_1_CP1", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "42ede9a6-c2b8-4c0d-a337-26342ffb236c" + }, + { + "id": "f58df4d9-08ff-41b7-ab73-95ebfb8103c4", + "resourceHandle": { + "resourceId": "res_id_VDU1_2_CP1", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "7a7fa30f-a303-4856-bc8b-b836cb682892" + } + ], + "currentVnfExtCpData": [ + { + "cpdId": "VDU2_CP1", + "cpConfig": { + "VDU2_CP2_1": { + "cpProtocolData": [ + { + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [ + { + "type": "IPV4", + "fixedAddresses": [ + '10.10.0.2' + ], + "subnetId": 'test' + } + ] + } + } + ] + } + } + } + ] + }, + { + "id": "d8141a5a-6b6e-4dab-9bf5-158f23a617d7", + "resourceHandle": { + "resourceId": "02bc95e0-3d43-4d11-83b8-f7b15d8661a9" + }, + "extLinkPorts": [ + { + "id": "12567a13-9fbd-4803-ad9f-d94ced266cd8", + "resourceHandle": { + "resourceId": "res_id_VDU2_CP2", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "f9f4b4b2-50e2-4c73-b89b-e0665e65ffbe" + }, + { + "id": "aa6646da-2e59-4de9-9b72-c62e7c4d9142", + "resourceHandle": { + "resourceId": "res_id_VDU1_1_CP2", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "05474d0b-a1f7-4be5-b57e-ef6873e1f3b6" + }, + { + "id": "a6c4c043-e082-4873-a871-02467af66224", + "resourceHandle": { + "resourceId": "res_id_VDU1_2_CP2", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "4f0ab1ad-b7de-482f-a69b-1093c71d2ceb" + } + ], + "currentVnfExtCpData": [ + { + "cpdId": "VDU1_CP2", + "cpConfig": { + "VDU1_CP2_1": { + "cpProtocolData": [ + { + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [ + { + "type": "IPV4", + "fixedAddresses": [ + '10.10.0.3' + ], + "subnetId": 'test' + } + ] + } + } + ] + } + } + }, + { + "cpdId": "VDU2_CP2", + "cpConfig": { + "VDU2_CP2_1": { + "cpProtocolData": [ + { + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [ + { + "type": "IPV4", + "fixedAddresses": [ + '10.10.0.4' + ], + "subnetId": 'test' + } + ] + } + } + ] + } + } + }, + ] + } + ], + "extManagedVirtualLinkInfo": [ + { + "id": "bad53df7-f1fa-482d-91b1-caec382aeec2", + "vnfVirtualLinkDescId": "internalVL1", + "networkResource": { + "resourceId": "56730009-169c-4f96-8141-828acf1ee067" + }, + "vnfLinkPorts": [ + { + "id": "74f387fe-6355-4af3-adc7-cdb507d5fa5f", + "resourceHandle": { + "resourceId": "res_id_VDU2_CP3", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "5b0b336b-c207-4fa8-8b41-a5ad87d85cd0", + "cpInstanceType": "VNFC_CP" + }, + { + "id": "4064ec55-b862-4527-a911-8752d3aa765a", + "resourceHandle": { + "resourceId": "res_id_VDU1_1_CP3", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "b0732fb7-a42a-4077-aebc-d22b67b64f13", + "cpInstanceType": "VNFC_CP" + }, + { + "id": "f3c9e62d-0f31-4a36-bd99-eecd8def0871", + "resourceHandle": { + "resourceId": "res_id_VDU1_2_CP3", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "9c65f67e-feb2-447c-b0e7-a4f896185b4f", + "cpInstanceType": "VNFC_CP" + } + ] + } + ], + "vnfcResourceInfo": [ + { + "id": "vnfc_res_info_id_VDU2", + "vduId": "VDU2", + "computeResource": { + "resourceId": "res_id_VDU2", + "vimLevelResourceType": "OS::Nova::Server" + }, + "vnfcCpInfo": [ + { + "id": "be955786-a0c7-4b61-8cd8-9bb8bcb1c6e3", + "cpdId": "VDU2_CP1", + "vnfExtCpId": "90561570-264c-4472-b84f-1fff98513475" + }, + { + "id": "c54fa2fc-185a-49a7-bb89-f30f7c3be6a4", + "cpdId": "VDU2_CP2", + "vnfExtCpId": "f9f4b4b2-50e2-4c73-b89b-e0665e65ffbe" + }, + { + "id": "5b0b336b-c207-4fa8-8b41-a5ad87d85cd0", + "cpdId": "VDU2_CP3", + "vnfLinkPortId": "74f387fe-6355-4af3-adc7-cdb507d5fa5f" + }, + { + "id": "2cb2b3a8-a7a0-41da-b3b8-4b82f576b090", + "cpdId": "VDU2_CP4", + "vnfLinkPortId": "8e01813f-35fc-4a35-8f64-0da08a45ea21" + }, + { + "id": "39a7d895-3b19-4330-b6ec-ae3557ea9c01", + "cpdId": "VDU2_CP5", + "vnfLinkPortId": "4dd7cadd-b9a1-484f-b2f2-1ff50ef0d90f" + }, + { + "id": "315a938a-b194-0c20-6398-ba92cce39c18", + "cpdId": "VDU2_CP6", + "vnfLinkPortId": "6dafdda1-db6c-492e-6dc2-4e5d323d6f98" + } + ] + }, + { + "id": "vnfc_res_info_id_VDU1_1", + "vduId": "VDU1", + "computeResource": { + "resourceId": "res_id_VDU1_1", + "vimLevelResourceType": "OS::Nova::Server" + }, + "storageResourceIds": ["2135b13c-e630-4700-8f8d-85b6e48f7871"], + "vnfcCpInfo": [ + { + "id": "235f920c-8b49-4894-9c36-73f5a3b9f74d", + "cpdId": "VDU1_CP1", + "vnfExtCpId": "42ede9a6-c2b8-4c0d-a337-26342ffb236c" + }, + { + "id": "fdbb289f-87c8-40d0-bf06-da07b41ba124", + "cpdId": "VDU1_CP2", + "vnfExtCpId": "05474d0b-a1f7-4be5-b57e-ef6873e1f3b6" + }, + { + "id": "b0732fb7-a42a-4077-aebc-d22b67b64f13", + "cpdId": "VDU1_CP3", + "vnfLinkPortId": "4064ec55-b862-4527-a911-8752d3aa765a" + }, + { + "id": "a49f8fb8-6fd9-4e9f-a6dd-0d268e51c83c", + "cpdId": "VDU1_CP4", + "vnfLinkPortId": "1666a0f7-6a34-474e-87a2-07fb0c30ecdb" + }, + { + "id": "33194d65-ecd6-48d9-8ef7-c15ce9fef46c", + "cpdId": "VDU1_CP5", + "vnfLinkPortId": "ace663cd-431b-402a-b2ae-d0824c996edb" + } + ] + }, + { + "id": "vnfc_res_info_id_VDU1_2", + "vduId": "VDU1", + "computeResource": { + "resourceId": "res_id_VDU1_2", + "vimLevelResourceType": "OS::Nova::Server" + }, + "storageResourceIds": ["739f7012-7973-485b-b34f-b006bc336150"], + "vnfcCpInfo": [ + { + "id": "259c5895-7be6-4bed-8a94-221c41b3d08f", + "cpdId": "VDU1_CP1", + # when extLinkPorts of extVirtualLinks specified, there is + # no vnfExtCpId nor vnfLinkPortId. + }, + { + "id": "e23d970d-9ea9-4c26-9d67-8f244383ea3c", + "cpdId": "VDU1_CP2", + "vnfExtCpId": "4f0ab1ad-b7de-482f-a69b-1093c71d2ceb" + }, + { + "id": "9c65f67e-feb2-447c-b0e7-a4f896185b4f", + "cpdId": "VDU1_CP3", + "vnfLinkPortId": "f3c9e62d-0f31-4a36-bd99-eecd8def0871" + }, + { + "id": "0716ac20-612a-4ac2-8c87-d83be31dd4b5", + "cpdId": "VDU1_CP4", + "vnfLinkPortId": "bce3159b-caca-45b7-8bb7-88015e951e56" + }, + { + "id": "c9112298-61eb-4bba-b285-ed3419593b1b", + "cpdId": "VDU1_CP5", + "vnfLinkPortId": "e0f98917-70ff-4f79-8747-9d7fc22827a4" + } + ] + }, + { + "id": "vnfc_res_no_cp_info", + "vduId": "VDU1", + "computeResource": { + "resourceId": "res_id_VDU1_3", + "vimLevelResourceType": "OS::Nova::Server" + } + } + ], + "vnfVirtualLinkResourceInfo": [ + { + "id": "18bd0111-d5e1-4aa3-b2d8-5b89833c6351", + "vnfVirtualLinkDescId": "internalVL3", + "networkResource": { + "resourceId": "res_id_internalVL3", + "vimLevelResourceType": "OS::Neutron::Net" + }, + "vnfLinkPorts": [ + { + "id": "4dd7cadd-b9a1-484f-b2f2-1ff50ef0d90f", + "resourceHandle": { + "resourceId": "res_id_VDU2_CP5", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "39a7d895-3b19-4330-b6ec-ae3557ea9c01", + "cpInstanceType": "VNFC_CP" + }, + { + "id": "ace663cd-431b-402a-b2ae-d0824c996edb", + "resourceHandle": { + "resourceId": "res_id_VDU1_1_CP5", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "33194d65-ecd6-48d9-8ef7-c15ce9fef46c", + "cpInstanceType": "VNFC_CP" + }, + { + "id": "e0f98917-70ff-4f79-8747-9d7fc22827a4", + "resourceHandle": { + "resourceId": "res_id_VDU1_2_CP5", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "c9112298-61eb-4bba-b285-ed3419593b1b", + "cpInstanceType": "VNFC_CP" + } + ] + }, + { + "id": "047aa313-b591-4529-aa98-cb8ce2b82e28", + "vnfVirtualLinkDescId": "internalVL2", + "networkResource": { + "resourceId": "res_id_internalVL2", + "vimLevelResourceType": "OS::Neutron::Net" + }, + "vnfLinkPorts": [ + { + "id": "8e01813f-35fc-4a35-8f64-0da08a45ea21", + "resourceHandle": { + "resourceId": "res_id_VDU2_CP4", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "2cb2b3a8-a7a0-41da-b3b8-4b82f576b090", + "cpInstanceType": "VNFC_CP" + }, + { + "id": "1666a0f7-6a34-474e-87a2-07fb0c30ecdb", + "resourceHandle": { + "resourceId": "res_id_VDU1_1_CP4", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "a49f8fb8-6fd9-4e9f-a6dd-0d268e51c83c", + "cpInstanceType": "VNFC_CP" + }, + { + "id": "bce3159b-caca-45b7-8bb7-88015e951e56", + "resourceHandle": { + "resourceId": "res_id_VDU1_2_CP4", + "vimLevelResourceType": "OS::Neutron::Port" + }, + "cpInstanceId": "0716ac20-612a-4ac2-8c87-d83be31dd4b5", + "cpInstanceType": "VNFC_CP" + } + ] + } + ], + "virtualStorageResourceInfo": [ + { + "id": "2135b13c-e630-4700-8f8d-85b6e48f7871", + "virtualStorageDescId": "VirtualStorage", + "storageResource": { + "resourceId": "res_id_VirtualStorage_1", + "vimLevelResourceType": "OS::Cinder::Volume" + } + }, + { + "id": "739f7012-7973-485b-b34f-b006bc336150", + "virtualStorageDescId": "VirtualStorage", + "storageResource": { + "resourceId": "res_id_VirtualStorage_2", + "vimLevelResourceType": "OS::Cinder::Volume" + } + } + ], + "vnfcInfo": [ + { + "id": "VDU2-vnfc_res_info_id_VDU2", + "vduId": "VDU2", + "vnfcResourceInfoId": "vnfc_res_info_id_VDU2", + "vnfcState": "STARTED" + }, + { + "id": "VDU1-vnfc_res_info_id_VDU1_1", + "vduId": "VDU1", + "vnfcResourceInfoId": "vnfc_res_info_id_VDU1_1", + "vnfcState": "STARTED" + }, + { + "id": "VDU1-vnfc_res_info_id_VDU1_2", + "vduId": "VDU1", + "vnfcResourceInfoId": "vnfc_res_info_id_VDU1_2", + "vnfcState": "STARTED" + } + ] +} + +_vnf_instance_example = { + "id": uuidutils.generate_uuid(), + "vnfdId": SAMPLE_VNFD_ID, + "vnfProvider": "provider", + "vnfProductName": "product name", + "vnfSoftwareVersion": "software version", + "vnfdVersion": "vnfd version", + "instantiationState": "NOT_INSTANTIATED", + "vimConnectionInfo": _inst_req_example['vimConnectionInfo'] +} + + +class TestUserdataDefault(base.BaseTestCase): + + def setUp(self): + super(TestUserdataDefault, self).setUp() + objects.register_all() + self.context = context.get_admin_context() + self.context.api_version = api_version.APIVersion('2.0.0') + self.grant = {} + cur_dir = os.path.dirname(__file__) + sample_dir = os.path.join(cur_dir, "../..", "samples") + self.tmp_csar_dir = os.path.join(sample_dir, "sample1") + self.userdata_default = userdata_default.DefaultUserData() + + def test_instantiate(self): + req = _inst_req_example + req['additionalParams'] = {"nfv": "req"} + inst = _vnf_instance_example + grant_req = { + "operation": fields.LcmOperationType.INSTANTIATE + } + self.grant['additionalParams'] = {"nfv": "grant"} + result = self.userdata_default.instantiate( + req, inst, grant_req, self.grant, self.tmp_csar_dir) + self.assertIsNotNone(result['template']) + self.assertIsNotNone(result['parameters']) + self.assertNotIn('req', result['parameters']['nfv']) + self.assertIn('grant', result['parameters']['nfv']) + self.assertIsNotNone(result['files']) + + def test_scale(self): + req = _scale_req_example + inst = _vnf_instance_example + inst['instantiationState'] = 'INSTANTIATED' + inst['instantiatedVnfInfo'] = _inst_info_example + grant_req = { + "operation": fields.LcmOperationType.SCALE + } + result = self.userdata_default.scale( + req, inst, grant_req, self.grant, self.tmp_csar_dir) + self.assertIn('VDU1', result['parameters']['nfv']['VDU']) + + def test_scale_rollback(self): + req = _scale_req_example + inst = _vnf_instance_example + inst['instantiationState'] = 'INSTANTIATED' + inst['instantiatedVnfInfo'] = _inst_info_example + grant_req = { + "operation": fields.LcmOperationType.SCALE + } + result = self.userdata_default.scale_rollback( + req, inst, grant_req, self.grant, self.tmp_csar_dir) + self.assertIn('VDU1', result['parameters']['nfv']['VDU']) + + def test_change_ext_conn(self): + req = _change_ext_conn_req_example + inst = _vnf_instance_example + inst['instantiationState'] = 'INSTANTIATED' + inst['instantiatedVnfInfo'] = _inst_info_example + grant_req = { + "operation": fields.LcmOperationType.CHANGE_EXT_CONN + } + result = self.userdata_default.change_ext_conn( + req, inst, grant_req, self.grant, self.tmp_csar_dir) + self.assertIn('VDU1_CP2', result['parameters']['nfv']['CP']) + self.assertIn('VDU2_CP1', result['parameters']['nfv']['CP']) + self.assertIn('VDU2_CP2', result['parameters']['nfv']['CP']) + + def test_change_ext_conn_rollback(self): + req = _change_ext_conn_req_example + inst = _vnf_instance_example + inst['instantiationState'] = 'INSTANTIATED' + inst['instantiatedVnfInfo'] = _inst_info_example + grant_req = { + "operation": fields.LcmOperationType.CHANGE_EXT_CONN + } + result = self.userdata_default.change_ext_conn_rollback( + req, inst, grant_req, self.grant, self.tmp_csar_dir) + self.assertIn('VDU1_CP2', result['parameters']['nfv']['CP']) + self.assertIn('VDU2_CP1', result['parameters']['nfv']['CP']) + self.assertIn('VDU2_CP2', result['parameters']['nfv']['CP']) + + def test_heal(self): + result = self.userdata_default.heal(None, None, None, None, None) + self.assertEqual({}, result['parameters']['nfv']) diff --git a/tacker/tests/unit/sol_refactored/nfvo/__init__.py b/tacker/tests/unit/sol_refactored/nfvo/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tacker/tests/unit/sol_refactored/nfvo/test_local_nfvo.py b/tacker/tests/unit/sol_refactored/nfvo/test_local_nfvo.py new file mode 100644 index 000000000..7360ef1c4 --- /dev/null +++ b/tacker/tests/unit/sol_refactored/nfvo/test_local_nfvo.py @@ -0,0 +1,749 @@ +# Copyright (C) 2022 FUJITSU +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from datetime import datetime +import os +from unittest import mock + +from oslo_utils import uuidutils + +from tacker import context +from tacker.objects import vnf_package +from tacker.objects import vnf_package_vnfd +from tacker.sol_refactored.api import api_version +from tacker.sol_refactored.common import config +from tacker.sol_refactored.common import exceptions as sol_ex +from tacker.sol_refactored.common import lcm_op_occ_utils as lcmocc_utils +from tacker.sol_refactored.common import vnf_instance_utils as inst_utils +from tacker.sol_refactored.nfvo import glance_utils +from tacker.sol_refactored.nfvo import local_nfvo +from tacker.sol_refactored import objects +from tacker.sol_refactored.objects.v2 import fields +from tacker.tests import base + + +SAMPLE_VNFD_ID = "b1bb0ce7-ebca-4fa7-95ed-4840d7000000" +SAMPLE_VNFPKG_ID = "d04753f1-493e-17dc-e4a9-65f73b3ccc24" +_inst_grant_req_example = { + "vnfInstanceId": "2d004394-d0f0-406d-845a-2b148f91039a", + "vnfLcmOpOccId": "34da9ba7-6ab1-4d8d-a68a-68892e56642c", + "vnfdId": "b1bb0ce7-ebca-4fa7-95ed-4840d7000000", + "flavourId": "simple", + "operation": "INSTANTIATE", + "isAutomaticInvocation": False, + "instantiationLevelId": "instantiation_level_2", + "addResources": [{ + "id": "6396a2a1-7e36-49bd-8fff-2d46394a6b29", + "type": "COMPUTE", + "resourceTemplateId": "VDU1" + }, { + "id": "VDU1_CP1-6396a2a1-7e36-49bd-8fff-2d46394a6b29", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP1" + }, { + "id": "VDU1_CP2-6396a2a1-7e36-49bd-8fff-2d46394a6b29", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP2" + }, { + "id": "VDU1_CP3-6396a2a1-7e36-49bd-8fff-2d46394a6b29", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP3" + }, { + "id": "VDU1_CP5-6396a2a1-7e36-49bd-8fff-2d46394a6b29", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP5" + }, { + "id": "VDU1_CP4-6396a2a1-7e36-49bd-8fff-2d46394a6b29", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP4" + }, { + "id": "VirtualStorage-6396a2a1-7e36-49bd-8fff-2d46394a6b29", + "type": "STORAGE", + "resourceTemplateId": "VirtualStorage" + }, { + "id": "39e0af93-aba5-4a15-9231-cdaa4149738e", + "type": "COMPUTE", + "resourceTemplateId": "VDU1" + }, { + "id": "VDU1_CP1-39e0af93-aba5-4a15-9231-cdaa4149738e", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP1" + }, { + "id": "VDU1_CP2-39e0af93-aba5-4a15-9231-cdaa4149738e", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP2" + }, { + "id": "VDU1_CP3-39e0af93-aba5-4a15-9231-cdaa4149738e", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP3" + }, { + "id": "VDU1_CP5-39e0af93-aba5-4a15-9231-cdaa4149738e", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP5" + }, { + "id": "VDU1_CP4-39e0af93-aba5-4a15-9231-cdaa4149738e", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP4" + }, { + "id": "VirtualStorage-39e0af93-aba5-4a15-9231-cdaa4149738e", + "type": "STORAGE", + "resourceTemplateId": "VirtualStorage" + }, { + "id": "8a3ab83f-187c-4feb-b1b5-a6bf587130fa", + "type": "COMPUTE", + "resourceTemplateId": "VDU1" + }, { + "id": "VDU1_CP1-8a3ab83f-187c-4feb-b1b5-a6bf587130fa", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP1" + }, { + "id": "VDU1_CP2-8a3ab83f-187c-4feb-b1b5-a6bf587130fa", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP2" + }, { + "id": "VDU1_CP3-8a3ab83f-187c-4feb-b1b5-a6bf587130fa", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP3" + }, { + "id": "VDU1_CP5-8a3ab83f-187c-4feb-b1b5-a6bf587130fa", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP5" + }, { + "id": "VDU1_CP4-8a3ab83f-187c-4feb-b1b5-a6bf587130fa", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP4" + }, { + "id": "VirtualStorage-8a3ab83f-187c-4feb-b1b5-a6bf587130fa", + "type": "STORAGE", + "resourceTemplateId": "VirtualStorage" + }, { + "id": "3f2385fa-3f13-436d-b93a-47c62ef237e0", + "type": "COMPUTE", + "resourceTemplateId": "VDU2" + }, { + "id": "VDU2_CP5-3f2385fa-3f13-436d-b93a-47c62ef237e0", + "type": "LINKPORT", + "resourceTemplateId": "VDU2_CP5" + }, { + "id": "VDU2_CP2-3f2385fa-3f13-436d-b93a-47c62ef237e0", + "type": "LINKPORT", + "resourceTemplateId": "VDU2_CP2" + }, { + "id": "VDU2_CP1-3f2385fa-3f13-436d-b93a-47c62ef237e0", + "type": "LINKPORT", + "resourceTemplateId": "VDU2_CP1" + }, { + "id": "VDU2_CP3-3f2385fa-3f13-436d-b93a-47c62ef237e0", + "type": "LINKPORT", + "resourceTemplateId": "VDU2_CP3" + }, { + "id": "VDU2_CP4-3f2385fa-3f13-436d-b93a-47c62ef237e0", + "type": "LINKPORT", + "resourceTemplateId": "VDU2_CP4" + }, { + "id": "a411a968-c00b-4d46-8553-a37f3537391e", + "type": "VL", + "resourceTemplateId": "internalVL2" + }, { + "id": "f06ba7cf-f5af-4856-8d3a-56b331d1b9a5", + "type": "VL", + "resourceTemplateId": "internalVL3" + }], + "placementConstraints": [{ + "affinityOrAntiAffinity": "ANTI_AFFINITY", + "scope": "NFVI_NODE", + "resource": [{ + "idType": "GRANT", + "resourceId": "6396a2a1-7e36-49bd-8fff-2d46394a6b29" + }, { + "idType": "GRANT", + "resourceId": "39e0af93-aba5-4a15-9231-cdaa4149738e" + }, { + "idType": "GRANT", + "resourceId": "8a3ab83f-187c-4feb-b1b5-a6bf587130fa" + }, { + "idType": "GRANT", + "resourceId": "3f2385fa-3f13-436d-b93a-47c62ef237e0" + }] + }], + "_links": { + "vnfLcmOpOcc": { + "href": "http://127.0.0.1:9890/vnflcm/v2/vnf_lcm_op_occs/" + "34da9ba7-6ab1-4d8d-a68a-68892e56642c" + }, + "vnfInstance": { + "href": "http://127.0.0.1:9890/vnflcm/v2/vnf_instances/" + "2d004394-d0f0-406d-845a-2b148f91039a" + } + } +} + +_inst_req_example = { + "vimConnectionInfo": { + "vim1": { + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.V_3", + "vimId": uuidutils.generate_uuid(), + "interfaceInfo": {"endpoint": "http://localhost/identity/v3"}, + "accessInfo": { + "username": "nfv_user", + "region": "RegionOne", + "password": "devstack", + "project": "nfv", + "projectDomain": "Default", + "userDomain": "Default" + } + } + } +} + +_change_vnfkg_grant_req_example = { + 'vnfInstanceId': '5eb4b136-6dac-4af1-b880-4e5eea6ec358', + 'vnfLcmOpOccId': '4ed07654-d459-4ae7-85c5-bed63c563646', + 'vnfdId': '1440b128-be42-4eed-9c4b-7df4b59c35d2', + 'dstVnfdId': 'b1bb0ce7-ebca-4fa7-95ed-4840d7000000', + 'flavourId': 'simple', + 'operation': 'CHANGE_VNFPKG', + 'isAutomaticInvocation': False, + 'addResources': [{ + 'id': 'aab5201a-7516-41d9-9bf3-6a2579df7004', + 'type': 'COMPUTE', + 'resourceTemplateId': 'VDU2' + }, { + 'id': '36027157-a86b-4294-95e8-4ebd4bd91abf', + 'type': 'COMPUTE', + 'resourceTemplateId': 'VDU1' + }, { + 'id': 'VirtualStorage-36027157-a86b-4294-95e8-4ebd4bd91abf', + 'type': 'STORAGE', + 'resourceTemplateId': 'VirtualStorage' + }, { + 'id': 'cf40bb5a-046d-4113-b69b-9e34878ba781', + 'type': 'COMPUTE', + 'resourceTemplateId': 'VDU1' + }, { + 'id': 'VirtualStorage-cf40bb5a-046d-4113-b69b-9e34878ba781', + 'type': 'STORAGE', + 'resourceTemplateId': 'VirtualStorage' + }, { + 'id': '7c093596-c98c-40a0-b2cb-f0c30ce6fb87', + 'type': 'COMPUTE', + 'resourceTemplateId': 'VDU1' + }], + 'removeResources': [{ + 'id': '0c8c248d-69cd-4dde-8b75-9791a02899c8', + 'type': 'COMPUTE', + 'resourceTemplateId': 'VDU2', + 'resource': { + 'resourceId': 'res_id_VDU2', + 'vimLevelResourceType': 'OS::Nova::Server' + } + }, { + 'id': '374dfdae-6add-4a96-a37f-95b8e1b3f79b', + 'type': 'COMPUTE', + 'resourceTemplateId': 'VDU1', + 'resource': { + 'resourceId': 'res_id_VDU1_1', + 'vimLevelResourceType': 'OS::Nova::Server' + } + }, { + 'id': 'VirtualStorage-374dfdae-6add-4a96-a37f-95b8e1b3f79b', + 'type': 'STORAGE', + 'resourceTemplateId': 'VirtualStorage', + 'resource': { + 'resourceId': 'res_id_VirtualStorage_1', + 'vimLevelResourceType': 'OS::Cinder::Volume' + } + }, { + 'id': '4e601248-185f-4405-91bb-ea579c1b866b', + 'type': 'COMPUTE', + 'resourceTemplateId': 'VDU1', + 'resource': { + 'resourceId': 'res_id_VDU1_2', + 'vimLevelResourceType': 'OS::Nova::Server' + } + }, { + 'id': 'VirtualStorage-4e601248-185f-4405-91bb-ea579c1b866b', + 'type': 'STORAGE', + 'resourceTemplateId': 'VirtualStorage', + 'resource': { + 'resourceId': 'res_id_VirtualStorage_2', + 'vimLevelResourceType': 'OS::Cinder::Volume' + } + }, { + 'id': 'cd8bee39-a7b1-4652-8de4-56f5863c9157', + 'type': 'COMPUTE', + 'resourceTemplateId': 'VDU1', + 'resource': { + 'resourceId': 'res_id_VDU1_3', + 'vimLevelResourceType': 'OS::Nova::Server' + } + }], + '_links': { + 'vnfLcmOpOcc': { + 'href': 'http://127.0.0.1:9890/vnflcm/v2/vnf_lcm_op_occs/' + '4ed07654-d459-4ae7-85c5-bed63c563646' + }, + 'vnfInstance': { + 'href': 'http://127.0.0.1:9890/vnflcm/v2/vnf_instances/' + '5eb4b136-6dac-4af1-b880-4e5eea6ec358' + } + } +} + +_change_vnfpkg_example = { + "vnfdId": '61723406-6634-2fc0-060a-0b11104d2667', + "additionalParams": { + "upgrade_type": "RollingUpdate", + "lcm-operation-coordinate-old-vnf": "./Scripts/coordinate_old_vnf.py", + "lcm-operation-coordinate-new-vnf": "./Scripts/coordinate_new_vnf.py", + "vdu_params": [{ + "vdu_id": "VDU1", + "old_vnfc_param": { + "cp_name": "CP1", + "username": "ubuntu", + "password": "ubuntu"}, + "new_vnfc_param": { + "cp_name": "CP1", + "username": "ubuntu", + "password": "ubuntu"}, + }] + } +} + +CONF = config.CONF + + +class TestLocalNfvo(base.BaseTestCase): + + def setUp(self): + super(TestLocalNfvo, self).setUp() + objects.register_all() + self.context = context.get_admin_context() + CONF.vnf_package.vnf_package_csar_path = ( + '/opt/stack/data/tacker/vnfpackage/') + self.context.api_version = api_version.APIVersion('2.0.0') + self.local_nfvo = local_nfvo.LocalNfvo() + + def _get_vnfpkg_or_vnfd( + self, type, state=fields.PackageOnboardingStateType.ONBOARDED): + if type == 'vnfd': + return vnf_package_vnfd.VnfPackageVnfd( + id=uuidutils.generate_uuid(), + package_uuid=SAMPLE_VNFPKG_ID, vnfd_id=SAMPLE_VNFD_ID, + vnf_provider='provider', vnf_product_name='product', + vnf_software_version='2.0', vnfd_version='3.3.1') + else: + return vnf_package.VnfPackage( + id=SAMPLE_VNFPKG_ID, + onboarding_state=state, + operational_state=fields.PackageOperationalStateType.DISABLED) + + @mock.patch.object(vnf_package_vnfd.VnfPackageVnfd, 'get_by_id') + @mock.patch.object(vnf_package.VnfPackage, 'get_by_id') + def test_onboarded_show(self, mock_vnfpackage, mock_vnfd): + mock_vnfd.return_value = self._get_vnfpkg_or_vnfd('vnfd') + mock_vnfpackage.return_value = self._get_vnfpkg_or_vnfd('vnfpkg') + result = self.local_nfvo.onboarded_show(self.context, SAMPLE_VNFD_ID) + self.assertEqual(SAMPLE_VNFPKG_ID, result.id) + self.assertEqual(SAMPLE_VNFD_ID, result.vnfdId) + + @mock.patch.object(vnf_package_vnfd.VnfPackageVnfd, 'get_by_id') + @mock.patch.object(vnf_package.VnfPackage, 'get_by_id') + def test_onboarded_show_vnfpackage_error(self, mock_vnfpackage, mock_vnfd): + mock_vnfd.return_value = self._get_vnfpkg_or_vnfd('vnfd') + self.assertRaises( + sol_ex.VnfdIdNotFound, self.local_nfvo.onboarded_show, + self.context, SAMPLE_VNFD_ID) + + @mock.patch.object(vnf_package_vnfd.VnfPackageVnfd, 'get_by_id') + @mock.patch.object(vnf_package.VnfPackage, 'get_by_id') + def test_onboarded_show_error(self, mock_vnfpackage, mock_vnfd): + mock_vnfd.return_value = self._get_vnfpkg_or_vnfd('vnfd') + mock_vnfpackage.return_value = self._get_vnfpkg_or_vnfd( + 'vnfpkg', fields.PackageOnboardingStateType.CREATED) + self.assertRaises( + sol_ex.VnfdIdNotFound, self.local_nfvo.onboarded_show, + self.context, SAMPLE_VNFD_ID) + + @mock.patch.object(vnf_package_vnfd.VnfPackageVnfd, 'get_by_id') + @mock.patch.object(os.path, 'isdir') + def test_get_csar_dir(self, mock_isdir, mock_vnfd): + mock_vnfd.return_value = self._get_vnfpkg_or_vnfd('vnfd') + mock_isdir.return_value = True + result = self.local_nfvo.get_csar_dir(self.context, SAMPLE_VNFD_ID) + self.assertEqual( + f'/opt/stack/data/tacker/vnfpackage/{SAMPLE_VNFPKG_ID}', result) + + @mock.patch.object(vnf_package_vnfd.VnfPackageVnfd, 'get_by_id') + def test_get_csar_dir_vnfd_error(self, mock_vnfd): + self.assertRaises( + sol_ex.VnfdIdNotFound, self.local_nfvo.get_csar_dir, + self.context, SAMPLE_VNFD_ID) + + @mock.patch.object(vnf_package_vnfd.VnfPackageVnfd, 'get_by_id') + @mock.patch.object(os.path, 'isdir') + def test_get_csar_dir_path_error(self, mock_isdir, mock_vnfd): + mock_vnfd.return_value = self._get_vnfpkg_or_vnfd('vnfd') + mock_isdir.return_value = False + self.assertRaises( + sol_ex.VnfdIdNotFound, self.local_nfvo.get_csar_dir, + self.context, SAMPLE_VNFD_ID) + + @mock.patch.object(local_nfvo.LocalNfvo, 'get_csar_dir') + def test_get_vnfd(self, mock_dir): + cur_dir = os.path.dirname(__file__) + sample_dir = os.path.join(cur_dir, "..", "samples") + mock_dir.return_value = os.path.join(sample_dir, "sample1") + result = self.local_nfvo.get_vnfd(self.context, SAMPLE_VNFD_ID) + self.assertEqual(SAMPLE_VNFD_ID, result.vnfd_id) + + @mock.patch.object(local_nfvo.LocalNfvo, 'get_csar_dir') + @mock.patch.object(lcmocc_utils, 'get_lcmocc') + @mock.patch.object(glance_utils.GlanceClient, 'create_image') + def test_instantiate_grant(self, mock_image, mock_lcmocc, mock_dir): + grant_req = objects.GrantRequestV1.from_dict(_inst_grant_req_example) + grant_res = objects.GrantV1( + id=uuidutils.generate_uuid(), + vnfInstanceId=grant_req.vnfInstanceId, + vnfLcmOpOccId=grant_req.vnfLcmOpOccId + ) + cur_dir = os.path.dirname(__file__) + sample_dir = os.path.join(cur_dir, "..", "samples") + mock_dir.return_value = os.path.join(sample_dir, "sample1") + req = objects.InstantiateVnfRequest.from_dict(_inst_req_example) + mock_lcmocc.return_value = objects.VnfLcmOpOccV2( + # only set used members in the method + operation=fields.LcmOperationType.INSTANTIATE, + operationParams=req) + mock_image.return_value = objects.GrantV1( + id=uuidutils.generate_uuid() + ) + self.local_nfvo.instantiate_grant(self.context, grant_req, grant_res) + result = grant_res.to_dict() + self.assertIsNotNone(result['addResources']) + self.assertEqual(mock_image.return_value.id, result[ + 'vimAssets']['softwareImages'][0]['vimSoftwareImageId']) + + @mock.patch.object(local_nfvo.LocalNfvo, 'get_csar_dir') + @mock.patch.object(local_nfvo.LocalNfvo, '_get_vim_info') + def test_instantiate_grant_no_vim_info(self, mock_vim_info, mock_dir): + cur_dir = os.path.dirname(__file__) + sample_dir = os.path.join(cur_dir, "..", "samples") + mock_dir.return_value = os.path.join(sample_dir, "sample1") + grant_req = objects.GrantRequestV1.from_dict(_inst_grant_req_example) + grant_res = objects.GrantV1( + id=uuidutils.generate_uuid(), + vnfInstanceId=grant_req.vnfInstanceId, + vnfLcmOpOccId=grant_req.vnfLcmOpOccId + ) + mock_vim_info.return_value = None + self.assertRaises( + sol_ex.LocalNfvoGrantFailed, self.local_nfvo.instantiate_grant, + self.context, grant_req, grant_res) + + @mock.patch.object(local_nfvo.LocalNfvo, 'get_csar_dir') + @mock.patch.object(lcmocc_utils, 'get_lcmocc') + @mock.patch.object(glance_utils.GlanceClient, 'create_image') + def test_instantiate_grant_no_image( + self, mock_image, mock_lcmocc, mock_dir): + grant_req = objects.GrantRequestV1.from_dict(_inst_grant_req_example) + grant_res = objects.GrantV1( + id=uuidutils.generate_uuid(), + vnfInstanceId=grant_req.vnfInstanceId, + vnfLcmOpOccId=grant_req.vnfLcmOpOccId + ) + cur_dir = os.path.dirname(__file__) + sample_dir = os.path.join(cur_dir, "..", "samples") + mock_dir.return_value = os.path.join(sample_dir, "sample1") + req = objects.InstantiateVnfRequest.from_dict(_inst_req_example) + mock_lcmocc.return_value = objects.VnfLcmOpOccV2( + # only set used members in the method + operation=fields.LcmOperationType.INSTANTIATE, + operationParams=req) + mock_image.return_value = Exception() + self.assertRaises( + sol_ex.LocalNfvoGrantFailed, self.local_nfvo.instantiate_grant, + self.context, grant_req, grant_res) + + @mock.patch.object(local_nfvo.LocalNfvo, 'get_csar_dir') + @mock.patch.object(lcmocc_utils, 'get_lcmocc') + @mock.patch.object(glance_utils.GlanceClient, 'create_image') + @mock.patch.object(inst_utils, 'get_inst') + def test_change_vnfpkg_grant(self, mock_inst, mock_image, + mock_lcmocc, mock_dir): + grant_req = objects.GrantRequestV1.from_dict( + _change_vnfkg_grant_req_example) + grant_res = objects.GrantV1( + id=uuidutils.generate_uuid(), + vnfInstanceId=grant_req.vnfInstanceId, + vnfLcmOpOccId=grant_req.vnfLcmOpOccId + ) + cur_dir = os.path.dirname(__file__) + sample_dir = os.path.join(cur_dir, "..", "samples") + mock_dir.return_value = os.path.join(sample_dir, "sample1") + req = objects.ChangeCurrentVnfPkgRequest.from_dict( + _change_vnfpkg_example) + mock_lcmocc.return_value = objects.VnfLcmOpOccV2( + operation=fields.LcmOperationType.CHANGE_VNFPKG, + operationParams=req) + mock_image.return_value = objects.GrantV1( + id=uuidutils.generate_uuid() + ) + req = objects.InstantiateVnfRequest.from_dict( + _inst_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req.vimConnectionInfo + ) + mock_inst.return_value = inst + self.local_nfvo.change_vnfpkg_grant(self.context, grant_req, grant_res) + result = grant_res.to_dict() + self.assertEqual(mock_image.return_value.id, result[ + 'vimAssets']['softwareImages'][0]['vimSoftwareImageId']) + + @mock.patch.object(local_nfvo.LocalNfvo, 'get_csar_dir') + @mock.patch.object(local_nfvo.LocalNfvo, '_get_vim_info') + def test_change_vnfpkg_grant_no_vim_info(self, mock_vim_info, mock_dir): + cur_dir = os.path.dirname(__file__) + sample_dir = os.path.join(cur_dir, "..", "samples") + mock_dir.return_value = os.path.join(sample_dir, "sample1") + grant_req = objects.GrantRequestV1.from_dict( + _change_vnfkg_grant_req_example) + grant_res = objects.GrantV1( + id=uuidutils.generate_uuid(), + vnfInstanceId=grant_req.vnfInstanceId, + vnfLcmOpOccId=grant_req.vnfLcmOpOccId + ) + mock_vim_info.return_value = None + self.assertRaises( + sol_ex.LocalNfvoGrantFailed, self.local_nfvo.change_vnfpkg_grant, + self.context, grant_req, grant_res) + + @mock.patch.object(local_nfvo.LocalNfvo, 'get_csar_dir') + @mock.patch.object(lcmocc_utils, 'get_lcmocc') + @mock.patch.object(glance_utils.GlanceClient, 'create_image') + @mock.patch.object(inst_utils, 'get_inst') + def test_change_vnfpkg_grant_no_image(self, mock_inst, mock_image, + mock_lcmocc, mock_dir): + grant_req = objects.GrantRequestV1.from_dict( + _change_vnfkg_grant_req_example) + grant_res = objects.GrantV1( + id=uuidutils.generate_uuid(), + vnfInstanceId=grant_req.vnfInstanceId, + vnfLcmOpOccId=grant_req.vnfLcmOpOccId + ) + cur_dir = os.path.dirname(__file__) + sample_dir = os.path.join(cur_dir, "..", "samples") + mock_dir.return_value = os.path.join(sample_dir, "sample1") + req = objects.ChangeCurrentVnfPkgRequest.from_dict( + _change_vnfpkg_example) + mock_lcmocc.return_value = objects.VnfLcmOpOccV2( + operation=fields.LcmOperationType.CHANGE_VNFPKG, + operationParams=req) + req = objects.InstantiateVnfRequest.from_dict( + _inst_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req.vimConnectionInfo + ) + mock_inst.return_value = inst + mock_image.return_value = Exception() + self.assertRaises( + sol_ex.LocalNfvoGrantFailed, self.local_nfvo.change_vnfpkg_grant, + self.context, grant_req, grant_res) + + @mock.patch.object(local_nfvo.LocalNfvo, 'get_csar_dir') + @mock.patch.object(lcmocc_utils, 'get_lcmocc') + @mock.patch.object(glance_utils.GlanceClient, 'create_image') + @mock.patch.object(inst_utils, 'get_inst') + def test_grant(self, mock_inst, mock_image, mock_lcmocc, mock_dir): + # instantiate + grant_req = objects.GrantRequestV1.from_dict(_inst_grant_req_example) + cur_dir = os.path.dirname(__file__) + sample_dir = os.path.join(cur_dir, "..", "samples") + mock_dir.return_value = os.path.join(sample_dir, "sample1") + req = objects.InstantiateVnfRequest.from_dict(_inst_req_example) + mock_lcmocc.return_value = objects.VnfLcmOpOccV2( + # only set used members in the method + operation=fields.LcmOperationType.INSTANTIATE, + operationParams=req) + mock_image.return_value = objects.GrantV1( + id=uuidutils.generate_uuid() + ) + grant_res = self.local_nfvo.grant(self.context, grant_req) + result = grant_res.to_dict() + self.assertIsNotNone(result['addResources']) + self.assertEqual(mock_image.return_value.id, result[ + 'vimAssets']['softwareImages'][0]['vimSoftwareImageId']) + + # change_vnfpkg + grant_req = objects.GrantRequestV1.from_dict( + _change_vnfkg_grant_req_example) + cur_dir = os.path.dirname(__file__) + sample_dir = os.path.join(cur_dir, "..", "samples") + mock_dir.return_value = os.path.join(sample_dir, "sample1") + req = objects.ChangeCurrentVnfPkgRequest.from_dict( + _change_vnfpkg_example) + mock_lcmocc.return_value = objects.VnfLcmOpOccV2( + operation=fields.LcmOperationType.CHANGE_VNFPKG, + operationParams=req + ) + mock_image.return_value = objects.GrantV1( + id=uuidutils.generate_uuid() + ) + req = objects.InstantiateVnfRequest.from_dict( + _inst_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req.vimConnectionInfo + ) + mock_inst.return_value = inst + grant_res = self.local_nfvo.grant(self.context, grant_req) + result = grant_res.to_dict() + self.assertEqual(mock_image.return_value.id, result[ + 'vimAssets']['softwareImages'][0]['vimSoftwareImageId']) + + @mock.patch.object(vnf_package_vnfd.VnfPackageVnfd, 'get_by_id') + @mock.patch.object(vnf_package.VnfPackage, 'get_by_id') + @mock.patch.object(vnf_package.VnfPackage, 'save') + def test_recv_inst_create_notification( + self, mock_save, mock_vnfpackage, mock_vnfd): + req = objects.InstantiateVnfRequest.from_dict( + _inst_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='NOT_INSTANTIATED', + vimConnectionInfo=req.vimConnectionInfo + ) + mock_vnfd.return_value = self._get_vnfpkg_or_vnfd('vnfd') + mock_vnfpackage.return_value = self._get_vnfpkg_or_vnfd('vnfpkg') + self.local_nfvo.recv_inst_create_notification(self.context, inst) + + @mock.patch.object(vnf_package_vnfd.VnfPackageVnfd, 'get_by_id') + @mock.patch.object(vnf_package.VnfPackage, 'get_by_id') + @mock.patch.object(vnf_package.VnfPackage, 'save') + @mock.patch.object(objects.base.TackerPersistentObject, 'get_by_filter') + def test_recv_inst_delete_notification( + self, mock_inst, mock_save, mock_vnfpackage, mock_vnfd): + req = objects.InstantiateVnfRequest.from_dict( + _inst_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req.vimConnectionInfo + ) + mock_inst.return_value = None + mock_vnfd.return_value = self._get_vnfpkg_or_vnfd('vnfd') + mock_vnfpackage.return_value = self._get_vnfpkg_or_vnfd('vnfpkg') + self.local_nfvo.recv_inst_delete_notification(self.context, inst) + + @mock.patch.object(local_nfvo.LocalNfvo, '_glance_delete_images') + @mock.patch.object(local_nfvo.LocalNfvo, '_update_vnf_pkg_usage_state') + def test_recv_lcmocc_notification(self, mock_delete_image, mock_update): + # terminate-processing + + req_inst = objects.InstantiateVnfRequest.from_dict(_inst_req_example) + inst = objects.VnfInstanceV2( + # required fields + id=uuidutils.generate_uuid(), + vnfdId=SAMPLE_VNFD_ID, + vnfProvider='provider', + vnfProductName='product name', + vnfSoftwareVersion='software version', + vnfdVersion='vnfd version', + instantiationState='INSTANTIATED', + vimConnectionInfo=req_inst.vimConnectionInfo, + ) + req = objects.TerminateVnfRequest(terminationType='FORCEFUL') + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.PROCESSING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.TERMINATE, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + self.local_nfvo.recv_lcmocc_notification(self.context, lcmocc, inst) + + # terminate-failed_temp + lcmocc.operationState = fields.LcmOperationStateType.FAILED_TEMP + self.local_nfvo.recv_lcmocc_notification(self.context, lcmocc, inst) + + # terminate-completed + self.local_nfvo.inst_vim_info = { + inst.id: req_inst.vimConnectionInfo['vim1'] + } + lcmocc.operationState = fields.LcmOperationStateType.COMPLETED + self.local_nfvo.recv_lcmocc_notification(self.context, lcmocc, inst) + + # change_vnfpkg-processing + req = objects.ChangeCurrentVnfPkgRequest.from_dict( + _change_vnfpkg_example) + lcmocc = objects.VnfLcmOpOccV2( + # required fields + id=uuidutils.generate_uuid(), + operationState=fields.LcmOperationStateType.PROCESSING, + stateEnteredTime=datetime.utcnow(), + startTime=datetime.utcnow(), + vnfInstanceId=inst.id, + operation=fields.LcmOperationType.CHANGE_VNFPKG, + isAutomaticInvocation=False, + isCancelPending=False, + operationParams=req) + self.local_nfvo.recv_lcmocc_notification(self.context, lcmocc, inst) + + # change_vnfpkg-failed_temp + lcmocc.operationState = fields.LcmOperationStateType.FAILED_TEMP + self.local_nfvo.recv_lcmocc_notification(self.context, lcmocc, inst) + + # change_vnfpkg-completed + self.local_nfvo.inst_vnfd_id = {inst.id: req.vnfdId} + lcmocc.operationState = fields.LcmOperationStateType.COMPLETED + self.local_nfvo.recv_lcmocc_notification(self.context, lcmocc, inst) diff --git a/tacker/tests/unit/sol_refactored/nfvo/test_nfvo_client.py b/tacker/tests/unit/sol_refactored/nfvo/test_nfvo_client.py new file mode 100644 index 000000000..4d68303e0 --- /dev/null +++ b/tacker/tests/unit/sol_refactored/nfvo/test_nfvo_client.py @@ -0,0 +1,421 @@ +# Copyright (C) 2022 FUJITSU +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import requests +from unittest import mock + +from oslo_utils import uuidutils + +from tacker import context +from tacker.sol_refactored.api import api_version +from tacker.sol_refactored.common import config +from tacker.sol_refactored.common import http_client +from tacker.sol_refactored.common import subscription_utils as subsc_utils +from tacker.sol_refactored.common import vnfd_utils +from tacker.sol_refactored.nfvo import local_nfvo +from tacker.sol_refactored.nfvo import nfvo_client +from tacker.sol_refactored import objects +from tacker.tests import base + +CONF = config.CONF + +SAMPLE_VNFD_ID = "b1bb0ce7-ebca-4fa7-95ed-4840d7000000" +_vnfpkg_body_example = { + "id": uuidutils.generate_uuid(), + "vnfdId": SAMPLE_VNFD_ID, + "vnfProvider": "Company", + "vnfProductName": "Sample VNF", + "vnfSoftwareVersion": "1.0", + "vnfdVersion": "1.0", + "onboardingState": "ONBOARDED", + "operationalState": "ENABLED", + "usageState": "NOT_IN_USE" +} +_grant_res = { + 'id': 'b94d05c9-eb45-4855-9132-20e1d3e7cecf', + 'vnfInstanceId': '2d004394-d0f0-406d-845a-2b148f91039a', + 'vnfLcmOpOccId': '34da9ba7-6ab1-4d8d-a68a-68892e56642c', + 'zones': [{ + 'id': 'b4eba78c-e957-4e66-8bd7-14822f954413', + 'zoneId': 'nova' + }], + 'addResources': [{ + 'resourceDefinitionId': '6396a2a1-7e36-49bd-8fff-2d46394a6b29', + 'zoneId': 'b4eba78c-e957-4e66-8bd7-14822f954413' + }, { + 'resourceDefinitionId': 'VDU1_CP1-6396a2a1-7e36-49bd-8fff-2d46394a6b29' + }, { + 'resourceDefinitionId': 'VDU1_CP2-6396a2a1-7e36-49bd-8fff-2d46394a6b29' + }, { + 'resourceDefinitionId': 'VDU1_CP3-6396a2a1-7e36-49bd-8fff-2d46394a6b29' + }, { + 'resourceDefinitionId': 'VDU1_CP5-6396a2a1-7e36-49bd-8fff-2d46394a6b29' + }, { + 'resourceDefinitionId': 'VDU1_CP4-6396a2a1-7e36-49bd-8fff-2d46394a6b29' + }, { + 'resourceDefinitionId': 'VirtualStorage-6396a2a1-7e36-' + '49bd-8fff-2d46394a6b29' + }, { + 'resourceDefinitionId': '39e0af93-aba5-4a15-9231-cdaa4149738e', + 'zoneId': 'b4eba78c-e957-4e66-8bd7-14822f954413' + }, { + 'resourceDefinitionId': 'VDU1_CP1-39e0af93-aba5-4a15-9231-cdaa4149738e' + }, { + 'resourceDefinitionId': 'VDU1_CP2-39e0af93-aba5-4a15-9231-cdaa4149738e' + }, { + 'resourceDefinitionId': 'VDU1_CP3-39e0af93-aba5-4a15-9231-cdaa4149738e' + }, { + 'resourceDefinitionId': 'VDU1_CP5-39e0af93-aba5-4a15-9231-cdaa4149738e' + }, { + 'resourceDefinitionId': 'VDU1_CP4-39e0af93-aba5-4a15-9231-cdaa4149738e' + }, { + 'resourceDefinitionId': 'VirtualStorage-39e0af93-aba5-' + '4a15-9231-cdaa4149738e' + }, { + 'resourceDefinitionId': '8a3ab83f-187c-4feb-b1b5-a6bf587130fa', + 'zoneId': 'b4eba78c-e957-4e66-8bd7-14822f954413' + }, { + 'resourceDefinitionId': 'VDU1_CP1-8a3ab83f-187c-4feb-b1b5-a6bf587130fa' + }, { + 'resourceDefinitionId': 'VDU1_CP2-8a3ab83f-187c-4feb-b1b5-a6bf587130fa' + }, { + 'resourceDefinitionId': 'VDU1_CP3-8a3ab83f-187c-4feb-b1b5-a6bf587130fa' + }, { + 'resourceDefinitionId': 'VDU1_CP5-8a3ab83f-187c-4feb-b1b5-a6bf587130fa' + }, { + 'resourceDefinitionId': 'VDU1_CP4-8a3ab83f-187c-4feb-b1b5-a6bf587130fa' + }, { + 'resourceDefinitionId': 'VirtualStorage-8a3ab83f-187c-' + '4feb-b1b5-a6bf587130fa' + }, { + 'resourceDefinitionId': '3f2385fa-3f13-436d-b93a-47c62ef237e0', + 'zoneId': 'b4eba78c-e957-4e66-8bd7-14822f954413' + }, { + 'resourceDefinitionId': 'VDU2_CP5-3f2385fa-3f13-436d-b93a-47c62ef237e0' + }, { + 'resourceDefinitionId': 'VDU2_CP2-3f2385fa-3f13-436d-b93a-47c62ef237e0' + }, { + 'resourceDefinitionId': 'VDU2_CP1-3f2385fa-3f13-436d-b93a-47c62ef237e0' + }, { + 'resourceDefinitionId': 'VDU2_CP3-3f2385fa-3f13-436d-b93a-47c62ef237e0' + }, { + 'resourceDefinitionId': 'VDU2_CP4-3f2385fa-3f13-436d-b93a-47c62ef237e0' + }, { + 'resourceDefinitionId': 'a411a968-c00b-4d46-8553-a37f3537391e' + }, { + 'resourceDefinitionId': 'f06ba7cf-f5af-4856-8d3a-56b331d1b9a5' + }], + 'vimAssets': { + 'softwareImages': [{ + 'vnfdSoftwareImageId': 'VDU2', + 'vimSoftwareImageId': '44fd5841-ce98-4d54-a828-6ef51f941c8a' + }, { + 'vnfdSoftwareImageId': 'VirtualStorage', + 'vimSoftwareImageId': 'image-1.0.0-x86_64-disk' + }] + } +} +_inst_grant_req_example = { + "vnfInstanceId": "2d004394-d0f0-406d-845a-2b148f91039a", + "vnfLcmOpOccId": "34da9ba7-6ab1-4d8d-a68a-68892e56642c", + "vnfdId": "b1bb0ce7-ebca-4fa7-95ed-4840d7000000", + "flavourId": "simple", + "operation": "INSTANTIATE", + "isAutomaticInvocation": False, + "instantiationLevelId": "instantiation_level_2", + "addResources": [{ + "id": "6396a2a1-7e36-49bd-8fff-2d46394a6b29", + "type": "COMPUTE", + "resourceTemplateId": "VDU1" + }, { + "id": "VDU1_CP1-6396a2a1-7e36-49bd-8fff-2d46394a6b29", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP1" + }, { + "id": "VDU1_CP2-6396a2a1-7e36-49bd-8fff-2d46394a6b29", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP2" + }, { + "id": "VDU1_CP3-6396a2a1-7e36-49bd-8fff-2d46394a6b29", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP3" + }, { + "id": "VDU1_CP5-6396a2a1-7e36-49bd-8fff-2d46394a6b29", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP5" + }, { + "id": "VDU1_CP4-6396a2a1-7e36-49bd-8fff-2d46394a6b29", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP4" + }, { + "id": "VirtualStorage-6396a2a1-7e36-49bd-8fff-2d46394a6b29", + "type": "STORAGE", + "resourceTemplateId": "VirtualStorage" + }, { + "id": "39e0af93-aba5-4a15-9231-cdaa4149738e", + "type": "COMPUTE", + "resourceTemplateId": "VDU1" + }, { + "id": "VDU1_CP1-39e0af93-aba5-4a15-9231-cdaa4149738e", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP1" + }, { + "id": "VDU1_CP2-39e0af93-aba5-4a15-9231-cdaa4149738e", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP2" + }, { + "id": "VDU1_CP3-39e0af93-aba5-4a15-9231-cdaa4149738e", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP3" + }, { + "id": "VDU1_CP5-39e0af93-aba5-4a15-9231-cdaa4149738e", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP5" + }, { + "id": "VDU1_CP4-39e0af93-aba5-4a15-9231-cdaa4149738e", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP4" + }, { + "id": "VirtualStorage-39e0af93-aba5-4a15-9231-cdaa4149738e", + "type": "STORAGE", + "resourceTemplateId": "VirtualStorage" + }, { + "id": "8a3ab83f-187c-4feb-b1b5-a6bf587130fa", + "type": "COMPUTE", + "resourceTemplateId": "VDU1" + }, { + "id": "VDU1_CP1-8a3ab83f-187c-4feb-b1b5-a6bf587130fa", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP1" + }, { + "id": "VDU1_CP2-8a3ab83f-187c-4feb-b1b5-a6bf587130fa", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP2" + }, { + "id": "VDU1_CP3-8a3ab83f-187c-4feb-b1b5-a6bf587130fa", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP3" + }, { + "id": "VDU1_CP5-8a3ab83f-187c-4feb-b1b5-a6bf587130fa", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP5" + }, { + "id": "VDU1_CP4-8a3ab83f-187c-4feb-b1b5-a6bf587130fa", + "type": "LINKPORT", + "resourceTemplateId": "VDU1_CP4" + }, { + "id": "VirtualStorage-8a3ab83f-187c-4feb-b1b5-a6bf587130fa", + "type": "STORAGE", + "resourceTemplateId": "VirtualStorage" + }, { + "id": "3f2385fa-3f13-436d-b93a-47c62ef237e0", + "type": "COMPUTE", + "resourceTemplateId": "VDU2" + }, { + "id": "VDU2_CP5-3f2385fa-3f13-436d-b93a-47c62ef237e0", + "type": "LINKPORT", + "resourceTemplateId": "VDU2_CP5" + }, { + "id": "VDU2_CP2-3f2385fa-3f13-436d-b93a-47c62ef237e0", + "type": "LINKPORT", + "resourceTemplateId": "VDU2_CP2" + }, { + "id": "VDU2_CP1-3f2385fa-3f13-436d-b93a-47c62ef237e0", + "type": "LINKPORT", + "resourceTemplateId": "VDU2_CP1" + }, { + "id": "VDU2_CP3-3f2385fa-3f13-436d-b93a-47c62ef237e0", + "type": "LINKPORT", + "resourceTemplateId": "VDU2_CP3" + }, { + "id": "VDU2_CP4-3f2385fa-3f13-436d-b93a-47c62ef237e0", + "type": "LINKPORT", + "resourceTemplateId": "VDU2_CP4" + }, { + "id": "a411a968-c00b-4d46-8553-a37f3537391e", + "type": "VL", + "resourceTemplateId": "internalVL2" + }, { + "id": "f06ba7cf-f5af-4856-8d3a-56b331d1b9a5", + "type": "VL", + "resourceTemplateId": "internalVL3" + }], + "placementConstraints": [{ + "affinityOrAntiAffinity": "ANTI_AFFINITY", + "scope": "NFVI_NODE", + "resource": [{ + "idType": "GRANT", + "resourceId": "6396a2a1-7e36-49bd-8fff-2d46394a6b29" + }, { + "idType": "GRANT", + "resourceId": "39e0af93-aba5-4a15-9231-cdaa4149738e" + }, { + "idType": "GRANT", + "resourceId": "8a3ab83f-187c-4feb-b1b5-a6bf587130fa" + }, { + "idType": "GRANT", + "resourceId": "3f2385fa-3f13-436d-b93a-47c62ef237e0" + }] + }], + "_links": { + "vnfLcmOpOcc": { + "href": "http://127.0.0.1:9890/vnflcm/v2/vnf_lcm_op_occs/" + "34da9ba7-6ab1-4d8d-a68a-68892e56642c" + }, + "vnfInstance": { + "href": "http://127.0.0.1:9890/vnflcm/v2/vnf_instances/" + "2d004394-d0f0-406d-845a-2b148f91039a" + } + } +} +_lcmocc_inst_value = { + 'id': 'test-1', + 'vnfInstanceId': 'instance-1', + 'operation': 'INSTANTIATE', + 'operationState': 'COMPLETED', + 'isAutomaticInvocation': False, + 'startTime': '2021-01-23 13:41:03+00:00' +} + + +class TestNfvoClient(base.BaseTestCase): + + def setUp(self): + super(TestNfvoClient, self).setUp() + objects.register_all() + self.context = context.get_admin_context() + CONF.vnf_package.vnf_package_csar_path = ( + '/opt/stack/data/tacker/vnfpackage/') + self.context.api_version = api_version.APIVersion('2.0.0') + self.nfvo_client = nfvo_client.NfvoClient() + self.nfvo_client.endpoint = 'http://127.0.0.1:9990' + auth_handle = http_client.OAuth2AuthHandle( + self.nfvo_client.endpoint, + 'http://127.0.0.1:9990/token', + 'test', + 'test' + ) + self.nfvo_client.client = http_client.HttpClient(auth_handle) + self.nfvo_client.grant_api_version = '1.4.0' + self.nfvo_client.vnfpkgm_api_version = '2.1.0' + + @mock.patch.object(local_nfvo.LocalNfvo, 'onboarded_show') + @mock.patch.object(http_client.HttpClient, 'do_request') + def test_get_vnf_package_info_vnfd( + self, mock_request, mock_onboarded_show): + # local nfvo + self.nfvo_client.is_local = True + mock_onboarded_show.return_value = vnfd_utils.Vnfd( + vnfd_id=SAMPLE_VNFD_ID) + result = self.nfvo_client.get_vnf_package_info_vnfd( + self.context, SAMPLE_VNFD_ID) + self.assertEqual(SAMPLE_VNFD_ID, result.vnfd_id) + + # external nfvo + self.nfvo_client.is_local = False + mock_request.return_value = (requests.Response(), _vnfpkg_body_example) + result = self.nfvo_client.get_vnf_package_info_vnfd( + self.context, SAMPLE_VNFD_ID) + self.assertEqual(SAMPLE_VNFD_ID, result.vnfdId) + + @mock.patch.object(http_client.HttpClient, 'do_request') + def test_onboarded_show_vnfd(self, mock_request): + # local nfvo + self.nfvo_client.is_local = True + result = self.nfvo_client.onboarded_show_vnfd( + self.context, SAMPLE_VNFD_ID) + self.assertIsNone(result) + + # external nfvo + self.nfvo_client.is_local = False + mock_request.return_value = (requests.Response(), 'test') + result = self.nfvo_client.onboarded_show_vnfd( + self.context, SAMPLE_VNFD_ID) + self.assertEqual('test', result) + + @mock.patch.object(http_client.HttpClient, 'do_request') + def test_onboarded_package_content(self, mock_request): + # local nfvo + self.nfvo_client.is_local = True + result = self.nfvo_client.onboarded_package_content( + self.context, SAMPLE_VNFD_ID) + self.assertIsNone(result) + + # external nfvo + self.nfvo_client.is_local = False + mock_request.return_value = (requests.Response(), 'test') + result = self.nfvo_client.onboarded_package_content( + self.context, SAMPLE_VNFD_ID) + self.assertEqual('test', result) + + @mock.patch.object(local_nfvo.LocalNfvo, 'grant') + @mock.patch.object(http_client.HttpClient, 'do_request') + def test_grant(self, mock_request, mock_grant): + # local nfvo + self.nfvo_client.is_local = True + mock_grant.return_value = objects.GrantV1.from_dict(_grant_res) + grant_req = objects.GrantRequestV1.from_dict(_inst_grant_req_example) + grant_res = self.nfvo_client.grant(self.context, grant_req) + result = grant_res.to_dict() + self.assertIsNotNone(result['addResources']) + self.assertEqual('44fd5841-ce98-4d54-a828-6ef51f941c8a', result[ + 'vimAssets']['softwareImages'][0]['vimSoftwareImageId']) + + # external nfvo + self.nfvo_client.is_local = False + mock_request.return_value = (requests.Response(), _grant_res) + grant_res = self.nfvo_client.grant(self.context, grant_req) + result = grant_res.to_dict() + self.assertIsNotNone(result['addResources']) + self.assertEqual('44fd5841-ce98-4d54-a828-6ef51f941c8a', result[ + 'vimAssets']['softwareImages'][0]['vimSoftwareImageId']) + + @mock.patch.object(objects.base.TackerPersistentObject, 'get_all') + @mock.patch.object(subsc_utils, 'send_notification') + @mock.patch.object(local_nfvo.LocalNfvo, 'recv_inst_create_notification') + def test_send_inst_create_notification( + self, mock_recv, mock_send, mock_subscs): + self.nfvo_client.is_local = True + inst = objects.VnfInstanceV2(id='test-instance') + mock_subscs.return_value = [objects.LccnSubscriptionV2(id='subsc-1')] + self.nfvo_client.send_inst_create_notification( + self.context, inst, 'http://127.0.0.1:9890') + self.assertEqual(1, mock_recv.call_count) + self.assertEqual(1, mock_send.call_count) + + @mock.patch.object(objects.base.TackerPersistentObject, 'get_all') + @mock.patch.object(subsc_utils, 'send_notification') + @mock.patch.object(local_nfvo.LocalNfvo, 'recv_inst_delete_notification') + def test_send_inst_delete_notification( + self, mock_recv, mock_send, mock_subscs): + self.nfvo_client.is_local = True + inst = objects.VnfInstanceV2(id='test-instance') + mock_subscs.return_value = [objects.LccnSubscriptionV2(id='subsc-1')] + self.nfvo_client.send_inst_delete_notification( + self.context, inst, 'http://127.0.0.1:9890') + self.assertEqual(1, mock_recv.call_count) + self.assertEqual(1, mock_send.call_count) + + @mock.patch.object(objects.base.TackerPersistentObject, 'get_all') + @mock.patch.object(subsc_utils, 'send_notification') + @mock.patch.object(local_nfvo.LocalNfvo, 'recv_lcmocc_notification') + def test_send_lcmocc_notification(self, mock_recv, mock_send, mock_subscs): + inst = objects.VnfInstanceV2(id='test-instance') + lcmocc = objects.VnfLcmOpOccV2.from_dict(_lcmocc_inst_value) + mock_subscs.return_value = [objects.LccnSubscriptionV2( + id='subsc-1', verbosity='FULL')] + self.nfvo_client.send_lcmocc_notification( + self.context, lcmocc, inst, 'http://127.0.0.1:9890') + self.assertEqual(1, mock_recv.call_count) + self.assertEqual(1, mock_send.call_count) diff --git a/tacker/tests/unit/sol_refactored/samples/sample1/BaseHOT/error/ut_sample1.yaml b/tacker/tests/unit/sol_refactored/samples/sample1/BaseHOT/error/ut_sample1.yaml new file mode 100644 index 000000000..ed0b10e18 --- /dev/null +++ b/tacker/tests/unit/sol_refactored/samples/sample1/BaseHOT/error/ut_sample1.yaml @@ -0,0 +1,122 @@ +heat_template_version: 2013-05-23 +description: 'Simple Base HOT for Sample VNF' + +parameters: + nfv: + type: json + +resources: + VDU1_scale_group: + type: OS::Heat::AutoScalingGroup + properties: + min_size: 1 + max_size: 3 + desired_capacity: 1 + resource: + type: VDU1.yaml + properties: + flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] } + image: { get_param: [ nfv, VDU, VirtualStorage, vcImageId ] } + net1: { get_param: [ nfv, CP, VDU1_CP1, network ] } + net2: { get_param: [ nfv, CP, VDU1_CP2, network ] } + subnet: { get_param: [nfv, CP, VDU1_CP2, fixed_ips, 0, subnet ]} + net3: { get_resource: internalVL1 } + net4: { get_resource: internalVL2 } + net5: { get_resource: internalVL3 } + net6: { get_param: { nfv, CP, VDU1_CP6, network } } + net7: { get_param: [ nfv, CP, VDU1_CP7 ] } + net8: { get_param: [ nfv_1, CP, VDU1_CP8, network ] } + affinity: { get_resource: nfvi_node_affinity } + + VDU2: + type: OS::Nova::Server + properties: + flavor: { get_param: [ nfv, VDU, VDU2, computeFlavourId ] } + image: { get_param: [ nfv, VDU, VDU2, vcImageId] } + networks: + - port: + get_resource: VDU2_CP1 + - port: + get_resource: VDU2_CP2 + - port: + get_resource: VDU2_CP3 + - port: + get_resource: VDU2_CP4 + - port: + get_resource: VDU2_CP5 + scheduler_hints: + group: {get_resource: nfvi_node_affinity } + +# extVL with FixedIP + VDU2_CP1: + type: OS::Neutron::Port + properties: + network: { get_param: [ nfv, CP, VDU2_CP1, network ] } + fixed_ips: + - ip_address: { get_param: [nfv, CP, VDU2_CP1, fixed_ips, 0, ip_address]} + +# extvVL with FixedIP and Subnet + VDU2_CP2: + type: OS::Neutron::Port + properties: + network: { get_param: [ nfv, CP, VDU2_CP2, network ] } + fixed_ips: + - ip_address: { get_param: [nfv, CP, VDU2_CP2, fixed_ips, 0, ip_address]} + subnet: { get_param: [nfv, CP, VDU2_CP2, fixed_ips, 0, subnet]} + + VDU2_CP3: + type: OS::Neutron::Port + properties: +# replace the following line to VL's ID when extmanagedVLs are specified in instantiatevnfrequest + network: { get_resource: internalVL1 } + + VDU2_CP4: + type: OS::Neutron::Port + properties: +# replace the following line to VL's ID when extmanagedVLs are specified in instantiatevnfrequest + network: { get_resource: internalVL2 } + + VDU2_CP5: + type: OS::Neutron::Port + properties: +# replace the following line to VL's ID when extmanagedVLs are specified in instantiatevnfrequest + network: { get_resource: internalVL3 } + +# delete the following lines when extmanagedVLs are specified in instantiatevnfrequest + internalVL1: + type: OS::Neutron::Net + internalVL2: + type: OS::Neutron::Net + internalVL3: + 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 + internalVL3_subnet: + type: OS::Neutron::Subnet + properties: + ip_version: 4 + network: + get_resource: internalVL3 + cidr: 192.168.5.0/24 + + nfvi_node_affinity: + type: OS::Nova::ServerGroup + properties: + name: nfvi_node_affinity + policies: [ 'affinity' ] + +outputs: {} diff --git a/tacker/tests/unit/sol_refactored/samples/sample1/BaseHOT/simple/ut_sample1.yaml b/tacker/tests/unit/sol_refactored/samples/sample1/BaseHOT/simple/ut_sample1.yaml index 50b94f452..fa22696eb 100644 --- a/tacker/tests/unit/sol_refactored/samples/sample1/BaseHOT/simple/ut_sample1.yaml +++ b/tacker/tests/unit/sol_refactored/samples/sample1/BaseHOT/simple/ut_sample1.yaml @@ -11,18 +11,22 @@ resources: properties: min_size: 1 max_size: 3 - desired_capacity: 1 + desired_capacity: { get_param: [ nfv, VDU, VDU1, desired_capacity ] } resource: type: VDU1.yaml properties: flavor: { get_param: [ nfv, VDU, VDU1, computeFlavourId ] } image: { get_param: [ nfv, VDU, VirtualStorage, vcImageId ] } + zone: { get_param: [ nfv, VDU, VDU1, locationConstraints] } net1: { get_param: [ nfv, CP, VDU1_CP1, network ] } net2: { get_param: [ nfv, CP, VDU1_CP2, network ] } subnet: { get_param: [nfv, CP, VDU1_CP2, fixed_ips, 0, subnet ]} net3: { get_resource: internalVL1 } net4: { get_resource: internalVL2 } net5: { get_resource: internalVL3 } + net6: { get_param: { nfv, CP, VDU1_CP6, network } } + net7: { get_param: [ nfv, CP, VDU1_CP7 ] } + net8: { get_param: [ nfv_1, CP, VDU1_CP8, network ] } affinity: { get_resource: nfvi_node_affinity } VDU2: diff --git a/tacker/tests/unit/sol_refactored/samples/sample1/Definitions/ut_sample1_df_simple_error.yaml b/tacker/tests/unit/sol_refactored/samples/sample1/Definitions/ut_sample1_df_simple_error.yaml new file mode 100644 index 000000000..37b1f4646 --- /dev/null +++ b/tacker/tests/unit/sol_refactored/samples/sample1/Definitions/ut_sample1_df_simple_error.yaml @@ -0,0 +1,310 @@ +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_sample2_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: error + requirements: + virtual_link_external1_1: [ VDU1_CP1, virtual_link ] + virtual_link_external1_2: [ VDU2_CP1, virtual_link ] + virtual_link_external2_1: [ VDU1_CP2, virtual_link ] + virtual_link_external2_2: [ VDU2_CP2, virtual_link ] + + node_templates: + + VDU1: + type: tosca.nodes.nfv.Vdu.Compute + properties: + name: VDU1 + description: VDU1 compute node + vdu_profile: + min_number_of_instances: 1 + max_number_of_instances: 3 + capabilities: + virtual_compute: + properties: + requested_additional_capabilities: + properties: + requested_additional_capability_name: m1.tiny + support_mandatory: true + target_performance_parameters: + entry_schema: test + virtual_memory: + virtual_mem_size: 512 MB + virtual_cpu: + num_virtual_cpu: 1 + virtual_local_storage: + - size_of_storage: 3 GB + requirements: + - virtual_storage: VirtualStorage + + VNF: + type: company.provider.VNF + properties: + flavour_description: A simple flavour + interfaces: + Vnflcm: + instantiate_start: + implementation: + instantiate_end: + implementation: sample-script + terminate_start: + implementation: error-script + terminate_end: + implementation: sample-script + scale_start: [ ] + artifacts: + sample-script: + description: Sample script + type: tosca.artifacts.Implementation.Python + file: + error-script: + description: Sample script + type: tosca.artifacts.Implementation.Error + file: 'test/sample_script.sh' + + VDU2: + type: tosca.nodes.nfv.Vdu.Compute + properties: + name: VDU1 + description: VDU1 compute node + vdu_profile: + min_number_of_instances: 1 + max_number_of_instances: 3 + capabilities: + virtual_compute: + properties: + requested_additional_capabilities: + properties: + requested_additional_capability_name: m1.tiny + support_mandatory: true + target_performance_parameters: + entry_schema: test + virtual_memory: + virtual_mem_size: 512 MB + virtual_cpu: + num_virtual_cpu: 1 + virtual_local_storage: + - size_of_storage: 3 GB + requirements: + - virtual_storage: VirtualStorage-2 + + VirtualStorage: + type: tosca.nodes.nfv.Vdu.VirtualBlockStorage + properties: + virtual_block_storage_data: + size_of_storage: 1gb GB + rdma_enabled: true + + VirtualStorage-2: + type: tosca.nodes.nfv.Vdu.VirtualBlockStorage + properties: + virtual_block_storage_data: + size_of_storage: 1gb GB + rdma_enabled: true + sw_image_data: + name: image-1.0.0-x86_64-disk + version: '1.0.0' + checksum: + algorithm: sha-256 + hash: a8dd75ecffd4cdd96072d60c2237b448e0c8b2bc94d57f10fdbc8c481d9005b8 + 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 + + VDU1_CP3: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 2 + requirements: + - virtual_binding: VDU1 + - virtual_link: internalVL1 + + VDU1_CP4: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 3 + requirements: + - virtual_binding: VDU1 + - virtual_link: internalVL2 + + VDU1_CP5: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 4 + requirements: + - virtual_binding: VDU1 + - virtual_link: internalVL3 + + 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 + + VDU2_CP3: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 2 + requirements: + - virtual_binding: VDU2 + - virtual_link: internalVL1 + + VDU2_CP4: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 3 + requirements: + - virtual_binding: VDU2 + - virtual_link: internalVL2 + + VDU2_CP5: + type: tosca.nodes.nfv.VduCp + properties: + layer_protocols: [ ipv4 ] + order: 4 + requirements: + - virtual_binding: VDU2 + - virtual_link: internalVL3 + + 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 + + internalVL3: + type: tosca.nodes.nfv.VnfVirtualLink + properties: + connectivity_type: + layer_protocols: [ ipv4 ] + description: Internal 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.5.0/24 + + groups: + affinityOrAntiAffinityGroup1: + type: tosca.groups.nfv.PlacementGroup + members: [ VDU1, VDU2 ] + + policies: + policy_antiaffinity_group: + type: tosca.policies.nfv.AntiAffinityRule + targets: [ affinityOrAntiAffinityGroup1 ] + properties: + scope: nfvi_node + + policy_affinity_group: + type: tosca.policies.nfv.AffinityRule + targets: [ affinityOrAntiAffinityGroup1 ] + properties: + scope: error + + policy_affinity_group_2: + type: tosca.policies.nfv.AffinityRule + targets: [VDU3] + properties: + scope: zone diff --git a/tacker/tests/unit/sol_refactored/samples/sample1/TOSCA-Metadata/TOSCA.meta b/tacker/tests/unit/sol_refactored/samples/sample1/TOSCA-Metadata/TOSCA.meta index 2ccc96c78..e7ec50bf1 100644 --- a/tacker/tests/unit/sol_refactored/samples/sample1/TOSCA-Metadata/TOSCA.meta +++ b/tacker/tests/unit/sol_refactored/samples/sample1/TOSCA-Metadata/TOSCA.meta @@ -2,3 +2,4 @@ TOSCA-Meta-File-Version: 1.0 CSAR-Version: 1.1 Created-by: Onboarding portal Entry-Definitions: Definitions/v2_sample2_top.vnfd.yaml +ETSI-Entry-Manifest: manifest.mf \ No newline at end of file diff --git a/tacker/tests/unit/sol_refactored/samples/sample1/manifest.mf b/tacker/tests/unit/sol_refactored/samples/sample1/manifest.mf new file mode 100644 index 000000000..533618cec --- /dev/null +++ b/tacker/tests/unit/sol_refactored/samples/sample1/manifest.mf @@ -0,0 +1,7 @@ +Source: Scripts/install.sh +Algorithm: SHA-256 +Hash: 27bbdb25d8f4ed6d07d6f6581b86515e8b2f0059b236ef7b6f50d6674b34f02a + +Source: Files/kubernetes/deployment.yaml +Algorithm: SHA-256 +Hash: e23cc3433835cea32ce790b4823313dc6d0744dce02e27b1b339c87ee993b8c2 \ No newline at end of file