diff --git a/setup.cfg b/setup.cfg index 7b7261cb5..8a6e54567 100644 --- a/setup.cfg +++ b/setup.cfg @@ -69,6 +69,9 @@ mistral.actions = tripleo.deployment.overcloudrc = tripleo_common.actions.deployment:OvercloudRcAction tripleo.heat_capabilities.get = tripleo_common.actions.heat_capabilities:GetCapabilitiesAction tripleo.heat_capabilities.update = tripleo_common.actions.heat_capabilities:UpdateCapabilitiesAction + tripleo.package_update.clear_breakpoints = tripleo_common.actions.package_update:ClearBreakpointsAction + tripleo.package_update.cancel_stack_update = tripleo_common.actions.package_update:CancelStackUpdateAction + tripleo.package_update.update_stack = tripleo_common.actions.package_update:UpdateStackAction tripleo.parameters.get = tripleo_common.actions.parameters:GetParametersAction tripleo.parameters.reset = tripleo_common.actions.parameters:ResetParametersAction tripleo.parameters.update = tripleo_common.actions.parameters:UpdateParametersAction diff --git a/tripleo_common/actions/package_update.py b/tripleo_common/actions/package_update.py new file mode 100644 index 000000000..075caf653 --- /dev/null +++ b/tripleo_common/actions/package_update.py @@ -0,0 +1,122 @@ +# Copyright 2016 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import logging +import time + +from heatclient.common import template_utils +from heatclient import exc as heat_exc +from mistral.workflow import utils as mistral_workflow_utils + +from tripleo_common.actions import base +from tripleo_common.actions import templates +from tripleo_common import constants +from tripleo_common.update import PackageUpdateManager + +LOG = logging.getLogger(__name__) + + +class ClearBreakpointsAction(base.TripleOAction): + def __init__(self, stack_id, refs): + super(ClearBreakpointsAction, self).__init__() + self.stack_id = stack_id + self.refs = refs + + def run(self): + heat = self.get_orchestration_client() + nova = self.get_compute_client() + update_manager = PackageUpdateManager(heat, nova, self.stack_id) + update_manager.clear_breakpoints(self.refs) + + +class CancelStackUpdateAction(base.TripleOAction): + def __init__(self, stack_id): + super(CancelStackUpdateAction, self).__init__() + self.stack_id = stack_id + + def run(self): + heat = self.get_orchestration_client() + nova = self.get_compute_client() + update_manager = PackageUpdateManager(heat, nova, self.stack_id) + update_manager.cancel() + + +class UpdateStackAction(templates.ProcessTemplatesAction): + + def __init__(self, timeout, container=constants.DEFAULT_CONTAINER_NAME): + super(UpdateStackAction, self).__init__(container) + self.timeout_mins = timeout + + def run(self): + # get the stack. Error if doesn't exist + heat = self.get_orchestration_client() + try: + stack = heat.stacks.get(self.container) + except heat_exc.HTTPNotFound: + msg = "Error retrieving stack: %s" % self.container + LOG.exception(msg) + return mistral_workflow_utils.Result("", msg) + + parameters = dict() + timestamp = int(time.time()) + parameters['DeployIdentifier'] = timestamp + parameters['UpdateIdentifier'] = timestamp + parameters['StackAction'] = 'UPDATE' + + wc = self.get_workflow_client() + try: + wf_env = wc.environments.get(self.container) + except Exception: + msg = "Error retrieving mistral environment: %s" % self.container + LOG.exception(msg) + return mistral_workflow_utils.Result("", msg) + + if 'parameter_defaults' not in wf_env.variables: + wf_env.variables['parameter_defaults'] = {} + wf_env.variables['parameter_defaults'].update(parameters) + env_kwargs = { + 'name': wf_env.name, + 'variables': wf_env.variables, + } + template_utils.deep_update(wf_env.variables, { + 'resource_registry': { + 'resources': { + '*': { + '*': { + constants.UPDATE_RESOURCE_NAME: { + 'hooks': 'pre-update'} + } + } + } + } + }) + + # store params changes back to db before call to process templates + wc.environments.update(**env_kwargs) + + # process all plan files and create or update a stack + processed_data = super(UpdateStackAction, self).run() + + # If we receive a 'Result' instance it is because the parent action + # had an error. + if isinstance(processed_data, mistral_workflow_utils.Result): + return processed_data + + stack_args = processed_data.copy() + stack_args['timeout_mins'] = self.timeout_mins + stack_args['existing'] = 'true' + + LOG.info("Performing Heat stack update") + LOG.info('updating stack: %s', stack.stack_name) + return heat.stacks.update(stack.id, **stack_args) diff --git a/tripleo_common/tests/actions/test_package_update.py b/tripleo_common/tests/actions/test_package_update.py new file mode 100644 index 000000000..bfb6ec7b9 --- /dev/null +++ b/tripleo_common/tests/actions/test_package_update.py @@ -0,0 +1,143 @@ +# Copyright 2016 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import mock + +from tripleo_common.actions import package_update +from tripleo_common.tests import base + + +class ClearBreakpointsActionTest(base.TestCase): + + def setUp(self,): + super(ClearBreakpointsActionTest, self).setUp() + self.stack_id = 'stack_id' + self.refs = 'refs' + + @mock.patch('tripleo_common.actions.package_update.PackageUpdateManager') + @mock.patch('tripleo_common.actions.base.TripleOAction.' + 'get_orchestration_client') + @mock.patch('tripleo_common.actions.base.TripleOAction.' + 'get_compute_client') + def test_run(self, mock_compute_client, + mock_orchestration_client, + mock_update_manager): + action = package_update.ClearBreakpointsAction(self.stack_id, + self.refs) + result = action.run() + self.assertEqual(None, result) + mock_compute_client.assert_called_once() + mock_orchestration_client.assert_called_once() + mock_update_manager().clear_breakpoints.assert_called_once_with( + self.refs) + + +class CancelStackUpdateActionTest(base.TestCase): + + def setUp(self,): + super(CancelStackUpdateActionTest, self).setUp() + self.stack_id = 'stack_id' + + @mock.patch('tripleo_common.actions.package_update.PackageUpdateManager') + @mock.patch('tripleo_common.actions.base.TripleOAction.' + 'get_orchestration_client') + @mock.patch('tripleo_common.actions.base.TripleOAction.' + 'get_compute_client') + def test_run(self, mock_compute_client, + mock_orchestration_client, + mock_update_manager): + action = package_update.CancelStackUpdateAction(self.stack_id) + result = action.run() + self.assertEqual(None, result) + mock_compute_client.assert_called_once() + mock_orchestration_client.assert_called_once() + mock_update_manager().cancel.assert_called_once() + + +class UpdateStackActionTest(base.TestCase): + + def setUp(self,): + super(UpdateStackActionTest, self).setUp() + self.timeout = 1 + self.container = 'container' + + @mock.patch('mistral.context.ctx') + @mock.patch('tripleo_common.actions.templates.ProcessTemplatesAction.run') + @mock.patch('tripleo_common.actions.base.TripleOAction.' + 'get_workflow_client') + @mock.patch('tripleo_common.actions.base.TripleOAction.' + 'get_orchestration_client') + @mock.patch('tripleo_common.actions.base.TripleOAction.' + 'get_compute_client') + @mock.patch('tripleo_common.actions.package_update.time') + @mock.patch('heatclient.common.template_utils.get_template_contents') + def test_run(self, mock_template_contents, + mock_time, + mock_compute_client, + mock_orchestration_client, + mock_workflow_client, + mock_templates_run, + mock_ctx,): + mock_ctx.return_value = mock.MagicMock() + heat = mock.MagicMock() + heat.stacks.get.return_value = mock.MagicMock( + stack_name='stack', id='stack_id') + mock_orchestration_client.return_value = heat + + mock_template_contents.return_value = ({}, { + 'heat_template_version': '2016-04-30' + }) + mock_mistral = mock.MagicMock() + mock_env = mock.MagicMock() + mock_env.variables = { + 'temp_environment': 'temp_environment', + 'template': 'template', + 'environments': [{u'path': u'environments/test.yaml'}], + 'parameter_defaults': {'random_data': 'a_value'}, + } + mock_mistral.environments.get.return_value = mock_env + mock_workflow_client.return_value = mock_mistral + + # freeze time at datetime.datetime(2016, 9, 8, 16, 24, 24) + mock_time.time.return_value = 1473366264 + + mock_templates_run.return_value = { + 'StackAction': 'UPDATE', + 'DeployIdentifier': 1473366264, + 'UpdateIdentifier': 1473366264 + } + + action = package_update.UpdateStackAction(self.timeout, + container=self.container) + action.run() + + # verify parameters are as expected + expected_defaults = { + 'StackAction': 'UPDATE', + 'DeployIdentifier': 1473366264, + 'UpdateIdentifier': 1473366264, + 'random_data': 'a_value', + } + self.assertEqual( + expected_defaults, mock_env.variables['parameter_defaults']) + + print(heat.mock_calls) + heat.stacks.update.assert_called_once_with( + 'stack_id', + StackAction='UPDATE', + DeployIdentifier=1473366264, + UpdateIdentifier=1473366264, + existing='true', + timeout_mins=1 + ) diff --git a/workbooks/package_update.yaml b/workbooks/package_update.yaml new file mode 100644 index 000000000..b79897499 --- /dev/null +++ b/workbooks/package_update.yaml @@ -0,0 +1,103 @@ +--- +version: '2.0' +name: tripleo.package_update.v1 +description: TripleO update workflows + +workflows: + + # Updates a workload cloud stack + package_update_plan: + description: Take a container and perform a package update with possible breakpoints + + input: + - container + - timeout: 240 + - queue_name: tripleo + + tasks: + update: + action: tripleo.package_update.update_stack container=<% $.container %> timeout=<% $.timeout %> + on-success: send_message + on-error: set_update_failed + + set_update_failed: + on-success: send_message + publish: + status: FAILED + message: <% task(update).result %> + + send_message: + action: zaqar.queue_post + input: + queue_name: <% $.queue_name %> + messages: + body: + type: tripleo.package_update.v1.package_update_plan + payload: + status: <% $.get('status', 'SUCCESS') %> + message: <% $.get('message', '') %> + execution: <% execution() %> + + # Clear an update breakpoint + clear_breakpoints: + description: Clear any pending breakpoints and continue with update + + input: + - stack_id + - refs + - queue_name: tripleo + + tasks: + clear: + action: tripleo.package_update.clear_breakpoints stack_id=<% $.stack_id %> refs=<% $.refs %> + on-success: send_message + on-error: set_clear_breakpoints_failed + + set_clear_breakpoints_failed: + on-success: send_message + publish: + status: FAILED + message: <% task(clear).result %> + + send_message: + action: zaqar.queue_post + input: + queue_name: <% $.queue_name %> + messages: + body: + type: tripleo.package_update.v1.clear_breakpoints + payload: + status: <% $.get('status', 'SUCCESS') %> + message: <% $.get('message', '') %> + execution: <% execution() %> + + cancel_stack_update: + description: Cancel a currently running stack update + + input: + - stack_id + - queue_name: tripleo + + tasks: + cancel_stack_update: + action: tripleo.package_update.cancel_stack_update stack_id=<% $.stack_id %> + on-success: send_message + on-error: set_cancel_stack_update_failed + + set_cancel_stack_update_failed: + on-success: send_message + publish: + status: FAILED + message: <% task(cancel_stack_update).result %> + + send_message: + action: zaqar.queue_post + input: + queue_name: <% $.queue_name + messages: + body: + type: tripleo.package_update.v1.cancel_stack_update + payload: + status: <% $.get('status', 'SUCCESS') %> + message: <% $.get('message', '') %> + execution: <% execution() %>