From: Angus Salkeld Date: Mon, 25 Feb 2013 00:44:19 +0000 (+1100) Subject: Update the metadata if an alarm action makes changes X-Git-Tag: 2014.1~856^2 X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=cca4de0c855f7224902900a0489df322e09a5bf5;p=openstack-build%2Fheat-build.git Update the metadata if an alarm action makes changes The point of this change? - You have another instances' ip in your metadata and if it gets restarted you want cfn-hup to see the new ip. How is it achieved? - poll for the metadata so you can see these changes (cfn-hup) - when ever an alarm action is run we make sure the metadata is refreshed. bug #1131024 Change-Id: I0798c4da9689b126c3c98cafa63138ff2c484ea9 --- diff --git a/heat/engine/resource.py b/heat/engine/resource.py index 68cb8ef0..6f914a24 100644 --- a/heat/engine/resource.py +++ b/heat/engine/resource.py @@ -493,12 +493,13 @@ class Resource(object): raise NotImplementedError("Update not implemented for Resource %s" % type(self)) - def metadata_update(self, metadata): + def metadata_update(self, new_metadata=None): ''' No-op for resources which don't explicitly override this method ''' - logger.warning("Resource %s does not implement metadata update" % - self.name) + if new_metadata: + logger.warning("Resource %s does not implement metadata update" % + self.name) class GenericResource(Resource): diff --git a/heat/engine/resources/instance.py b/heat/engine/resources/instance.py index 1d66c5ea..cd073533 100644 --- a/heat/engine/resources/instance.py +++ b/heat/engine/resources/instance.py @@ -335,6 +335,13 @@ class Instance(resource.Resource): return status + def metadata_update(self, new_metadata=None): + ''' + Refresh the metadata if new_metadata is None + ''' + if new_metadata is None: + self.metadata = self.parsed_template('Metadata') + def validate(self): ''' Validate any of the provided params diff --git a/heat/engine/resources/wait_condition.py b/heat/engine/resources/wait_condition.py index 0ebe69e3..87b110a5 100644 --- a/heat/engine/resources/wait_condition.py +++ b/heat/engine/resources/wait_condition.py @@ -130,21 +130,24 @@ class WaitConditionHandle(resource.Resource): if sorted(metadata.keys()) == expected_keys: return metadata['Status'] in (SUCCESS, FAILURE) - def metadata_update(self, metadata): + def metadata_update(self, new_metadata=None): ''' Validate and update the resource metadata ''' - if self._metadata_format_ok(metadata): + if new_metadata is None: + return + + if self._metadata_format_ok(new_metadata): rsrc_metadata = self.metadata - if metadata['UniqueId'] in rsrc_metadata: + if new_metadata['UniqueId'] in rsrc_metadata: logger.warning("Overwriting Metadata item for UniqueId %s!" % - metadata['UniqueId']) - new_metadata = {} + new_metadata['UniqueId']) + safe_metadata = {} for k in ('Data', 'Reason', 'Status'): - new_metadata[k] = metadata[k] + safe_metadata[k] = new_metadata[k] # Note we can't update self.metadata directly, as it # is a Metadata descriptor object which only supports get/set - rsrc_metadata.update({metadata['UniqueId']: new_metadata}) + rsrc_metadata.update({new_metadata['UniqueId']: safe_metadata}) self.metadata = rsrc_metadata else: logger.error("Metadata failed validation for %s" % self.name) diff --git a/heat/engine/service.py b/heat/engine/service.py index 8806dfb4..ae323fd8 100644 --- a/heat/engine/service.py +++ b/heat/engine/service.py @@ -482,7 +482,7 @@ class EngineService(service.Service): stack_name=stack.name) resource = stack[resource_name] - resource.metadata_update(metadata) + resource.metadata_update(new_metadata=metadata) return resource.metadata @@ -517,6 +517,10 @@ class EngineService(service.Service): for action in actions: action() + stk = parser.Stack.load(admin_context, stack=stack) + for res in stk: + res.metadata_update() + for wr in wrs: rule = watchrule.WatchRule.load(stack_context, watch=wr) actions = rule.evaluate() diff --git a/heat/tests/test_loadbalancer.py b/heat/tests/test_loadbalancer.py index 33a76b85..76d8074c 100644 --- a/heat/tests/test_loadbalancer.py +++ b/heat/tests/test_loadbalancer.py @@ -103,7 +103,7 @@ class LoadBalancerTest(unittest.TestCase): self.fc.servers.list()[1]) #stack.Stack.create_with_template(mox.IgnoreArg()).AndReturn(None) Metadata.__set__(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(None) + mox.IgnoreArg()).MultipleTimes().AndReturn(None) lb.LoadBalancer.nova().MultipleTimes().AndReturn(self.fc) self.m.ReplayAll() diff --git a/heat/tests/test_metadata_refresh.py b/heat/tests/test_metadata_refresh.py new file mode 100644 index 00000000..22249fe6 --- /dev/null +++ b/heat/tests/test_metadata_refresh.py @@ -0,0 +1,148 @@ +# 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 +import uuid +import time +import datetime +import json + +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 + +import heat.db as db_api +from heat.common import template_format +from heat.common import identifier +from heat.engine import parser +from heat.engine.resources import instance +from heat.common import context + +test_template_metadata = ''' +{ + "AWSTemplateFormatVersion" : "2010-09-09", + "Description" : "", + "Parameters" : { + "KeyName" : {"Type" : "String", "Default": "mine" }, + }, + "Resources" : { + "S1": { + "Type": "AWS::EC2::Instance", + "Metadata" : { + "AWS::CloudFormation::Init" : { + "config" : { + "files" : { + "/tmp/random_file" : { + "content" : { "Fn::Join" : ["", [ + "s2-ip=", {"Fn::GetAtt": ["S2", "PublicIp"]} + ]]}, + "mode" : "000400", + "owner" : "root", + "group" : "root" + } + } + } + } + }, + "Properties": { + "ImageId" : "a", + "InstanceType" : "m1.large", + "KeyName" : { "Ref" : "KeyName" }, + "UserData" : "#!/bin/bash -v\n" + } + }, + "S2": { + "Type": "AWS::EC2::Instance", + "Properties": { + "ImageId" : "a", + "InstanceType" : "m1.large", + "KeyName" : { "Ref" : "KeyName" }, + "UserData" : "#!/bin/bash -v\n" + } + } + } +} +''' + + +@attr(tag=['unit', 'resource', 'Metadata']) +@attr(speed='slow') +class MetadataRefreshTest(unittest.TestCase): + ''' + The point of the test is to confirm that metadata gets updated + when FnGetAtt() returns something different. + gets called. + ''' + def setUp(self): + self.m = mox.Mox() + self.m.StubOutWithMock(eventlet, 'sleep') + + self.fc = fakes.FakeKeystoneClient() + + 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, params={}, + stub=True): + temp = template_format.parse(template) + template = parser.Template(temp) + parameters = parser.Parameters(stack_name, template, params) + ctx = context.get_admin_context() + ctx.tenant_id = 'test_tenant' + stack = parser.Stack(ctx, stack_name, template, parameters, + disable_rollback=True) + + self.stack_id = stack.store() + + if stub: + self.m.StubOutWithMock(instance.Instance, 'handle_create') + instance.Instance.handle_create().MultipleTimes().AndReturn(None) + self.m.StubOutWithMock(instance.Instance, 'FnGetAtt') + + return stack + + @stack_delete_after + def test_FnGetAtt(self): + self.stack = self.create_stack() + + instance.Instance.FnGetAtt('PublicIp').AndReturn('1.2.3.5') + + # called by metadata_update() + instance.Instance.FnGetAtt('PublicIp').AndReturn('10.0.0.5') + + self.m.ReplayAll() + self.stack.create() + + s1 = self.stack.resources['S1'] + s2 = self.stack.resources['S2'] + files = s1.metadata['AWS::CloudFormation::Init']['config']['files'] + cont = files['/tmp/random_file']['content'] + self.assertEqual(cont, 's2-ip=1.2.3.5') + + s1.metadata_update() + s2.metadata_update() + files = s1.metadata['AWS::CloudFormation::Init']['config']['files'] + cont = files['/tmp/random_file']['content'] + self.assertEqual(cont, 's2-ip=10.0.0.5') + + self.m.VerifyAll() diff --git a/heat/tests/test_waitcondition.py b/heat/tests/test_waitcondition.py index e8e73a29..2977d217 100644 --- a/heat/tests/test_waitcondition.py +++ b/heat/tests/test_waitcondition.py @@ -258,13 +258,13 @@ class WaitConditionTest(unittest.TestCase): test_metadata = {'Data': 'foo', 'Reason': 'bar', 'Status': 'SUCCESS', 'UniqueId': '123'} - handle.metadata_update(test_metadata) + handle.metadata_update(new_metadata=test_metadata) wc_att = resource.FnGetAtt('Data') self.assertEqual(wc_att, '{"123": "foo"}') test_metadata = {'Data': 'dog', 'Reason': 'cat', 'Status': 'SUCCESS', 'UniqueId': '456'} - handle.metadata_update(test_metadata) + handle.metadata_update(new_metadata=test_metadata) wc_att = resource.FnGetAtt('Data') self.assertEqual(wc_att, u'{"123": "foo", "456": "dog"}') self.m.VerifyAll() @@ -457,7 +457,7 @@ class WaitConditionHandleTest(unittest.TestCase): test_metadata = {'Data': 'foo', 'Reason': 'bar', 'Status': 'SUCCESS', 'UniqueId': '123'} - resource.metadata_update(test_metadata) + resource.metadata_update(new_metadata=test_metadata) handle_metadata = {u'123': {u'Data': u'foo', u'Reason': u'bar', u'Status': u'SUCCESS'}} @@ -472,31 +472,39 @@ class WaitConditionHandleTest(unittest.TestCase): # metadata_update should raise a ValueError if the metadata # is missing any of the expected keys err_metadata = {'Data': 'foo', 'Status': 'SUCCESS', 'UniqueId': '123'} - self.assertRaises(ValueError, resource.metadata_update, err_metadata) + self.assertRaises(ValueError, resource.metadata_update, + new_metadata=err_metadata) err_metadata = {'Data': 'foo', 'Reason': 'bar', 'UniqueId': '1234'} - self.assertRaises(ValueError, resource.metadata_update, err_metadata) + self.assertRaises(ValueError, resource.metadata_update, + new_metadata=err_metadata) err_metadata = {'Data': 'foo', 'Reason': 'bar', 'UniqueId': '1234'} - self.assertRaises(ValueError, resource.metadata_update, err_metadata) + self.assertRaises(ValueError, resource.metadata_update, + new_metadata=err_metadata) err_metadata = {'data': 'foo', 'reason': 'bar', 'status': 'SUCCESS', 'uniqueid': '1234'} - self.assertRaises(ValueError, resource.metadata_update, err_metadata) + self.assertRaises(ValueError, resource.metadata_update, + new_metadata=err_metadata) # Also any Status other than SUCCESS or FAILURE should be rejected err_metadata = {'Data': 'foo', 'Reason': 'bar', 'Status': 'UCCESS', 'UniqueId': '123'} - self.assertRaises(ValueError, resource.metadata_update, err_metadata) + self.assertRaises(ValueError, resource.metadata_update, + new_metadata=err_metadata) err_metadata = {'Data': 'foo', 'Reason': 'bar', 'Status': 'wibble', 'UniqueId': '123'} - self.assertRaises(ValueError, resource.metadata_update, err_metadata) + self.assertRaises(ValueError, resource.metadata_update, + new_metadata=err_metadata) err_metadata = {'Data': 'foo', 'Reason': 'bar', 'Status': 'success', 'UniqueId': '123'} - self.assertRaises(ValueError, resource.metadata_update, err_metadata) + self.assertRaises(ValueError, resource.metadata_update, + new_metadata=err_metadata) err_metadata = {'Data': 'foo', 'Reason': 'bar', 'Status': 'FAIL', 'UniqueId': '123'} - self.assertRaises(ValueError, resource.metadata_update, err_metadata) + self.assertRaises(ValueError, resource.metadata_update, + new_metadata=err_metadata) self.m.VerifyAll() @stack_delete_after @@ -512,12 +520,12 @@ class WaitConditionHandleTest(unittest.TestCase): test_metadata = {'Data': 'foo', 'Reason': 'bar', 'Status': 'SUCCESS', 'UniqueId': '123'} - resource.metadata_update(test_metadata) + resource.metadata_update(new_metadata=test_metadata) self.assertEqual(resource.get_status(), ['SUCCESS']) test_metadata = {'Data': 'foo', 'Reason': 'bar', 'Status': 'SUCCESS', 'UniqueId': '456'} - resource.metadata_update(test_metadata) + resource.metadata_update(new_metadata=test_metadata) self.assertEqual(resource.get_status(), ['SUCCESS', 'SUCCESS']) # re-stub keystone() with fake client or stack delete fails @@ -532,16 +540,16 @@ class WaitConditionHandleTest(unittest.TestCase): test_metadata = {'Data': 'foo', 'Reason': 'bar', 'Status': 'SUCCESS', 'UniqueId': '123'} - resource.metadata_update(test_metadata) + resource.metadata_update(new_metadata=test_metadata) self.assertEqual(resource.get_status_reason('SUCCESS'), 'bar') test_metadata = {'Data': 'dog', 'Reason': 'cat', 'Status': 'SUCCESS', 'UniqueId': '456'} - resource.metadata_update(test_metadata) + resource.metadata_update(new_metadata=test_metadata) self.assertEqual(resource.get_status_reason('SUCCESS'), 'bar;cat') test_metadata = {'Data': 'boo', 'Reason': 'hoo', 'Status': 'FAILURE', 'UniqueId': '789'} - resource.metadata_update(test_metadata) + resource.metadata_update(new_metadata=test_metadata) self.assertEqual(resource.get_status_reason('FAILURE'), 'hoo') self.m.VerifyAll()