Make timestamps available in Stack/Resource objects

Provide the creation time and last-updated time as attributes of Stack and
Resource objects so that external code does not need to access the database
in order to retrieve them. Use a Descriptor class so that the latest values
are always fetched from the database.

Change-Id: Ic3fa173b1dc8f2e5dc676a9152e8928ed2290913
Signed-off-by: Zane Bitter <zbitter@redhat.com>
This commit is contained in:
Zane Bitter 2012-07-13 16:46:19 -04:00
parent b0ec487799
commit 07be1d7269
4 changed files with 86 additions and 5 deletions

View File

@ -22,7 +22,7 @@ import logging
from heat.common import exception
from heat.engine import checkeddict
from heat.engine import dependencies
from heat.engine.resources import Resource
from heat.engine import resources
from heat.db import api as db_api
@ -234,6 +234,9 @@ class Stack(object):
DELETE_FAILED = 'DELETE_FAILED'
DELETE_COMPLETE = 'DELETE_COMPLETE'
created_time = resources.Timestamp(db_api.stack_get, 'created_at')
updated_time = resources.Timestamp(db_api.stack_get, 'updated_at')
def __init__(self, context, stack_name, template, parameters=None,
stack_id=None, state=None, state_description='',
timeout_mins=60):
@ -257,7 +260,7 @@ class Stack(object):
self.outputs = self.resolve_static_data(self.t[OUTPUTS])
self.resources = dict((name,
Resource(name, data, self))
resources.Resource(name, data, self))
for (name, data) in self.t[RESOURCES].items())
self.dependencies = self._get_dependencies(self.resources.itervalues())

View File

@ -64,6 +64,32 @@ class Metadata(object):
return None
class Timestamp(object):
'''
A descriptor for fetching an up-to-date timestamp from the database.
'''
def __init__(self, db_fetch, attribute):
'''
Initialise with a function to fetch the database representation of an
object (given a context and ID) and the name of the attribute to
retrieve.
'''
self.db_fetch = db_fetch
self.attribute = attribute
def __get__(self, obj, obj_class):
'''
Get the latest data from the database for the given object and class.
'''
if obj is None or obj.id is None:
return None
o = self.db_fetch(obj.context, obj.id)
o.refresh(attrs=[self.attribute])
return getattr(o, self.attribute)
class Resource(object):
CREATE_IN_PROGRESS = 'IN_PROGRESS'
CREATE_FAILED = 'CREATE_FAILED'
@ -78,6 +104,9 @@ class Resource(object):
# If True, this resource must be created before it can be referenced.
strict_dependency = True
created_time = Timestamp(db_api.resource_get, 'created_at')
updated_time = Timestamp(db_api.resource_get, 'updated_at')
metadata = Metadata()
def __new__(cls, name, json, stack):

View File

@ -17,8 +17,9 @@ import nose
import unittest
from nose.plugins.attrib import attr
import mox
import json
from heat.common import context
from heat.common import exception
from heat.engine import parser
from heat.engine import checkeddict
@ -318,6 +319,20 @@ class ParametersTest(unittest.TestCase):
@attr(tag=['unit', 'parser', 'stack'])
@attr(speed='fast')
class StackTest(unittest.TestCase):
def setUp(self):
self.username = 'parser_stack_test_user'
self.m = mox.Mox()
self.ctx = context.get_admin_context()
self.m.StubOutWithMock(self.ctx, 'username')
self.ctx.username = self.username
self.m.ReplayAll()
def tearDown(self):
self.m.UnsetStubs()
def test_state_defaults(self):
stack = parser.Stack(None, 'test_stack', parser.Template({}))
self.assertEqual(stack.state, None)
@ -341,6 +356,23 @@ class StackTest(unittest.TestCase):
self.assertRaises(exception.NotFound, parser.Stack.load,
None, -1)
def test_created_time(self):
stack = parser.Stack(self.ctx, 'creation_time_test',
parser.Template({}))
self.assertEqual(stack.created_time, None)
stack.store()
self.assertNotEqual(stack.created_time, None)
def test_updated_time(self):
stack = parser.Stack(self.ctx, 'update_time_test',
parser.Template({}))
self.assertEqual(stack.updated_time, None)
stack.store()
stored_time = stack.updated_time
stack.state_set(stack.IN_PROGRESS, 'testing')
self.assertNotEqual(stack.updated_time, None)
self.assertNotEqual(stack.updated_time, stored_time)
# allows testing of the test directly, shown below
if __name__ == '__main__':
sys.argv.append(__file__)

View File

@ -28,11 +28,12 @@ from heat.engine import resources
@attr(speed='fast')
class ResourceTest(unittest.TestCase):
def setUp(self):
self.stack = parser.Stack(None, 'test_stack', parser.Template({}))
self.stack = parser.Stack(None, 'test_stack', parser.Template({}),
stack_id=-1)
def test_state_defaults(self):
tmpl = {'Type': 'Foo'}
res = resources.GenericResource('test_resource', tmpl, self.stack)
res = resources.GenericResource('test_res_def', tmpl, self.stack)
self.assertEqual(res.state, None)
self.assertEqual(res.state_description, '')
@ -48,6 +49,22 @@ class ResourceTest(unittest.TestCase):
res.state_set('blarg', 'wibble')
self.assertEqual(res.state_description, 'wibble')
def test_created_time(self):
tmpl = {'Type': 'Foo'}
res = resources.GenericResource('test_res_new', tmpl, self.stack)
self.assertEqual(res.created_time, None)
res._store()
self.assertNotEqual(res.created_time, None)
def test_updated_time(self):
tmpl = {'Type': 'Foo'}
res = resources.GenericResource('test_res_upd', tmpl, self.stack)
res._store()
stored_time = res.updated_time
res.state_set(res.CREATE_IN_PROGRESS, 'testing')
self.assertNotEqual(res.updated_time, None)
self.assertNotEqual(res.updated_time, stored_time)
def test_parsed_template(self):
tmpl = {
'Type': 'Foo',