// ensureUserFriendlyErrorLog ensures that error will be logged and displayed // in a user-friendly manner with readable and digestable error message. func (c *destroyCommand) ensureUserFriendlyErrorLog(destroyErr error, ctx *cmd.Context, api destroyControllerAPI) error { if destroyErr == nil { return nil } if params.IsCodeOperationBlocked(destroyErr) { logger.Errorf(destroyControllerBlockedMsg) if api != nil { models, err := api.ListBlockedModels() out := &bytes.Buffer{} if err == nil { var info interface{} info, err = block.FormatModelBlockInfo(models) if err != nil { return errors.Trace(err) } err = block.FormatTabularBlockedModels(out, info) } if err != nil { logger.Errorf("Unable to list models: %s", err) return cmd.ErrSilent } ctx.Infof(out.String()) } return cmd.ErrSilent } if params.IsCodeHasHostedModels(destroyErr) { return destroyErr } logger.Errorf(stdFailureMsg, c.ControllerName()) return destroyErr }
// ensureUserFriendlyErrorLog ensures that error will be logged and displayed // in a user-friendly manner with readable and digestable error message. func (c *destroyCommand) ensureUserFriendlyErrorLog(destroyErr error, ctx *cmd.Context, api destroyControllerAPI) error { if destroyErr == nil { return nil } if params.IsCodeOperationBlocked(destroyErr) { logger.Errorf(destroyControllerBlockedMsg) if api != nil { models, err := api.ListBlockedModels() var bytes []byte if err == nil { bytes, err = formatTabularBlockedModels(models) } if err != nil { logger.Errorf("Unable to list blocked models: %s", err) return cmd.ErrSilent } ctx.Infof(string(bytes)) } return cmd.ErrSilent } if params.IsCodeHasHostedModels(destroyErr) { return destroyErr } logger.Errorf(stdFailureMsg, c.ControllerName()) return destroyErr }
// RestoreError makes a best effort at converting the given error // back into an error originally converted by ServerError(). func RestoreError(err error) error { err = errors.Cause(err) if apiErr, ok := err.(*params.Error); !ok { return err } else if apiErr == nil { return nil } if params.ErrCode(err) == "" { return err } msg := err.Error() if singleton, ok := singletonError(err); ok { return singleton } // TODO(ericsnow) Support the other error types handled by ServerError(). switch { case params.IsCodeUnauthorized(err): return errors.NewUnauthorized(nil, msg) case params.IsCodeNotFound(err): // TODO(ericsnow) UnknownModelError should be handled here too. // ...by parsing msg? return errors.NewNotFound(nil, msg) case params.IsCodeAlreadyExists(err): return errors.NewAlreadyExists(nil, msg) case params.IsCodeNotAssigned(err): return errors.NewNotAssigned(nil, msg) case params.IsCodeHasAssignedUnits(err): // TODO(ericsnow) Handle state.HasAssignedUnitsError here. // ...by parsing msg? return err case params.IsCodeHasHostedModels(err): return err case params.IsCodeNoAddressSet(err): // TODO(ericsnow) Handle isNoAddressSetError here. // ...by parsing msg? return err case params.IsCodeNotProvisioned(err): return errors.NewNotProvisioned(nil, msg) case params.IsCodeUpgradeInProgress(err): // TODO(ericsnow) Handle state.UpgradeInProgressError here. // ...by parsing msg? return err case params.IsCodeMachineHasAttachedStorage(err): // TODO(ericsnow) Handle state.HasAttachmentsError here. // ...by parsing msg? return err case params.IsCodeNotSupported(err): return errors.NewNotSupported(nil, msg) case params.IsBadRequest(err): return errors.NewBadRequest(nil, msg) case params.IsMethodNotAllowed(err): return errors.NewMethodNotAllowed(nil, msg) case params.ErrCode(err) == params.CodeDischargeRequired: // TODO(ericsnow) Handle DischargeRequiredError here. return err default: return err } }
// Run implements Command.Run func (c *destroyCommand) Run(ctx *cmd.Context) error { controllerName := c.ControllerName() store := c.ClientStore() if !c.assumeYes { if err := confirmDestruction(ctx, c.ControllerName()); err != nil { return err } } // Attempt to connect to the API. If we can't, fail the destroy. Users will // need to use the controller kill command if we can't connect. api, err := c.getControllerAPI() if err != nil { return c.ensureUserFriendlyErrorLog(errors.Annotate(err, "cannot connect to API"), ctx, nil) } defer api.Close() // Obtain controller environ so we can clean up afterwards. controllerEnviron, err := c.getControllerEnviron(ctx, store, controllerName, api) if err != nil { return errors.Annotate(err, "getting controller environ") } for { // Attempt to destroy the controller. ctx.Infof("Destroying controller") var hasHostedModels bool err = api.DestroyController(c.destroyModels) if err != nil { if params.IsCodeHasHostedModels(err) { hasHostedModels = true } else { return c.ensureUserFriendlyErrorLog( errors.Annotate(err, "cannot destroy controller"), ctx, api, ) } } updateStatus := newTimedStatusUpdater(ctx, api, controllerEnviron.Config().UUID(), clock.WallClock) ctrStatus, modelsStatus := updateStatus(0) if !c.destroyModels { if err := c.checkNoAliveHostedModels(ctx, modelsStatus); err != nil { return errors.Trace(err) } if hasHostedModels && !hasUnDeadModels(modelsStatus) { // When we called DestroyController before, we were // informed that there were hosted models remaining. // When we checked just now, there were none. We should // try destroying again. continue } } // Even if we've not just requested for hosted models to be destroyed, // there may be some being destroyed already. We need to wait for them. ctx.Infof("Waiting for hosted model resources to be reclaimed") for ; hasUnDeadModels(modelsStatus); ctrStatus, modelsStatus = updateStatus(2 * time.Second) { ctx.Infof(fmtCtrStatus(ctrStatus)) for _, model := range modelsStatus { ctx.Verbosef(fmtModelStatus(model)) } } ctx.Infof("All hosted models reclaimed, cleaning up controller machines") return environs.Destroy(c.ControllerName(), controllerEnviron, store) } }