deb-heat/heat/tests/test_stack_resource.py
Liang Chen 112283f135 Enhance StackResource update for more use cases
Enhance StackResource.update_with_template to accommodate more
possible use cases. This will take care of the nested stack update
failure by raising an exception in such case, so the the concrete
nested stack resource implementations will have a chance to take
some actions upon nested stack update failure.

blueprint nested-stack-updates

Change-Id: Id31ef67e1d7c4928704c6e169bce13f0f7232932
2013-08-24 11:14:44 +08:00

379 lines
14 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 mox
from heat.common import template_format
from heat.common import exception
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.engine import template
from heat.openstack.common import uuidutils
from heat.tests.common import HeatTestCase
from heat.tests import generic_resource as generic_rsrc
from heat.tests import utils
ws_res_snippet = {"Type": "some_magic_type",
"metadata": {
"key": "value",
"some": "more stuff"}}
param_template = '''
{
"Parameters" : {
"KeyName" : {
"Description" : "KeyName",
"Type" : "String",
"Default" : "test"
}
},
"Resources" : {
"WebServer": {
"Type": "GenericResource",
"Properties": {}
}
}
}
'''
simple_template = '''
{
"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_tempalte, params):
self.nested_tempalte = nested_tempalte
self.nested_params = params
def handle_create(self):
return self.create_with_template(self.nested_tempalte,
self.nested_params)
def handle_delete(self):
self.delete_nested()
class StackResourceTest(HeatTestCase):
def setUp(self):
super(StackResourceTest, self).setUp()
utils.setup_dummy_db()
resource._register_class('some_magic_type',
MyStackResource)
resource._register_class('GenericResource',
generic_rsrc.GenericResource)
t = parser.Template({template.RESOURCES:
{"provider_resource": ws_res_snippet}})
self.parent_stack = parser.Stack(utils.dummy_context(), 'test_stack',
t, stack_id=uuidutils.generate_uuid())
self.parent_resource = MyStackResource('test',
ws_res_snippet,
self.parent_stack)
self.templ = template_format.parse(param_template)
self.simple_template = template_format.parse(simple_template)
@utils.stack_delete_after
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)
@utils.stack_delete_after
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'})
@utils.stack_delete_after
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'})
@utils.stack_delete_after
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
update_result = self.parent_resource.update_with_template(
new_templ, {})
self.assertEqual(self.stack.state, ('UPDATE', 'COMPLETE'))
self.assertEqual(set(self.stack.resources.keys()),
set(["WebServer", "WebServer2"]))
# The stack's owner_id is maintained.
saved_stack = parser.Stack.load(
self.parent_stack.context, self.stack.id)
self.assertEqual(saved_stack.owner_id, self.parent_stack.id)
@utils.stack_delete_after
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 change_state(stack):
self.stack.state_set(parser.Stack.UPDATE, parser.Stack.FAILED, '')
self.m.StubOutWithMock(self.stack, 'update')
self.stack.update(mox.IgnoreArg()).WithSideEffects(change_state)
self.m.ReplayAll()
try:
self.parent_resource.update_with_template(new_templ, {})
except exception.Error as ex:
self.assertEqual('Nested stack update failed: ', ex.message)
self.m.VerifyAll()
@utils.stack_delete_after
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).AndReturn('s')
self.m.ReplayAll()
self.parent_resource.nested()
self.m.VerifyAll()
@utils.stack_delete_after
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)
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_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()
@utils.stack_delete_after
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(parser, 'Template')
parser.Template(self.templ).AndReturn(templ)
self.m.StubOutWithMock(environment, 'Environment')
environment.Environment({"KeyName": "test"}).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)\
.AndReturn(self.stack)
st_set = self.stack.state_set
self.m.StubOutWithMock(self.stack, 'state_set')
self.stack.state_set(parser.Stack.CREATE, parser.Stack.IN_PROGRESS,
"Stack CREATE started").WithSideEffects(st_set)
self.stack.state_set(parser.Stack.CREATE, parser.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
@utils.stack_delete_after
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
@utils.stack_delete_after
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