Merge lp:~axwalk/juju-core/lp1246983-cli-api-removeagents into lp:~go-bot/juju-core/trunk
- lp1246983-cli-api-removeagents
- Merge into trunk
Status: | Merged | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Merged at revision: | 2131 | ||||||||||||
Proposed branch: | lp:~axwalk/juju-core/lp1246983-cli-api-removeagents | ||||||||||||
Merge into: | lp:~go-bot/juju-core/trunk | ||||||||||||
Diff against target: |
936 lines (+564/-49) 19 files modified
cmd/juju/destroyenvironment.go (+12/-4) cmd/jujud/machine.go (+3/-0) environs/manual/provisioner.go (+1/-1) provider/dummy/environs.go (+3/-1) provider/null/environ.go (+1/-1) state/api/client.go (+6/-0) state/api/machiner/environ.go (+41/-0) state/api/machiner/machine.go (+2/-20) state/api/machiner/machiner.go (+39/-3) state/api/params/internal.go (+6/-0) state/apiserver/client/destroyjuju.go (+126/-0) state/apiserver/client/destroyjuju_test.go (+118/-0) state/apiserver/machine/machiner.go (+31/-3) state/environ.go (+64/-12) state/interface.go (+1/-0) state/state.go (+23/-2) state/state_test.go (+36/-0) state/watcher.go (+5/-0) worker/machiner/machiner.go (+46/-2) |
||||||||||||
To merge this branch: | bzr merge lp:~axwalk/juju-core/lp1246983-cli-api-removeagents | ||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+194801@code.launchpad.net |
Commit message
Description of the change
Migrate juju destroy-environment to use API
This change introduces a new client API
method, DestroyJuju. This method does the
following, server-side, in order:
- marks the environment as Dying, preventing
the addition of machines and services to
state
- ensures there are no non-manager manually-
provisioned machines in state
- marks all services as Dying, thus prevening
units from being added
- stops all non-manager machine instances
- marks the environment as Dead
Machine agents watch, via the machiner API, for
changes to the environment's lifecycle, and
terminate themselves when the environment
becomes Dead. This is how we terminate manual
bootstrap nodes.
A followup will update agent termination logic
to uninstall mongo on state servers, and also
remove the data-dir/log-dir.
Fixes #1218688
Fixes #1246343
Andrew Wilkins (axwalk) wrote : | # |
John A Meinel (jameinel) wrote : | # |
some thougts. we did most of the discussion on IRC, I think.
https:/
File cmd/juju/
https:/
cmd/juju/
conn.State.
I'm intrigued that you pass in the timeout.I was wondering if we need it
configable (I know my manual machines take at least 2min to shutdown
cleanly). At least we can expose it on the CLI if we find it is
necessary.
https:/
File state/api/
https:/
state/api/
Environment(
I don't quite understand why you take a machineTag for an environment.
Is this just a copy and paste that didn't finish renaming things?
But the MachineEnvironm
If it *does* need a Machine, then I'm not sure why this is on State
rather than Machine. (though to be fair, this is Machine.State and not
state.State which is a personal source of much confusion).
https:/
File state/apiserver
https:/
state/apiserver
*state.State, attemptStrategy utils.AttemptSt
It is surprising to me that destroyUnits would be triggered on the
Client side, rather than on the API server in response to the
env.Destroy request.
https:/
File state/apiserver
https:/
state/apiserver
environment.
I don't think we want to design multi-tenancy into the API today.
I'm willing to be corrected by someone else, but we should keep in mind
the future, but only build what we need right now. (Because we're likely
to get it wrong until we're actually building the next thing anyway.)
Andrew Wilkins (axwalk) wrote : | # |
So, based on conversations on IRC, I'm going to rework this. From
fwereade:
"(1) abort if manual machines exist (2) destroy services, set destroying
flag (3) directly StopInstances for non-managers in the API server (4)
environ.Destroy in the CLI"
Sounds fair, I'll do that.
A follow-up will deal with manual machines, and will do the two-phase
start/wait business with a watcher and timeout in between.
https:/
File cmd/juju/
https:/
cmd/juju/
conn.State.
On 2013/11/12 08:39:26, jameinel wrote:
> I'm intrigued that you pass in the timeout.I was wondering if we need
it
> configable (I know my manual machines take at least 2min to shutdown
cleanly).
> At least we can expose it on the CLI if we find it is necessary.
rogpeppe has suggested we break the API into two (in the usual async
pattern): one that starts, one that finalises. The client can use the
all-watcher to observe machine/unit destruction, and provide feedback
along the way. Despite the added complexity, I'm in favour.
https:/
File state/api/
https:/
state/api/
Environment(
On 2013/11/12 08:39:26, jameinel wrote:
> I don't quite understand why you take a machineTag for an environment.
> Is this just a copy and paste that didn't finish renaming things?
> But the MachineEnvironm
> If it *does* need a Machine, then I'm not sure why this is on State
rather than
> Machine. (though to be fair, this is Machine.State and not state.State
which is
> a personal source of much confusion).
It needs a Machine for when we have multi-tenancy. The machine-agent
doesn't know its environment name at the point of call, hence this. I'm
open to suggestions, I know it's a bit weird.
https:/
File state/apiserver
https:/
state/apiserver
*state.State, attemptStrategy utils.AttemptSt
On 2013/11/12 08:39:26, jameinel wrote:
> It is surprising to me that destroyUnits would be triggered on the
Client side,
> rather than on the API server in response to the env.Destroy request.
This is happening on the API server side. This is just the "Client API",
as used by the CLI and GUI. Am I misunderstanding your comment?
https:/
File state/apiserver
https:/
state/apiserver
Andrew Wilkins (axwalk) wrote : | # |
Please take a look.
William Reade (fwereade) wrote : | # |
Some good bits, some I'ma bit alarmed about. Can we do a single CL for
the state changes, please, and leave the API bits and additional workers
and so on out for the moment?
There are a couple of open questions in the state code, it's true, but
resolving and reproposing those will be the quickest way to get some of
this landed.
https:/
File state/api/
https:/
state/api/
Environment(
On 2013/11/12 10:03:08, axw wrote:
> On 2013/11/12 08:39:26, jameinel wrote:
> > I don't quite understand why you take a machineTag for an
environment.
> > Is this just a copy and paste that didn't finish renaming things?
> >
> > But the MachineEnvironm
> >
> > If it *does* need a Machine, then I'm not sure why this is on State
rather
> than
> > Machine. (though to be fair, this is Machine.State and not
state.State which
> is
> > a personal source of much confusion).
> It needs a Machine for when we have multi-tenancy. The machine-agent
doesn't
> know its environment name at the point of call, hence this. I'm open
to
> suggestions, I know it's a bit weird.
I don't think it is necessary, actually, because we will surely know
what environment we're connected to -- and I don't think it'd work
*anyway*, because the machine-tag doesn't identify an environment
anyway.
https:/
File state/api/
https:/
state/api/
(watcher.
Environment?
https:/
File state/api/
https:/
state/api/
Whoa. I hope this doesn't include a name -- the moment we start messing
around with environment tags we need to un-screw-up this situation
(environment names are not globally unique -- any time we use it we must
be aware it's only an alias).
Regardless I don't think there's a justification for talking about more
than one environment at this level anyway.
https:/
File state/apiserver
https:/
state/apiserver
env.StopInstanc
There's a wrinkle here in that some machines may be missing for random
stupid provider reasons, and those won't end up getting stopped. I'm
becoming convinced that it's really stupid to have StopInstances take
Instances instead of ids -- it means that we need to go through all the
hassle of finding out everything about one (ie hitting the provider...
Andrew Wilkins (axwalk) wrote : | # |
I'll create a new CL for the state bits, as requested.
https:/
File state/api/
https:/
state/api/
(watcher.
On 2013/11/18 14:43:37, william.reade wrote:
> Environment?
Are you talking about the comment? Fixed.
https:/
File state/api/
https:/
state/api/
On 2013/11/18 14:43:37, william.reade wrote:
> Whoa. I hope this doesn't include a name -- the moment we start
messing around
> with environment tags we need to un-screw-up this situation
(environment names
> are not globally unique -- any time we use it we must be aware it's
only an
> alias).
> Regardless I don't think there's a justification for talking about
more than one
> environment at this level anyway.
I'm not sure what you mean by "talking about more than one environment".
That tag is just a handle so we can get a watcher, and is always
expected to be the same.
Do you have a preferred API here?
https:/
File state/apiserver
https:/
state/apiserver
env.StopInstanc
On 2013/11/18 14:43:37, william.reade wrote:
> There's a wrinkle here in that some machines may be missing for random
stupid
> provider reasons, and those won't end up getting stopped. I'm becoming
convinced
> that it's really stupid to have StopInstances take Instances instead
of ids --
> it means that we need to go through all the hassle of finding out
everything
> about one (ie hitting the provider api, potentially triggering rate
limiting,
> etc) before we can just simply stop them.
Agreed.
https:/
state/apiserver
tests, we cannot import manual.
On 2013/11/18 14:43:37, william.reade wrote:
> In tests? I'm surprised by that.
The environs/manual tests import something that imports state/apiserver.
Thus, importing environs/manual here prevents running the
environs/manual tests.
https:/
File state/apiserver
https:/
state/apiserver
params.
On 2013/11/18 14:43:37, william.reade wrote:
> Why do we persist in naming things after the entities that call them?
This is
> surely an EnvironmentResult.
The idea here was "give me the environment which this machine belongs
to", not an arbitrary...
Preview Diff
1 | === modified file 'cmd/juju/destroyenvironment.go' |
2 | --- cmd/juju/destroyenvironment.go 2013-10-17 17:30:56 +0000 |
3 | +++ cmd/juju/destroyenvironment.go 2013-11-13 04:45:49 +0000 |
4 | @@ -15,6 +15,8 @@ |
5 | "launchpad.net/juju-core/cmd" |
6 | "launchpad.net/juju-core/environs" |
7 | "launchpad.net/juju-core/environs/configstore" |
8 | + "launchpad.net/juju-core/juju" |
9 | + "launchpad.net/juju-core/state/api" |
10 | ) |
11 | |
12 | var NoEnvironmentError = errors.New("no environment specified") |
13 | @@ -63,10 +65,16 @@ |
14 | } |
15 | } |
16 | |
17 | - // TODO(axw) 2013-08-30 bug 1218688 |
18 | - // destroy manually provisioned machines, or otherwise |
19 | - // block destroy-environment until all manually provisioned |
20 | - // machines have been manually "destroyed". |
21 | + // First, cleanly remove Juju from the environment. |
22 | + conn, err := juju.NewAPIConn(environ, api.DefaultDialOpts()) |
23 | + if err != nil { |
24 | + return err |
25 | + } |
26 | + defer conn.Close() |
27 | + if err = conn.State.Client().DestroyJuju(); err != nil { |
28 | + return fmt.Errorf("could not remove agents: %v", err) |
29 | + } |
30 | + // Finally, allow the provider to release environment resources. |
31 | return environs.Destroy(environ, store) |
32 | } |
33 | |
34 | |
35 | === modified file 'cmd/jujud/machine.go' |
36 | --- cmd/jujud/machine.go 2013-10-02 23:18:21 +0000 |
37 | +++ cmd/jujud/machine.go 2013-11-13 04:45:49 +0000 |
38 | @@ -167,6 +167,9 @@ |
39 | runner.StartWorker("machiner", func() (worker.Worker, error) { |
40 | return machiner.NewMachiner(st.Machiner(), agentConfig), nil |
41 | }) |
42 | + runner.StartWorker("machine-environer", func() (worker.Worker, error) { |
43 | + return machiner.NewMachineEnvironer(st.Machiner(), agentConfig), nil |
44 | + }) |
45 | runner.StartWorker("upgrader", func() (worker.Worker, error) { |
46 | return upgrader.NewUpgrader(st.Upgrader(), agentConfig), nil |
47 | }) |
48 | |
49 | === modified file 'environs/manual/provisioner.go' |
50 | --- environs/manual/provisioner.go 2013-11-08 02:52:25 +0000 |
51 | +++ environs/manual/provisioner.go 2013-11-13 04:45:49 +0000 |
52 | @@ -183,7 +183,7 @@ |
53 | CACert: configParameters.CACert, |
54 | } |
55 | apiInfo := &api.Info{ |
56 | - Addrs: configParameters.StateAddrs, |
57 | + Addrs: configParameters.APIAddrs, |
58 | Password: configParameters.Password, |
59 | Tag: configParameters.Tag, |
60 | CACert: configParameters.CACert, |
61 | |
62 | === modified file 'provider/dummy/environs.go' |
63 | --- provider/dummy/environs.go 2013-11-07 09:09:55 +0000 |
64 | +++ provider/dummy/environs.go 2013-11-13 04:45:49 +0000 |
65 | @@ -779,8 +779,10 @@ |
66 | if inst == nil { |
67 | err = environs.ErrPartialInstances |
68 | notFound++ |
69 | + insts = append(insts, nil) |
70 | + } else { |
71 | + insts = append(insts, inst) |
72 | } |
73 | - insts = append(insts, inst) |
74 | } |
75 | if notFound == len(ids) { |
76 | return nil, environs.ErrNoInstances |
77 | |
78 | === modified file 'provider/null/environ.go' |
79 | --- provider/null/environ.go 2013-10-24 00:20:59 +0000 |
80 | +++ provider/null/environ.go 2013-11-13 04:45:49 +0000 |
81 | @@ -181,7 +181,7 @@ |
82 | } |
83 | |
84 | func (e *nullEnviron) Destroy() error { |
85 | - return errors.New("null provider destruction is not implemented yet") |
86 | + return nil |
87 | } |
88 | |
89 | func (e *nullEnviron) OpenPorts(ports []instance.Port) error { |
90 | |
91 | === modified file 'state/api/client.go' |
92 | --- state/api/client.go 2013-11-11 22:19:10 +0000 |
93 | +++ state/api/client.go 2013-11-13 04:45:49 +0000 |
94 | @@ -341,3 +341,9 @@ |
95 | args := params.EnvironmentSet{Config: config} |
96 | return c.st.Call("Client", "", "EnvironmentSet", args, nil) |
97 | } |
98 | + |
99 | +// DestroyJuju cleanly removes all Juju agents from the environment, |
100 | +// except for manually-provisioned non-manager machines. |
101 | +func (c *Client) DestroyJuju() error { |
102 | + return c.st.Call("Client", "", "DestroyJuju", nil, nil) |
103 | +} |
104 | |
105 | === added file 'state/api/machiner/environ.go' |
106 | --- state/api/machiner/environ.go 1970-01-01 00:00:00 +0000 |
107 | +++ state/api/machiner/environ.go 2013-11-13 04:45:49 +0000 |
108 | @@ -0,0 +1,41 @@ |
109 | +// Copyright 2012, 2013 Canonical Ltd. |
110 | +// Licensed under the AGPLv3, see LICENCE file for details. |
111 | + |
112 | +package machiner |
113 | + |
114 | +import ( |
115 | + "launchpad.net/juju-core/state/api/params" |
116 | + "launchpad.net/juju-core/state/api/watcher" |
117 | +) |
118 | + |
119 | +// Environment represents a juju environment as seen by a machiner worker. |
120 | +type Environment struct { |
121 | + tag string |
122 | + life params.Life |
123 | + st *State |
124 | +} |
125 | + |
126 | +// Tag returns the environment's tag. |
127 | +func (e *Environment) Tag() string { |
128 | + return e.tag |
129 | +} |
130 | + |
131 | +// Life returns the environment's lifecycle value. |
132 | +func (e *Environment) Life() params.Life { |
133 | + return e.life |
134 | +} |
135 | + |
136 | +// Refresh updates the cached local copy of the environment's data. |
137 | +func (e *Environment) Refresh() error { |
138 | + life, err := e.st.entityLife(e.tag) |
139 | + if err != nil { |
140 | + return err |
141 | + } |
142 | + e.life = life |
143 | + return nil |
144 | +} |
145 | + |
146 | +// Watch returns a watcher for observing changes to the machine. |
147 | +func (e *Environment) Watch() (watcher.NotifyWatcher, error) { |
148 | + return e.st.watch(e.tag) |
149 | +} |
150 | |
151 | === modified file 'state/api/machiner/machine.go' |
152 | --- state/api/machiner/machine.go 2013-09-17 18:18:44 +0000 |
153 | +++ state/api/machiner/machine.go 2013-11-13 04:45:49 +0000 |
154 | @@ -4,8 +4,6 @@ |
155 | package machiner |
156 | |
157 | import ( |
158 | - "fmt" |
159 | - |
160 | "launchpad.net/juju-core/state/api/params" |
161 | "launchpad.net/juju-core/state/api/watcher" |
162 | ) |
163 | @@ -29,7 +27,7 @@ |
164 | |
165 | // Refresh updates the cached local copy of the machine's data. |
166 | func (m *Machine) Refresh() error { |
167 | - life, err := m.st.machineLife(m.tag) |
168 | + life, err := m.st.entityLife(m.tag) |
169 | if err != nil { |
170 | return err |
171 | } |
172 | @@ -68,21 +66,5 @@ |
173 | |
174 | // Watch returns a watcher for observing changes to the machine. |
175 | func (m *Machine) Watch() (watcher.NotifyWatcher, error) { |
176 | - var results params.NotifyWatchResults |
177 | - args := params.Entities{ |
178 | - Entities: []params.Entity{{Tag: m.tag}}, |
179 | - } |
180 | - err := m.st.caller.Call("Machiner", "", "Watch", args, &results) |
181 | - if err != nil { |
182 | - return nil, err |
183 | - } |
184 | - if len(results.Results) != 1 { |
185 | - return nil, fmt.Errorf("expected one result, got %d", len(results.Results)) |
186 | - } |
187 | - result := results.Results[0] |
188 | - if result.Error != nil { |
189 | - return nil, result.Error |
190 | - } |
191 | - w := watcher.NewNotifyWatcher(m.st.caller, result) |
192 | - return w, nil |
193 | + return m.st.watch(m.tag) |
194 | } |
195 | |
196 | === modified file 'state/api/machiner/machiner.go' |
197 | --- state/api/machiner/machiner.go 2013-07-22 12:19:48 +0000 |
198 | +++ state/api/machiner/machiner.go 2013-11-13 04:45:49 +0000 |
199 | @@ -8,6 +8,7 @@ |
200 | |
201 | "launchpad.net/juju-core/state/api/common" |
202 | "launchpad.net/juju-core/state/api/params" |
203 | + "launchpad.net/juju-core/state/api/watcher" |
204 | ) |
205 | |
206 | // State provides access to the Machiner API facade. |
207 | @@ -20,8 +21,8 @@ |
208 | return &State{caller} |
209 | } |
210 | |
211 | -// machineLife requests the lifecycle of the given machine from the server. |
212 | -func (st *State) machineLife(tag string) (params.Life, error) { |
213 | +// entityLife requests the lifecycle of the given machine from the server. |
214 | +func (st *State) entityLife(tag string) (params.Life, error) { |
215 | var result params.LifeResults |
216 | args := params.Entities{ |
217 | Entities: []params.Entity{{Tag: tag}}, |
218 | @@ -41,7 +42,7 @@ |
219 | |
220 | // Machine provides access to methods of a state.Machine through the facade. |
221 | func (st *State) Machine(tag string) (*Machine, error) { |
222 | - life, err := st.machineLife(tag) |
223 | + life, err := st.entityLife(tag) |
224 | if err != nil { |
225 | return nil, err |
226 | } |
227 | @@ -51,3 +52,38 @@ |
228 | st: st, |
229 | }, nil |
230 | } |
231 | + |
232 | +// Environment returns the Environment corresponding |
233 | +// to the machine with the given tag. |
234 | +func (st *State) Environment() (*Environment, error) { |
235 | + var result params.MachineEnvironmentResult |
236 | + err := st.caller.Call("Machiner", "", "Environment", nil, &result) |
237 | + if err != nil { |
238 | + return nil, err |
239 | + } |
240 | + return &Environment{ |
241 | + tag: result.EnvironmentTag, |
242 | + life: result.Life, |
243 | + st: st, |
244 | + }, nil |
245 | +} |
246 | + |
247 | +// watch returns a watcher for observing changes to the given entity. |
248 | +func (st *State) watch(tag string) (watcher.NotifyWatcher, error) { |
249 | + var results params.NotifyWatchResults |
250 | + args := params.Entities{ |
251 | + Entities: []params.Entity{{Tag: tag}}, |
252 | + } |
253 | + err := st.caller.Call("Machiner", "", "Watch", args, &results) |
254 | + if err != nil { |
255 | + return nil, err |
256 | + } |
257 | + if len(results.Results) != 1 { |
258 | + return nil, fmt.Errorf("expected one result, got %d", len(results.Results)) |
259 | + } |
260 | + result := results.Results[0] |
261 | + if result.Error != nil { |
262 | + return nil, result.Error |
263 | + } |
264 | + return watcher.NewNotifyWatcher(st.caller, result), nil |
265 | +} |
266 | |
267 | === modified file 'state/api/params/internal.go' |
268 | --- state/api/params/internal.go 2013-10-03 13:13:25 +0000 |
269 | +++ state/api/params/internal.go 2013-11-13 04:45:49 +0000 |
270 | @@ -490,3 +490,9 @@ |
271 | type RelationUnitsWatchResults struct { |
272 | Results []RelationUnitsWatchResult |
273 | } |
274 | + |
275 | +// MachineEnvironment holds the results of the Machine.Environment API call. |
276 | +type MachineEnvironmentResult struct { |
277 | + EnvironmentTag string |
278 | + Life Life |
279 | +} |
280 | |
281 | === added file 'state/apiserver/client/destroyjuju.go' |
282 | --- state/apiserver/client/destroyjuju.go 1970-01-01 00:00:00 +0000 |
283 | +++ state/apiserver/client/destroyjuju.go 2013-11-13 04:45:49 +0000 |
284 | @@ -0,0 +1,126 @@ |
285 | +// Copyright 2013 Canonical Ltd. |
286 | +// Licensed under the AGPLv3, see LICENCE file for details. |
287 | + |
288 | +package client |
289 | + |
290 | +import ( |
291 | + "fmt" |
292 | + "strings" |
293 | + |
294 | + "launchpad.net/juju-core/environs" |
295 | + "launchpad.net/juju-core/instance" |
296 | + "launchpad.net/juju-core/state" |
297 | +) |
298 | + |
299 | +// DestroyJuju cleanly removes all Juju agents from the environment. |
300 | +func (c *Client) DestroyJuju() error { |
301 | + // First, set the environment to Dying, to lock out new machines, |
302 | + // services and units. |
303 | + env, err := c.api.state.Environment() |
304 | + if err != nil { |
305 | + return err |
306 | + } |
307 | + if err = env.Destroy(); err != nil { |
308 | + return err |
309 | + } |
310 | + // Destroy all services, preventing addition of units. |
311 | + services, err := c.api.state.AllServices() |
312 | + for _, s := range services { |
313 | + if err := s.Destroy(); err != nil { |
314 | + return err |
315 | + } |
316 | + } |
317 | + // Make sure there are no manually provisioned non-manager machines. |
318 | + // Manually provisioned manager machines can self-destruct when |
319 | + // environment goes to Dead. |
320 | + machines, err := c.api.state.AllMachines() |
321 | + if err != nil { |
322 | + return err |
323 | + } |
324 | + if err := checkManualMachines(machines); err != nil { |
325 | + return err |
326 | + } |
327 | + // We must destroy instances server-side to support hosted Juju, |
328 | + // as there's no CLI to fall back on. In that case, we only ever |
329 | + // destroy non-state machines; we leave destroying state servers |
330 | + // in non-hosted environments to the CLI, as otherwise the API |
331 | + // server may get cut off. |
332 | + if err := destroyInstances(c.api.state, machines); err != nil { |
333 | + return err |
334 | + } |
335 | + // Set the environment to Dead, which will cause all agents to |
336 | + // terminate and uninstall themselves. This must be the last thing |
337 | + // we do. |
338 | + return env.EnsureDead() |
339 | +} |
340 | + |
341 | +// destroyInstances directly destroys all non-manager machine instances. |
342 | +func destroyInstances(st *state.State, machines []*state.Machine) error { |
343 | + var ids []instance.Id |
344 | + for _, m := range machines { |
345 | + if m.IsStateServer() { |
346 | + continue |
347 | + } |
348 | + id, err := m.InstanceId() |
349 | + if err == nil { |
350 | + ids = append(ids, id) |
351 | + } |
352 | + } |
353 | + if len(ids) == 0 { |
354 | + return nil |
355 | + } |
356 | + envcfg, err := st.EnvironConfig() |
357 | + if err != nil { |
358 | + return err |
359 | + } |
360 | + env, err := environs.New(envcfg) |
361 | + if err != nil { |
362 | + return err |
363 | + } |
364 | + instances, err := env.Instances(ids) |
365 | + switch err { |
366 | + case nil: |
367 | + default: |
368 | + return err |
369 | + case environs.ErrNoInstances: |
370 | + return nil |
371 | + case environs.ErrPartialInstances: |
372 | + var nonNilInstances []instance.Instance |
373 | + for _, inst := range instances { |
374 | + if inst == nil { |
375 | + continue |
376 | + } |
377 | + nonNilInstances = append(nonNilInstances, inst) |
378 | + } |
379 | + instances = nonNilInstances |
380 | + } |
381 | + return env.StopInstances(instances) |
382 | +} |
383 | + |
384 | +// checkManualMachines checks if any of the machines in the slice were |
385 | +// manually provisioned, and are non-manager machines. These machines |
386 | +// must (currently) by manually destroyed via destroy-machine before |
387 | +// destroy-environment can successfully complete. |
388 | +func checkManualMachines(machines []*state.Machine) error { |
389 | + var ids []string |
390 | + for _, m := range machines { |
391 | + if isManuallyProvisioned(m) && !m.IsStateServer() { |
392 | + ids = append(ids, m.Id()) |
393 | + } |
394 | + } |
395 | + if len(ids) > 0 { |
396 | + return fmt.Errorf("manually provisioned machines must first be destroyed with `juju destroy-machine %s`", strings.Join(ids, " ")) |
397 | + } |
398 | + return nil |
399 | +} |
400 | + |
401 | +// isManuallyProvisioned returns true iff the the machine was |
402 | +// manually provisioned. |
403 | +func isManuallyProvisioned(m *state.Machine) bool { |
404 | + iid, err := m.InstanceId() |
405 | + if err != nil { |
406 | + return false |
407 | + } |
408 | + // Due to an import loop in tests, we cannot import manual. |
409 | + return strings.HasPrefix(string(iid), "manual:") |
410 | +} |
411 | |
412 | === added file 'state/apiserver/client/destroyjuju_test.go' |
413 | --- state/apiserver/client/destroyjuju_test.go 1970-01-01 00:00:00 +0000 |
414 | +++ state/apiserver/client/destroyjuju_test.go 2013-11-13 04:45:49 +0000 |
415 | @@ -0,0 +1,118 @@ |
416 | +// Copyright 2012, 2013 Canonical Ltd. |
417 | +// Licensed under the AGPLv3, see LICENCE file for details. |
418 | + |
419 | +package client_test |
420 | + |
421 | +import ( |
422 | + "fmt" |
423 | + |
424 | + gc "launchpad.net/gocheck" |
425 | + |
426 | + "launchpad.net/juju-core/environs" |
427 | + coreerrors "launchpad.net/juju-core/errors" |
428 | + "launchpad.net/juju-core/instance" |
429 | + "launchpad.net/juju-core/juju/testing" |
430 | + "launchpad.net/juju-core/state" |
431 | + jc "launchpad.net/juju-core/testing/checkers" |
432 | +) |
433 | + |
434 | +type destroyJujuSuite struct { |
435 | + baseSuite |
436 | +} |
437 | + |
438 | +var _ = gc.Suite(&destroyJujuSuite{}) |
439 | + |
440 | +// setUpManual adds "manually provisioned" machines to state: |
441 | +// one manager machine, and one non-manager. |
442 | +func (s *destroyJujuSuite) setUpManual(c *gc.C) (m0, m1 *state.Machine) { |
443 | + m0, err := s.State.AddMachine("precise", state.JobManageEnviron, state.JobManageState) |
444 | + c.Assert(err, gc.IsNil) |
445 | + err = m0.SetProvisioned(instance.Id("manual:0"), "fake_nonce", nil) |
446 | + c.Assert(err, gc.IsNil) |
447 | + m1, err = s.State.AddMachine("precise", state.JobHostUnits) |
448 | + c.Assert(err, gc.IsNil) |
449 | + err = m1.SetProvisioned(instance.Id("manual:1"), "fake_nonce", nil) |
450 | + c.Assert(err, gc.IsNil) |
451 | + return m0, m1 |
452 | +} |
453 | + |
454 | +// setUpInstances adds machines to state backed by instances: |
455 | +// one manager machine, and one non-manager. |
456 | +func (s *destroyJujuSuite) setUpInstances(c *gc.C) (m0, m1 *state.Machine) { |
457 | + m0, err := s.State.AddMachine("precise", state.JobManageEnviron, state.JobManageState) |
458 | + c.Assert(err, gc.IsNil) |
459 | + inst, _ := testing.AssertStartInstance(c, s.APIConn.Environ, m0.Id()) |
460 | + err = m0.SetProvisioned(inst.Id(), "fake_nonce", nil) |
461 | + c.Assert(err, gc.IsNil) |
462 | + m1, err = s.State.AddMachine("precise", state.JobHostUnits) |
463 | + c.Assert(err, gc.IsNil) |
464 | + inst, _ = testing.AssertStartInstance(c, s.APIConn.Environ, m1.Id()) |
465 | + err = m1.SetProvisioned(inst.Id(), "fake_nonce", nil) |
466 | + c.Assert(err, gc.IsNil) |
467 | + return m0, m1 |
468 | +} |
469 | + |
470 | +func (s *destroyJujuSuite) TestDestroyJujuManual(c *gc.C) { |
471 | + s.setUpScenario(c) |
472 | + _, nonManager := s.setUpManual(c) |
473 | + |
474 | + // If there are any non-manager manual machines in state, DestroyJuju will |
475 | + // error. It *will* still set the Dying flag on the environment, though. |
476 | + err := s.APIState.Client().DestroyJuju() |
477 | + c.Assert(err, gc.ErrorMatches, fmt.Sprintf("manually provisioned machines must first be destroyed with `juju destroy-machine %s`", nonManager.Id())) |
478 | + env, err := s.State.Environment() |
479 | + c.Assert(err, gc.IsNil) |
480 | + c.Assert(env.Life(), gc.Equals, state.Dying) |
481 | + |
482 | + // If we remove the non-manager machine, it should pass. Manager machines |
483 | + // will remain, but should tear themselves down (in a real agent) when |
484 | + // they see that the environment is Dead. |
485 | + err = nonManager.EnsureDead() |
486 | + c.Assert(err, gc.IsNil) |
487 | + err = nonManager.Remove() |
488 | + c.Assert(err, gc.IsNil) |
489 | + err = s.APIState.Client().DestroyJuju() |
490 | + c.Assert(err, gc.IsNil) |
491 | + err = env.Refresh() |
492 | + c.Assert(err, gc.IsNil) |
493 | + c.Assert(env.Life(), gc.Equals, state.Dead) |
494 | +} |
495 | + |
496 | +func (s *destroyJujuSuite) TestDestroyJuju(c *gc.C) { |
497 | + s.setUpScenario(c) |
498 | + manager, nonManager := s.setUpInstances(c) |
499 | + managerId, _ := manager.InstanceId() |
500 | + nonManagerId, _ := nonManager.InstanceId() |
501 | + |
502 | + instances, err := s.APIConn.Environ.Instances([]instance.Id{managerId, nonManagerId}) |
503 | + c.Assert(err, gc.IsNil) |
504 | + for _, inst := range instances { |
505 | + c.Assert(inst, gc.NotNil) |
506 | + } |
507 | + |
508 | + services, err := s.State.AllServices() |
509 | + c.Assert(err, gc.IsNil) |
510 | + |
511 | + err = s.APIState.Client().DestroyJuju() |
512 | + c.Assert(err, gc.IsNil) |
513 | + |
514 | + // After DestroyJuju returns, we should have: |
515 | + // - all non-manager instances stopped |
516 | + instances, err = s.APIConn.Environ.Instances([]instance.Id{managerId, nonManagerId}) |
517 | + c.Assert(err, gc.Equals, environs.ErrPartialInstances) |
518 | + c.Assert(instances[0], gc.NotNil) |
519 | + c.Assert(instances[1], gc.IsNil) |
520 | + // - all services in state are Dying or Dead (or removed altogether) |
521 | + for _, s := range services { |
522 | + err = s.Refresh() |
523 | + if err != nil { |
524 | + c.Assert(err, jc.Satisfies, coreerrors.IsNotFoundError) |
525 | + } else { |
526 | + c.Assert(s.Life(), gc.Not(gc.Equals), state.Alive) |
527 | + } |
528 | + } |
529 | + // - environment is Dead |
530 | + env, err := s.State.Environment() |
531 | + c.Assert(err, gc.IsNil) |
532 | + c.Assert(env.Life(), gc.Equals, state.Dead) |
533 | +} |
534 | |
535 | === modified file 'state/apiserver/machine/machiner.go' |
536 | --- state/apiserver/machine/machiner.go 2013-08-08 17:38:41 +0000 |
537 | +++ state/apiserver/machine/machiner.go 2013-11-13 04:45:49 +0000 |
538 | @@ -6,7 +6,9 @@ |
539 | package machine |
540 | |
541 | import ( |
542 | + "launchpad.net/juju-core/names" |
543 | "launchpad.net/juju-core/state" |
544 | + "launchpad.net/juju-core/state/api/params" |
545 | "launchpad.net/juju-core/state/apiserver/common" |
546 | ) |
547 | |
548 | @@ -26,15 +28,41 @@ |
549 | if !authorizer.AuthMachineAgent() { |
550 | return nil, common.ErrPerm |
551 | } |
552 | - getCanRead := func() (common.AuthFunc, error) { |
553 | + // getAuthEnviron returns an AuthFunc that permits anyone |
554 | + // access to an environment tag. |
555 | + getAuthEnviron := func() (common.AuthFunc, error) { |
556 | + auth := func(tag string) bool { |
557 | + kind, err := names.TagKind(tag) |
558 | + if err != nil { |
559 | + panic(err) |
560 | + } |
561 | + return kind == names.EnvironTagKind |
562 | + } |
563 | + return auth, nil |
564 | + } |
565 | + getAuthOwner := func() (common.AuthFunc, error) { |
566 | return authorizer.AuthOwner, nil |
567 | } |
568 | + getCanRead := common.AuthEither(getAuthEnviron, getAuthOwner) |
569 | return &MachinerAPI{ |
570 | LifeGetter: common.NewLifeGetter(st, getCanRead), |
571 | - StatusSetter: common.NewStatusSetter(st, getCanRead), |
572 | - DeadEnsurer: common.NewDeadEnsurer(st, getCanRead), |
573 | + StatusSetter: common.NewStatusSetter(st, getAuthOwner), |
574 | + DeadEnsurer: common.NewDeadEnsurer(st, getAuthOwner), |
575 | AgentEntityWatcher: common.NewAgentEntityWatcher(st, resources, getCanRead), |
576 | st: st, |
577 | auth: authorizer, |
578 | }, nil |
579 | } |
580 | + |
581 | +// Environment returns the tag and life of the specified |
582 | +// machine's enclosing environment. |
583 | +func (m *MachinerAPI) Environment() (params.MachineEnvironmentResult, error) { |
584 | + var result params.MachineEnvironmentResult |
585 | + env, err := m.st.Environment() |
586 | + if err != nil { |
587 | + return result, err |
588 | + } |
589 | + result.EnvironmentTag = env.Tag() |
590 | + result.Life = params.Life(env.Life().String()) |
591 | + return result, nil |
592 | +} |
593 | |
594 | === modified file 'state/environ.go' |
595 | --- state/environ.go 2013-07-29 15:15:41 +0000 |
596 | +++ state/environ.go 2013-11-13 04:45:49 +0000 |
597 | @@ -22,25 +22,21 @@ |
598 | annotator |
599 | } |
600 | |
601 | +var _ Living = (*Environment)(nil) |
602 | + |
603 | // environmentDoc represents the internal state of the environment in MongoDB. |
604 | type environmentDoc struct { |
605 | UUID string `bson:"_id"` |
606 | Name string |
607 | + Life Life `bson:-` |
608 | } |
609 | |
610 | // Environment returns the environment entity. |
611 | func (st *State) Environment() (*Environment, error) { |
612 | - doc := environmentDoc{} |
613 | - err := st.environments.Find(D{{"uuid", D{{"$ne", ""}}}}).One(&doc) |
614 | - if err == mgo.ErrNotFound { |
615 | - return nil, errors.NotFoundf("environment") |
616 | - } else if err != nil { |
617 | + env := &Environment{st: st} |
618 | + if err := env.Refresh(); err != nil { |
619 | return nil, err |
620 | } |
621 | - env := &Environment{ |
622 | - st: st, |
623 | - doc: doc, |
624 | - } |
625 | env.annotator = annotator{ |
626 | globalKey: env.globalKey(), |
627 | tag: env.Tag(), |
628 | @@ -52,24 +48,70 @@ |
629 | // Tag returns a name identifying the environment. |
630 | // The returned name will be different from other Tag values returned |
631 | // by any other entities from the same state. |
632 | -func (e Environment) Tag() string { |
633 | +func (e *Environment) Tag() string { |
634 | return names.EnvironTag(e.doc.Name) |
635 | } |
636 | |
637 | // UUID returns the universally unique identifier of the environment. |
638 | -func (e Environment) UUID() string { |
639 | +func (e *Environment) UUID() string { |
640 | return e.doc.UUID |
641 | } |
642 | |
643 | +// Life returns whether the environment is Alive, Dying or Dead. |
644 | +func (e *Environment) Life() Life { |
645 | + return e.doc.Life |
646 | +} |
647 | + |
648 | // globalKey returns the global database key for the environment. |
649 | func (e *Environment) globalKey() string { |
650 | return environGlobalKey |
651 | } |
652 | |
653 | +func (e *Environment) Refresh() error { |
654 | + err := e.st.environments.Find(D{{"uuid", D{{"$ne", ""}}}}).One(&e.doc) |
655 | + if err == mgo.ErrNotFound { |
656 | + return errors.NotFoundf("environment") |
657 | + } |
658 | + return err |
659 | +} |
660 | + |
661 | +func (e *Environment) Destroy() error { |
662 | + return e.advanceLifecycle(Dying) |
663 | +} |
664 | + |
665 | +func (e *Environment) EnsureDead() error { |
666 | + return e.advanceLifecycle(Dead) |
667 | +} |
668 | + |
669 | +// advanceLifecycle ensures that the environment's lifecycle is no earlier |
670 | +// than the supplied value. |
671 | +func (e *Environment) advanceLifecycle(life Life) error { |
672 | + var assert D |
673 | + if life != Dead { |
674 | + assert = notDeadDoc |
675 | + } |
676 | + op := txn.Op{ |
677 | + C: e.st.environments.Name, |
678 | + Id: e.doc.UUID, |
679 | + Update: D{{"$set", D{{"life", life}}}}, |
680 | + Assert: assert, |
681 | + } |
682 | + err := e.st.runTransaction([]txn.Op{op}) |
683 | + switch err { |
684 | + case nil: |
685 | + e.doc.Life = life |
686 | + case txn.ErrAborted: |
687 | + // If the transaction aborted, the environment is already Dead. |
688 | + e.doc.Life = Dead |
689 | + err = nil |
690 | + } |
691 | + return err |
692 | +} |
693 | + |
694 | // createEnvironmentOp returns the operation needed to create |
695 | // an environment document with the given name and UUID. |
696 | func createEnvironmentOp(st *State, name, uuid string) txn.Op { |
697 | - doc := &environmentDoc{uuid, name} |
698 | + doc := &environmentDoc{uuid, name, Alive} |
699 | return txn.Op{ |
700 | C: st.environments.Name, |
701 | Id: uuid, |
702 | @@ -77,3 +119,13 @@ |
703 | Insert: doc, |
704 | } |
705 | } |
706 | + |
707 | +// assertAliveOp returns a read-only txn.Op that asserts |
708 | +// the environment is alive. |
709 | +func (e *Environment) assertAliveOp() txn.Op { |
710 | + return txn.Op{ |
711 | + C: e.st.environments.Name, |
712 | + Id: e.UUID(), |
713 | + Assert: isAliveDoc, |
714 | + } |
715 | +} |
716 | |
717 | === modified file 'state/interface.go' |
718 | --- state/interface.go 2013-10-02 13:14:49 +0000 |
719 | +++ state/interface.go 2013-11-13 04:45:49 +0000 |
720 | @@ -128,6 +128,7 @@ |
721 | _ NotifyWatcherFactory = (*Machine)(nil) |
722 | _ NotifyWatcherFactory = (*Unit)(nil) |
723 | _ NotifyWatcherFactory = (*Service)(nil) |
724 | + _ NotifyWatcherFactory = (*Environment)(nil) |
725 | ) |
726 | |
727 | // AgentEntity represents an entity that can |
728 | |
729 | === modified file 'state/state.go' |
730 | --- state/state.go 2013-11-11 22:19:10 +0000 |
731 | +++ state/state.go 2013-11-13 04:45:49 +0000 |
732 | @@ -355,12 +355,17 @@ |
733 | sdoc := statusDoc{ |
734 | Status: params.StatusPending, |
735 | } |
736 | + env, err := st.Environment() |
737 | + if err != nil { |
738 | + return nil, nil, err |
739 | + } |
740 | // Machine constraints do not use a container constraint value. |
741 | // Both provisioning and deployment constraints use the same constraints.Value struct |
742 | // so here we clear the container value. Provisioning ignores the container value but |
743 | // clearing it avoids potential confusion. |
744 | cons.Container = nil |
745 | ops := []txn.Op{ |
746 | + env.assertAliveOp(), |
747 | { |
748 | C: st.machines.Name, |
749 | Id: mdoc.Id, |
750 | @@ -477,10 +482,16 @@ |
751 | } |
752 | ops = append(ops, machineOps...) |
753 | |
754 | - err = st.runTransaction(ops) |
755 | - if err != nil { |
756 | + if err = st.runTransaction(ops); err == txn.ErrAborted { |
757 | + if env, err := st.Environment(); err != nil { |
758 | + return nil, err |
759 | + } else if env.Life() != Alive { |
760 | + return nil, fmt.Errorf("environment is being destroyed") |
761 | + } |
762 | + } else if err != nil { |
763 | return nil, err |
764 | } |
765 | + |
766 | // Refresh to pick the txn-revno. |
767 | m = newMachine(st, mdoc) |
768 | if err = m.Refresh(); err != nil { |
769 | @@ -716,6 +727,10 @@ |
770 | } else if exists { |
771 | return nil, fmt.Errorf("service already exists") |
772 | } |
773 | + env, err := st.Environment() |
774 | + if err != nil { |
775 | + return nil, err |
776 | + } |
777 | // Create the service addition operations. |
778 | peers := ch.Meta().Peers |
779 | svcDoc := &serviceDoc{ |
780 | @@ -728,6 +743,7 @@ |
781 | } |
782 | svc := newService(st, svcDoc) |
783 | ops := []txn.Op{ |
784 | + env.assertAliveOp(), |
785 | createConstraintsOp(st, svc.globalKey(), constraints.Value{}), |
786 | createSettingsOp(st, svc.settingsKey(), nil), |
787 | { |
788 | @@ -752,6 +768,11 @@ |
789 | // because all the possible failed assertions imply that the service |
790 | // already exists. |
791 | if err := st.runTransaction(ops); err == txn.ErrAborted { |
792 | + if err := env.Refresh(); err != nil { |
793 | + return nil, err |
794 | + } else if env.Life() != Alive { |
795 | + return nil, fmt.Errorf("environment is being destroyed") |
796 | + } |
797 | return nil, fmt.Errorf("service already exists") |
798 | } else if err != nil { |
799 | return nil, err |
800 | |
801 | === modified file 'state/state_test.go' |
802 | --- state/state_test.go 2013-11-08 16:15:10 +0000 |
803 | +++ state/state_test.go 2013-11-13 04:45:49 +0000 |
804 | @@ -206,6 +206,23 @@ |
805 | check(m[1], "1", "blahblah", allJobs) |
806 | } |
807 | |
808 | +func (s *StateSuite) TestAddMachinesEnvironmentLife(c *gc.C) { |
809 | + _, err := s.State.AddMachine("quantal", state.JobHostUnits) |
810 | + c.Assert(err, gc.IsNil) |
811 | + // Check that machines cannot be added if the environment is Dying. |
812 | + env, err := s.State.Environment() |
813 | + c.Assert(err, gc.IsNil) |
814 | + err = env.Destroy() |
815 | + c.Assert(err, gc.IsNil) |
816 | + _, err = s.State.AddMachine("quantal", state.JobHostUnits) |
817 | + c.Assert(err, gc.ErrorMatches, ".*environment is being destroyed") |
818 | + // Same again for Dead. |
819 | + err = env.EnsureDead() |
820 | + c.Assert(err, gc.IsNil) |
821 | + _, err = s.State.AddMachine("quantal", state.JobHostUnits) |
822 | + c.Assert(err, gc.ErrorMatches, ".*environment is being destroyed") |
823 | +} |
824 | + |
825 | func (s *StateSuite) TestAddMachineExtraConstraints(c *gc.C) { |
826 | err := s.State.SetEnvironConstraints(constraints.MustParse("mem=4G")) |
827 | c.Assert(err, gc.IsNil) |
828 | @@ -529,6 +546,25 @@ |
829 | c.Assert(ch.URL(), gc.DeepEquals, charm.URL()) |
830 | } |
831 | |
832 | +func (s *StateSuite) TestAddServiceEnvironmentLife(c *gc.C) { |
833 | + charm := s.AddTestingCharm(c, "dummy") |
834 | + _, err := s.State.AddService("s0", charm) |
835 | + c.Assert(err, gc.IsNil) |
836 | + |
837 | + // Check that machines cannot be added if the environment is Dying. |
838 | + env, err := s.State.Environment() |
839 | + c.Assert(err, gc.IsNil) |
840 | + err = env.Destroy() |
841 | + c.Assert(err, gc.IsNil) |
842 | + _, err = s.State.AddService("s1", charm) |
843 | + c.Assert(err, gc.ErrorMatches, ".*environment is being destroyed") |
844 | + // Same again for Dead. |
845 | + err = env.EnsureDead() |
846 | + c.Assert(err, gc.IsNil) |
847 | + _, err = s.State.AddService("s2", charm) |
848 | + c.Assert(err, gc.ErrorMatches, ".*environment is being destroyed") |
849 | +} |
850 | + |
851 | func (s *StateSuite) TestServiceNotFound(c *gc.C) { |
852 | _, err := s.State.Service("bummer") |
853 | c.Assert(err, gc.ErrorMatches, `service "bummer" not found`) |
854 | |
855 | === modified file 'state/watcher.go' |
856 | --- state/watcher.go 2013-09-11 13:48:43 +0000 |
857 | +++ state/watcher.go 2013-11-13 04:45:49 +0000 |
858 | @@ -1059,6 +1059,11 @@ |
859 | return newEntityWatcher(u.st, u.st.units, u.doc.Name) |
860 | } |
861 | |
862 | +// Watch returns a watcher for observing changes to an environment. |
863 | +func (e *Environment) Watch() NotifyWatcher { |
864 | + return newEntityWatcher(e.st, e.st.environments, e.doc.UUID) |
865 | +} |
866 | + |
867 | // WatchForEnvironConfigChanges return a NotifyWatcher waiting for the Environ |
868 | // Config to change. This differs from WatchEnvironConfig in that the watcher |
869 | // is a NotifyWatcher that does not give content during Changes() |
870 | |
871 | === modified file 'worker/machiner/machiner.go' |
872 | --- worker/machiner/machiner.go 2013-09-17 18:18:44 +0000 |
873 | +++ worker/machiner/machiner.go 2013-11-13 04:45:49 +0000 |
874 | @@ -23,12 +23,26 @@ |
875 | machine *machiner.Machine |
876 | } |
877 | |
878 | +// machineEnvironer is responsible for watching a machine agent's |
879 | +// environment lifecycle, and terminating the agent if it becomes |
880 | +// Dead. |
881 | +type machineEnvironer struct { |
882 | + st *machiner.State |
883 | + machineTag string |
884 | + environ *machiner.Environment |
885 | +} |
886 | + |
887 | // NewMachiner returns a Worker that will wait for the identified machine |
888 | // to become Dying and make it Dead; or until the machine becomes Dead by |
889 | // other means. |
890 | func NewMachiner(st *machiner.State, agentConfig agent.Config) worker.Worker { |
891 | - mr := &Machiner{st: st, tag: agentConfig.Tag()} |
892 | - return worker.NewNotifyWorker(mr) |
893 | + machiner := &Machiner{st: st, tag: agentConfig.Tag()} |
894 | + return worker.NewNotifyWorker(machiner) |
895 | +} |
896 | + |
897 | +func NewMachineEnvironer(st *machiner.State, agentConfig agent.Config) worker.Worker { |
898 | + machineEnvironer := &machineEnvironer{st: st, machineTag: agentConfig.Tag()} |
899 | + return worker.NewNotifyWorker(machineEnvironer) |
900 | } |
901 | |
902 | func isNotFoundOrUnauthorized(err error) bool { |
903 | @@ -80,3 +94,33 @@ |
904 | // Nothing to do here. |
905 | return nil |
906 | } |
907 | + |
908 | +func (me *machineEnvironer) SetUp() (watcher.NotifyWatcher, error) { |
909 | + env, err := me.st.Environment() |
910 | + if isNotFoundOrUnauthorized(err) { |
911 | + return nil, worker.ErrTerminateAgent |
912 | + } else if err != nil { |
913 | + return nil, err |
914 | + } |
915 | + me.environ = env |
916 | + return env.Watch() |
917 | +} |
918 | + |
919 | +func (me *machineEnvironer) Handle() error { |
920 | + if err := me.environ.Refresh(); isNotFoundOrUnauthorized(err) { |
921 | + return worker.ErrTerminateAgent |
922 | + } else if err != nil { |
923 | + return err |
924 | + } |
925 | + if me.environ.Life() == params.Dead { |
926 | + logger.Infof("%q is dead, terminating agent", me.environ.Tag()) |
927 | + return worker.ErrTerminateAgent |
928 | + } |
929 | + logger.Debugf("%q is now %s", me.environ.Tag(), me.environ.Life()) |
930 | + return nil |
931 | +} |
932 | + |
933 | +func (me *machineEnvironer) TearDown() error { |
934 | + // Nothing to do here. |
935 | + return nil |
936 | +} |
Reviewers: mp+194801_ code.launchpad. net,
Message:
Please take a look.
Description:
Migrate juju destroy-environment to use API
(This is a prototype, hence lacking tests.
Please review as such, and if it's in the
right direction I'll update with tests.)
This change introduces a new client API
method, DestroyJuju. This method (server-side)
marks the environment as Dying, destroys (and
waits on death of) all units, and then marks
the environment as Dead. When the environment
is marked as Dying, addition of units, services
and machines is prevented.
Machine agents watch, via the machiner API, for
changes to the environment's lifecycle, and
terminate themselves when the environment
becomes Dead.
A followup will update agent termination logic
to uninstall mongo on state servers, and also
remove the data-dir/log-dir.
Fixes #1218688
Fixes #1246343
https:/ /code.launchpad .net/~axwalk/ juju-core/ lp1246983- cli-api- removeagents/ +merge/ 194801
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/22870045/
Affected files (+425, -45 lines): destroyenvironm ent.go machine. go null/environ. go machiner/ environ. go machiner/ machine. go machiner/ machiner. go params/ internal. go params/ params. go /client/ destroyjuju. go /machine/ machiner. go test.go machiner/ machiner. go
A [revision details]
M cmd/juju/
M cmd/jujud/
M provider/
M state/api/client.go
A state/api/
M state/api/
M state/api/
M state/api/
M state/api/
A state/apiserver
M state/apiserver
M state/environ.go
M state/interface.go
M state/service.go
M state/service_
M state/state.go
M state/watcher.go
M worker/