github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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/cmd" 11 "github.com/juju/errors" 12 "github.com/juju/gnuflag" 13 "github.com/juju/utils/clock" 14 15 "github.com/juju/juju/apiserver/common" 16 "github.com/juju/juju/cmd/modelcmd" 17 "github.com/juju/juju/environs" 18 "github.com/juju/juju/environs/config" 19 ) 20 21 const killDoc = ` 22 Forcibly destroy the specified controller. If the API server is accessible, 23 this command will attempt to destroy the controller model and all hosted models 24 and their resources. 25 26 If the API server is unreachable, the machines of the controller model will be 27 destroyed through the cloud provisioner. If there are additional machines, 28 including machines within hosted models, these machines will not be destroyed 29 and will never be reconnected to the Juju controller being destroyed. 30 31 The normal process of killing the controller will involve watching the hosted 32 models as they are brought down in a controlled manner. If for some reason the 33 models do not stop cleanly, there is a default five minute timeout. If no change 34 in the model state occurs for the duration of this timeout, the command will 35 stop watching and destroy the models directly through the cloud provider. 36 37 See also: 38 destroy-controller 39 unregister 40 ` 41 42 // NewKillCommand returns a command to kill a controller. Killing is a forceful 43 // destroy. 44 func NewKillCommand() cmd.Command { 45 // Even though this command is all about killing a controller we end up 46 // needing environment endpoints so we can fall back to the client destroy 47 // environment method. This shouldn't really matter in practice as the 48 // user trying to take down the controller will need to have access to the 49 // controller environment anyway. 50 return wrapKillCommand(&killCommand{ 51 clock: clock.WallClock, 52 }, nil, clock.WallClock) 53 } 54 55 // wrapKillCommand provides the common wrapping used by tests and 56 // the default NewKillCommand above. 57 func wrapKillCommand(kill *killCommand, apiOpen modelcmd.APIOpener, clock clock.Clock) cmd.Command { 58 if apiOpen == nil { 59 apiOpen = modelcmd.OpenFunc(kill.JujuCommandBase.NewAPIRoot) 60 } 61 openStrategy := modelcmd.NewTimeoutOpener(apiOpen, clock, 10*time.Second) 62 return modelcmd.WrapController( 63 kill, 64 modelcmd.WrapControllerSkipControllerFlags, 65 modelcmd.WrapControllerSkipDefaultController, 66 modelcmd.WrapControllerAPIOpener(openStrategy), 67 ) 68 } 69 70 // killCommand kills the specified controller. 71 type killCommand struct { 72 destroyCommandBase 73 74 clock clock.Clock 75 timeout time.Duration 76 } 77 78 // SetFlags implements Command.SetFlags. 79 func (c *killCommand) SetFlags(f *gnuflag.FlagSet) { 80 c.destroyCommandBase.SetFlags(f) 81 f.Var(newDurationValue(time.Minute*5, &c.timeout), "t", "Timeout before direct destruction") 82 f.Var(newDurationValue(time.Minute*5, &c.timeout), "timeout", "") 83 } 84 85 // Info implements Command.Info. 86 func (c *killCommand) Info() *cmd.Info { 87 return &cmd.Info{ 88 Name: "kill-controller", 89 Args: "<controller name>", 90 Purpose: "Forcibly terminate all machines and other associated resources for a Juju controller.", 91 Doc: killDoc, 92 } 93 } 94 95 // Init implements Command.Init. 96 func (c *killCommand) Init(args []string) error { 97 return c.destroyCommandBase.Init(args) 98 } 99 100 // Run implements Command.Run 101 func (c *killCommand) Run(ctx *cmd.Context) error { 102 controllerName := c.ControllerName() 103 store := c.ClientStore() 104 if !c.assumeYes { 105 if err := confirmDestruction(ctx, controllerName); err != nil { 106 return err 107 } 108 } 109 110 // Attempt to connect to the API. 111 api, err := c.getControllerAPI() 112 switch { 113 case err == nil: 114 defer api.Close() 115 case errors.Cause(err) == common.ErrPerm: 116 return errors.Annotate(err, "cannot destroy controller") 117 default: 118 if errors.Cause(err) != modelcmd.ErrConnTimedOut { 119 logger.Debugf("unable to open api: %s", err) 120 } 121 ctx.Infof("Unable to open API: %s\n", err) 122 api = nil 123 } 124 125 // Obtain controller environ so we can clean up afterwards. 126 controllerEnviron, err := c.getControllerEnviron(ctx, store, controllerName, api) 127 if err != nil { 128 return errors.Annotate(err, "getting controller environ") 129 } 130 // If we were unable to connect to the API, just destroy the controller through 131 // the environs interface. 132 if api == nil { 133 ctx.Infof("Unable to connect to the API server, destroying through provider") 134 return environs.Destroy(controllerName, controllerEnviron, store) 135 } 136 137 // Attempt to destroy the controller and all environments. 138 err = api.DestroyController(true) 139 if err != nil { 140 ctx.Infof("Unable to destroy controller through the API: %s\nDestroying through provider", err) 141 return environs.Destroy(controllerName, controllerEnviron, store) 142 } 143 144 ctx.Infof("Destroying controller %q\nWaiting for resources to be reclaimed", controllerName) 145 146 uuid := controllerEnviron.Config().UUID() 147 if err := c.WaitForModels(ctx, api, uuid); err != nil { 148 c.DirectDestroyRemaining(ctx, api) 149 } 150 return environs.Destroy(controllerName, controllerEnviron, store) 151 } 152 153 // DirectDestroyRemaining will attempt to directly destroy any remaining 154 // models that have machines left. 155 func (c *killCommand) DirectDestroyRemaining(ctx *cmd.Context, api destroyControllerAPI) { 156 hasErrors := false 157 hostedConfig, err := api.HostedModelConfigs() 158 if err != nil { 159 hasErrors = true 160 logger.Errorf("unable to retrieve hosted model config: %v", err) 161 } 162 for _, model := range hostedConfig { 163 ctx.Infof("Killing %s/%s directly", model.Owner.Canonical(), model.Name) 164 cfg, err := config.New(config.NoDefaults, model.Config) 165 if err != nil { 166 logger.Errorf(err.Error()) 167 hasErrors = true 168 continue 169 } 170 env, err := environs.New(environs.OpenParams{ 171 Cloud: model.CloudSpec, 172 Config: cfg, 173 }) 174 if err != nil { 175 logger.Errorf(err.Error()) 176 hasErrors = true 177 continue 178 } 179 if err := env.Destroy(); err != nil { 180 logger.Errorf(err.Error()) 181 hasErrors = true 182 } else { 183 ctx.Infof(" done") 184 } 185 } 186 if hasErrors { 187 logger.Errorf("there were problems destroying some models, manual intervention may be necessary to ensure resources are released") 188 } else { 189 ctx.Infof("All hosted models destroyed, cleaning up controller machines") 190 } 191 } 192 193 // WaitForModels will wait for the models to bring themselves down nicely. 194 // It will return the UUIDs of any models that need to be removed forceably. 195 func (c *killCommand) WaitForModels(ctx *cmd.Context, api destroyControllerAPI, uuid string) error { 196 thirtySeconds := (time.Second * 30) 197 updateStatus := newTimedStatusUpdater(ctx, api, uuid, c.clock) 198 199 ctrStatus, modelsStatus := updateStatus(0) 200 lastStatus := ctrStatus 201 lastChange := c.clock.Now().Truncate(time.Second) 202 deadline := lastChange.Add(c.timeout) 203 for ; hasUnDeadModels(modelsStatus) && (deadline.After(c.clock.Now())); ctrStatus, modelsStatus = updateStatus(5 * time.Second) { 204 now := c.clock.Now().Truncate(time.Second) 205 if ctrStatus != lastStatus { 206 lastStatus = ctrStatus 207 lastChange = now 208 deadline = lastChange.Add(c.timeout) 209 } 210 timeSinceLastChange := now.Sub(lastChange) 211 timeUntilDestruction := deadline.Sub(now) 212 warning := "" 213 // We want to show the warning if it has been more than 30 seconds since 214 // the last change, or we are within 30 seconds of our timeout. 215 if timeSinceLastChange > thirtySeconds || timeUntilDestruction < thirtySeconds { 216 warning = fmt.Sprintf(", will kill machines directly in %s", timeUntilDestruction) 217 } 218 ctx.Infof("%s%s", fmtCtrStatus(ctrStatus), warning) 219 for _, modelStatus := range modelsStatus { 220 ctx.Verbosef(fmtModelStatus(modelStatus)) 221 } 222 } 223 if hasUnDeadModels(modelsStatus) { 224 return errors.New("timed out") 225 } else { 226 ctx.Infof("All hosted models reclaimed, cleaning up controller machines") 227 } 228 return nil 229 } 230 231 type durationValue time.Duration 232 233 func newDurationValue(value time.Duration, p *time.Duration) *durationValue { 234 *p = value 235 return (*durationValue)(p) 236 } 237 238 func (d *durationValue) Set(s string) error { 239 v, err := time.ParseDuration(s) 240 if err != nil { 241 return err 242 } 243 *d = durationValue(v) 244 return err 245 } 246 247 func (d *durationValue) String() string { return (*time.Duration)(d).String() }