Merge lp:~fwereade/juju-core/fix-1089289-for-1.16 into lp:juju-core/1.16

Proposed by William Reade
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
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-machine in state
2050, 2051 for force-destroy-machine in api

...and a tweak to cmd/jujud/machine_test.go to revert functionality in r2045
that depends on post-1.16 address-handling code.

https://codereview.appspot.com/26100043/

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-machine in state
2050, 2051 for force-destroy-machine in api

...and a tweak to cmd/jujud/machine_test.go to revert functionality in r2045
that depends on post-1.16 address-handling code.

https://codereview.appspot.com/26100043/

To post a comment you must log in.
Revision history for this message
William Reade (fwereade) wrote :

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
2045 for force-destroy-machine in state
2050, 2051 for force-destroy-machine in api

...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:
https://code.launchpad.net/~fwereade/juju-core/fix-1233457-for-1.16/+merge/195003

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/26100043/

Affected files (+905, -483 lines):
   A [revision details]
   M cmd/juju/destroymachine.go
   M cmd/juju/destroymachine_test.go
   M cmd/juju/destroyunit.go
   M cmd/jujud/machine_test.go
   M state/api/client.go
   M state/api/params/params.go
   M state/apiserver/client/client.go
   M state/apiserver/client/client_test.go
   M state/apiserver/client/perm_test.go
   A state/cleanup.go
   A state/cleanup_test.go
   M state/life.go
   M state/life_test.go
   M state/machine.go
   M state/machine_test.go
   M state/relationunit_test.go
   M state/service_test.go
   M state/state.go
   M state/state_test.go
   D state/statecmd/destroyunit.go
   M state/unit_test.go

Revision history for this message
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.

https://codereview.appspot.com/26100043/

Revision history for this message
Go Bot (go-bot) wrote :
Download full text (6.8 KiB)

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.net/juju-core/agent 0.261s
ok launchpad.net/juju-core/agent/tools 0.265s
ok launchpad.net/juju-core/bzr 6.595s
ok launchpad.net/juju-core/cert 3.998s
ok launchpad.net/juju-core/charm 0.544s
? launchpad.net/juju-core/charm/hooks [no test files]
ok launchpad.net/juju-core/cloudinit 0.026s
ok launchpad.net/juju-core/cmd 0.201s
? launchpad.net/juju-core/cmd/charmd [no test files]
? launchpad.net/juju-core/cmd/charmload [no test files]
FAIL launchpad.net/juju-core/cmd/juju [build failed]
ok launchpad.net/juju-core/cmd/jujud 47.906s
ok launchpad.net/juju-core/cmd/plugins/juju-metadata 2.375s
ok launchpad.net/juju-core/constraints 0.028s
ok launchpad.net/juju-core/container/lxc 0.378s
? launchpad.net/juju-core/container/lxc/mock [no test files]
ok launchpad.net/juju-core/downloader 5.282s
ok launchpad.net/juju-core/environs 3.357s
ok launchpad.net/juju-core/environs/bootstrap 5.141s
ok launchpad.net/juju-core/environs/cloudinit 0.543s
ok launchpad.net/juju-core/environs/config 0.745s
ok launchpad.net/juju-core/environs/configstore 0.041s
ok launchpad.net/juju-core/environs/filestorage 0.030s
ok launchpad.net/juju-core/environs/httpstorage 0.950s
ok launchpad.net/juju-core/environs/imagemetadata 0.507s
ok launchpad.net/juju-core/environs/instances 0.052s
ok launchpad.net/juju-core/environs/jujutest 0.225s
ok launchpad.net/juju-core/environs/manual 4.287s
ok launchpad.net/juju-core/environs/simplestreams 0.331s
? launchpad.net/juju-core/environs/simplestreams/testing [no test files]
ok launchpad.net/juju-core/environs/sshstorage 1.265s
ok launchpad.net/juju-core/environs/storage 1.189s
ok launchpad.net/juju-core/environs/sync 37.033s
ok launchpad.net/juju-core/environs/testing 0.211s
ok launchpad.net/juju-core/environs/tools 6.799s
? launchpad.net/juju-core/environs/tools/testing [no test files]
ok launchpad.net/juju-core/errors 0.015s
ok launchpad.net/juju-core/instance 0.022s
? launchpad.net/juju-core/instance/testing [no test files]
ok launchpad.net/juju-core/juju 17.913s
ok launchpad.net/juju-core/juju/osenv 0.018s
? launchpad.net/juju-core/juju/testing [no test files]
ok launchpad.net/juju-core/log 0.015s
ok launchpad.net/juju-core/log/syslog 0.029s
ok launchpad.net/juju-core/names 0.026s
? launchpad.net/juju-core/provider [no test files]
? launchpad.net/juju-core/provider/all [no test files]
ok launchpad.net/juju-core/provider/azure 6.210s
ok launchpad.net/juju-core/provider/common 0.329s
ok launchpad.net/juju-core/provider/dummy 21.076s
ok launchpad.net/juju-core/provider/ec2 5.488s
ok launchpad.net/juju-core/provider/ec2/httpstorage 0.188s
ok launchpad.net/juju-core/provider/local 2.189s
ok launchpad.net/juju-core/provider/maas 9.946s
ok launchpad.net/juju-core/provider/null 1.200s
ok launchpad.net/juju-core/provider/openstack 13.454s
ok launchpad.net/juju-core/rpc 0.081s
ok launchpad.net/juju-core/rpc/jsoncodec 0.027s
? launchpad.net/juju-core/rpc/rpcreflect [no test ...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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(&params)
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) {

Subscribers

People subscribed via source and target branches

to all changes: