From e85078924d6af9f6c7813fb466728113305f809a Mon Sep 17 00:00:00 2001 From: Steven Hardy Date: Tue, 18 Jun 2013 18:07:57 +0100 Subject: [PATCH] Convert Stack to separate action/status Next and hopefully final step in decoupling action/status This adjusts the Stack DB columns, model and class to be consistent with Event/Resource, and split action/status. This also modifies the both APIs to join the action/status to avoid changing the API Change-Id: Ifbdf254c62cad271b775b88de5b873f4e0b6d736 --- heat/api/cfn/v1/stacks.py | 10 +- heat/api/openstack/v1/stacks.py | 8 + .../migrate_repo/versions/020_stack_action.py | 37 ++++ heat/db/sqlalchemy/models.py | 1 + heat/engine/api.py | 7 +- heat/engine/parser.py | 130 ++++++------- heat/engine/stack_resource.py | 3 +- heat/engine/watchrule.py | 6 +- heat/rpc/api.py | 4 +- heat/tests/test_api_cfn_v1.py | 9 +- heat/tests/test_api_openstack_v1.py | 6 +- heat/tests/test_engine_service.py | 4 +- heat/tests/test_metadata_refresh.py | 6 +- heat/tests/test_nested_stack.py | 2 +- heat/tests/test_parser.py | 175 ++++++++++++------ heat/tests/test_vpc.py | 4 +- heat/tests/test_watch.py | 3 +- 17 files changed, 260 insertions(+), 155 deletions(-) create mode 100644 heat/db/sqlalchemy/migrate_repo/versions/020_stack_action.py diff --git a/heat/api/cfn/v1/stacks.py b/heat/api/cfn/v1/stacks.py index 21966d05..b0f66055 100644 --- a/heat/api/cfn/v1/stacks.py +++ b/heat/api/cfn/v1/stacks.py @@ -125,13 +125,16 @@ class StackController(object): engine_api.STACK_UPDATED_TIME: 'LastUpdatedTime', engine_api.STACK_ID: 'StackId', engine_api.STACK_NAME: 'StackName', - engine_api.STACK_STATUS: 'StackStatus', engine_api.STACK_STATUS_DATA: 'StackStatusReason', engine_api.STACK_TMPL_DESCRIPTION: 'TemplateDescription', } result = api_utils.reformat_dict_keys(keymap, s) + action = s[engine_api.STACK_ACTION] + status = s[engine_api.STACK_STATUS] + result['StackStatus'] = '_'.join((action, status)) + # AWS docs indicate DeletionTime is ommitted for current stacks # This is still TODO(unknown) in the engine, we don't keep data for # stacks after they are deleted @@ -195,13 +198,16 @@ class StackController(object): engine_api.STACK_PARAMETERS: 'Parameters', engine_api.STACK_ID: 'StackId', engine_api.STACK_NAME: 'StackName', - engine_api.STACK_STATUS: 'StackStatus', engine_api.STACK_STATUS_DATA: 'StackStatusReason', engine_api.STACK_TIMEOUT: 'TimeoutInMinutes', } result = api_utils.reformat_dict_keys(keymap, s) + action = s[engine_api.STACK_ACTION] + status = s[engine_api.STACK_STATUS] + result['StackStatus'] = '_'.join((action, status)) + # Reformat outputs, these are handled separately as they are # only present in the engine output for a completely created # stack diff --git a/heat/api/openstack/v1/stacks.py b/heat/api/openstack/v1/stacks.py index 6e7f897d..a2ef92a8 100644 --- a/heat/api/openstack/v1/stacks.py +++ b/heat/api/openstack/v1/stacks.py @@ -147,6 +147,14 @@ def format_stack(req, stack, keys=[]): if key == engine_api.STACK_ID: yield ('id', value['stack_id']) yield ('links', [util.make_link(req, value)]) + elif key == engine_api.STACK_ACTION: + return + elif (key == engine_api.STACK_STATUS and + engine_api.STACK_ACTION in stack): + # To avoid breaking API compatibility, we join RES_ACTION + # and RES_STATUS, so the API format doesn't expose the + # internal split of state into action/status + yield (key, '_'.join((stack[engine_api.STACK_ACTION], value))) else: # TODO(zaneb): ensure parameters can be formatted for XML #elif key == engine_api.STACK_PARAMETERS: diff --git a/heat/db/sqlalchemy/migrate_repo/versions/020_stack_action.py b/heat/db/sqlalchemy/migrate_repo/versions/020_stack_action.py new file mode 100644 index 00000000..b919a389 --- /dev/null +++ b/heat/db/sqlalchemy/migrate_repo/versions/020_stack_action.py @@ -0,0 +1,37 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from sqlalchemy import * +from migrate import * + + +def upgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + stack = Table('stack', meta, autoload=True) + # Align with action/status now used in the event/resource tables + Column('action', String(length=255, + convert_unicode=False, + assert_unicode=None, + unicode_error=None, + _warn_on_bytestring=False)).create(stack) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + stack = Table('stack', meta, autoload=True) + stack.c.action.drop() diff --git a/heat/db/sqlalchemy/models.py b/heat/db/sqlalchemy/models.py index bffe24cb..a239774d 100644 --- a/heat/db/sqlalchemy/models.py +++ b/heat/db/sqlalchemy/models.py @@ -155,6 +155,7 @@ class Stack(BASE, HeatBase): raw_template = relationship(RawTemplate, backref=backref('stack')) username = Column(String) tenant = Column(String) + action = Column('action', String) status = Column('status', String) status_reason = Column('status_reason', String) parameters = Column('parameters', Json) diff --git a/heat/engine/api.py b/heat/engine/api.py index ad338b71..449395ba 100644 --- a/heat/engine/api.py +++ b/heat/engine/api.py @@ -76,15 +76,16 @@ def format_stack(stack): STACK_PARAMETERS: stack.parameters.map(str), STACK_DESCRIPTION: stack.t[template.DESCRIPTION], STACK_TMPL_DESCRIPTION: stack.t[template.DESCRIPTION], - STACK_STATUS: stack.state, - STACK_STATUS_DATA: stack.state_description, + STACK_ACTION: stack.action or '', + STACK_STATUS: stack.status or '', + STACK_STATUS_DATA: stack.status_reason, STACK_CAPABILITIES: [], # TODO Not implemented yet STACK_DISABLE_ROLLBACK: stack.disable_rollback, STACK_TIMEOUT: stack.timeout_mins, } # only show the outputs on a completely created or updated stack - if stack.state in (stack.CREATE_COMPLETE, stack.UPDATE_COMPLETE): + if (stack.action != stack.DELETE and stack.status == stack.COMPLETE): info[STACK_OUTPUTS] = format_stack_outputs(stack, stack.outputs) return info diff --git a/heat/engine/parser.py b/heat/engine/parser.py index f51246a9..92ded021 100644 --- a/heat/engine/parser.py +++ b/heat/engine/parser.py @@ -44,21 +44,8 @@ class Stack(object): ACTIONS = (CREATE, DELETE, UPDATE, ROLLBACK ) = ('CREATE', 'DELETE', 'UPDATE', 'ROLLBACK') - CREATE_IN_PROGRESS = 'CREATE_IN_PROGRESS' - CREATE_FAILED = 'CREATE_FAILED' - CREATE_COMPLETE = 'CREATE_COMPLETE' - - DELETE_IN_PROGRESS = 'DELETE_IN_PROGRESS' - DELETE_FAILED = 'DELETE_FAILED' - DELETE_COMPLETE = 'DELETE_COMPLETE' - - UPDATE_IN_PROGRESS = 'UPDATE_IN_PROGRESS' - UPDATE_COMPLETE = 'UPDATE_COMPLETE' - UPDATE_FAILED = 'UPDATE_FAILED' - - ROLLBACK_IN_PROGRESS = 'ROLLBACK_IN_PROGRESS' - ROLLBACK_COMPLETE = 'ROLLBACK_COMPLETE' - ROLLBACK_FAILED = 'ROLLBACK_FAILED' + STATUSES = (IN_PROGRESS, FAILED, COMPLETE + ) = ('IN_PROGRESS', 'FAILED', 'COMPLETE') created_time = timestamp.Timestamp(db_api.stack_get, 'created_at') updated_time = timestamp.Timestamp(db_api.stack_get, 'updated_at') @@ -66,8 +53,9 @@ class Stack(object): _zones = None def __init__(self, context, stack_name, tmpl, env=None, - stack_id=None, state=None, state_description='', - timeout_mins=60, resolve_data=True, disable_rollback=True): + stack_id=None, action=None, status=None, + status_reason='', timeout_mins=60, resolve_data=True, + disable_rollback=True): ''' Initialise from a context, name, Template object and (optionally) Environment object. The database ID may also be initialised, if the @@ -85,8 +73,9 @@ class Stack(object): self.clients = Clients(context) self.t = tmpl self.name = stack_name - self.state = state - self.state_description = state_description + self.action = action + self.status = status + self.status_reason = status_reason self.timeout_mins = timeout_mins self.disable_rollback = disable_rollback @@ -145,8 +134,8 @@ class Stack(object): template = Template.load(context, stack.raw_template_id) env = environment.Environment(stack.parameters) stack = cls(context, stack.name, template, env, - stack.id, stack.status, stack.status_reason, stack.timeout, - resolve_data, stack.disable_rollback) + stack.id, stack.action, stack.status, stack.status_reason, + stack.timeout, resolve_data, stack.disable_rollback) return stack @@ -165,8 +154,9 @@ class Stack(object): 'user_creds_id': new_creds.id, 'username': self.context.username, 'tenant': self.context.tenant_id, - 'status': self.state, - 'status_reason': self.state_description, + 'action': self.action, + 'status': self.status, + 'status_reason': self.status_reason, 'timeout': self.timeout_mins, 'disable_rollback': self.disable_rollback, } @@ -254,18 +244,31 @@ class Stack(object): if result: raise StackValidationFailed(message=result) - def state_set(self, new_status, reason): + def state_set(self, action, status, reason): '''Update the stack state in the database.''' - self.state = new_status - self.state_description = reason + if action not in self.ACTIONS: + raise ValueError("Invalid action %s" % action) + + if status not in self.STATUSES: + raise ValueError("Invalid status %s" % status) + + self.action = action + self.status = status + self.status_reason = reason if self.id is None: return stack = db_api.stack_get(self.context, self.id) - stack.update_and_save({'status': new_status, + stack.update_and_save({'action': action, + 'status': status, 'status_reason': reason}) + @property + def state(self): + '''Returns state, tuple of action, status.''' + return (self.action, self.status) + def timeout_secs(self): ''' Return the stack creation timeout in seconds, or None if no timeout @@ -288,9 +291,9 @@ class Stack(object): ''' A task to create the stack and all of the resources. ''' - self.state_set(self.CREATE_IN_PROGRESS, 'Stack creation started') + self.state_set(self.CREATE, self.IN_PROGRESS, 'Stack creation started') - stack_status = self.CREATE_COMPLETE + stack_status = self.COMPLETE reason = 'Stack successfully created' res = None @@ -303,15 +306,15 @@ class Stack(object): try: yield create_task() except exception.ResourceFailure as ex: - stack_status = self.CREATE_FAILED + stack_status = self.FAILED reason = 'Resource failed: %s' % str(ex) except scheduler.Timeout: - stack_status = self.CREATE_FAILED + stack_status = self.FAILED reason = 'Timed out' - self.state_set(stack_status, reason) + self.state_set(self.CREATE, stack_status, reason) - if stack_status == self.CREATE_FAILED and not self.disable_rollback: + if stack_status == self.FAILED and not self.disable_rollback: self.delete(action=self.ROLLBACK) def update(self, newstack, action=UPDATE): @@ -328,27 +331,21 @@ class Stack(object): ''' if action not in (self.UPDATE, self.ROLLBACK): logger.error("Unexpected action %s passed to update!" % action) - self.state_set(self.UPDATE_FAILED, "Invalid action %s" % action) + self.state_set(self.UPDATE, self.FAILED, + "Invalid action %s" % action) return - if self.state not in (self.CREATE_COMPLETE, self.UPDATE_COMPLETE, - self.ROLLBACK_COMPLETE): + if self.status != self.COMPLETE: if (action == self.ROLLBACK and - self.state == self.UPDATE_IN_PROGRESS): + self.state == (self.UPDATE, self.IN_PROGRESS)): logger.debug("Starting update rollback for %s" % self.name) else: - if action == self.UPDATE: - self.state_set(self.UPDATE_FAILED, - 'State invalid for update') - else: - self.state_set(self.ROLLBACK_FAILED, - 'State invalid for rollback') + self.state_set(action, self.FAILED, + 'State invalid for %s' % action) return - if action == self.UPDATE: - self.state_set(self.UPDATE_IN_PROGRESS, 'Stack update started') - else: - self.state_set(self.ROLLBACK_IN_PROGRESS, 'Stack rollback started') + self.state_set(self.UPDATE, self.IN_PROGRESS, + 'Stack %s started' % action) # cache all the resources runtime data. for r in self: @@ -416,15 +413,14 @@ class Stack(object): (res.name, self.name)) if action == self.UPDATE: - stack_status = self.UPDATE_COMPLETE reason = 'Stack successfully updated' else: - stack_status = self.ROLLBACK_COMPLETE reason = 'Stack rollback completed' + stack_status = self.COMPLETE except eventlet.Timeout as t: if t is tmo: - stack_status = self.UPDATE_FAILED + stack_status = self.FAILED reason = 'Timed out waiting for %s' % str(res) else: # not my timeout @@ -432,21 +428,17 @@ class Stack(object): except exception.ResourceFailure as e: reason = str(e) or "Error : %s" % type(e) + stack_status = self.FAILED if action == self.UPDATE: - stack_status = self.UPDATE_FAILED # If rollback is enabled, we do another update, with the # existing template, so we roll back to the original state - if self.disable_rollback: - stack_status = self.UPDATE_FAILED - else: + if not self.disable_rollback: oldstack = Stack(self.context, self.name, self.t, self.env) self.update(oldstack, action=self.ROLLBACK) return - else: - stack_status = self.ROLLBACK_FAILED - self.state_set(stack_status, reason) + self.state_set(action, stack_status, reason) # flip the template & environment to the newstack values # Note we do this on success and failure, so the current @@ -466,15 +458,14 @@ class Stack(object): create, which amount to the same thing, but the states are recorded differently. ''' - if action == self.DELETE: - self.state_set(self.DELETE_IN_PROGRESS, 'Stack deletion started') - elif action == self.ROLLBACK: - self.state_set(self.ROLLBACK_IN_PROGRESS, 'Stack rollback started') - else: + if action not in (self.DELETE, self.ROLLBACK): logger.error("Unexpected action %s passed to delete!" % action) - self.state_set(self.DELETE_FAILED, "Invalid action %s" % action) + self.state_set(self.DELETE, self.FAILED, + "Invalid action %s" % action) return + self.state_set(action, self.IN_PROGRESS, 'Stack %s started' % action) + failures = [] for res in reversed(self): try: @@ -485,17 +476,10 @@ class Stack(object): failures.append(str(res)) if failures: - if action == self.DELETE: - self.state_set(self.DELETE_FAILED, - 'Failed to delete ' + ', '.join(failures)) - elif action == self.ROLLBACK: - self.state_set(self.ROLLBACK_FAILED, - 'Failed to rollback ' + ', '.join(failures)) + self.state_set(action, self.FAILED, + 'Failed to %s : %s' % (action, ', '.join(failures))) else: - if action == self.DELETE: - self.state_set(self.DELETE_COMPLETE, 'Deleted successfully') - elif action == self.ROLLBACK: - self.state_set(self.ROLLBACK_COMPLETE, 'Rollback completed') + self.state_set(action, self.COMPLETE, '%s completed' % action) db_api.stack_delete(self.context, self.id) self.id = None diff --git a/heat/engine/stack_resource.py b/heat/engine/stack_resource.py index 1bab2627..170234b7 100644 --- a/heat/engine/stack_resource.py +++ b/heat/engine/stack_resource.py @@ -73,7 +73,8 @@ class StackResource(resource.Resource): def check_create_complete(self, stack_creator): done = stack_creator.step() if done: - if self._nested.state != self._nested.CREATE_COMPLETE: + if self._nested.state != (self._nested.CREATE, + self._nested.COMPLETE): raise exception.Error(self._nested.state_description) return done diff --git a/heat/engine/watchrule.py b/heat/engine/watchrule.py index 26b6aa21..e6db474b 100644 --- a/heat/engine/watchrule.py +++ b/heat/engine/watchrule.py @@ -235,9 +235,9 @@ class WatchRule(object): new_state) else: s = db_api.stack_get(self.context, self.stack_id) - if s and s.status in (parser.Stack.CREATE_COMPLETE, - parser.Stack.UPDATE_COMPLETE): - stack = parser.Stack.load(self.context, stack=s) + stack = parser.Stack.load(self.context, stack=s) + if (stack.action != stack.DELETE + and stack.status == stack.COMPLETE): for a in self.rule[self.ACTION_MAP[new_state]]: actions.append(stack[a].alarm) else: diff --git a/heat/rpc/api.py b/heat/rpc/api.py index 20d7d30e..f9dbacec 100644 --- a/heat/rpc/api.py +++ b/heat/rpc/api.py @@ -25,7 +25,7 @@ STACK_KEYS = ( STACK_CREATION_TIME, STACK_UPDATED_TIME, STACK_DELETION_TIME, STACK_NOTIFICATION_TOPICS, STACK_DESCRIPTION, STACK_TMPL_DESCRIPTION, - STACK_PARAMETERS, STACK_OUTPUTS, + STACK_PARAMETERS, STACK_OUTPUTS, STACK_ACTION, STACK_STATUS, STACK_STATUS_DATA, STACK_CAPABILITIES, STACK_DISABLE_ROLLBACK, STACK_TIMEOUT, ) = ( @@ -33,7 +33,7 @@ STACK_KEYS = ( 'creation_time', 'updated_time', 'deletion_time', 'notification_topics', 'description', 'template_description', - 'parameters', 'outputs', + 'parameters', 'outputs', 'stack_action', 'stack_status', 'stack_status_reason', 'capabilities', 'disable_rollback', 'timeout_mins' ) diff --git a/heat/tests/test_api_cfn_v1.py b/heat/tests/test_api_cfn_v1.py index cf00633c..ba2307e6 100644 --- a/heat/tests/test_api_cfn_v1.py +++ b/heat/tests/test_api_cfn_v1.py @@ -122,7 +122,8 @@ class CfnStackControllerTest(HeatTestCase): u'stack_status_reason': u'Stack successfully created', u'creation_time': u'2012-07-09T09:12:45Z', u'stack_name': u'wordpress', - u'stack_status': u'CREATE_COMPLETE'}] + u'stack_action': u'CREATE', + u'stack_status': u'COMPLETE'}] self.m.StubOutWithMock(rpc, 'call') rpc.call(dummy_req.context, self.topic, {'namespace': None, @@ -220,7 +221,8 @@ class CfnStackControllerTest(HeatTestCase): u'creation_time': u'2012-07-09T09:12:45Z', u'stack_name': u'wordpress', u'notification_topics': [], - u'stack_status': u'CREATE_COMPLETE', + u'stack_action': u'CREATE', + u'stack_status': u'COMPLETE', u'description': u'blah', u'disable_rollback': 'true', u'timeout_mins':60, @@ -309,7 +311,8 @@ class CfnStackControllerTest(HeatTestCase): u'creation_time': u'2012-07-09T09:12:45Z', u'stack_name': u'wordpress', u'notification_topics': [], - u'stack_status': u'CREATE_COMPLETE', + u'stack_action': u'CREATE', + u'stack_status': u'COMPLETE', u'description': u'blah', u'disable_rollback': 'true', u'timeout_mins':60, diff --git a/heat/tests/test_api_openstack_v1.py b/heat/tests/test_api_openstack_v1.py index cb336611..2d8232f3 100644 --- a/heat/tests/test_api_openstack_v1.py +++ b/heat/tests/test_api_openstack_v1.py @@ -267,7 +267,8 @@ class StackControllerTest(ControllerTest, HeatTestCase): u'stack_status_reason': u'Stack successfully created', u'creation_time': u'2012-07-09T09:12:45Z', u'stack_name': identity.stack_name, - u'stack_status': u'CREATE_COMPLETE', + u'stack_action': u'CREATE', + u'stack_status': u'COMPLETE', u'parameters': {}, u'outputs': [], u'notification_topics': [], @@ -604,7 +605,8 @@ class StackControllerTest(ControllerTest, HeatTestCase): u'creation_time': u'2012-07-09T09:12:45Z', u'stack_name': identity.stack_name, u'notification_topics': [], - u'stack_status': u'CREATE_COMPLETE', + u'stack_action': u'CREATE', + u'stack_status': u'COMPLETE', u'description': u'blah', u'disable_rollback': True, u'timeout_mins':60, diff --git a/heat/tests/test_engine_service.py b/heat/tests/test_engine_service.py index 55018db6..c1a4123b 100644 --- a/heat/tests/test_engine_service.py +++ b/heat/tests/test_engine_service.py @@ -220,8 +220,10 @@ class stackCreateTest(HeatTestCase): rsrc = stack.resources['WebServer'] self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state) + self.assertEqual((stack.DELETE, stack.COMPLETE), rsrc.state) self.assertEqual(db_api.stack_get(ctx, stack_id), None) - self.assertEqual(db_s.status, 'DELETE_COMPLETE') + self.assertEqual(db_s.action, 'DELETE') + self.assertEqual(db_s.status, 'COMPLETE') class stackServiceCreateUpdateDeleteTest(HeatTestCase): diff --git a/heat/tests/test_metadata_refresh.py b/heat/tests/test_metadata_refresh.py index bb9edbbb..39458d35 100644 --- a/heat/tests/test_metadata_refresh.py +++ b/heat/tests/test_metadata_refresh.py @@ -174,7 +174,8 @@ class MetadataRefreshTest(HeatTestCase): self.m.ReplayAll() self.stack.create() - self.assertEqual(self.stack.state, self.stack.CREATE_COMPLETE) + self.assertEqual(self.stack.state, + (self.stack.CREATE, self.stack.COMPLETE)) s1 = self.stack.resources['S1'] s2 = self.stack.resources['S2'] @@ -267,7 +268,8 @@ class WaitCondMetadataUpdateTest(HeatTestCase): self.m.ReplayAll() self.stack.create() - self.assertEqual(self.stack.state, self.stack.CREATE_COMPLETE) + self.assertEqual(self.stack.state, + (self.stack.CREATE, self.stack.COMPLETE)) self.assertEqual(watch.FnGetAtt('Data'), '{"123": "foo"}') self.assertEqual(inst.metadata['test'], '{"123": "foo"}') diff --git a/heat/tests/test_nested_stack.py b/heat/tests/test_nested_stack.py index 77d90386..5945b2ff 100644 --- a/heat/tests/test_nested_stack.py +++ b/heat/tests/test_nested_stack.py @@ -55,7 +55,7 @@ Outputs: t = template_format.parse(template) stack = self.parse_stack(t) stack.create() - self.assertEqual(stack.state, stack.CREATE_COMPLETE) + self.assertEqual(stack.state, (stack.CREATE, stack.COMPLETE)) return stack def parse_stack(self, t): diff --git a/heat/tests/test_parser.py b/heat/tests/test_parser.py index d0637077..f323766f 100644 --- a/heat/tests/test_parser.py +++ b/heat/tests/test_parser.py @@ -462,22 +462,40 @@ class StackTest(HeatTestCase): def test_state_defaults(self): stack = parser.Stack(None, 'test_stack', parser.Template({})) - self.assertEqual(stack.state, None) - self.assertEqual(stack.state_description, '') + self.assertEqual(stack.state, (None, None)) + self.assertEqual(stack.status_reason, '') def test_state(self): stack = parser.Stack(None, 'test_stack', parser.Template({}), - state='foo') - self.assertEqual(stack.state, 'foo') - stack.state_set('bar', '') - self.assertEqual(stack.state, 'bar') - - def test_state_description(self): + action=parser.Stack.CREATE, + status=parser.Stack.IN_PROGRESS) + self.assertEqual(stack.state, + (parser.Stack.CREATE, parser.Stack.IN_PROGRESS)) + stack.state_set(parser.Stack.CREATE, parser.Stack.COMPLETE, 'test') + self.assertEqual(stack.state, + (parser.Stack.CREATE, parser.Stack.COMPLETE)) + stack.state_set(parser.Stack.DELETE, parser.Stack.COMPLETE, 'test') + self.assertEqual(stack.state, + (parser.Stack.DELETE, parser.Stack.COMPLETE)) + + def test_state_bad(self): + stack = parser.Stack(None, 'test_stack', parser.Template({}), + action=parser.Stack.CREATE, + status=parser.Stack.IN_PROGRESS) + self.assertEqual(stack.state, + (parser.Stack.CREATE, parser.Stack.IN_PROGRESS)) + self.assertRaises(ValueError, stack.state_set, + 'baad', parser.Stack.COMPLETE, 'test') + self.assertRaises(ValueError, stack.state_set, + parser.Stack.CREATE, 'oops', 'test') + + def test_status_reason(self): stack = parser.Stack(None, 'test_stack', parser.Template({}), - state_description='quux') - self.assertEqual(stack.state_description, 'quux') - stack.state_set('blarg', 'wibble') - self.assertEqual(stack.state_description, 'wibble') + status_reason='quux') + self.assertEqual(stack.status_reason, 'quux') + stack.state_set(parser.Stack.CREATE, parser.Stack.IN_PROGRESS, + 'wibble') + self.assertEqual(stack.status_reason, 'wibble') def test_load_nonexistant_id(self): self.assertRaises(exception.NotFound, parser.Stack.load, @@ -542,7 +560,7 @@ class StackTest(HeatTestCase): self.assertEqual(self.stack.updated_time, None) self.stack.store() stored_time = self.stack.updated_time - self.stack.state_set(self.stack.CREATE_IN_PROGRESS, 'testing') + self.stack.state_set(self.stack.CREATE, self.stack.IN_PROGRESS, 'test') self.assertNotEqual(self.stack.updated_time, None) self.assertNotEqual(self.stack.updated_time, stored_time) @@ -559,7 +577,8 @@ class StackTest(HeatTestCase): db_s = db_api.stack_get(self.ctx, stack_id) self.assertEqual(db_s, None) - self.assertEqual(self.stack.state, self.stack.DELETE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.DELETE, parser.Stack.COMPLETE)) @stack_delete_after def test_delete_rollback(self): @@ -574,7 +593,8 @@ class StackTest(HeatTestCase): db_s = db_api.stack_get(self.ctx, stack_id) self.assertEqual(db_s, None) - self.assertEqual(self.stack.state, self.stack.ROLLBACK_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.ROLLBACK, parser.Stack.COMPLETE)) @stack_delete_after def test_delete_badaction(self): @@ -589,16 +609,20 @@ class StackTest(HeatTestCase): db_s = db_api.stack_get(self.ctx, stack_id) self.assertNotEqual(db_s, None) - self.assertEqual(self.stack.state, self.stack.DELETE_FAILED) + self.assertEqual(self.stack.state, + (parser.Stack.DELETE, parser.Stack.FAILED)) @stack_delete_after def test_update_badstate(self): self.stack = parser.Stack(self.ctx, 'test_stack', parser.Template({}), - state=parser.Stack.CREATE_FAILED) + action=parser.Stack.CREATE, + status=parser.Stack.FAILED) stack_id = self.stack.store() - self.assertEqual(self.stack.state, parser.Stack.CREATE_FAILED) + self.assertEqual(self.stack.state, + (parser.Stack.CREATE, parser.Stack.FAILED)) self.stack.update({}) - self.assertEqual(self.stack.state, parser.Stack.UPDATE_FAILED) + self.assertEqual(self.stack.state, + (parser.Stack.UPDATE, parser.Stack.FAILED)) @stack_delete_after def test_resource_by_refid(self): @@ -608,7 +632,8 @@ class StackTest(HeatTestCase): template.Template(tmpl)) self.stack.store() self.stack.create() - self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.CREATE, parser.Stack.COMPLETE)) self.assertTrue('AResource' in self.stack) rsrc = self.stack['AResource'] rsrc.resource_id_set('aaaa') @@ -630,7 +655,8 @@ class StackTest(HeatTestCase): template.Template(tmpl)) self.stack.store() self.stack.create() - self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.CREATE, parser.Stack.COMPLETE)) tmpl2 = {'Resources': { 'AResource': {'Type': 'GenericResourceType'}, @@ -638,7 +664,8 @@ class StackTest(HeatTestCase): updated_stack = parser.Stack(self.ctx, 'updated_stack', template.Template(tmpl2)) self.stack.update(updated_stack) - self.assertEqual(self.stack.state, parser.Stack.UPDATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.UPDATE, parser.Stack.COMPLETE)) self.assertTrue('BResource' in self.stack) @stack_delete_after @@ -651,14 +678,16 @@ class StackTest(HeatTestCase): template.Template(tmpl)) self.stack.store() self.stack.create() - self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.CREATE, parser.Stack.COMPLETE)) tmpl2 = {'Resources': {'AResource': {'Type': 'GenericResourceType'}}} updated_stack = parser.Stack(self.ctx, 'updated_stack', template.Template(tmpl2)) self.stack.update(updated_stack) - self.assertEqual(self.stack.state, parser.Stack.UPDATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.UPDATE, parser.Stack.COMPLETE)) self.assertFalse('BResource' in self.stack) @stack_delete_after @@ -670,7 +699,8 @@ class StackTest(HeatTestCase): template.Template(tmpl)) self.stack.store() self.stack.create() - self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.CREATE, parser.Stack.COMPLETE)) tmpl2 = {'Description': 'BTemplate', 'Resources': {'AResource': {'Type': 'GenericResourceType'}}} @@ -678,7 +708,8 @@ class StackTest(HeatTestCase): updated_stack = parser.Stack(self.ctx, 'updated_stack', template.Template(tmpl2)) self.stack.update(updated_stack) - self.assertEqual(self.stack.state, parser.Stack.UPDATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.UPDATE, parser.Stack.COMPLETE)) self.assertEqual(self.stack.t[template.DESCRIPTION], 'BTemplate') @stack_delete_after @@ -694,7 +725,8 @@ class StackTest(HeatTestCase): template.Template(tmpl)) self.stack.store() self.stack.create() - self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.CREATE, parser.Stack.COMPLETE)) tmpl2 = {'Resources': {'AResource': {'Type': 'GenericResourceType', 'Properties': {'Foo': 'xyz'}}}} @@ -708,7 +740,8 @@ class StackTest(HeatTestCase): self.m.ReplayAll() self.stack.update(updated_stack) - self.assertEqual(self.stack.state, parser.Stack.UPDATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.UPDATE, parser.Stack.COMPLETE)) self.assertEqual(self.stack['AResource'].properties['Foo'], 'xyz') self.m.VerifyAll() @@ -726,7 +759,8 @@ class StackTest(HeatTestCase): disable_rollback=True) self.stack.store() self.stack.create() - self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.CREATE, parser.Stack.COMPLETE)) res = self.stack['AResource'] res.update_allowed_keys = ('Properties',) @@ -748,7 +782,8 @@ class StackTest(HeatTestCase): self.m.ReplayAll() self.stack.update(updated_stack) - self.assertEqual(self.stack.state, parser.Stack.UPDATE_FAILED) + self.assertEqual(self.stack.state, + (parser.Stack.UPDATE, parser.Stack.FAILED)) self.m.VerifyAll() @stack_delete_after @@ -765,7 +800,8 @@ class StackTest(HeatTestCase): disable_rollback=True) self.stack.store() self.stack.create() - self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.CREATE, parser.Stack.COMPLETE)) tmpl2 = {'Resources': {'AResource': {'Type': 'GenericResourceType', 'Properties': {'Foo': 'xyz'}}}} @@ -784,7 +820,8 @@ class StackTest(HeatTestCase): self.m.ReplayAll() self.stack.update(updated_stack) - self.assertEqual(self.stack.state, parser.Stack.UPDATE_FAILED) + self.assertEqual(self.stack.state, + (parser.Stack.UPDATE, parser.Stack.FAILED)) self.m.VerifyAll() # Unset here so destroy() is not stubbed for stack.delete cleanup self.m.UnsetStubs() @@ -803,7 +840,8 @@ class StackTest(HeatTestCase): disable_rollback=True) self.stack.store() self.stack.create() - self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.CREATE, parser.Stack.COMPLETE)) tmpl2 = {'Resources': {'AResource': {'Type': 'GenericResourceType', 'Properties': {'Foo': 'xyz'}}}} @@ -821,7 +859,8 @@ class StackTest(HeatTestCase): self.m.ReplayAll() self.stack.update(updated_stack) - self.assertEqual(self.stack.state, parser.Stack.UPDATE_FAILED) + self.assertEqual(self.stack.state, + (parser.Stack.UPDATE, parser.Stack.FAILED)) self.m.VerifyAll() @stack_delete_after @@ -832,7 +871,8 @@ class StackTest(HeatTestCase): template.Template(tmpl)) self.stack.store() self.stack.create() - self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.CREATE, parser.Stack.COMPLETE)) tmpl2 = {'Resources': { 'AResource': {'Type': 'GenericResourceType'}, @@ -846,7 +886,8 @@ class StackTest(HeatTestCase): self.m.ReplayAll() self.stack.update(updated_stack) - self.assertEqual(self.stack.state, parser.Stack.UPDATE_FAILED) + self.assertEqual(self.stack.state, + (parser.Stack.UPDATE, parser.Stack.FAILED)) self.assertTrue('BResource' in self.stack) # Reload the stack from the DB and prove that it contains the failed @@ -869,7 +910,8 @@ class StackTest(HeatTestCase): disable_rollback=False) self.stack.store() self.stack.create() - self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.CREATE, parser.Stack.COMPLETE)) tmpl2 = {'Resources': {'AResource': {'Type': 'GenericResourceType', 'Properties': {'Foo': 'xyz'}}}} @@ -889,7 +931,8 @@ class StackTest(HeatTestCase): self.m.ReplayAll() self.stack.update(updated_stack) - self.assertEqual(self.stack.state, parser.Stack.ROLLBACK_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.ROLLBACK, parser.Stack.COMPLETE)) self.assertEqual(self.stack['AResource'].properties['Foo'], 'abc') self.m.VerifyAll() @@ -907,7 +950,8 @@ class StackTest(HeatTestCase): disable_rollback=False) self.stack.store() self.stack.create() - self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.CREATE, parser.Stack.COMPLETE)) tmpl2 = {'Resources': {'AResource': {'Type': 'GenericResourceType', 'Properties': {'Foo': 'xyz'}}}} @@ -927,7 +971,8 @@ class StackTest(HeatTestCase): self.m.ReplayAll() self.stack.update(updated_stack) - self.assertEqual(self.stack.state, parser.Stack.ROLLBACK_FAILED) + self.assertEqual(self.stack.state, + (parser.Stack.ROLLBACK, parser.Stack.FAILED)) self.m.VerifyAll() @stack_delete_after @@ -939,7 +984,8 @@ class StackTest(HeatTestCase): disable_rollback=False) self.stack.store() self.stack.create() - self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.CREATE, parser.Stack.COMPLETE)) tmpl2 = {'Resources': { 'AResource': {'Type': 'GenericResourceType'}, @@ -955,7 +1001,8 @@ class StackTest(HeatTestCase): self.m.ReplayAll() self.stack.update(updated_stack) - self.assertEqual(self.stack.state, parser.Stack.ROLLBACK_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.ROLLBACK, parser.Stack.COMPLETE)) self.assertFalse('BResource' in self.stack) self.m.VerifyAll() @@ -970,7 +1017,8 @@ class StackTest(HeatTestCase): disable_rollback=False) self.stack.store() self.stack.create() - self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.CREATE, parser.Stack.COMPLETE)) tmpl2 = {'Resources': {'AResource': {'Type': 'GenericResourceType'}}} @@ -984,7 +1032,8 @@ class StackTest(HeatTestCase): self.m.ReplayAll() self.stack.update(updated_stack) - self.assertEqual(self.stack.state, parser.Stack.ROLLBACK_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.ROLLBACK, parser.Stack.COMPLETE)) self.assertTrue('BResource' in self.stack) self.m.VerifyAll() # Unset here so delete() is not stubbed for stack.delete cleanup @@ -1023,7 +1072,8 @@ class StackTest(HeatTestCase): self.stack.store() self.stack.create() self.m.VerifyAll() - self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.CREATE, parser.Stack.COMPLETE)) self.assertEqual(self.stack['AResource'].properties['Foo'], 'abc') self.assertEqual(self.stack['BResource'].properties['Foo'], 'AResource') @@ -1042,7 +1092,8 @@ class StackTest(HeatTestCase): updated_stack = parser.Stack(self.ctx, 'updated_stack', template.Template(tmpl2)) self.stack.update(updated_stack) - self.assertEqual(self.stack.state, parser.Stack.UPDATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.UPDATE, parser.Stack.COMPLETE)) self.assertEqual(self.stack['AResource'].properties['Foo'], 'smelly') self.assertEqual(self.stack['BResource'].properties['Foo'], 'inst-007') self.m.VerifyAll() @@ -1082,7 +1133,8 @@ class StackTest(HeatTestCase): self.stack.create() self.m.VerifyAll() - self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.CREATE, parser.Stack.COMPLETE)) self.assertEqual(self.stack['AResource'].properties['Foo'], 'abc') self.assertEqual(self.stack['BResource'].properties['Foo'], 'AResource') @@ -1110,7 +1162,8 @@ class StackTest(HeatTestCase): template.Template(tmpl2), disable_rollback=False) self.stack.update(updated_stack) - self.assertEqual(self.stack.state, parser.Stack.ROLLBACK_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.ROLLBACK, parser.Stack.COMPLETE)) self.assertEqual(self.stack['AResource'].properties['Foo'], 'abc') self.m.VerifyAll() @@ -1150,7 +1203,8 @@ class StackTest(HeatTestCase): self.stack.create() self.m.VerifyAll() - self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.CREATE, parser.Stack.COMPLETE)) self.assertEqual(self.stack['AResource'].properties['Foo'], 'abc') self.assertEqual(self.stack['BResource'].properties['Foo'], 'AResource') @@ -1166,19 +1220,19 @@ class StackTest(HeatTestCase): 'AResource') generic_rsrc.GenericResource.FnGetRefId().AndReturn( 'inst-007') - # self.state_set(self.UPDATE_IN_PROGRESS) + # self.state_set(self.UPDATE, self.IN_PROGRESS) generic_rsrc.GenericResource.FnGetRefId().AndReturn( 'inst-007') - # self.state_set(self.DELETE_IN_PROGRESS) + # self.state_set(self.DELETE, self.IN_PROGRESS) generic_rsrc.GenericResource.FnGetRefId().AndReturn( 'inst-007') - # self.state_set(self.DELETE_COMPLETE) + # self.state_set(self.DELETE, self.COMPLETE) generic_rsrc.GenericResource.FnGetRefId().AndReturn( 'inst-007') # self.properties.validate() generic_rsrc.GenericResource.FnGetRefId().AndReturn( 'inst-007') - # self.state_set(self.CREATE_IN_PROGRESS) + # self.state_set(self.CREATE, self.IN_PROGRESS) generic_rsrc.GenericResource.FnGetRefId().AndReturn( 'inst-007') @@ -1191,10 +1245,10 @@ class StackTest(HeatTestCase): # resource.UpdateReplace because we've not specified the modified # key/property in update_allowed_keys/update_allowed_properties - # self.state_set(self.DELETE_IN_PROGRESS) + # self.state_set(self.DELETE, self.IN_PROGRESS) generic_rsrc.GenericResource.FnGetRefId().AndReturn( 'inst-007') - # self.state_set(self.DELETE_IN_PROGRESS) + # self.state_set(self.DELETE, self.IN_PROGRESS) generic_rsrc.GenericResource.FnGetRefId().AndReturn( 'inst-007') @@ -1211,7 +1265,8 @@ class StackTest(HeatTestCase): template.Template(tmpl2), disable_rollback=False) self.stack.update(updated_stack) - self.assertEqual(self.stack.state, parser.Stack.ROLLBACK_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.ROLLBACK, parser.Stack.COMPLETE)) self.assertEqual(self.stack['AResource'].properties['Foo'], 'abc') self.m.VerifyAll() @@ -1236,8 +1291,9 @@ class StackTest(HeatTestCase): stack.create() - self.assertEqual(stack.state, stack.CREATE_FAILED) - self.assertEqual(stack.state_description, 'Timed out') + self.assertEqual(stack.state, + (parser.Stack.CREATE, parser.Stack.FAILED)) + self.assertEqual(stack.status_reason, 'Timed out') self.m.VerifyAll() @@ -1296,7 +1352,8 @@ class StackTest(HeatTestCase): template.Template(tmpl)) self.stack.store() self.stack.create() - self.assertEqual(self.stack.state, parser.Stack.CREATE_COMPLETE) + self.assertEqual(self.stack.state, + (parser.Stack.CREATE, parser.Stack.COMPLETE)) self.assertTrue('AResource' in self.stack) rsrc = self.stack['AResource'] rsrc.resource_id_set('aaaa') diff --git a/heat/tests/test_vpc.py b/heat/tests/test_vpc.py index 5244ac8e..83695516 100644 --- a/heat/tests/test_vpc.py +++ b/heat/tests/test_vpc.py @@ -460,7 +460,7 @@ Resources: stack = self.create_stack(self.test_template) try: - self.assertEqual(stack.CREATE_COMPLETE, stack.state) + self.assertEqual((stack.CREATE, stack.COMPLETE), stack.state) rsrc = stack['the_nic'] self.assertResourceState(rsrc, 'dddd') @@ -510,7 +510,7 @@ Resources: stack = self.create_stack(self.test_template_error_no_ref) try: - self.assertEqual(stack.CREATE_FAILED, stack.state) + self.assertEqual((stack.CREATE, stack.FAILED), stack.state) rsrc = stack['the_nic'] self.assertEqual((rsrc.CREATE, rsrc.FAILED), rsrc.state) reason = rsrc.status_reason diff --git a/heat/tests/test_watch.py b/heat/tests/test_watch.py index 46453dce..5066cd08 100644 --- a/heat/tests/test_watch.py +++ b/heat/tests/test_watch.py @@ -53,7 +53,8 @@ class WatchRuleTest(HeatTestCase): tmpl = parser.Template(empty_tmpl) stack_name = 'dummystack' dummy_stack = parser.Stack(ctx, stack_name, tmpl) - dummy_stack.state_set(dummy_stack.CREATE_COMPLETE, 'Testing') + dummy_stack.state_set(dummy_stack.CREATE, dummy_stack.COMPLETE, + 'Testing') dummy_stack.store() cls.stack_id = dummy_stack.id -- 2.45.2