From 3795395a62a91bf012a0233883ba16ca1cee44e0 Mon Sep 17 00:00:00 2001 From: Aldinson Esto Date: Fri, 18 Sep 2020 22:54:10 +0900 Subject: [PATCH] Fix for Multi BaseHot VNF cannot be instantiated This fix will allow Instantiation to be successful when Multiple Base Hot is defined in the VNFD Package. Closes-Bug: # 1895830 https://bugs.launchpad.net/tacker/+bug/1895830 Change-Id: I2d5e677820a4978d609ab492aa64cdc5269fd5c9 (cherry picked from commit 59e166b62a11ee4adfc92abea91f0224a4789476) --- .../openstack/test_openstack_driver.py | 153 +++++++++-- .../vnfm/lcm_user_data/utils/test_utils.py | 9 +- tacker/vnflcm/utils.py | 58 +++++ tacker/vnflcm/vnflcm_driver.py | 6 +- .../vnfm/infra_drivers/openstack/openstack.py | 47 +++- tacker/vnfm/lcm_user_data/utils.py | 243 ++++++++++++++++-- 6 files changed, 455 insertions(+), 61 deletions(-) diff --git a/tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack_driver.py b/tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack_driver.py index 4d9ed6519..8b08ef3a1 100644 --- a/tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack_driver.py +++ b/tacker/tests/unit/vnfm/infra_drivers/openstack/test_openstack_driver.py @@ -134,8 +134,15 @@ class TestOpenStack(base.FixturedTestCase): yaml_file_dict = yaml.safe_load(f) return yaml_file_dict + @mock.patch('tacker.vnfm.infra_drivers.openstack.openstack' + '.OpenStack._format_base_hot') + @mock.patch('tacker.vnflcm.utils._get_vnflcm_interface') + @mock.patch('tacker.vnflcm.utils.get_base_nest_hot_dict') @mock.patch('tacker.common.clients.OpenstackClients') - def test_create_normal(self, mock_OpenstackClients_heat): + def test_create_normal(self, mock_OpenstackClients_heat, + mock_get_base_hot_dict, + mock_get_vnflcm_interface, + mock_format_base_hot): vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() @@ -148,14 +155,20 @@ class TestOpenStack(base.FixturedTestCase): 'instantiate_vnf_request_lcm_userdata.json') inst_req_info_test.additional_params = test_json['additionalParams'] inst_req_info_test.ext_virtual_links = None + inst_req_info_test.flavour_id = test_json['flavourId'] vnf_resource = type('', (), {}) vnf_resource.resource_identifier = constants.INVALID_UUID grant_info_test = {'vdu_name': {vnf_resource}} + nested_hot_dict = {'parameters': {'vnf': 'test'}} + mock_get_base_hot_dict.return_value = \ + self._read_file(), nested_hot_dict + vnf_instance = fd_utils.get_vnf_instance_object() self.openstack.create(self.plugin, self.context, vnf, self.auth_attr, inst_req_info=inst_req_info_test, vnf_package_path=vnf_package_path_test, base_hot_dict=base_hot_dict_test, - grant_info=grant_info_test) + grant_info=grant_info_test, + vnf_instance=vnf_instance) @mock.patch('tacker.common.clients.OpenstackClients') def test_create_heat_stack(self, mock_OpenstackClients_heat): @@ -218,8 +231,10 @@ class TestOpenStack(base.FixturedTestCase): inst_req_info=inst_req_info_test, grant_info=grant_info_test) + @mock.patch('tacker.vnflcm.utils.get_base_nest_hot_dict') @mock.patch('tacker.common.clients.OpenstackClients') - def test_create_userdata_null(self, mock_OpenstackClients_heat): + def test_create_userdata_null(self, mock_OpenstackClients_heat, + mock_get_base_hot_dict): vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() @@ -235,17 +250,25 @@ class TestOpenStack(base.FixturedTestCase): inst_req_info_test.additional_params = test_json['additionalParams'] inst_req_info_test.ext_virtual_links = None + inst_req_info_test.flavour_id = test_json['flavourId'] grant_info_test = None + nested_hot_dict = {'test': 'test'} + mock_get_base_hot_dict.return_value = \ + self._read_file(), nested_hot_dict + vnf_instance = fd_utils.get_vnf_instance_object() self.assertRaises(vnfm.LCMUserDataFailed, self.openstack.create, self.plugin, self.context, vnf, self.auth_attr, base_hot_dict_test, vnf_package_path_test, inst_req_info=inst_req_info_test, - grant_info=grant_info_test) + grant_info=grant_info_test, + vnf_instance=vnf_instance) + @mock.patch('tacker.vnflcm.utils.get_base_nest_hot_dict') @mock.patch('tacker.common.clients.OpenstackClients') - def test_create_userdataclass_null(self, mock_OpenstackClients_heat): + def test_create_userdataclass_null(self, mock_OpenstackClients_heat, + mock_get_base_hot_dict): vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() @@ -261,17 +284,25 @@ class TestOpenStack(base.FixturedTestCase): inst_req_info_test.additional_params = test_json['additionalParams'] inst_req_info_test.ext_virtual_links = None + inst_req_info_test.flavour_id = test_json['flavourId'] grant_info_test = None + nested_hot_dict = {'test': 'test'} + mock_get_base_hot_dict.return_value = \ + self._read_file(), nested_hot_dict + vnf_instance = fd_utils.get_vnf_instance_object() self.assertRaises(vnfm.LCMUserDataFailed, self.openstack.create, self.plugin, self.context, vnf, self.auth_attr, base_hot_dict_test, vnf_package_path_test, inst_req_info=inst_req_info_test, - grant_info=grant_info_test) + grant_info=grant_info_test, + vnf_instance=vnf_instance) + @mock.patch('tacker.vnflcm.utils.get_base_nest_hot_dict') @mock.patch('tacker.common.clients.OpenstackClients') - def test_create_import_module_exception(self, mock_OpenstackClients_heat): + def test_create_import_module_exception(self, mock_OpenstackClients_heat, + mock_get_base_hot_dict): vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() @@ -284,7 +315,12 @@ class TestOpenStack(base.FixturedTestCase): 'instantiate_vnf_request_lcm_userdata.json') inst_req_info_test.additional_params = test_json['additionalParams'] inst_req_info_test.ext_virtual_links = None + inst_req_info_test.flavour_id = test_json['flavourId'] grant_info_test = None + nested_hot_dict = {'test': 'test'} + mock_get_base_hot_dict.return_value = \ + self._read_file(), nested_hot_dict + vnf_instance = fd_utils.get_vnf_instance_object() with mock.patch.object(importlib, 'import_module') as mock_importlib: mock_importlib.side_effect = Exception('Test Exception') self.assertRaises(vnfm.LCMUserDataFailed, @@ -293,10 +329,13 @@ class TestOpenStack(base.FixturedTestCase): self.auth_attr, base_hot_dict_test, vnf_package_path_test, inst_req_info=inst_req_info_test, - grant_info=grant_info_test) + grant_info=grant_info_test, + vnf_instance=vnf_instance) + @mock.patch('tacker.vnflcm.utils.get_base_nest_hot_dict') @mock.patch('tacker.common.clients.OpenstackClients') - def test_create_getattr_none(self, mock_OpenstackClients_heat): + def test_create_getattr_none(self, mock_OpenstackClients_heat, + mock_get_base_hot_dict): vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() @@ -309,7 +348,12 @@ class TestOpenStack(base.FixturedTestCase): 'instantiate_vnf_request_lcm_userdata.json') inst_req_info_test.additional_params = test_json['additionalParams'] inst_req_info_test.ext_virtual_links = None + inst_req_info_test.flavour_id = test_json['flavourId'] grant_info_test = None + nested_hot_dict = {'test': 'test'} + mock_get_base_hot_dict.return_value = \ + self._read_file(), nested_hot_dict + vnf_instance = fd_utils.get_vnf_instance_object() with mock.patch.object(importlib, 'import_module') as mock_importlib: mock_importlib.return_value = None self.assertRaises(vnfm.LCMUserDataFailed, @@ -318,10 +362,13 @@ class TestOpenStack(base.FixturedTestCase): self.auth_attr, base_hot_dict_test, vnf_package_path_test, inst_req_info=inst_req_info_test, - grant_info=grant_info_test) + grant_info=grant_info_test, + vnf_instance=vnf_instance) + @mock.patch('tacker.vnflcm.utils.get_base_nest_hot_dict') @mock.patch('tacker.common.clients.OpenstackClients') - def test_create_missing_file(self, mock_OpenstackClients_heat): + def test_create_missing_file(self, mock_OpenstackClients_heat, + mock_get_base_hot_dict): vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() @@ -334,19 +381,27 @@ class TestOpenStack(base.FixturedTestCase): 'instantiate_vnf_request_lcm_userdata.json') inst_req_info_test.additional_params = test_json['additionalParams'] inst_req_info_test.ext_virtual_links = None + inst_req_info_test.flavour_id = test_json['flavourId'] vnf_resource = type('', (), {}) vnf_resource.resource_identifier = constants.INVALID_UUID grant_info_test = {'vdu_name': {vnf_resource}} + nested_hot_dict = {'test': 'test'} + mock_get_base_hot_dict.return_value = \ + self._read_file(), nested_hot_dict + vnf_instance = fd_utils.get_vnf_instance_object() self.assertRaises(vnfm.LCMUserDataFailed, self.openstack.create, self.plugin, self.context, vnf, self.auth_attr, inst_req_info=inst_req_info_test, vnf_package_path=vnf_package_path_test, base_hot_dict=base_hot_dict_test, - grant_info=grant_info_test) + grant_info=grant_info_test, + vnf_instance=vnf_instance) + @mock.patch('tacker.vnflcm.utils.get_base_nest_hot_dict') @mock.patch('tacker.common.clients.OpenstackClients') - def test_create_return_none_dict(self, mock_OpenstackClients_heat): + def test_create_return_none_dict(self, mock_OpenstackClients_heat, + mock_get_base_hot_dict): vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() @@ -361,38 +416,54 @@ class TestOpenStack(base.FixturedTestCase): 'UserData/lcm_user_data_non_dict.py' inst_req_info_test.additional_params = test_json['additionalParams'] inst_req_info_test.ext_virtual_links = None + inst_req_info_test.flavour_id = test_json['flavourId'] vnf_resource = type('', (), {}) vnf_resource.resource_identifier = constants.INVALID_UUID grant_info_test = {'vdu_name': {vnf_resource}} + nested_hot_dict = {'test': 'test'} + mock_get_base_hot_dict.return_value = \ + self._read_file(), nested_hot_dict + vnf_instance = fd_utils.get_vnf_instance_object() self.assertRaises(vnfm.LCMUserDataFailed, self.openstack.create, self.plugin, self.context, vnf, self.auth_attr, inst_req_info=inst_req_info_test, vnf_package_path=vnf_package_path_test, base_hot_dict=base_hot_dict_test, - grant_info=grant_info_test) + grant_info=grant_info_test, + vnf_instance=vnf_instance) + @mock.patch('tacker.vnflcm.utils.get_base_nest_hot_dict') @mock.patch('tacker.common.clients.OpenstackClients') - def test_create_none_base_hot_dict(self, mock_OpenstackClients_heat): + def test_create_none_base_hot_dict(self, mock_OpenstackClients_heat, + mock_get_base_hot_dict): vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) vnf['placement_attr'] = {'region_name': 'dummy_region'} inst_req_info_test = type('', (), {}) test_json = self._json_load( 'instantiate_vnf_request_lcm_userdata.json') inst_req_info_test.additional_params = test_json['additionalParams'] + inst_req_info_test.flavour_id = test_json['flavourId'] base_hot_dict_test = None vnf_package_path_test = None grant_info_test = None + nested_hot_dict = {} + mock_get_base_hot_dict.return_value = \ + self._read_file(), nested_hot_dict + vnf_instance = fd_utils.get_vnf_instance_object() self.assertRaises(vnfm.LCMUserDataFailed, self.openstack.create, self.plugin, self.context, vnf, self.auth_attr, base_hot_dict_test, vnf_package_path_test, inst_req_info=inst_req_info_test, - grant_info=grant_info_test) + grant_info=grant_info_test, + vnf_instance=vnf_instance) + @mock.patch('tacker.vnflcm.utils.get_base_nest_hot_dict') @mock.patch('tacker.common.clients.OpenstackClients') - def test_create_invalid_user_data(self, mock_OpenstackClients_heat): + def test_create_invalid_user_data(self, mock_OpenstackClients_heat, + mock_get_base_hot_dict): vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() @@ -409,18 +480,26 @@ class TestOpenStack(base.FixturedTestCase): inst_req_info_test.additional_params = test_json['additionalParams'] inst_req_info_test.ext_virtual_links = None + inst_req_info_test.flavour_id = test_json['flavourId'] grant_info_test = None + nested_hot_dict = {'test': 'test'} + mock_get_base_hot_dict.return_value = \ + self._read_file(), nested_hot_dict + vnf_instance = fd_utils.get_vnf_instance_object() self.assertRaises(vnfm.LCMUserDataFailed, self.openstack.create, self.plugin, self.context, vnf, self.auth_attr, base_hot_dict_test, vnf_package_path_test, inst_req_info=inst_req_info_test, - grant_info=grant_info_test) + grant_info=grant_info_test, + vnf_instance=vnf_instance) + @mock.patch('tacker.vnflcm.utils.get_base_nest_hot_dict') @mock.patch('tacker.common.clients.OpenstackClients') def test_create_invalid_user_data_class(self, - mock_OpenstackClients_heat): + mock_OpenstackClients_heat, + mock_get_base_hot_dict): vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() @@ -437,18 +516,25 @@ class TestOpenStack(base.FixturedTestCase): inst_req_info_test.additional_params = test_json['additionalParams'] inst_req_info_test.ext_virtual_links = None + inst_req_info_test.flavour_id = test_json['flavourId'] grant_info_test = None + nested_hot_dict = {'test': 'test'} + mock_get_base_hot_dict.return_value = \ + self._read_file(), nested_hot_dict + vnf_instance = fd_utils.get_vnf_instance_object() self.assertRaises(vnfm.LCMUserDataFailed, self.openstack.create, self.plugin, self.context, vnf, self.auth_attr, base_hot_dict_test, vnf_package_path_test, inst_req_info=inst_req_info_test, - grant_info=grant_info_test) + grant_info=grant_info_test, + vnf_instance=vnf_instance) + @mock.patch('tacker.vnflcm.utils.get_base_nest_hot_dict') @mock.patch('tacker.common.clients.OpenstackClients') def test_create_lcm_user_data_and_user_data_class_no_value(self, - mock_OpenstackClients_heat): + mock_OpenstackClients_heat, mock_get_base_hot_dict): vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() @@ -466,16 +552,22 @@ class TestOpenStack(base.FixturedTestCase): inst_req_info_test.additional_params = test_json['additionalParams'] inst_req_info_test.ext_virtual_links = test_json['extVirtualLinks'] + inst_req_info_test.flavour_id = test_json['flavourId'] vnf_resource = type('', (), {}) vnf_resource.resource_identifier = constants.INVALID_UUID grant_info_test = {'vdu_name': {vnf_resource}} + nested_hot_dict = {'test': 'test'} + mock_get_base_hot_dict.return_value = \ + self._read_file(), nested_hot_dict + vnf_instance = fd_utils.get_vnf_instance_object() self.assertRaises(vnfm.LCMUserDataFailed, self.openstack.create, self.plugin, self.context, vnf, self.auth_attr, inst_req_info=inst_req_info_test, vnf_package_path=vnf_package_path_test, base_hot_dict=base_hot_dict_test, - grant_info=grant_info_test) + grant_info=grant_info_test, + vnf_instance=vnf_instance) @mock.patch('tacker.common.clients.OpenstackClients') def test_create_lcm_user_data_and_user_data_class_none(self, @@ -523,8 +615,10 @@ class TestOpenStack(base.FixturedTestCase): vnf_package_path=vnf_package_path_test, inst_req_info=inst_req_info_test) + @mock.patch('tacker.vnflcm.utils.get_base_nest_hot_dict') @mock.patch('tacker.common.clients.OpenstackClients') - def test_create_instance_exception(self, mock_OpenstackClients_heat): + def test_create_instance_exception(self, mock_OpenstackClients_heat, + mock_get_base_hot_dict): vnf = utils.get_dummy_vnf(instance_id=self.instance_uuid) vnf['placement_attr'] = {'region_name': 'dummy_region'} base_hot_dict_test = self._read_file() @@ -539,16 +633,22 @@ class TestOpenStack(base.FixturedTestCase): 'UserData/lcm_user_data_invalid_script.py' inst_req_info_test.additional_params = test_json['additionalParams'] inst_req_info_test.ext_virtual_links = None + inst_req_info_test.flavour_id = test_json['flavourId'] vnf_resource = type('', (), {}) vnf_resource.resource_identifier = constants.INVALID_UUID grant_info_test = {'vdu_name': {vnf_resource}} + nested_hot_dict = {'test': 'test'} + mock_get_base_hot_dict.return_value = \ + self._read_file(), nested_hot_dict + vnf_instance = fd_utils.get_vnf_instance_object() self.assertRaises(vnfm.LCMUserDataFailed, self.openstack.create, self.plugin, self.context, vnf, self.auth_attr, inst_req_info=inst_req_info_test, vnf_package_path=vnf_package_path_test, base_hot_dict=base_hot_dict_test, - grant_info=grant_info_test) + grant_info=grant_info_test, + vnf_instance=vnf_instance) def test_create_wait(self): self._response_in_wait_until_stack_ready(["CREATE_IN_PROGRESS", @@ -1082,10 +1182,11 @@ class TestOpenStack(base.FixturedTestCase): self.requests_mock.register_uri( 'POST', url, json={'stack': fd_utils.get_dummy_stack()}, headers=self.json_headers) + vnf_instance = fd_utils.get_vnf_instance_object() instance_id = self.openstack.instantiate_vnf( - self.context, None, vnfd_dict, vim_connection_info, - inst_req_info, grant_response) + self.context, vnf_instance, vnfd_dict, vim_connection_info, + inst_req_info, grant_response, self.plugin) self.assertEqual(uuidsentinel.instance_id, instance_id) diff --git a/tacker/tests/unit/vnfm/lcm_user_data/utils/test_utils.py b/tacker/tests/unit/vnfm/lcm_user_data/utils/test_utils.py index 8a3aa1ced..1d611b2ac 100644 --- a/tacker/tests/unit/vnfm/lcm_user_data/utils/test_utils.py +++ b/tacker/tests/unit/vnfm/lcm_user_data/utils/test_utils.py @@ -51,7 +51,8 @@ class TestUtils(testtools.TestCase): return yaml_file_dict def test_create_initial_param_dict(self): - base_hot_dict = self._read_file("hot_lcm_user_data.yaml") + base_hot_dict = {} + base_hot_dict['resources'] = self._read_file("hot_lcm_user_data.yaml") initial_param_dict = utils.create_initial_param_dict(base_hot_dict) self.assertEqual(example_initial_param_dict, initial_param_dict) @@ -153,7 +154,8 @@ class TestUtils(testtools.TestCase): self.assertEqual({}, vdu_image_dict) def test_create_cpd_vl_dict(self): - base_hot_dict = {'resources': {'dummy_cpd_id': "101010_d"}} + base_hot_dict = \ + {'resources': {'resources': {'dummy_cpd_id': "101010_d"}}} inst_req_info = instantiate_vnf_req.InstantiateVnfRequest() ext_virtual_links_test_value = instantiate_vnf_req.ExtVirtualLinkData() ext_virtual_links_test_value.resource_id = 'dummy_resource_id' @@ -169,7 +171,8 @@ class TestUtils(testtools.TestCase): self.assertEqual({'dummy_cpd_id': 'dummy_resource_id'}, cpd_vl_dict) def test_create_cpd_vl_dict_no_cp_resource(self): - base_hot_dict = {'resources': {'dummy_cpd_id': "101010_d"}} + base_hot_dict = \ + {'resources': {'resources': {'dummy_cpd_id': "101010_d"}}} inst_req_info = instantiate_vnf_req.InstantiateVnfRequest() ext_virtual_links_test_value = instantiate_vnf_req.ExtVirtualLinkData() ext_virtual_links_test_value.resource_id = 'dummy_resource_id' diff --git a/tacker/vnflcm/utils.py b/tacker/vnflcm/utils.py index 56e7ae346..dae900055 100644 --- a/tacker/vnflcm/utils.py +++ b/tacker/vnflcm/utils.py @@ -76,6 +76,36 @@ def _get_vnfd_dict(context, vnfd_id, flavour_id): return vnfd_dict +def _get_vnflcm_interface(context, interface, vnf_instance, flavour_id): + '''Gets the interface found in vnfd + + ... + node_templates: + VNF: + interfaces: + Vnflcm: + + ''' + interface_value = None + vnfd_dict = _get_vnfd_dict(context, vnf_instance.vnfd_id, flavour_id) + + if not isinstance(vnfd_dict, dict): + raise exceptions.InvalidContentType(msg="VNFD not valid") + + if vnfd_dict.get('topology_template'): + topology_template = vnfd_dict.get('topology_template') + if topology_template.get('node_templates'): + for a_val in topology_template.get('node_templates').values(): + if 'interfaces' in a_val.keys(): + interfaces = a_val.get('interfaces') + if interfaces.get('Vnflcm'): + vnflcm = interfaces.get('Vnflcm') + if vnflcm: + interface_value = vnflcm.get(interface) + + return interface_value + + def _build_affected_resources(vnf_instance, change_type=fields.ResourceChangeType.ADDED): '''build affected resources from vnf_instance instantiated info ''' @@ -1022,3 +1052,31 @@ def _get_base_hot_dict(context, vnfd_id): base_hot_dict = yaml.safe_load(open(source_file_path)) LOG.debug("Loaded base hot: %s", base_hot_dict) return base_hot_dict + + +def get_base_nest_hot_dict(context, flavour_id, vnfd_id): + vnf_package_id = _get_vnf_package_id(context, vnfd_id) + vnf_package_base_path = cfg.CONF.vnf_package.vnf_package_csar_path + vnf_package_csar_path = vnf_package_base_path + '/' + vnf_package_id + base_hot_dir = 'BaseHOT' + ext = [".yaml", ".yml"] + + base_hot_path = vnf_package_csar_path + '/' + \ + base_hot_dir + '/' + flavour_id + base_hot_dict = None + nested_hot_path = base_hot_path + '/nested' + nested_hot_dict = {} + if os.path.exists(base_hot_path): + for file in os.listdir(base_hot_path): + if file.endswith(tuple(ext)): + source_file_path = os.path.join(base_hot_path, file) + base_hot_dict = yaml.safe_load(open(source_file_path)) + if os.path.exists(nested_hot_path): + for file in os.listdir(nested_hot_path): + if file.endswith(tuple(ext)): + source_file_path = os.path.join(nested_hot_path, file) + nested_hot = yaml.safe_load(open(source_file_path)) + nested_hot_dict[file] = nested_hot + LOG.debug("Loaded base hot: %s", base_hot_dict) + LOG.debug("Loaded nested_hot_dict: %s", nested_hot_dict) + return base_hot_dict, nested_hot_dict diff --git a/tacker/vnflcm/vnflcm_driver.py b/tacker/vnflcm/vnflcm_driver.py index c77f773f7..f97ea6a52 100644 --- a/tacker/vnflcm/vnflcm_driver.py +++ b/tacker/vnflcm/vnflcm_driver.py @@ -136,8 +136,10 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver): vim_connection_info, instantiate_vnf_req): vnfd_dict = vnflcm_utils._get_vnfd_dict(context, vnf_instance.vnfd_id, instantiate_vnf_req.flavour_id) - base_hot_dict = vnflcm_utils._get_base_hot_dict( - context, vnf_instance.vnfd_id) + base_hot_dict, nested_hot_dict = vnflcm_utils. \ + get_base_nest_hot_dict(context, + instantiate_vnf_req.flavour_id, + vnf_instance.vnfd_id) vnf_package_path = None if base_hot_dict is not None: vnf_package_path = vnflcm_utils._get_vnf_package_path( diff --git a/tacker/vnfm/infra_drivers/openstack/openstack.py b/tacker/vnfm/infra_drivers/openstack/openstack.py index fd4946a92..9293e258f 100644 --- a/tacker/vnfm/infra_drivers/openstack/openstack.py +++ b/tacker/vnfm/infra_drivers/openstack/openstack.py @@ -14,12 +14,13 @@ # License for the specific language governing permissions and limitations # under the License. - +import copy import eventlet import importlib import os import sys import time +import yaml from collections import OrderedDict from oslo_config import cfg @@ -28,8 +29,6 @@ from oslo_serialization import jsonutils from oslo_utils import encodeutils from oslo_utils import excutils from oslo_utils import uuidutils -import yaml - from tacker._i18n import _ from tacker.common import exceptions from tacker.common import log @@ -39,6 +38,7 @@ from tacker.extensions import vnfm from tacker import objects from tacker.objects import fields from tacker.tosca.utils import represent_odict +from tacker.vnflcm import utils as vnflcm_utils from tacker.vnfm.infra_drivers import abstract_driver from tacker.vnfm.infra_drivers.openstack import constants as infra_cnst from tacker.vnfm.infra_drivers.openstack import glance_client as gc @@ -48,6 +48,7 @@ from tacker.vnfm.infra_drivers.openstack import vdu from tacker.vnfm.infra_drivers import scale_driver from tacker.vnfm.lcm_user_data.constants import USER_DATA_TIMEOUT + eventlet.monkey_patch(time=True) LOG = logging.getLogger(__name__) @@ -89,6 +90,8 @@ OUTPUT_PREFIX = 'mgmt_ip-' ALARMING_POLICY = 'tosca.policies.tacker.Alarming' SCALING_POLICY = 'tosca.policies.tacker.Scaling' +NOVA_SERVER_RESOURCE = "OS::Nova::Server" + def get_scaling_policy_name(action, policy_name): return '%s_scale_%s' % (policy_name, action) @@ -117,7 +120,8 @@ class OpenStack(abstract_driver.VnfAbstractDriver, @log.log def create(self, plugin, context, vnf, auth_attr, base_hot_dict=None, vnf_package_path=None, - inst_req_info=None, grant_info=None): + inst_req_info=None, grant_info=None, + vnf_instance=None): LOG.debug('vnf %s', vnf) region_name = vnf.get('placement_attr', {}).get('region_name', None) heatclient = hc.HeatClient(auth_attr, region_name) @@ -138,10 +142,20 @@ class OpenStack(abstract_driver.VnfAbstractDriver, if user_data_path is not None and user_data_class is not None: LOG.info('Execute user data and create heat-stack.') + base_hot_dict, nested_hot_dict = vnflcm_utils. \ + get_base_nest_hot_dict(context, + inst_req_info.flavour_id, + vnf_instance.vnfd_id) if base_hot_dict is None: error_reason = _("failed to get Base HOT.") raise vnfm.LCMUserDataFailed(reason=error_reason) + if base_hot_dict is None: + nested_hot_dict = {} + + for name, hot in nested_hot_dict.items(): + vnf['attributes'][name] = self._format_base_hot(hot) + vnfd_str = vnf['vnfd']['attributes']['vnfd'] vnfd_dict = yaml.safe_load(vnfd_str) LOG.debug('VNFD: %s', vnfd_dict) @@ -175,10 +189,13 @@ class OpenStack(abstract_driver.VnfAbstractDriver, # Set the timeout and execute the UserData script. hot_param_dict = None + param_base_hot_dict = copy.deepcopy(nested_hot_dict) + param_base_hot_dict['heat_template'] = base_hot_dict with eventlet.timeout.Timeout(USER_DATA_TIMEOUT, False): try: hot_param_dict = klass.instantiate( - base_hot_dict, vnfd_dict, inst_req_info, grant_info) + param_base_hot_dict, vnfd_dict, + inst_req_info, grant_info) except Exception: raise finally: @@ -196,9 +213,13 @@ class OpenStack(abstract_driver.VnfAbstractDriver, "is not in dict format.") raise vnfm.LCMUserDataFailed(reason=error_reason) + # Add stack param to vnf_attributes + vnf['attributes'].update({'stack_param': str(hot_param_dict)}) + # Create heat-stack with BaseHOT and parameters stack = self._create_stack_with_user_data( - heatclient, vnf, base_hot_dict, hot_param_dict) + heatclient, vnf, base_hot_dict, + nested_hot_dict, hot_param_dict) elif user_data_path is None and user_data_class is None: LOG.info('Execute heat-translator and create heat-stack.') @@ -230,13 +251,18 @@ class OpenStack(abstract_driver.VnfAbstractDriver, @log.log def _create_stack_with_user_data(self, heatclient, vnf, - base_hot_dict, hot_param_dict): + base_hot_dict, nested_hot_dict, + hot_param_dict): fields = {} fields['stack_name'] = ("vnflcm_" + vnf["id"]) fields['template'] = self._format_base_hot(base_hot_dict) fields['parameters'] = hot_param_dict fields['timeout_mins'] = ( self.STACK_RETRIES * self.STACK_RETRY_WAIT // 60) + if nested_hot_dict: + fields['files'] = {} + for name, value in nested_hot_dict.items(): + fields['files'][name] = self._format_base_hot(value) LOG.debug('fields: %s', fields) LOG.debug('template: %s', fields['template']) @@ -771,7 +797,7 @@ class OpenStack(abstract_driver.VnfAbstractDriver, def instantiate_vnf(self, context, vnf_instance, vnfd_dict, vim_connection_info, instantiate_vnf_req, - grant_response, + grant_response, plugin, base_hot_dict=None, vnf_package_path=None): access_info = vim_connection_info.access_info region_name = access_info.get('region') @@ -779,10 +805,11 @@ class OpenStack(abstract_driver.VnfAbstractDriver, placement_attr.update({'region_name': region_name}) vnfd_dict['placement_attr'] = placement_attr - instance_id = self.create(None, context, vnfd_dict, + instance_id = self.create(plugin, context, vnfd_dict, access_info, base_hot_dict, vnf_package_path, inst_req_info=instantiate_vnf_req, - grant_info=grant_response) + grant_info=grant_response, + vnf_instance=vnf_instance) vnfd_dict['instance_id'] = instance_id return instance_id diff --git a/tacker/vnfm/lcm_user_data/utils.py b/tacker/vnfm/lcm_user_data/utils.py index 64549c5c7..6097b1167 100644 --- a/tacker/vnfm/lcm_user_data/utils.py +++ b/tacker/vnfm/lcm_user_data/utils.py @@ -33,7 +33,11 @@ LOG = logging.getLogger(__name__) HOT_NOVA_SERVER = 'OS::Nova::Server' HOT_NOVA_FLAVOR = 'OS::Nova::Flavor' HOT_NEUTRON_PORT = 'OS::Neutron::Port' -SUPPORTED_HOT_TYPE = [HOT_NOVA_SERVER, HOT_NOVA_FLAVOR, HOT_NEUTRON_PORT] +AUTO_SCALING_GROUP = 'OS::Heat::AutoScalingGroup' +HOT_CINDER_VOLUME = 'OS::Cinder::Volume' +SUPPORTED_HOT_TYPE = [HOT_NOVA_SERVER, HOT_NOVA_FLAVOR, + HOT_NEUTRON_PORT, HOT_CINDER_VOLUME] +PORT_SERVER_TYPE = [HOT_NOVA_SERVER, HOT_NEUTRON_PORT, HOT_CINDER_VOLUME] def create_initial_param_dict(base_hot_dict): @@ -55,20 +59,123 @@ def create_initial_param_dict(base_hot_dict): } } - resources = base_hot_dict.get('resources', {}) - for resource_name, resource_val in resources.items(): - resource_type = resource_val.get('type') - if resource_type in SUPPORTED_HOT_TYPE: - resource_props = resource_val.get('properties', {}) - for prop_key, prop_val in resource_props.items(): - if isinstance(prop_val, dict) and 'get_param' in prop_val: - param_list = prop_val['get_param'] - if len(param_list) == 4: - resource_info = initial_param_dict.get( - param_list[0], {}).get( - param_list[1], {}) - if param_list[2] not in resource_info: - resource_info[param_list[2]] = {} + for hot_dict in base_hot_dict.values(): + param_nfv = hot_dict.get('parameters', {}).get('nfv') + if not param_nfv: + continue + resources = hot_dict.get('resources', {}) + for resource_name, resource_val in resources.items(): + resource_type = resource_val.get('type') + if resource_type in SUPPORTED_HOT_TYPE: + resource_props = resource_val.get('properties', {}) + for prop_key, prop_val in resource_props.items(): + if isinstance(prop_val, dict) and 'get_param' in prop_val: + param_list = prop_val['get_param'] + if len(param_list) == 4: + resource_info = initial_param_dict.get( + param_list[0], {}).get( + param_list[1], {}) + if param_list[2] not in resource_info: + resource_info[param_list[2]] = {} + if resource_type == HOT_NOVA_SERVER: + cinder_boot = resource_props.get('block_device_mapping_v2', + {}) + if cinder_boot: + for boot in cinder_boot: + b_param = boot.get('image') + if b_param and \ + isinstance(b_param, dict) and \ + 'get_param' in b_param: + param_list = b_param['get_param'] + if len(param_list) == 4: + resource_info = initial_param_dict.get( + param_list[0], {}).get( + param_list[1], {}) + if param_list[2] not in resource_info: + resource_info[param_list[2]] = {} + elif resource_type == AUTO_SCALING_GROUP: + resource_nest = resource_val.get('properties').get('resource') + resource_props = resource_nest.get('properties', {}) + for prop_key, prop_val in resource_props.items(): + if isinstance(prop_val, dict) and 'get_param' in prop_val: + param_list = prop_val['get_param'] + if len(param_list) == 4: + resource_info = initial_param_dict.get( + param_list[0], {}).get( + param_list[1], {}) + if param_list[2] not in resource_info: + resource_info[param_list[2]] = {} + + LOG.info('initial_param_dict: %s', initial_param_dict) + return initial_param_dict + + +def create_initial_param_server_port_dict(base_hot_dict): + """Create initial dict containing information about get_param resources. + + :param base_hot_dict: dict(Base HOT dict format) + :return: dict('nfv', Initial HOT resource dict) + + NOTE: 'nfv' is a fixed value for 1st element. + 'VDU' and 'CP' are supported for 2nd element. + 3rd and 4th element are mandatory. + """ + initial_param_dict = { + 'nfv': { + 'VDU': { + }, + 'CP': { + } + } + } + + for hot_dict in base_hot_dict.values(): + LOG.debug("init hot_dict: %s", hot_dict) + param_nfv = hot_dict.get('parameters', {}).get('nfv') + if not param_nfv: + continue + resources = hot_dict.get('resources', {}) + for resource_name, resource_val in resources.items(): + resource_type = resource_val.get('type') + if resource_type in PORT_SERVER_TYPE: + resource_props = resource_val.get('properties', {}) + for prop_key, prop_val in resource_props.items(): + if isinstance(prop_val, dict) and 'get_param' in prop_val: + param_list = prop_val['get_param'] + if len(param_list) == 4: + resource_info = initial_param_dict.get( + param_list[0], {}).get( + param_list[1], {}) + if param_list[2] not in resource_info: + resource_info[param_list[2]] = {} + if resource_type == HOT_NOVA_SERVER: + cinder_boot = resource_props.get('block_device_mapping_v2', + {}) + if cinder_boot: + for boot in cinder_boot: + b_param = boot.get('image') + if b_param and \ + isinstance(b_param, dict) and \ + 'get_param' in b_param: + param_list = b_param['get_param'] + if len(param_list) == 4: + resource_info = initial_param_dict.get( + param_list[0], {}).get( + param_list[1], {}) + if param_list[2] not in resource_info: + resource_info[param_list[2]] = {} + elif resource_type == AUTO_SCALING_GROUP: + resource_nest = resource_val.get('properties').get('resource') + resource_props = resource_nest.get('properties', {}) + for prop_key, prop_val in resource_props.items(): + if isinstance(prop_val, dict) and 'get_param' in prop_val: + param_list = prop_val['get_param'] + if len(param_list) == 4: + resource_info = initial_param_dict.get( + param_list[0], {}).get( + param_list[1], {}) + if param_list[2] not in resource_info: + resource_info[param_list[2]] = {} LOG.info('initial_param_dict: %s', initial_param_dict) return initial_param_dict @@ -162,11 +269,107 @@ def create_cpd_vl_dict(base_hot_dict, inst_req_info): ext_cps = ext_vl.ext_cps vl_uuid = ext_vl.resource_id for ext_cp in ext_cps: - cp_resource = base_hot_dict['resources'].get( - ext_cp.cpd_id) - if cp_resource is None: - continue - cpd_vl_dict[ext_cp.cpd_id] = vl_uuid + for hot_dict in base_hot_dict.values(): + cp_resource = hot_dict['resources'].get( + ext_cp.cpd_id) + if cp_resource is None: + continue + cpd_vl_dict[ext_cp.cpd_id] = vl_uuid + break LOG.info('cpd_vl_dict: %s', cpd_vl_dict) return cpd_vl_dict + + +def get_diff_base_hot_param_from_api(base_hot_dict, inst_req_info): + """Compare base hot param from API param. + + :param base_hot_dict: dict(Base HOT dict format) + :param inst_req_info: dict(Instantiation request information format) + :return: dict(Parameters) + """ + param_value = {} + additional_param = inst_req_info.additional_params + + if additional_param is None: + additional_param = {} + input_attributes = base_hot_dict['heat_template'].get('parameters') + + for input_attr, value in input_attributes.items(): + if additional_param.get(input_attr): + param_value.update({input_attr: additional_param.get( + input_attr)}) + + return param_value + + +def create_vdu_flavor_capability_name_dict(vnfd_dict): + """Create a dict containing information about VDU's flavor. + + :param vnfd_dict: dict(VNFD dict format) + :return: dict(VDU name, VDU Capability Name dict) + """ + vdu_flavor_dict = {} + node_templates = vnfd_dict.get( + 'topology_template', {}).get( + 'node_templates', {}) + + for vdu_name, val in node_templates.items(): + vdu_flavor_props = val.get( + 'capabilities', {}).get( + 'virtual_compute', {}).get('properties', {}) + if vdu_flavor_props is not {}: + for key, val in vdu_flavor_props.items(): + if key == 'requested_additional_capabilities': + capability_props = val.get('properties', {}) + if 'requested_additional_capability_name'\ + in capability_props.keys(): + vdu_flavor_dict[vdu_name] = \ + capability_props[ + "requested_additional" + "_capability_name"] + + LOG.info('vdu_flavor_dict: %s', vdu_flavor_dict) + return vdu_flavor_dict + + +def create_sw_image_dict(vnfd_dict): + """Create a dict containing information about VDU's flavor. + + :param vnfd_dict: dict(VNFD dict format) + :return: dict(VDU name, VDU SW Image data dict) + """ + sw_image_data = {} + node_templates = vnfd_dict.get( + 'topology_template', {}).get( + 'node_templates', {}) + + for vdu_name, val in node_templates.items(): + sw_image_data_props = val.get( + 'properties', {}).get('sw_image_data', {}) + if sw_image_data_props is not {}: + if 'name' in sw_image_data_props.keys(): + sw_image_data[vdu_name] = sw_image_data_props['name'] + + LOG.info('sw_image_data: %s', sw_image_data) + return sw_image_data + + +def create_network_dict(inst_req_info, param_dict): + """Create a dict containing information about VDU's network. + + :param inst_req_info: dict(Instantiation request information format) + :param param_dict: dict('nfv', Initial HOT resource dict) + :return: dict(VDU name, VDU SW Image data dict) + """ + cp_data = {} + ext_vl_param = inst_req_info.ext_virtual_links + cp_param = param_dict.get('nfv', {}).get('CP') + + for ext_vl in ext_vl_param: + for ext_cp in ext_vl.ext_cps: + if ext_cp.cpd_id in cp_param.keys(): + cp_data[ext_cp.cpd_id] = ext_vl.resource_id + + LOG.info('cp_data: %s', cp_data) + return cp_data