diff --git a/tripleo_common/actions/parameters.py b/tripleo_common/actions/parameters.py index d5654793b..e47ed6e8e 100644 --- a/tripleo_common/actions/parameters.py +++ b/tripleo_common/actions/parameters.py @@ -13,7 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -import json import logging from mistral_lib import actions @@ -24,7 +23,6 @@ from tripleo_common import constants from tripleo_common import exception from tripleo_common.utils import parameters as parameter_utils from tripleo_common.utils import stack_parameters as stack_param_utils -from tripleo_common.utils import template as template_utils LOG = logging.getLogger(__name__) @@ -155,79 +153,11 @@ class GetNetworkConfigAction(base.TripleOAction): self.role_name = role_name def run(self, context): - swift = self.get_object_client(context) - heat = self.get_orchestration_client(context) - - processed_data = template_utils.process_templates( - swift, heat, container=self.container - ) - - # Default temporary value is used when no user input for any - # interface routes for the role networks to find network config. - role_networks = processed_data['template'].get('resources', {}).get( - self.role_name + 'GroupVars', {}).get('properties', {}).get( - 'value', {}).get('role_networks', []) - for nw in role_networks: - rt = nw + 'InterfaceRoutes' - if rt not in processed_data['environment']['parameter_defaults']: - processed_data['environment']['parameter_defaults'][rt] = [[]] - - # stacks.preview method raises validation message if stack is - # already deployed. here renaming container to get preview data. - container_temp = self.container + "-TEMP" - fields = { - 'template': processed_data['template'], - 'files': processed_data['files'], - 'environment': processed_data['environment'], - 'stack_name': container_temp, - } - orc = self.get_orchestration_client(context) - preview_data = orc.stacks.preview(**fields) try: - result = self.get_network_config(preview_data, container_temp, - self.role_name) - return result - except exception.DeriveParamsError as err: - LOG.exception('Derive Params Error: %s' % err) - return actions.Result(error=str(err)) - - def get_network_config(self, preview_data, stack_name, role_name): - result = None - if preview_data: - for res in preview_data.resources: - net_script = self.process_preview_list(res, - stack_name, - role_name) - if net_script: - ns_len = len(net_script) - start_index = (net_script.find( - "echo '{\"network_config\"", 0, ns_len) + 6) - # In file network/scripts/run-os-net-config.sh - end_str = "' > /etc/os-net-config/config.json" - end_index = net_script.find(end_str, start_index, ns_len) - if (end_index > start_index): - net_config = net_script[start_index:end_index] - if net_config: - result = json.loads(net_config) - break - - if not result: - err_msg = ("Unable to determine network config for role '%s'." - % self.role_name) - raise exception.DeriveParamsError(err_msg) - - return result - - def process_preview_list(self, res, stack_name, role_name): - if type(res) == list: - for item in res: - out = self.process_preview_list(item, stack_name, role_name) - if out: - return out - elif type(res) == dict: - res_stack_name = stack_name + '-' + role_name - if res['resource_name'] == "OsNetConfigImpl" and \ - res['resource_identity'] and \ - res_stack_name in res['resource_identity']['stack_name']: - return res['properties']['config'] - return None + return stack_param_utils.get_network_configs( + self.get_baremetal_client(context), + self.get_compute_client(context), + self.container, self.role_name) + except Exception as err: + LOG.exception(six.text_type(err)) + return actions.Result(six.text_type(err)) diff --git a/tripleo_common/tests/actions/test_parameters.py b/tripleo_common/tests/actions/test_parameters.py index a0518205c..3c4b51944 100644 --- a/tripleo_common/tests/actions/test_parameters.py +++ b/tripleo_common/tests/actions/test_parameters.py @@ -14,10 +14,6 @@ # under the License. from unittest import mock -import yaml - -from swiftclient import exceptions as swiftexceptions - from tripleo_common.actions import parameters from tripleo_common import exception from tripleo_common.tests import base @@ -49,273 +45,3 @@ class GetProfileOfFlavorActionTest(base.TestCase): result = action.run(mock_ctx) self.assertTrue(result.is_error()) mock_get_profile_of_flavor.assert_called_once() - - -class GetNetworkConfigActionTest(base.TestCase): - - @mock.patch('tripleo_common.utils.plan.' - 'cache_set') - @mock.patch('tripleo_common.utils.plan.' - 'cache_get') - @mock.patch('heatclient.common.template_utils.' - 'process_multiple_environments_and_files') - @mock.patch('heatclient.common.template_utils.get_template_contents') - @mock.patch('tripleo_common.actions.base.TripleOAction.' - 'get_orchestration_client') - @mock.patch('tripleo_common.actions.base.TripleOAction.' - 'get_workflow_client') - @mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client') - def test_run_valid_network_config( - self, mock_get_object_client, mock_get_workflow_client, - mock_get_orchestration_client, mock_get_template_contents, - mock_process_multiple_environments_and_files, - mock_cache_get, - mock_cache_set): - - mock_ctx = mock.MagicMock() - swift = mock.MagicMock(url="http://test.com") - mock_env = yaml.safe_dump({ - 'temp_environment': 'temp_environment', - 'template': 'template', - 'environments': [{u'path': u'environments/test.yaml'}] - }, default_flow_style=False) - swift.get_object.side_effect = ( - ({}, mock_env), - swiftexceptions.ClientException('atest2'), - ({}, mock_env) - ) - mock_get_object_client.return_value = swift - - mock_get_template_contents.return_value = ({}, { - 'heat_template_version': '2016-04-30' - }) - mock_process_multiple_environments_and_files.return_value = ({}, {}) - - mock_heat = mock.MagicMock() - mock_heat.stacks.preview.return_value = mock.Mock(resources=[{ - "resource_identity": {"stack_name": "overcloud-TEMP-Compute-0"}, - "resource_name": "OsNetConfigImpl", - "properties": {"config": "echo \'{\"network_config\": {}}\'" - " > /etc/os-net-config/config.json"} - }]) - - mock_get_orchestration_client.return_value = mock_heat - - mock_cache_get.return_value = None - expected = {"network_config": {}} - # Test - action = parameters.GetNetworkConfigAction(container='overcloud', - role_name='Compute') - result = action.run(mock_ctx) - self.assertEqual(expected, result) - mock_heat.stacks.preview.assert_called_once_with( - environment={}, - files={}, - template={'heat_template_version': '2016-04-30'}, - stack_name='overcloud-TEMP', - ) - - @mock.patch('tripleo_common.utils.plan.' - 'cache_set') - @mock.patch('tripleo_common.utils.plan.' - 'cache_get') - @mock.patch('heatclient.common.template_utils.' - 'process_multiple_environments_and_files') - @mock.patch('heatclient.common.template_utils.get_template_contents') - @mock.patch('tripleo_common.actions.base.TripleOAction.' - 'get_orchestration_client') - @mock.patch('tripleo_common.actions.base.TripleOAction.' - 'get_workflow_client') - @mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client') - def test_run_valid_network_config_with_no_interface_routes_inputs( - self, mock_get_object_client, mock_get_workflow_client, - mock_get_orchestration_client, mock_get_template_contents, - mock_process_multiple_environments_and_files, - mock_cache_get, - mock_cache_set): - - mock_ctx = mock.MagicMock() - swift = mock.MagicMock(url="http://test.com") - mock_env = yaml.safe_dump({ - 'temp_environment': 'temp_environment', - 'template': 'template', - 'environments': [{u'path': u'environments/test.yaml'}] - }, default_flow_style=False) - swift.get_object.side_effect = ( - ({}, mock_env), - swiftexceptions.ClientException('atest2'), - ({}, mock_env) - ) - mock_get_object_client.return_value = swift - - mock_get_template_contents.return_value = ({}, { - 'heat_template_version': '2016-04-30', - 'resources': {'ComputeGroupVars': {'properties': { - 'value': {'role_networks': ['InternalApi', 'Storage']} - } - }} - }) - mock_process_multiple_environments_and_files.return_value = ( - {}, {'parameter_defaults': {}}) - - mock_heat = mock.MagicMock() - mock_heat.stacks.preview.return_value = mock.Mock(resources=[{ - "resource_identity": {"stack_name": "overcloud-TEMP-Compute-0"}, - "resource_name": "OsNetConfigImpl", - "properties": {"config": "echo \'{\"network_config\": {}}\'" - " > /etc/os-net-config/config.json"} - }]) - - mock_get_orchestration_client.return_value = mock_heat - - mock_cache_get.return_value = None - expected = {"network_config": {}} - # Test - action = parameters.GetNetworkConfigAction(container='overcloud', - role_name='Compute') - result = action.run(mock_ctx) - self.assertEqual(expected, result) - mock_heat.stacks.preview.assert_called_once_with( - environment={'parameter_defaults': { - 'InternalApiInterfaceRoutes': [[]], - 'StorageInterfaceRoutes': [[]]}}, - files={}, - template={'heat_template_version': '2016-04-30', - 'resources': {'ComputeGroupVars': { - 'properties': {'value': { - 'role_networks': ['InternalApi', - 'Storage']}}}}}, - stack_name='overcloud-TEMP', - ) - - @mock.patch('tripleo_common.utils.plan.' - 'cache_set') - @mock.patch('tripleo_common.utils.plan.' - 'cache_get') - @mock.patch('heatclient.common.template_utils.' - 'process_multiple_environments_and_files') - @mock.patch('heatclient.common.template_utils.get_template_contents') - @mock.patch('tripleo_common.actions.base.TripleOAction.' - 'get_orchestration_client') - @mock.patch('tripleo_common.actions.base.TripleOAction.' - 'get_workflow_client') - @mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client') - def test_run_valid_network_config_with_interface_routes_inputs( - self, mock_get_object_client, mock_get_workflow_client, - mock_get_orchestration_client, mock_get_template_contents, - mock_process_multiple_environments_and_files, - mock_cache_get, - mock_cache_set): - - mock_ctx = mock.MagicMock() - swift = mock.MagicMock(url="http://test.com") - mock_env = yaml.safe_dump({ - 'temp_environment': 'temp_environment', - 'template': 'template', - 'environments': [{u'path': u'environments/test.yaml'}] - }, default_flow_style=False) - swift.get_object.side_effect = ( - ({}, mock_env), - swiftexceptions.ClientException('atest2'), - ({}, mock_env) - ) - mock_get_object_client.return_value = swift - - mock_get_template_contents.return_value = ({}, { - 'heat_template_version': '2016-04-30', - 'resources': {'ComputeGroupVars': {'properties': { - 'value': {'role_networks': ['InternalApi', 'Storage']}}}} - }) - mock_process_multiple_environments_and_files.return_value = ( - {}, {'parameter_defaults': { - 'InternalApiInterfaceRoutes': ['test1'], - 'StorageInterfaceRoutes': ['test2']}}) - - mock_heat = mock.MagicMock() - mock_heat.stacks.preview.return_value = mock.Mock(resources=[{ - "resource_identity": {"stack_name": "overcloud-TEMP-Compute-0"}, - "resource_name": "OsNetConfigImpl", - "properties": {"config": "echo \'{\"network_config\": {}}\'" - " > /etc/os-net-config/config.json"} - }]) - - mock_get_orchestration_client.return_value = mock_heat - - mock_cache_get.return_value = None - expected = {"network_config": {}} - # Test - action = parameters.GetNetworkConfigAction(container='overcloud', - role_name='Compute') - result = action.run(mock_ctx) - self.assertEqual(expected, result) - mock_heat.stacks.preview.assert_called_once_with( - environment={'parameter_defaults': { - 'InternalApiInterfaceRoutes': ['test1'], - 'StorageInterfaceRoutes': ['test2']}}, - files={}, - template={'heat_template_version': '2016-04-30', - 'resources': {'ComputeGroupVars': {'properties': { - 'value': {'role_networks': ['InternalApi', - 'Storage']}}}}}, - stack_name='overcloud-TEMP', - ) - - @mock.patch('tripleo_common.utils.plan.' - 'cache_set') - @mock.patch('tripleo_common.utils.plan.' - 'cache_get') - @mock.patch('heatclient.common.template_utils.' - 'process_multiple_environments_and_files') - @mock.patch('heatclient.common.template_utils.get_template_contents') - @mock.patch('tripleo_common.actions.base.TripleOAction.' - 'get_orchestration_client') - @mock.patch('tripleo_common.actions.base.TripleOAction.' - 'get_workflow_client') - @mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client') - def test_run_invalid_network_config( - self, mock_get_object_client, - mock_get_workflow_client, mock_get_orchestration_client, - mock_get_template_contents, - mock_process_multiple_environments_and_files, - mock_cache_get, mock_cache_set): - - mock_ctx = mock.MagicMock() - swift = mock.MagicMock(url="http://test.com") - mock_env = yaml.safe_dump({ - 'temp_environment': 'temp_environment', - 'template': 'template', - 'environments': [{u'path': u'environments/test.yaml'}] - }, default_flow_style=False) - swift.get_object.side_effect = ( - ({}, mock_env), - swiftexceptions.ClientException('atest2'), - ({}, mock_env) - ) - mock_get_object_client.return_value = swift - - mock_get_template_contents.return_value = ({}, { - 'heat_template_version': '2016-04-30' - }) - mock_process_multiple_environments_and_files.return_value = ({}, {}) - - mock_heat = mock.MagicMock() - mock_heat.stacks.preview.return_value = mock.Mock(resources=[{ - "resource_identity": {"stack_name": "overcloud-TEMP-Compute-0"}, - "resource_name": "OsNetConfigImpl", - "properties": {"config": ""} - }]) - - mock_get_orchestration_client.return_value = mock_heat - - mock_cache_get.return_value = None - # Test - action = parameters.GetNetworkConfigAction(container='overcloud', - role_name='Compute') - result = action.run(mock_ctx) - self.assertTrue(result.is_error()) - mock_heat.stacks.preview.assert_called_once_with( - environment={}, - files={}, - template={'heat_template_version': '2016-04-30'}, - stack_name='overcloud-TEMP', - ) diff --git a/tripleo_common/tests/utils/test_stack_parameters.py b/tripleo_common/tests/utils/test_stack_parameters.py index d74afe354..5b63fc986 100644 --- a/tripleo_common/tests/utils/test_stack_parameters.py +++ b/tripleo_common/tests/utils/test_stack_parameters.py @@ -680,3 +680,182 @@ class StackParametersTest(base.TestCase): "pcmk_host_list": "compute_name_5" } }) + + @mock.patch('tripleo_common.utils.template.process_templates') + def test_run_valid_network_config(self, mock_process_templates): + swift = mock.MagicMock(url="http://test.com") + + mock_env = { + 'template': {}, + 'files': {}, + 'environment': [{'path': 'environments/test.yaml'}] + } + + mock_process_templates.return_value = mock_env + mock_heat = mock.MagicMock() + + mock_heat.stacks.preview.return_value = mock.Mock(resources=[{ + "resource_identity": {"stack_name": "overcloud-TEMP-Compute-0"}, + "resource_name": "OsNetConfigImpl", + "properties": {"config": "echo \'{\"network_config\": {}}\'" + " > /etc/os-net-config/config.json"} + }]) + + mock_heat.stacks.get.return_value = mock.Mock( + stack_name="overcloud-TEMP") + + expected = {"network_config": {}} + # Test + result = stack_parameters.get_network_configs( + swift, mock_heat, container='overcloud', role_name='Compute') + self.assertEqual(expected, result) + mock_heat.stacks.preview.assert_called_once_with( + environment=[{'path': 'environments/test.yaml'}], + files={}, + template={}, + stack_name='overcloud-TEMP', + ) + + @mock.patch('tripleo_common.utils.template.process_templates') + def test_run_invalid_network_config(self, mock_process_templates): + swift = mock.MagicMock(url="http://test.com") + + mock_env = { + 'template': {}, + 'files': {}, + 'environment': [{'path': 'environments/test.yaml'}] + } + + mock_process_templates.return_value = mock_env + mock_heat = mock.MagicMock() + + mock_heat.stacks.preview.return_value = mock.Mock(resources=[{ + "resource_identity": {"stack_name": "overcloud-TEMP-Compute-0"}, + "resource_name": "OsNetConfigImpl", + "properties": {"config": ""} + }]) + + mock_heat.stacks.get.return_value = mock.Mock( + stack_name="overcloud-TEMP") + + # Test + self.assertRaises(RuntimeError, + stack_parameters.get_network_configs, swift, + mock_heat, container='overcloud', + role_name='Compute') + mock_heat.stacks.preview.assert_called_once_with( + environment=[{'path': 'environments/test.yaml'}], + files={}, + template={}, + stack_name='overcloud-TEMP', + ) + + @mock.patch('tripleo_common.utils.template.process_templates') + def test_run_valid_network_config_with_no_if_routes_inputs( + self, mock_process_templates): + swift = mock.MagicMock(url="http://test.com") + + mock_env = { + 'template': { + 'resources': { + 'ComputeGroupVars': { + 'properties': { + 'value': { + 'role_networks': ['InternalApi', + 'Storage']} + } + } + } + }, + 'files': {}, + 'environment': {'parameter_defaults': {}} + } + + mock_process_templates.return_value = mock_env + mock_heat = mock.MagicMock() + + mock_heat.stacks.preview.return_value = mock.Mock(resources=[{ + "resource_identity": {"stack_name": "overcloud-TEMP-Compute-0"}, + "resource_name": "OsNetConfigImpl", + "properties": {"config": "echo \'{\"network_config\": {}}\'" + " > /etc/os-net-config/config.json"} + }]) + + mock_heat.stacks.get.return_value = mock.Mock( + stack_name="overcloud-TEMP") + + expected = {"network_config": {}} + # Test + result = stack_parameters.get_network_configs( + swift, mock_heat, container='overcloud', role_name='Compute') + self.assertEqual(expected, result) + mock_heat.stacks.preview.assert_called_once_with( + environment={ + 'parameter_defaults': { + 'InternalApiInterfaceRoutes': [[]], + 'StorageInterfaceRoutes': [[]] + } + }, + files={}, + template={'resources': {'ComputeGroupVars': {'properties': { + 'value': {'role_networks': ['InternalApi', 'Storage']} + }}}}, + stack_name='overcloud-TEMP', + ) + + @mock.patch('tripleo_common.utils.template.process_templates') + def test_run_valid_network_config_with_if_routes_inputs( + self, mock_process_templates): + swift = mock.MagicMock(url="http://test.com") + + mock_env = { + 'template': { + 'resources': { + 'ComputeGroupVars': { + 'properties': { + 'value': { + 'role_networks': ['InternalApi', + 'Storage']} + } + } + } + }, + 'files': {}, + 'environment': { + 'parameter_defaults': { + 'InternalApiInterfaceRoutes': ['test1'], + 'StorageInterfaceRoutes': ['test2'] + }} + } + + mock_process_templates.return_value = mock_env + mock_heat = mock.MagicMock() + + mock_heat.stacks.preview.return_value = mock.Mock(resources=[{ + "resource_identity": {"stack_name": "overcloud-TEMP-Compute-0"}, + "resource_name": "OsNetConfigImpl", + "properties": {"config": "echo \'{\"network_config\": {}}\'" + " > /etc/os-net-config/config.json"} + }]) + + mock_heat.stacks.get.return_value = mock.Mock( + stack_name="overcloud-TEMP") + + expected = {"network_config": {}} + # Test + result = stack_parameters.get_network_configs( + swift, mock_heat, container='overcloud', role_name='Compute') + self.assertEqual(expected, result) + mock_heat.stacks.preview.assert_called_once_with( + environment={ + 'parameter_defaults': { + 'InternalApiInterfaceRoutes': ['test1'], + 'StorageInterfaceRoutes': ['test2'] + } + }, + files={}, + template={'resources': {'ComputeGroupVars': {'properties': { + 'value': {'role_networks': ['InternalApi', 'Storage']} + }}}}, + stack_name='overcloud-TEMP', + ) diff --git a/tripleo_common/utils/stack.py b/tripleo_common/utils/stack.py index f4a5e489c..fc0aca223 100644 --- a/tripleo_common/utils/stack.py +++ b/tripleo_common/utils/stack.py @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json import logging import time import uuid @@ -278,3 +279,67 @@ def get_local_cacert(local_ca_path): return None except Exception: raise + + +def preview_stack_and_network_configs(heat, processed_data, + container, role_name): + # stacks.preview method raises validation message if stack is + # already deployed. here renaming container to get preview data. + container_temp = container + "-TEMP" + fields = { + 'template': processed_data['template'], + 'files': processed_data['files'], + 'environment': processed_data['environment'], + 'stack_name': container_temp, + } + preview_data = heat.stacks.preview(**fields) + + try: + stack = heat.stacks.get(container_temp) + except heat_exc.HTTPNotFound: + msg = "Error retrieving stack: %s" % container_temp + LOG.exception(msg) + raise RuntimeError(msg) + + return get_network_config(preview_data, stack.stack_name, role_name) + + +def get_network_config(preview_data, stack_name, role_name): + result = None + if preview_data: + for res in preview_data.resources: + net_script = process_preview_list(res, stack_name, + role_name) + if net_script: + ns_len = len(net_script) + start_index = (net_script.find( + "echo '{\"network_config\"", 0, ns_len) + 6) + # In file network/scripts/run-os-net-config.sh + end_str = "' > /etc/os-net-config/config.json" + end_index = net_script.find(end_str, start_index, ns_len) + if (end_index > start_index): + net_config = net_script[start_index:end_index] + if net_config: + result = json.loads(net_config) + break + if not result: + err_msg = ("Unable to determine network config for role '%s'." + % role_name) + LOG.exception(err_msg) + raise RuntimeError(err_msg) + return result + + +def process_preview_list(res, stack_name, role_name): + if type(res) == list: + for item in res: + out = process_preview_list(item, stack_name, role_name) + if out: + return out + elif type(res) == dict: + res_stack_name = stack_name + '-' + role_name + if res['resource_name'] == "OsNetConfigImpl" and \ + res['resource_identity'] and \ + res_stack_name in res['resource_identity']['stack_name']: + return res['properties']['config'] + return None diff --git a/tripleo_common/utils/stack_parameters.py b/tripleo_common/utils/stack_parameters.py index cbf67e793..7226cbfca 100644 --- a/tripleo_common/utils/stack_parameters.py +++ b/tripleo_common/utils/stack_parameters.py @@ -220,3 +220,23 @@ def generate_fencing_parameters(ironic, compute, nodes_json, delay, fence_params["FencingConfig"]["devices"] = devices return {"parameter_defaults": fence_params} + + +def get_network_configs(swift, heat, container, role_name): + processed_data = template_utils.process_templates( + swift, heat, container=container + ) + + # Default temporary value is used when no user input for any + # interface routes for the role networks to find network config. + role_networks = processed_data['template'].get('resources', {}).get( + role_name + 'GroupVars', {}).get('properties', {}).get( + 'value', {}).get('role_networks', []) + for nw in role_networks: + rt = nw + 'InterfaceRoutes' + if rt not in processed_data['environment']['parameter_defaults']: + processed_data['environment']['parameter_defaults'][rt] = [[]] + + network_configs = stack_utils.preview_stack_and_network_configs( + heat, processed_data, container, role_name) + return network_configs