From c538c6b6e4a56209bc54b54b1b9dafd17e388b5d Mon Sep 17 00:00:00 2001 From: Angus Salkeld Date: Fri, 1 Mar 2013 09:40:59 +1100 Subject: [PATCH] Do a metadata refresh after an explicit metadata write. if we have a template with (logically) the following: instance2: metadata GetAtt waitcond.data waitcond: when the waitcond is fired, it updates the metadata but it needs a call to update the references in "instance2's" metadata. bug #1135229 Signed-off-by: Angus Salkeld Change-Id: I1a6af9112ba100b17075063f59165b448d6a11f4 --- heat/engine/service.py | 8 ++ heat/tests/test_metadata_refresh.py | 125 +++++++++++++++++++++++++++- 2 files changed, 132 insertions(+), 1 deletion(-) diff --git a/heat/engine/service.py b/heat/engine/service.py index 27cd83bc..ee8bbceb 100644 --- a/heat/engine/service.py +++ b/heat/engine/service.py @@ -485,6 +485,14 @@ class EngineService(service.Service): resource = stack[resource_name] resource.metadata_update(new_metadata=metadata) + # Refresh the metadata for all other resources, since we expect + # resource_name to be a WaitCondition resource, and other + # resources may refer to WaitCondition Fn::GetAtt Data, which + # is updated here. + for res in stack: + if res.name != resource_name: + res.metadata_update() + return resource.metadata def _periodic_watcher_task(self, sid): diff --git a/heat/tests/test_metadata_refresh.py b/heat/tests/test_metadata_refresh.py index c7631cfe..fff98ff3 100644 --- a/heat/tests/test_metadata_refresh.py +++ b/heat/tests/test_metadata_refresh.py @@ -19,13 +19,18 @@ import eventlet import unittest from nose.plugins.attrib import attr +from oslo.config import cfg from heat.tests import fakes from heat.tests.utils import stack_delete_after +from heat.common import identifier from heat.common import template_format from heat.engine import parser +from heat.engine import service from heat.engine.resources import instance from heat.common import context +from heat.engine.resources import wait_condition as wc + test_template_metadata = ''' { @@ -73,6 +78,40 @@ test_template_metadata = ''' } ''' +test_template_waitcondition = ''' +{ + "AWSTemplateFormatVersion" : "2010-09-09", + "Description" : "Just a WaitCondition.", + "Parameters" : { + "KeyName" : {"Type" : "String", "Default": "mine" }, + }, + "Resources" : { + "S1": { + "Type": "AWS::EC2::Instance", + "Metadata" : { + "test" : {"Fn::GetAtt": ["WC", "Data"]} + }, + "Properties": { + "ImageId" : "a", + "InstanceType" : "m1.large", + "KeyName" : { "Ref" : "KeyName" }, + "UserData" : "#!/bin/bash -v\n" + } + }, + "WH" : { + "Type" : "AWS::CloudFormation::WaitConditionHandle" + }, + "WC" : { + "Type" : "AWS::CloudFormation::WaitCondition", + "Properties" : { + "Handle" : {"Ref" : "WH"}, + "Timeout" : "5" + } + } + } +} +''' + @attr(tag=['unit', 'resource', 'Metadata']) @attr(speed='slow') @@ -85,7 +124,6 @@ class MetadataRefreshTest(unittest.TestCase): def setUp(self): self.m = mox.Mox() self.m.StubOutWithMock(eventlet, 'sleep') - self.fc = fakes.FakeKeystoneClient() def tearDown(self): @@ -138,3 +176,88 @@ class MetadataRefreshTest(unittest.TestCase): self.assertEqual(cont, 's2-ip=10.0.0.5') self.m.VerifyAll() + + +@attr(tag=['unit', 'resource', 'Metadata']) +@attr(speed='slow') +class WaitCondMetadataUpdateTest(unittest.TestCase): + def setUp(self): + self.m = mox.Mox() + self.ctx = context.get_admin_context() + self.ctx.tenant_id = 'test_tenant' + self.fc = fakes.FakeKeystoneClient() + self.man = service.EngineService('a-host', 'a-topic') + cfg.CONF.set_default('heat_waitcondition_server_url', + 'http://127.0.0.1:8000/v1/waitcondition') + + def tearDown(self): + self.m.UnsetStubs() + + # Note tests creating a stack should be decorated with @stack_delete_after + # to ensure the stack is properly cleaned up + def create_stack(self, stack_name='test_stack', + template=test_template_metadata): + temp = template_format.parse(template) + template = parser.Template(temp) + parameters = parser.Parameters(stack_name, template, {}) + stack = parser.Stack(self.ctx, stack_name, template, parameters, + disable_rollback=True) + + self.stack_id = stack.store() + + self.m.StubOutWithMock(instance.Instance, 'handle_create') + instance.Instance.handle_create().MultipleTimes().AndReturn(None) + + self.m.StubOutWithMock(wc.WaitConditionHandle, 'keystone') + wc.WaitConditionHandle.keystone().MultipleTimes().AndReturn(self.fc) + + id = identifier.ResourceIdentifier('test_tenant', stack.name, + stack.id, '', 'WH') + self.m.StubOutWithMock(wc.WaitConditionHandle, 'identifier') + wc.WaitConditionHandle.identifier().MultipleTimes().AndReturn(id) + + self.m.StubOutWithMock(wc.WaitConditionHandle, 'get_status') + self.m.StubOutWithMock(wc.WaitCondition, '_create_timeout') + self.m.StubOutWithMock(eventlet, 'sleep') + + return stack + + @stack_delete_after + def test_wait_meta(self): + ''' + 1 create stack + 2 assert empty instance metadata + 3 service.metadata_update() + 4 assert valid waitcond metadata + 5 assert valid instance metadata + ''' + + self.stack = self.create_stack(template=test_template_waitcondition) + + wc.WaitCondition._create_timeout().AndReturn(eventlet.Timeout(5)) + wc.WaitConditionHandle.get_status().AndReturn([]) + eventlet.sleep(1).AndReturn(None) + wc.WaitConditionHandle.get_status().AndReturn([]) + eventlet.sleep(1).AndReturn(None) + wc.WaitConditionHandle.get_status().AndReturn(['SUCCESS']) + + self.m.ReplayAll() + self.stack.create() + + s1 = self.stack.resources['S1'] + s1._store() + watch = self.stack.resources['WC'] + + self.assertEqual(watch.FnGetAtt('Data'), '{}') + self.assertEqual(s1.metadata['test'], '{}') + + test_metadata = {'Data': 'foo', 'Reason': 'bar', + 'Status': 'SUCCESS', 'UniqueId': '123'} + self.man.metadata_update(self.ctx, + dict(self.stack.identifier()), + 'WH', test_metadata) + + self.assertEqual(watch.FnGetAtt('Data'), '{"123": "foo"}') + self.assertEqual(s1.metadata['test'], '{"123": "foo"}') + + self.m.VerifyAll() -- 2.45.2