Merge "implement stack metadata"

This commit is contained in:
Jenkins 2013-07-03 07:51:07 +00:00 committed by Gerrit Code Review
commit f50cbe4ac2
5 changed files with 273 additions and 5 deletions

View File

@ -59,7 +59,7 @@ class Stack(object):
def __init__(self, context, stack_name, tmpl, env=None,
stack_id=None, action=None, status=None,
status_reason='', timeout_mins=60, resolve_data=True,
disable_rollback=True):
disable_rollback=True, parent_resource=None):
'''
Initialise from a context, name, Template object and (optionally)
Environment object. The database ID may also be initialised, if the
@ -82,6 +82,7 @@ class Stack(object):
self.status_reason = status_reason
self.timeout_mins = timeout_mins
self.disable_rollback = disable_rollback
self.parent_resource = parent_resource
resources.initialise()
@ -127,7 +128,8 @@ class Stack(object):
return deps
@classmethod
def load(cls, context, stack_id=None, stack=None, resolve_data=True):
def load(cls, context, stack_id=None, stack=None, resolve_data=True,
parent_resource=None):
'''Retrieve a Stack from the database.'''
if stack is None:
stack = db_api.stack_get(context, stack_id)
@ -139,7 +141,8 @@ class Stack(object):
env = environment.Environment(stack.parameters)
stack = cls(context, stack.name, template, env,
stack.id, stack.action, stack.status, stack.status_reason,
stack.timeout, resolve_data, stack.disable_rollback)
stack.timeout, resolve_data, stack.disable_rollback,
parent_resource)
return stack
@ -544,6 +547,8 @@ def resolve_static_data(template, stack, parameters, snippet):
parameters=parameters),
functools.partial(template.resolve_availability_zones,
stack=stack),
functools.partial(template.resolve_resource_facade,
stack=stack),
template.resolve_find_in_map,
template.reduce_joins])

View File

@ -51,7 +51,8 @@ class StackResource(resource.Resource):
'''
if self._nested is None and self.resource_id is not None:
self._nested = parser.Stack.load(self.context,
self.resource_id)
self.resource_id,
parent_resource=self)
if self._nested is None:
raise exception.NotFound('Nested stack not found in DB')
@ -73,7 +74,8 @@ class StackResource(resource.Resource):
template,
environment.Environment(user_params),
timeout_mins=timeout_mins,
disable_rollback=True)
disable_rollback=True,
parent_resource=self)
nested_id = self._nested.store(self.stack)
self.resource_id_set(nested_id)

View File

