def stack_task(self, action, reverse=False, post_func=None, error_wait_time=None, aggregate_exceptions=False): ''' A task to perform an action on the stack and all of the resources in forward or reverse dependency order as specified by reverse ''' try: lifecycle_plugin_utils.do_pre_ops(self.context, self, None, action) except Exception as e: self.state_set( action, self.FAILED, e.args[0] if e.args else 'Failed stack pre-ops: %s' % six.text_type(e)) if callable(post_func): post_func() return self.state_set(action, self.IN_PROGRESS, 'Stack %s started' % action) stack_status = self.COMPLETE reason = 'Stack %s completed successfully' % action def resource_action(r): # Find e.g resource.create and call it action_l = action.lower() handle = getattr(r, '%s' % action_l) # If a local _$action_kwargs function exists, call it to get the # action specific argument list, otherwise an empty arg list handle_kwargs = getattr(self, '_%s_kwargs' % action_l, lambda x: {}) return handle(**handle_kwargs(r)) action_task = scheduler.DependencyTaskGroup( self.dependencies, resource_action, reverse, error_wait_time=error_wait_time, aggregate_exceptions=aggregate_exceptions) try: yield action_task() except (exception.ResourceFailure, scheduler.ExceptionGroup) as ex: stack_status = self.FAILED reason = 'Resource %s failed: %s' % (action, six.text_type(ex)) except scheduler.Timeout: stack_status = self.FAILED reason = '%s timed out' % action.title() self.state_set(action, stack_status, reason) if callable(post_func): post_func() lifecycle_plugin_utils.do_post_ops(self.context, self, None, action, (self.status == self.FAILED))
def stack_task(self, action, reverse=False, post_func=None, error_wait_time=None, aggregate_exceptions=False): ''' A task to perform an action on the stack and all of the resources in forward or reverse dependency order as specified by reverse ''' try: lifecycle_plugin_utils.do_pre_ops(self.context, self, None, action) except Exception as e: self.state_set(action, self.FAILED, e.args[0] if e.args else 'Failed stack pre-ops: %s' % six.text_type(e)) if callable(post_func): post_func() return self.state_set(action, self.IN_PROGRESS, 'Stack %s started' % action) stack_status = self.COMPLETE reason = 'Stack %s completed successfully' % action def resource_action(r): # Find e.g resource.create and call it action_l = action.lower() handle = getattr(r, '%s' % action_l) # If a local _$action_kwargs function exists, call it to get the # action specific argument list, otherwise an empty arg list handle_kwargs = getattr(self, '_%s_kwargs' % action_l, lambda x: {}) return handle(**handle_kwargs(r)) action_task = scheduler.DependencyTaskGroup( self.dependencies, resource_action, reverse, error_wait_time=error_wait_time, aggregate_exceptions=aggregate_exceptions) try: yield action_task() except (exception.ResourceFailure, scheduler.ExceptionGroup) as ex: stack_status = self.FAILED reason = 'Resource %s failed: %s' % (action, six.text_type(ex)) except scheduler.Timeout: stack_status = self.FAILED self.error_codes.append('heat_server_task_00005') reason = '%s timed out' % action.title() self.state_set(action, stack_status, reason) if callable(post_func): post_func() lifecycle_plugin_utils.do_post_ops(self.context, self, None, action, (self.status == self.FAILED))
def test_do_pre_and_post_callouts(self): lcp_mappings = [('A::B::C1', TestLifecycleCallout1)] self.mock_lcp_class_map(lcp_mappings) mc = mock.Mock() mc.__setattr__("pre_counter_for_unit_test", 0) mc.__setattr__("post_counter_for_unit_test", 0) ms = mock.Mock() ms.__setattr__("action", 'A') lifecycle_plugin_utils.do_pre_ops(mc, ms, None, None) self.assertEqual(1, mc.pre_counter_for_unit_test) lifecycle_plugin_utils.do_post_ops(mc, ms, None, None) self.assertEqual(1, mc.post_counter_for_unit_test) self.mock_get_plugins.assert_called_once_with()
def test_do_pre_and_post_callouts(self): lcp_mappings = [('A::B::C1', TestLifecycleCallout1)] self.mock_lcp_class_map(lcp_mappings) mc = mock.Mock() mc.__setattr__("pre_counter_for_unit_test", 0) mc.__setattr__("post_counter_for_unit_test", 0) ms = mock.Mock() ms.__setattr__("action", 'A') lifecycle_plugin_utils.do_pre_ops(mc, ms, None, None) self.assertEqual(mc.pre_counter_for_unit_test, 1) lifecycle_plugin_utils.do_post_ops(mc, ms, None, None) self.assertEqual(mc.post_counter_for_unit_test, 1) return
def test_do_pre_op_failure(self): lcp_mappings = [('A::B::C5', TestLifecycleCallout1), ('A::B::C4', TestLifecycleCallout4)] self.mock_lcp_class_map(lcp_mappings) mc = mock.Mock() mc.__setattr__("pre_counter_for_unit_test", 0) mc.__setattr__("post_counter_for_unit_test", 0) ms = mock.Mock() ms.__setattr__("action", 'A') failed = False try: lifecycle_plugin_utils.do_pre_ops(mc, ms, None, None) except Exception: failed = True self.assertTrue(failed) self.assertEqual(1, mc.pre_counter_for_unit_test) self.assertEqual(1, mc.post_counter_for_unit_test) self.mock_get_plugins.assert_called_once_with()
def test_do_pre_op_failure(self): lcp_mappings = [('A::B::C5', TestLifecycleCallout1), ('A::B::C4', TestLifecycleCallout4)] self.mock_lcp_class_map(lcp_mappings) mc = mock.Mock() mc.__setattr__("pre_counter_for_unit_test", 0) mc.__setattr__("post_counter_for_unit_test", 0) ms = mock.Mock() ms.__setattr__("action", 'A') failed = False try: lifecycle_plugin_utils.do_pre_ops(mc, ms, None, None) except Exception: failed = True self.assertTrue(failed) self.assertEqual(mc.pre_counter_for_unit_test, 1) self.assertEqual(mc.post_counter_for_unit_test, 1) return
def delete(self, action=DELETE, backup=False, abandon=False): ''' Delete all of the resources, and then the stack itself. The action parameter is used to differentiate between a user initiated delete and an automatic stack rollback after a failed create, which amount to the same thing, but the states are recorded differently. Note abandon is a delete where all resources have been set to a RETAIN deletion policy, but we also don't want to delete anything required for those resources, e.g the stack_user_project. ''' if action not in (self.DELETE, self.ROLLBACK): LOG.error(_LE("Unexpected action %s passed to delete!"), action) self.state_set(self.DELETE, self.FAILED, "Invalid action %s" % action) return stack_status = self.COMPLETE reason = 'Stack %s completed successfully' % action self.state_set(action, self.IN_PROGRESS, 'Stack %s started' % action) backup_stack = self._backup_stack(False) if backup_stack: self._delete_backup_stack(backup_stack) if backup_stack.status != backup_stack.COMPLETE: errs = backup_stack.status_reason failure = 'Error deleting backup resources: %s' % errs self.state_set(action, self.FAILED, 'Failed to %s : %s' % (action, failure)) return snapshots = db_api.snapshot_get_all(self.context, self.id) for snapshot in snapshots: self.delete_snapshot(snapshot) if not backup: try: lifecycle_plugin_utils.do_pre_ops(self.context, self, None, action) except Exception as e: self.state_set( action, self.FAILED, e.args[0] if e.args else 'Failed stack pre-ops: %s' % six.text_type(e)) return action_task = scheduler.DependencyTaskGroup(self.dependencies, resource.Resource.destroy, reverse=True) try: scheduler.TaskRunner(action_task)(timeout=self.timeout_secs()) except exception.ResourceFailure as ex: stack_status = self.FAILED reason = 'Resource %s failed: %s' % (action, six.text_type(ex)) except scheduler.Timeout: stack_status = self.FAILED reason = '%s timed out' % action.title() # If the stack delete succeeded, this is not a backup stack and it's # not a nested stack, we should delete the credentials if stack_status != self.FAILED and not backup and not self.owner_id: stack_status, reason = self._delete_credentials( stack_status, reason, abandon) try: self.state_set(action, stack_status, reason) except exception.NotFound: LOG.info(_LI("Tried to delete stack that does not exist " "%s "), self.id) if not backup: lifecycle_plugin_utils.do_post_ops(self.context, self, None, action, (self.status == self.FAILED)) if stack_status != self.FAILED: # delete the stack try: db_api.stack_delete(self.context, self.id) except exception.NotFound: LOG.info( _LI("Tried to delete stack that does not exist " "%s "), self.id) self.id = None
def update_task(self, newstack, action=UPDATE, event=None): if action not in (self.UPDATE, self.ROLLBACK, self.RESTORE): LOG.error(_LE("Unexpected action %s passed to update!"), action) self.state_set(self.UPDATE, self.FAILED, "Invalid action %s" % action) return try: lifecycle_plugin_utils.do_pre_ops(self.context, self, newstack, action) except Exception as e: self.state_set( action, self.FAILED, e.args[0] if e.args else 'Failed stack pre-ops: %s' % six.text_type(e)) return if self.status == self.IN_PROGRESS: if action == self.ROLLBACK: LOG.debug("Starting update rollback for %s" % self.name) else: self.state_set(action, self.FAILED, 'State invalid for %s' % action) return self.state_set(action, self.IN_PROGRESS, 'Stack %s started' % action) if action == self.UPDATE: # Oldstack is useless when the action is not UPDATE , so we don't # need to build it, this can avoid some unexpected errors. oldstack = Stack(self.context, self.name, copy.deepcopy(self.t), self.env) backup_stack = self._backup_stack() try: update_task = update.StackUpdate( self, newstack, backup_stack, rollback=action == self.ROLLBACK, error_wait_time=cfg.CONF.error_wait_time) updater = scheduler.TaskRunner(update_task) self.env = newstack.env self.parameters = newstack.parameters self.t.files = newstack.t.files self.disable_rollback = newstack.disable_rollback self.timeout_mins = newstack.timeout_mins self._set_param_stackid() try: updater.start(timeout=self.timeout_secs()) yield while not updater.step(): if event is None or not event.ready(): yield else: message = event.wait() if message == rpc_api.THREAD_CANCEL: raise ForcedCancel() finally: self.reset_dependencies() if action == self.UPDATE: reason = 'Stack successfully updated' elif action == self.RESTORE: reason = 'Stack successfully restored' else: reason = 'Stack rollback completed' stack_status = self.COMPLETE except scheduler.Timeout: stack_status = self.FAILED reason = 'Timed out' except ForcedCancel as e: reason = six.text_type(e) stack_status = self.FAILED if action == self.UPDATE: update_task.updater.cancel_all() yield self.update_task(oldstack, action=self.ROLLBACK) return except exception.ResourceFailure as e: reason = six.text_type(e) stack_status = self.FAILED if action == self.UPDATE: # If rollback is enabled, we do another update, with the # existing template, so we roll back to the original state if not self.disable_rollback: yield self.update_task(oldstack, action=self.ROLLBACK) return else: LOG.debug('Deleting backup stack') backup_stack.delete(backup=True) # flip the template to the newstack values self.t = newstack.t template_outputs = self.t[self.t.OUTPUTS] self.outputs = self.resolve_static_data(template_outputs) # Don't use state_set to do only one update query and avoid race # condition with the COMPLETE status self.action = action self.status = stack_status self.status_reason = reason self.store() lifecycle_plugin_utils.do_post_ops(self.context, self, newstack, action, (self.status == self.FAILED)) notification.send(self)
def delete(self, action=DELETE, backup=False, abandon=False): ''' Delete all of the resources, and then the stack itself. The action parameter is used to differentiate between a user initiated delete and an automatic stack rollback after a failed create, which amount to the same thing, but the states are recorded differently. Note abandon is a delete where all resources have been set to a RETAIN deletion policy, but we also don't want to delete anything required for those resources, e.g the stack_user_project. ''' if action not in (self.DELETE, self.ROLLBACK): LOG.error(_("Unexpected action %s passed to delete!") % action) self.state_set(self.DELETE, self.FAILED, "Invalid action %s" % action) return stack_status = self.COMPLETE reason = 'Stack %s completed successfully' % action self.state_set(action, self.IN_PROGRESS, 'Stack %s started' % action) backup_stack = self._backup_stack(False) if backup_stack: def failed(child): return (child.action == child.CREATE and child.status in (child.FAILED, child.IN_PROGRESS)) for key, backup_resource in backup_stack.resources.items(): # If UpdateReplace is failed, we must restore backup_resource # to existing_stack in case of it may have dependencies in # these stacks. current_resource is the resource that just # created and failed, so put into the backup_stack to delete # anyway. backup_resource_id = backup_resource.resource_id current_resource = self.resources[key] current_resource_id = current_resource.resource_id if backup_resource_id: if (any(failed(child) for child in self.dependencies[current_resource]) or current_resource.status in (current_resource.FAILED, current_resource.IN_PROGRESS)): # If child resource failed to update, current_resource # should be replaced to resolve dependencies. But this # is not fundamental solution. If there are update # failer and success resources in the children, cannot # delete the stack. # Stack class owns dependencies as set of resource's # objects, so we switch members of the resource that is # needed to delete it. self.resources[key].resource_id = backup_resource_id self.resources[ key].properties = backup_resource.properties backup_stack.resources[ key].resource_id = current_resource_id backup_stack.resources[ key].properties = current_resource.properties backup_stack.delete(backup=True) if backup_stack.status != backup_stack.COMPLETE: errs = backup_stack.status_reason failure = 'Error deleting backup resources: %s' % errs self.state_set(action, self.FAILED, 'Failed to %s : %s' % (action, failure)) return snapshots = db_api.snapshot_get_all(self.context, self.id) for snapshot in snapshots: self.delete_snapshot(snapshot) if not backup: try: lifecycle_plugin_utils.do_pre_ops(self.context, self, None, action) except Exception as e: self.state_set(action, self.FAILED, e.args[0] if e.args else 'Failed stack pre-ops: %s' % six.text_type(e)) return action_task = scheduler.DependencyTaskGroup(self.dependencies, resource.Resource.destroy, reverse=True) try: scheduler.TaskRunner(action_task)(timeout=self.timeout_secs()) except exception.ResourceFailure as ex: stack_status = self.FAILED reason = 'Resource %s failed: %s' % (action, six.text_type(ex)) except scheduler.Timeout: stack_status = self.FAILED reason = '%s timed out' % action.title() # If the stack delete succeeded, this is not a backup stack and it's # not a nested stack, we should delete the credentials if stack_status != self.FAILED and not backup and not self.owner_id: # Cleanup stored user_creds so they aren't accessible via # the soft-deleted stack which remains in the DB if self.user_creds_id: user_creds = db_api.user_creds_get(self.user_creds_id) # If we created a trust, delete it if user_creds is not None: trust_id = user_creds.get('trust_id') if trust_id: try: # If the trustor doesn't match the context user the # we have to use the stored context to cleanup the # trust, as although the user evidently has # permission to delete the stack, they don't have # rights to delete the trust unless an admin trustor_id = user_creds.get('trustor_user_id') if self.context.user_id != trustor_id: LOG.debug('Context user_id doesn\'t match ' 'trustor, using stored context') sc = self.stored_context() sc.clients.client('keystone').delete_trust( trust_id) else: self.clients.client('keystone').delete_trust( trust_id) except Exception as ex: LOG.exception(ex) stack_status = self.FAILED reason = ("Error deleting trust: %s" % six.text_type(ex)) # Delete the stored credentials try: db_api.user_creds_delete(self.context, self.user_creds_id) except exception.NotFound: LOG.info(_("Tried to delete user_creds that do not exist " "(stack=%(stack)s user_creds_id=%(uc)s)") % {'stack': self.id, 'uc': self.user_creds_id}) try: self.user_creds_id = None self.store() except exception.NotFound: LOG.info(_("Tried to store a stack that does not exist " "%s ") % self.id) # If the stack has a domain project, delete it if self.stack_user_project_id and not abandon: try: keystone = self.clients.client('keystone') keystone.delete_stack_domain_project( project_id=self.stack_user_project_id) except Exception as ex: LOG.exception(ex) stack_status = self.FAILED reason = "Error deleting project: %s" % six.text_type(ex) try: self.state_set(action, stack_status, reason) except exception.NotFound: LOG.info(_("Tried to delete stack that does not exist " "%s ") % self.id) if not backup: lifecycle_plugin_utils.do_post_ops(self.context, self, None, action, (self.status == self.FAILED)) if stack_status != self.FAILED: # delete the stack try: db_api.stack_delete(self.context, self.id) except exception.NotFound: LOG.info(_("Tried to delete stack that does not exist " "%s ") % self.id) self.id = None
def update_task(self, newstack, action=UPDATE, event=None): if action not in (self.UPDATE, self.ROLLBACK): LOG.error(_("Unexpected action %s passed to update!") % action) self.state_set(self.UPDATE, self.FAILED, "Invalid action %s" % action) return try: lifecycle_plugin_utils.do_pre_ops(self.context, self, newstack, action) except Exception as e: self.state_set(action, self.FAILED, e.args[0] if e.args else 'Failed stack pre-ops: %s' % six.text_type(e)) return if self.status == self.IN_PROGRESS: if action == self.ROLLBACK: LOG.debug("Starting update rollback for %s" % self.name) else: self.state_set(action, self.FAILED, 'State invalid for %s' % action) return self.state_set(action, self.IN_PROGRESS, 'Stack %s started' % action) if action == self.UPDATE: # Oldstack is useless when the action is not UPDATE , so we don't # need to build it, this can avoid some unexpected errors. oldstack = Stack(self.context, self.name, copy.deepcopy(self.t), self.env) backup_stack = self._backup_stack() try: update_task = update.StackUpdate(self, newstack, backup_stack, rollback=action == self.ROLLBACK, error_wait_time=ERROR_WAIT_TIME) updater = scheduler.TaskRunner(update_task) self.env = newstack.env self.parameters = newstack.parameters self.t.files = newstack.t.files self.disable_rollback = newstack.disable_rollback self.timeout_mins = newstack.timeout_mins self._set_param_stackid() try: updater.start(timeout=self.timeout_secs()) yield while not updater.step(): if event is None or not event.ready(): yield else: message = event.wait() if message == rpc_api.THREAD_CANCEL: raise ForcedCancel() finally: self.reset_dependencies() if action == self.UPDATE: reason = 'Stack successfully updated' else: reason = 'Stack rollback completed' stack_status = self.COMPLETE except scheduler.Timeout: stack_status = self.FAILED reason = 'Timed out' except ForcedCancel as e: reason = six.text_type(e) stack_status = self.FAILED if action == self.UPDATE: update_task.updater.cancel_all() yield self.update_task(oldstack, action=self.ROLLBACK) return except exception.ResourceFailure as e: reason = six.text_type(e) stack_status = self.FAILED if action == self.UPDATE: # If rollback is enabled, we do another update, with the # existing template, so we roll back to the original state if not self.disable_rollback: yield self.update_task(oldstack, action=self.ROLLBACK) return else: LOG.debug('Deleting backup stack') backup_stack.delete(backup=True) # flip the template to the newstack values self.t = newstack.t template_outputs = self.t[self.t.OUTPUTS] self.outputs = self.resolve_static_data(template_outputs) # Don't use state_set to do only one update query and avoid race # condition with the COMPLETE status self.action = action self.status = stack_status self.status_reason = reason self.store() lifecycle_plugin_utils.do_post_ops(self.context, self, newstack, action, (self.status == self.FAILED)) notification.send(self)
def delete(self, action=DELETE, backup=False, abandon=False): ''' Delete all of the resources, and then the stack itself. The action parameter is used to differentiate between a user initiated delete and an automatic stack rollback after a failed create, which amount to the same thing, but the states are recorded differently. Note abandon is a delete where all resources have been set to a RETAIN deletion policy, but we also don't want to delete anything required for those resources, e.g the stack_user_project. ''' if action not in (self.DELETE, self.ROLLBACK): LOG.error(_LE("Unexpected action %s passed to delete!"), action) self.state_set(self.DELETE, self.FAILED, "Invalid action %s" % action) return stack_status = self.COMPLETE reason = 'Stack %s completed successfully' % action self.state_set(action, self.IN_PROGRESS, 'Stack %s started' % action) backup_stack = self._backup_stack(False) if backup_stack: self._delete_backup_stack(backup_stack) if backup_stack.status != backup_stack.COMPLETE: errs = backup_stack.status_reason failure = 'Error deleting backup resources: %s' % errs self.state_set(action, self.FAILED, 'Failed to %s : %s' % (action, failure)) return snapshots = db_api.snapshot_get_all(self.context, self.id) for snapshot in snapshots: self.delete_snapshot(snapshot) if not backup: try: lifecycle_plugin_utils.do_pre_ops(self.context, self, None, action) except Exception as e: self.state_set(action, self.FAILED, e.args[0] if e.args else 'Failed stack pre-ops: %s' % six.text_type(e)) return action_task = scheduler.DependencyTaskGroup(self.dependencies, resource.Resource.destroy, reverse=True) try: scheduler.TaskRunner(action_task)(timeout=self.timeout_secs()) except exception.ResourceFailure as ex: stack_status = self.FAILED reason = 'Resource %s failed: %s' % (action, six.text_type(ex)) except scheduler.Timeout: stack_status = self.FAILED reason = '%s timed out' % action.title() # If the stack delete succeeded, this is not a backup stack and it's # not a nested stack, we should delete the credentials if stack_status != self.FAILED and not backup and not self.owner_id: stack_status, reason = self._delete_credentials(stack_status, reason, abandon) try: self.state_set(action, stack_status, reason) except exception.NotFound: LOG.info(_LI("Tried to delete stack that does not exist " "%s "), self.id) if not backup: lifecycle_plugin_utils.do_post_ops(self.context, self, None, action, (self.status == self.FAILED)) if stack_status != self.FAILED: # delete the stack try: db_api.stack_delete(self.context, self.id) except exception.NotFound: LOG.info(_LI("Tried to delete stack that does not exist " "%s "), self.id) self.id = None
def delete(self, action=DELETE, backup=False, abandon=False): ''' Delete all of the resources, and then the stack itself. The action parameter is used to differentiate between a user initiated delete and an automatic stack rollback after a failed create, which amount to the same thing, but the states are recorded differently. Note abandon is a delete where all resources have been set to a RETAIN deletion policy, but we also don't want to delete anything required for those resources, e.g the stack_user_project. ''' if action not in (self.DELETE, self.ROLLBACK): LOG.error(_("Unexpected action %s passed to delete!") % action) self.state_set(self.DELETE, self.FAILED, "Invalid action %s" % action) return stack_status = self.COMPLETE reason = 'Stack %s completed successfully' % action self.state_set(action, self.IN_PROGRESS, 'Stack %s started' % action) backup_stack = self._backup_stack(False) if backup_stack: def failed(child): return (child.action == child.CREATE and child.status in (child.FAILED, child.IN_PROGRESS)) for key, backup_resource in backup_stack.resources.items(): # If UpdateReplace is failed, we must restore backup_resource # to existing_stack in case of it may have dependencies in # these stacks. current_resource is the resource that just # created and failed, so put into the backup_stack to delete # anyway. backup_resource_id = backup_resource.resource_id current_resource = self.resources[key] current_resource_id = current_resource.resource_id if backup_resource_id: if (any( failed(child) for child in self.dependencies[current_resource]) or current_resource.status in (current_resource.FAILED, current_resource.IN_PROGRESS)): # If child resource failed to update, current_resource # should be replaced to resolve dependencies. But this # is not fundamental solution. If there are update # failer and success resources in the children, cannot # delete the stack. # Stack class owns dependencies as set of resource's # objects, so we switch members of the resource that is # needed to delete it. self.resources[key].resource_id = backup_resource_id self.resources[ key].properties = backup_resource.properties backup_stack.resources[ key].resource_id = current_resource_id backup_stack.resources[ key].properties = current_resource.properties backup_stack.delete(backup=True) if backup_stack.status != backup_stack.COMPLETE: errs = backup_stack.status_reason failure = 'Error deleting backup resources: %s' % errs self.state_set(action, self.FAILED, 'Failed to %s : %s' % (action, failure)) return snapshots = db_api.snapshot_get_all(self.context, self.id) for snapshot in snapshots: self.delete_snapshot(snapshot) if not backup: try: lifecycle_plugin_utils.do_pre_ops(self.context, self, None, action) except Exception as e: self.state_set( action, self.FAILED, e.args[0] if e.args else 'Failed stack pre-ops: %s' % six.text_type(e)) return action_task = scheduler.DependencyTaskGroup(self.dependencies, resource.Resource.destroy, reverse=True) try: scheduler.TaskRunner(action_task)(timeout=self.timeout_secs()) except exception.ResourceFailure as ex: stack_status = self.FAILED reason = 'Resource %s failed: %s' % (action, six.text_type(ex)) except scheduler.Timeout: stack_status = self.FAILED reason = '%s timed out' % action.title() # If the stack delete succeeded, this is not a backup stack and it's # not a nested stack, we should delete the credentials if stack_status != self.FAILED and not backup and not self.owner_id: # Cleanup stored user_creds so they aren't accessible via # the soft-deleted stack which remains in the DB if self.user_creds_id: user_creds = db_api.user_creds_get(self.user_creds_id) # If we created a trust, delete it if user_creds is not None: trust_id = user_creds.get('trust_id') if trust_id: try: # If the trustor doesn't match the context user the # we have to use the stored context to cleanup the # trust, as although the user evidently has # permission to delete the stack, they don't have # rights to delete the trust unless an admin trustor_id = user_creds.get('trustor_user_id') if self.context.user_id != trustor_id: LOG.debug('Context user_id doesn\'t match ' 'trustor, using stored context') sc = self.stored_context() sc.clients.client('keystone').delete_trust( trust_id) else: self.clients.client('keystone').delete_trust( trust_id) except Exception as ex: LOG.exception(ex) stack_status = self.FAILED reason = ("Error deleting trust: %s" % six.text_type(ex)) # Delete the stored credentials try: db_api.user_creds_delete(self.context, self.user_creds_id) except exception.NotFound: LOG.info( _("Tried to delete user_creds that do not exist " "(stack=%(stack)s user_creds_id=%(uc)s)") % { 'stack': self.id, 'uc': self.user_creds_id }) try: self.user_creds_id = None self.store() except exception.NotFound: LOG.info( _("Tried to store a stack that does not exist " "%s ") % self.id) # If the stack has a domain project, delete it if self.stack_user_project_id and not abandon: try: keystone = self.clients.client('keystone') keystone.delete_stack_domain_project( project_id=self.stack_user_project_id) except Exception as ex: LOG.exception(ex) stack_status = self.FAILED reason = "Error deleting project: %s" % six.text_type(ex) try: self.state_set(action, stack_status, reason) except exception.NotFound: LOG.info( _("Tried to delete stack that does not exist " "%s ") % self.id) if not backup: lifecycle_plugin_utils.do_post_ops(self.context, self, None, action, (self.status == self.FAILED)) if stack_status != self.FAILED: # delete the stack try: db_api.stack_delete(self.context, self.id) except exception.NotFound: LOG.info( _("Tried to delete stack that does not exist " "%s ") % self.id) self.id = None