diff --git a/tripleo_common/_stack_update.py b/tripleo_common/_stack_update.py deleted file mode 100644 index 5820c2728..000000000 --- a/tripleo_common/_stack_update.py +++ /dev/null @@ -1,230 +0,0 @@ -# Copyright 2015 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 fnmatch -import logging -import re -import time - -import six - -import heatclient.exc - -LOG = logging.getLogger(__name__) - - -class DeployedServer(object): - id = None - name = None - - -class StackUpdateManager(object): - def __init__(self, heatclient, novaclient, stack, hook_type, - nested_depth=5, hook_resource=None): - self.heatclient = heatclient - self.novaclient = novaclient - self.stack = stack - self.hook_type = hook_type - self.nested_depth = nested_depth - self.hook_resource = hook_resource - self.server_names = {} - self.servers = [] - - def clear_breakpoints(self, refs): - resources = self._resources_by_state() - succeeds = [] - fails = [] - for ref in refs: - server_name = None - try: - res = resources['on_breakpoint'][ref] - server_name = self._server_name(ref) - LOG.info("removing breakpoint on %s", server_name) - stack_id = next(x['href'] for x in res.links if - x['rel'] == 'stack').rsplit('/', 1)[1] - self.heatclient.resources.signal( - stack_id=stack_id, - resource_name=res.logical_resource_id, - data={'unset_hook': self.hook_type}) - succeeds.append(ref) - except Exception as err: - LOG.error("failed to remove breakpoint on %s: %s", - server_name or ref, err) - fails.append(ref) - return (succeeds, fails) - - def get_status(self): - self.stack = self.heatclient.stacks.get(self.stack.id) - # check if any of deployments' child resource has last - # event indicating that it has reached a breakpoint (this - # seems to be the only way how to check pre-create breakpoints ATM) - resources = self._resources_by_state() - if self.stack.status == 'IN_PROGRESS': - if resources['on_breakpoint']: - if resources['in_progress']: - status = 'IN_PROGRESS' - else: - status = 'WAITING' - else: - status = 'IN_PROGRESS' - else: - status = self.stack.status - LOG.debug('%s status: %s', self.stack.stack_name, status) - return (status, resources) - - def do_interactive_update(self): - status, _ = self.get_status() - - # wait for the stack-update to start - while status in ['COMPLETE', 'FAILED']: - status, _ = self.get_status() - time.sleep(5) - - while status not in ['COMPLETE', 'FAILED']: - status, resources = self.get_status() - print(status) - if status == 'WAITING': - for state in resources: - if resources[state]: - print("{0}: {1}".format(state, self._server_names( - resources[state].keys()))) - user_input = six.moves.input( - "Breakpoint reached, continue? Regexp or " - "Enter=proceed (will clear %s), " - "C-c=quit interactive mode: " - % resources['on_breakpoint'].keys()[-1]) - refs = self._input_to_refs( - user_input.strip(), - resources['on_breakpoint'].keys()) - self.clear_breakpoints(refs) - time.sleep(5) - print('update finished with status {0}'.format(status)) - - def _resources_by_state(self): - resources = { - 'not_started': {}, - 'in_progress': {}, - 'on_breakpoint': {}, - 'completed': {}, - 'failed': {}, - } - all_resources = self.heatclient.resources.list( - self.stack.id, nested_depth=self.nested_depth) - if self.hook_type == 'pre-create': - hook_reason = 'CREATE paused until Hook pre-create is cleared' - hook_clear_reason = 'Hook pre-create is cleared' - else: - hook_reason = 'UPDATE paused until Hook pre-update is cleared' - hook_clear_reason = 'Hook pre-update is cleared' - - stack_change_time = self._stack_change_time() - - for res in all_resources: - if self.hook_resource: - if not fnmatch.fnmatchcase(res.resource_name, - self.hook_resource): - continue - stack_name, stack_id = next( - x['href'] for x in res.links if - x['rel'] == 'stack').rsplit('/', 2)[1:] - try: - events = self.heatclient.events.list( - stack_id=stack_id, - resource_name=res.logical_resource_id, - sort_dir='asc') - except heatclient.exc.HTTPNotFound: - events = [] - state = 'not_started' - for ev in events: - # ignore events older than start of the last stack change - if ev.event_time < stack_change_time: - continue - if ev.resource_status_reason == hook_reason: - state = 'on_breakpoint' - elif ev.resource_status_reason == hook_clear_reason: - state = 'in_progress' - elif ev.resource_status in ('CREATE_IN_PROGRESS', - 'UPDATE_IN_PROGRESS'): - state = 'in_progress' - elif ev.resource_status in ('CREATE_COMPLETE', - 'UPDATE_COMPLETE'): - state = 'completed' - resources[state][res.physical_resource_id] = res - - return resources - - def _stack_change_time(self): - if self.hook_type == 'pre-create': - status_reason = 'Stack CREATE started' - else: - status_reason = 'Stack UPDATE started' - events = self.heatclient.events.list( - stack_id=self.stack.id, - sort_dir='desc') - try: - ev = next(e for e in events if - e.resource_status_reason == status_reason) - return ev.event_time - except StopIteration: - return None - - def _server_names(self, deployment_ids): - return [self._server_name(i) for i in deployment_ids] - - def _server_name(self, deployment_id): - name = self.server_names.get(deployment_id) - if not name: - if not self.servers: - self.servers = self._get_servers() - depl = self.heatclient.software_deployments.get(deployment_id) - name = next(server.name for server in self.servers if - server.id == depl.server_id) - self.server_names[deployment_id] = name - return name - - def _get_servers(self): - servers = self.novaclient.servers.list() - - # If no servers were found from Nova, we must be using split-stack, - # so we will have to interrogate Heat for the names and id's. - if not servers: - resources = self.heatclient.resources.list( - self.stack.id, nested_depth=self.nested_depth, - filters=dict(type="OS::Heat::DeployedServer")) - for res in resources: - server = DeployedServer() - stack_name, stack_id = next( - x['href'] for x in res.links if - x['rel'] == 'stack').rsplit('/', 2)[1:] - stack = self.heatclient.stacks.get(stack_id) - server.name = next(o['output_value'] for o in stack.outputs if - o['output_key'] == 'name') - server.id = res.physical_resource_id - servers.append(server) - - return servers - - def _input_to_refs(self, regexp, refs): - if regexp: - try: - pattern = "\A{0}\Z".format(regexp) - return [ref for ref in refs if - re.match(pattern, self._server_name(ref))] - except re.error as err: - LOG.warning("'%s' is invalid regular expression: %s", - regexp.encode('string-escape'), err) - return [] - else: - return [refs.pop()] diff --git a/tripleo_common/tests/test_stack_update.py b/tripleo_common/tests/test_stack_update.py deleted file mode 100644 index ba08b3e30..000000000 --- a/tripleo_common/tests/test_stack_update.py +++ /dev/null @@ -1,142 +0,0 @@ -# Copyright 2015 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 import _stack_update -from tripleo_common.tests import base - - -class StackUpdateManagerTest(base.TestCase): - - def setUp(self): - super(StackUpdateManagerTest, self).setUp() - self.heatclient = mock.MagicMock() - self.novaclient = mock.MagicMock() - self.stack = mock.MagicMock(id='123', status='IN_PROGRESS', - stack_name='stack') - self.heatclient.stacks.get.return_value = self.stack - - server_mock = mock.MagicMock(id='instance_id') - server_mock.name = 'instance_name' - self.novaclient.servers.list.return_value = [server_mock] - - self.heatclient.software_deployments.get.return_value = \ - mock.MagicMock(server_id='instance_id') - self.heatclient.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' - ) - ] - - def return_events(*args, **kwargs): - if 'resource_name' in kwargs: - return [ - mock.MagicMock( - event_time='2015-03-25T09:15:04Z', - resource_name='Controller-0', - resource_status='UPDATE_IN_PROGRESS', - resource_status_reason='UPDATE paused until Hook ' - 'pre-update is cleared') - ] - else: - return [ - mock.MagicMock( - event_time='2015-03-25T09:14:02Z', - resource_status_reason='Stack UPDATE started') - - ] - - self.heatclient.events.list.side_effect = return_events - self.stack_update_manager = _stack_update.StackUpdateManager( - self.heatclient, self.novaclient, self.stack, 'pre-update') - - def test_get_status(self): - status, resources = self.stack_update_manager.get_status() - self.assertEqual('WAITING', status) - - def test_clear_breakpoints(self): - good, bad = self.stack_update_manager.clear_breakpoints( - ['resource_id']) - self.heatclient.resources.signal.assert_called_once_with( - stack_id='123', - resource_name='logical_id', - data={'unset_hook': 'pre-update'}) - self.assertEqual(good, ['resource_id']) - self.assertEqual(bad, []) - - def test_clear_breakpoints_fails(self): - self.heatclient.resources.signal.side_effect = Exception('error') - good, bad = self.stack_update_manager.clear_breakpoints( - ['resource_id']) - self.assertEqual(good, []) - self.assertEqual(bad, ['resource_id']) - - def test_intput_to_refs_regexp(self): - result = self.stack_update_manager._input_to_refs( - 'instance_name.*', ['instance_id']) - self.assertEqual(result, ['instance_id']) - - def test_intput_to_refs_invalid_regexp(self): - result = self.stack_update_manager._input_to_refs( - ']].*', ['instance_id']) - self.assertEqual(result, []) - - def test_get_servers(self): - self.stack_update_manager._get_servers() - self.novaclient.servers.list.assert_called() - - def test_get_servers_deployed_server(self): - self.novaclient.servers.list.return_value = [] - self.heatclient.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='controller_resource_id', - type='OS::Heat::DeployedServer' - ), - 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='compute_resource_id', - type='OS::Heat::DeployedServer' - ) - ] - self.heatclient.stacks.get.side_effect = [ - mock.MagicMock( - outputs=[{'output_key': 'name', - 'output_value': 'overcloud-controller-0'}]), - mock.MagicMock( - outputs=[{'output_key': 'name', - 'output_value': 'overcloud-compute-0'}]), - ] - - servers = self.stack_update_manager._get_servers() - self.assertEqual(servers[0].name, 'overcloud-controller-0') - self.assertEqual(servers[0].id, 'controller_resource_id') - self.assertEqual(servers[1].name, 'overcloud-compute-0') - self.assertEqual(servers[1].id, 'compute_resource_id') diff --git a/tripleo_common/tests/test_update.py b/tripleo_common/tests/test_update.py deleted file mode 100644 index d92ab51c1..000000000 --- a/tripleo_common/tests/test_update.py +++ /dev/null @@ -1,72 +0,0 @@ -# Copyright 2015 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.tests import base -from tripleo_common import update - - -class UpdateManagerTest(base.TestCase): - - def setUp(self): - super(UpdateManagerTest, self).setUp() - - @mock.patch('time.time') - def test_update(self, mock_time): - heatclient = mock.MagicMock() - novaclient = mock.MagicMock() - mock_time.return_value = 123.5 - heatclient.stacks.get.return_value = mock.MagicMock( - stack_name='stack', id='stack_id') - stack_fields = { - 'stack_id': 'stack_id', - 'stack_name': 'mystack', - 'template': 'template body', - 'environment': {}, - 'files': {}, - } - update.PackageUpdateManager( - heatclient=heatclient, - novaclient=novaclient, - stack_id='stack_id', - stack_fields=stack_fields, - ).update() - params = { - 'existing': True, - 'stack_name': 'mystack', - 'stack_id': 'stack_id', - 'template': 'template body', - 'files': {}, - 'environment': { - 'resource_registry': { - 'resources': { - '*': { - '*': { - 'UpdateDeployment': {'hooks': 'pre-update'} - } - } - } - }, - 'parameter_defaults': { - 'DeployIdentifier': 123, - 'UpdateIdentifier': 123, - 'StackAction': 'UPDATE' - }, - }, - 'timeout_mins': 240, - } - heatclient.stacks.update.assert_called_once_with(**params) diff --git a/tripleo_common/update.py b/tripleo_common/update.py index 1a730e7e4..cf3c8cb14 100644 --- a/tripleo_common/update.py +++ b/tripleo_common/update.py @@ -13,16 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. -import logging -import time - from heatclient.common import template_utils -from tripleo_common import _stack_update from tripleo_common import constants -LOG = logging.getLogger(__name__) - def add_breakpoints_cleanup_into_env(env): template_utils.deep_update(env, { @@ -31,57 +25,3 @@ def add_breakpoints_cleanup_into_env(env): constants.UPDATE_RESOURCE_NAME: {'hooks': []}}}} } }) - - -class PackageUpdateManager(_stack_update.StackUpdateManager): - def __init__(self, heatclient, novaclient, stack_id, stack_fields): - stack = heatclient.stacks.get(stack_id) - self.stack_fields = stack_fields - super(PackageUpdateManager, self).__init__( - heatclient=heatclient, novaclient=novaclient, stack=stack, - hook_type='pre-update', nested_depth=5, - hook_resource=constants.UPDATE_RESOURCE_NAME) - - def update(self, timeout_mins=constants.STACK_TIMEOUT_DEFAULT): - env = {} - if 'environment' in self.stack_fields: - env = self.stack_fields['environment'] - - template_utils.deep_update(env, { - 'resource_registry': { - 'resources': { - '*': { - '*': { - constants.UPDATE_RESOURCE_NAME: { - 'hooks': 'pre-update'} - } - } - } - } - }) - - # time rounded to seconds - timestamp = int(time.time()) - - stack_params = { - 'DeployIdentifier': timestamp, - 'UpdateIdentifier': timestamp, - 'StackAction': 'UPDATE' - } - template_utils.deep_update(env, {'parameter_defaults': stack_params}) - - self.stack_fields['environment'] = env - - fields = { - 'existing': True, - 'stack_id': self.stack.id, - 'template': self.stack_fields['template'], - 'files': self.stack_fields['files'], - 'environment': self.stack_fields['environment'], - 'timeout_mins': timeout_mins, - 'stack_name': self.stack_fields['stack_name'], - } - - LOG.info('updating stack: %s', self.stack.stack_name) - LOG.debug('stack update params: %s', fields) - self.heatclient.stacks.update(**fields)