diff --git a/tripleo_common/actions/deployment.py b/tripleo_common/actions/deployment.py index fd810f76b..313b81c16 100644 --- a/tripleo_common/actions/deployment.py +++ b/tripleo_common/actions/deployment.py @@ -15,8 +15,10 @@ import json import logging import os +import time import yaml +from heatclient.common import deployment_utils from heatclient import exc as heat_exc from mistral_lib import actions from swiftclient import exceptions as swiftexceptions @@ -30,6 +32,102 @@ from tripleo_common.utils import swift as swiftutils LOG = logging.getLogger(__name__) +class OrchestrationDeployAction(base.TripleOAction): + + def __init__(self, server_id, config, name, input_values=[], + action='CREATE', signal_transport='TEMP_URL_SIGNAL', + timeout=300, group='script'): + super(OrchestrationDeployAction, self).__init__() + self.server_id = server_id + self.config = config + self.input_values = input_values + self.action = action + self.name = name + self.signal_transport = signal_transport + self.timeout = timeout + self.group = group + + def _extract_container_object_from_swift_url(self, swift_url): + container_name = swift_url.split('/')[-2] + object_name = swift_url.split('/')[-1].split('?')[0] + return (container_name, object_name) + + def _build_sc_params(self, swift_url): + source = { + 'config': self.config, + 'group': self.group, + } + return deployment_utils.build_derived_config_params( + action=self.action, + source=source, + name=self.name, + input_values=self.input_values, + server_id=self.server_id, + signal_transport=self.signal_transport, + signal_id=swift_url + ) + + def _wait_for_data(self, container_name, object_name, context): + body = None + count_check = 0 + swift_client = self.get_object_client(context) + while not body: + body = swiftutils.get_object_string(swift_client, container_name, + object_name) + count_check += 3 + if body or count_check > self.timeout: + break + time.sleep(3) + + return body + + def run(self, context): + heat = self.get_orchestration_client(context) + swift_client = self.get_object_client(context) + + swift_url = deployment_utils.create_temp_url(swift_client, + self.name, + self.timeout) + container_name, object_name = \ + self._extract_container_object_from_swift_url(swift_url) + + params = self._build_sc_params(swift_url) + config = heat.software_configs.create(**params) + + sd = heat.software_deployments.create( + tenant_id='asdf', # heatclient requires this + config_id=config.id, + server_id=self.server_id, + action=self.action, + status='IN_PROGRESS' + ) + + body = self._wait_for_data(container_name, object_name, context) + + # cleanup + try: + sd.delete() + config.delete() + swift_client.delete_object(container_name, object_name) + swift_client.delete_container(container_name) + except Exception as err: + LOG.error("Error cleaning up heat deployment resources.", err) + + error = None + if not body: + body_json = {} + error = "Timeout for heat deployment '%s'" % self.name + else: + body_json = json.loads(body) + if body_json['deploy_status_code'] != 0: + error = "Heat deployment failed for '%s'" % self.name + + if error: + LOG.error(error) + + return actions.Result(data=body_json, error=error) + + class OvercloudRcAction(base.TripleOAction): """Generate the overcloudrc for a plan diff --git a/tripleo_common/tests/actions/test_deployment.py b/tripleo_common/tests/actions/test_deployment.py index acb184f59..6e7152de3 100644 --- a/tripleo_common/tests/actions/test_deployment.py +++ b/tripleo_common/tests/actions/test_deployment.py @@ -16,12 +16,191 @@ import json import mock from heatclient import exc as heat_exc +from mistral_lib import actions from swiftclient import exceptions as swiftexceptions from tripleo_common.actions import deployment from tripleo_common.tests import base +class OrchestrationDeployActionTest(base.TestCase): + + def setUp(self,): + super(OrchestrationDeployActionTest, self).setUp() + self.server_id = 'server_id' + self.config = 'config' + self.name = 'name' + self.input_values = [] + self.action = 'CREATE' + self.signal_transport = 'TEMP_URL_SIGNAL' + self.timeout = 300 + self.group = 'script' + + def test_extract_container_object_from_swift_url(self): + swift_url = 'https://example.com' + \ + '/v1/a422b2-91f3-2f46-74b7-d7c9e8958f5d30/container/object' + \ + '?temp_url_sig=da39a3ee5e6b4&temp_url_expires=1323479485' + + action = deployment.OrchestrationDeployAction(self.server_id, + self.config, self.name, + self.timeout) + self.assertEqual(('container', 'object'), + action._extract_container_object_from_swift_url( + swift_url)) + + @mock.patch( + 'heatclient.common.deployment_utils.build_derived_config_params') + def test_build_sc_params(self, build_derived_config_params_mock): + build_derived_config_params_mock.return_value = 'built_params' + action = deployment.OrchestrationDeployAction(self.server_id, + self.config, self.name) + self.assertEqual('built_params', action._build_sc_params('swift_url')) + build_derived_config_params_mock.assert_called_once() + + @mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client') + def test_wait_for_data(self, get_obj_client_mock): + mock_ctx = mock.MagicMock() + + swift = mock.MagicMock() + swift.get_object.return_value = ({}, 'body') + get_obj_client_mock.return_value = swift + + action = deployment.OrchestrationDeployAction(self.server_id, + self.config, self.name) + self.assertEqual('body', action._wait_for_data('container', + 'object', + context=mock_ctx)) + get_obj_client_mock.assert_called_once() + swift.get_object.assert_called_once_with('container', 'object') + + @mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client') + @mock.patch('time.sleep') + def test_wait_for_data_timeout(self, sleep, get_obj_client_mock): + mock_ctx = mock.MagicMock() + swift = mock.MagicMock() + swift.get_object.return_value = ({}, None) + get_obj_client_mock.return_value = swift + + action = deployment.OrchestrationDeployAction(self.server_id, + self.config, self.name, + timeout=10) + self.assertIsNone(action._wait_for_data('container', + 'object', + context=mock_ctx)) + get_obj_client_mock.assert_called_once() + swift.get_object.assert_called_with('container', 'object') + # Trying every 3 seconds, so 4 times for a timeout of 10 seconds + self.assertEqual(swift.get_object.call_count, 4) + + @mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client') + @mock.patch('tripleo_common.actions.base.TripleOAction.' + 'get_orchestration_client') + @mock.patch('heatclient.common.deployment_utils.create_temp_url') + @mock.patch('tripleo_common.actions.deployment.OrchestrationDeployAction.' + '_extract_container_object_from_swift_url') + @mock.patch('tripleo_common.actions.deployment.OrchestrationDeployAction.' + '_build_sc_params') + @mock.patch('tripleo_common.actions.deployment.OrchestrationDeployAction.' + '_wait_for_data') + def test_run(self, wait_for_data_mock, build_sc_params_mock, + extract_from_swift_url_mock, create_temp_url_mock, + get_heat_mock, get_obj_client_mock): + extract_from_swift_url_mock.return_value = ('container', 'object') + mock_ctx = mock.MagicMock() + build_sc_params_mock.return_value = {'foo': 'bar'} + config = mock.MagicMock() + sd = mock.MagicMock() + get_heat_mock().software_configs.create.return_value = config + get_heat_mock().software_deployments.create.return_value = sd + wait_for_data_mock.return_value = '{"deploy_status_code": 0}' + + action = deployment.OrchestrationDeployAction(self.server_id, + self.config, self.name) + expected = actions.Result( + data={"deploy_status_code": 0}, + error=None) + self.assertEqual(expected, action.run(context=mock_ctx)) + create_temp_url_mock.assert_called_once() + extract_from_swift_url_mock.assert_called_once() + build_sc_params_mock.assert_called_once() + get_obj_client_mock.assert_called_once() + wait_for_data_mock.assert_called_once() + + sd.delete.assert_called_once() + config.delete.assert_called_once() + get_obj_client_mock.delete_object.called_once_with('container', + 'object') + get_obj_client_mock.delete_container.called_once_with('container') + + @mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client') + @mock.patch('tripleo_common.actions.base.TripleOAction.' + 'get_orchestration_client') + @mock.patch('heatclient.common.deployment_utils.create_temp_url') + @mock.patch('tripleo_common.actions.deployment.OrchestrationDeployAction.' + '_extract_container_object_from_swift_url') + @mock.patch('tripleo_common.actions.deployment.OrchestrationDeployAction.' + '_build_sc_params') + @mock.patch('tripleo_common.actions.deployment.OrchestrationDeployAction.' + '_wait_for_data') + def test_run_timeout(self, wait_for_data_mock, build_sc_params_mock, + extract_from_swift_url_mock, create_temp_url_mock, + get_heat_mock, get_obj_client_mock): + extract_from_swift_url_mock.return_value = ('container', 'object') + mock_ctx = mock.MagicMock() + config = mock.MagicMock() + sd = mock.MagicMock() + get_heat_mock().software_configs.create.return_value = config + get_heat_mock().software_deployments.create.return_value = sd + wait_for_data_mock.return_value = None + + action = deployment.OrchestrationDeployAction(self.server_id, + self.config, self.name) + expected = actions.Result( + data={}, + error="Timeout for heat deployment 'name'") + self.assertEqual(expected, action.run(mock_ctx)) + + sd.delete.assert_called_once() + config.delete.assert_called_once() + get_obj_client_mock.delete_object.called_once_with('container', + 'object') + get_obj_client_mock.delete_container.called_once_with('container') + + @mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client') + @mock.patch('tripleo_common.actions.base.TripleOAction.' + 'get_orchestration_client') + @mock.patch('heatclient.common.deployment_utils.create_temp_url') + @mock.patch('tripleo_common.actions.deployment.OrchestrationDeployAction.' + '_extract_container_object_from_swift_url') + @mock.patch('tripleo_common.actions.deployment.OrchestrationDeployAction.' + '_build_sc_params') + @mock.patch('tripleo_common.actions.deployment.OrchestrationDeployAction.' + '_wait_for_data') + def test_run_failed(self, wait_for_data_mock, build_sc_params_mock, + extract_from_swift_url_mock, create_temp_url_mock, + get_heat_mock, get_obj_client_mock): + extract_from_swift_url_mock.return_value = ('container', 'object') + mock_ctx = mock.MagicMock() + config = mock.MagicMock() + sd = mock.MagicMock() + get_heat_mock().software_configs.create.return_value = config + get_heat_mock().software_deployments.create.return_value = sd + wait_for_data_mock.return_value = '{"deploy_status_code": 1}' + + action = deployment.OrchestrationDeployAction(self.server_id, + self.config, self.name) + expected = actions.Result( + data={"deploy_status_code": 1}, + error="Heat deployment failed for 'name'") + self.assertEqual(expected, action.run(mock_ctx)) + + sd.delete.assert_called_once() + config.delete.assert_called_once() + get_obj_client_mock.delete_object.called_once_with('container', + 'object') + get_obj_client_mock.delete_container.called_once_with('container') + + class OvercloudRcActionTestCase(base.TestCase): @mock.patch('tripleo_common.actions.base.TripleOAction.' 'get_object_client')