diff --git a/heat/db/sqlalchemy/migrate_repo/versions/007_resource_work.py b/heat/db/sqlalchemy/migrate_repo/versions/007_resource_work.py new file mode 100644 index 0000000000..02bf553c0e --- /dev/null +++ b/heat/db/sqlalchemy/migrate_repo/versions/007_resource_work.py @@ -0,0 +1,32 @@ +from sqlalchemy import * +from migrate import * + + +def upgrade(migrate_engine): + meta = MetaData(bind=migrate_engine) + + resource = Table('resource', meta, autoload=True) + Column('rsrc_metadata', Text()).create(resource) + + stack = Table('stack', meta, autoload=True) + Column('status', String(length=255, + convert_unicode=False, + assert_unicode=None, + unicode_error=None, + _warn_on_bytestring=False)).create(stack) + Column('status_reason', String(length=255, + convert_unicode=False, + assert_unicode=None, + unicode_error=None, + _warn_on_bytestring=False)).create(stack) + + +def downgrade(migrate_engine): + meta = MetaData(bind=migrate_engine) + + resource = Table('resource', meta, autoload=True) + resource.c.rsrc_metadata.drop() + + stack = Table('stack', meta, autoload=True) + stack.c.status.drop() + stack.c.status_reason.drop() diff --git a/heat/db/sqlalchemy/models.py b/heat/db/sqlalchemy/models.py index e4721d253c..9d477815a4 100644 --- a/heat/db/sqlalchemy/models.py +++ b/heat/db/sqlalchemy/models.py @@ -142,6 +142,8 @@ class Stack(BASE, HeatBase): raw_template = relationship(RawTemplate, backref=backref('stack')) username = Column(String) + status = Column('status', String) + status_reason = Column('status_reason', String) user_creds_id = Column(Integer, ForeignKey('user_creds.id'), nullable=False) owner_id = Column(Integer, nullable=True) @@ -196,6 +198,8 @@ class Resource(BASE, HeatBase): name = Column('name', String, nullable=False) nova_instance = Column('nova_instance', String) state_description = Column('state_description', String) + # odd name as "metadata" is reserved + rsrc_metadata = Column('rsrc_metadata', Json) parsed_template_id = Column(Integer, ForeignKey('parsed_template.id'), nullable=True) parsed_template = relationship(ParsedTemplate, diff --git a/heat/engine/manager.py b/heat/engine/manager.py index 7b8eb94599..06e397874b 100644 --- a/heat/engine/manager.py +++ b/heat/engine/manager.py @@ -155,7 +155,7 @@ class EngineManager(manager.Manager): mem['CreationTime'] = heat_utils.strtime(s.created_at) mem['TemplateDescription'] = ps.t.get('Description', 'No description') - mem['StackStatus'] = ps.t.get('stack_status', 'unknown') + mem['StackStatus'] = s.status res['stacks'].append(mem) return res @@ -185,12 +185,11 @@ class EngineManager(manager.Manager): mem['TimeoutInMinutes'] = ps.t.get('Timeout', '60') mem['TemplateDescription'] = ps.t.get('Description', 'No description') - mem['StackStatus'] = ps.t.get('stack_status', 'unknown') - mem['StackStatusReason'] = ps.t.get('stack_status_reason', - 'State changed') + mem['StackStatus'] = s.status + mem['StackStatusReason'] = s.status_reason # only show the outputs on a completely created stack - if ps.t['stack_status'] == ps.CREATE_COMPLETE: + if s.state == ps.CREATE_COMPLETE: mem['Outputs'] = ps.get_outputs() res['stacks'].append(mem) @@ -481,12 +480,11 @@ class EngineManager(manager.Manager): if not s: return ['stack', None] - template = s.raw_template.parsed_template.template - if not resource_id in template.get('Resources', {}): + r = db_api.resource_get_by_name_and_stack(None, resource_id, s.id) + if r is None: return ['resource', None] - metadata = template['Resources'][resource_id].get('Metadata', {}) - return [None, metadata] + return [None, r.rsrc_metadata] def metadata_update(self, context, stack_name, resource_id, metadata): """ @@ -495,21 +493,14 @@ class EngineManager(manager.Manager): s = db_api.stack_get_by_name(None, stack_name) if not s: return ['stack', None] - pt_id = s.raw_template.parsed_template.id - pt = db_api.parsed_template_get(None, pt_id) - if not resource_id in pt.template.get('Resources', {}): + r = db_api.resource_get_by_name_and_stack(None, resource_id, s.id) + if r is None: + logger.warn("Resource not found %s:%s." % (stack_name, + resource_id)) return ['resource', None] - # TODO(shadower) deep copy of the template is required here. Without - # it, we directly modify parsed_template.template by assigning the new - # metadata. When we then call parsed_template.update_and_save, the - # session will detect no changes and thus not update the database. - # Just updating the values and calling save didn't seem to work either. - # There's probably an idiomatic way I'm missing right now. - t = deepcopy(pt.template) - t['Resources'][resource_id]['Metadata'] = metadata - pt.update_and_save({'template': t}) + r.update_and_save({'rsrc_metadata': metadata}) return [None, metadata] @manager.periodic_task diff --git a/heat/engine/parser.py b/heat/engine/parser.py index 2ae68a5548..331a942fd0 100644 --- a/heat/engine/parser.py +++ b/heat/engine/parser.py @@ -178,9 +178,17 @@ class Stack(object): self.parsed_template_id) def state_set(self, new_status, reason='change in resource state'): - self.t['stack_status'] = new_status - self.t['stack_status_reason'] = reason - self.update_parsed_template() + if self.id != 0: + stack = db_api.stack_get(self.context, self.id) + else: + stack = db_api.stack_get_by_name(self.context, self.name) + + if stack is None: + return + + self.id = stack.id + stack.update_and_save({'status': new_status, + 'status_reason': reason}) def _timeout(self): '''Return the stack creation timeout in seconds''' diff --git a/heat/engine/resources.py b/heat/engine/resources.py index 67cd13e5cf..2e3e39290e 100644 --- a/heat/engine/resources.py +++ b/heat/engine/resources.py @@ -64,6 +64,10 @@ class Resource(object): # make a dummy entry to prevent having to check all over the # place for it. self.t['Properties'] = {} + if 'Metadata' not in self.t: + # make a dummy entry to prevent having to check all over the + # place for it. + self.t['Metadata'] = {} resource = db_api.resource_get_by_name_and_stack(self.stack.context, name, stack.id) @@ -146,11 +150,10 @@ class Resource(object): logger.info('creating %s' % str(self)) - self.state_set(self.CREATE_IN_PROGRESS) - try: self.calculate_properties() self.properties.validate() + self.state_set(self.CREATE_IN_PROGRESS) if callable(getattr(self, 'handle_create', None)): self.handle_create() except Exception as ex: @@ -207,7 +210,7 @@ class Resource(object): def instance_id_set(self, inst): self.instance_id = inst - def _create_db(self): + def _create_db(self, metadata=None): '''Create the resource in the database''' try: rs = {'state': self.state, @@ -215,6 +218,7 @@ class Resource(object): 'parsed_template_id': self.stack.parsed_template_id, 'nova_instance': self.instance_id, 'name': self.name, + 'rsrc_metadata': metadata, 'stack_name': self.stack.name} new_rs = db_api.resource_create(self.stack.context, rs) @@ -259,7 +263,7 @@ class Resource(object): logger.error('DB error %s' % str(ex)) elif new_state in (self.CREATE_COMPLETE, self.CREATE_FAILED): - self._create_db() + self._create_db(metadata=self.parsed_template()['Metadata']) if new_state != old_state: self._add_event(new_state, reason) diff --git a/heat/engine/wait_condition.py b/heat/engine/wait_condition.py index 8d2fa05d6c..16d64082bc 100644 --- a/heat/engine/wait_condition.py +++ b/heat/engine/wait_condition.py @@ -75,11 +75,11 @@ class WaitCondition(Resource): tmo = eventlet.Timeout(self.timeout) status = 'WAITING' reason = '' + res = None try: while status == 'WAITING': - pt = None try: - pt = self.stack.parsed_template_get() + res = db_api.resource_get(self.stack.context, self.id) except Exception as ex: if 'not found' in ex: # it has been deleted @@ -87,11 +87,11 @@ class WaitCondition(Resource): else: pass - if pt: - res = pt.template['Resources'][self.resource_id] - metadata = res.get('Metadata', {}) - status = metadata.get('Status', 'WAITING') - reason = metadata.get('Reason', 'Reason not provided') + if res and res.rsrc_metadata: + metadata = res.rsrc_metadata + if metadata: + status = metadata.get('Status', 'WAITING') + reason = metadata.get('Reason', 'Reason not provided') logger.debug('got %s' % json.dumps(metadata)) if status == 'WAITING': logger.debug('Waiting some more for the Metadata[Status]') @@ -111,10 +111,14 @@ class WaitCondition(Resource): def FnGetAtt(self, key): res = None - self._get_handle_resource_id() if key == 'Data': - resource = self.stack.t['Resources'][self.resource_id] - res = resource['Metadata']['Data'] + try: + r = db_api.resource_get(self.stack.context, self.id) + if r.rsrc_metadata and 'Data' in r.rsrc_metadata: + res = r.rsrc_metadata['Data'] + except Exception as ex: + pass + else: raise exception.InvalidTemplateAttribute(resource=self.name, key=key) diff --git a/heat/tests/test_stacks.py b/heat/tests/test_stacks.py index e9b437db77..1a785467f5 100644 --- a/heat/tests/test_stacks.py +++ b/heat/tests/test_stacks.py @@ -90,7 +90,7 @@ class stacksTest(unittest.TestCase): assert(stack.resources['WebServer'].instance_id > 0) stack.delete() assert(stack.resources['WebServer'].state == 'DELETE_COMPLETE') - assert(stack.t['stack_status'] == 'DELETE_COMPLETE') + assert(new_s.status == 'DELETE_COMPLETE') def test_stack_event_list(self): stack = self.start_wordpress_stack('test_event_list_stack')