# 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 oslo_config import cfg from oslo_messaging.rpc import dispatcher import six from heat.common import exception from heat.common import identifier from heat.engine.clients.os import keystone from heat.engine import dependencies from heat.engine import resource as res from heat.engine import service from heat.engine import stack from heat.engine import stack_lock from heat.engine import template as templatem from heat.objects import stack as stack_object from heat.tests import common from heat.tests.engine import tools from heat.tests import fakes as test_fakes from heat.tests import generic_resource as generic_rsrc from heat.tests import utils policy_template = ''' { "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "alarming", "Resources" : { "WebServerScaleDownPolicy" : { "Type" : "AWS::AutoScaling::ScalingPolicy", "Properties" : { "AdjustmentType" : "ChangeInCapacity", "AutoScalingGroupName" : "", "Cooldown" : "60", "ScalingAdjustment" : "-1" } }, "Random" : { "Type" : "OS::Heat::RandomString" } } } ''' class StackResourcesServiceTest(common.HeatTestCase): def setUp(self): super(StackResourcesServiceTest, self).setUp() self.ctx = utils.dummy_context(tenant_id='stack_resource_test_tenant') self.eng = service.EngineService('a-host', 'a-topic') self.eng.thread_group_mgr = tools.DummyThreadGroupManager() self.eng.engine_id = 'engine-fake-uuid' cfg.CONF.set_default('heat_stack_user_role', 'stack_user_role') @mock.patch.object(stack.Stack, 'load') def _test_describe_stack_resource(self, mock_load): mock_load.return_value = self.stack # Patch _resolve_all_attributes or it tries to call novaclient self.patchobject(res.Resource, '_resolve_all_attributes', return_value=None) r = self.eng.describe_stack_resource(self.ctx, self.stack.identifier(), 'WebServer', with_attr=None) self.assertIn('resource_identity', r) self.assertIn('description', r) self.assertIn('updated_time', r) self.assertIn('stack_identity', r) self.assertIsNotNone(r['stack_identity']) self.assertIn('stack_name', r) self.assertEqual(self.stack.name, r['stack_name']) self.assertIn('metadata', r) self.assertIn('resource_status', r) self.assertIn('resource_status_reason', r) self.assertIn('resource_type', r) self.assertIn('physical_resource_id', r) self.assertIn('resource_name', r) self.assertIn('attributes', r) self.assertEqual('WebServer', r['resource_name']) mock_load.assert_called_once_with(self.ctx, stack=mock.ANY) @tools.stack_context('service_stack_resource_describe__test_stack') def test_stack_resource_describe(self): self._test_describe_stack_resource() @mock.patch.object(service.EngineService, '_get_stack') def test_stack_resource_describe_nonexist_stack(self, mock_get): non_exist_identifier = identifier.HeatIdentifier( self.ctx.tenant_id, 'wibble', '18d06e2e-44d3-4bef-9fbf-52480d604b02') mock_get.side_effect = exception.EntityNotFound( entity='Stack', name='test') ex = self.assertRaises(dispatcher.ExpectedException, self.eng.describe_stack_resource, self.ctx, non_exist_identifier, 'WebServer') self.assertEqual(exception.EntityNotFound, ex.exc_info[0]) mock_get.assert_called_once_with(self.ctx, non_exist_identifier) @mock.patch.object(stack.Stack, 'load') @tools.stack_context('service_resource_describe_nonexist_test_stack') def test_stack_resource_describe_nonexist_resource(self, mock_load): mock_load.return_value = self.stack ex = self.assertRaises(dispatcher.ExpectedException, self.eng.describe_stack_resource, self.ctx, self.stack.identifier(), 'foo') self.assertEqual(exception.ResourceNotFound, ex.exc_info[0]) mock_load.assert_called_once_with(self.ctx, stack=mock.ANY) @tools.stack_context('service_resource_describe_noncreated_test_stack', create_res=False) def test_stack_resource_describe_noncreated_resource(self): self._test_describe_stack_resource() @mock.patch.object(service.EngineService, '_authorize_stack_user') @tools.stack_context('service_resource_describe_user_deny_test_stack') def test_stack_resource_describe_stack_user_deny(self, mock_auth): self.ctx.roles = [cfg.CONF.heat_stack_user_role] mock_auth.return_value = False ex = self.assertRaises(dispatcher.ExpectedException, self.eng.describe_stack_resource, self.ctx, self.stack.identifier(), 'foo') self.assertEqual(exception.Forbidden, ex.exc_info[0]) mock_auth.assert_called_once_with(self.ctx, mock.ANY, 'foo') @mock.patch.object(stack.Stack, 'load') @tools.stack_context('service_resources_describe_test_stack') def test_stack_resources_describe(self, mock_load): mock_load.return_value = self.stack resources = self.eng.describe_stack_resources(self.ctx, self.stack.identifier(), 'WebServer') self.assertEqual(1, len(resources)) r = resources[0] self.assertIn('resource_identity', r) self.assertIn('description', r) self.assertIn('updated_time', r) self.assertIn('stack_identity', r) self.assertIsNotNone(r['stack_identity']) self.assertIn('stack_name', r) self.assertEqual(self.stack.name, r['stack_name']) self.assertIn('resource_status', r) self.assertIn('resource_status_reason', r) self.assertIn('resource_type', r) self.assertIn('physical_resource_id', r) self.assertIn('resource_name', r) self.assertEqual('WebServer', r['resource_name']) mock_load.assert_called_once_with(self.ctx, stack=mock.ANY) @mock.patch.object(stack.Stack, 'load') @tools.stack_context('service_resources_describe_no_filter_test_stack') def test_stack_resources_describe_no_filter(self, mock_load): mock_load.return_value = self.stack resources = self.eng.describe_stack_resources( self.ctx, self.stack.identifier(), None) self.assertEqual(1, len(resources)) r = resources[0] self.assertIn('resource_name', r) self.assertEqual('WebServer', r['resource_name']) mock_load.assert_called_once_with(self.ctx, stack=mock.ANY) @mock.patch.object(service.EngineService, '_get_stack') def test_stack_resources_describe_bad_lookup(self, mock_get): mock_get.side_effect = TypeError self.assertRaises(TypeError, self.eng.describe_stack_resources, self.ctx, None, 'WebServer') mock_get.assert_called_once_with(self.ctx, None) def test_stack_resources_describe_nonexist_stack(self): non_exist_identifier = identifier.HeatIdentifier( self.ctx.tenant_id, 'wibble', '18d06e2e-44d3-4bef-9fbf-52480d604b02') ex = self.assertRaises(dispatcher.ExpectedException, self.eng.describe_stack_resources, self.ctx, non_exist_identifier, 'WebServer') self.assertEqual(exception.EntityNotFound, ex.exc_info[0]) @tools.stack_context('find_phys_res_stack') def test_find_physical_resource(self): resources = self.eng.describe_stack_resources(self.ctx, self.stack.identifier(), None) phys_id = resources[0]['physical_resource_id'] result = self.eng.find_physical_resource(self.ctx, phys_id) self.assertIsInstance(result, dict) resource_identity = identifier.ResourceIdentifier(**result) self.assertEqual(self.stack.identifier(), resource_identity.stack()) self.assertEqual('WebServer', resource_identity.resource_name) def test_find_physical_resource_nonexist(self): ex = self.assertRaises(dispatcher.ExpectedException, self.eng.find_physical_resource, self.ctx, 'foo') self.assertEqual(exception.EntityNotFound, ex.exc_info[0]) @mock.patch.object(stack.Stack, 'load') @tools.stack_context('service_resources_list_test_stack') def test_stack_resources_list(self, mock_load): mock_load.return_value = self.stack resources = self.eng.list_stack_resources(self.ctx, self.stack.identifier()) self.assertEqual(1, len(resources)) r = resources[0] self.assertIn('resource_identity', r) self.assertIn('updated_time', r) self.assertIn('physical_resource_id', r) self.assertIn('resource_name', r) self.assertEqual('WebServer', r['resource_name']) self.assertIn('resource_status', r) self.assertIn('resource_status_reason', r) self.assertIn('resource_type', r) mock_load.assert_called_once_with(self.ctx, stack=mock.ANY) @mock.patch.object(stack.Stack, 'load') @tools.stack_context('service_resources_list_test_stack_with_depth') def test_stack_resources_list_with_depth(self, mock_load): mock_load.return_value = self.stack resources = six.itervalues(self.stack) self.stack.iter_resources = mock.Mock(return_value=resources) self.eng.list_stack_resources(self.ctx, self.stack.identifier(), 2) self.stack.iter_resources.assert_called_once_with(2, filters=None) @mock.patch.object(stack.Stack, 'load') @tools.stack_context('service_resources_list_test_stack_with_max_depth') def test_stack_resources_list_with_max_depth(self, mock_load): mock_load.return_value = self.stack resources = six.itervalues(self.stack) self.stack.iter_resources = mock.Mock(return_value=resources) self.eng.list_stack_resources(self.ctx, self.stack.identifier(), 99) max_depth = cfg.CONF.max_nested_stack_depth self.stack.iter_resources.assert_called_once_with(max_depth, filters=None) @mock.patch.object(stack.Stack, 'load') @tools.stack_context('service_resources_list_test_stack') def test_stack_resources_filter_type(self, mock_load): mock_load.return_value = self.stack resources = six.itervalues(self.stack) self.stack.iter_resources = mock.Mock(return_value=resources) filters = {'type': 'AWS::EC2::Instance'} resources = self.eng.list_stack_resources(self.ctx, self.stack.identifier(), filters=filters) self.stack.iter_resources.assert_called_once_with( 0, filters={}) self.assertIn('AWS::EC2::Instance', resources[0]['resource_type']) @mock.patch.object(stack.Stack, 'load') @tools.stack_context('service_resources_list_test_stack') def test_stack_resources_filter_type_not_found(self, mock_load): mock_load.return_value = self.stack resources = six.itervalues(self.stack) self.stack.iter_resources = mock.Mock(return_value=resources) filters = {'type': 'NonExisted'} resources = self.eng.list_stack_resources(self.ctx, self.stack.identifier(), filters=filters) self.stack.iter_resources.assert_called_once_with( 0, filters={}) self.assertEqual(0, len(resources)) @mock.patch.object(stack.Stack, 'load') def test_stack_resources_list_deleted_stack(self, mock_load): stk = tools.setup_stack('resource_list_deleted_stack', self.ctx) stack_id = stk.identifier() mock_load.return_value = stk tools.clean_up_stack(stk) resources = self.eng.list_stack_resources(self.ctx, stack_id) self.assertEqual(0, len(resources)) @mock.patch.object(service.EngineService, '_get_stack') def test_stack_resources_list_nonexist_stack(self, mock_get): non_exist_identifier = identifier.HeatIdentifier( self.ctx.tenant_id, 'wibble', '18d06e2e-44d3-4bef-9fbf-52480d604b02') mock_get.side_effect = exception.EntityNotFound(entity='Stack', name='test') ex = self.assertRaises(dispatcher.ExpectedException, self.eng.list_stack_resources, self.ctx, non_exist_identifier) self.assertEqual(exception.EntityNotFound, ex.exc_info[0]) mock_get.assert_called_once_with(self.ctx, non_exist_identifier, show_deleted=True) def _stack_create(self, stack_name): self.patchobject(keystone.KeystoneClientPlugin, '_create', return_value=test_fakes.FakeKeystoneClient()) stk = tools.get_stack(stack_name, self.ctx, policy_template) stk.store() stk.create() s = stack_object.Stack.get_by_id(self.ctx, stk.id) self.patchobject(service.EngineService, '_get_stack', return_value=s) return stk def test_signal_reception_async(self): self.eng.thread_group_mgr = tools.DummyThreadGroupMgrLogStart() stack_name = 'signal_reception_async' self.stack = self._stack_create(stack_name) test_data = {'food': 'yum'} self.eng.resource_signal(self.ctx, dict(self.stack.identifier()), 'WebServerScaleDownPolicy', test_data) self.assertEqual([(self.stack.id, mock.ANY)], self.eng.thread_group_mgr.started) @mock.patch.object(res.Resource, 'signal') def test_signal_reception_sync(self, mock_signal): mock_signal.return_value = None stack_name = 'signal_reception_sync' self.stack = self._stack_create(stack_name) test_data = {'food': 'yum'} self.eng.resource_signal(self.ctx, dict(self.stack.identifier()), 'WebServerScaleDownPolicy', test_data, sync_call=True) mock_signal.assert_called_once_with(mock.ANY, False) def test_signal_reception_no_resource(self): stack_name = 'signal_reception_no_resource' self.stack = self._stack_create(stack_name) test_data = {'food': 'yum'} ex = self.assertRaises(dispatcher.ExpectedException, self.eng.resource_signal, self.ctx, dict(self.stack.identifier()), 'resource_does_not_exist', test_data) self.assertEqual(exception.ResourceNotFound, ex.exc_info[0]) @mock.patch.object(stack.Stack, 'load') @mock.patch.object(service.EngineService, '_get_stack') def test_signal_reception_unavailable_resource(self, mock_get, mock_load): stack_name = 'signal_reception_unavailable_resource' stk = tools.get_stack(stack_name, self.ctx, policy_template) stk.store() self.stack = stk s = stack_object.Stack.get_by_id(self.ctx, self.stack.id) mock_load.return_value = stk mock_get.return_value = s test_data = {'food': 'yum'} ex = self.assertRaises(dispatcher.ExpectedException, self.eng.resource_signal, self.ctx, dict(self.stack.identifier()), 'WebServerScaleDownPolicy', test_data) self.assertEqual(exception.ResourceNotAvailable, ex.exc_info[0]) mock_load.assert_called_once_with(self.ctx, stack=mock.ANY, use_stored_context=mock.ANY) mock_get.assert_called_once_with(self.ctx, self.stack.identifier()) @mock.patch.object(res.Resource, 'signal') def test_signal_returns_metadata(self, mock_signal): mock_signal.return_value = None self.stack = self._stack_create('signal_reception') rsrc = self.stack['WebServerScaleDownPolicy'] test_metadata = {'food': 'yum'} rsrc.metadata_set(test_metadata) md = self.eng.resource_signal(self.ctx, dict(self.stack.identifier()), 'WebServerScaleDownPolicy', None, sync_call=True) self.assertEqual(test_metadata, md) mock_signal.assert_called_once_with(mock.ANY, False) def test_signal_unset_invalid_hook(self): self.stack = self._stack_create('signal_unset_invalid_hook') details = {'unset_hook': 'invalid_hook'} ex = self.assertRaises(dispatcher.ExpectedException, self.eng.resource_signal, self.ctx, dict(self.stack.identifier()), 'WebServerScaleDownPolicy', details) msg = 'Invalid hook type "invalid_hook"' self.assertIn(msg, six.text_type(ex.exc_info[1])) self.assertEqual(exception.InvalidBreakPointHook, ex.exc_info[0]) def test_signal_unset_not_defined_hook(self): self.stack = self._stack_create('signal_unset_not_defined_hook') details = {'unset_hook': 'pre-update'} ex = self.assertRaises(dispatcher.ExpectedException, self.eng.resource_signal, self.ctx, dict(self.stack.identifier()), 'WebServerScaleDownPolicy', details) msg = ('The "pre-update" hook is not defined on ' 'AWSScalingPolicy "WebServerScaleDownPolicy"') self.assertIn(msg, six.text_type(ex.exc_info[1])) self.assertEqual(exception.InvalidBreakPointHook, ex.exc_info[0]) @mock.patch.object(res.Resource, 'metadata_update') @mock.patch.object(res.Resource, 'signal') @mock.patch.object(service.EngineService, '_get_stack') def test_signal_calls_metadata_update(self, mock_get, mock_signal, mock_update): # fake keystone client self.patchobject(keystone.KeystoneClientPlugin, '_create', return_value=test_fakes.FakeKeystoneClient()) stk = tools.get_stack('signal_reception', self.ctx, policy_template) self.stack = stk stk.store() stk.create() s = stack_object.Stack.get_by_id(self.ctx, self.stack.id) mock_get.return_value = s mock_signal.return_value = True # this will be called once for the Random resource mock_update.return_value = None self.eng.resource_signal(self.ctx, dict(self.stack.identifier()), 'WebServerScaleDownPolicy', None, sync_call=True) mock_get.assert_called_once_with(self.ctx, self.stack.identifier()) mock_signal.assert_called_once_with(mock.ANY, False) mock_update.assert_called_once_with() @mock.patch.object(res.Resource, 'metadata_update') @mock.patch.object(res.Resource, 'signal') @mock.patch.object(service.EngineService, '_get_stack') def test_signal_no_calls_metadata_update(self, mock_get, mock_signal, mock_update): # fake keystone client self.patchobject(keystone.KeystoneClientPlugin, '_create', return_value=test_fakes.FakeKeystoneClient()) stk = tools.get_stack('signal_reception', self.ctx, policy_template) self.stack = stk stk.store() stk.create() s = stack_object.Stack.get_by_id(self.ctx, self.stack.id) mock_get.return_value = s mock_signal.return_value = False self.eng.resource_signal(self.ctx, dict(self.stack.identifier()), 'WebServerScaleDownPolicy', None, sync_call=True) mock_get.assert_called_once_with(self.ctx, self.stack.identifier()) mock_signal.assert_called_once_with(mock.ANY, False) # this will never be called self.assertEqual(0, mock_update.call_count) def test_lazy_load_resources(self): stack_name = 'lazy_load_test' lazy_load_template = { 'HeatTemplateFormatVersion': '2012-12-12', 'Resources': { 'foo': {'Type': 'GenericResourceType'}, 'bar': { 'Type': 'ResourceWithPropsType', 'Properties': { 'Foo': {'Ref': 'foo'}, } } } } templ = templatem.Template(lazy_load_template) stk = stack.Stack(self.ctx, stack_name, templ) self.assertIsNone(stk._resources) self.assertIsNone(stk._dependencies) resources = stk.resources self.assertIsInstance(resources, dict) self.assertEqual(2, len(resources)) self.assertIsInstance(resources.get('foo'), generic_rsrc.GenericResource) self.assertIsInstance(resources.get('bar'), generic_rsrc.ResourceWithProps) stack_dependencies = stk.dependencies self.assertIsInstance(stack_dependencies, dependencies.Dependencies) self.assertEqual(2, len(stack_dependencies.graph())) @tools.stack_context('service_mark_healthy_create_complete_test_stk') def test_mark_healthy_in_create_complete(self): self.eng.resource_mark_unhealthy(self.ctx, self.stack.identifier(), 'WebServer', False, resource_status_reason='noop') r = self.eng.describe_stack_resource(self.ctx, self.stack.identifier(), 'WebServer', with_attr=None) self.assertIn('resource_action', r) self.assertIn('resource_status', r) self.assertIn('resource_status_reason', r) self.assertEqual(r['resource_action'], 'CREATE') self.assertEqual(r['resource_status'], 'COMPLETE') self.assertEqual(r['resource_status_reason'], 'state changed') @tools.stack_context('service_mark_unhealthy_create_complete_test_stk') def test_mark_unhealthy_in_create_complete(self): self.eng.resource_mark_unhealthy(self.ctx, self.stack.identifier(), 'WebServer', True, resource_status_reason='Some Reason') r = self.eng.describe_stack_resource(self.ctx, self.stack.identifier(), 'WebServer', with_attr=None) self.assertEqual(r['resource_action'], 'CHECK') self.assertEqual(r['resource_status'], 'FAILED') self.assertEqual(r['resource_status_reason'], 'Some Reason') @tools.stack_context('service_mark_healthy_check_failed_test_stk') def test_mark_healthy_check_failed(self): self.eng.resource_mark_unhealthy(self.ctx, self.stack.identifier(), 'WebServer', True, resource_status_reason='Some Reason') r = self.eng.describe_stack_resource(self.ctx, self.stack.identifier(), 'WebServer', with_attr=None) self.assertEqual(r['resource_action'], 'CHECK') self.assertEqual(r['resource_status'], 'FAILED') self.assertEqual(r['resource_status_reason'], 'Some Reason') self.eng.resource_mark_unhealthy(self.ctx, self.stack.identifier(), 'WebServer', False, resource_status_reason='Good Reason') r = self.eng.describe_stack_resource(self.ctx, self.stack.identifier(), 'WebServer', with_attr=None) self.assertEqual(r['resource_action'], 'CHECK') self.assertEqual(r['resource_status'], 'COMPLETE') self.assertEqual(r['resource_status_reason'], 'Good Reason') @tools.stack_context('service_mark_unhealthy_check_failed_test_stack') def test_mark_unhealthy_check_failed(self): self.eng.resource_mark_unhealthy(self.ctx, self.stack.identifier(), 'WebServer', True, resource_status_reason='Some Reason') r = self.eng.describe_stack_resource(self.ctx, self.stack.identifier(), 'WebServer', with_attr=None) self.assertEqual(r['resource_action'], 'CHECK') self.assertEqual(r['resource_status'], 'FAILED') self.assertEqual(r['resource_status_reason'], 'Some Reason') self.eng.resource_mark_unhealthy(self.ctx, self.stack.identifier(), 'WebServer', True, resource_status_reason='New Reason') r = self.eng.describe_stack_resource(self.ctx, self.stack.identifier(), 'WebServer', with_attr=None) self.assertEqual(r['resource_action'], 'CHECK') self.assertEqual(r['resource_status'], 'FAILED') self.assertEqual(r['resource_status_reason'], 'New Reason') @tools.stack_context('service_mark_unhealthy_invalid_value_test_stk') def test_mark_unhealthy_invalid_value(self): ex = self.assertRaises(dispatcher.ExpectedException, self.eng.resource_mark_unhealthy, self.ctx, self.stack.identifier(), 'WebServer', "This is wrong", resource_status_reason="Some Reason") self.assertEqual(exception.Invalid, ex.exc_info[0]) @tools.stack_context('service_mark_unhealthy_none_reason_test_stk') def test_mark_unhealthy_none_reason(self): self.eng.resource_mark_unhealthy(self.ctx, self.stack.identifier(), 'WebServer', True) r = self.eng.describe_stack_resource(self.ctx, self.stack.identifier(), 'WebServer', with_attr=None) self.assertEqual(r['resource_action'], 'CHECK') self.assertEqual(r['resource_status'], 'FAILED') self.assertEqual(r['resource_status_reason'], 'state changed by resource_mark_unhealthy api') @tools.stack_context('service_mark_unhealthy_empty_reason_test_stk') def test_mark_unhealthy_empty_reason(self): self.eng.resource_mark_unhealthy(self.ctx, self.stack.identifier(), 'WebServer', True, resource_status_reason="") r = self.eng.describe_stack_resource(self.ctx, self.stack.identifier(), 'WebServer', with_attr=None) self.assertEqual(r['resource_action'], 'CHECK') self.assertEqual(r['resource_status'], 'FAILED') self.assertEqual(r['resource_status_reason'], 'state changed by resource_mark_unhealthy api') @tools.stack_context('service_mark_unhealthy_lock_no_converge_test_stk') def test_mark_unhealthy_lock_no_convergence(self): mock_acquire = self.patchobject(stack_lock.StackLock, 'acquire', return_value=None) mock_release = self.patchobject(stack_lock.StackLock, 'release', return_value=None) self.eng.resource_mark_unhealthy(self.ctx, self.stack.identifier(), 'WebServer', True, resource_status_reason="") mock_acquire.assert_called_once_with() mock_release.assert_called_once_with() @tools.stack_context('service_mark_unhealthy_lock_converge_test_stk', convergence=True) def test_mark_unhealthy_stack_lock_convergence(self): mock_acquire = self.patchobject(res.Resource, '_acquire', return_value=None) self.eng.resource_mark_unhealthy(self.ctx, self.stack.identifier(), 'WebServer', True, resource_status_reason="") mock_acquire.assert_called_once_with(self.eng.engine_id) @tools.stack_context('service_mark_unhealthy_lockexc_converge_test_stk', convergence=True) def test_mark_unhealthy_stack_lock_exc_convergence(self): def _acquire(*args, **kwargs): raise exception.UpdateInProgress(self.stack.name) self.patchobject( res.Resource, '_acquire', return_value=None, side_effect=exception.UpdateInProgress(self.stack.name)) ex = self.assertRaises(dispatcher.ExpectedException, self.eng.resource_mark_unhealthy, self.ctx, self.stack.identifier(), 'WebServer', True, resource_status_reason="") self.assertEqual(exception.ActionInProgress, ex.exc_info[0]) @tools.stack_context('service_mark_unhealthy_lockexc_no_converge_test_stk') def test_mark_unhealthy_stack_lock_exc_no_convergence(self): self.patchobject( stack_lock.StackLock, 'acquire', return_value=None, side_effect=exception.ActionInProgress( stack_name=self.stack.name, action=self.stack.action)) ex = self.assertRaises(dispatcher.ExpectedException, self.eng.resource_mark_unhealthy, self.ctx, self.stack.identifier(), 'WebServer', True, resource_status_reason="") self.assertEqual(exception.ActionInProgress, ex.exc_info[0])