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)