From 7cf6e52cacd2382b44b9255e9a5ed13271841261 Mon Sep 17 00:00:00 2001 From: ananta Date: Tue, 5 Aug 2014 17:07:25 +0530 Subject: [PATCH] Hard delete backup stack after successful update. After successful update of a stack, the backup stack and it's associated template, resources and events should be permanently deleted. Closes-Bug: #1332300 Change-Id: I3a462690b5f6bfadfbcf16eeb6bdec7c5ece2e83 --- heat/db/api.py | 4 ++++ heat/db/sqlalchemy/api.py | 21 +++++++++++++++++ heat/db/sqlalchemy/models.py | 3 ++- heat/engine/stack.py | 9 ++++++-- heat/tests/test_sqlalchemy_api.py | 38 +++++++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 3 deletions(-) diff --git a/heat/db/api.py b/heat/db/api.py index c43579868..f6032431e 100644 --- a/heat/db/api.py +++ b/heat/db/api.py @@ -154,6 +154,10 @@ def stack_delete(context, stack_id): return IMPL.stack_delete(context, stack_id) +def stack_delete_hard(context, stack_id): + return IMPL.stack_delete_hard(context, stack_id) + + def stack_lock_create(stack_id, engine_id): return IMPL.stack_lock_create(stack_id, engine_id) diff --git a/heat/db/sqlalchemy/api.py b/heat/db/sqlalchemy/api.py index 121db00dc..69f27e1e5 100644 --- a/heat/db/sqlalchemy/api.py +++ b/heat/db/sqlalchemy/api.py @@ -430,6 +430,27 @@ def stack_delete(context, stack_id): session.flush() +def stack_delete_hard(context, stack_id): + s = stack_get(context, stack_id) + if not s: + raise exception.NotFound(_('Attempt to hard-delete a stack with id: ' + '%(id)s %(msg)s') % { + 'id': stack_id, + 'msg': 'that does not exist'}) + + session = Session.object_session(s) + + for r in s.resources: + session.delete(r) + + for e in s.events: + session.delete(e) + + session.delete(s) + + session.flush() + + def stack_lock_create(stack_id, engine_id): session = get_session() with session.begin(): diff --git a/heat/db/sqlalchemy/models.py b/heat/db/sqlalchemy/models.py index ec8210f9e..87ffdefe1 100644 --- a/heat/db/sqlalchemy/models.py +++ b/heat/db/sqlalchemy/models.py @@ -120,7 +120,8 @@ class Stack(BASE, HeatBase, SoftDelete, StateAware): sqlalchemy.Integer, sqlalchemy.ForeignKey('raw_template.id'), nullable=False) - raw_template = relationship(RawTemplate, backref=backref('stack')) + raw_template = relationship(RawTemplate, cascade="all,delete", + backref=backref('stack')) username = sqlalchemy.Column(sqlalchemy.String(256)) tenant = sqlalchemy.Column(sqlalchemy.String(256)) parameters = sqlalchemy.Column('parameters', Json) diff --git a/heat/engine/stack.py b/heat/engine/stack.py index 53b7e8a52..8a11b42f2 100644 --- a/heat/engine/stack.py +++ b/heat/engine/stack.py @@ -775,7 +775,9 @@ class Stack(collections.Mapping): LOG.debug('Deleting backup stack') backup_stack.delete(backup=True) - # flip the template to the newstack values + # Flip the template to the newstack values, but keep + # reference to old stack id to overwrite DB entry + newstack.t.id = self.t.id self.t = newstack.t template_outputs = self.t[self.t.OUTPUTS] self.outputs = self.resolve_static_data(template_outputs) @@ -958,7 +960,10 @@ class Stack(collections.Mapping): if stack_status != self.FAILED: # delete the stack try: - db_api.stack_delete(self.context, self.id) + if backup: + db_api.stack_delete_hard(self.context, self.id) + else: + db_api.stack_delete(self.context, self.id) except exception.NotFound: LOG.info(_("Tried to delete stack that does not exist " "%s ") % self.id) diff --git a/heat/tests/test_sqlalchemy_api.py b/heat/tests/test_sqlalchemy_api.py index 54497adaa..151650b15 100644 --- a/heat/tests/test_sqlalchemy_api.py +++ b/heat/tests/test_sqlalchemy_api.py @@ -1329,6 +1329,44 @@ class DBAPIStackTest(HeatTestCase): self.assertRaises(exception.NotFound, db_api.resource_get, self.ctx, resource.id) + def test_stack_delete_hard(self): + stack = create_stack(self.ctx, self.template, self.user_creds) + stack_id = stack.id + resource = create_resource(self.ctx, stack) + template_id = stack.raw_template.id + db_api.stack_delete_hard(self.ctx, stack_id) + + self.assertIsNone(db_api.stack_get(self.ctx, stack_id, + show_deleted=False)) + + self.assertRaises(exception.NotFound, db_api.stack_delete, + self.ctx, stack_id) + + # Even soft-delete aware should not find it + self.assertIsNone(db_api.stack_get(self.ctx, stack_id, + show_deleted=True)) + + # Testing child resources deletion + self.assertRaises(exception.NotFound, db_api.resource_get, + self.ctx, resource.id) + + # Testing raw_template deletion + self.assertRaises(exception.NotFound, db_api.raw_template_get, + self.ctx, template_id) + + def test_stack_delete_hard_deletes_events(self): + stack = create_stack(self.ctx, self.template, self.user_creds) + stack_id = stack.id + values = [ + {'stack_id': stack_id, 'resource_name': 'res1'}, + {'stack_id': stack_id, 'resource_name': 'res2'}, + ] + [create_event(self.ctx, **val) for val in values] + + db_api.stack_delete_hard(self.ctx, stack_id) + events = db_api.event_get_all_by_stack(self.ctx, stack_id) + self.assertEqual(0, len(events)) + def test_stack_update(self): stack = create_stack(self.ctx, self.template, self.user_creds) values = {