]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Update the metadata if an alarm action makes changes
authorAngus Salkeld <asalkeld@redhat.com>
Mon, 25 Feb 2013 00:44:19 +0000 (11:44 +1100)
committerAngus Salkeld <asalkeld@redhat.com>
Mon, 25 Feb 2013 22:16:44 +0000 (09:16 +1100)
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

heat/engine/resource.py
heat/engine/resources/instance.py
heat/engine/resources/wait_condition.py
heat/engine/service.py
heat/tests/test_loadbalancer.py
heat/tests/test_metadata_refresh.py [new file with mode: 0644]
heat/tests/test_waitcondition.py

index 68cb8ef0602b802ca75c95967d0876affdbb1998..6f914a24c72696050ad8213f01136c3fd36c7fc8 100644 (file)
@@ -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):
index 1d66c5ea4d55ce5d31ec400fd10052d8107b526a..cd073533498145a68515dca613afc1671a94d731 100644 (file)
@@ -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
index 0ebe69e34e1957d3de42148a4bb66f4caf140ff3..87b110a5e7445a1ba979131a17a90829e74ab221 100644 (file)
@@ -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)
index 8806dfb4dfd6f07b808667a72e48ad9dc048ee49..ae323fd8713e844c43f50fea1702a7ab915502fb 100644 (file)
@@ -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()
index 33a76b854ee184bf62b99e136ec7cd72f151adaf..76d8074cb26cee977f1a03238c0f4caebc8d91d8 100644 (file)
@@ -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 (file)
index 0000000..22249fe
--- /dev/null
@@ -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()
index e8e73a29bcab0cc53835e2303c7c30241c7b26a5..2977d2179d0c5f2539c752227615d43df9c818a6 100644 (file)
@@ -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()