StackResource convert operations to use RPC

Modifies create/update/delete to use an RPC call,
so it can potentially be handled by a different engine in a
scaled-out heat deployment with multiple heat-engine processes or
workers.

The current introspection interface via nested() is maintained, so we
minimize the impact on existing StackResource subclasses.  A future
optimisation would be to remove the DB polling from the
check_create_complete and instead rely on an RPC notification from the
engine handling the create when it's done.

Remove test_{create,update}_with_template_validates as this is
the normal behaviour of stack create/update.

Change-Id: I7b88a329ddb5df4005b0bc10810bc5e3cdd077ab
blueprint: decouple-nested
Co-Authored-by: Angus Salkeld <asalkeld@mirantis.com>
Co-Authored-by: Steven Hardy <shardy@redhat.com>
This commit is contained in:
Angus Salkeld 2015-03-17 13:59:54 +10:00
parent e1aa606741
commit 2c2ae11456
4 changed files with 304 additions and 257 deletions

View File

@ -11,7 +11,9 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
import hashlib
import json
from oslo_config import cfg
from oslo_log import log as logging
@ -20,8 +22,8 @@ import six
from heat.common import exception
from heat.common.i18n import _
from heat.common.i18n import _LI
from heat.common.i18n import _LW
from heat.common import identifier
from heat.common import template_format
from heat.engine import attributes
from heat.engine import environment
@ -29,8 +31,7 @@ from heat.engine import resource
from heat.engine import scheduler
from heat.engine import stack as parser
from heat.engine import template
cfg.CONF.import_opt('error_wait_time', 'heat.common.config')
from heat.rpc import api as rpc_api
LOG = logging.getLogger(__name__)
@ -155,34 +156,20 @@ class StackResource(resource.Resource):
files=self.stack.t.files, env=child_env)
def _parse_nested_stack(self, stack_name, child_template,
child_params=None, timeout_mins=None,
child_params, timeout_mins=None,
adopt_data=None):
if self.stack.nested_depth >= cfg.CONF.max_nested_stack_depth:
msg = _("Recursion depth exceeds %d."
) % cfg.CONF.max_nested_stack_depth
raise exception.RequestLimitExceeded(message=msg)
if timeout_mins is None:
timeout_mins = self.stack.timeout_mins
if child_params is None:
child_params = self.child_params()
stack_user_project_id = self.stack.stack_user_project_id
new_nested_depth = self._child_nested_depth()
child_env = environment.get_child_environment(
self.stack.env, child_params,
item_to_remove=self.resource_info)
parsed_template = self._parse_child_template(child_template, child_env)
self._validate_nested_resources(parsed_template)
# Don't overwrite the attributes_schema for subclasses that
# define their own attributes_schema.
if not hasattr(type(self), 'attributes_schema'):
self.attributes = None
self._outputs_to_attribs(parsed_template)
if timeout_mins is None:
timeout_mins = self.stack.timeout_mins
stack_user_project_id = self.stack.stack_user_project_id
new_nested_depth = self.stack.nested_depth + 1
parsed_template = self._child_parsed_template(child_template,
child_env)
# Note we disable rollback for nested stacks, since they
# should be rolled back by the parent stack on failure
@ -199,6 +186,24 @@ class StackResource(resource.Resource):
nested_depth=new_nested_depth)
return nested
def _child_nested_depth(self):
if self.stack.nested_depth >= cfg.CONF.max_nested_stack_depth:
msg = _("Recursion depth exceeds %d."
) % cfg.CONF.max_nested_stack_depth
raise exception.RequestLimitExceeded(message=msg)
return self.stack.nested_depth + 1
def _child_parsed_template(self, child_template, child_env):
parsed_template = self._parse_child_template(child_template, child_env)
self._validate_nested_resources(parsed_template)
# Don't overwrite the attributes_schema for subclasses that
# define their own attributes_schema.
if not hasattr(type(self), 'attributes_schema'):
self.attributes = None
self._outputs_to_attribs(parsed_template)
return parsed_template
def _validate_nested_resources(self, templ):
total_resources = (len(templ[templ.RESOURCES]) +
self.stack.root_stack.total_resources())
@ -215,156 +220,226 @@ class StackResource(resource.Resource):
timeout_mins=None, adopt_data=None):
"""Create the nested stack with the given template."""
name = self.physical_resource_name()
self._nested = self._parse_nested_stack(name, child_template,
user_params, timeout_mins,
adopt_data)
self._nested.validate()
nested_id = self._nested.store()
self.resource_id_set(nested_id)
if timeout_mins is None:
timeout_mins = self.stack.timeout_mins
stack_user_project_id = self.stack.stack_user_project_id
action = self._nested.CREATE
error_wait_time = cfg.CONF.error_wait_time
if adopt_data:
action = self._nested.ADOPT
error_wait_time = None
if user_params is None:
user_params = self.child_params()
child_env = environment.get_child_environment(
self.stack.env,
user_params,
item_to_remove=self.resource_info)
stack_creator = scheduler.TaskRunner(self._nested.stack_task,
action=action,
error_wait_time=error_wait_time)
stack_creator.start(timeout=self._nested.timeout_secs())
return stack_creator
new_nested_depth = self._child_nested_depth()
parsed_template = self._child_parsed_template(child_template,
child_env)
def check_create_complete(self, stack_creator):
if stack_creator is None:
adopt_data_str = None
if adopt_data is not None:
if 'environment' not in adopt_data:
adopt_data['environment'] = child_env.user_env_as_dict()
if 'template' not in adopt_data:
adopt_data['template'] = child_template
adopt_data_str = json.dumps(adopt_data)
args = {rpc_api.PARAM_TIMEOUT: timeout_mins,
rpc_api.PARAM_DISABLE_ROLLBACK: True,
rpc_api.PARAM_ADOPT_STACK_DATA: adopt_data_str}
try:
result = self.rpc_client()._create_stack(
self.context,
name,
parsed_template.t,
child_env.user_env_as_dict(),
parsed_template.files,
args,
owner_id=self.stack.id,
user_creds_id=self.stack.user_creds_id,
stack_user_project_id=stack_user_project_id,
nested_depth=new_nested_depth)
except Exception as ex:
self.raise_local_exception(ex)
self.resource_id_set(result['stack_id'])
def raise_local_exception(self, ex):
ex_type = ex.__class__.__name__
is_remote = ex_type.endswith('_Remote')
if is_remote:
ex_type = ex_type[:-len('_Remote')]
full_message = six.text_type(ex)
if full_message.find('\n') > -1 and is_remote:
message, msg_trace = full_message.split('\n', 1)
else:
message = full_message
if isinstance(ex, exception.HeatException):
message = ex.message
local_ex = copy.copy(getattr(exception, ex_type))
local_ex.msg_fmt = "%(message)s"
raise local_ex(message=message)
def check_create_complete(self, cookie=None):
return self._check_status_complete(resource.Resource.CREATE)
def _check_status_complete(self, action, show_deleted=False,
cookie=None):
try:
nested = self.nested(force_reload=True, show_deleted=show_deleted)
except exception.NotFound:
if action == resource.Resource.DELETE:
return True
# It's possible the engine handling the create hasn't persisted
# the stack to the DB when we first start polling for state
return False
if nested is None:
return True
done = stack_creator.step()
if done:
if self._nested.state != (self._nested.CREATE,
self._nested.COMPLETE):
raise exception.Error(self._nested.status_reason)
return done
# Has the action really started?
#
# The rpc call to update does not guarantee that the stack will be
# placed into IN_PROGRESS by the time it returns (it runs stack.update
# in a thread) so you could also have a situation where we get into
# this method and the update hasn't even started.
#
# So we are using a mixture of state (action+status) and updated_at
# to see if the action has actually progressed.
# - very fast updates (like something with one RandomString) we will
# probably miss the state change, but we should catch the updated_at.
# - very slow updates we won't see the updated_at for quite a while,
# but should see the state change.
if cookie is not None:
prev_state = cookie['previous']['state']
prev_updated_at = cookie['previous']['updated_at']
if (prev_updated_at == nested.updated_time and
prev_state == nested.state):
return False
def check_adopt_complete(self, stack_creator):
if stack_creator is None:
if nested.status == resource.Resource.IN_PROGRESS:
return False
elif nested.status == resource.Resource.COMPLETE:
return True
done = stack_creator.step()
if done:
if self._nested.state != (self._nested.ADOPT,
self._nested.COMPLETE):
raise exception.Error(self._nested.status_reason)
elif nested.status == resource.Resource.FAILED:
raise resource.ResourceUnknownStatus(
resource_status=nested.status,
status_reason=nested.status_reason)
else:
raise resource.ResourceUnknownStatus(
resource_status=nested.status,
result=_('Stack unknown status'))
return done
def check_adopt_complete(self, cookie=None):
return self._check_status_complete(resource.Resource.ADOPT)
def update_with_template(self, child_template, user_params=None,
timeout_mins=None):
"""Update the nested stack with the new template."""
if self.id is None:
self._store()
name = self.physical_resource_name()
nested_stack = self.nested()
if nested_stack is None:
# if the create failed for some reason and the nested
# stack was not created, we need to create an empty stack
# here so that the update will work.
def _check_for_completion(creator_fn):
while not self.check_create_complete(creator_fn):
yield
empty_temp = template_format.parse(
"heat_template_version: '2013-05-23'")
stack_creator = self.create_with_template(empty_temp, {})
stack_creator.run_to_completion()
checker = scheduler.TaskRunner(_check_for_completion,
stack_creator)
checker(timeout=self.stack.timeout_secs())
if stack_creator is not None:
stack_creator.run_to_completion()
nested_stack = self.nested()
stack = self._parse_nested_stack(name, child_template, user_params,
timeout_mins)
stack.validate()
stack.parameters.set_stack_id(nested_stack.identifier())
nested_stack.updated_time = self.updated_time
updater = scheduler.TaskRunner(nested_stack.update_task, stack)
updater.start()
return updater
if timeout_mins is None:
timeout_mins = self.stack.timeout_mins
def check_update_complete(self, updater):
if updater is not None:
if not updater.step():
return False
if user_params is None:
user_params = self.child_params()
nested_stack = self.nested()
if nested_stack.state != (nested_stack.UPDATE,
nested_stack.COMPLETE):
raise exception.Error(_("Nested stack UPDATE failed: %s") %
nested_stack.status_reason)
return True
child_env = environment.get_child_environment(
self.stack.env,
user_params, item_to_remove=self.resource_info)
parsed_template = self._child_parsed_template(child_template,
child_env)
cookie = {'previous': {
'updated_at': nested_stack.updated_time,
'state': nested_stack.state}}
args = {rpc_api.PARAM_TIMEOUT: timeout_mins}
try:
self.rpc_client().update_stack(
self.context,
nested_stack.identifier(),
parsed_template.t,
child_env.user_env_as_dict(),
parsed_template.files,
args)
except Exception as ex:
LOG.exception('update_stack')
self.raise_local_exception(ex)
return cookie
def check_update_complete(self, cookie=None):
return self._check_status_complete(resource.Resource.UPDATE,
cookie=cookie)
def delete_nested(self):
'''
Delete the nested stack.
'''
stack_identity = identifier.HeatIdentifier(
self.context.tenant_id,
self.physical_resource_name(),
self.resource_id)
try:
stack = self.nested()
except exception.NotFound:
LOG.info(_LI("Stack not found to delete"))
else:
if stack is not None:
delete_task = scheduler.TaskRunner(stack.delete)
delete_task.start()
return delete_task
self.rpc_client().delete_stack(self.context, stack_identity)
except Exception as ex:
self.rpc_client().ignore_error_named(ex, 'NotFound')
def check_delete_complete(self, delete_task):
if delete_task is None:
return True
done = delete_task.step()
if done:
nested_stack = self.nested()
if nested_stack.state != (nested_stack.DELETE,
nested_stack.COMPLETE):
raise exception.Error(nested_stack.status_reason)
return done
def check_delete_complete(self, cookie=None):
return self._check_status_complete(resource.Resource.DELETE,
show_deleted=True)
def handle_suspend(self):
stack = self.nested()
if stack is None:
raise exception.Error(_('Cannot suspend %s, stack not created')
% self.name)
stack_identity = identifier.HeatIdentifier(
self.context.tenant_id,
self.physical_resource_name(),
self.resource_id)
self.rpc_client().stack_suspend(self.context, stack_identity)
suspend_task = scheduler.TaskRunner(self._nested.stack_task,
action=self._nested.SUSPEND,
reverse=True)
suspend_task.start(timeout=self._nested.timeout_secs())
return suspend_task
def check_suspend_complete(self, suspend_task):
done = suspend_task.step()
if done:
if self._nested.state != (self._nested.SUSPEND,
self._nested.COMPLETE):
raise exception.Error(self._nested.status_reason)
return done
def check_suspend_complete(self, cookie=None):
return self._check_status_complete(resource.Resource.SUSPEND)
def handle_resume(self):
stack = self.nested()
if stack is None:
raise exception.Error(_('Cannot resume %s, stack not created')
% self.name)
stack_identity = identifier.HeatIdentifier(
self.context.tenant_id,
self.physical_resource_name(),
self.resource_id)
self.rpc_client().stack_resume(self.context, stack_identity)
resume_task = scheduler.TaskRunner(self._nested.stack_task,
action=self._nested.RESUME,
reverse=False)
resume_task.start(timeout=self._nested.timeout_secs())
return resume_task
def check_resume_complete(self, resume_task):
done = resume_task.step()
if done:
if self._nested.state != (self._nested.RESUME,
self._nested.COMPLETE):
raise exception.Error(self._nested.status_reason)
return done
def check_resume_complete(self, cookie=None):
return self._check_status_complete(resource.Resource.RESUME)
def handle_check(self):
stack = self.nested()
@ -372,15 +447,14 @@ class StackResource(resource.Resource):
raise exception.Error(_('Cannot check %s, stack not created')
% self.name)
check_task = scheduler.TaskRunner(self._nested.stack_task,
action=self._nested.CHECK,
aggregate_exceptions=True)
stack_identity = identifier.HeatIdentifier(
self.context.tenant_id,
self.physical_resource_name(),
self.resource_id)
self.rpc_client().stack_check(self.context, stack_identity)
check_task.start(timeout=self._nested.timeout_secs())
return check_task
def check_check_complete(self, check_task):
return check_task.step()
def check_check_complete(self, cookie=None):
return self._check_status_complete(resource.Resource.CHECK)
def prepare_abandon(self):
return self.nested().prepare_abandon()

