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'))
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):
# 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.'''
def handle_create(self):
self.instance_id = '%s/stacks/%s/resources/%s' % \
- (resources.metadata_server(),
+ (resources.Metadata.server(),
self.stack.id,
self.name)
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):
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__':