~juju-qa/ubuntu/xenial/juju/2.0-rc2

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/cmd/juju/controller/kill.go

  • Committer: Nicholas Skaggs
  • Date: 2016-09-30 14:39:30 UTC
  • mfrom: (1.8.1)
  • Revision ID: nicholas.skaggs@canonical.com-20160930143930-vwwhrefh6ftckccy
import upstream

Show diffs side-by-side

added added

removed removed

Lines of Context:
4
4
package controller
5
5
 
6
6
import (
 
7
        "fmt"
7
8
        "time"
8
9
 
9
10
        "github.com/juju/cmd"
10
11
        "github.com/juju/errors"
 
12
        "github.com/juju/gnuflag"
11
13
        "github.com/juju/utils/clock"
12
14
 
13
15
        "github.com/juju/juju/apiserver/common"
14
16
        "github.com/juju/juju/cmd/modelcmd"
15
17
        "github.com/juju/juju/environs"
 
18
        "github.com/juju/juju/environs/config"
16
19
)
17
20
 
18
21
const killDoc = `
25
28
including machines within hosted models, these machines will not be destroyed
26
29
and will never be reconnected to the Juju controller being destroyed.
27
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
 
28
37
See also:
29
38
    destroy-controller
30
39
    unregister
38
47
        // environment method. This shouldn't really matter in practice as the
39
48
        // user trying to take down the controller will need to have access to the
40
49
        // controller environment anyway.
41
 
        return wrapKillCommand(&killCommand{}, nil, clock.WallClock)
 
50
        return wrapKillCommand(&killCommand{
 
51
                clock: clock.WallClock,
 
52
        }, nil, clock.WallClock)
42
53
}
43
54
 
44
55
// wrapKillCommand provides the common wrapping used by tests and
59
70
// killCommand kills the specified controller.
60
71
type killCommand struct {
61
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", "")
62
83
}
63
84
 
64
85
// Info implements Command.Info.
102
123
        }
103
124
 
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)
106
127
        if err != nil {
107
128
                return errors.Annotate(err, "getting controller environ")
108
129
        }
109
130
        // If we were unable to connect to the API, just destroy the controller through
110
131
        // the environs interface.
111
132
        if api == nil {
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)
114
135
        }
115
136
 
116
137
        // Attempt to destroy the controller and all environments.
117
138
        err = api.DestroyController(true)
118
139
        if err != nil {
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)
121
142
        }
122
143
 
123
144
        ctx.Infof("Destroying controller %q\nWaiting for resources to be reclaimed", controllerName)
124
145
 
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))
130
 
                }
 
146
        uuid := controllerEnviron.Config().UUID()
 
147
        if err := c.WaitForModels(ctx, api, uuid); err != nil {
 
148
                c.DirectDestroyRemaining(ctx, api)
131
149
        }
132
 
 
133
 
        ctx.Infof("All hosted models reclaimed, cleaning up controller machines")
134
 
 
135
150
        return environs.Destroy(controllerName, controllerEnviron, store)
136
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() }