github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/controller/kill.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package controller 5 6 import ( 7 "fmt" 8 "time" 9 10 "github.com/juju/clock" 11 "github.com/juju/cmd" 12 "github.com/juju/errors" 13 "github.com/juju/gnuflag" 14 15 "github.com/juju/juju/api/controller" 16 "github.com/juju/juju/api/credentialmanager" 17 "github.com/juju/juju/apiserver/common" 18 jujucmd "github.com/juju/juju/cmd" 19 "github.com/juju/juju/cmd/modelcmd" 20 "github.com/juju/juju/environs" 21 "github.com/juju/juju/environs/config" 22 ) 23 24 const killDoc = ` 25 Forcibly destroy the specified controller. If the API server is accessible, 26 this command will attempt to destroy the controller model and all hosted models 27 and their resources. 28 29 If the API server is unreachable, the machines of the controller model will be 30 destroyed through the cloud provisioner. If there are additional machines, 31 including machines within hosted models, these machines will not be destroyed 32 and will never be reconnected to the Juju controller being destroyed. 33 34 The normal process of killing the controller will involve watching the hosted 35 models as they are brought down in a controlled manner. If for some reason the 36 models do not stop cleanly, there is a default five minute timeout. If no change 37 in the model state occurs for the duration of this timeout, the command will 38 stop watching and destroy the models directly through the cloud provider. 39 40 See also: 41 destroy-controller 42 unregister 43 ` 44 45 // NewKillCommand returns a command to kill a controller. Killing is a 46 // forceful destroy. 47 func NewKillCommand() modelcmd.Command { 48 cmd := killCommand{clock: clock.WallClock} 49 cmd.environsDestroy = environs.Destroy 50 return wrapKillCommand(&cmd) 51 } 52 53 // wrapKillCommand provides the common wrapping used by tests and 54 // the default NewKillCommand above. 55 func wrapKillCommand(kill *killCommand) modelcmd.Command { 56 return modelcmd.WrapController( 57 kill, 58 modelcmd.WrapControllerSkipControllerFlags, 59 modelcmd.WrapControllerSkipDefaultController, 60 ) 61 } 62 63 // killCommand kills the specified controller. 64 type killCommand struct { 65 destroyCommandBase 66 67 clock clock.Clock 68 timeout time.Duration 69 } 70 71 // SetFlags implements Command.SetFlags. 72 func (c *killCommand) SetFlags(f *gnuflag.FlagSet) { 73 c.destroyCommandBase.SetFlags(f) 74 f.Var(newDurationValue(time.Minute*5, &c.timeout), "t", "Timeout before direct destruction") 75 f.Var(newDurationValue(time.Minute*5, &c.timeout), "timeout", "") 76 } 77 78 // Info implements Command.Info. 79 func (c *killCommand) Info() *cmd.Info { 80 return jujucmd.Info(&cmd.Info{ 81 Name: "kill-controller", 82 Args: "<controller name>", 83 Purpose: "Forcibly terminate all machines and other associated resources for a Juju controller.", 84 Doc: killDoc, 85 }) 86 } 87 88 // Init implements Command.Init. 89 func (c *killCommand) Init(args []string) error { 90 return c.destroyCommandBase.Init(args) 91 } 92 93 var errConnTimedOut = errors.New("open connection timed out") 94 95 // Run implements Command.Run 96 func (c *killCommand) Run(ctx *cmd.Context) error { 97 controllerName, err := c.ControllerName() 98 if err != nil { 99 return errors.Trace(err) 100 } 101 store := c.ClientStore() 102 if !c.assumeYes { 103 if err := confirmDestruction(ctx, controllerName); err != nil { 104 return err 105 } 106 } 107 108 // Attempt to connect to the API. 109 api, err := c.getControllerAPIWithTimeout(10 * time.Second) 110 switch errors.Cause(err) { 111 case nil: 112 defer api.Close() 113 case common.ErrPerm: 114 return errors.Annotate(err, "cannot destroy controller") 115 default: 116 ctx.Infof("Unable to open API: %s\n", err) 117 } 118 119 // Obtain controller environ so we can clean up afterwards. 120 controllerEnviron, err := c.getControllerEnviron(ctx, store, controllerName, api) 121 if err != nil { 122 return errors.Annotate(err, "getting controller environ") 123 } 124 cloudCallCtx := cloudCallContext(c.controllerCredentialAPIFunc) 125 // If we were unable to connect to the API, just destroy the controller through 126 // the environs interface. 127 if api == nil { 128 ctx.Infof("Unable to connect to the API server, destroying through provider") 129 return c.environsDestroy(controllerName, controllerEnviron, cloudCallCtx, store) 130 } 131 132 // Attempt to destroy the controller and all models and storage. 133 destroyStorage := true 134 err = api.DestroyController(controller.DestroyControllerParams{ 135 DestroyModels: true, 136 DestroyStorage: &destroyStorage, 137 }) 138 if err != nil { 139 ctx.Infof("Unable to destroy controller through the API: %s\nDestroying through provider", err) 140 return c.environsDestroy(controllerName, controllerEnviron, cloudCallCtx, store) 141 } 142 143 ctx.Infof("Destroying controller %q\nWaiting for resources to be reclaimed", controllerName) 144 145 uuid := controllerEnviron.Config().UUID() 146 if err := c.WaitForModels(ctx, api, uuid); err != nil { 147 c.DirectDestroyRemaining(ctx, api) 148 } 149 return c.environsDestroy(controllerName, controllerEnviron, cloudCallCtx, store) 150 } 151 152 func (c *killCommand) getControllerAPIWithTimeout(timeout time.Duration) (destroyControllerAPI, error) { 153 type result struct { 154 c destroyControllerAPI 155 err error 156 } 157 resultc := make(chan result) 158 done := make(chan struct{}) 159 go func() { 160 api, err := c.getControllerAPI() 161 select { 162 case resultc <- result{api, err}: 163 case <-done: 164 if api != nil { 165 api.Close() 166 } 167 } 168 }() 169 select { 170 case r := <-resultc: 171 return r.c, r.err 172 case <-c.clock.After(timeout): 173 close(done) 174 return nil, errConnTimedOut 175 } 176 } 177 178 // DirectDestroyRemaining will attempt to directly destroy any remaining 179 // models that have machines left. 180 func (c *killCommand) DirectDestroyRemaining(ctx *cmd.Context, api destroyControllerAPI) { 181 hasErrors := false 182 hostedConfig, err := api.HostedModelConfigs() 183 if err != nil { 184 hasErrors = true 185 logger.Errorf("unable to retrieve hosted model config: %v", err) 186 } 187 for _, model := range hostedConfig { 188 if model.Error != nil { 189 // We can only display model name here since 190 // the error coming from api can be anything 191 // including the parsing of the model owner tag. 192 // Only model name is guaranteed to be set in the result 193 // when an error is returned. 194 hasErrors = true 195 logger.Errorf("could not kill %s directly: %v", model.Name, model.Error) 196 continue 197 } 198 ctx.Infof("Killing %s/%s directly", model.Owner.Id(), model.Name) 199 cfg, err := config.New(config.NoDefaults, model.Config) 200 if err != nil { 201 logger.Errorf(err.Error()) 202 hasErrors = true 203 continue 204 } 205 p, err := environs.Provider(model.CloudSpec.Type) 206 if err != nil { 207 logger.Errorf(err.Error()) 208 hasErrors = true 209 continue 210 } 211 // TODO(caas) - only cloud providers support Destroy() 212 if cloudProvider, ok := p.(environs.CloudEnvironProvider); ok { 213 env, err := environs.Open(cloudProvider, environs.OpenParams{ 214 Cloud: model.CloudSpec, 215 Config: cfg, 216 }) 217 if err != nil { 218 logger.Errorf(err.Error()) 219 hasErrors = true 220 continue 221 } 222 cloudCallCtx := cloudCallContext(c.credentialAPIFunctionForModel(model.Name)) 223 if err := env.Destroy(cloudCallCtx); err != nil { 224 logger.Errorf(err.Error()) 225 hasErrors = true 226 continue 227 } 228 } 229 ctx.Infof(" done") 230 } 231 if hasErrors { 232 logger.Errorf("there were problems destroying some models, manual intervention may be necessary to ensure resources are released") 233 } else { 234 ctx.Infof("All hosted models destroyed, cleaning up controller machines") 235 } 236 } 237 238 func (c *killCommand) credentialAPIFunctionForModel(modelName string) newCredentialAPIFunc { 239 f := func(api CredentialAPI, err error) newCredentialAPIFunc { 240 return func() (CredentialAPI, error) { 241 return api, err 242 } 243 } 244 root, err := c.NewModelAPIRoot(modelName) 245 if err != nil { 246 return f(nil, errors.Trace(err)) 247 } 248 return f(credentialmanager.NewClient(root), nil) 249 } 250 251 // WaitForModels will wait for the models to bring themselves down nicely. 252 // It will return the UUIDs of any models that need to be removed forceably. 253 func (c *killCommand) WaitForModels(ctx *cmd.Context, api destroyControllerAPI, uuid string) error { 254 thirtySeconds := (time.Second * 30) 255 updateStatus := newTimedStatusUpdater(ctx, api, uuid, c.clock) 256 257 envStatus := updateStatus(0) 258 lastStatus := envStatus.controller 259 lastChange := c.clock.Now().Truncate(time.Second) 260 deadline := lastChange.Add(c.timeout) 261 // Check for both undead models and live machines, as machines may be 262 // in the controller model. 263 for ; hasUnreclaimedResources(envStatus) && (deadline.After(c.clock.Now())); envStatus = updateStatus(5 * time.Second) { 264 now := c.clock.Now().Truncate(time.Second) 265 if envStatus.controller != lastStatus { 266 lastStatus = envStatus.controller 267 lastChange = now 268 deadline = lastChange.Add(c.timeout) 269 } 270 timeSinceLastChange := now.Sub(lastChange) 271 timeUntilDestruction := deadline.Sub(now) 272 warning := "" 273 // We want to show the warning if it has been more than 30 seconds since 274 // the last change, or we are within 30 seconds of our timeout. 275 if timeSinceLastChange > thirtySeconds || timeUntilDestruction < thirtySeconds { 276 warning = fmt.Sprintf(", will kill machines directly in %s", timeUntilDestruction) 277 } 278 ctx.Infof("%s%s", fmtCtrStatus(envStatus.controller), warning) 279 for _, modelStatus := range envStatus.models { 280 ctx.Verbosef(fmtModelStatus(modelStatus)) 281 } 282 } 283 if hasUnreclaimedResources(envStatus) { 284 return errors.New("timed out") 285 } else { 286 ctx.Infof("All hosted models reclaimed, cleaning up controller machines") 287 } 288 return nil 289 } 290 291 type durationValue time.Duration 292 293 func newDurationValue(value time.Duration, p *time.Duration) *durationValue { 294 *p = value 295 return (*durationValue)(p) 296 } 297 298 func (d *durationValue) Set(s string) error { 299 v, err := time.ParseDuration(s) 300 if err != nil { 301 return err 302 } 303 *d = durationValue(v) 304 return err 305 } 306 307 func (d *durationValue) String() string { return (*time.Duration)(d).String() }