From a7d3879d7c7c3d67d6d58a05fed1319b52bf082f Mon Sep 17 00:00:00 2001 From: Steven Hardy Date: Thu, 12 Jul 2012 12:56:44 +0100 Subject: [PATCH] heat engine : Store stack timeout in database Store the TimeoutInMinutes common query parameter value in the DB so it can be returned correctly with show_stack/DescribeStacks ref #125 Change-Id: I4ccc79b586087f61e415d5d2c24b7bd52844399a Signed-off-by: Steven Hardy --- .../versions/010_add_stack_timeout_col.py | 25 +++++++++++++++++++ heat/db/sqlalchemy/models.py | 1 + heat/engine/api.py | 3 ++- heat/engine/manager.py | 11 +++++--- heat/engine/parser.py | 13 ++++++---- heat/tests/test_engine_api_utils.py | 10 ++++---- 6 files changed, 48 insertions(+), 15 deletions(-) create mode 100644 heat/db/sqlalchemy/migrate_repo/versions/010_add_stack_timeout_col.py diff --git a/heat/db/sqlalchemy/migrate_repo/versions/010_add_stack_timeout_col.py b/heat/db/sqlalchemy/migrate_repo/versions/010_add_stack_timeout_col.py new file mode 100644 index 00000000..49b97ff4 --- /dev/null +++ b/heat/db/sqlalchemy/migrate_repo/versions/010_add_stack_timeout_col.py @@ -0,0 +1,25 @@ +from sqlalchemy import * +from migrate import * + + +def upgrade(migrate_engine): + meta = MetaData(bind=migrate_engine) + stack = Table('stack', meta, autoload=True) + + # Note hard-coded default 60 (minutes) here from the value in the + # engine, means we can upgrade and populate existing rows + try: + col = Column('timeout', Integer, nullable=False, default=60) + col.create(stack, populate_default=True) + except Exception as ex: + print "Caught exception adding timeout column to stacks %s" % ex + # *Hack-alert* Sqlite in the unit tests can't handle the above + # approach to nullable=False, so retry with nullable=True + Column('timeout', Integer, nullable=True, default=60).create(stack) + + +def downgrade(migrate_engine): + meta = MetaData(bind=migrate_engine) + stack = Table('stack', meta, autoload=True) + + stack.c.timeout.drop() diff --git a/heat/db/sqlalchemy/models.py b/heat/db/sqlalchemy/models.py index 625ee7fd..c92bf131 100644 --- a/heat/db/sqlalchemy/models.py +++ b/heat/db/sqlalchemy/models.py @@ -152,6 +152,7 @@ class Stack(BASE, HeatBase): user_creds_id = Column(Integer, ForeignKey('user_creds.id'), nullable=False) owner_id = Column(Integer, nullable=True) + timeout = Column(Integer) class UserCreds(BASE, HeatBase): diff --git a/heat/engine/api.py b/heat/engine/api.py index d11c405c..982988d1 100644 --- a/heat/engine/api.py +++ b/heat/engine/api.py @@ -71,7 +71,7 @@ def extract_args(params): logger.exception('create timeout conversion') else: if timeout_mins > 0: - kwargs['timeout_in_minutes'] = timeout_mins + kwargs['timeout_mins'] = timeout_mins return kwargs @@ -162,6 +162,7 @@ def format_stack(stack, keys=None): STACK_TMPL_DESCRIPTION: stack.t[parser.DESCRIPTION], STACK_STATUS: s.status, STACK_STATUS_DATA: s.status_reason, + STACK_TIMEOUT: stack.timeout_mins, } # only show the outputs on a completely created stack diff --git a/heat/engine/manager.py b/heat/engine/manager.py index 247ccb1e..6ee9df5e 100644 --- a/heat/engine/manager.py +++ b/heat/engine/manager.py @@ -122,16 +122,19 @@ class EngineManager(manager.Manager): return {'Error': 'Stack already exists with that name.'} tmpl = parser.Template(template) - user_params = parser.Parameters(stack_name, tmpl, - api.extract_user_params(params)) - stack = parser.Stack(context, stack_name, tmpl, user_params) + # Extract the template parameters, and any common query parameters + template_params = parser.Parameters(stack_name, tmpl, + api.extract_user_params(params)) + common_params = api.extract_args(params) + stack = parser.Stack(context, stack_name, tmpl, template_params, + **common_params) response = stack.validate() if response['Description'] != 'Successfully validated': return response stack_id = stack.store() - greenpool.spawn_n(stack.create, **api.extract_args(params)) + greenpool.spawn_n(stack.create) return {'StackId': stack.stack_id()} diff --git a/heat/engine/parser.py b/heat/engine/parser.py index e3c02e16..1c0cc8b5 100644 --- a/heat/engine/parser.py +++ b/heat/engine/parser.py @@ -235,7 +235,8 @@ class Stack(object): DELETE_COMPLETE = 'DELETE_COMPLETE' def __init__(self, context, stack_name, template, parameters=None, - stack_id=None, state=None, state_description=''): + stack_id=None, state=None, state_description='', + timeout_mins=60): ''' Initialise from a context, name, Template object and (optionally) Parameters object. The database ID may also be initialised, if the @@ -247,6 +248,7 @@ class Stack(object): self.name = stack_name self.state = state self.state_description = state_description + self.timeout_mins = timeout_mins if parameters is None: parameters = Parameters(stack_name, template) @@ -280,7 +282,7 @@ class Stack(object): template = Template.load(context, s.raw_template_id) params = Parameters(s.name, template, s.parameters) stack = cls(context, s.name, template, params, - stack_id, s.status, s.status_reason) + stack_id, s.status, s.status_reason, s.timeout) return stack @@ -298,6 +300,7 @@ class Stack(object): 'username': self.context.username, 'status': self.state, 'status_reason': self.state_description, + 'timeout': self.timeout_mins, } new_s = db_api.stack_create(self.context, s) self.id = new_s.id @@ -388,12 +391,12 @@ class Stack(object): stack.update_and_save({'status': new_status, 'status_reason': reason}) - def create(self, timeout_in_minutes=60): + def create(self): ''' Create the stack and all of the resources. Creation will fail if it exceeds the specified timeout. The default is - 60 minutes. + 60 minutes, set in the constructor ''' self.state_set(self.IN_PROGRESS, 'Stack creation started') @@ -401,7 +404,7 @@ class Stack(object): reason = 'Stack successfully created' res = None - with eventlet.Timeout(timeout_in_minutes * 60) as tmo: + with eventlet.Timeout(self.timeout_mins * 60) as tmo: try: for res in self: if stack_status != self.CREATE_FAILED: diff --git a/heat/tests/test_engine_api_utils.py b/heat/tests/test_engine_api_utils.py index 453af34a..9dc589cb 100644 --- a/heat/tests/test_engine_api_utils.py +++ b/heat/tests/test_engine_api_utils.py @@ -72,23 +72,23 @@ class EngineApiTest(unittest.TestCase): def test_timeout_extract(self): p = {'TimeoutInMinutes': '5'} args = api.extract_args(p) - self.assertEqual(args['timeout_in_minutes'], 5) + self.assertEqual(args['timeout_mins'], 5) def test_timeout_extract_zero(self): p = {'TimeoutInMinutes': '0'} args = api.extract_args(p) - self.assertTrue('timeout_in_minutes' not in args) + self.assertTrue('timeout_mins' not in args) def test_timeout_extract_garbage(self): p = {'TimeoutInMinutes': 'wibble'} args = api.extract_args(p) - self.assertTrue('timeout_in_minutes' not in args) + self.assertTrue('timeout_mins' not in args) def test_timeout_extract_none(self): p = {'TimeoutInMinutes': None} args = api.extract_args(p) - self.assertTrue('timeout_in_minutes' not in args) + self.assertTrue('timeout_mins' not in args) def test_timeout_extract_not_present(self): args = api.extract_args({}) - self.assertTrue('timeout_in_minutes' not in args) + self.assertTrue('timeout_mins' not in args) -- 2.45.2