An IO optimised method for accessing resource data

This provides a wrapper over the db_api.resource_data_* methods that
attempts to avoid unnecessary database calls for get operations.

On resource create, all resource data is loaded with a single query and
stored with the resource as a dict. Calling data_set() or data_delete()
clears this dict so the next time data() is called all resource
data is loaded again from the database.

There is a future potential optimisation to prefetch the resource.data on
resource_get_by_name_and_stack, which would result in zero resource data
queries for a Stack.load.

This is part of an effort to reduce the number of database queries required
for a Stack.load operation.

Change-Id: I2564a9f953841d895acd5b853a67aa4bc375b635
Related-Bug: #1306743
This commit is contained in:
Steve Baker 2014-04-18 15:37:08 +12:00
parent db7d45f6a4
commit 7383f40eb0
3 changed files with 47 additions and 6 deletions

View File

@ -163,7 +163,10 @@ class Resource(object):
self.status = resource.status
self.status_reason = resource.status_reason
self.id = resource.id
self.data = resource.data
try:
self._data = db_api.resource_data_get_all(self, resource.data)
except exception.NotFound:
self._data = {}
self.created_time = resource.created_at
self.updated_time = resource.updated_at
else:
@ -176,7 +179,7 @@ class Resource(object):
self.status = self.COMPLETE
self.status_reason = ''
self.id = None
self.data = []
self._data = {}
self.created_time = None
self.updated_time = None
@ -486,7 +489,7 @@ class Resource(object):
# save the resource data
if data and isinstance(data, dict):
for key, value in data.iteritems():
db_api.resource_data_set(self, key, value)
self.data_set(key, value)
# save the resource metadata
self.metadata = metadata
@ -929,3 +932,41 @@ class Resource(object):
},
'Outputs': Attributes.as_outputs(resource_name, cls)
}
def data(self):
'''
Resource data for this resource
Use methods data_set and data_delete to modify the resource data
for this resource.
:returns: a dict representing the resource data for this resource.
'''
if self._data is None and self.id:
try:
self._data = db_api.resource_data_get_all(self)
except exception.NotFound:
pass
return self._data or {}
def data_set(self, key, value, redact=False):
'''Save resource's key/value pair to database.'''
db_api.resource_data_set(self, key, value, redact)
# force fetch all resource data from the database again
self._data = None
def data_delete(self, key):
'''
Remove a resource_data element associated to a resource.
:returns: True if the key existed to delete
'''
try:
db_api.resource_data_delete(self, key)
except exception.NotFound:
return False
else:
# force fetch all resource data from the database again
self._data = None
return True

View File

@ -639,7 +639,7 @@ Resources:
class ResDataResource(generic_rsrc.GenericResource):
def handle_create(self):
db_api.resource_data_set(self, "test", 'A secret value', True)
self.data_set("test", 'A secret value', True)
class ResDataNestedStackTest(NestedStackTest):

View File

@ -82,7 +82,7 @@ class MyResource(Resource):
@my_secret.setter
def my_secret(self, my_secret):
db_api.resource_data_set(self, 'my_secret', my_secret, True)
self.data_set('my_secret', my_secret, True)
class SqlAlchemyTest(HeatTestCase):
@ -275,7 +275,7 @@ class SqlAlchemyTest(HeatTestCase):
self.m.ReplayAll()
stack.create()
rsrc = stack['WebServer']
db_api.resource_data_set(rsrc, 'test', 'test_data')
rsrc.data_set('test', 'test_data')
self.assertEqual('test_data', db_api.resource_data_get(rsrc, 'test'))
db_api.resource_data_delete(rsrc, 'test')
self.assertRaises(exception.NotFound,