From 602b481804a79d0ef106b1b21f0bdcd15df7f39f Mon Sep 17 00:00:00 2001 From: Tim Rozet Date: Mon, 16 Oct 2017 13:56:24 -0400 Subject: [PATCH] Adds detection for legacy API network resource Currently in network_data.yaml there is compat_name which is used as a workaround for upgrades with regards to a bug where the Heat resource for API network was being created as InternalNetwork instead of InternalApiNetwork. This workaround does work for upgrades, but consequently also causes all future new deployments to have the wrong resource name. This patch adds detection, so that if the legacy resource is detected already in the overcloud stack, then the compatibility will be enabled if not already set in network_data.yaml. This allows for the removal of compat_name from the network in network_data.yaml and thus removing the incorrect name for future deployments. Partial-Bug: 1718764 Change-Id: I695259ad87e2303f948875078bccb619786470e0 Signed-off-by: Tim Rozet --- ...pi-network-rendering-5a65009051a0f464.yaml | 7 + tripleo_common/actions/templates.py | 31 ++++- tripleo_common/constants.py | 3 + .../tests/actions/test_templates.py | 126 +++++++++++++++++- 4 files changed, 159 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/fix-api-network-rendering-5a65009051a0f464.yaml diff --git a/releasenotes/notes/fix-api-network-rendering-5a65009051a0f464.yaml b/releasenotes/notes/fix-api-network-rendering-5a65009051a0f464.yaml new file mode 100644 index 000000000..0ee5c8487 --- /dev/null +++ b/releasenotes/notes/fix-api-network-rendering-5a65009051a0f464.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixes compatibility between older deployments with Heat resource network + "InternalNetwork" and corrected "InternalApiNetwork". Upgrades from + previous versions will still use the old naming scheme, while new + deployments will use the correct name of "InternalApiNetwork". diff --git a/tripleo_common/actions/templates.py b/tripleo_common/actions/templates.py index 11959203f..fc21e38a2 100644 --- a/tripleo_common/actions/templates.py +++ b/tripleo_common/actions/templates.py @@ -157,6 +157,26 @@ class ProcessTemplatesAction(base.TripleOAction): "the J2 excludes list to: %s" % j2_excl_data) return j2_excl_data + def _heat_resource_exists(self, resource_name, context): + heatclient = self.get_orchestration_client(context) + stack_exists = False + for stack in heatclient.stacks.list(): + if self.container == str(stack.stack_name): + stack_exists = True + break + if not stack_exists: + LOG.debug("Resource does not exist because stack does not exist") + return False + + resources = heatclient.resources.list(self.container, nested_depth=6) + for resource in resources: + if str(resource.resource_name) == resource_name: + LOG.debug("Resource exists: {}".format(resource_name)) + return True + + LOG.debug("Resource does not exist: {}".format(resource_name)) + return False + def _process_custom_roles(self, context): swift = self.get_object_client(context) @@ -199,10 +219,19 @@ class ProcessTemplatesAction(base.TripleOAction): n_map = {} for n in network_data: - if (n.get('enabled') is not False): + if n.get('enabled') is not False: n_map[n.get('name')] = n if not n.get('name_lower'): n_map[n.get('name')]['name_lower'] = n.get('name').lower() + if n.get('name') == constants.API_NETWORK and 'compat_name' \ + not in n.keys(): + # Check to see if legacy named API network exists + # and if so we need to set compat_name + api_net = "{}Network".format(constants.LEGACY_API_NETWORK) + if self._heat_resource_exists(api_net, context): + n['compat_name'] = 'Internal' + LOG.info("Upgrade compatibility enabled for legacy " + "network resource Internal.") else: LOG.info("skipping %s network: network is disabled." % n.get('name')) diff --git a/tripleo_common/constants.py b/tripleo_common/constants.py index d1fbddbc0..a95f0d4ab 100644 --- a/tripleo_common/constants.py +++ b/tripleo_common/constants.py @@ -141,6 +141,9 @@ TRIPLEO_CACHE_CONTAINER = "__cache__" TRIPLEO_UI_LOG_FILE_SIZE = 1e7 # 10MB TRIPLEO_UI_LOG_FILENAME = 'tripleo-ui.logs' +API_NETWORK = 'InternalApi' +LEGACY_API_NETWORK = 'Internal' + # Default nested depth when recursing Heat stacks NESTED_DEPTH = 7 diff --git a/tripleo_common/tests/actions/test_templates.py b/tripleo_common/tests/actions/test_templates.py index 1949d50b0..41504b919 100644 --- a/tripleo_common/tests/actions/test_templates.py +++ b/tripleo_common/tests/actions/test_templates.py @@ -43,7 +43,7 @@ ROLE_DATA_YAML = r""" NETWORK_DATA_YAML = r""" - - name: anetwork + name: InternalApi """ EXPECTED_JINJA_RESULT = r""" @@ -86,7 +86,7 @@ ROLE_DATA_DISABLE_CONSTRAINTS_YAML = r""" ROLE_DATA_ENABLE_NETWORKS = r""" - name: RoleWithNetworks networks: - - anetwork + - InternalApi """ JINJA_SNIPPET_DISABLE_CONSTRAINTS_OLD = r""" @@ -125,8 +125,8 @@ JINJA_SNIPPET_ROLE_NETWORKS = r""" """ EXPECTED_JINJA_RESULT_ROLE_NETWORKS = r""" - anetworkPort: - type: RoleWithNetworks::anetwork::Port + InternalApiPort: + type: RoleWithNetworks::InternalApi::Port """ @@ -285,8 +285,12 @@ class ProcessTemplatesActionTest(base.TestCase): side_effect=return_container_files) return swift + @mock.patch('tripleo_common.actions.templates.ProcessTemplatesAction' + '._heat_resource_exists') @mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client') - def test_process_custom_roles(self, get_obj_client_mock): + def test_process_custom_roles(self, get_obj_client_mock, + resource_exists_mock): + resource_exists_mock.return_value = False swift = self._custom_roles_mock_objclient( 'foo.j2.yaml', JINJA_SNIPPET) get_obj_client_mock.return_value = swift @@ -315,9 +319,12 @@ class ProcessTemplatesActionTest(base.TestCase): swift.put_object.assert_has_calls( put_object_mock_calls, any_order=True) + @mock.patch('tripleo_common.actions.templates.ProcessTemplatesAction' + '._heat_resource_exists') @mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client') def _process_custom_roles_disable_constraints( - self, snippet, get_obj_client_mock): + self, snippet, get_obj_client_mock, resource_exists_mock): + resource_exists_mock.return_value = False swift = self._custom_roles_mock_objclient( 'disable-constraints.role.j2.yaml', snippet, ROLE_DATA_DISABLE_CONSTRAINTS_YAML) @@ -352,8 +359,12 @@ class ProcessTemplatesActionTest(base.TestCase): self._process_custom_roles_disable_constraints( JINJA_SNIPPET_DISABLE_CONSTRAINTS) + @mock.patch('tripleo_common.actions.templates.ProcessTemplatesAction' + '._heat_resource_exists') @mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client') - def test_custom_roles_networks(self, get_obj_client_mock): + def test_custom_roles_networks(self, get_obj_client_mock, + resource_exists_mock): + resource_exists_mock.return_value = False swift = self._custom_roles_mock_objclient( 'role-networks.role.j2.yaml', JINJA_SNIPPET_ROLE_NETWORKS, ROLE_DATA_ENABLE_NETWORKS) @@ -486,3 +497,104 @@ class ProcessTemplatesActionTest(base.TestCase): # Test - J2 exclude file empty action = templates.ProcessTemplatesAction() self.assertTrue({'name': []} == action._get_j2_excludes_file(mock_ctx)) + + @mock.patch('tripleo_common.actions.base.TripleOAction.' + 'get_orchestration_client') + def test_heat_resource_exists(self, client_mock): + mock_ctx = mock.MagicMock() + heat_client = mock.MagicMock() + heat_client.stacks.list.return_value = [ + mock.MagicMock(stack_name='overcloud') + ] + heat_client.resources.list.return_value = [ + mock.MagicMock( + links=[{'rel': 'stack', + 'href': 'http://192.0.2.1:8004/v1/' + 'a959ac7d6a4a475daf2428df315c41ef/' + 'stacks/overcloud/123'}], + logical_resource_id='logical_id', + physical_resource_id='resource_id', + resource_type='OS::Heat::ResourceGroup', + resource_name='InternalNetwork' + ), + ] + client_mock.return_value = heat_client + action = templates.ProcessTemplatesAction() + self.assertTrue(action._heat_resource_exists('InternalNetwork', + mock_ctx)) + + @mock.patch('tripleo_common.actions.base.TripleOAction.' + 'get_orchestration_client') + def test_no_heat_resource_exists(self, client_mock): + mock_ctx = mock.MagicMock() + heat_client = mock.MagicMock() + heat_client.stacks.list.return_value = [ + mock.MagicMock(stack_name='overcloud') + ] + heat_client.resources.list.return_value = [ + mock.MagicMock( + links=[{'rel': 'stack', + 'href': 'http://192.0.2.1:8004/v1/' + 'a959ac7d6a4a475daf2428df315c41ef/' + 'stacks/overcloud/123'}], + logical_resource_id='logical_id', + physical_resource_id='resource_id', + resource_type='OS::Heat::ResourceGroup', + resource_name='InternalApiNetwork' + ), + ] + client_mock.return_value = heat_client + action = templates.ProcessTemplatesAction() + self.assertFalse(action._heat_resource_exists('InternalNetwork', + mock_ctx)) + + @mock.patch('tripleo_common.actions.templates.ProcessTemplatesAction' + '._heat_resource_exists') + @mock.patch('tripleo_common.actions.templates.ProcessTemplatesAction' + '._j2_render_and_put') + @mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client') + def test_legacy_api_network_exists(self, get_obj_client_mock, j2_mock, + resource_exists_mock): + resource_exists_mock.return_value = True + swift = self._custom_roles_mock_objclient( + 'role-networks.role.j2.yaml', JINJA_SNIPPET_ROLE_NETWORKS, + ROLE_DATA_ENABLE_NETWORKS) + get_obj_client_mock.return_value = swift + + # Test + action = templates.ProcessTemplatesAction() + mock_ctx = mock.MagicMock() + action._process_custom_roles(mock_ctx) + expected_j2_template = get_obj_client_mock.get_object( + action.container, 'foo.j2.yaml')[1] + expected_j2_data = {'roles': [{'name': 'CustomRole'}], + 'networks': [{'name': 'InternalApi', + 'compat_name': 'Internal'}] + } + assert j2_mock.called_with(expected_j2_template, expected_j2_data, + 'foo.yaml', mock_ctx) + + @mock.patch('tripleo_common.actions.templates.ProcessTemplatesAction' + '._heat_resource_exists') + @mock.patch('tripleo_common.actions.templates.ProcessTemplatesAction' + '._j2_render_and_put') + @mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client') + def test_no_legacy_api_network_exists(self, get_obj_client_mock, j2_mock, + resource_exists_mock): + resource_exists_mock.return_value = False + swift = self._custom_roles_mock_objclient( + 'role-networks.role.j2.yaml', JINJA_SNIPPET_ROLE_NETWORKS, + ROLE_DATA_ENABLE_NETWORKS) + get_obj_client_mock.return_value = swift + + # Test + action = templates.ProcessTemplatesAction() + mock_ctx = mock.MagicMock() + action._process_custom_roles(mock_ctx) + expected_j2_template = get_obj_client_mock.get_object( + action.container, 'foo.j2.yaml')[1] + expected_j2_data = {'roles': [{'name': 'CustomRole'}], + 'networks': [{'name': 'InternalApi'}] + } + assert j2_mock.called_with(expected_j2_template, expected_j2_data, + 'foo.yaml', mock_ctx)