@ -361,6 +361,30 @@ class Template(collections.Mapping):
return _resolve(lambda k, v: k == 'Fn::Base64', handle_base64, s)
@staticmethod
def resolve_resource_facade(s, stack):
'''
Resolve constructs of the form {'Fn::ResourceFacade': 'Metadata'}
'''
resource_attributes = ('Metadata', 'DeletionPolicy', 'UpdatePolicy')
def handle_resource_facade(arg):
if arg not in resource_attributes:
raise ValueError(
'Incorrect arguments to "Fn::ResourceFacade" %s: %s' %
('should be one of', str(resource_attributes)))
try:
if arg == 'Metadata':
return stack.parent_resource.metadata
return stack.parent_resource.t[arg]
except KeyError:
raise KeyError('"%s" is not specified in parent resource' %
arg)
return _resolve(lambda k, v: k == 'Fn::ResourceFacade',
handle_resource_facade,
s)
def _resolve(match, handle, snippet):
'''

View File

@ -18,6 +18,7 @@ import time
import uuid
from heat.common import context
from heat.engine import environment
from heat.common import exception
from heat.common import template_format
from heat.engine import clients
@ -457,6 +458,54 @@ Mappings:
parser.Template.resolve_replace(snippet),
'"foo" is "${var3}"')
def test_resource_facade(self):
metadata_snippet = {'Fn::ResourceFacade': 'Metadata'}
deletion_policy_snippet = {'Fn::ResourceFacade': 'DeletionPolicy'}
update_policy_snippet = {'Fn::ResourceFacade': 'UpdatePolicy'}
class DummyClass:
pass
parent_resource = DummyClass()
parent_resource.metadata = '{"foo": "bar"}'
parent_resource.t = {'DeletionPolicy': 'Retain',
'UpdatePolicy': '{"foo": "bar"}'}
stack = parser.Stack(None, 'test_stack',
parser.Template({}),
parent_resource=parent_resource)
self.assertEqual(
parser.Template.resolve_resource_facade(metadata_snippet, stack),
'{"foo": "bar"}')
self.assertEqual(
parser.Template.resolve_resource_facade(deletion_policy_snippet,
stack), 'Retain')
self.assertEqual(
parser.Template.resolve_resource_facade(update_policy_snippet,
stack), '{"foo": "bar"}')
def test_resource_facade_invalid_arg(self):
snippet = {'Fn::ResourceFacade': 'wibble'}
stack = parser.Stack(None, 'test_stack', parser.Template({}))
self.assertRaises(ValueError,
parser.Template.resolve_resource_facade,
snippet,
stack)
def test_resource_facade_missing_key(self):
snippet = {'Fn::ResourceFacade': 'DeletionPolicy'}
class DummyClass:
pass
parent_resource = DummyClass()
parent_resource.metadata = '{"foo": "bar"}'
parent_resource.t = {}
stack = parser.Stack(None, 'test_stack',
parser.Template({}),
parent_resource=parent_resource)
self.assertRaises(KeyError,
parser.Template.resolve_resource_facade,
snippet,
stack)
class StackTest(HeatTestCase):
def setUp(self):
@ -517,6 +566,33 @@ class StackTest(HeatTestCase):
self.assertRaises(exception.NotFound, parser.Stack.load,
None, -1)
@stack_delete_after
def test_load_parent_resource(self):
self.stack = parser.Stack(self.ctx, 'load_parent_resource',
parser.Template({}))
self.stack.store()
stack = db_api.stack_get(self.ctx, self.stack.id)
t = template.Template.load(self.ctx, stack.raw_template_id)
self.m.StubOutWithMock(template.Template, 'load')
template.Template.load(self.ctx, stack.raw_template_id).AndReturn(t)
env = environment.Environment(stack.parameters)
self.m.StubOutWithMock(environment, 'Environment')
environment.Environment(stack.parameters).AndReturn(env)
self.m.StubOutWithMock(parser.Stack, '__init__')
parser.Stack.__init__(self.ctx, stack.name, t, env, stack.id,
stack.action, stack.status, stack.status_reason,
stack.timeout, True, stack.disable_rollback,
'parent')
self.m.ReplayAll()
parser.Stack.load(self.ctx, stack_id=self.stack.id,
parent_resource='parent')
self.m.VerifyAll()
# Note tests creating a stack should be decorated with @stack_delete_after
# to ensure the self.stack is properly cleaned up
@stack_delete_after

View File

@ -0,0 +1,161 @@
# 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.
from heat.common import template_format
from heat.common import context
from heat.common import exception
from heat.engine import parser
from heat.engine import resource
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.utils import setup_dummy_db
from heat.tests.utils import stack_delete_after
ws_res_snippet = {"Type": "some_magic_type",
"metadata": {
"key": "value",
"some": "more stuff"}}
wp_template = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "WordPress",
"Parameters" : {
"KeyName" : {
"Description" : "KeyName",
"Type" : "String",
"Default" : "test"
}
},
"Resources" : {
"WebServer": {
"Type": "AWS::EC2::Instance",
"metadata": {"Fn::ResourceFacade": "Metadata"},
"Properties": {
"ImageId" : "F17-x86_64-gold",
"InstanceType" : "m1.large",
"KeyName" : "test",
"UserData" : "wordpress"
}
}
}
}
'''
class MyStackResource(stack_resource.StackResource,
generic_rsrc.GenericResource):
def physical_resource_name(self):
return "cb2f2b28-a663-4683-802c-4b40c916e1ff"
class StackResourceTest(HeatTestCase):
def setUp(self):
super(StackResourceTest, self).setUp()
setup_dummy_db()
resource._register_class('some_magic_type',
MyStackResource)
t = parser.Template({template.RESOURCES:
{"provider_resource": ws_res_snippet}})
self.parent_stack = parser.Stack(None, 'test_stack', t,
stack_id=uuidutils.generate_uuid())
self.parent_resource = MyStackResource('test',
ws_res_snippet,
self.parent_stack)
self.parent_resource.context = context.get_admin_context()
self.templ = template_format.parse(wp_template)
@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)
@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()
@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()