View File

@ -19,6 +19,7 @@ import six
import yaml
from heat.common import exception
from heat.common import identifier
from heat.common import template_format
from heat.common import urlfetch
from heat.engine import parser
@ -307,9 +308,9 @@ Outputs:
def setUp(self):
super(NestedStackCrudTest, self).setUp()
ctx = utils.dummy_context('test_username', 'aaaa', 'password')
self.ctx = utils.dummy_context('test_username', 'aaaa', 'password')
empty_template = {"HeatTemplateFormatVersion": "2012-12-12"}
stack = parser.Stack(ctx, 'test', parser.Template(empty_template))
stack = parser.Stack(self.ctx, 'test', parser.Template(empty_template))
stack.store()
self.patchobject(urlfetch, 'get', return_value=self.nested_template)
@ -323,6 +324,7 @@ Outputs:
self.res = stack_res.NestedStack('test_t_res',
self.defn, stack)
self.assertIsNone(self.res.validate())
self.res._store()
def test_handle_create(self):
self.res.create_with_template = mock.Mock(return_value=None)
@ -350,7 +352,12 @@ Outputs:
self.nested_parsed, self.nested_params, None)
def test_handle_delete(self):
self.res.nested = mock.MagicMock()
self.res.nested.return_value.delete.return_value = None
self.res.rpc_client = mock.MagicMock()
stack_identity = identifier.HeatIdentifier(
self.ctx.tenant_id,
self.res.physical_resource_name(),
self.res.resource_id)
self.res.handle_delete()
self.res.nested.return_value.delete.assert_called_once_with()
self.res.rpc_client.return_value.delete_stack.assert_called_once_with(
self.ctx, stack_identity)

