104
125
// Obtain controller environ so we can clean up afterwards.
105
controllerEnviron, err := c.getControllerEnviron(store, controllerName, api)
126
controllerEnviron, err := c.getControllerEnviron(ctx, store, controllerName, api)
107
128
return errors.Annotate(err, "getting controller environ")
109
130
// If we were unable to connect to the API, just destroy the controller through
110
131
// the environs interface.
112
ctx.Infof("Unable to connect to the API server. Destroying through provider.")
133
ctx.Infof("Unable to connect to the API server, destroying through provider")
113
134
return environs.Destroy(controllerName, controllerEnviron, store)
116
137
// Attempt to destroy the controller and all environments.
117
138
err = api.DestroyController(true)
119
ctx.Infof("Unable to destroy controller through the API: %s. Destroying through provider.", err)
140
ctx.Infof("Unable to destroy controller through the API: %s\nDestroying through provider", err)
120
141
return environs.Destroy(controllerName, controllerEnviron, store)
123
144
ctx.Infof("Destroying controller %q\nWaiting for resources to be reclaimed", controllerName)
125
updateStatus := newTimedStatusUpdater(ctx, api, controllerEnviron.Config().UUID())
126
for ctrStatus, envsStatus := updateStatus(0); hasUnDeadModels(envsStatus); ctrStatus, envsStatus = updateStatus(2 * time.Second) {
127
ctx.Infof(fmtCtrStatus(ctrStatus))
128
for _, envStatus := range envsStatus {
129
ctx.Verbosef(fmtModelStatus(envStatus))
146
uuid := controllerEnviron.Config().UUID()
147
if err := c.WaitForModels(ctx, api, uuid); err != nil {
148
c.DirectDestroyRemaining(ctx, api)
133
ctx.Infof("All hosted models reclaimed, cleaning up controller machines")
135
150
return environs.Destroy(controllerName, controllerEnviron, store)
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) {
157
hostedConfig, err := api.HostedModelConfigs()
160
logger.Errorf("unable to retrieve hosted model config: %v", err)
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)
166
logger.Errorf(err.Error())
170
env, err := environs.New(environs.OpenParams{
171
Cloud: model.CloudSpec,
175
logger.Errorf(err.Error())
179
if err := env.Destroy(); err != nil {
180
logger.Errorf(err.Error())
187
logger.Errorf("there were problems destroying some models, manual intervention may be necessary to ensure resources are released")
189
ctx.Infof("All hosted models destroyed, cleaning up controller machines")
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)
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
208
deadline = lastChange.Add(c.timeout)
210
timeSinceLastChange := now.Sub(lastChange)
211
timeUntilDestruction := deadline.Sub(now)
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)
218
ctx.Infof("%s%s", fmtCtrStatus(ctrStatus), warning)
219
for _, modelStatus := range modelsStatus {
220
ctx.Verbosef(fmtModelStatus(modelStatus))
223
if hasUnDeadModels(modelsStatus) {
224
return errors.New("timed out")
226
ctx.Infof("All hosted models reclaimed, cleaning up controller machines")
231
type durationValue time.Duration
233
func newDurationValue(value time.Duration, p *time.Duration) *durationValue {
235
return (*durationValue)(p)
238
func (d *durationValue) Set(s string) error {
239
v, err := time.ParseDuration(s)
243
*d = durationValue(v)
247
func (d *durationValue) String() string { return (*time.Duration)(d).String() }