From 6c7d0596892f848d6df368867e9644b7b38d1bed Mon Sep 17 00:00:00 2001 From: rabi Date: Thu, 31 Aug 2017 10:55:13 +0530 Subject: [PATCH] Add functional tests for stack cancel This adds tests for user initiated stack cancel operations w/ or w/o rollback. Change-Id: I8ff787dc02dd76f99e9ece073b2f700c1922104d Depends-On: Ib107c82f341f4d271859ca7681d65f7ce4c5d0b1 Related-Bug: #1709041 --- heat_integrationtests/common/test.py | 21 ++++- .../functional/test_stack_cancel.py | 92 +++++++++++++++++++ 2 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 heat_integrationtests/functional/test_stack_cancel.py diff --git a/heat_integrationtests/common/test.py b/heat_integrationtests/common/test.py index a6aab7e614..30d720020e 100644 --- a/heat_integrationtests/common/test.py +++ b/heat_integrationtests/common/test.py @@ -297,11 +297,15 @@ class HeatIntegrationTest(testscenarios.WithScenarios, return False return res.resource_status == status - def _verify_status(self, stack, stack_identifier, status, fail_regexp): + def _verify_status(self, stack, stack_identifier, status, + fail_regexp, is_action_cancelled=False): if stack.stack_status == status: # Handle UPDATE_COMPLETE/FAILED case: Make sure we don't # wait for a stale UPDATE_COMPLETE/FAILED status. if status in ('UPDATE_FAILED', 'UPDATE_COMPLETE'): + if is_action_cancelled: + return True + if self.updated_time.get( stack_identifier) != stack.updated_time: self.updated_time[stack_identifier] = stack.updated_time @@ -335,7 +339,8 @@ class HeatIntegrationTest(testscenarios.WithScenarios, failure_pattern=None, success_on_not_found=False, signal_required=False, - resources_to_signal=None): + resources_to_signal=None, + is_action_cancelled=False): """Waits for a Stack to reach a given status. Note this compares the full $action_$status, e.g @@ -365,7 +370,7 @@ class HeatIntegrationTest(testscenarios.WithScenarios, # been created yet else: if self._verify_status(stack, stack_identifier, status, - fail_regexp): + fail_regexp, is_action_cancelled): return if signal_required: self.signal_resources(resources_to_signal) @@ -436,7 +441,7 @@ class HeatIntegrationTest(testscenarios.WithScenarios, self._wait_for_stack_status(**kwargs) - def cancel_update_stack(self, stack_identifier, + def cancel_update_stack(self, stack_identifier, rollback=True, expected_status='ROLLBACK_COMPLETE'): stack_name = stack_identifier.split('/')[0] @@ -444,10 +449,16 @@ class HeatIntegrationTest(testscenarios.WithScenarios, self.updated_time[stack_identifier] = self.client.stacks.get( stack_identifier, resolve_outputs=False).updated_time - self.client.actions.cancel_update(stack_name) + if rollback: + self.client.actions.cancel_update(stack_name) + else: + self.client.actions.cancel_without_rollback(stack_name) kwargs = {'stack_identifier': stack_identifier, 'status': expected_status} + if expected_status == 'UPDATE_FAILED': + kwargs['is_action_cancelled'] = True + if expected_status in ['ROLLBACK_COMPLETE']: # To trigger rollback you would intentionally fail the stack # Hence check for rollback failures diff --git a/heat_integrationtests/functional/test_stack_cancel.py b/heat_integrationtests/functional/test_stack_cancel.py new file mode 100644 index 0000000000..5fe0e508f1 --- /dev/null +++ b/heat_integrationtests/functional/test_stack_cancel.py @@ -0,0 +1,92 @@ +# 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 copy +import eventlet + +from heat_integrationtests.common import test +from heat_integrationtests.functional import functional_base + + +template = { + 'heat_template_version': 'pike', + 'resources': { + 'test1': { + 'type': 'OS::Heat::TestResource', + 'properties': { + 'value': 'Test1', + 'action_wait_secs': { + 'update': 30, + } + } + }, + 'test2': { + 'type': 'OS::Heat::TestResource', + 'properties': { + 'value': 'Test1', + }, + 'depends_on': ['test1'] + }, + } +} + + +def get_templates(delay_s=None): + before = copy.deepcopy(template) + after = copy.deepcopy(before) + for r in after['resources'].values(): + r['properties']['value'] = 'Test2' + + if delay_s: + before_props = before['resources']['test1']['properties'] + before_props['action_wait_secs']['create'] = delay_s + return before, after + + +class StackCancelTest(functional_base.FunctionalTestsBase): + + def _test_cancel_update(self, rollback=True, + expected_status='ROLLBACK_COMPLETE'): + before, after = get_templates() + stack_id = self.stack_create(template=before) + self.update_stack(stack_id, template=after, + expected_status='UPDATE_IN_PROGRESS') + self._wait_for_resource_status(stack_id, 'test1', 'UPDATE_IN_PROGRESS') + self.cancel_update_stack(stack_id, rollback, expected_status) + return stack_id + + def test_cancel_update_with_rollback(self): + self._test_cancel_update() + + def test_cancel_update_without_rollback(self): + stack_id = self._test_cancel_update(rollback=False, + expected_status='UPDATE_FAILED') + self.assertTrue(test.call_until_true( + 60, 2, self.verify_resource_status, + stack_id, 'test1', 'UPDATE_COMPLETE')) + eventlet.sleep(2) + self.assertTrue(self.verify_resource_status(stack_id, 'test2', + 'CREATE_COMPLETE')) + + def test_cancel_create_without_rollback(self): + before, after = get_templates(delay_s=30) + stack_id = self.stack_create(template=before, + expected_status='CREATE_IN_PROGRESS') + self._wait_for_resource_status(stack_id, 'test1', 'CREATE_IN_PROGRESS') + self.cancel_update_stack(stack_id, rollback=False, + expected_status='CREATE_FAILED') + self.assertTrue(test.call_until_true( + 60, 2, self.verify_resource_status, + stack_id, 'test1', 'CREATE_COMPLETE')) + eventlet.sleep(2) + self.assertTrue(self.verify_resource_status(stack_id, 'test2', + 'INIT_COMPLETE'))