示例#1
0
    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))
示例#2
0
    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))
示例#3
0
 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
示例#5
0
 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
示例#7
0
    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
示例#8
0
    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)
示例#9
0
文件: stack.py 项目: andrew2king/heat
    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
示例#10
0
文件: stack.py 项目: andrew2king/heat
    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)
示例#11
0
文件: stack.py 项目: andersonvom/heat
    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
示例#12
0
    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