]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Add a helper class for metadata
authorZane Bitter <zbitter@redhat.com>
Tue, 3 Jul 2012 16:17:05 +0000 (18:17 +0200)
committerZane Bitter <zbitter@redhat.com>
Wed, 11 Jul 2012 15:20:42 +0000 (11:20 -0400)
With this patch we can now access resource metadata through the 'metadata'
attribute and have it always fetch the latest data from the database. This
reduces the chance of accidentally accessing stale data (due to the
metadata being updated in a different database session via the metadata
server) by providing a single implementation to access it through.

Change-Id: Id411ae891d3eace746d16008a7d58bb19b4f652d
Signed-off-by: Zane Bitter <zbitter@redhat.com>
heat/engine/instance.py
heat/engine/resources.py
heat/engine/wait_condition.py
heat/tests/test_resource.py

index 0bc122381e77bc447addea02113e93a009e52d27..a8f147dc9d388087f23e094962c6489f48458f35 100644 (file)
@@ -187,8 +187,8 @@ class Instance(resources.Resource):
                 attachments.append((json.dumps(metadata),
                                     'cfn-init-data', 'x-cfninitdata'))
 
-            metadata_server = resources.metadata_server()
-            if metadata_server:
+            metadata_server = resources.Metadata.server()
+            if metadata_server is not None:
                 attachments.append((metadata_server,
                                     'cfn-metadata-server', 'x-cfninitdata'))
 
index 5fa519aeb8e72833b7d934a8d69cafbf41769725..99c7e85cecc82c20f23a330cd6d35dde7fcd5c47 100644 (file)
@@ -29,11 +29,39 @@ from heat.engine import auth
 logger = logging.getLogger('heat.engine.resources')
 
 
-def metadata_server():
-    try:
-        return config.FLAGS.heat_metadata_server_url
-    except AttributeError:
-        return None
+class Metadata(object):
+    '''
+    A descriptor for accessing the metadata of a resource while ensuring the
+    most up-to-date data is always obtained from the database.
+    '''
+
+    def __get__(self, resource, resource_class):
+        '''Return the metadata for the owning resource.'''
+        if resource is None:
+            return None
+        if resource.id is None:
+            return resource.parsed_template('Metadata')
+        rs = db_api.resource_get(resource.stack.context, resource.id)
+        rs.refresh(attrs=['rsrc_metadata'])
+        return rs.rsrc_metadata
+
+    def __set__(self, resource, metadata):
+        '''Update the metadata for the owning resource.'''
+        if resource.id is None:
+            raise AttributeError("Resource has not yet been created")
+        rs = db_api.resource_get(resource.stack.context, resource.id)
+        rs.update_and_save({'rsrc_metadata': metadata})
+
+    @staticmethod
+    def server():
+        '''
+        Get the address of the currently registered metadata server. Return
+        None if no server is registered.
+        '''
+        try:
+            return config.FLAGS.heat_metadata_server_url
+        except AttributeError:
+            return None
 
 
 class Resource(object):
@@ -50,6 +78,8 @@ class Resource(object):
     # If True, this resource must be created before it can be referenced.
     strict_dependency = True
 
+    metadata = Metadata()
+
     def __new__(cls, name, json, stack):
         '''Create a new Resource of the appropriate class for its type.'''
 
index 22d508063093bf5bffbeb5270124d2653e3714b3..46c2049943a3649fceb2523a76ff4c935f76163e 100644 (file)
@@ -39,7 +39,7 @@ class WaitConditionHandle(resources.Resource):
 
     def handle_create(self):
         self.instance_id = '%s/stacks/%s/resources/%s' % \
-                           (resources.metadata_server(),
+                           (resources.Metadata.server(),
                             self.stack.id,
                             self.name)
 
index a5bf9db4bae7548555d7f746cda66b508e807fe5..915a2e1499a4939d05efbbddd09dd3a350f77ebd 100644 (file)
@@ -19,11 +19,12 @@ from nose.plugins.attrib import attr
 import mox
 
 import json
+from heat.common import context
 from heat.engine import parser
 from heat.engine import resources
 
 
-@attr(tag=['unit', 'parser', 'stack'])
+@attr(tag=['unit', 'resource'])
 @attr(speed='fast')
 class ResourceTest(unittest.TestCase):
     def setUp(self):
@@ -67,6 +68,41 @@ class ResourceTest(unittest.TestCase):
         self.assertEqual(res.parsed_template('foo'), {})
         self.assertEqual(res.parsed_template('foo', 'bar'), 'bar')
 
+    def test_metadata_default(self):
+        tmpl = {'Type': 'Foo'}
+        res = resources.GenericResource('test_resource', tmpl, self.stack)
+        self.assertEqual(res.metadata, {})
+
+
+@attr(tag=['unit', 'resource'])
+@attr(speed='fast')
+class MetadataTest(unittest.TestCase):
+    def setUp(self):
+        self.m = mox.Mox()
+        tmpl = {
+            'Type': 'Foo',
+            'Metadata': {'Test': 'Initial metadata'}
+        }
+        ctx = context.get_admin_context()
+        self.m.StubOutWithMock(ctx, 'username')
+        ctx.username = 'metadata_test_user'
+        self.stack = parser.Stack(ctx, 'test_stack', parser.Template({}))
+        self.stack.store()
+        self.res = resources.GenericResource('metadata_resource',
+                                             tmpl, self.stack)
+        self.res.create()
+
+    def tearDown(self):
+        self.stack.delete()
+        self.m.UnsetStubs()
+
+    def test_read_initial(self):
+        self.assertEqual(self.res.metadata, {'Test': 'Initial metadata'})
+
+    def test_write(self):
+        test_data = {'Test': 'Newly-written data'}
+        self.res.metadata = test_data
+        self.assertEqual(self.res.metadata, test_data)
 
 # allows testing of the test directly, shown below
 if __name__ == '__main__':