View File

@ -20,6 +20,7 @@ import six
from heat.common import exception
from heat.common.i18n import _
from heat.common import identifier
from heat.common import template_format
from heat.common import urlfetch
from heat.engine import attributes
@ -823,6 +824,7 @@ class TemplateResourceCrudTest(common.HeatTestCase):
def setUp(self):
super(TemplateResourceCrudTest, self).setUp()
files = {'test_resource.template': json.dumps(self.provider)}
self.ctx = utils.dummy_context()
class DummyResource(object):
support_status = support.SupportStatus()
@ -835,7 +837,7 @@ class TemplateResourceCrudTest(common.HeatTestCase):
resource._register_class('DummyResource', DummyResource)
env.load({'resource_registry':
{'DummyResource': 'test_resource.template'}})
stack = parser.Stack(utils.dummy_context(), 'test_stack',
stack = parser.Stack(self.ctx, 'test_stack',
parser.Template(empty_template, files=files,
env=env),
stack_id=str(uuid.uuid4()))
@ -872,7 +874,13 @@ class TemplateResourceCrudTest(common.HeatTestCase):
self.provider, {'Foo': 'bar'})
def test_handle_delete(self):
self.res.nested = mock.MagicMock()
self.res.nested.return_value.delete.return_value = None
self.res.rpc_client = mock.MagicMock()
self.res.id = 55
self.res.uuid = six.text_type(uuid.uuid4())
self.res.resource_id = six.text_type(uuid.uuid4())
ident = identifier.HeatIdentifier(self.ctx.tenant_id,
self.res.physical_resource_name(),
self.res.resource_id)
self.res.handle_delete()
self.res.nested.return_value.delete.assert_called_once_with()
rpcc = self.res.rpc_client.return_value
rpcc.delete_stack.assert_called_once_with(self.ctx, ident)

