deb-heat/heat/tests/test_stack_resource.py
Anderson Mesquita 87ff54290a Check resources are in expected states
Allow resource to check whether it is in an expected state and update
status and action accordingly.

This adds a check action to the stack that will perform checks in all
their resources and nested resources as well.  If a resource doesn't
have a handle_check() method, check for that resource will just pass.

If any resource at the end of a Stack.check() doesn't have a
handle_check() method, then a message will be added to the status_reason
of both the resource and the stack saying that CHECK is not (fully)
supported.  This allows for the other resources to implement their own
checks for their expected states.

Co-Authored-By: Richard Lee <rblee88@gmail.com>
Implements: blueprint stack-check (partial)
Change-Id: I18985476696faeaa47149c233a067f9494c2effa
2014-08-12 10:51:38 -04:00

637 lines
25 KiB
Python

#
# 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 six
import uuid
import mock
import mox
from heat.common import exception
from heat.common import template_format
from heat.engine import environment
from heat.engine import parser
from heat.engine import resource
from heat.engine import scheduler
from heat.engine import stack_resource
from heat.tests.common import HeatTestCase
from heat.tests import generic_resource as generic_rsrc
from heat.tests import utils
ws_res_snippet = {"HeatTemplateFormatVersion": "2012-12-12",
"Type": "some_magic_type",
"metadata": {
"key": "value",
"some": "more stuff"}}
param_template = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Parameters" : {
"KeyName" : {
"Description" : "KeyName",
"Type" : "String",
"Default" : "test"
}
},
"Resources" : {
"WebServer": {
"Type": "GenericResource",
"Properties": {}
}
}
}
'''
simple_template = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Parameters" : {},
"Resources" : {
"WebServer": {
"Type": "GenericResource",
"Properties": {}
}
}
}
'''
class MyStackResource(stack_resource.StackResource,
generic_rsrc.GenericResource):
def physical_resource_name(self):
return "cb2f2b28-a663-4683-802c-4b40c916e1ff"
def set_template(self, nested_template, params):
self.nested_template = nested_template
self.nested_params = params
def handle_create(self):
return self.create_with_template(self.nested_template,
self.nested_params)
def handle_adopt(self, resource_data):
return self.create_with_template(self.nested_template,
self.nested_params,
adopt_data=resource_data)
def handle_delete(self):
self.delete_nested()
class MyImplementedStackResource(MyStackResource):
def child_template(self):
return self.nested_template
def child_params(self):
return self.nested_params
class StackResourceTest(HeatTestCase):
def setUp(self):
super(StackResourceTest, self).setUp()
resource._register_class('some_magic_type',
MyStackResource)
resource._register_class('GenericResource',
generic_rsrc.GenericResource)
self.ws_resname = "provider_resource"
t = parser.Template({'HeatTemplateFormatVersion': '2012-12-12',
'Resources':
{self.ws_resname: ws_res_snippet}})
self.parent_stack = parser.Stack(utils.dummy_context(), 'test_stack',
t, stack_id=str(uuid.uuid4()),
user_creds_id='uc123')
resource_defns = t.resource_definitions(self.parent_stack)
self.parent_resource = MyStackResource('test',
resource_defns[self.ws_resname],
self.parent_stack)
self.templ = template_format.parse(param_template)
self.simple_template = template_format.parse(simple_template)
def test_child_template_defaults_to_not_implemented(self):
self.assertRaises(NotImplementedError,
self.parent_resource.child_template)
def test_child_params_defaults_to_not_implemented(self):
self.assertRaises(NotImplementedError,
self.parent_resource.child_params)
def test_preview_defaults_to_stack_resource_itself(self):
preview = self.parent_resource.preview()
self.assertIsInstance(preview, stack_resource.StackResource)
def test_propagated_files(self):
self.parent_stack.t.files["foo"] = "bar"
self.parent_resource.create_with_template(self.templ,
{"KeyName": "key"})
self.stack = self.parent_resource.nested()
self.assertEqual({"foo": "bar"}, self.stack.t.files)
@mock.patch.object(stack_resource.StackResource, '_nested_environment')
@mock.patch.object(stack_resource.parser, 'Template')
@mock.patch.object(stack_resource.parser, 'Stack')
def test_preview_with_implemented_child_resource(self, mock_stack_class,
mock_template_class,
mock_env_class):
nested_stack = mock.Mock()
mock_stack_class.return_value = nested_stack
nested_stack.preview_resources.return_value = 'preview_nested_stack'
mock_template_class.return_value = 'parsed_template'
mock_env_class.return_value = 'environment'
template = template_format.parse(param_template)
parent_t = self.parent_stack.t
resource_defns = parent_t.resource_definitions(self.parent_stack)
parent_resource = MyImplementedStackResource(
'test',
resource_defns[self.ws_resname],
self.parent_stack)
params = {'KeyName': 'test'}
parent_resource.set_template(template, params)
validation_mock = mock.Mock(return_value=None)
parent_resource._validate_nested_resources = validation_mock
result = parent_resource.preview()
mock_env_class.assert_called_once_with(params)
mock_template_class.assert_called_once_with(template)
self.assertEqual('preview_nested_stack', result)
mock_stack_class.assert_called_once_with(
mock.ANY,
'test_stack-test',
'parsed_template',
'environment',
disable_rollback=mock.ANY,
parent_resource=parent_resource,
owner_id=self.parent_stack.id,
user_creds_id=self.parent_stack.user_creds_id
)
def test_preview_validates_nested_resources(self):
parent_t = self.parent_stack.t
resource_defns = parent_t.resource_definitions(self.parent_stack)
stack_resource = MyImplementedStackResource(
'test',
resource_defns[self.ws_resname],
self.parent_stack)
stack_resource.child_template = \
mock.Mock(return_value={'HeatTemplateFormatVersion': '2012-12-12'})
stack_resource.child_params = mock.Mock()
exc = exception.RequestLimitExceeded(message='Validation Failed')
validation_mock = mock.Mock(side_effect=exc)
stack_resource._validate_nested_resources = validation_mock
self.assertRaises(exception.RequestLimitExceeded,
stack_resource.preview)
def test__validate_nested_resources_checks_num_of_resources(self):
stack_resource.cfg.CONF.set_override('max_resources_per_stack', 2)
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
'Resources': [1]}
template = stack_resource.parser.Template(tmpl)
root_resources = mock.Mock(return_value=2)
self.parent_resource.stack.root_stack.total_resources = root_resources
self.assertRaises(exception.RequestLimitExceeded,
self.parent_resource._validate_nested_resources,
template)
def test_create_with_template_ok(self):
self.parent_resource.create_with_template(self.templ,
{"KeyName": "key"})
self.stack = self.parent_resource.nested()
self.assertEqual(self.parent_resource, self.stack.parent_resource)
self.assertEqual("cb2f2b28-a663-4683-802c-4b40c916e1ff",
self.stack.name)
self.assertEqual(self.templ, self.stack.t.t)
self.assertEqual(self.stack.id, self.parent_resource.resource_id)
self.assertIsNone(self.stack.timeout_mins)
def test_create_with_template_timeout_mins(self):
self.assertIsNone(self.parent_stack.timeout_mins)
self.m.StubOutWithMock(self.parent_stack, 'timeout_mins')
self.parent_stack.timeout_mins = 100
self.m.ReplayAll()
self.parent_resource.create_with_template(self.templ,
{"KeyName": "key"})
self.stack = self.parent_resource.nested()
self.assertEqual(100, self.stack.timeout_mins)
self.m.VerifyAll()
def test_adopt_with_template_ok(self):
adopt_data = {
"resources": {
"WebServer": {
"resource_id": "test-res-id"
}
}
}
self.parent_resource.create_with_template(self.templ,
{"KeyName": "key"},
adopt_data=adopt_data)
self.stack = self.parent_resource.nested()
self.assertEqual(self.stack.ADOPT, self.stack.action)
self.assertEqual('test-res-id',
self.stack.resources['WebServer'].resource_id)
self.assertEqual(self.parent_resource, self.stack.parent_resource)
self.assertEqual("cb2f2b28-a663-4683-802c-4b40c916e1ff",
self.stack.name)
self.assertEqual(self.templ, self.stack.t.t)
self.assertEqual(self.stack.id, self.parent_resource.resource_id)
def test_prepare_abandon(self):
self.parent_resource.create_with_template(self.templ,
{"KeyName": "key"})
ret = self.parent_resource.prepare_abandon()
# check abandoned data contains all the necessary information.
# (no need to check stack/resource IDs, because they are
# randomly generated uuids)
self.assertEqual(6, len(ret))
self.assertEqual('CREATE', ret['action'])
self.assertIn('name', ret)
self.assertIn('id', ret)
self.assertIn('resources', ret)
self.assertEqual(template_format.parse(param_template),
ret['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."""
create_result = self.parent_resource.create_with_template(
self.simple_template, {})
while not create_result.step():
pass
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_update_with_template_ok(self):
"""
The update_with_template method updates the nested stack with the
given template and user parameters.
"""
create_result = self.parent_resource.create_with_template(
self.simple_template, {})
while not create_result.step():
pass
self.stack = self.parent_resource.nested()
new_templ = self.simple_template.copy()
inst_snippet = new_templ["Resources"]["WebServer"].copy()
new_templ["Resources"]["WebServer2"] = inst_snippet
updater = self.parent_resource.update_with_template(
new_templ, {})
updater.run_to_completion()
self.assertIs(True,
self.parent_resource.check_update_complete(updater))
self.assertEqual(('UPDATE', 'COMPLETE'), self.stack.state)
self.assertEqual(set(["WebServer", "WebServer2"]),
set(self.stack.keys()))
self.assertIsNone(self.stack.timeout_mins)
# The stack's owner_id is maintained.
saved_stack = parser.Stack.load(
self.parent_stack.context, self.stack.id)
self.assertEqual(self.parent_stack.id, saved_stack.owner_id)
def test_update_with_template_timeout_mins(self):
self.assertIsNone(self.parent_stack.timeout_mins)
self.m.StubOutWithMock(self.parent_stack, 'timeout_mins')
self.parent_stack.timeout_mins = 100
self.m.ReplayAll()
create_result = self.parent_resource.create_with_template(
self.simple_template, {})
while not create_result.step():
pass
self.stack = self.parent_resource.nested()
self.assertEqual(100, self.stack.timeout_mins)
self.parent_stack.timeout_mins = 200
self.m.ReplayAll()
updater = self.parent_resource.update_with_template(
self.simple_template, {})
updater.run_to_completion()
self.assertEqual(200, self.stack.timeout_mins)
self.m.VerifyAll()
def test_update_with_template_files(self):
create_result = self.parent_resource.create_with_template(
self.simple_template, {})
while not create_result.step():
pass
self.stack = self.parent_resource.nested()
new_templ = self.simple_template.copy()
inst_snippet = new_templ["Resources"]["WebServer"].copy()
new_templ["Resources"]["WebServer2"] = inst_snippet
self.parent_stack.t.files["foo"] = "bar"
updater = self.parent_resource.update_with_template(
new_templ, {})
updater.run_to_completion()
self.assertEqual({"foo": "bar"}, self.stack.t.files)
def test_update_with_template_state_err(self):
"""
update_with_template_state_err method should raise error when update
task is done but the nested stack is in (UPDATE, FAILED) state.
"""
create_creator = self.parent_resource.create_with_template(
self.simple_template, {})
create_creator.run_to_completion()
self.stack = self.parent_resource.nested()
new_templ = self.simple_template.copy()
inst_snippet = new_templ["Resources"]["WebServer"].copy()
new_templ["Resources"]["WebServer2"] = inst_snippet
def update_task():
yield
self.stack.state_set(parser.Stack.UPDATE, parser.Stack.FAILED, '')
self.m.StubOutWithMock(self.stack, 'update_task')
self.stack.update_task(mox.IgnoreArg()).AndReturn(update_task())
self.m.ReplayAll()
updater = self.parent_resource.update_with_template(new_templ, {})
updater.run_to_completion()
self.assertEqual((self.stack.UPDATE, self.stack.FAILED),
self.stack.state)
ex = self.assertRaises(exception.Error,
self.parent_resource.check_update_complete,
updater)
self.assertEqual('Nested stack UPDATE failed: ', six.text_type(ex))
self.m.VerifyAll()
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.m.StubOutWithMock(parser.Stack, 'load')
parser.Stack.load(self.parent_resource.context,
self.parent_resource.resource_id,
parent_resource=self.parent_resource,
show_deleted=False).AndReturn('s')
self.m.ReplayAll()
self.parent_resource.nested()
self.m.VerifyAll()
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.m.StubOutWithMock(parser.Stack, 'load')
parser.Stack.load(self.parent_resource.context,
self.parent_resource.resource_id,
parent_resource=self.parent_resource,
show_deleted=False)
self.m.ReplayAll()
self.assertRaises(exception.NotFound, self.parent_resource.nested)
self.m.VerifyAll()
def test_delete_nested_ok(self):
nested = self.m.CreateMockAnything()
self.m.StubOutWithMock(stack_resource.StackResource, 'nested')
stack_resource.StackResource.nested().AndReturn(nested)
nested.delete()
self.m.ReplayAll()
self.parent_resource.delete_nested()
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,
show_deleted=False).AndRaise(exception.NotFound(''))
self.m.ReplayAll()
self.assertIsNone(self.parent_resource.delete_nested())
def test_get_output_ok(self):
nested = self.m.CreateMockAnything()
self.m.StubOutWithMock(stack_resource.StackResource, 'nested')
stack_resource.StackResource.nested().AndReturn(nested)
nested.outputs = {"key": "value"}
nested.output('key').AndReturn("value")
self.m.ReplayAll()
self.assertEqual("value", self.parent_resource.get_output("key"))
self.m.VerifyAll()
def test_get_output_key_not_found(self):
nested = self.m.CreateMockAnything()
self.m.StubOutWithMock(stack_resource.StackResource, 'nested')
stack_resource.StackResource.nested().AndReturn(nested)
nested.outputs = {}
self.m.ReplayAll()
self.assertRaises(exception.InvalidTemplateAttribute,
self.parent_resource.get_output,
"key")
self.m.VerifyAll()
def test_resolve_attribute_string(self):
nested = self.m.CreateMockAnything()
self.m.StubOutWithMock(stack_resource.StackResource, 'nested')
stack_resource.StackResource.nested().AndReturn(nested)
nested.outputs = {'key': 'value'}
nested.output('key').AndReturn('value')
self.m.ReplayAll()
self.assertEqual('value',
self.parent_resource._resolve_attribute("key"))
self.m.VerifyAll()
def test_resolve_attribute_dict(self):
nested = self.m.CreateMockAnything()
self.m.StubOutWithMock(stack_resource.StackResource, 'nested')
stack_resource.StackResource.nested().AndReturn(nested)
nested.outputs = {'key': {'a': 1, 'b': 2}}
nested.output('key').AndReturn({'a': 1, 'b': 2})
self.m.ReplayAll()
self.assertEqual({'a': 1, 'b': 2},
self.parent_resource._resolve_attribute("key"))
self.m.VerifyAll()
def test_resolve_attribute_list(self):
nested = self.m.CreateMockAnything()
self.m.StubOutWithMock(stack_resource.StackResource, 'nested')
stack_resource.StackResource.nested().AndReturn(nested)
nested.outputs = {"key": [1, 2, 3]}
nested.output('key').AndReturn([1, 2, 3])
self.m.ReplayAll()
self.assertEqual([1, 2, 3],
self.parent_resource._resolve_attribute("key"))
self.m.VerifyAll()
def test_create_complete_state_err(self):
"""
check_create_complete should raise error when create task is
done but the nested stack is not in (CREATE,COMPLETE) state
"""
del self.templ['Resources']['WebServer']
self.parent_resource.set_template(self.templ, {"KeyName": "test"})
ctx = self.parent_resource.context
phy_id = "cb2f2b28-a663-4683-802c-4b40c916e1ff"
templ = parser.Template(self.templ)
env = environment.Environment({"KeyName": "test"})
self.stack = parser.Stack(ctx, phy_id, templ, env, timeout_mins=None,
disable_rollback=True,
parent_resource=self.parent_resource)
self.m.StubOutWithMock(environment, 'Environment')
environment.Environment().AndReturn(env)
self.m.StubOutWithMock(parser, 'Stack')
parser.Stack(ctx, phy_id, templ, env, timeout_mins=None,
disable_rollback=True,
parent_resource=self.parent_resource,
owner_id=self.parent_stack.id,
user_creds_id=self.parent_stack.user_creds_id,
adopt_stack_data=None).AndReturn(self.stack)
st_set = self.stack.state_set
self.m.StubOutWithMock(self.stack, 'state_set')
self.stack.state_set(self.stack.CREATE, self.stack.IN_PROGRESS,
"Stack CREATE started").WithSideEffects(st_set)
self.stack.state_set(self.stack.CREATE, self.stack.COMPLETE,
"Stack CREATE completed successfully")
self.m.ReplayAll()
self.assertRaises(exception.ResourceFailure,
scheduler.TaskRunner(self.parent_resource.create))
self.assertEqual(('CREATE', 'FAILED'), self.parent_resource.state)
self.assertEqual(('Error: Stack CREATE started'),
self.parent_resource.status_reason)
self.m.VerifyAll()
# Restore state_set to let clean up proceed
self.stack.state_set = st_set
def test_suspend_complete_state_err(self):
"""
check_suspend_complete should raise error when suspend task is
done but the nested stack is not in (SUSPEND,COMPLETE) state
"""
del self.templ['Resources']['WebServer']
self.parent_resource.set_template(self.templ, {"KeyName": "test"})
scheduler.TaskRunner(self.parent_resource.create)()
self.stack = self.parent_resource.nested()
st_set = self.stack.state_set
self.m.StubOutWithMock(self.stack, 'state_set')
self.stack.state_set(parser.Stack.SUSPEND, parser.Stack.IN_PROGRESS,
"Stack SUSPEND started").WithSideEffects(st_set)
self.stack.state_set(parser.Stack.SUSPEND, parser.Stack.COMPLETE,
"Stack SUSPEND completed successfully")
self.m.ReplayAll()
self.assertRaises(exception.ResourceFailure,
scheduler.TaskRunner(self.parent_resource.suspend))
self.assertEqual(('SUSPEND', 'FAILED'), self.parent_resource.state)
self.assertEqual(('Error: Stack SUSPEND started'),
self.parent_resource.status_reason)
self.m.VerifyAll()
# Restore state_set to let clean up proceed
self.stack.state_set = st_set
def test_resume_complete_state_err(self):
"""
check_resume_complete should raise error when resume task is
done but the nested stack is not in (RESUME,COMPLETE) state
"""
del self.templ['Resources']['WebServer']
self.parent_resource.set_template(self.templ, {"KeyName": "test"})
scheduler.TaskRunner(self.parent_resource.create)()
self.stack = self.parent_resource.nested()
scheduler.TaskRunner(self.parent_resource.suspend)()
st_set = self.stack.state_set
self.m.StubOutWithMock(self.stack, 'state_set')
self.stack.state_set(parser.Stack.RESUME, parser.Stack.IN_PROGRESS,
"Stack RESUME started").WithSideEffects(st_set)
self.stack.state_set(parser.Stack.RESUME, parser.Stack.COMPLETE,
"Stack RESUME completed successfully")
self.m.ReplayAll()
self.assertRaises(exception.ResourceFailure,
scheduler.TaskRunner(self.parent_resource.resume))
self.assertEqual(('RESUME', 'FAILED'), self.parent_resource.state)
self.assertEqual(('Error: Stack RESUME started'),
self.parent_resource.status_reason)
self.m.VerifyAll()
# Restore state_set to let clean up proceed
self.stack.state_set = st_set
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()]