Merge lp:~fwereade/juju-core/fix-1089289-for-1.16 into lp:juju-core/1.16
- fix-1089289-for-1.16
- Merge into 1.16
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | William Reade | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 1985 | ||||
Proposed branch: | lp:~fwereade/juju-core/fix-1089289-for-1.16 | ||||
Merge into: | lp:juju-core/1.16 | ||||
Prerequisite: | lp:~fwereade/juju-core/fix-1233457-for-1.16 | ||||
Diff against target: |
2202 lines (+911/-483) 21 files modified
cmd/juju/destroymachine.go (+22/-4) cmd/juju/destroymachine_test.go (+58/-14) cmd/juju/destroyunit.go (+3/-8) cmd/jujud/machine_test.go (+13/-1) state/api/client.go (+13/-1) state/api/params/params.go (+6/-0) state/apiserver/client/client.go (+56/-1) state/apiserver/client/client_test.go (+139/-0) state/apiserver/client/perm_test.go (+1/-1) state/cleanup.go (+208/-0) state/cleanup_test.go (+229/-0) state/life.go (+8/-1) state/life_test.go (+5/-15) state/machine.go (+20/-3) state/machine_test.go (+13/-44) state/relationunit_test.go (+88/-87) state/service_test.go (+3/-3) state/state.go (+0/-157) state/state_test.go (+0/-33) state/statecmd/destroyunit.go (+0/-16) state/unit_test.go (+26/-94) |
||||
To merge this branch: | bzr merge lp:~fwereade/juju-core/fix-1089289-for-1.16 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+195088@code.launchpad.net |
Commit message
fix lp:1089289 for 1.16
involves trunk revisions:
2015, 2025 for destroy-machine and destroy-unit over the API
2045 for force-destroy-
2050, 2051 for force-destroy-
...and a tweak to cmd/jujud/
that depends on post-1.16 address-handling code.
Description of the change
fix lp:1089289 for 1.16
involves trunk revisions:
2015, 2025 for destroy-machine and destroy-unit over the API
2045 for force-destroy-
2050, 2051 for force-destroy-
...and a tweak to cmd/jujud/
that depends on post-1.16 address-handling code.
William Reade (fwereade) wrote : | # |
Roger Peppe (rogpeppe) wrote : | # |
On 2013/11/13 16:04:22, fwereade wrote:
> Please take a look.
LGTM assuming it all works live.
Although this is obviously not a thorough review - it's too
much to take in at one sitting, especially with respect
to the subtly different 1.16 code.
Go Bot (go-bot) wrote : | # |
The attempt to merge lp:~fwereade/juju-core/fix-1089289-for-1.16 into lp:juju-core/1.16 failed. Below is the output from the failed tests.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
? launchpad.
? launchpad.
FAIL launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
Preview Diff
1 | === modified file 'cmd/juju/destroymachine.go' |
2 | --- cmd/juju/destroymachine.go 2013-08-13 19:07:35 +0000 |
3 | +++ cmd/juju/destroymachine.go 2013-11-15 11:35:17 +0000 |
4 | @@ -6,6 +6,8 @@ |
5 | import ( |
6 | "fmt" |
7 | |
8 | + "launchpad.net/gnuflag" |
9 | + |
10 | "launchpad.net/juju-core/cmd" |
11 | "launchpad.net/juju-core/juju" |
12 | "launchpad.net/juju-core/names" |
13 | @@ -15,18 +17,31 @@ |
14 | type DestroyMachineCommand struct { |
15 | cmd.EnvCommandBase |
16 | MachineIds []string |
17 | + Force bool |
18 | } |
19 | |
20 | +const destroyMachineDoc = ` |
21 | +Machines that are responsible for the environment cannot be destroyed. Machines |
22 | +running units or containers can only be destroyed with the --force flag; doing |
23 | +so will also destroy all those units and containers without giving them any |
24 | +opportunity to shut down cleanly. |
25 | +` |
26 | + |
27 | func (c *DestroyMachineCommand) Info() *cmd.Info { |
28 | return &cmd.Info{ |
29 | Name: "destroy-machine", |
30 | Args: "<machine> ...", |
31 | Purpose: "destroy machines", |
32 | - Doc: "Machines that have assigned units, or are responsible for the environment, cannot be destroyed.", |
33 | + Doc: destroyMachineDoc, |
34 | Aliases: []string{"terminate-machine"}, |
35 | } |
36 | } |
37 | |
38 | +func (c *DestroyMachineCommand) SetFlags(f *gnuflag.FlagSet) { |
39 | + c.EnvCommandBase.SetFlags(f) |
40 | + f.BoolVar(&c.Force, "force", false, "completely remove machine and all dependencies") |
41 | +} |
42 | + |
43 | func (c *DestroyMachineCommand) Init(args []string) error { |
44 | if len(args) == 0 { |
45 | return fmt.Errorf("no machines specified") |
46 | @@ -41,10 +56,13 @@ |
47 | } |
48 | |
49 | func (c *DestroyMachineCommand) Run(_ *cmd.Context) error { |
50 | - conn, err := juju.NewConnFromName(c.EnvName) |
51 | + apiclient, err := juju.NewAPIClientFromName(c.EnvName) |
52 | if err != nil { |
53 | return err |
54 | } |
55 | - defer conn.Close() |
56 | - return conn.State.DestroyMachines(c.MachineIds...) |
57 | + defer apiclient.Close() |
58 | + if c.Force { |
59 | + return apiclient.ForceDestroyMachines(c.MachineIds...) |
60 | + } |
61 | + return apiclient.DestroyMachines(c.MachineIds...) |
62 | } |
63 | |
64 | === modified file 'cmd/juju/destroymachine_test.go' |
65 | --- cmd/juju/destroymachine_test.go 2013-09-27 07:05:45 +0000 |
66 | +++ cmd/juju/destroymachine_test.go 2013-11-15 11:35:17 +0000 |
67 | @@ -6,9 +6,11 @@ |
68 | import ( |
69 | gc "launchpad.net/gocheck" |
70 | |
71 | + "launchpad.net/juju-core/errors" |
72 | jujutesting "launchpad.net/juju-core/juju/testing" |
73 | "launchpad.net/juju-core/state" |
74 | "launchpad.net/juju-core/testing" |
75 | + jc "launchpad.net/juju-core/testing/checkers" |
76 | ) |
77 | |
78 | type DestroyMachineSuite struct { |
79 | @@ -22,7 +24,7 @@ |
80 | return err |
81 | } |
82 | |
83 | -func (s *DestroyMachineSuite) TestDestroyMachine(c *gc.C) { |
84 | +func (s *DestroyMachineSuite) TestDestroyMachineWithUnit(c *gc.C) { |
85 | // Create a machine running a unit. |
86 | testing.Charms.BundlePath(s.SeriesPath, "riak") |
87 | err := runDeploy(c, "local:riak", "riak") |
88 | @@ -38,19 +40,16 @@ |
89 | // Try to destroy the machine and fail. |
90 | err = runDestroyMachine(c, "0") |
91 | c.Assert(err, gc.ErrorMatches, `no machines were destroyed: machine 0 has unit "riak/0" assigned`) |
92 | +} |
93 | |
94 | - // Remove the unit, and try to destroy the machine along with another that |
95 | - // doesn't exist; check that the machine is destroyed, but the missing one |
96 | - // is warned about. |
97 | - err = u.Destroy() |
98 | - c.Assert(err, gc.IsNil) |
99 | - err = u.EnsureDead() |
100 | - c.Assert(err, gc.IsNil) |
101 | - err = u.Remove() |
102 | +func (s *DestroyMachineSuite) TestDestroyEmptyMachine(c *gc.C) { |
103 | + // Destroy an empty machine alongside a state server; only the empty machine |
104 | + // gets destroyed. |
105 | + m0, err := s.State.AddMachine("quantal", state.JobHostUnits) |
106 | c.Assert(err, gc.IsNil) |
107 | err = runDestroyMachine(c, "0", "1") |
108 | c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 1 does not exist`) |
109 | - m0, err := s.State.Machine("0") |
110 | + err = m0.Refresh() |
111 | c.Assert(err, gc.IsNil) |
112 | c.Assert(m0.Life(), gc.Equals, state.Dying) |
113 | |
114 | @@ -60,9 +59,13 @@ |
115 | err = m0.Refresh() |
116 | c.Assert(err, gc.IsNil) |
117 | c.Assert(m0.Life(), gc.Equals, state.Dying) |
118 | +} |
119 | |
120 | - // As is destroying a Dead machine; destroying it alongside a JobManageEnviron |
121 | - // machine complains only about the JMA machine. |
122 | +func (s *DestroyMachineSuite) TestDestroyDeadMachine(c *gc.C) { |
123 | + // Destroying a Dead machine is a no-op; destroying it alongside a JobManageEnviron |
124 | + // machine complains only about the JME machine. |
125 | + m0, err := s.State.AddMachine("quantal", state.JobHostUnits) |
126 | + c.Assert(err, gc.IsNil) |
127 | err = m0.EnsureDead() |
128 | c.Assert(err, gc.IsNil) |
129 | m1, err := s.State.AddMachine("quantal", state.JobManageEnviron) |
130 | @@ -75,10 +78,51 @@ |
131 | err = m1.Refresh() |
132 | c.Assert(err, gc.IsNil) |
133 | c.Assert(m1.Life(), gc.Equals, state.Alive) |
134 | - |
135 | +} |
136 | + |
137 | +func (s *DestroyMachineSuite) TestForce(c *gc.C) { |
138 | + // Create a machine running a unit. |
139 | + testing.Charms.BundlePath(s.SeriesPath, "riak") |
140 | + err := runDeploy(c, "local:riak", "riak") |
141 | + c.Assert(err, gc.IsNil) |
142 | + |
143 | + // Get the state entities to allow sane testing. |
144 | + u, err := s.State.Unit("riak/0") |
145 | + c.Assert(err, gc.IsNil) |
146 | + m0, err := s.State.Machine("0") |
147 | + c.Assert(err, gc.IsNil) |
148 | + |
149 | + // Create a manager machine. |
150 | + m1, err := s.State.AddMachine("quantal", state.JobManageEnviron) |
151 | + c.Assert(err, gc.IsNil) |
152 | + |
153 | + // Try to force-destroy the machines. |
154 | + err = runDestroyMachine(c, "0", "1", "--force") |
155 | + c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 1 is required by the environment`) |
156 | + |
157 | + // Clean up, check state. |
158 | + err = s.State.Cleanup() |
159 | + c.Assert(err, gc.IsNil) |
160 | + err = m0.Refresh() |
161 | + c.Assert(err, jc.Satisfies, errors.IsNotFoundError) |
162 | + err = u.Refresh() |
163 | + c.Assert(err, jc.Satisfies, errors.IsNotFoundError) |
164 | + err = m1.Refresh() |
165 | + c.Assert(err, gc.IsNil) |
166 | + c.Assert(m1.Life(), gc.Equals, state.Alive) |
167 | +} |
168 | + |
169 | +func (s *DestroyMachineSuite) TestBadArgs(c *gc.C) { |
170 | // Check invalid args. |
171 | - err = runDestroyMachine(c) |
172 | + err := runDestroyMachine(c) |
173 | c.Assert(err, gc.ErrorMatches, `no machines specified`) |
174 | err = runDestroyMachine(c, "1", "2", "nonsense", "rubbish") |
175 | c.Assert(err, gc.ErrorMatches, `invalid machine id "nonsense"`) |
176 | } |
177 | + |
178 | +func (s *DestroyMachineSuite) TestEnvironmentArg(c *gc.C) { |
179 | + _, err := s.State.AddMachine("quantal", state.JobHostUnits) |
180 | + c.Assert(err, gc.IsNil) |
181 | + err = runDestroyMachine(c, "0", "-e", "dummyenv") |
182 | + c.Assert(err, gc.IsNil) |
183 | +} |
184 | |
185 | === modified file 'cmd/juju/destroyunit.go' |
186 | --- cmd/juju/destroyunit.go 2013-08-13 19:07:35 +0000 |
187 | +++ cmd/juju/destroyunit.go 2013-11-15 11:35:17 +0000 |
188 | @@ -10,8 +10,6 @@ |
189 | "launchpad.net/juju-core/cmd" |
190 | "launchpad.net/juju-core/juju" |
191 | "launchpad.net/juju-core/names" |
192 | - "launchpad.net/juju-core/state/api/params" |
193 | - "launchpad.net/juju-core/state/statecmd" |
194 | ) |
195 | |
196 | // DestroyUnitCommand is responsible for destroying service units. |
197 | @@ -45,13 +43,10 @@ |
198 | // Run connects to the environment specified on the command line and destroys |
199 | // units therein. |
200 | func (c *DestroyUnitCommand) Run(_ *cmd.Context) error { |
201 | - conn, err := juju.NewConnFromName(c.EnvName) |
202 | + client, err := juju.NewAPIClientFromName(c.EnvName) |
203 | if err != nil { |
204 | return err |
205 | } |
206 | - defer conn.Close() |
207 | - params := params.DestroyServiceUnits{ |
208 | - UnitNames: c.UnitNames, |
209 | - } |
210 | - return statecmd.DestroyServiceUnits(conn.State, params) |
211 | + defer client.Close() |
212 | + return client.DestroyServiceUnits(c.UnitNames...) |
213 | } |
214 | |
215 | === modified file 'cmd/jujud/machine_test.go' |
216 | --- cmd/jujud/machine_test.go 2013-10-03 00:52:29 +0000 |
217 | +++ cmd/jujud/machine_test.go 2013-11-15 11:35:17 +0000 |
218 | @@ -78,7 +78,19 @@ |
219 | err = m.SetPassword(initialMachinePassword) |
220 | c.Assert(err, gc.IsNil) |
221 | tag := names.MachineTag(m.Id()) |
222 | - if m.IsStateServer() { |
223 | + |
224 | + // NOTE(fwereade): this used to use IsManager() which used to be spelled |
225 | + // IsStateServer and mean "has JobManageState"; and now means "has any |
226 | + // Manage job"; IsManager works correctly given addressing changes in |
227 | + // trunk, post-1.16, but primes the agent incorrectly in 1.16-based code. |
228 | + var isStateManager bool |
229 | + for _, job := range m.Jobs() { |
230 | + if job == state.JobManageState { |
231 | + isStateManager = true |
232 | + break |
233 | + } |
234 | + } |
235 | + if isStateManager { |
236 | err = m.SetMongoPassword(initialMachinePassword) |
237 | c.Assert(err, gc.IsNil) |
238 | config, tools = s.agentSuite.primeStateAgent(c, tag, initialMachinePassword) |
239 | |
240 | === modified file 'state/api/client.go' |
241 | --- state/api/client.go 2013-08-28 07:19:00 +0000 |
242 | +++ state/api/client.go 2013-11-15 11:35:17 +0000 |
243 | @@ -84,6 +84,18 @@ |
244 | return c.st.Call("Client", "", "DestroyRelation", params, nil) |
245 | } |
246 | |
247 | +// DestroyMachines removes a given set of machines. |
248 | +func (c *Client) DestroyMachines(machines ...string) error { |
249 | + params := params.DestroyMachines{MachineNames: machines} |
250 | + return c.st.Call("Client", "", "DestroyMachines", params, nil) |
251 | +} |
252 | + |
253 | +// ForceDestroyMachines removes a given set of machines and all associated units. |
254 | +func (c *Client) ForceDestroyMachines(machines ...string) error { |
255 | + params := params.DestroyMachines{Force: true, MachineNames: machines} |
256 | + return c.st.Call("Client", "", "DestroyMachines", params, nil) |
257 | +} |
258 | + |
259 | // ServiceExpose changes the juju-managed firewall to expose any ports that |
260 | // were also explicitly marked by units as open. |
261 | func (c *Client) ServiceExpose(service string) error { |
262 | @@ -141,7 +153,7 @@ |
263 | } |
264 | |
265 | // DestroyServiceUnits decreases the number of units dedicated to a service. |
266 | -func (c *Client) DestroyServiceUnits(unitNames []string) error { |
267 | +func (c *Client) DestroyServiceUnits(unitNames ...string) error { |
268 | params := params.DestroyServiceUnits{unitNames} |
269 | return c.st.Call("Client", "", "DestroyServiceUnits", params, nil) |
270 | } |
271 | |
272 | === modified file 'state/api/params/params.go' |
273 | --- state/api/params/params.go 2013-10-31 09:39:00 +0000 |
274 | +++ state/api/params/params.go 2013-11-15 11:35:17 +0000 |
275 | @@ -59,6 +59,12 @@ |
276 | Endpoints []string |
277 | } |
278 | |
279 | +// DestroyMachines holds parameters for the DestroyMachines call. |
280 | +type DestroyMachines struct { |
281 | + MachineNames []string |
282 | + Force bool |
283 | +} |
284 | + |
285 | // ServiceDeploy holds the parameters for making the ServiceDeploy call. |
286 | type ServiceDeploy struct { |
287 | ServiceName string |
288 | |
289 | === modified file 'state/apiserver/client/client.go' |
290 | --- state/apiserver/client/client.go 2013-09-11 08:56:44 +0000 |
291 | +++ state/apiserver/client/client.go 2013-11-15 11:35:17 +0000 |
292 | @@ -6,8 +6,10 @@ |
293 | import ( |
294 | "errors" |
295 | "fmt" |
296 | + "strings" |
297 | |
298 | "launchpad.net/juju-core/charm" |
299 | + coreerrors "launchpad.net/juju-core/errors" |
300 | "launchpad.net/juju-core/juju" |
301 | "launchpad.net/juju-core/state" |
302 | "launchpad.net/juju-core/state/api" |
303 | @@ -300,7 +302,25 @@ |
304 | |
305 | // DestroyServiceUnits removes a given set of service units. |
306 | func (c *Client) DestroyServiceUnits(args params.DestroyServiceUnits) error { |
307 | - return statecmd.DestroyServiceUnits(c.api.state, args) |
308 | + var errs []string |
309 | + for _, name := range args.UnitNames { |
310 | + unit, err := c.api.state.Unit(name) |
311 | + switch { |
312 | + case coreerrors.IsNotFoundError(err): |
313 | + err = fmt.Errorf("unit %q does not exist", name) |
314 | + case err != nil: |
315 | + case unit.Life() != state.Alive: |
316 | + continue |
317 | + case unit.IsPrincipal(): |
318 | + err = unit.Destroy() |
319 | + default: |
320 | + err = fmt.Errorf("unit %q is a subordinate", name) |
321 | + } |
322 | + if err != nil { |
323 | + errs = append(errs, err.Error()) |
324 | + } |
325 | + } |
326 | + return destroyErr("units", args.UnitNames, errs) |
327 | } |
328 | |
329 | // ServiceDestroy destroys a given service. |
330 | @@ -328,6 +348,29 @@ |
331 | return statecmd.DestroyRelation(c.api.state, args) |
332 | } |
333 | |
334 | +// DestroyMachines removes a given set of machines. |
335 | +func (c *Client) DestroyMachines(args params.DestroyMachines) error { |
336 | + var errs []string |
337 | + for _, id := range args.MachineNames { |
338 | + machine, err := c.api.state.Machine(id) |
339 | + switch { |
340 | + case coreerrors.IsNotFoundError(err): |
341 | + err = fmt.Errorf("machine %s does not exist", id) |
342 | + case err != nil: |
343 | + case args.Force: |
344 | + err = machine.ForceDestroy() |
345 | + case machine.Life() != state.Alive: |
346 | + continue |
347 | + default: |
348 | + err = machine.Destroy() |
349 | + } |
350 | + if err != nil { |
351 | + errs = append(errs, err.Error()) |
352 | + } |
353 | + } |
354 | + return destroyErr("machines", args.MachineNames, errs) |
355 | +} |
356 | + |
357 | // CharmInfo returns information about the requested charm. |
358 | func (c *Client) CharmInfo(args params.CharmInfo) (api.CharmInfo, error) { |
359 | curl, err := charm.ParseURL(args.CharmURL) |
360 | @@ -436,3 +479,15 @@ |
361 | } |
362 | return changes, nil |
363 | } |
364 | + |
365 | +func destroyErr(desc string, ids, errs []string) error { |
366 | + if len(errs) == 0 { |
367 | + return nil |
368 | + } |
369 | + msg := "some %s were not destroyed" |
370 | + if len(errs) == len(ids) { |
371 | + msg = "no %s were destroyed" |
372 | + } |
373 | + msg = fmt.Sprintf(msg, desc) |
374 | + return fmt.Errorf("%s: %s", msg, strings.Join(errs, "; ")) |
375 | +} |
376 | |
377 | === modified file 'state/apiserver/client/client_test.go' |
378 | --- state/apiserver/client/client_test.go 2013-09-27 07:05:45 +0000 |
379 | +++ state/apiserver/client/client_test.go 2013-11-15 11:35:17 +0000 |
380 | @@ -355,6 +355,145 @@ |
381 | c.Assert(service.Life(), gc.Not(gc.Equals), state.Alive) |
382 | } |
383 | |
384 | +func assertLife(c *gc.C, entity state.Living, life state.Life) { |
385 | + err := entity.Refresh() |
386 | + c.Assert(err, gc.IsNil) |
387 | + c.Assert(entity.Life(), gc.Equals, life) |
388 | +} |
389 | + |
390 | +func assertRemoved(c *gc.C, entity state.Living) { |
391 | + err := entity.Refresh() |
392 | + c.Assert(err, jc.Satisfies, errors.IsNotFoundError) |
393 | +} |
394 | + |
395 | +func (s *clientSuite) setupDestroyMachinesTest(c *gc.C) (*state.Machine, *state.Machine, *state.Machine, *state.Unit) { |
396 | + m0, err := s.State.AddMachine("quantal", state.JobHostUnits) |
397 | + c.Assert(err, gc.IsNil) |
398 | + m1, err := s.State.AddMachine("quantal", state.JobManageEnviron) |
399 | + c.Assert(err, gc.IsNil) |
400 | + m2, err := s.State.AddMachine("quantal", state.JobHostUnits) |
401 | + c.Assert(err, gc.IsNil) |
402 | + |
403 | + sch := s.AddTestingCharm(c, "wordpress") |
404 | + wordpress, err := s.State.AddService("wordpress", sch) |
405 | + c.Assert(err, gc.IsNil) |
406 | + u, err := wordpress.AddUnit() |
407 | + c.Assert(err, gc.IsNil) |
408 | + err = u.AssignToMachine(m0) |
409 | + c.Assert(err, gc.IsNil) |
410 | + |
411 | + return m0, m1, m2, u |
412 | +} |
413 | + |
414 | +func (s *clientSuite) TestDestroyMachines(c *gc.C) { |
415 | + m0, m1, m2, u := s.setupDestroyMachinesTest(c) |
416 | + |
417 | + err := s.APIState.Client().DestroyMachines("0", "1", "2") |
418 | + c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 0 has unit "wordpress/0" assigned; machine 1 is required by the environment`) |
419 | + assertLife(c, m0, state.Alive) |
420 | + assertLife(c, m1, state.Alive) |
421 | + assertLife(c, m2, state.Dying) |
422 | + |
423 | + err = u.UnassignFromMachine() |
424 | + c.Assert(err, gc.IsNil) |
425 | + err = s.APIState.Client().DestroyMachines("0", "1", "2") |
426 | + c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 1 is required by the environment`) |
427 | + assertLife(c, m0, state.Dying) |
428 | + assertLife(c, m1, state.Alive) |
429 | + assertLife(c, m2, state.Dying) |
430 | +} |
431 | + |
432 | +func (s *clientSuite) TestForceDestroyMachines(c *gc.C) { |
433 | + m0, m1, m2, u := s.setupDestroyMachinesTest(c) |
434 | + |
435 | + err := s.APIState.Client().ForceDestroyMachines("0", "1", "2") |
436 | + c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 1 is required by the environment`) |
437 | + assertLife(c, m0, state.Alive) |
438 | + assertLife(c, m1, state.Alive) |
439 | + assertLife(c, m2, state.Alive) |
440 | + assertLife(c, u, state.Alive) |
441 | + |
442 | + err = s.State.Cleanup() |
443 | + c.Assert(err, gc.IsNil) |
444 | + assertRemoved(c, m0) |
445 | + assertLife(c, m1, state.Alive) |
446 | + assertRemoved(c, m2) |
447 | + assertRemoved(c, u) |
448 | +} |
449 | + |
450 | +func (s *clientSuite) TestDestroyPrincipalUnits(c *gc.C) { |
451 | + wordpress, err := s.State.AddService("wordpress", s.AddTestingCharm(c, "wordpress")) |
452 | + c.Assert(err, gc.IsNil) |
453 | + units := make([]*state.Unit, 5) |
454 | + for i := range units { |
455 | + unit, err := wordpress.AddUnit() |
456 | + c.Assert(err, gc.IsNil) |
457 | + err = unit.SetStatus(params.StatusStarted, "", nil) |
458 | + c.Assert(err, gc.IsNil) |
459 | + units[i] = unit |
460 | + } |
461 | + |
462 | + // Destroy 2 of them; check they become Dying. |
463 | + err = s.APIState.Client().DestroyServiceUnits("wordpress/0", "wordpress/1") |
464 | + c.Assert(err, gc.IsNil) |
465 | + assertLife(c, units[0], state.Dying) |
466 | + assertLife(c, units[1], state.Dying) |
467 | + |
468 | + // Try to destroy an Alive one and a Dying one; check |
469 | + // it destroys the Alive one and ignores the Dying one. |
470 | + err = s.APIState.Client().DestroyServiceUnits("wordpress/2", "wordpress/0") |
471 | + c.Assert(err, gc.IsNil) |
472 | + assertLife(c, units[2], state.Dying) |
473 | + |
474 | + // Try to destroy an Alive one along with a nonexistent one; check that |
475 | + // the valid instruction is followed but the invalid one is warned about. |
476 | + err = s.APIState.Client().DestroyServiceUnits("boojum/123", "wordpress/3") |
477 | + c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "boojum/123" does not exist`) |
478 | + assertLife(c, units[3], state.Dying) |
479 | + |
480 | + // Make one Dead, and destroy an Alive one alongside it; check no errors. |
481 | + wp0, err := s.State.Unit("wordpress/0") |
482 | + c.Assert(err, gc.IsNil) |
483 | + err = wp0.EnsureDead() |
484 | + c.Assert(err, gc.IsNil) |
485 | + err = s.APIState.Client().DestroyServiceUnits("wordpress/0", "wordpress/4") |
486 | + c.Assert(err, gc.IsNil) |
487 | + assertLife(c, units[0], state.Dead) |
488 | + assertLife(c, units[4], state.Dying) |
489 | +} |
490 | + |
491 | +func (s *clientSuite) TestDestroySubordinateUnits(c *gc.C) { |
492 | + wordpress, err := s.State.AddService("wordpress", s.AddTestingCharm(c, "wordpress")) |
493 | + c.Assert(err, gc.IsNil) |
494 | + wordpress0, err := wordpress.AddUnit() |
495 | + c.Assert(err, gc.IsNil) |
496 | + _, err = s.State.AddService("logging", s.AddTestingCharm(c, "logging")) |
497 | + c.Assert(err, gc.IsNil) |
498 | + eps, err := s.State.InferEndpoints([]string{"logging", "wordpress"}) |
499 | + c.Assert(err, gc.IsNil) |
500 | + rel, err := s.State.AddRelation(eps...) |
501 | + c.Assert(err, gc.IsNil) |
502 | + ru, err := rel.Unit(wordpress0) |
503 | + c.Assert(err, gc.IsNil) |
504 | + err = ru.EnterScope(nil) |
505 | + c.Assert(err, gc.IsNil) |
506 | + logging0, err := s.State.Unit("logging/0") |
507 | + c.Assert(err, gc.IsNil) |
508 | + |
509 | + // Try to destroy the subordinate alone; check it fails. |
510 | + err = s.APIState.Client().DestroyServiceUnits("logging/0") |
511 | + c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`) |
512 | + assertLife(c, logging0, state.Alive) |
513 | + |
514 | + // Try to destroy the principal and the subordinate together; check it warns |
515 | + // about the subordinate, but destroys the one it can. (The principal unit |
516 | + // agent will be resposible for destroying the subordinate.) |
517 | + err = s.APIState.Client().DestroyServiceUnits("wordpress/0", "logging/0") |
518 | + c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "logging/0" is a subordinate`) |
519 | + assertLife(c, wordpress0, state.Dying) |
520 | + assertLife(c, logging0, state.Alive) |
521 | +} |
522 | + |
523 | func (s *clientSuite) TestClientUnitResolved(c *gc.C) { |
524 | // Setup: |
525 | s.setUpScenario(c) |
526 | |
527 | === modified file 'state/apiserver/client/perm_test.go' |
528 | --- state/apiserver/client/perm_test.go 2013-09-27 07:05:45 +0000 |
529 | +++ state/apiserver/client/perm_test.go 2013-11-15 11:35:17 +0000 |
530 | @@ -336,7 +336,7 @@ |
531 | } |
532 | |
533 | func opClientDestroyServiceUnits(c *gc.C, st *api.State, mst *state.State) (func(), error) { |
534 | - err := st.Client().DestroyServiceUnits([]string{"wordpress/99"}) |
535 | + err := st.Client().DestroyServiceUnits("wordpress/99") |
536 | if err != nil && strings.HasPrefix(err.Error(), "no units were destroyed") { |
537 | err = nil |
538 | } |
539 | |
540 | === added file 'state/cleanup.go' |
541 | --- state/cleanup.go 1970-01-01 00:00:00 +0000 |
542 | +++ state/cleanup.go 2013-11-15 11:35:17 +0000 |
543 | @@ -0,0 +1,208 @@ |
544 | +package state |
545 | + |
546 | +import ( |
547 | + "fmt" |
548 | + |
549 | + "labix.org/v2/mgo/bson" |
550 | + "labix.org/v2/mgo/txn" |
551 | + |
552 | + "launchpad.net/juju-core/errors" |
553 | +) |
554 | + |
555 | +// cleanupDoc represents a potentially large set of documents that should be |
556 | +// removed. |
557 | +type cleanupDoc struct { |
558 | + Id bson.ObjectId `bson:"_id"` |
559 | + Kind string |
560 | + Prefix string |
561 | +} |
562 | + |
563 | +// newCleanupOp returns a txn.Op that creates a cleanup document with a unique |
564 | +// id and the supplied kind and prefix. |
565 | +func (st *State) newCleanupOp(kind, prefix string) txn.Op { |
566 | + doc := &cleanupDoc{ |
567 | + Id: bson.NewObjectId(), |
568 | + Kind: kind, |
569 | + Prefix: prefix, |
570 | + } |
571 | + return txn.Op{ |
572 | + C: st.cleanups.Name, |
573 | + Id: doc.Id, |
574 | + Insert: doc, |
575 | + } |
576 | +} |
577 | + |
578 | +// NeedsCleanup returns true if documents previously marked for removal exist. |
579 | +func (st *State) NeedsCleanup() (bool, error) { |
580 | + count, err := st.cleanups.Count() |
581 | + if err != nil { |
582 | + return false, err |
583 | + } |
584 | + return count > 0, nil |
585 | +} |
586 | + |
587 | +// Cleanup removes all documents that were previously marked for removal, if |
588 | +// any such exist. It should be called periodically by at least one element |
589 | +// of the system. |
590 | +func (st *State) Cleanup() error { |
591 | + doc := cleanupDoc{} |
592 | + iter := st.cleanups.Find(nil).Iter() |
593 | + for iter.Next(&doc) { |
594 | + var err error |
595 | + logger.Debugf("running %q cleanup: %q", doc.Kind, doc.Prefix) |
596 | + switch doc.Kind { |
597 | + case "settings": |
598 | + err = st.cleanupSettings(doc.Prefix) |
599 | + case "units": |
600 | + err = st.cleanupUnits(doc.Prefix) |
601 | + case "machine": |
602 | + err = st.cleanupMachine(doc.Prefix) |
603 | + default: |
604 | + err = fmt.Errorf("unknown cleanup kind %q", doc.Kind) |
605 | + } |
606 | + if err != nil { |
607 | + logger.Warningf("cleanup failed: %v", err) |
608 | + continue |
609 | + } |
610 | + ops := []txn.Op{{ |
611 | + C: st.cleanups.Name, |
612 | + Id: doc.Id, |
613 | + Remove: true, |
614 | + }} |
615 | + if err := st.runTransaction(ops); err != nil { |
616 | + logger.Warningf("cannot remove empty cleanup document: %v", err) |
617 | + } |
618 | + } |
619 | + if err := iter.Err(); err != nil { |
620 | + return fmt.Errorf("cannot read cleanup document: %v", err) |
621 | + } |
622 | + return nil |
623 | +} |
624 | + |
625 | +func (st *State) cleanupSettings(prefix string) error { |
626 | + // Documents marked for cleanup are not otherwise referenced in the |
627 | + // system, and will not be under watch, and are therefore safe to |
628 | + // delete directly. |
629 | + sel := D{{"_id", D{{"$regex", "^" + prefix}}}} |
630 | + if count, err := st.settings.Find(sel).Count(); err != nil { |
631 | + return fmt.Errorf("cannot detect cleanup targets: %v", err) |
632 | + } else if count != 0 { |
633 | + if _, err := st.settings.RemoveAll(sel); err != nil { |
634 | + return fmt.Errorf("cannot remove documents marked for cleanup: %v", err) |
635 | + } |
636 | + } |
637 | + return nil |
638 | +} |
639 | + |
640 | +// cleanupUnits sets all units with the given prefix to Dying, if they are not |
641 | +// already Dying or Dead. It's expected to be used when a service is destroyed. |
642 | +func (st *State) cleanupUnits(prefix string) error { |
643 | + // This won't miss units, because a Dying service cannot have units added |
644 | + // to it. But we do have to remove the units themselves via individual |
645 | + // transactions, because they could be in any state at all. |
646 | + unit := &Unit{st: st} |
647 | + sel := D{{"_id", D{{"$regex", "^" + prefix}}}, {"life", Alive}} |
648 | + iter := st.units.Find(sel).Iter() |
649 | + for iter.Next(&unit.doc) { |
650 | + if err := unit.Destroy(); err != nil { |
651 | + return err |
652 | + } |
653 | + } |
654 | + if err := iter.Err(); err != nil { |
655 | + return fmt.Errorf("cannot read unit document: %v", err) |
656 | + } |
657 | + return nil |
658 | +} |
659 | + |
660 | +// cleanupMachine systematically destroys and removes all entities that |
661 | +// depend upon the supplied machine, and removes the machine from state. It's |
662 | +// expected to be used in response to destroy-machine --force. |
663 | +func (st *State) cleanupMachine(machineId string) error { |
664 | + machine, err := st.Machine(machineId) |
665 | + if errors.IsNotFoundError(err) { |
666 | + return nil |
667 | + } else if err != nil { |
668 | + return err |
669 | + } |
670 | + // In an ideal world, we'd call machine.Destroy() here, and thus prevent |
671 | + // new dependencies being added while we clean up the ones we know about. |
672 | + // But machine destruction is unsophisticated, and doesn't allow for |
673 | + // destruction while dependencies exist; so we just have to deal with that |
674 | + // possibility below. |
675 | + if err := st.cleanupContainers(machine); err != nil { |
676 | + return err |
677 | + } |
678 | + for _, unitName := range machine.doc.Principals { |
679 | + if err := st.obliterateUnit(unitName); err != nil { |
680 | + return err |
681 | + } |
682 | + } |
683 | + // We need to refresh the machine at this point, because the local copy |
684 | + // of the document will not reflect changes caused by the unit cleanups |
685 | + // above, and may thus fail immediately. |
686 | + if err := machine.Refresh(); errors.IsNotFoundError(err) { |
687 | + return nil |
688 | + } else if err != nil { |
689 | + return err |
690 | + } |
691 | + // TODO(fwereade): 2013-11-11 bug 1250104 |
692 | + // If this fails, it's *probably* due to a race in which new dependencies |
693 | + // were added while we cleaned up the old ones. If the cleanup doesn't run |
694 | + // again -- which it *probably* will anyway -- the issue can be resolved by |
695 | + // force-destroying the machine again; that's better than adding layer |
696 | + // upon layer of complication here. |
697 | + if err := machine.EnsureDead(); err != nil { |
698 | + return err |
699 | + } |
700 | + return machine.Remove() |
701 | +} |
702 | + |
703 | +// cleanupContainers recursively calls cleanupMachine on the supplied |
704 | +// machine's containers. |
705 | +func (st *State) cleanupContainers(machine *Machine) error { |
706 | + containerIds, err := machine.Containers() |
707 | + if errors.IsNotFoundError(err) { |
708 | + return nil |
709 | + } else if err != nil { |
710 | + return err |
711 | + } |
712 | + for _, containerId := range containerIds { |
713 | + if err := st.cleanupMachine(containerId); err != nil { |
714 | + return err |
715 | + } |
716 | + } |
717 | + return nil |
718 | +} |
719 | + |
720 | +// obliterateUnit removes a unit from state completely. It is not safe or |
721 | +// sane to obliterate any unit in isolation; its only reasonable use is in |
722 | +// the context of machine obliteration, in which we can be sure that unclean |
723 | +// shutdown of units is not going to leave a machine in a difficult state. |
724 | +func (st *State) obliterateUnit(unitName string) error { |
725 | + unit, err := st.Unit(unitName) |
726 | + if errors.IsNotFoundError(err) { |
727 | + return nil |
728 | + } else if err != nil { |
729 | + return err |
730 | + } |
731 | + // Unlike the machine, we *can* always destroy the unit, and (at least) |
732 | + // prevent further dependencies being added. If we're really lucky, the |
733 | + // unit will be removed immediately. |
734 | + if err := unit.Destroy(); err != nil { |
735 | + return err |
736 | + } |
737 | + if err := unit.Refresh(); errors.IsNotFoundError(err) { |
738 | + return nil |
739 | + } else if err != nil { |
740 | + return err |
741 | + } |
742 | + for _, subName := range unit.SubordinateNames() { |
743 | + if err := st.obliterateUnit(subName); err != nil { |
744 | + return err |
745 | + } |
746 | + } |
747 | + if err := unit.EnsureDead(); err != nil { |
748 | + return err |
749 | + } |
750 | + return unit.Remove() |
751 | +} |
752 | |
753 | === added file 'state/cleanup_test.go' |
754 | --- state/cleanup_test.go 1970-01-01 00:00:00 +0000 |
755 | +++ state/cleanup_test.go 2013-11-15 11:35:17 +0000 |
756 | @@ -0,0 +1,229 @@ |
757 | +package state_test |
758 | + |
759 | +import ( |
760 | + "fmt" |
761 | + |
762 | + gc "launchpad.net/gocheck" |
763 | + |
764 | + "launchpad.net/juju-core/charm" |
765 | + "launchpad.net/juju-core/errors" |
766 | + "launchpad.net/juju-core/instance" |
767 | + "launchpad.net/juju-core/state" |
768 | + jc "launchpad.net/juju-core/testing/checkers" |
769 | +) |
770 | + |
771 | +type CleanupSuite struct { |
772 | + ConnSuite |
773 | +} |
774 | + |
775 | +var _ = gc.Suite(&CleanupSuite{}) |
776 | + |
777 | +func (s *CleanupSuite) TestCleanupDyingServiceUnits(c *gc.C) { |
778 | + s.assertDoesNotNeedCleanup(c) |
779 | + |
780 | + // Create a service with some units. |
781 | + mysql, err := s.State.AddService("mysql", s.AddTestingCharm(c, "mysql")) |
782 | + c.Assert(err, gc.IsNil) |
783 | + units := make([]*state.Unit, 3) |
784 | + for i := range units { |
785 | + unit, err := mysql.AddUnit() |
786 | + c.Assert(err, gc.IsNil) |
787 | + units[i] = unit |
788 | + } |
789 | + preventUnitDestroyRemove(c, units[0]) |
790 | + s.assertDoesNotNeedCleanup(c) |
791 | + |
792 | + // Destroy the service and check the units are unaffected, but a cleanup |
793 | + // has been scheduled. |
794 | + err = mysql.Destroy() |
795 | + c.Assert(err, gc.IsNil) |
796 | + for _, unit := range units { |
797 | + err := unit.Refresh() |
798 | + c.Assert(err, gc.IsNil) |
799 | + } |
800 | + s.assertNeedsCleanup(c) |
801 | + |
802 | + // Run the cleanup, and check that units are all destroyed as appropriate. |
803 | + s.assertCleanupRuns(c) |
804 | + s.assertDoesNotNeedCleanup(c) |
805 | + err = units[0].Refresh() |
806 | + c.Assert(err, gc.IsNil) |
807 | + c.Assert(units[0].Life(), gc.Equals, state.Dying) |
808 | + err = units[1].Refresh() |
809 | + c.Assert(err, jc.Satisfies, errors.IsNotFoundError) |
810 | + err = units[2].Refresh() |
811 | + c.Assert(err, jc.Satisfies, errors.IsNotFoundError) |
812 | +} |
813 | + |
814 | +func (s *CleanupSuite) TestCleanupRelationSettings(c *gc.C) { |
815 | + s.assertDoesNotNeedCleanup(c) |
816 | + |
817 | + // Create a relation with a unit in scope. |
818 | + pr := NewPeerRelation(c, s.State) |
819 | + rel := pr.ru0.Relation() |
820 | + err := pr.ru0.EnterScope(map[string]interface{}{"some": "settings"}) |
821 | + c.Assert(err, gc.IsNil) |
822 | + s.assertDoesNotNeedCleanup(c) |
823 | + |
824 | + // Destroy the service, check the relation's still around. |
825 | + err = pr.svc.Destroy() |
826 | + c.Assert(err, gc.IsNil) |
827 | + s.assertNeedsCleanup(c) |
828 | + s.assertCleanupRuns(c) |
829 | + err = rel.Refresh() |
830 | + c.Assert(err, gc.IsNil) |
831 | + c.Assert(rel.Life(), gc.Equals, state.Dying) |
832 | + s.assertDoesNotNeedCleanup(c) |
833 | + |
834 | + // The unit leaves scope, triggering relation removal. |
835 | + err = pr.ru0.LeaveScope() |
836 | + c.Assert(err, gc.IsNil) |
837 | + s.assertNeedsCleanup(c) |
838 | + |
839 | + // Settings are not destroyed yet... |
840 | + settings, err := pr.ru1.ReadSettings("riak/0") |
841 | + c.Assert(err, gc.IsNil) |
842 | + c.Assert(settings, gc.DeepEquals, map[string]interface{}{"some": "settings"}) |
843 | + |
844 | + // ...but they are on cleanup. |
845 | + s.assertCleanupRuns(c) |
846 | + s.assertDoesNotNeedCleanup(c) |
847 | + _, err = pr.ru1.ReadSettings("riak/0") |
848 | + c.Assert(err, gc.ErrorMatches, `cannot read settings for unit "riak/0" in relation "riak:ring": settings not found`) |
849 | +} |
850 | + |
851 | +func (s *CleanupSuite) testForceDestroyManagerError(c *gc.C, job state.MachineJob) { |
852 | + manager, err := s.State.AddMachine("quantal", job) |
853 | + c.Assert(err, gc.IsNil) |
854 | + s.assertDoesNotNeedCleanup(c) |
855 | + err = manager.ForceDestroy() |
856 | + expect := fmt.Sprintf("machine %s is required by the environment", manager.Id()) |
857 | + c.Assert(err, gc.ErrorMatches, expect) |
858 | + s.assertDoesNotNeedCleanup(c) |
859 | + assertLife(c, manager, state.Alive) |
860 | +} |
861 | + |
862 | +func (s *CleanupSuite) TestForceDestroyMachineErrors(c *gc.C) { |
863 | + s.testForceDestroyManagerError(c, state.JobManageState) |
864 | + s.testForceDestroyManagerError(c, state.JobManageEnviron) |
865 | +} |
866 | + |
867 | +func (s *CleanupSuite) TestCleanupForceDestroyedMachineUnit(c *gc.C) { |
868 | + s.assertDoesNotNeedCleanup(c) |
869 | + |
870 | + // Create a machine. |
871 | + machine, err := s.State.AddMachine("quantal", state.JobHostUnits) |
872 | + c.Assert(err, gc.IsNil) |
873 | + |
874 | + // Create a relation with a unit in scope and assigned to the machine. |
875 | + pr := NewPeerRelation(c, s.State) |
876 | + err = pr.u0.AssignToMachine(machine) |
877 | + c.Assert(err, gc.IsNil) |
878 | + err = pr.ru0.EnterScope(nil) |
879 | + c.Assert(err, gc.IsNil) |
880 | + s.assertDoesNotNeedCleanup(c) |
881 | + |
882 | + // Force machine destruction, check cleanup queued. |
883 | + err = machine.ForceDestroy() |
884 | + c.Assert(err, gc.IsNil) |
885 | + s.assertNeedsCleanup(c) |
886 | + |
887 | + // Clean up, and check that the machine has been removed... |
888 | + s.assertCleanupRuns(c) |
889 | + s.assertDoesNotNeedCleanup(c) |
890 | + err = machine.Refresh() |
891 | + c.Assert(err, jc.Satisfies, errors.IsNotFoundError) |
892 | + |
893 | + // ...and so has the unit... |
894 | + assertRemoved(c, pr.u0) |
895 | + |
896 | + // ...and the unit has departed relation scope. |
897 | + assertNotInScope(c, pr.ru0) |
898 | +} |
899 | + |
900 | +func (s *CleanupSuite) TestCleanupForceDestroyedMachineWithContainer(c *gc.C) { |
901 | + s.assertDoesNotNeedCleanup(c) |
902 | + |
903 | + // Create a machine with a container. |
904 | + machine, err := s.State.AddMachine("quantal", state.JobHostUnits) |
905 | + c.Assert(err, gc.IsNil) |
906 | + container, err := s.State.AddMachineWithConstraints(&state.AddMachineParams{ |
907 | + Series: "quantal", |
908 | + ParentId: machine.Id(), |
909 | + ContainerType: instance.LXC, |
910 | + Jobs: []state.MachineJob{state.JobHostUnits}, |
911 | + }) |
912 | + c.Assert(err, gc.IsNil) |
913 | + |
914 | + // Create active units (in relation scope, with subordinates). |
915 | + prr := NewProReqRelation(c, &s.ConnSuite, charm.ScopeContainer) |
916 | + err = prr.pru0.EnterScope(nil) |
917 | + c.Assert(err, gc.IsNil) |
918 | + err = prr.pru1.EnterScope(nil) |
919 | + c.Assert(err, gc.IsNil) |
920 | + err = prr.rru0.EnterScope(nil) |
921 | + c.Assert(err, gc.IsNil) |
922 | + err = prr.rru1.EnterScope(nil) |
923 | + c.Assert(err, gc.IsNil) |
924 | + |
925 | + // Assign the various units to machines. |
926 | + err = prr.pu0.AssignToMachine(machine) |
927 | + c.Assert(err, gc.IsNil) |
928 | + err = prr.pu1.AssignToMachine(container) |
929 | + c.Assert(err, gc.IsNil) |
930 | + s.assertDoesNotNeedCleanup(c) |
931 | + |
932 | + // Force removal of the top-level machine. |
933 | + err = machine.ForceDestroy() |
934 | + c.Assert(err, gc.IsNil) |
935 | + s.assertNeedsCleanup(c) |
936 | + |
937 | + // And do it again, just to check that the second cleanup doc for the same |
938 | + // machine doesn't cause problems down the line. |
939 | + err = machine.ForceDestroy() |
940 | + c.Assert(err, gc.IsNil) |
941 | + s.assertNeedsCleanup(c) |
942 | + |
943 | + // Clean up, and check that all the machines have been removed... |
944 | + s.assertCleanupRuns(c) |
945 | + s.assertDoesNotNeedCleanup(c) |
946 | + err = machine.Refresh() |
947 | + c.Assert(err, jc.Satisfies, errors.IsNotFoundError) |
948 | + err = container.Refresh() |
949 | + c.Assert(err, jc.Satisfies, errors.IsNotFoundError) |
950 | + |
951 | + // ...and so have all the units... |
952 | + assertRemoved(c, prr.pu0) |
953 | + assertRemoved(c, prr.pu1) |
954 | + assertRemoved(c, prr.ru0) |
955 | + assertRemoved(c, prr.ru1) |
956 | + |
957 | + // ...and none of the units have left relation scopes occupied. |
958 | + assertNotInScope(c, prr.pru0) |
959 | + assertNotInScope(c, prr.pru1) |
960 | + assertNotInScope(c, prr.rru0) |
961 | + assertNotInScope(c, prr.rru1) |
962 | +} |
963 | + |
964 | +func (s *CleanupSuite) TestNothingToCleanup(c *gc.C) { |
965 | + s.assertDoesNotNeedCleanup(c) |
966 | + s.assertCleanupRuns(c) |
967 | + s.assertDoesNotNeedCleanup(c) |
968 | +} |
969 | + |
970 | +func (s *CleanupSuite) assertCleanupRuns(c *gc.C) { |
971 | + err := s.State.Cleanup() |
972 | + c.Assert(err, gc.IsNil) |
973 | +} |
974 | + |
975 | +func (s *CleanupSuite) assertNeedsCleanup(c *gc.C) { |
976 | + actual, err := s.State.NeedsCleanup() |
977 | + c.Assert(err, gc.IsNil) |
978 | + c.Assert(actual, jc.IsTrue) |
979 | +} |
980 | + |
981 | +func (s *CleanupSuite) assertDoesNotNeedCleanup(c *gc.C) { |
982 | + actual, err := s.State.NeedsCleanup() |
983 | + c.Assert(err, gc.IsNil) |
984 | + c.Assert(actual, jc.IsFalse) |
985 | +} |
986 | |
987 | === modified file 'state/life.go' |
988 | --- state/life.go 2013-07-09 10:32:23 +0000 |
989 | +++ state/life.go 2013-11-15 11:35:17 +0000 |
990 | @@ -38,8 +38,15 @@ |
991 | type Living interface { |
992 | Life() Life |
993 | Destroy() error |
994 | + Refresh() error |
995 | +} |
996 | + |
997 | +// AgentLiving describes state entities with a lifecycle and an agent that |
998 | +// manages it. |
999 | +type AgentLiving interface { |
1000 | + Living |
1001 | EnsureDead() error |
1002 | - Refresh() error |
1003 | + Remove() error |
1004 | } |
1005 | |
1006 | func isAlive(coll *mgo.Collection, id interface{}) (bool, error) { |
1007 | |
1008 | === modified file 'state/life_test.go' |
1009 | --- state/life_test.go 2013-09-27 07:05:45 +0000 |
1010 | +++ state/life_test.go 2013-11-15 11:35:17 +0000 |
1011 | @@ -81,8 +81,7 @@ |
1012 | |
1013 | type lifeFixture interface { |
1014 | id() (coll string, id interface{}) |
1015 | - setup(s *LifeSuite, c *gc.C) state.Living |
1016 | - teardown(s *LifeSuite, c *gc.C) |
1017 | + setup(s *LifeSuite, c *gc.C) state.AgentLiving |
1018 | } |
1019 | |
1020 | type unitLife struct { |
1021 | @@ -93,7 +92,7 @@ |
1022 | return "units", l.unit.Name() |
1023 | } |
1024 | |
1025 | -func (l *unitLife) setup(s *LifeSuite, c *gc.C) state.Living { |
1026 | +func (l *unitLife) setup(s *LifeSuite, c *gc.C) state.AgentLiving { |
1027 | unit, err := s.svc.AddUnit() |
1028 | c.Assert(err, gc.IsNil) |
1029 | preventUnitDestroyRemove(c, unit) |
1030 | @@ -101,11 +100,6 @@ |
1031 | return l.unit |
1032 | } |
1033 | |
1034 | -func (l *unitLife) teardown(s *LifeSuite, c *gc.C) { |
1035 | - err := l.unit.Remove() |
1036 | - c.Assert(err, gc.IsNil) |
1037 | -} |
1038 | - |
1039 | type machineLife struct { |
1040 | machine *state.Machine |
1041 | } |
1042 | @@ -114,18 +108,13 @@ |
1043 | return "machines", l.machine.Id() |
1044 | } |
1045 | |
1046 | -func (l *machineLife) setup(s *LifeSuite, c *gc.C) state.Living { |
1047 | +func (l *machineLife) setup(s *LifeSuite, c *gc.C) state.AgentLiving { |
1048 | var err error |
1049 | l.machine, err = s.State.AddMachine("quantal", state.JobHostUnits) |
1050 | c.Assert(err, gc.IsNil) |
1051 | return l.machine |
1052 | } |
1053 | |
1054 | -func (l *machineLife) teardown(s *LifeSuite, c *gc.C) { |
1055 | - err := l.machine.Remove() |
1056 | - c.Assert(err, gc.IsNil) |
1057 | -} |
1058 | - |
1059 | func (s *LifeSuite) prepareFixture(living state.Living, lfix lifeFixture, cached, dbinitial state.Life, c *gc.C) { |
1060 | collName, id := lfix.id() |
1061 | coll := s.MgoSuite.Session.DB("juju").C(collName) |
1062 | @@ -165,7 +154,8 @@ |
1063 | c.Assert(living.Life(), gc.Equals, v.dbfinal) |
1064 | err = living.EnsureDead() |
1065 | c.Assert(err, gc.IsNil) |
1066 | - lfix.teardown(s, c) |
1067 | + err = living.Remove() |
1068 | + c.Assert(err, gc.IsNil) |
1069 | } |
1070 | } |
1071 | } |
1072 | |
1073 | === modified file 'state/machine.go' |
1074 | --- state/machine.go 2013-10-02 13:14:49 +0000 |
1075 | +++ state/machine.go 2013-11-15 11:35:17 +0000 |
1076 | @@ -181,10 +181,11 @@ |
1077 | return m.doc.Jobs |
1078 | } |
1079 | |
1080 | -// IsStateServer returns true if the machine has a JobManageState job. |
1081 | -func (m *Machine) IsStateServer() bool { |
1082 | +// IsManager returns true if the machine has JobManageState or JobManageEnviron. |
1083 | +func (m *Machine) IsManager() bool { |
1084 | for _, job := range m.doc.Jobs { |
1085 | - if job == JobManageState { |
1086 | + switch job { |
1087 | + case JobManageEnviron, JobManageState: |
1088 | return true |
1089 | } |
1090 | } |
1091 | @@ -269,6 +270,22 @@ |
1092 | return m.advanceLifecycle(Dying) |
1093 | } |
1094 | |
1095 | +// ForceDestroy queues the machine for complete removal, including the |
1096 | +// destruction of all units and containers on the machine. |
1097 | +func (m *Machine) ForceDestroy() error { |
1098 | + if !m.IsManager() { |
1099 | + ops := []txn.Op{{ |
1100 | + C: m.st.machines.Name, |
1101 | + Id: m.doc.Id, |
1102 | + Assert: D{{"jobs", D{{"$nin", []MachineJob{JobManageState}}}}}, |
1103 | + }, m.st.newCleanupOp("machine", m.doc.Id)} |
1104 | + if err := m.st.runTransaction(ops); err != txn.ErrAborted { |
1105 | + return err |
1106 | + } |
1107 | + } |
1108 | + return fmt.Errorf("machine %s is required by the environment", m.doc.Id) |
1109 | +} |
1110 | + |
1111 | // EnsureDead sets the machine lifecycle to Dead if it is Alive or Dying. |
1112 | // It does nothing otherwise. EnsureDead will fail if the machine has |
1113 | // principal units assigned, or if the machine has JobManageEnviron. |
1114 | |
1115 | === modified file 'state/machine_test.go' |
1116 | --- state/machine_test.go 2013-10-02 13:14:49 +0000 |
1117 | +++ state/machine_test.go 2013-11-15 11:35:17 +0000 |
1118 | @@ -57,15 +57,17 @@ |
1119 | c.Assert(ok, gc.Equals, true) |
1120 | } |
1121 | |
1122 | -func (s *MachineSuite) TestMachineIsStateServer(c *gc.C) { |
1123 | +func (s *MachineSuite) TestMachineIsManager(c *gc.C) { |
1124 | tests := []struct { |
1125 | isStateServer bool |
1126 | jobs []state.MachineJob |
1127 | }{ |
1128 | {false, []state.MachineJob{state.JobHostUnits}}, |
1129 | - {false, []state.MachineJob{state.JobHostUnits, state.JobManageEnviron}}, |
1130 | + {true, []state.MachineJob{state.JobManageState}}, |
1131 | + {true, []state.MachineJob{state.JobManageEnviron}}, |
1132 | + {true, []state.MachineJob{state.JobHostUnits, state.JobManageState}}, |
1133 | + {true, []state.MachineJob{state.JobHostUnits, state.JobManageEnviron}}, |
1134 | {true, []state.MachineJob{state.JobHostUnits, state.JobManageState, state.JobManageEnviron}}, |
1135 | - {true, []state.MachineJob{state.JobManageState}}, |
1136 | } |
1137 | for _, test := range tests { |
1138 | params := state.AddMachineParams{ |
1139 | @@ -74,7 +76,7 @@ |
1140 | } |
1141 | m, err := s.State.AddMachineWithConstraints(¶ms) |
1142 | c.Assert(err, gc.IsNil) |
1143 | - c.Assert(m.IsStateServer(), gc.Equals, test.isStateServer) |
1144 | + c.Assert(m.IsManager(), gc.Equals, test.isStateServer) |
1145 | } |
1146 | } |
1147 | |
1148 | @@ -84,6 +86,8 @@ |
1149 | c.Assert(err, gc.IsNil) |
1150 | err = m.Destroy() |
1151 | c.Assert(err, gc.ErrorMatches, "machine 1 is required by the environment") |
1152 | + err = m.ForceDestroy() |
1153 | + c.Assert(err, gc.ErrorMatches, "machine 1 is required by the environment") |
1154 | err = m.EnsureDead() |
1155 | c.Assert(err, gc.ErrorMatches, "machine 1 is required by the environment") |
1156 | } |
1157 | @@ -208,41 +212,6 @@ |
1158 | c.Assert(err, gc.IsNil) |
1159 | } |
1160 | |
1161 | -func (s *MachineSuite) TestDestroyMachines(c *gc.C) { |
1162 | - m0 := s.machine |
1163 | - m1, err := s.State.AddMachine("quantal", state.JobManageEnviron) |
1164 | - c.Assert(err, gc.IsNil) |
1165 | - m2, err := s.State.AddMachine("quantal", state.JobHostUnits) |
1166 | - c.Assert(err, gc.IsNil) |
1167 | - |
1168 | - sch := s.AddTestingCharm(c, "wordpress") |
1169 | - wordpress, err := s.State.AddService("wordpress", sch) |
1170 | - c.Assert(err, gc.IsNil) |
1171 | - u, err := wordpress.AddUnit() |
1172 | - c.Assert(err, gc.IsNil) |
1173 | - err = u.AssignToMachine(m0) |
1174 | - c.Assert(err, gc.IsNil) |
1175 | - |
1176 | - err = s.State.DestroyMachines("0", "1", "2") |
1177 | - c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 0 has unit "wordpress/0" assigned; machine 1 is required by the environment`) |
1178 | - assertLife := func(m *state.Machine, life state.Life) { |
1179 | - err := m.Refresh() |
1180 | - c.Assert(err, gc.IsNil) |
1181 | - c.Assert(m.Life(), gc.Equals, life) |
1182 | - } |
1183 | - assertLife(m0, state.Alive) |
1184 | - assertLife(m1, state.Alive) |
1185 | - assertLife(m2, state.Dying) |
1186 | - |
1187 | - err = u.UnassignFromMachine() |
1188 | - c.Assert(err, gc.IsNil) |
1189 | - err = s.State.DestroyMachines("0", "1", "2") |
1190 | - c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 1 is required by the environment`) |
1191 | - assertLife(m0, state.Dying) |
1192 | - assertLife(m1, state.Alive) |
1193 | - assertLife(m2, state.Dying) |
1194 | -} |
1195 | - |
1196 | func (s *MachineSuite) TestMachineSetAgentAlive(c *gc.C) { |
1197 | alive, err := s.machine.AgentAlive() |
1198 | c.Assert(err, gc.IsNil) |
1199 | @@ -611,11 +580,11 @@ |
1200 | func (s *MachineSuite) TestWatchDiesOnStateClose(c *gc.C) { |
1201 | // This test is testing logic in watcher.entityWatcher, which |
1202 | // is also used by: |
1203 | - // Machine.WatchHardwareCharacteristics |
1204 | - // Service.Watch |
1205 | - // Unit.Watch |
1206 | - // State.WatchForEnvironConfigChanges |
1207 | - // Unit.WatchConfigSettings |
1208 | + // Machine.WatchHardwareCharacteristics |
1209 | + // Service.Watch |
1210 | + // Unit.Watch |
1211 | + // State.WatchForEnvironConfigChanges |
1212 | + // Unit.WatchConfigSettings |
1213 | testWatcherDiesWhenStateCloses(c, func(c *gc.C, st *state.State) waiter { |
1214 | m, err := st.Machine(s.machine.Id()) |
1215 | c.Assert(err, gc.IsNil) |
1216 | |
1217 | === modified file 'state/relationunit_test.go' |
1218 | --- state/relationunit_test.go 2013-09-08 09:23:01 +0000 |
1219 | +++ state/relationunit_test.go 2013-11-15 11:35:17 +0000 |
1220 | @@ -27,10 +27,16 @@ |
1221 | |
1222 | var _ = gc.Suite(&RelationUnitSuite{}) |
1223 | |
1224 | -func (s *RelationUnitSuite) assertInScope(c *gc.C, ru *state.RelationUnit, inScope bool) { |
1225 | - ok, err := ru.InScope() |
1226 | - c.Assert(err, gc.IsNil) |
1227 | - c.Assert(ok, gc.Equals, inScope) |
1228 | +func assertInScope(c *gc.C, ru *state.RelationUnit) { |
1229 | + ok, err := ru.InScope() |
1230 | + c.Assert(err, gc.IsNil) |
1231 | + c.Assert(ok, jc.IsTrue) |
1232 | +} |
1233 | + |
1234 | +func assertNotInScope(c *gc.C, ru *state.RelationUnit) { |
1235 | + ok, err := ru.InScope() |
1236 | + c.Assert(err, gc.IsNil) |
1237 | + c.Assert(ok, jc.IsFalse) |
1238 | } |
1239 | |
1240 | func (s *RelationUnitSuite) TestReadSettingsErrors(c *gc.C) { |
1241 | @@ -66,7 +72,7 @@ |
1242 | } |
1243 | |
1244 | // Add settings for one RU. |
1245 | - s.assertInScope(c, pr.ru0, false) |
1246 | + assertNotInScope(c, pr.ru0) |
1247 | err := pr.ru0.EnterScope(map[string]interface{}{"gene": "kelly"}) |
1248 | c.Assert(err, gc.IsNil) |
1249 | node, err := pr.ru0.Settings() |
1250 | @@ -88,7 +94,7 @@ |
1251 | } |
1252 | } |
1253 | assertSettings(pr.u0, normal) |
1254 | - s.assertInScope(c, pr.ru0, true) |
1255 | + assertInScope(c, pr.ru0) |
1256 | |
1257 | // Check that EnterScope when scope already entered does not touch |
1258 | // settings at all. |
1259 | @@ -96,37 +102,37 @@ |
1260 | err = pr.ru0.EnterScope(changed) |
1261 | c.Assert(err, gc.IsNil) |
1262 | assertSettings(pr.u0, normal) |
1263 | - s.assertInScope(c, pr.ru0, true) |
1264 | + assertInScope(c, pr.ru0) |
1265 | |
1266 | // Leave scope, check settings are still as accessible as before. |
1267 | err = pr.ru0.LeaveScope() |
1268 | c.Assert(err, gc.IsNil) |
1269 | assertSettings(pr.u0, normal) |
1270 | - s.assertInScope(c, pr.ru0, false) |
1271 | + assertNotInScope(c, pr.ru0) |
1272 | |
1273 | // Re-enter scope wih changed settings, and check they completely overwrite |
1274 | // the old ones. |
1275 | err = pr.ru0.EnterScope(changed) |
1276 | c.Assert(err, gc.IsNil) |
1277 | assertSettings(pr.u0, changed) |
1278 | - s.assertInScope(c, pr.ru0, true) |
1279 | + assertInScope(c, pr.ru0) |
1280 | |
1281 | // Leave and re-enter with nil nettings, and check they overwrite to become |
1282 | // an empty map. |
1283 | err = pr.ru0.LeaveScope() |
1284 | c.Assert(err, gc.IsNil) |
1285 | - s.assertInScope(c, pr.ru0, false) |
1286 | + assertNotInScope(c, pr.ru0) |
1287 | err = pr.ru0.EnterScope(nil) |
1288 | c.Assert(err, gc.IsNil) |
1289 | assertSettings(pr.u0, map[string]interface{}{}) |
1290 | - s.assertInScope(c, pr.ru0, true) |
1291 | + assertInScope(c, pr.ru0) |
1292 | |
1293 | // Check that entering scope for the first time with nil settings works correctly. |
1294 | - s.assertInScope(c, pr.ru1, false) |
1295 | + assertNotInScope(c, pr.ru1) |
1296 | err = pr.ru1.EnterScope(nil) |
1297 | c.Assert(err, gc.IsNil) |
1298 | assertSettings(pr.u1, map[string]interface{}{}) |
1299 | - s.assertInScope(c, pr.ru1, true) |
1300 | + assertInScope(c, pr.ru1) |
1301 | } |
1302 | |
1303 | func (s *RelationUnitSuite) TestProReqSettings(c *gc.C) { |
1304 | @@ -140,7 +146,7 @@ |
1305 | } |
1306 | |
1307 | // Add settings for one RU. |
1308 | - s.assertInScope(c, prr.pru0, false) |
1309 | + assertNotInScope(c, prr.pru0) |
1310 | err := prr.pru0.EnterScope(map[string]interface{}{"gene": "simmons"}) |
1311 | c.Assert(err, gc.IsNil) |
1312 | node, err := prr.pru0.Settings() |
1313 | @@ -148,7 +154,7 @@ |
1314 | node.Set("meme", "foul-bachelor-frog") |
1315 | _, err = node.Write() |
1316 | c.Assert(err, gc.IsNil) |
1317 | - s.assertInScope(c, prr.pru0, true) |
1318 | + assertInScope(c, prr.pru0) |
1319 | |
1320 | // Check settings can be read by every RU. |
1321 | for _, ru := range rus { |
1322 | @@ -170,7 +176,7 @@ |
1323 | } |
1324 | |
1325 | // Add settings for one RU. |
1326 | - s.assertInScope(c, prr.pru0, false) |
1327 | + assertNotInScope(c, prr.pru0) |
1328 | err := prr.pru0.EnterScope(map[string]interface{}{"gene": "hackman"}) |
1329 | c.Assert(err, gc.IsNil) |
1330 | node, err := prr.pru0.Settings() |
1331 | @@ -178,7 +184,7 @@ |
1332 | node.Set("meme", "foul-bachelor-frog") |
1333 | _, err = node.Write() |
1334 | c.Assert(err, gc.IsNil) |
1335 | - s.assertInScope(c, prr.pru0, true) |
1336 | + assertInScope(c, prr.pru0) |
1337 | |
1338 | // Check settings can be read by RUs in the same container. |
1339 | rus0 := RUs{prr.pru0, prr.rru0} |
1340 | @@ -221,27 +227,27 @@ |
1341 | assertSubCount(0) |
1342 | |
1343 | // Enter principal's scope and check a subordinate was created. |
1344 | - s.assertInScope(c, pru, false) |
1345 | + assertNotInScope(c, pru) |
1346 | err = pru.EnterScope(nil) |
1347 | c.Assert(err, gc.IsNil) |
1348 | assertSubCount(1) |
1349 | - s.assertInScope(c, pru, true) |
1350 | + assertInScope(c, pru) |
1351 | |
1352 | // Enter principal scope again and check no more subordinates created. |
1353 | err = pru.EnterScope(nil) |
1354 | c.Assert(err, gc.IsNil) |
1355 | assertSubCount(1) |
1356 | - s.assertInScope(c, pru, true) |
1357 | + assertInScope(c, pru) |
1358 | |
1359 | // Leave principal scope, then re-enter, and check that still no further |
1360 | // subordinates are created. |
1361 | err = pru.LeaveScope() |
1362 | c.Assert(err, gc.IsNil) |
1363 | - s.assertInScope(c, pru, false) |
1364 | + assertNotInScope(c, pru) |
1365 | err = pru.EnterScope(nil) |
1366 | c.Assert(err, gc.IsNil) |
1367 | runits := assertSubCount(1) |
1368 | - s.assertInScope(c, pru, true) |
1369 | + assertInScope(c, pru) |
1370 | |
1371 | // Set the subordinate to Dying, and enter scope again; because the scope |
1372 | // is already entered, no error is returned. |
1373 | @@ -250,15 +256,15 @@ |
1374 | c.Assert(err, gc.IsNil) |
1375 | err = pru.EnterScope(nil) |
1376 | c.Assert(err, gc.IsNil) |
1377 | - s.assertInScope(c, pru, true) |
1378 | + assertInScope(c, pru) |
1379 | |
1380 | // Leave scope, then try to enter again with the Dying subordinate. |
1381 | err = pru.LeaveScope() |
1382 | c.Assert(err, gc.IsNil) |
1383 | - s.assertInScope(c, pru, false) |
1384 | + assertNotInScope(c, pru) |
1385 | err = pru.EnterScope(nil) |
1386 | c.Assert(err, gc.Equals, state.ErrCannotEnterScopeYet) |
1387 | - s.assertInScope(c, pru, false) |
1388 | + assertNotInScope(c, pru) |
1389 | |
1390 | // Remove the subordinate, and enter scope again; this should work, and |
1391 | // create a new subordinate. |
1392 | @@ -267,11 +273,11 @@ |
1393 | err = runit.Remove() |
1394 | c.Assert(err, gc.IsNil) |
1395 | assertSubCount(0) |
1396 | - s.assertInScope(c, pru, false) |
1397 | + assertNotInScope(c, pru) |
1398 | err = pru.EnterScope(nil) |
1399 | c.Assert(err, gc.IsNil) |
1400 | assertSubCount(1) |
1401 | - s.assertInScope(c, pru, true) |
1402 | + assertInScope(c, pru) |
1403 | } |
1404 | |
1405 | func (s *RelationUnitSuite) TestDestroyRelationWithUnitsInScope(c *gc.C) { |
1406 | @@ -280,14 +286,14 @@ |
1407 | |
1408 | // Enter two units, and check that Destroying the service sets the |
1409 | // relation to Dying (rather than removing it directly). |
1410 | - s.assertInScope(c, pr.ru0, false) |
1411 | + assertNotInScope(c, pr.ru0) |
1412 | err := pr.ru0.EnterScope(map[string]interface{}{"some": "settings"}) |
1413 | c.Assert(err, gc.IsNil) |
1414 | - s.assertInScope(c, pr.ru0, true) |
1415 | - s.assertInScope(c, pr.ru1, false) |
1416 | + assertInScope(c, pr.ru0) |
1417 | + assertNotInScope(c, pr.ru1) |
1418 | err = pr.ru1.EnterScope(nil) |
1419 | c.Assert(err, gc.IsNil) |
1420 | - s.assertInScope(c, pr.ru1, true) |
1421 | + assertInScope(c, pr.ru1) |
1422 | err = pr.svc.Destroy() |
1423 | c.Assert(err, gc.IsNil) |
1424 | err = rel.Refresh() |
1425 | @@ -295,20 +301,20 @@ |
1426 | c.Assert(rel.Life(), gc.Equals, state.Dying) |
1427 | |
1428 | // Check that we can't add a new unit now. |
1429 | - s.assertInScope(c, pr.ru2, false) |
1430 | + assertNotInScope(c, pr.ru2) |
1431 | err = pr.ru2.EnterScope(nil) |
1432 | c.Assert(err, gc.Equals, state.ErrCannotEnterScope) |
1433 | - s.assertInScope(c, pr.ru2, false) |
1434 | + assertNotInScope(c, pr.ru2) |
1435 | |
1436 | // Check that we created no settings for the unit we failed to add. |
1437 | _, err = pr.ru0.ReadSettings("riak/2") |
1438 | c.Assert(err, gc.ErrorMatches, `cannot read settings for unit "riak/2" in relation "riak:ring": settings not found`) |
1439 | |
1440 | // ru0 leaves the scope; check that service Destroy is still a no-op. |
1441 | - s.assertInScope(c, pr.ru0, true) |
1442 | + assertInScope(c, pr.ru0) |
1443 | err = pr.ru0.LeaveScope() |
1444 | c.Assert(err, gc.IsNil) |
1445 | - s.assertInScope(c, pr.ru0, false) |
1446 | + assertNotInScope(c, pr.ru0) |
1447 | err = pr.svc.Destroy() |
1448 | c.Assert(err, gc.IsNil) |
1449 | |
1450 | @@ -324,10 +330,10 @@ |
1451 | assertSettings() |
1452 | |
1453 | // The final unit leaves the scope, and cleans up after itself. |
1454 | - s.assertInScope(c, pr.ru1, true) |
1455 | + assertInScope(c, pr.ru1) |
1456 | err = pr.ru1.LeaveScope() |
1457 | c.Assert(err, gc.IsNil) |
1458 | - s.assertInScope(c, pr.ru1, false) |
1459 | + assertNotInScope(c, pr.ru1) |
1460 | err = rel.Refresh() |
1461 | c.Assert(err, jc.Satisfies, errors.IsNotFoundError) |
1462 | |
1463 | @@ -339,11 +345,6 @@ |
1464 | c.Assert(err, gc.IsNil) |
1465 | _, err = pr.ru1.ReadSettings("riak/0") |
1466 | c.Assert(err, gc.ErrorMatches, `cannot read settings for unit "riak/0" in relation "riak:ring": settings not found`) |
1467 | - |
1468 | - // Because this is the only sensible place, check that a further call |
1469 | - // to Cleanup does not error out. |
1470 | - err = s.State.Cleanup() |
1471 | - c.Assert(err, gc.IsNil) |
1472 | } |
1473 | |
1474 | func (s *RelationUnitSuite) TestAliveRelationScope(c *gc.C) { |
1475 | @@ -351,14 +352,14 @@ |
1476 | rel := pr.ru0.Relation() |
1477 | |
1478 | // Two units enter... |
1479 | - s.assertInScope(c, pr.ru0, false) |
1480 | + assertNotInScope(c, pr.ru0) |
1481 | err := pr.ru0.EnterScope(nil) |
1482 | c.Assert(err, gc.IsNil) |
1483 | - s.assertInScope(c, pr.ru0, true) |
1484 | - s.assertInScope(c, pr.ru1, false) |
1485 | + assertInScope(c, pr.ru0) |
1486 | + assertNotInScope(c, pr.ru1) |
1487 | err = pr.ru1.EnterScope(nil) |
1488 | c.Assert(err, gc.IsNil) |
1489 | - s.assertInScope(c, pr.ru1, true) |
1490 | + assertInScope(c, pr.ru1) |
1491 | |
1492 | // One unit becomes Dying, then re-enters the scope; this is not an error, |
1493 | // because the state is already as requested. |
1494 | @@ -366,15 +367,15 @@ |
1495 | c.Assert(err, gc.IsNil) |
1496 | err = pr.ru0.EnterScope(nil) |
1497 | c.Assert(err, gc.IsNil) |
1498 | - s.assertInScope(c, pr.ru0, true) |
1499 | + assertInScope(c, pr.ru0) |
1500 | |
1501 | // Two units leave... |
1502 | err = pr.ru0.LeaveScope() |
1503 | c.Assert(err, gc.IsNil) |
1504 | - s.assertInScope(c, pr.ru0, false) |
1505 | + assertNotInScope(c, pr.ru0) |
1506 | err = pr.ru1.LeaveScope() |
1507 | c.Assert(err, gc.IsNil) |
1508 | - s.assertInScope(c, pr.ru1, false) |
1509 | + assertNotInScope(c, pr.ru1) |
1510 | |
1511 | // The relation scope is empty, but the relation is still alive... |
1512 | err = rel.Refresh() |
1513 | @@ -382,18 +383,18 @@ |
1514 | c.Assert(rel.Life(), gc.Equals, state.Alive) |
1515 | |
1516 | // ...and new units can still join it... |
1517 | - s.assertInScope(c, pr.ru2, false) |
1518 | + assertNotInScope(c, pr.ru2) |
1519 | err = pr.ru2.EnterScope(nil) |
1520 | c.Assert(err, gc.IsNil) |
1521 | - s.assertInScope(c, pr.ru2, true) |
1522 | + assertInScope(c, pr.ru2) |
1523 | |
1524 | // ...but Dying units cannot. |
1525 | err = pr.u3.Destroy() |
1526 | c.Assert(err, gc.IsNil) |
1527 | - s.assertInScope(c, pr.ru3, false) |
1528 | + assertNotInScope(c, pr.ru3) |
1529 | err = pr.ru3.EnterScope(nil) |
1530 | c.Assert(err, gc.Equals, state.ErrCannotEnterScope) |
1531 | - s.assertInScope(c, pr.ru3, false) |
1532 | + assertNotInScope(c, pr.ru3) |
1533 | } |
1534 | |
1535 | func (s *StateSuite) TestWatchWatchScopeDiesOnStateClose(c *gc.C) { |
1536 | @@ -415,35 +416,35 @@ |
1537 | s.assertNoScopeChange(c, w0) |
1538 | |
1539 | // ru0 enters; check no change, but settings written. |
1540 | - s.assertInScope(c, pr.ru0, false) |
1541 | + assertNotInScope(c, pr.ru0) |
1542 | err := pr.ru0.EnterScope(map[string]interface{}{"foo": "bar"}) |
1543 | c.Assert(err, gc.IsNil) |
1544 | s.assertNoScopeChange(c, w0) |
1545 | node, err := pr.ru0.Settings() |
1546 | c.Assert(err, gc.IsNil) |
1547 | c.Assert(node.Map(), gc.DeepEquals, map[string]interface{}{"foo": "bar"}) |
1548 | - s.assertInScope(c, pr.ru0, true) |
1549 | + assertInScope(c, pr.ru0) |
1550 | |
1551 | // ru1 enters; check change is observed. |
1552 | - s.assertInScope(c, pr.ru1, false) |
1553 | + assertNotInScope(c, pr.ru1) |
1554 | err = pr.ru1.EnterScope(nil) |
1555 | c.Assert(err, gc.IsNil) |
1556 | s.assertScopeChange(c, w0, []string{"riak/1"}, nil) |
1557 | s.assertNoScopeChange(c, w0) |
1558 | - s.assertInScope(c, pr.ru1, true) |
1559 | + assertInScope(c, pr.ru1) |
1560 | |
1561 | // ru1 enters again, check no problems and no changes. |
1562 | err = pr.ru1.EnterScope(nil) |
1563 | c.Assert(err, gc.IsNil) |
1564 | s.assertNoScopeChange(c, w0) |
1565 | - s.assertInScope(c, pr.ru1, true) |
1566 | + assertInScope(c, pr.ru1) |
1567 | |
1568 | // Stop watching; ru2 enters. |
1569 | testing.AssertStop(c, w0) |
1570 | - s.assertInScope(c, pr.ru2, false) |
1571 | + assertNotInScope(c, pr.ru2) |
1572 | err = pr.ru2.EnterScope(nil) |
1573 | c.Assert(err, gc.IsNil) |
1574 | - s.assertInScope(c, pr.ru2, true) |
1575 | + assertInScope(c, pr.ru2) |
1576 | |
1577 | // Start watch again, check initial event. |
1578 | w0 = pr.ru0.WatchScope() |
1579 | @@ -452,18 +453,18 @@ |
1580 | s.assertNoScopeChange(c, w0) |
1581 | |
1582 | // ru1 leaves; check event. |
1583 | - s.assertInScope(c, pr.ru1, true) |
1584 | + assertInScope(c, pr.ru1) |
1585 | err = pr.ru1.LeaveScope() |
1586 | c.Assert(err, gc.IsNil) |
1587 | s.assertScopeChange(c, w0, nil, []string{"riak/1"}) |
1588 | s.assertNoScopeChange(c, w0) |
1589 | - s.assertInScope(c, pr.ru1, false) |
1590 | + assertNotInScope(c, pr.ru1) |
1591 | |
1592 | // ru1 leaves again; check no problems and no changes. |
1593 | err = pr.ru1.LeaveScope() |
1594 | c.Assert(err, gc.IsNil) |
1595 | s.assertNoScopeChange(c, w0) |
1596 | - s.assertInScope(c, pr.ru1, false) |
1597 | + assertNotInScope(c, pr.ru1) |
1598 | } |
1599 | |
1600 | func (s *RelationUnitSuite) TestProReqWatchScope(c *gc.C) { |
1601 | @@ -480,7 +481,7 @@ |
1602 | s.assertNoScopeChange(c, ws...) |
1603 | |
1604 | // pru0 enters; check detected only by req RUs. |
1605 | - s.assertInScope(c, prr.pru0, false) |
1606 | + assertNotInScope(c, prr.pru0) |
1607 | err := prr.pru0.EnterScope(nil) |
1608 | c.Assert(err, gc.IsNil) |
1609 | rws := func() []*state.RelationScopeWatcher { |
1610 | @@ -490,10 +491,10 @@ |
1611 | s.assertScopeChange(c, w, []string{"mysql/0"}, nil) |
1612 | } |
1613 | s.assertNoScopeChange(c, ws...) |
1614 | - s.assertInScope(c, prr.pru0, true) |
1615 | + assertInScope(c, prr.pru0) |
1616 | |
1617 | // req0 enters; check detected only by pro RUs. |
1618 | - s.assertInScope(c, prr.rru0, false) |
1619 | + assertNotInScope(c, prr.rru0) |
1620 | err = prr.rru0.EnterScope(nil) |
1621 | c.Assert(err, gc.IsNil) |
1622 | pws := func() []*state.RelationScopeWatcher { |
1623 | @@ -503,20 +504,20 @@ |
1624 | s.assertScopeChange(c, w, []string{"wordpress/0"}, nil) |
1625 | } |
1626 | s.assertNoScopeChange(c, ws...) |
1627 | - s.assertInScope(c, prr.rru0, true) |
1628 | + assertInScope(c, prr.rru0) |
1629 | |
1630 | // Stop watches; remaining RUs enter. |
1631 | for _, w := range ws { |
1632 | testing.AssertStop(c, w) |
1633 | } |
1634 | - s.assertInScope(c, prr.pru1, false) |
1635 | + assertNotInScope(c, prr.pru1) |
1636 | err = prr.pru1.EnterScope(nil) |
1637 | c.Assert(err, gc.IsNil) |
1638 | - s.assertInScope(c, prr.pru1, true) |
1639 | - s.assertInScope(c, prr.rru1, false) |
1640 | + assertInScope(c, prr.pru1) |
1641 | + assertNotInScope(c, prr.rru1) |
1642 | err = prr.rru1.EnterScope(nil) |
1643 | c.Assert(err, gc.IsNil) |
1644 | - s.assertInScope(c, prr.rru0, true) |
1645 | + assertInScope(c, prr.rru0) |
1646 | |
1647 | // Start new watches, check initial events. |
1648 | ws = prr.watches() |
1649 | @@ -532,24 +533,24 @@ |
1650 | s.assertNoScopeChange(c, ws...) |
1651 | |
1652 | // pru0 leaves; check detected only by req RUs. |
1653 | - s.assertInScope(c, prr.pru0, true) |
1654 | + assertInScope(c, prr.pru0) |
1655 | err = prr.pru0.LeaveScope() |
1656 | c.Assert(err, gc.IsNil) |
1657 | for _, w := range rws() { |
1658 | s.assertScopeChange(c, w, nil, []string{"mysql/0"}) |
1659 | } |
1660 | s.assertNoScopeChange(c, ws...) |
1661 | - s.assertInScope(c, prr.pru0, false) |
1662 | + assertNotInScope(c, prr.pru0) |
1663 | |
1664 | // rru0 leaves; check detected only by pro RUs. |
1665 | - s.assertInScope(c, prr.rru0, true) |
1666 | + assertInScope(c, prr.rru0) |
1667 | err = prr.rru0.LeaveScope() |
1668 | c.Assert(err, gc.IsNil) |
1669 | for _, w := range pws() { |
1670 | s.assertScopeChange(c, w, nil, []string{"wordpress/0"}) |
1671 | } |
1672 | s.assertNoScopeChange(c, ws...) |
1673 | - s.assertInScope(c, prr.rru0, false) |
1674 | + assertNotInScope(c, prr.rru0) |
1675 | } |
1676 | |
1677 | func (s *RelationUnitSuite) TestContainerWatchScope(c *gc.C) { |
1678 | @@ -566,29 +567,29 @@ |
1679 | s.assertNoScopeChange(c, ws...) |
1680 | |
1681 | // pru0 enters; check detected only by same-container req. |
1682 | - s.assertInScope(c, prr.pru0, false) |
1683 | + assertNotInScope(c, prr.pru0) |
1684 | err := prr.pru0.EnterScope(nil) |
1685 | c.Assert(err, gc.IsNil) |
1686 | s.assertScopeChange(c, ws[2], []string{"mysql/0"}, nil) |
1687 | s.assertNoScopeChange(c, ws...) |
1688 | - s.assertInScope(c, prr.pru0, true) |
1689 | + assertInScope(c, prr.pru0) |
1690 | |
1691 | // req1 enters; check detected only by same-container pro. |
1692 | - s.assertInScope(c, prr.rru1, false) |
1693 | + assertNotInScope(c, prr.rru1) |
1694 | err = prr.rru1.EnterScope(nil) |
1695 | c.Assert(err, gc.IsNil) |
1696 | s.assertScopeChange(c, ws[1], []string{"logging/1"}, nil) |
1697 | s.assertNoScopeChange(c, ws...) |
1698 | - s.assertInScope(c, prr.rru1, true) |
1699 | + assertInScope(c, prr.rru1) |
1700 | |
1701 | // Stop watches; remaining RUs enter scope. |
1702 | for _, w := range ws { |
1703 | testing.AssertStop(c, w) |
1704 | } |
1705 | - s.assertInScope(c, prr.pru1, false) |
1706 | + assertNotInScope(c, prr.pru1) |
1707 | err = prr.pru1.EnterScope(nil) |
1708 | c.Assert(err, gc.IsNil) |
1709 | - s.assertInScope(c, prr.rru0, false) |
1710 | + assertNotInScope(c, prr.rru0) |
1711 | err = prr.rru0.EnterScope(nil) |
1712 | c.Assert(err, gc.IsNil) |
1713 | |
1714 | @@ -602,24 +603,24 @@ |
1715 | s.assertScopeChange(c, ws[2], []string{"mysql/0"}, nil) |
1716 | s.assertScopeChange(c, ws[3], []string{"mysql/1"}, nil) |
1717 | s.assertNoScopeChange(c, ws...) |
1718 | - s.assertInScope(c, prr.pru1, true) |
1719 | - s.assertInScope(c, prr.rru0, true) |
1720 | + assertInScope(c, prr.pru1) |
1721 | + assertInScope(c, prr.rru0) |
1722 | |
1723 | // pru0 leaves; check detected only by same-container req. |
1724 | - s.assertInScope(c, prr.pru0, true) |
1725 | + assertInScope(c, prr.pru0) |
1726 | err = prr.pru0.LeaveScope() |
1727 | c.Assert(err, gc.IsNil) |
1728 | s.assertScopeChange(c, ws[2], nil, []string{"mysql/0"}) |
1729 | s.assertNoScopeChange(c, ws...) |
1730 | - s.assertInScope(c, prr.pru0, false) |
1731 | + assertNotInScope(c, prr.pru0) |
1732 | |
1733 | // rru0 leaves; check detected only by same-container pro. |
1734 | - s.assertInScope(c, prr.rru0, true) |
1735 | + assertInScope(c, prr.rru0) |
1736 | err = prr.rru0.LeaveScope() |
1737 | c.Assert(err, gc.IsNil) |
1738 | s.assertScopeChange(c, ws[0], nil, []string{"logging/0"}) |
1739 | s.assertNoScopeChange(c, ws...) |
1740 | - s.assertInScope(c, prr.rru0, false) |
1741 | + assertNotInScope(c, prr.rru0) |
1742 | } |
1743 | |
1744 | func (s *RelationUnitSuite) assertScopeChange(c *gc.C, w *state.RelationScopeWatcher, entered, left []string) { |
1745 | |
1746 | === modified file 'state/service_test.go' |
1747 | --- state/service_test.go 2013-09-27 19:20:20 +0000 |
1748 | +++ state/service_test.go 2013-11-15 11:35:17 +0000 |
1749 | @@ -1090,7 +1090,7 @@ |
1750 | err = s.mysql.Destroy() |
1751 | c.Assert(err, gc.IsNil) |
1752 | for _, unit := range units { |
1753 | - assertUnitLife(c, unit, state.Alive) |
1754 | + assertLife(c, unit, state.Alive) |
1755 | } |
1756 | |
1757 | // Check a cleanup doc was added. |
1758 | @@ -1103,9 +1103,9 @@ |
1759 | c.Assert(err, gc.IsNil) |
1760 | for i, unit := range units { |
1761 | if i%2 != 0 { |
1762 | - assertUnitLife(c, unit, state.Dying) |
1763 | + assertLife(c, unit, state.Dying) |
1764 | } else { |
1765 | - assertUnitRemoved(c, unit) |
1766 | + assertRemoved(c, unit) |
1767 | } |
1768 | } |
1769 | |
1770 | |
1771 | === modified file 'state/state.go' |
1772 | --- state/state.go 2013-09-27 19:20:20 +0000 |
1773 | +++ state/state.go 2013-11-15 11:35:17 +0000 |
1774 | @@ -947,63 +947,6 @@ |
1775 | return newUnit(st, &doc), nil |
1776 | } |
1777 | |
1778 | -// DestroyUnits destroys the units with the specified names. |
1779 | -func (st *State) DestroyUnits(names ...string) (err error) { |
1780 | - // TODO(rog) make this a transaction? |
1781 | - var errs []string |
1782 | - for _, name := range names { |
1783 | - unit, err := st.Unit(name) |
1784 | - switch { |
1785 | - case errors.IsNotFoundError(err): |
1786 | - err = fmt.Errorf("unit %q does not exist", name) |
1787 | - case err != nil: |
1788 | - case unit.Life() != Alive: |
1789 | - continue |
1790 | - case unit.IsPrincipal(): |
1791 | - err = unit.Destroy() |
1792 | - default: |
1793 | - err = fmt.Errorf("unit %q is a subordinate", name) |
1794 | - } |
1795 | - if err != nil { |
1796 | - errs = append(errs, err.Error()) |
1797 | - } |
1798 | - } |
1799 | - return destroyErr("units", names, errs) |
1800 | -} |
1801 | - |
1802 | -// DestroyMachines destroys the machines with the specified ids. |
1803 | -func (st *State) DestroyMachines(ids ...string) (err error) { |
1804 | - var errs []string |
1805 | - for _, id := range ids { |
1806 | - machine, err := st.Machine(id) |
1807 | - switch { |
1808 | - case errors.IsNotFoundError(err): |
1809 | - err = fmt.Errorf("machine %s does not exist", id) |
1810 | - case err != nil: |
1811 | - case machine.Life() != Alive: |
1812 | - continue |
1813 | - default: |
1814 | - err = machine.Destroy() |
1815 | - } |
1816 | - if err != nil { |
1817 | - errs = append(errs, err.Error()) |
1818 | - } |
1819 | - } |
1820 | - return destroyErr("machines", ids, errs) |
1821 | -} |
1822 | - |
1823 | -func destroyErr(desc string, ids, errs []string) error { |
1824 | - if len(errs) == 0 { |
1825 | - return nil |
1826 | - } |
1827 | - msg := "some %s were not destroyed" |
1828 | - if len(errs) == len(ids) { |
1829 | - msg = "no %s were destroyed" |
1830 | - } |
1831 | - msg = fmt.Sprintf(msg, desc) |
1832 | - return fmt.Errorf("%s: %s", msg, strings.Join(errs, "; ")) |
1833 | -} |
1834 | - |
1835 | // AssignUnit places the unit on a machine. Depending on the policy, and the |
1836 | // state of the environment, this may lead to new instances being launched |
1837 | // within the environment. |
1838 | @@ -1077,106 +1020,6 @@ |
1839 | return nil |
1840 | } |
1841 | |
1842 | -// cleanupDoc represents a potentially large set of documents that should be |
1843 | -// removed. |
1844 | -type cleanupDoc struct { |
1845 | - Id bson.ObjectId `bson:"_id"` |
1846 | - Kind string |
1847 | - Prefix string |
1848 | -} |
1849 | - |
1850 | -// newCleanupOp returns a txn.Op that creates a cleanup document with a unique |
1851 | -// id and the supplied kind and prefix. |
1852 | -func (st *State) newCleanupOp(kind, prefix string) txn.Op { |
1853 | - doc := &cleanupDoc{ |
1854 | - Id: bson.NewObjectId(), |
1855 | - Kind: kind, |
1856 | - Prefix: prefix, |
1857 | - } |
1858 | - return txn.Op{ |
1859 | - C: st.cleanups.Name, |
1860 | - Id: doc.Id, |
1861 | - Insert: doc, |
1862 | - } |
1863 | -} |
1864 | - |
1865 | -// NeedsCleanup returns true if documents previously marked for removal exist. |
1866 | -func (st *State) NeedsCleanup() (bool, error) { |
1867 | - count, err := st.cleanups.Count() |
1868 | - if err != nil { |
1869 | - return false, err |
1870 | - } |
1871 | - return count > 0, nil |
1872 | -} |
1873 | - |
1874 | -// Cleanup removes all documents that were previously marked for removal, if |
1875 | -// any such exist. It should be called periodically by at least one element |
1876 | -// of the system. |
1877 | -func (st *State) Cleanup() error { |
1878 | - doc := cleanupDoc{} |
1879 | - iter := st.cleanups.Find(nil).Iter() |
1880 | - for iter.Next(&doc) { |
1881 | - var err error |
1882 | - switch doc.Kind { |
1883 | - case "settings": |
1884 | - err = st.cleanupSettings(doc.Prefix) |
1885 | - case "units": |
1886 | - err = st.cleanupUnits(doc.Prefix) |
1887 | - default: |
1888 | - err = fmt.Errorf("unknown cleanup kind %q", doc.Kind) |
1889 | - } |
1890 | - if err != nil { |
1891 | - logger.Warningf("cleanup failed: %v", err) |
1892 | - continue |
1893 | - } |
1894 | - ops := []txn.Op{{ |
1895 | - C: st.cleanups.Name, |
1896 | - Id: doc.Id, |
1897 | - Remove: true, |
1898 | - }} |
1899 | - if err := st.runTransaction(ops); err != nil { |
1900 | - return fmt.Errorf("cannot remove empty cleanup document: %v", err) |
1901 | - } |
1902 | - } |
1903 | - if err := iter.Err(); err != nil { |
1904 | - return fmt.Errorf("cannot read cleanup document: %v", err) |
1905 | - } |
1906 | - return nil |
1907 | -} |
1908 | - |
1909 | -func (st *State) cleanupSettings(prefix string) error { |
1910 | - // Documents marked for cleanup are not otherwise referenced in the |
1911 | - // system, and will not be under watch, and are therefore safe to |
1912 | - // delete directly. |
1913 | - sel := D{{"_id", D{{"$regex", "^" + prefix}}}} |
1914 | - if count, err := st.settings.Find(sel).Count(); err != nil { |
1915 | - return fmt.Errorf("cannot detect cleanup targets: %v", err) |
1916 | - } else if count != 0 { |
1917 | - if _, err := st.settings.RemoveAll(sel); err != nil { |
1918 | - return fmt.Errorf("cannot remove documents marked for cleanup: %v", err) |
1919 | - } |
1920 | - } |
1921 | - return nil |
1922 | -} |
1923 | - |
1924 | -func (st *State) cleanupUnits(prefix string) error { |
1925 | - // This won't miss units, because a Dying service cannot have units added |
1926 | - // to it. But we do have to remove the units themselves via individual |
1927 | - // transactions, because they could be in any state at all. |
1928 | - unit := &Unit{st: st} |
1929 | - sel := D{{"_id", D{{"$regex", "^" + prefix}}}, {"life", Alive}} |
1930 | - iter := st.units.Find(sel).Iter() |
1931 | - for iter.Next(&unit.doc) { |
1932 | - if err := unit.Destroy(); err != nil { |
1933 | - return err |
1934 | - } |
1935 | - } |
1936 | - if err := iter.Err(); err != nil { |
1937 | - return fmt.Errorf("cannot read unit document: %v", err) |
1938 | - } |
1939 | - return nil |
1940 | -} |
1941 | - |
1942 | // ResumeTransactions resumes all pending transactions. |
1943 | func (st *State) ResumeTransactions() error { |
1944 | return st.runner.ResumeAll() |
1945 | |
1946 | === modified file 'state/state_test.go' |
1947 | --- state/state_test.go 2013-10-02 13:14:49 +0000 |
1948 | +++ state/state_test.go 2013-11-15 11:35:17 +0000 |
1949 | @@ -1608,39 +1608,6 @@ |
1950 | c.Assert(err, gc.IsNil) |
1951 | } |
1952 | |
1953 | -func (s *StateSuite) TestCleanup(c *gc.C) { |
1954 | - needed, err := s.State.NeedsCleanup() |
1955 | - c.Assert(err, gc.IsNil) |
1956 | - c.Assert(needed, gc.Equals, false) |
1957 | - |
1958 | - _, err = s.State.AddService("wordpress", s.AddTestingCharm(c, "wordpress")) |
1959 | - c.Assert(err, gc.IsNil) |
1960 | - _, err = s.State.AddService("mysql", s.AddTestingCharm(c, "mysql")) |
1961 | - c.Assert(err, gc.IsNil) |
1962 | - eps, err := s.State.InferEndpoints([]string{"wordpress", "mysql"}) |
1963 | - c.Assert(err, gc.IsNil) |
1964 | - relM, err := s.State.AddRelation(eps...) |
1965 | - c.Assert(err, gc.IsNil) |
1966 | - |
1967 | - needed, err = s.State.NeedsCleanup() |
1968 | - c.Assert(err, gc.IsNil) |
1969 | - c.Assert(needed, gc.Equals, false) |
1970 | - |
1971 | - err = relM.Destroy() |
1972 | - c.Assert(err, gc.IsNil) |
1973 | - |
1974 | - needed, err = s.State.NeedsCleanup() |
1975 | - c.Assert(err, gc.IsNil) |
1976 | - c.Assert(needed, gc.Equals, true) |
1977 | - |
1978 | - err = s.State.Cleanup() |
1979 | - c.Assert(err, gc.IsNil) |
1980 | - |
1981 | - needed, err = s.State.NeedsCleanup() |
1982 | - c.Assert(err, gc.IsNil) |
1983 | - c.Assert(needed, gc.Equals, false) |
1984 | -} |
1985 | - |
1986 | func (s *StateSuite) TestWatchCleanups(c *gc.C) { |
1987 | // Check initial event. |
1988 | w := s.State.WatchCleanups() |
1989 | |
1990 | === removed file 'state/statecmd/destroyunit.go' |
1991 | --- state/statecmd/destroyunit.go 2013-05-02 15:55:42 +0000 |
1992 | +++ state/statecmd/destroyunit.go 1970-01-01 00:00:00 +0000 |
1993 | @@ -1,16 +0,0 @@ |
1994 | -// Copyright 2013 Canonical Ltd. |
1995 | -// Licensed under the AGPLv3, see LICENCE file for details. |
1996 | - |
1997 | -// Code shared by the CLI and API for the DestroyServiceUnits function. |
1998 | - |
1999 | -package statecmd |
2000 | - |
2001 | -import ( |
2002 | - "launchpad.net/juju-core/state" |
2003 | - "launchpad.net/juju-core/state/api/params" |
2004 | -) |
2005 | - |
2006 | -// DestroyServiceUnits removes the specified units. |
2007 | -func DestroyServiceUnits(st *state.State, args params.DestroyServiceUnits) error { |
2008 | - return st.DestroyUnits(args.UnitNames...) |
2009 | -} |
2010 | |
2011 | === modified file 'state/unit_test.go' |
2012 | --- state/unit_test.go 2013-11-13 08:47:41 +0000 |
2013 | +++ state/unit_test.go 2013-11-15 11:35:17 +0000 |
2014 | @@ -435,49 +435,12 @@ |
2015 | c.Assert(err, gc.ErrorMatches, `unit "wordpress/0" is dead`) |
2016 | } |
2017 | |
2018 | -func (s *UnitSuite) TestDestroyPrincipalUnits(c *gc.C) { |
2019 | - preventUnitDestroyRemove(c, s.unit) |
2020 | - for i := 0; i < 4; i++ { |
2021 | - unit, err := s.service.AddUnit() |
2022 | - c.Assert(err, gc.IsNil) |
2023 | - preventUnitDestroyRemove(c, unit) |
2024 | - } |
2025 | - |
2026 | - // Destroy 2 of them; check they become Dying. |
2027 | - err := s.State.DestroyUnits("wordpress/0", "wordpress/1") |
2028 | - c.Assert(err, gc.IsNil) |
2029 | - s.assertUnitLife(c, "wordpress/0", state.Dying) |
2030 | - s.assertUnitLife(c, "wordpress/1", state.Dying) |
2031 | - |
2032 | - // Try to destroy an Alive one and a Dying one; check |
2033 | - // it destroys the Alive one and ignores the Dying one. |
2034 | - err = s.State.DestroyUnits("wordpress/2", "wordpress/0") |
2035 | - c.Assert(err, gc.IsNil) |
2036 | - s.assertUnitLife(c, "wordpress/2", state.Dying) |
2037 | - |
2038 | - // Try to destroy an Alive one along with a nonexistent one; check that |
2039 | - // the valid instruction is followed but the invalid one is warned about. |
2040 | - err = s.State.DestroyUnits("boojum/123", "wordpress/3") |
2041 | - c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "boojum/123" does not exist`) |
2042 | - s.assertUnitLife(c, "wordpress/3", state.Dying) |
2043 | - |
2044 | - // Make one Dead, and destroy an Alive one alongside it; check no errors. |
2045 | - wp0, err := s.State.Unit("wordpress/0") |
2046 | - c.Assert(err, gc.IsNil) |
2047 | - err = wp0.EnsureDead() |
2048 | - c.Assert(err, gc.IsNil) |
2049 | - err = s.State.DestroyUnits("wordpress/0", "wordpress/4") |
2050 | - c.Assert(err, gc.IsNil) |
2051 | - s.assertUnitLife(c, "wordpress/0", state.Dead) |
2052 | - s.assertUnitLife(c, "wordpress/4", state.Dying) |
2053 | -} |
2054 | - |
2055 | func (s *UnitSuite) TestDestroySetStatusRetry(c *gc.C) { |
2056 | defer state.SetRetryHooks(c, s.State, func() { |
2057 | err := s.unit.SetStatus(params.StatusStarted, "", nil) |
2058 | c.Assert(err, gc.IsNil) |
2059 | }, func() { |
2060 | - assertUnitLife(c, s.unit, state.Dying) |
2061 | + assertLife(c, s.unit, state.Dying) |
2062 | }).Check() |
2063 | err := s.unit.Destroy() |
2064 | c.Assert(err, gc.IsNil) |
2065 | @@ -488,7 +451,7 @@ |
2066 | err := s.unit.SetCharmURL(s.charm.URL()) |
2067 | c.Assert(err, gc.IsNil) |
2068 | }, func() { |
2069 | - assertUnitRemoved(c, s.unit) |
2070 | + assertRemoved(c, s.unit) |
2071 | }).Check() |
2072 | err := s.unit.Destroy() |
2073 | c.Assert(err, gc.IsNil) |
2074 | @@ -505,7 +468,7 @@ |
2075 | err := s.unit.SetCharmURL(newCharm.URL()) |
2076 | c.Assert(err, gc.IsNil) |
2077 | }, func() { |
2078 | - assertUnitRemoved(c, s.unit) |
2079 | + assertRemoved(c, s.unit) |
2080 | }).Check() |
2081 | err = s.unit.Destroy() |
2082 | c.Assert(err, gc.IsNil) |
2083 | @@ -519,7 +482,7 @@ |
2084 | err := s.unit.AssignToMachine(machine) |
2085 | c.Assert(err, gc.IsNil) |
2086 | }, func() { |
2087 | - assertUnitRemoved(c, s.unit) |
2088 | + assertRemoved(c, s.unit) |
2089 | // Also check the unit ref was properly removed from the machine doc -- |
2090 | // if it weren't, we wouldn't be able to make the machine Dead. |
2091 | err := machine.EnsureDead() |
2092 | @@ -539,7 +502,7 @@ |
2093 | err := s.unit.UnassignFromMachine() |
2094 | c.Assert(err, gc.IsNil) |
2095 | }, func() { |
2096 | - assertUnitRemoved(c, s.unit) |
2097 | + assertRemoved(c, s.unit) |
2098 | }).Check() |
2099 | err = s.unit.Destroy() |
2100 | c.Assert(err, gc.IsNil) |
2101 | @@ -550,7 +513,7 @@ |
2102 | err := s.unit.Destroy() |
2103 | c.Assert(err, gc.IsNil) |
2104 | c.Assert(s.unit.Life(), gc.Equals, state.Dying) |
2105 | - assertUnitRemoved(c, s.unit) |
2106 | + assertRemoved(c, s.unit) |
2107 | } |
2108 | |
2109 | func (s *UnitSuite) TestCannotShortCircuitDestroyWithSubordinates(c *gc.C) { |
2110 | @@ -568,7 +531,7 @@ |
2111 | err = s.unit.Destroy() |
2112 | c.Assert(err, gc.IsNil) |
2113 | c.Assert(s.unit.Life(), gc.Equals, state.Dying) |
2114 | - assertUnitLife(c, s.unit, state.Dying) |
2115 | + assertLife(c, s.unit, state.Dying) |
2116 | } |
2117 | |
2118 | func (s *UnitSuite) TestCannotShortCircuitDestroyWithStatus(c *gc.C) { |
2119 | @@ -592,7 +555,7 @@ |
2120 | err = unit.Destroy() |
2121 | c.Assert(err, gc.IsNil) |
2122 | c.Assert(unit.Life(), gc.Equals, state.Dying) |
2123 | - assertUnitLife(c, unit, state.Dying) |
2124 | + assertLife(c, unit, state.Dying) |
2125 | } |
2126 | } |
2127 | |
2128 | @@ -610,56 +573,25 @@ |
2129 | err = s.unit.Destroy() |
2130 | c.Assert(err, gc.IsNil) |
2131 | c.Assert(s.unit.Life(), gc.Equals, state.Dying) |
2132 | - assertUnitRemoved(c, s.unit) |
2133 | -} |
2134 | - |
2135 | -func (s *UnitSuite) TestDestroySubordinateUnits(c *gc.C) { |
2136 | - lgsch := s.AddTestingCharm(c, "logging") |
2137 | - _, err := s.State.AddService("logging", lgsch) |
2138 | - c.Assert(err, gc.IsNil) |
2139 | - eps, err := s.State.InferEndpoints([]string{"logging", "wordpress"}) |
2140 | - c.Assert(err, gc.IsNil) |
2141 | - rel, err := s.State.AddRelation(eps...) |
2142 | - c.Assert(err, gc.IsNil) |
2143 | - ru, err := rel.Unit(s.unit) |
2144 | - c.Assert(err, gc.IsNil) |
2145 | - err = ru.EnterScope(nil) |
2146 | - c.Assert(err, gc.IsNil) |
2147 | - |
2148 | - // Try to destroy the subordinate alone; check it fails. |
2149 | - err = s.State.DestroyUnits("logging/0") |
2150 | - c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`) |
2151 | - s.assertUnitLife(c, "logging/0", state.Alive) |
2152 | - |
2153 | - // Try to destroy the principal and the subordinate together; check it warns |
2154 | - // about the subordinate, but destroys the one it can. (The principal unit |
2155 | - // agent will be resposible for destroying the subordinate.) |
2156 | - err = s.State.DestroyUnits("wordpress/0", "logging/0") |
2157 | - c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "logging/0" is a subordinate`) |
2158 | - s.assertUnitLife(c, "wordpress/0", state.Dying) |
2159 | - s.assertUnitLife(c, "logging/0", state.Alive) |
2160 | -} |
2161 | - |
2162 | -func (s *UnitSuite) assertUnitLife(c *gc.C, name string, life state.Life) { |
2163 | - unit, err := s.State.Unit(name) |
2164 | - c.Assert(err, gc.IsNil) |
2165 | - assertUnitLife(c, unit, life) |
2166 | -} |
2167 | - |
2168 | -func assertUnitLife(c *gc.C, unit *state.Unit, life state.Life) { |
2169 | - c.Assert(unit.Refresh(), gc.IsNil) |
2170 | - c.Assert(unit.Life(), gc.Equals, life) |
2171 | -} |
2172 | - |
2173 | -func assertUnitRemoved(c *gc.C, unit *state.Unit) { |
2174 | - err := unit.Refresh() |
2175 | + assertRemoved(c, s.unit) |
2176 | +} |
2177 | + |
2178 | +func assertLife(c *gc.C, entity state.Living, life state.Life) { |
2179 | + c.Assert(entity.Refresh(), gc.IsNil) |
2180 | + c.Assert(entity.Life(), gc.Equals, life) |
2181 | +} |
2182 | + |
2183 | +func assertRemoved(c *gc.C, entity state.Living) { |
2184 | + err := entity.Refresh() |
2185 | c.Assert(err, jc.Satisfies, errors.IsNotFoundError) |
2186 | - err = unit.Destroy() |
2187 | - c.Assert(err, gc.IsNil) |
2188 | - err = unit.EnsureDead() |
2189 | - c.Assert(err, gc.IsNil) |
2190 | - err = unit.Remove() |
2191 | - c.Assert(err, gc.IsNil) |
2192 | + err = entity.Destroy() |
2193 | + c.Assert(err, gc.IsNil) |
2194 | + if entity, ok := entity.(state.AgentLiving); ok { |
2195 | + err = entity.EnsureDead() |
2196 | + c.Assert(err, gc.IsNil) |
2197 | + err = entity.Remove() |
2198 | + c.Assert(err, gc.IsNil) |
2199 | + } |
2200 | } |
2201 | |
2202 | func (s *UnitSuite) TestTag(c *gc.C) { |
Reviewers: mp+195088_ code.launchpad. net,
Message:
Please take a look.
Description:
fix lp:1089289 for 1.16
involves trunk revisions:
2015, 2025 for destroy-machine and destroy-unit over the API machine in state machine in api
2045 for force-destroy-
2050, 2051 for force-destroy-
...and a tweak to cmd/jujud/ machine_ test.go to revert functionality in
r2045
that depends on post-1.16 address-handling code.
https:/ /code.launchpad .net/~fwereade/ juju-core/ fix-1089289- for-1.16/ +merge/ 195088
Requires: /code.launchpad .net/~fwereade/ juju-core/ fix-1233457- for-1.16/ +merge/ 195003
https:/
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/26100043/
Affected files (+905, -483 lines): destroymachine. go destroymachine_ test.go destroyunit. go machine_ test.go params/ params. go /client/ client. go /client/ client_ test.go /client/ perm_test. go test.go test.go nit_test. go test.go destroyunit. go
A [revision details]
M cmd/juju/
M cmd/juju/
M cmd/juju/
M cmd/jujud/
M state/api/client.go
M state/api/
M state/apiserver
M state/apiserver
M state/apiserver
A state/cleanup.go
A state/cleanup_
M state/life.go
M state/life_test.go
M state/machine.go
M state/machine_
M state/relationu
M state/service_
M state/state.go
M state/state_test.go
D state/statecmd/
M state/unit_test.go