View File

@ -21,7 +21,6 @@ from heat.common import exception
from heat.common import template_format
from heat.engine import resource
from heat.engine.resources import stack_resource
from heat.engine import scheduler
from heat.engine import stack as parser
from heat.engine import template as templatem
from heat.tests import common
@ -419,36 +418,7 @@ class StackResourceTest(common.HeatTestCase):
self.parent_resource._validate_nested_resources,
template)
def test_create_with_template_validates(self):
"""
Creating a stack with a template validates the created stack, so that
an invalid template will cause an error to be raised.
"""
# Make a parameter key with the same name as the resource to cause a
# simple validation error
template = self.simple_template.copy()
template['Parameters']['WebServer'] = {'Type': 'String'}
self.assertRaises(
exception.StackValidationFailed,
self.parent_resource.create_with_template,
template, {'WebServer': 'foo'})
def test_update_with_template_validates(self):
"""Updating a stack with a template validates the created stack."""
self.parent_resource._nested = mock.MagicMock()
template = self.simple_template.copy()
template['Parameters']['WebServer'] = {'Type': 'String'}
self.assertRaises(
exception.StackValidationFailed,
self.parent_resource.update_with_template,
template, {'WebServer': 'foo'})
def test_load_nested_ok(self):
self.parent_resource.create_with_template(self.templ,
{"KeyName": "key"})
self.stack = self.parent_resource.nested()
self.parent_resource._nested = None
self.parent_resource.resource_id = 319
self.m.StubOutWithMock(parser.Stack, 'load')
@ -458,59 +428,55 @@ class StackResourceTest(common.HeatTestCase):
show_deleted=False,
force_reload=False).AndReturn('s')
self.m.ReplayAll()
self.parent_resource.nested()
self.m.VerifyAll()
def test_load_nested_force_reload(self):
create_creator = self.parent_resource.create_with_template(
self.templ, {"KeyName": "key"})
create_creator.run_to_completion()
expected_state = (parser.Stack.CREATE, parser.Stack.COMPLETE)
self.assertEqual(expected_state, self.parent_resource.nested().state)
stack = parser.Stack.load(
self.parent_resource.context,
self.parent_resource.resource_id,
parent_resource=self.parent_resource.name,
show_deleted=False)
stack.state_set(parser.Stack.CREATE, parser.Stack.FAILED, "foo")
self.assertEqual(expected_state, self.parent_resource.nested().state)
expected_state = (parser.Stack.CREATE, parser.Stack.FAILED)
self.assertEqual(expected_state,
self.parent_resource.nested(force_reload=True).state)
def test_load_nested_deleted(self):
create_creator = self.parent_resource.create_with_template(
self.templ, {"KeyName": "key"})
create_creator.run_to_completion()
expected_state = (parser.Stack.CREATE, parser.Stack.COMPLETE)
self.assertEqual(expected_state, self.parent_resource.nested().state)
delete_deletor = self.parent_resource.delete_nested()
delete_deletor.run_to_completion()
expected_state = (parser.Stack.DELETE, parser.Stack.COMPLETE)
self.assertEqual(expected_state,
self.parent_resource.nested(force_reload=True,
show_deleted=True).state)
def test_load_nested_non_exist(self):
self.parent_resource.create_with_template(self.templ,
{"KeyName": "key"})
self.stack = self.parent_resource.nested()
self.parent_resource._nested = None
self.parent_resource._nested = 'write-over-me'
self.parent_resource.resource_id = 319
self.m.StubOutWithMock(parser.Stack, 'load')
parser.Stack.load(self.parent_resource.context,
self.parent_resource.resource_id,
parent_resource=self.parent_resource.name,
show_deleted=False,
force_reload=False)
force_reload=True).AndReturn('ok')
self.m.ReplayAll()
self.parent_resource.nested(force_reload=True)
self.assertEqual('ok', self.parent_resource._nested)
self.m.VerifyAll()
def test_load_nested_non_exist(self):
self.parent_resource._nested = None
self.parent_resource.resource_id = '90-8'
self.m.StubOutWithMock(parser.Stack, 'load')
parser.Stack.load(self.parent_resource.context,
self.parent_resource.resource_id,
parent_resource=self.parent_resource.name,
show_deleted=False,
force_reload=False).AndReturn(None)
self.m.ReplayAll()
self.assertRaises(exception.NotFound, self.parent_resource.nested)
self.m.VerifyAll()
def test_load_nested_cached(self):
self.parent_resource._nested = 'gotthis'
self.assertEqual('gotthis', self.parent_resource.nested())
def test_load_nested_force_reload_ok(self):
self.parent_resource._nested = mock.MagicMock()
self.parent_resource.resource_id = '90-8'
self.m.StubOutWithMock(parser.Stack, 'load')
parser.Stack.load(self.parent_resource.context,
self.parent_resource.resource_id,
parent_resource=self.parent_resource.name,
show_deleted=False,
force_reload=True).AndReturn('s')
self.m.ReplayAll()
st = self.parent_resource.nested(force_reload=True)
self.assertEqual('s', st)
self.m.VerifyAll()
def test_load_nested_force_reload_none(self):
self.parent_resource._nested = mock.MagicMock()
self.parent_resource.resource_id = '90-8'
@ -526,20 +492,12 @@ class StackResourceTest(common.HeatTestCase):
self.m.VerifyAll()
def test_delete_nested_not_found_nested_stack(self):
self.parent_resource.create_with_template(self.templ,
{"KeyName": "key"})
self.stack = self.parent_resource.nested()
self.parent_resource._nested = None
self.m.StubOutWithMock(parser.Stack, 'load')
parser.Stack.load(
self.parent_resource.context,
self.parent_resource.resource_id,
parent_resource=self.parent_resource.name,
show_deleted=False, force_reload=False
).AndRaise(exception.NotFound(''))
self.m.ReplayAll()
rpcc = mock.Mock()
self.parent_resource.rpc_client = rpcc
rpcc.return_value.delete_stack = mock.Mock(
side_effect=exception.NotFound())
self.assertIsNone(self.parent_resource.delete_nested())
def test_need_update_in_failed_state_for_nested_resource(self):
@ -556,18 +514,6 @@ class StackResourceTest(common.HeatTestCase):
self.assertEqual(True, need_update)
def test_check_nested_resources(self):
def _mock_check(res):
res.handle_check = mock.Mock()
self.parent_resource.create_with_template(self.templ, {"KeyName": "k"})
nested = self.parent_resource.nested()
[_mock_check(res) for res in nested.resources.values()]
scheduler.TaskRunner(self.parent_resource.check)()
[self.assertTrue(res.handle_check.called)
for res in nested.resources.values()]
class StackResourceLimitTest(common.HeatTestCase):
scenarios = [
@ -719,11 +665,11 @@ class StackResourceAttrTest(common.HeatTestCase):
class StackResourceCheckCompleteTest(common.HeatTestCase):
scenarios = [
('create', dict(action='create')),
('update', dict(action='update')),
('suspend', dict(action='suspend')),
('resume', dict(action='resume')),
('delete', dict(action='delete')),
('create', dict(action='create', show_deleted=False)),
('update', dict(action='update', show_deleted=False)),
('suspend', dict(action='suspend', show_deleted=False)),
('resume', dict(action='resume', show_deleted=False)),
('delete', dict(action='delete', show_deleted=True)),
]
def setUp(self):
@ -744,8 +690,10 @@ class StackResourceCheckCompleteTest(common.HeatTestCase):
self.parent_stack)
self.nested = mock.MagicMock()
self.parent_resource.nested = mock.MagicMock(return_value=self.nested)
self.parent_resource._nested = self.nested
setattr(self.nested, self.action.upper(), self.action.upper())
self.nested.action = self.action.upper()
self.nested.COMPLETE = 'COMPLETE'
def test_state_ok(self):
@ -753,33 +701,43 @@ class StackResourceCheckCompleteTest(common.HeatTestCase):
check_create_complete should return True create task is
done and the nested stack is in (<action>,COMPLETE) state.
"""
self.nested.state = (self.action.upper(), 'COMPLETE')
task = mock.MagicMock()
task.step.return_value = True
self.nested.status = 'COMPLETE'
complete = getattr(self.parent_resource,
'check_%s_complete' % self.action)
self.assertIs(True, complete(task))
self.assertIs(True, complete(None))
self.parent_resource.nested.assert_called_once_with(
show_deleted=self.show_deleted, force_reload=True)
def test_state_err(self):
"""
check_create_complete should raise error when create task is
done but the nested stack is not in (<action>,COMPLETE) state
"""
self.nested.state = (self.action.upper(), 'FAILED')
self.nested.status = 'FAILED'
self.nested.status_reason = 'broken on purpose'
task = mock.MagicMock()
task.step.return_value = True
complete = getattr(self.parent_resource,
'check_%s_complete' % self.action)
self.assertRaises(exception.Error, complete, task)
def test_step_false(self):
task = mock.MagicMock()
task.step.return_value = False
self.assertRaises(resource.ResourceUnknownStatus, complete, None)
self.parent_resource.nested.assert_called_once_with(
show_deleted=self.show_deleted, force_reload=True)
def test_state_unknown(self):
"""
check_create_complete should raise error when create task is
done but the nested stack is not in (<action>,COMPLETE) state
"""
self.nested.status = 'WTF'
self.nested.status_reason = 'broken on purpose'
complete = getattr(self.parent_resource,
'check_%s_complete' % self.action)
self.assertIs(False, complete(task))
self.assertRaises(resource.ResourceUnknownStatus, complete, None)
self.parent_resource.nested.assert_called_once_with(
show_deleted=self.show_deleted, force_reload=True)
def test_in_progress(self):
self.nested.status = 'IN_PROGRESS'
complete = getattr(self.parent_resource,
'check_%s_complete' % self.action)
self.assertFalse(complete(None))
self.parent_resource.nested.assert_called_once_with(
show_deleted=self.show_deleted, force_reload=True)