github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/cmd/juju/model/destroy.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for infos. 3 4 package model 5 6 import ( 7 "fmt" 8 "time" 9 10 "github.com/juju/cmd" 11 "github.com/juju/errors" 12 "github.com/juju/gnuflag" 13 "github.com/juju/loggo" 14 "gopkg.in/juju/names.v2" 15 16 "github.com/juju/juju/api/base" 17 "github.com/juju/juju/api/modelmanager" 18 "github.com/juju/juju/apiserver/params" 19 jujucmd "github.com/juju/juju/cmd" 20 "github.com/juju/juju/cmd/juju/block" 21 "github.com/juju/juju/cmd/modelcmd" 22 "github.com/juju/juju/jujuclient" 23 ) 24 25 var logger = loggo.GetLogger("juju.cmd.juju.model") 26 27 // NewDestroyCommand returns a command used to destroy a model. 28 func NewDestroyCommand() cmd.Command { 29 destroyCmd := &destroyCommand{} 30 destroyCmd.RefreshModels = destroyCmd.ModelCommandBase.RefreshModels 31 destroyCmd.sleepFunc = time.Sleep 32 return modelcmd.Wrap( 33 destroyCmd, 34 modelcmd.WrapSkipDefaultModel, 35 modelcmd.WrapSkipModelFlags, 36 ) 37 } 38 39 // destroyCommand destroys the specified model. 40 type destroyCommand struct { 41 modelcmd.ModelCommandBase 42 // RefreshModels hides the RefreshModels function defined 43 // in ModelCommandBase. This allows overriding for testing. 44 // NOTE: ideal solution would be to have the base implement a method 45 // like store.ModelByName which auto-refreshes. 46 RefreshModels func(jujuclient.ClientStore, string) error 47 48 // sleepFunc is used when calling the timed function to get model status updates. 49 sleepFunc func(time.Duration) 50 51 envName string 52 assumeYes bool 53 api DestroyModelAPI 54 } 55 56 var destroyDoc = ` 57 Destroys the specified model. This will result in the non-recoverable 58 removal of all the units operating in the model and any resources stored 59 there. Due to the irreversible nature of the command, it will prompt for 60 confirmation (unless overridden with the '-y' option) before taking any 61 action. 62 63 Examples: 64 65 juju destroy-model test 66 juju destroy-model -y mymodel 67 68 See also: 69 destroy-controller 70 ` 71 var destroyEnvMsg = ` 72 WARNING! This command will destroy the %q model. 73 This includes all machines, applications, data and other resources. 74 75 Continue [y/N]? `[1:] 76 77 // DestroyModelAPI defines the methods on the modelmanager 78 // API that the destroy command calls. It is exported for mocking in tests. 79 type DestroyModelAPI interface { 80 Close() error 81 DestroyModel(names.ModelTag) error 82 ModelStatus(models ...names.ModelTag) ([]base.ModelStatus, error) 83 } 84 85 // Info implements Command.Info. 86 func (c *destroyCommand) Info() *cmd.Info { 87 return &cmd.Info{ 88 Name: "destroy-model", 89 Args: "[<controller name>:]<model name>", 90 Purpose: "Terminate all machines and resources for a non-controller model.", 91 Doc: destroyDoc, 92 } 93 } 94 95 // SetFlags implements Command.SetFlags. 96 func (c *destroyCommand) SetFlags(f *gnuflag.FlagSet) { 97 c.ModelCommandBase.SetFlags(f) 98 f.BoolVar(&c.assumeYes, "y", false, "Do not prompt for confirmation") 99 f.BoolVar(&c.assumeYes, "yes", false, "") 100 } 101 102 // Init implements Command.Init. 103 func (c *destroyCommand) Init(args []string) error { 104 switch len(args) { 105 case 0: 106 return errors.New("no model specified") 107 case 1: 108 return c.SetModelName(args[0]) 109 default: 110 return cmd.CheckEmpty(args[1:]) 111 } 112 } 113 114 func (c *destroyCommand) getAPI() (DestroyModelAPI, error) { 115 if c.api != nil { 116 return c.api, nil 117 } 118 root, err := c.NewControllerAPIRoot() 119 if err != nil { 120 return nil, errors.Trace(err) 121 } 122 return modelmanager.NewClient(root), nil 123 } 124 125 // Run implements Command.Run 126 func (c *destroyCommand) Run(ctx *cmd.Context) error { 127 store := c.ClientStore() 128 controllerName := c.ControllerName() 129 modelName := c.ModelName() 130 131 controllerDetails, err := store.ControllerByName(controllerName) 132 if err != nil { 133 return errors.Annotate(err, "cannot read controller details") 134 } 135 modelDetails, err := store.ModelByName(controllerName, modelName) 136 if errors.IsNotFound(err) { 137 if err := c.RefreshModels(store, controllerName); err != nil { 138 return errors.Annotate(err, "refreshing models cache") 139 } 140 // Now try again. 141 modelDetails, err = store.ModelByName(controllerName, modelName) 142 } 143 if err != nil { 144 return errors.Annotate(err, "cannot read model info") 145 } 146 147 if modelDetails.ModelUUID == controllerDetails.ControllerUUID { 148 return errors.Errorf("%q is a controller; use 'juju destroy-controller' to destroy it", modelName) 149 } 150 151 if !c.assumeYes { 152 fmt.Fprintf(ctx.Stdout, destroyEnvMsg, modelName) 153 154 if err := jujucmd.UserConfirmYes(ctx); err != nil { 155 return errors.Annotate(err, "model destruction") 156 } 157 } 158 159 // Attempt to connect to the API. If we can't, fail the destroy. 160 api, err := c.getAPI() 161 if err != nil { 162 return errors.Annotate(err, "cannot connect to API") 163 } 164 defer api.Close() 165 166 // Attempt to destroy the model. 167 ctx.Infof("Destroying model") 168 err = api.DestroyModel(names.NewModelTag(modelDetails.ModelUUID)) 169 if err != nil { 170 return c.handleError(errors.Annotate(err, "cannot destroy model"), modelName) 171 } 172 173 // Wait for model to be destroyed. 174 const modelStatusPollWait = 2 * time.Second 175 modelStatus := newTimedModelStatus(ctx, api, names.NewModelTag(modelDetails.ModelUUID), c.sleepFunc) 176 modelData := modelStatus(0) 177 for modelData != nil { 178 ctx.Infof(formatDestroyModelInfo(modelData) + "...") 179 modelData = modelStatus(modelStatusPollWait) 180 } 181 182 err = store.RemoveModel(controllerName, modelName) 183 if err != nil && !errors.IsNotFound(err) { 184 return errors.Trace(err) 185 } 186 return nil 187 } 188 189 type modelData struct { 190 machineCount int 191 applicationCount int 192 } 193 194 // newTimedModelStatus returns a function which waits a given period of time 195 // before querying the API server for the status of a model. 196 func newTimedModelStatus(ctx *cmd.Context, api DestroyModelAPI, tag names.ModelTag, sleepFunc func(time.Duration)) func(time.Duration) *modelData { 197 return func(wait time.Duration) *modelData { 198 sleepFunc(wait) 199 status, err := api.ModelStatus(tag) 200 if err != nil { 201 if params.ErrCode(err) != params.CodeNotFound { 202 ctx.Infof("Unable to get the model status from the API: %v.", err) 203 } 204 return nil 205 } 206 if l := len(status); l != 1 { 207 ctx.Infof("error finding model status: expected one result, got %d", l) 208 return nil 209 } 210 return &modelData{ 211 machineCount: status[0].HostedMachineCount, 212 applicationCount: status[0].ServiceCount, 213 } 214 } 215 } 216 217 func formatDestroyModelInfo(data *modelData) string { 218 out := "Waiting on model to be removed" 219 if data.machineCount == 0 && data.applicationCount == 0 { 220 return out 221 } 222 if data.machineCount > 0 { 223 out += fmt.Sprintf(", %d machine(s)", data.machineCount) 224 } 225 if data.applicationCount > 0 { 226 out += fmt.Sprintf(", %d application(s)", data.applicationCount) 227 } 228 return out 229 } 230 231 func (c *destroyCommand) handleError(err error, modelName string) error { 232 if err == nil { 233 return nil 234 } 235 if params.IsCodeOperationBlocked(err) { 236 return block.ProcessBlockedError(err, block.BlockDestroy) 237 } 238 logger.Errorf(`failed to destroy model %q`, modelName) 239 return err 240 }