Merge lp:~fwereade/juju-core/api-force-destroy-machines into lp:~go-bot/juju-core/trunk
- api-force-destroy-machines
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | William Reade | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 2050 | ||||
Proposed branch: | lp:~fwereade/juju-core/api-force-destroy-machines | ||||
Merge into: | lp:~go-bot/juju-core/trunk | ||||
Diff against target: |
1119 lines (+369/-287) 17 files modified
cmd/juju/destroymachine.go (+18/-1) cmd/juju/destroymachine_test.go (+51/-14) cmd/juju/destroyunit.go (+1/-1) cmd/jujud/machine_test.go (+1/-1) state/api/client.go (+7/-1) state/api/params/params.go (+1/-0) state/apiserver/client/client.go (+52/-2) state/apiserver/client/client_test.go (+136/-31) state/apiserver/client/perm_test.go (+1/-1) state/cleanup_test.go (+26/-8) state/life.go (+8/-1) state/life_test.go (+5/-15) state/machine.go (+20/-3) state/machine_test.go (+13/-44) state/service_test.go (+3/-3) state/state.go (+0/-67) state/unit_test.go (+26/-94) |
||||
To merge this branch: | bzr merge lp:~fwereade/juju-core/api-force-destroy-machines | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+194764@code.launchpad.net |
Commit message
fix lp:1089291
...which involved a lot more churn than I wanted, mainly because those
DestroyWhatevers methods really shouldn't ever have been on state in the
first place, and moving them turned out to be a ridiculous hassle.
Despite the mess, this branch really just involves:
1) moving multi-Destroy methods off state and onto the api
2) fixing/
3) hooking up a --force flag to destroy-machine
4) oh, and making the relevant api.Client methods a teeny tiny bit
consistent.
Description of the change
fix lp:1089291
...which involved a lot more churn than I wanted, mainly because those
DestroyWhatevers methods really shouldn't ever have been on state in the
first place, and moving them turned out to be a ridiculous hassle.
Despite the mess, this branch really just involves:
1) moving multi-Destroy methods off state and onto the api
2) fixing/
3) hooking up a --force flag to destroy-machine
4) oh, and making the relevant api.Client methods a teeny tiny bit
consistent.
William Reade (fwereade) wrote : | # |
Ian Booth (wallyworld) wrote : | # |
Some nice refactoring. It does make me a little sad that we are putting
service methods (ie verbs like Destroy) on domain objects. IMO we want
to be heading towards a SOA and domain objects should only be
responsible for their own internal state, not interacting with services
and jobs etc. So it is great that the methods have come off State but
putting them on Machine is wrong to me.
My other main issue is that forcibly destroying a machine leaves its
life as Alive. That is very confusing to me.
https:/
File cmd/juju/
https:/
cmd/juju/
"completely remove machine and all dependencies")
Perhaps expand a bit:
"completely remove machine and all dependencies, even if Juju thinks the
machine has running units or containers"
I think we need to be verbose as to what the implications are. I would
even go so far as to say we need a [y/N] prompt like for
destroy-environment as a typo in the machine id could be catastrophic.
https:/
File cmd/juju/
https:/
cmd/juju/
The above comment needs rewording now the test has been split up
https:/
File state/api/client.go (right):
https:/
state/api/
...string) error {
It makes me sad that we need to add a whole new endpoint because Go
doesn't support default parameter values :-(
https:/
File state/apiserver
https:/
state/apiserver
state.Alive)
Why doesn't force destroy cause the life to be set to Dying? I find it
particularly unintuitive that a status would show the machine as alive
when in fact it is on death row and waiting for the clean up job to
remove it.
https:/
File state/cleanup_
https:/
state/cleanup_
TestForceDestro
The code in this test can definitely be extracted out into a helper
func (s *CleanupSuite) assertForceDest
state.MachineJob) {
m, err := s.State.
c.Assert(err, gc.IsNil)
err = environManager.
c.Assert(err, gc.ErrorMatches, "machine [0-9] is required by the
environment")
s.
assertLife(c, m, state.Alive)
}
https:/
File state/machin...
John A Meinel (jameinel) wrote : | # |
just some of my thoughts, not a full review.
https:/
File state/api/client.go (right):
https:/
state/api/
...string) error {
On 2013/11/12 00:43:42, wallyworld wrote:
> It makes me sad that we need to add a whole new endpoint because Go
doesn't
> support default parameter values :-(
It isn't really an endpoint, but it is a new function.
I only make the distinction because I was about to say the next change:
-func (c *Client) DestroyServiceU
+func (c *Client) DestroyServiceU
was an API break, but this is all just within the client process.
so we *could* spell it:
DestroyMachines
I'm guessing it was a bit ackward and William wanted to avoid bool in
APIs especially for a Force sort of function.
https:/
File state/apiserver
https:/
state/apiserver
state.Alive)
On 2013/11/12 00:43:42, wallyworld wrote:
> Why doesn't force destroy cause the life to be set to Dying? I find it
> particularly unintuitive that a status would show the machine as alive
when in
> fact it is on death row and waiting for the clean up job to remove it.
In earlier discussions, this is a limitation of the Machine lifecycle
model. A Machine can't be set to dying until all Units are no longer on
the machine.
William Reade (fwereade) wrote : | # |
Machine lifecycle is out of scope; SOA concepts demand more discussion
in a separate forum; I think I've addressed the rest.
https:/
File cmd/juju/
https:/
cmd/juju/
"completely remove machine and all dependencies")
On 2013/11/12 00:43:42, wallyworld wrote:
> Perhaps expand a bit:
> "completely remove machine and all dependencies, even if Juju thinks
the machine
> has running units or containers"
> I think we need to be verbose as to what the implications are. I would
even go
> so far as to say we need a [y/N] prompt like for destroy-environment
as a typo
> in the machine id could be catastrophic.
Expanded the doc string -- I'm kinda -1 on an additional gate beyond
specifying "--force" though.
https:/
File cmd/juju/
https:/
cmd/juju/
On 2013/11/12 00:43:42, wallyworld wrote:
> The above comment needs rewording now the test has been split up
Whoops, thanks.
https:/
File state/api/client.go (right):
https:/
state/api/
...string) error {
On 2013/11/12 02:58:37, jameinel wrote:
> On 2013/11/12 00:43:42, wallyworld wrote:
> > It makes me sad that we need to add a whole new endpoint because Go
doesn't
> > support default parameter values :-(
> It isn't really an endpoint, but it is a new function.
> I only make the distinction because I was about to say the next
change:
> -func (c *Client) DestroyServiceU
> +func (c *Client) DestroyServiceU
> was an API break, but this is all just within the client process.
> so we *could* spell it:
> DestroyMachines
> I'm guessing it was a bit ackward and William wanted to avoid bool in
APIs
> especially for a Force sort of function.
Yeah, it's just a new function, and the bool option felt horrible. Re
DestroyServiceUnits I'm willing to impose that minimal pain on anyone
who's using api.Client in an external library, because I think the
global pain of inconsistency is worse.
https:/
File state/apiserver
https:/
state/apiserver
state.Alive)
On 2013/11/12 02:58:37, jameinel wrote:
> On 2013/11/12 00:43:42, wallyworld wrote:
> > Why doesn't force destroy cause the life to be set to Dying? I find
it
> > particularly unintuitive that a status would show the machine as
alive when in
> > fact it is on death row and waiting for the clean ...
William Reade (fwereade) wrote : | # |
Please take a look.
Ian Booth (wallyworld) wrote : | # |
John A Meinel (jameinel) wrote : | # |
LGTM as well
Preview Diff
1 | === modified file 'cmd/juju/destroymachine.go' | |||
2 | --- cmd/juju/destroymachine.go 2013-11-01 06:53:54 +0000 | |||
3 | +++ cmd/juju/destroymachine.go 2013-11-12 10:22:12 +0000 | |||
4 | @@ -6,6 +6,8 @@ | |||
5 | 6 | import ( | 6 | import ( |
6 | 7 | "fmt" | 7 | "fmt" |
7 | 8 | 8 | ||
8 | 9 | "launchpad.net/gnuflag" | ||
9 | 10 | |||
10 | 9 | "launchpad.net/juju-core/cmd" | 11 | "launchpad.net/juju-core/cmd" |
11 | 10 | "launchpad.net/juju-core/juju" | 12 | "launchpad.net/juju-core/juju" |
12 | 11 | "launchpad.net/juju-core/names" | 13 | "launchpad.net/juju-core/names" |
13 | @@ -15,18 +17,30 @@ | |||
14 | 15 | type DestroyMachineCommand struct { | 17 | type DestroyMachineCommand struct { |
15 | 16 | cmd.EnvCommandBase | 18 | cmd.EnvCommandBase |
16 | 17 | MachineIds []string | 19 | MachineIds []string |
17 | 20 | Force bool | ||
18 | 18 | } | 21 | } |
19 | 19 | 22 | ||
20 | 23 | const destroyMachineDoc = ` | ||
21 | 24 | Machines that are responsible for the environment cannot be destroyed. Machines | ||
22 | 25 | running units or containers can only be destroyed with the --force flag; doing | ||
23 | 26 | so will also destroy all those units and containers without giving them any | ||
24 | 27 | opportunity to shut down cleanly. | ||
25 | 28 | ` | ||
26 | 29 | |||
27 | 20 | func (c *DestroyMachineCommand) Info() *cmd.Info { | 30 | func (c *DestroyMachineCommand) Info() *cmd.Info { |
28 | 21 | return &cmd.Info{ | 31 | return &cmd.Info{ |
29 | 22 | Name: "destroy-machine", | 32 | Name: "destroy-machine", |
30 | 23 | Args: "<machine> ...", | 33 | Args: "<machine> ...", |
31 | 24 | Purpose: "destroy machines", | 34 | Purpose: "destroy machines", |
33 | 25 | Doc: "Machines that have assigned units, or are responsible for the environment, cannot be destroyed.", | 35 | Doc: "", |
34 | 26 | Aliases: []string{"terminate-machine"}, | 36 | Aliases: []string{"terminate-machine"}, |
35 | 27 | } | 37 | } |
36 | 28 | } | 38 | } |
37 | 29 | 39 | ||
38 | 40 | func (c *DestroyMachineCommand) SetFlags(f *gnuflag.FlagSet) { | ||
39 | 41 | f.BoolVar(&c.Force, "force", false, "completely remove machine and all dependencies") | ||
40 | 42 | } | ||
41 | 43 | |||
42 | 30 | func (c *DestroyMachineCommand) Init(args []string) error { | 44 | func (c *DestroyMachineCommand) Init(args []string) error { |
43 | 31 | if len(args) == 0 { | 45 | if len(args) == 0 { |
44 | 32 | return fmt.Errorf("no machines specified") | 46 | return fmt.Errorf("no machines specified") |
45 | @@ -46,5 +60,8 @@ | |||
46 | 46 | return err | 60 | return err |
47 | 47 | } | 61 | } |
48 | 48 | defer apiclient.Close() | 62 | defer apiclient.Close() |
49 | 63 | if c.Force { | ||
50 | 64 | return apiclient.ForceDestroyMachines(c.MachineIds...) | ||
51 | 65 | } | ||
52 | 49 | return apiclient.DestroyMachines(c.MachineIds...) | 66 | return apiclient.DestroyMachines(c.MachineIds...) |
53 | 50 | } | 67 | } |
54 | 51 | 68 | ||
55 | === modified file 'cmd/juju/destroymachine_test.go' | |||
56 | --- cmd/juju/destroymachine_test.go 2013-09-27 07:05:45 +0000 | |||
57 | +++ cmd/juju/destroymachine_test.go 2013-11-12 10:22:12 +0000 | |||
58 | @@ -6,9 +6,11 @@ | |||
59 | 6 | import ( | 6 | import ( |
60 | 7 | gc "launchpad.net/gocheck" | 7 | gc "launchpad.net/gocheck" |
61 | 8 | 8 | ||
62 | 9 | "launchpad.net/juju-core/errors" | ||
63 | 9 | jujutesting "launchpad.net/juju-core/juju/testing" | 10 | jujutesting "launchpad.net/juju-core/juju/testing" |
64 | 10 | "launchpad.net/juju-core/state" | 11 | "launchpad.net/juju-core/state" |
65 | 11 | "launchpad.net/juju-core/testing" | 12 | "launchpad.net/juju-core/testing" |
66 | 13 | jc "launchpad.net/juju-core/testing/checkers" | ||
67 | 12 | ) | 14 | ) |
68 | 13 | 15 | ||
69 | 14 | type DestroyMachineSuite struct { | 16 | type DestroyMachineSuite struct { |
70 | @@ -22,7 +24,7 @@ | |||
71 | 22 | return err | 24 | return err |
72 | 23 | } | 25 | } |
73 | 24 | 26 | ||
75 | 25 | func (s *DestroyMachineSuite) TestDestroyMachine(c *gc.C) { | 27 | func (s *DestroyMachineSuite) TestDestroyMachineWithUnit(c *gc.C) { |
76 | 26 | // Create a machine running a unit. | 28 | // Create a machine running a unit. |
77 | 27 | testing.Charms.BundlePath(s.SeriesPath, "riak") | 29 | testing.Charms.BundlePath(s.SeriesPath, "riak") |
78 | 28 | err := runDeploy(c, "local:riak", "riak") | 30 | err := runDeploy(c, "local:riak", "riak") |
79 | @@ -38,19 +40,16 @@ | |||
80 | 38 | // Try to destroy the machine and fail. | 40 | // Try to destroy the machine and fail. |
81 | 39 | err = runDestroyMachine(c, "0") | 41 | err = runDestroyMachine(c, "0") |
82 | 40 | c.Assert(err, gc.ErrorMatches, `no machines were destroyed: machine 0 has unit "riak/0" assigned`) | 42 | c.Assert(err, gc.ErrorMatches, `no machines were destroyed: machine 0 has unit "riak/0" assigned`) |
83 | 43 | } | ||
84 | 41 | 44 | ||
93 | 42 | // Remove the unit, and try to destroy the machine along with another that | 45 | func (s *DestroyMachineSuite) TestDestroyEmptyMachine(c *gc.C) { |
94 | 43 | // doesn't exist; check that the machine is destroyed, but the missing one | 46 | // Destroy an empty machine alongside a state server; only the empty machine |
95 | 44 | // is warned about. | 47 | // gets destroyed. |
96 | 45 | err = u.Destroy() | 48 | m0, err := s.State.AddMachine("quantal", state.JobHostUnits) |
89 | 46 | c.Assert(err, gc.IsNil) | ||
90 | 47 | err = u.EnsureDead() | ||
91 | 48 | c.Assert(err, gc.IsNil) | ||
92 | 49 | err = u.Remove() | ||
97 | 50 | c.Assert(err, gc.IsNil) | 49 | c.Assert(err, gc.IsNil) |
98 | 51 | err = runDestroyMachine(c, "0", "1") | 50 | err = runDestroyMachine(c, "0", "1") |
99 | 52 | c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 1 does not exist`) | 51 | c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 1 does not exist`) |
101 | 53 | m0, err := s.State.Machine("0") | 52 | err = m0.Refresh() |
102 | 54 | c.Assert(err, gc.IsNil) | 53 | c.Assert(err, gc.IsNil) |
103 | 55 | c.Assert(m0.Life(), gc.Equals, state.Dying) | 54 | c.Assert(m0.Life(), gc.Equals, state.Dying) |
104 | 56 | 55 | ||
105 | @@ -60,9 +59,13 @@ | |||
106 | 60 | err = m0.Refresh() | 59 | err = m0.Refresh() |
107 | 61 | c.Assert(err, gc.IsNil) | 60 | c.Assert(err, gc.IsNil) |
108 | 62 | c.Assert(m0.Life(), gc.Equals, state.Dying) | 61 | c.Assert(m0.Life(), gc.Equals, state.Dying) |
109 | 62 | } | ||
110 | 63 | 63 | ||
113 | 64 | // As is destroying a Dead machine; destroying it alongside a JobManageEnviron | 64 | func (s *DestroyMachineSuite) TestDestroyDeadMachine(c *gc.C) { |
114 | 65 | // machine complains only about the JMA machine. | 65 | // Destroying a Dead machine is a no-op; destroying it alongside a JobManageEnviron |
115 | 66 | // machine complains only about the JME machine. | ||
116 | 67 | m0, err := s.State.AddMachine("quantal", state.JobHostUnits) | ||
117 | 68 | c.Assert(err, gc.IsNil) | ||
118 | 66 | err = m0.EnsureDead() | 69 | err = m0.EnsureDead() |
119 | 67 | c.Assert(err, gc.IsNil) | 70 | c.Assert(err, gc.IsNil) |
120 | 68 | m1, err := s.State.AddMachine("quantal", state.JobManageEnviron) | 71 | m1, err := s.State.AddMachine("quantal", state.JobManageEnviron) |
121 | @@ -75,9 +78,43 @@ | |||
122 | 75 | err = m1.Refresh() | 78 | err = m1.Refresh() |
123 | 76 | c.Assert(err, gc.IsNil) | 79 | c.Assert(err, gc.IsNil) |
124 | 77 | c.Assert(m1.Life(), gc.Equals, state.Alive) | 80 | c.Assert(m1.Life(), gc.Equals, state.Alive) |
126 | 78 | 81 | } | |
127 | 82 | |||
128 | 83 | func (s *DestroyMachineSuite) TestForce(c *gc.C) { | ||
129 | 84 | // Create a machine running a unit. | ||
130 | 85 | testing.Charms.BundlePath(s.SeriesPath, "riak") | ||
131 | 86 | err := runDeploy(c, "local:riak", "riak") | ||
132 | 87 | c.Assert(err, gc.IsNil) | ||
133 | 88 | |||
134 | 89 | // Get the state entities to allow sane testing. | ||
135 | 90 | u, err := s.State.Unit("riak/0") | ||
136 | 91 | c.Assert(err, gc.IsNil) | ||
137 | 92 | m0, err := s.State.Machine("0") | ||
138 | 93 | c.Assert(err, gc.IsNil) | ||
139 | 94 | |||
140 | 95 | // Create a manager machine. | ||
141 | 96 | m1, err := s.State.AddMachine("quantal", state.JobManageEnviron) | ||
142 | 97 | c.Assert(err, gc.IsNil) | ||
143 | 98 | |||
144 | 99 | // Try to force-destroy the machines. | ||
145 | 100 | err = runDestroyMachine(c, "0", "1", "--force") | ||
146 | 101 | c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 1 is required by the environment`) | ||
147 | 102 | |||
148 | 103 | // Clean up, check state. | ||
149 | 104 | err = s.State.Cleanup() | ||
150 | 105 | c.Assert(err, gc.IsNil) | ||
151 | 106 | err = m0.Refresh() | ||
152 | 107 | c.Assert(err, jc.Satisfies, errors.IsNotFoundError) | ||
153 | 108 | err = u.Refresh() | ||
154 | 109 | c.Assert(err, jc.Satisfies, errors.IsNotFoundError) | ||
155 | 110 | err = m1.Refresh() | ||
156 | 111 | c.Assert(err, gc.IsNil) | ||
157 | 112 | c.Assert(m1.Life(), gc.Equals, state.Alive) | ||
158 | 113 | } | ||
159 | 114 | |||
160 | 115 | func (s *DestroyMachineSuite) TestBadArgs(c *gc.C) { | ||
161 | 79 | // Check invalid args. | 116 | // Check invalid args. |
163 | 80 | err = runDestroyMachine(c) | 117 | err := runDestroyMachine(c) |
164 | 81 | c.Assert(err, gc.ErrorMatches, `no machines specified`) | 118 | c.Assert(err, gc.ErrorMatches, `no machines specified`) |
165 | 82 | err = runDestroyMachine(c, "1", "2", "nonsense", "rubbish") | 119 | err = runDestroyMachine(c, "1", "2", "nonsense", "rubbish") |
166 | 83 | c.Assert(err, gc.ErrorMatches, `invalid machine id "nonsense"`) | 120 | c.Assert(err, gc.ErrorMatches, `invalid machine id "nonsense"`) |
167 | 84 | 121 | ||
168 | === modified file 'cmd/juju/destroyunit.go' | |||
169 | --- cmd/juju/destroyunit.go 2013-11-05 01:22:35 +0000 | |||
170 | +++ cmd/juju/destroyunit.go 2013-11-12 10:22:12 +0000 | |||
171 | @@ -48,5 +48,5 @@ | |||
172 | 48 | return err | 48 | return err |
173 | 49 | } | 49 | } |
174 | 50 | defer client.Close() | 50 | defer client.Close() |
176 | 51 | return client.DestroyServiceUnits(c.UnitNames) | 51 | return client.DestroyServiceUnits(c.UnitNames...) |
177 | 52 | } | 52 | } |
178 | 53 | 53 | ||
179 | === modified file 'cmd/jujud/machine_test.go' | |||
180 | --- cmd/jujud/machine_test.go 2013-11-11 10:57:06 +0000 | |||
181 | +++ cmd/jujud/machine_test.go 2013-11-12 10:22:12 +0000 | |||
182 | @@ -78,7 +78,7 @@ | |||
183 | 78 | err = m.SetPassword(initialMachinePassword) | 78 | err = m.SetPassword(initialMachinePassword) |
184 | 79 | c.Assert(err, gc.IsNil) | 79 | c.Assert(err, gc.IsNil) |
185 | 80 | tag := names.MachineTag(m.Id()) | 80 | tag := names.MachineTag(m.Id()) |
187 | 81 | if m.IsStateServer() { | 81 | if m.IsManager() { |
188 | 82 | err = m.SetMongoPassword(initialMachinePassword) | 82 | err = m.SetMongoPassword(initialMachinePassword) |
189 | 83 | c.Assert(err, gc.IsNil) | 83 | c.Assert(err, gc.IsNil) |
190 | 84 | config, tools = s.agentSuite.primeStateAgent(c, tag, initialMachinePassword) | 84 | config, tools = s.agentSuite.primeStateAgent(c, tag, initialMachinePassword) |
191 | 85 | 85 | ||
192 | === modified file 'state/api/client.go' | |||
193 | --- state/api/client.go 2013-11-08 06:36:55 +0000 | |||
194 | +++ state/api/client.go 2013-11-12 10:22:12 +0000 | |||
195 | @@ -150,6 +150,12 @@ | |||
196 | 150 | return c.st.Call("Client", "", "DestroyMachines", params, nil) | 150 | return c.st.Call("Client", "", "DestroyMachines", params, nil) |
197 | 151 | } | 151 | } |
198 | 152 | 152 | ||
199 | 153 | // ForceDestroyMachines removes a given set of machines and all associated units. | ||
200 | 154 | func (c *Client) ForceDestroyMachines(machines ...string) error { | ||
201 | 155 | params := params.DestroyMachines{Force: true, MachineNames: machines} | ||
202 | 156 | return c.st.Call("Client", "", "DestroyMachines", params, nil) | ||
203 | 157 | } | ||
204 | 158 | |||
205 | 153 | // ServiceExpose changes the juju-managed firewall to expose any ports that | 159 | // ServiceExpose changes the juju-managed firewall to expose any ports that |
206 | 154 | // were also explicitly marked by units as open. | 160 | // were also explicitly marked by units as open. |
207 | 155 | func (c *Client) ServiceExpose(service string) error { | 161 | func (c *Client) ServiceExpose(service string) error { |
208 | @@ -207,7 +213,7 @@ | |||
209 | 207 | } | 213 | } |
210 | 208 | 214 | ||
211 | 209 | // DestroyServiceUnits decreases the number of units dedicated to a service. | 215 | // DestroyServiceUnits decreases the number of units dedicated to a service. |
213 | 210 | func (c *Client) DestroyServiceUnits(unitNames []string) error { | 216 | func (c *Client) DestroyServiceUnits(unitNames ...string) error { |
214 | 211 | params := params.DestroyServiceUnits{unitNames} | 217 | params := params.DestroyServiceUnits{unitNames} |
215 | 212 | return c.st.Call("Client", "", "DestroyServiceUnits", params, nil) | 218 | return c.st.Call("Client", "", "DestroyServiceUnits", params, nil) |
216 | 213 | } | 219 | } |
217 | 214 | 220 | ||
218 | === modified file 'state/api/params/params.go' | |||
219 | --- state/api/params/params.go 2013-11-08 06:36:55 +0000 | |||
220 | +++ state/api/params/params.go 2013-11-12 10:22:12 +0000 | |||
221 | @@ -93,6 +93,7 @@ | |||
222 | 93 | // DestroyMachines holds parameters for the DestroyMachines call. | 93 | // DestroyMachines holds parameters for the DestroyMachines call. |
223 | 94 | type DestroyMachines struct { | 94 | type DestroyMachines struct { |
224 | 95 | MachineNames []string | 95 | MachineNames []string |
225 | 96 | Force bool | ||
226 | 96 | } | 97 | } |
227 | 97 | 98 | ||
228 | 98 | // ServiceDeploy holds the parameters for making the ServiceDeploy call. | 99 | // ServiceDeploy holds the parameters for making the ServiceDeploy call. |
229 | 99 | 100 | ||
230 | === modified file 'state/apiserver/client/client.go' | |||
231 | --- state/apiserver/client/client.go 2013-11-08 06:36:55 +0000 | |||
232 | +++ state/apiserver/client/client.go 2013-11-12 10:22:12 +0000 | |||
233 | @@ -6,11 +6,13 @@ | |||
234 | 6 | import ( | 6 | import ( |
235 | 7 | "errors" | 7 | "errors" |
236 | 8 | "fmt" | 8 | "fmt" |
237 | 9 | "strings" | ||
238 | 9 | 10 | ||
239 | 10 | "launchpad.net/loggo" | 11 | "launchpad.net/loggo" |
240 | 11 | 12 | ||
241 | 12 | "launchpad.net/juju-core/charm" | 13 | "launchpad.net/juju-core/charm" |
242 | 13 | "launchpad.net/juju-core/environs" | 14 | "launchpad.net/juju-core/environs" |
243 | 15 | coreerrors "launchpad.net/juju-core/errors" | ||
244 | 14 | "launchpad.net/juju-core/instance" | 16 | "launchpad.net/juju-core/instance" |
245 | 15 | "launchpad.net/juju-core/juju" | 17 | "launchpad.net/juju-core/juju" |
246 | 16 | "launchpad.net/juju-core/names" | 18 | "launchpad.net/juju-core/names" |
247 | @@ -413,7 +415,25 @@ | |||
248 | 413 | 415 | ||
249 | 414 | // DestroyServiceUnits removes a given set of service units. | 416 | // DestroyServiceUnits removes a given set of service units. |
250 | 415 | func (c *Client) DestroyServiceUnits(args params.DestroyServiceUnits) error { | 417 | func (c *Client) DestroyServiceUnits(args params.DestroyServiceUnits) error { |
252 | 416 | return c.api.state.DestroyUnits(args.UnitNames...) | 418 | var errs []string |
253 | 419 | for _, name := range args.UnitNames { | ||
254 | 420 | unit, err := c.api.state.Unit(name) | ||
255 | 421 | switch { | ||
256 | 422 | case coreerrors.IsNotFoundError(err): | ||
257 | 423 | err = fmt.Errorf("unit %q does not exist", name) | ||
258 | 424 | case err != nil: | ||
259 | 425 | case unit.Life() != state.Alive: | ||
260 | 426 | continue | ||
261 | 427 | case unit.IsPrincipal(): | ||
262 | 428 | err = unit.Destroy() | ||
263 | 429 | default: | ||
264 | 430 | err = fmt.Errorf("unit %q is a subordinate", name) | ||
265 | 431 | } | ||
266 | 432 | if err != nil { | ||
267 | 433 | errs = append(errs, err.Error()) | ||
268 | 434 | } | ||
269 | 435 | } | ||
270 | 436 | return destroyErr("units", args.UnitNames, errs) | ||
271 | 417 | } | 437 | } |
272 | 418 | 438 | ||
273 | 419 | // ServiceDestroy destroys a given service. | 439 | // ServiceDestroy destroys a given service. |
274 | @@ -625,7 +645,25 @@ | |||
275 | 625 | 645 | ||
276 | 626 | // DestroyMachines removes a given set of machines. | 646 | // DestroyMachines removes a given set of machines. |
277 | 627 | func (c *Client) DestroyMachines(args params.DestroyMachines) error { | 647 | func (c *Client) DestroyMachines(args params.DestroyMachines) error { |
279 | 628 | return c.api.state.DestroyMachines(args.MachineNames...) | 648 | var errs []string |
280 | 649 | for _, id := range args.MachineNames { | ||
281 | 650 | machine, err := c.api.state.Machine(id) | ||
282 | 651 | switch { | ||
283 | 652 | case coreerrors.IsNotFoundError(err): | ||
284 | 653 | err = fmt.Errorf("machine %s does not exist", id) | ||
285 | 654 | case err != nil: | ||
286 | 655 | case args.Force: | ||
287 | 656 | err = machine.ForceDestroy() | ||
288 | 657 | case machine.Life() != state.Alive: | ||
289 | 658 | continue | ||
290 | 659 | default: | ||
291 | 660 | err = machine.Destroy() | ||
292 | 661 | } | ||
293 | 662 | if err != nil { | ||
294 | 663 | errs = append(errs, err.Error()) | ||
295 | 664 | } | ||
296 | 665 | } | ||
297 | 666 | return destroyErr("machines", args.MachineNames, errs) | ||
298 | 629 | } | 667 | } |
299 | 630 | 668 | ||
300 | 631 | // CharmInfo returns information about the requested charm. | 669 | // CharmInfo returns information about the requested charm. |
301 | @@ -786,3 +824,15 @@ | |||
302 | 786 | // Now try to apply the new validated config. | 824 | // Now try to apply the new validated config. |
303 | 787 | return c.api.state.SetEnvironConfig(newProviderConfig) | 825 | return c.api.state.SetEnvironConfig(newProviderConfig) |
304 | 788 | } | 826 | } |
305 | 827 | |||
306 | 828 | func destroyErr(desc string, ids, errs []string) error { | ||
307 | 829 | if len(errs) == 0 { | ||
308 | 830 | return nil | ||
309 | 831 | } | ||
310 | 832 | msg := "some %s were not destroyed" | ||
311 | 833 | if len(errs) == len(ids) { | ||
312 | 834 | msg = "no %s were destroyed" | ||
313 | 835 | } | ||
314 | 836 | msg = fmt.Sprintf(msg, desc) | ||
315 | 837 | return fmt.Errorf("%s: %s", msg, strings.Join(errs, "; ")) | ||
316 | 838 | } | ||
317 | 789 | 839 | ||
318 | === modified file 'state/apiserver/client/client_test.go' | |||
319 | --- state/apiserver/client/client_test.go 2013-11-08 06:36:55 +0000 | |||
320 | +++ state/apiserver/client/client_test.go 2013-11-12 10:22:12 +0000 | |||
321 | @@ -493,38 +493,143 @@ | |||
322 | 493 | c.Assert(service.Life(), gc.Not(gc.Equals), state.Alive) | 493 | c.Assert(service.Life(), gc.Not(gc.Equals), state.Alive) |
323 | 494 | } | 494 | } |
324 | 495 | 495 | ||
356 | 496 | func (s *clientSuite) TestClientDestroyMachines(c *gc.C) { | 496 | func assertLife(c *gc.C, entity state.Living, life state.Life) { |
357 | 497 | s.setUpScenario(c) | 497 | err := entity.Refresh() |
358 | 498 | err := s.APIState.Client().DestroyMachines("1") | 498 | c.Assert(err, gc.IsNil) |
359 | 499 | c.Assert(err, gc.ErrorMatches, `no machines were destroyed: machine 1 has unit "wordpress/0" assigned`) | 499 | c.Assert(entity.Life(), gc.Equals, life) |
360 | 500 | m, err := s.State.AddMachine("trusty", state.JobHostUnits) | 500 | } |
361 | 501 | c.Assert(err, gc.IsNil) | 501 | |
362 | 502 | err = s.APIState.Client().DestroyMachines(m.Id()) | 502 | func assertRemoved(c *gc.C, entity state.Living) { |
363 | 503 | c.Assert(err, gc.IsNil) | 503 | err := entity.Refresh() |
364 | 504 | err = m.Refresh() | 504 | c.Assert(err, jc.Satisfies, errors.IsNotFoundError) |
365 | 505 | c.Assert(err, gc.IsNil) | 505 | } |
366 | 506 | c.Assert(m.Life(), gc.Not(gc.Equals), state.Alive) | 506 | |
367 | 507 | } | 507 | func (s *clientSuite) setupDestroyMachinesTest(c *gc.C) (*state.Machine, *state.Machine, *state.Machine, *state.Unit) { |
368 | 508 | 508 | m0, err := s.State.AddMachine("quantal", state.JobHostUnits) | |
369 | 509 | func (s *clientSuite) TestClientDestroyUnits(c *gc.C) { | 509 | c.Assert(err, gc.IsNil) |
370 | 510 | // Setup: | 510 | m1, err := s.State.AddMachine("quantal", state.JobManageEnviron) |
371 | 511 | s.setUpScenario(c) | 511 | c.Assert(err, gc.IsNil) |
372 | 512 | service, err := s.State.Service("wordpress") | 512 | m2, err := s.State.AddMachine("quantal", state.JobHostUnits) |
373 | 513 | c.Assert(err, gc.IsNil) | 513 | c.Assert(err, gc.IsNil) |
374 | 514 | _, err = service.AddUnit() | 514 | |
375 | 515 | c.Assert(err, gc.IsNil) | 515 | sch := s.AddTestingCharm(c, "wordpress") |
376 | 516 | // Destroy some units and check the result. | 516 | wordpress, err := s.State.AddService("wordpress", sch) |
377 | 517 | err = s.APIState.Client().DestroyServiceUnits([]string{"wordpress/1", "wordpress/2"}) | 517 | c.Assert(err, gc.IsNil) |
378 | 518 | c.Assert(err, gc.IsNil) | 518 | u, err := wordpress.AddUnit() |
379 | 519 | units, err := service.AllUnits() | 519 | c.Assert(err, gc.IsNil) |
380 | 520 | c.Assert(err, gc.IsNil) | 520 | err = u.AssignToMachine(m0) |
381 | 521 | for i, u := range units { | 521 | c.Assert(err, gc.IsNil) |
382 | 522 | if i == 0 { | 522 | |
383 | 523 | c.Assert(u.Life(), gc.Equals, state.Alive) | 523 | return m0, m1, m2, u |
384 | 524 | } else { | 524 | } |
385 | 525 | c.Assert(u.Life(), gc.Equals, state.Dying) | 525 | |
386 | 526 | } | 526 | func (s *clientSuite) TestDestroyMachines(c *gc.C) { |
387 | 527 | m0, m1, m2, u := s.setupDestroyMachinesTest(c) | ||
388 | 528 | |||
389 | 529 | err := s.APIState.Client().DestroyMachines("0", "1", "2") | ||
390 | 530 | c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 0 has unit "wordpress/0" assigned; machine 1 is required by the environment`) | ||
391 | 531 | assertLife(c, m0, state.Alive) | ||
392 | 532 | assertLife(c, m1, state.Alive) | ||
393 | 533 | assertLife(c, m2, state.Dying) | ||
394 | 534 | |||
395 | 535 | err = u.UnassignFromMachine() | ||
396 | 536 | c.Assert(err, gc.IsNil) | ||
397 | 537 | err = s.APIState.Client().DestroyMachines("0", "1", "2") | ||
398 | 538 | c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 1 is required by the environment`) | ||
399 | 539 | assertLife(c, m0, state.Dying) | ||
400 | 540 | assertLife(c, m1, state.Alive) | ||
401 | 541 | assertLife(c, m2, state.Dying) | ||
402 | 542 | } | ||
403 | 543 | |||
404 | 544 | func (s *clientSuite) TestForceDestroyMachines(c *gc.C) { | ||
405 | 545 | m0, m1, m2, u := s.setupDestroyMachinesTest(c) | ||
406 | 546 | |||
407 | 547 | err := s.APIState.Client().ForceDestroyMachines("0", "1", "2") | ||
408 | 548 | c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 1 is required by the environment`) | ||
409 | 549 | assertLife(c, m0, state.Alive) | ||
410 | 550 | assertLife(c, m1, state.Alive) | ||
411 | 551 | assertLife(c, m2, state.Alive) | ||
412 | 552 | assertLife(c, u, state.Alive) | ||
413 | 553 | |||
414 | 554 | err = s.State.Cleanup() | ||
415 | 555 | c.Assert(err, gc.IsNil) | ||
416 | 556 | assertRemoved(c, m0) | ||
417 | 557 | assertLife(c, m1, state.Alive) | ||
418 | 558 | assertRemoved(c, m2) | ||
419 | 559 | assertRemoved(c, u) | ||
420 | 560 | } | ||
421 | 561 | |||
422 | 562 | func (s *clientSuite) TestDestroyPrincipalUnits(c *gc.C) { | ||
423 | 563 | wordpress, err := s.State.AddService("wordpress", s.AddTestingCharm(c, "wordpress")) | ||
424 | 564 | c.Assert(err, gc.IsNil) | ||
425 | 565 | units := make([]*state.Unit, 5) | ||
426 | 566 | for i := range units { | ||
427 | 567 | unit, err := wordpress.AddUnit() | ||
428 | 568 | c.Assert(err, gc.IsNil) | ||
429 | 569 | err = unit.SetStatus(params.StatusStarted, "", nil) | ||
430 | 570 | c.Assert(err, gc.IsNil) | ||
431 | 571 | units[i] = unit | ||
432 | 527 | } | 572 | } |
433 | 573 | |||
434 | 574 | // Destroy 2 of them; check they become Dying. | ||
435 | 575 | err = s.APIState.Client().DestroyServiceUnits("wordpress/0", "wordpress/1") | ||
436 | 576 | c.Assert(err, gc.IsNil) | ||
437 | 577 | assertLife(c, units[0], state.Dying) | ||
438 | 578 | assertLife(c, units[1], state.Dying) | ||
439 | 579 | |||
440 | 580 | // Try to destroy an Alive one and a Dying one; check | ||
441 | 581 | // it destroys the Alive one and ignores the Dying one. | ||
442 | 582 | err = s.APIState.Client().DestroyServiceUnits("wordpress/2", "wordpress/0") | ||
443 | 583 | c.Assert(err, gc.IsNil) | ||
444 | 584 | assertLife(c, units[2], state.Dying) | ||
445 | 585 | |||
446 | 586 | // Try to destroy an Alive one along with a nonexistent one; check that | ||
447 | 587 | // the valid instruction is followed but the invalid one is warned about. | ||
448 | 588 | err = s.APIState.Client().DestroyServiceUnits("boojum/123", "wordpress/3") | ||
449 | 589 | c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "boojum/123" does not exist`) | ||
450 | 590 | assertLife(c, units[3], state.Dying) | ||
451 | 591 | |||
452 | 592 | // Make one Dead, and destroy an Alive one alongside it; check no errors. | ||
453 | 593 | wp0, err := s.State.Unit("wordpress/0") | ||
454 | 594 | c.Assert(err, gc.IsNil) | ||
455 | 595 | err = wp0.EnsureDead() | ||
456 | 596 | c.Assert(err, gc.IsNil) | ||
457 | 597 | err = s.APIState.Client().DestroyServiceUnits("wordpress/0", "wordpress/4") | ||
458 | 598 | c.Assert(err, gc.IsNil) | ||
459 | 599 | assertLife(c, units[0], state.Dead) | ||
460 | 600 | assertLife(c, units[4], state.Dying) | ||
461 | 601 | } | ||
462 | 602 | |||
463 | 603 | func (s *clientSuite) TestDestroySubordinateUnits(c *gc.C) { | ||
464 | 604 | wordpress, err := s.State.AddService("wordpress", s.AddTestingCharm(c, "wordpress")) | ||
465 | 605 | c.Assert(err, gc.IsNil) | ||
466 | 606 | wordpress0, err := wordpress.AddUnit() | ||
467 | 607 | c.Assert(err, gc.IsNil) | ||
468 | 608 | _, err = s.State.AddService("logging", s.AddTestingCharm(c, "logging")) | ||
469 | 609 | c.Assert(err, gc.IsNil) | ||
470 | 610 | eps, err := s.State.InferEndpoints([]string{"logging", "wordpress"}) | ||
471 | 611 | c.Assert(err, gc.IsNil) | ||
472 | 612 | rel, err := s.State.AddRelation(eps...) | ||
473 | 613 | c.Assert(err, gc.IsNil) | ||
474 | 614 | ru, err := rel.Unit(wordpress0) | ||
475 | 615 | c.Assert(err, gc.IsNil) | ||
476 | 616 | err = ru.EnterScope(nil) | ||
477 | 617 | c.Assert(err, gc.IsNil) | ||
478 | 618 | logging0, err := s.State.Unit("logging/0") | ||
479 | 619 | c.Assert(err, gc.IsNil) | ||
480 | 620 | |||
481 | 621 | // Try to destroy the subordinate alone; check it fails. | ||
482 | 622 | err = s.APIState.Client().DestroyServiceUnits("logging/0") | ||
483 | 623 | c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`) | ||
484 | 624 | assertLife(c, logging0, state.Alive) | ||
485 | 625 | |||
486 | 626 | // Try to destroy the principal and the subordinate together; check it warns | ||
487 | 627 | // about the subordinate, but destroys the one it can. (The principal unit | ||
488 | 628 | // agent will be resposible for destroying the subordinate.) | ||
489 | 629 | err = s.APIState.Client().DestroyServiceUnits("wordpress/0", "logging/0") | ||
490 | 630 | c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "logging/0" is a subordinate`) | ||
491 | 631 | assertLife(c, wordpress0, state.Dying) | ||
492 | 632 | assertLife(c, logging0, state.Alive) | ||
493 | 528 | } | 633 | } |
494 | 529 | 634 | ||
495 | 530 | func (s *clientSuite) testClientUnitResolved(c *gc.C, retry bool, expectedResolvedMode state.ResolvedMode) { | 635 | func (s *clientSuite) testClientUnitResolved(c *gc.C, retry bool, expectedResolvedMode state.ResolvedMode) { |
496 | 531 | 636 | ||
497 | === modified file 'state/apiserver/client/perm_test.go' | |||
498 | --- state/apiserver/client/perm_test.go 2013-11-06 10:51:34 +0000 | |||
499 | +++ state/apiserver/client/perm_test.go 2013-11-12 10:22:12 +0000 | |||
500 | @@ -348,7 +348,7 @@ | |||
501 | 348 | } | 348 | } |
502 | 349 | 349 | ||
503 | 350 | func opClientDestroyServiceUnits(c *gc.C, st *api.State, mst *state.State) (func(), error) { | 350 | func opClientDestroyServiceUnits(c *gc.C, st *api.State, mst *state.State) (func(), error) { |
505 | 351 | err := st.Client().DestroyServiceUnits([]string{"wordpress/99"}) | 351 | err := st.Client().DestroyServiceUnits("wordpress/99") |
506 | 352 | if err != nil && strings.HasPrefix(err.Error(), "no units were destroyed") { | 352 | if err != nil && strings.HasPrefix(err.Error(), "no units were destroyed") { |
507 | 353 | err = nil | 353 | err = nil |
508 | 354 | } | 354 | } |
509 | 355 | 355 | ||
510 | === modified file 'state/cleanup_test.go' | |||
511 | --- state/cleanup_test.go 2013-11-11 15:45:06 +0000 | |||
512 | +++ state/cleanup_test.go 2013-11-12 10:22:12 +0000 | |||
513 | @@ -1,6 +1,8 @@ | |||
514 | 1 | package state_test | 1 | package state_test |
515 | 2 | 2 | ||
516 | 3 | import ( | 3 | import ( |
517 | 4 | "fmt" | ||
518 | 5 | |||
519 | 4 | gc "launchpad.net/gocheck" | 6 | gc "launchpad.net/gocheck" |
520 | 5 | 7 | ||
521 | 6 | "launchpad.net/juju-core/charm" | 8 | "launchpad.net/juju-core/charm" |
522 | @@ -90,6 +92,22 @@ | |||
523 | 90 | c.Assert(err, gc.ErrorMatches, `cannot read settings for unit "riak/0" in relation "riak:ring": settings not found`) | 92 | c.Assert(err, gc.ErrorMatches, `cannot read settings for unit "riak/0" in relation "riak:ring": settings not found`) |
524 | 91 | } | 93 | } |
525 | 92 | 94 | ||
526 | 95 | func (s *CleanupSuite) testForceDestroyManagerError(c *gc.C, job state.MachineJob) { | ||
527 | 96 | manager, err := s.State.AddMachine("quantal", job) | ||
528 | 97 | c.Assert(err, gc.IsNil) | ||
529 | 98 | s.assertDoesNotNeedCleanup(c) | ||
530 | 99 | err = manager.ForceDestroy() | ||
531 | 100 | expect := fmt.Sprintf("machine %s is required by the environment", manager.Id()) | ||
532 | 101 | c.Assert(err, gc.ErrorMatches, expect) | ||
533 | 102 | s.assertDoesNotNeedCleanup(c) | ||
534 | 103 | assertLife(c, manager, state.Alive) | ||
535 | 104 | } | ||
536 | 105 | |||
537 | 106 | func (s *CleanupSuite) TestForceDestroyMachineErrors(c *gc.C) { | ||
538 | 107 | s.testForceDestroyManagerError(c, state.JobManageState) | ||
539 | 108 | s.testForceDestroyManagerError(c, state.JobManageEnviron) | ||
540 | 109 | } | ||
541 | 110 | |||
542 | 93 | func (s *CleanupSuite) TestCleanupForceDestroyedMachineUnit(c *gc.C) { | 111 | func (s *CleanupSuite) TestCleanupForceDestroyedMachineUnit(c *gc.C) { |
543 | 94 | s.assertDoesNotNeedCleanup(c) | 112 | s.assertDoesNotNeedCleanup(c) |
544 | 95 | 113 | ||
545 | @@ -106,7 +124,7 @@ | |||
546 | 106 | s.assertDoesNotNeedCleanup(c) | 124 | s.assertDoesNotNeedCleanup(c) |
547 | 107 | 125 | ||
548 | 108 | // Force machine destruction, check cleanup queued. | 126 | // Force machine destruction, check cleanup queued. |
550 | 109 | err = s.State.ForceDestroyMachines(machine.Id()) | 127 | err = machine.ForceDestroy() |
551 | 110 | c.Assert(err, gc.IsNil) | 128 | c.Assert(err, gc.IsNil) |
552 | 111 | s.assertNeedsCleanup(c) | 129 | s.assertNeedsCleanup(c) |
553 | 112 | 130 | ||
554 | @@ -117,7 +135,7 @@ | |||
555 | 117 | c.Assert(err, jc.Satisfies, errors.IsNotFoundError) | 135 | c.Assert(err, jc.Satisfies, errors.IsNotFoundError) |
556 | 118 | 136 | ||
557 | 119 | // ...and so has the unit... | 137 | // ...and so has the unit... |
559 | 120 | assertUnitRemoved(c, pr.u0) | 138 | assertRemoved(c, pr.u0) |
560 | 121 | 139 | ||
561 | 122 | // ...and the unit has departed relation scope. | 140 | // ...and the unit has departed relation scope. |
562 | 123 | assertNotInScope(c, pr.ru0) | 141 | assertNotInScope(c, pr.ru0) |
563 | @@ -156,13 +174,13 @@ | |||
564 | 156 | s.assertDoesNotNeedCleanup(c) | 174 | s.assertDoesNotNeedCleanup(c) |
565 | 157 | 175 | ||
566 | 158 | // Force removal of the top-level machine. | 176 | // Force removal of the top-level machine. |
568 | 159 | err = s.State.ForceDestroyMachines(machine.Id()) | 177 | err = machine.ForceDestroy() |
569 | 160 | c.Assert(err, gc.IsNil) | 178 | c.Assert(err, gc.IsNil) |
570 | 161 | s.assertNeedsCleanup(c) | 179 | s.assertNeedsCleanup(c) |
571 | 162 | 180 | ||
572 | 163 | // And do it again, just to check that the second cleanup doc for the same | 181 | // And do it again, just to check that the second cleanup doc for the same |
573 | 164 | // machine doesn't cause problems down the line. | 182 | // machine doesn't cause problems down the line. |
575 | 165 | err = s.State.ForceDestroyMachines(machine.Id()) | 183 | err = machine.ForceDestroy() |
576 | 166 | c.Assert(err, gc.IsNil) | 184 | c.Assert(err, gc.IsNil) |
577 | 167 | s.assertNeedsCleanup(c) | 185 | s.assertNeedsCleanup(c) |
578 | 168 | 186 | ||
579 | @@ -175,10 +193,10 @@ | |||
580 | 175 | c.Assert(err, jc.Satisfies, errors.IsNotFoundError) | 193 | c.Assert(err, jc.Satisfies, errors.IsNotFoundError) |
581 | 176 | 194 | ||
582 | 177 | // ...and so have all the units... | 195 | // ...and so have all the units... |
587 | 178 | assertUnitRemoved(c, prr.pu0) | 196 | assertRemoved(c, prr.pu0) |
588 | 179 | assertUnitRemoved(c, prr.pu1) | 197 | assertRemoved(c, prr.pu1) |
589 | 180 | assertUnitRemoved(c, prr.ru0) | 198 | assertRemoved(c, prr.ru0) |
590 | 181 | assertUnitRemoved(c, prr.ru1) | 199 | assertRemoved(c, prr.ru1) |
591 | 182 | 200 | ||
592 | 183 | // ...and none of the units have left relation scopes occupied. | 201 | // ...and none of the units have left relation scopes occupied. |
593 | 184 | assertNotInScope(c, prr.pru0) | 202 | assertNotInScope(c, prr.pru0) |
594 | 185 | 203 | ||
595 | === modified file 'state/life.go' | |||
596 | --- state/life.go 2013-07-09 10:32:23 +0000 | |||
597 | +++ state/life.go 2013-11-12 10:22:12 +0000 | |||
598 | @@ -38,8 +38,15 @@ | |||
599 | 38 | type Living interface { | 38 | type Living interface { |
600 | 39 | Life() Life | 39 | Life() Life |
601 | 40 | Destroy() error | 40 | Destroy() error |
602 | 41 | Refresh() error | ||
603 | 42 | } | ||
604 | 43 | |||
605 | 44 | // AgentLiving describes state entities with a lifecycle and an agent that | ||
606 | 45 | // manages it. | ||
607 | 46 | type AgentLiving interface { | ||
608 | 47 | Living | ||
609 | 41 | EnsureDead() error | 48 | EnsureDead() error |
611 | 42 | Refresh() error | 49 | Remove() error |
612 | 43 | } | 50 | } |
613 | 44 | 51 | ||
614 | 45 | func isAlive(coll *mgo.Collection, id interface{}) (bool, error) { | 52 | func isAlive(coll *mgo.Collection, id interface{}) (bool, error) { |
615 | 46 | 53 | ||
616 | === modified file 'state/life_test.go' | |||
617 | --- state/life_test.go 2013-09-27 07:05:45 +0000 | |||
618 | +++ state/life_test.go 2013-11-12 10:22:12 +0000 | |||
619 | @@ -81,8 +81,7 @@ | |||
620 | 81 | 81 | ||
621 | 82 | type lifeFixture interface { | 82 | type lifeFixture interface { |
622 | 83 | id() (coll string, id interface{}) | 83 | id() (coll string, id interface{}) |
625 | 84 | setup(s *LifeSuite, c *gc.C) state.Living | 84 | setup(s *LifeSuite, c *gc.C) state.AgentLiving |
624 | 85 | teardown(s *LifeSuite, c *gc.C) | ||
626 | 86 | } | 85 | } |
627 | 87 | 86 | ||
628 | 88 | type unitLife struct { | 87 | type unitLife struct { |
629 | @@ -93,7 +92,7 @@ | |||
630 | 93 | return "units", l.unit.Name() | 92 | return "units", l.unit.Name() |
631 | 94 | } | 93 | } |
632 | 95 | 94 | ||
634 | 96 | func (l *unitLife) setup(s *LifeSuite, c *gc.C) state.Living { | 95 | func (l *unitLife) setup(s *LifeSuite, c *gc.C) state.AgentLiving { |
635 | 97 | unit, err := s.svc.AddUnit() | 96 | unit, err := s.svc.AddUnit() |
636 | 98 | c.Assert(err, gc.IsNil) | 97 | c.Assert(err, gc.IsNil) |
637 | 99 | preventUnitDestroyRemove(c, unit) | 98 | preventUnitDestroyRemove(c, unit) |
638 | @@ -101,11 +100,6 @@ | |||
639 | 101 | return l.unit | 100 | return l.unit |
640 | 102 | } | 101 | } |
641 | 103 | 102 | ||
642 | 104 | func (l *unitLife) teardown(s *LifeSuite, c *gc.C) { | ||
643 | 105 | err := l.unit.Remove() | ||
644 | 106 | c.Assert(err, gc.IsNil) | ||
645 | 107 | } | ||
646 | 108 | |||
647 | 109 | type machineLife struct { | 103 | type machineLife struct { |
648 | 110 | machine *state.Machine | 104 | machine *state.Machine |
649 | 111 | } | 105 | } |
650 | @@ -114,18 +108,13 @@ | |||
651 | 114 | return "machines", l.machine.Id() | 108 | return "machines", l.machine.Id() |
652 | 115 | } | 109 | } |
653 | 116 | 110 | ||
655 | 117 | func (l *machineLife) setup(s *LifeSuite, c *gc.C) state.Living { | 111 | func (l *machineLife) setup(s *LifeSuite, c *gc.C) state.AgentLiving { |
656 | 118 | var err error | 112 | var err error |
657 | 119 | l.machine, err = s.State.AddMachine("quantal", state.JobHostUnits) | 113 | l.machine, err = s.State.AddMachine("quantal", state.JobHostUnits) |
658 | 120 | c.Assert(err, gc.IsNil) | 114 | c.Assert(err, gc.IsNil) |
659 | 121 | return l.machine | 115 | return l.machine |
660 | 122 | } | 116 | } |
661 | 123 | 117 | ||
662 | 124 | func (l *machineLife) teardown(s *LifeSuite, c *gc.C) { | ||
663 | 125 | err := l.machine.Remove() | ||
664 | 126 | c.Assert(err, gc.IsNil) | ||
665 | 127 | } | ||
666 | 128 | |||
667 | 129 | func (s *LifeSuite) prepareFixture(living state.Living, lfix lifeFixture, cached, dbinitial state.Life, c *gc.C) { | 118 | func (s *LifeSuite) prepareFixture(living state.Living, lfix lifeFixture, cached, dbinitial state.Life, c *gc.C) { |
668 | 130 | collName, id := lfix.id() | 119 | collName, id := lfix.id() |
669 | 131 | coll := s.MgoSuite.Session.DB("juju").C(collName) | 120 | coll := s.MgoSuite.Session.DB("juju").C(collName) |
670 | @@ -165,7 +154,8 @@ | |||
671 | 165 | c.Assert(living.Life(), gc.Equals, v.dbfinal) | 154 | c.Assert(living.Life(), gc.Equals, v.dbfinal) |
672 | 166 | err = living.EnsureDead() | 155 | err = living.EnsureDead() |
673 | 167 | c.Assert(err, gc.IsNil) | 156 | c.Assert(err, gc.IsNil) |
675 | 168 | lfix.teardown(s, c) | 157 | err = living.Remove() |
676 | 158 | c.Assert(err, gc.IsNil) | ||
677 | 169 | } | 159 | } |
678 | 170 | } | 160 | } |
679 | 171 | } | 161 | } |
680 | 172 | 162 | ||
681 | === modified file 'state/machine.go' | |||
682 | --- state/machine.go 2013-11-11 23:42:49 +0000 | |||
683 | +++ state/machine.go 2013-11-12 10:22:12 +0000 | |||
684 | @@ -195,10 +195,11 @@ | |||
685 | 195 | return m.doc.Jobs | 195 | return m.doc.Jobs |
686 | 196 | } | 196 | } |
687 | 197 | 197 | ||
690 | 198 | // IsStateServer returns true if the machine has a JobManageState job. | 198 | // IsManager returns true if the machine has JobManageState or JobManageEnviron. |
691 | 199 | func (m *Machine) IsStateServer() bool { | 199 | func (m *Machine) IsManager() bool { |
692 | 200 | for _, job := range m.doc.Jobs { | 200 | for _, job := range m.doc.Jobs { |
694 | 201 | if job == JobManageState { | 201 | switch job { |
695 | 202 | case JobManageEnviron, JobManageState: | ||
696 | 202 | return true | 203 | return true |
697 | 203 | } | 204 | } |
698 | 204 | } | 205 | } |
699 | @@ -314,6 +315,22 @@ | |||
700 | 314 | return m.advanceLifecycle(Dying) | 315 | return m.advanceLifecycle(Dying) |
701 | 315 | } | 316 | } |
702 | 316 | 317 | ||
703 | 318 | // ForceDestroy queues the machine for complete removal, including the | ||
704 | 319 | // destruction of all units and containers on the machine. | ||
705 | 320 | func (m *Machine) ForceDestroy() error { | ||
706 | 321 | if !m.IsManager() { | ||
707 | 322 | ops := []txn.Op{{ | ||
708 | 323 | C: m.st.machines.Name, | ||
709 | 324 | Id: m.doc.Id, | ||
710 | 325 | Assert: D{{"jobs", D{{"$nin", []MachineJob{JobManageState}}}}}, | ||
711 | 326 | }, m.st.newCleanupOp("machine", m.doc.Id)} | ||
712 | 327 | if err := m.st.runTransaction(ops); err != txn.ErrAborted { | ||
713 | 328 | return err | ||
714 | 329 | } | ||
715 | 330 | } | ||
716 | 331 | return fmt.Errorf("machine %s is required by the environment", m.doc.Id) | ||
717 | 332 | } | ||
718 | 333 | |||
719 | 317 | // EnsureDead sets the machine lifecycle to Dead if it is Alive or Dying. | 334 | // EnsureDead sets the machine lifecycle to Dead if it is Alive or Dying. |
720 | 318 | // It does nothing otherwise. EnsureDead will fail if the machine has | 335 | // It does nothing otherwise. EnsureDead will fail if the machine has |
721 | 319 | // principal units assigned, or if the machine has JobManageEnviron. | 336 | // principal units assigned, or if the machine has JobManageEnviron. |
722 | 320 | 337 | ||
723 | === modified file 'state/machine_test.go' | |||
724 | --- state/machine_test.go 2013-11-12 01:59:52 +0000 | |||
725 | +++ state/machine_test.go 2013-11-12 10:22:12 +0000 | |||
726 | @@ -67,15 +67,17 @@ | |||
727 | 67 | c.Assert(ok, gc.Equals, true) | 67 | c.Assert(ok, gc.Equals, true) |
728 | 68 | } | 68 | } |
729 | 69 | 69 | ||
731 | 70 | func (s *MachineSuite) TestMachineIsStateServer(c *gc.C) { | 70 | func (s *MachineSuite) TestMachineIsManager(c *gc.C) { |
732 | 71 | tests := []struct { | 71 | tests := []struct { |
733 | 72 | isStateServer bool | 72 | isStateServer bool |
734 | 73 | jobs []state.MachineJob | 73 | jobs []state.MachineJob |
735 | 74 | }{ | 74 | }{ |
736 | 75 | {false, []state.MachineJob{state.JobHostUnits}}, | 75 | {false, []state.MachineJob{state.JobHostUnits}}, |
738 | 76 | {false, []state.MachineJob{state.JobHostUnits, state.JobManageEnviron}}, | 76 | {true, []state.MachineJob{state.JobManageState}}, |
739 | 77 | {true, []state.MachineJob{state.JobManageEnviron}}, | ||
740 | 78 | {true, []state.MachineJob{state.JobHostUnits, state.JobManageState}}, | ||
741 | 79 | {true, []state.MachineJob{state.JobHostUnits, state.JobManageEnviron}}, | ||
742 | 77 | {true, []state.MachineJob{state.JobHostUnits, state.JobManageState, state.JobManageEnviron}}, | 80 | {true, []state.MachineJob{state.JobHostUnits, state.JobManageState, state.JobManageEnviron}}, |
743 | 78 | {true, []state.MachineJob{state.JobManageState}}, | ||
744 | 79 | } | 81 | } |
745 | 80 | for _, test := range tests { | 82 | for _, test := range tests { |
746 | 81 | params := state.AddMachineParams{ | 83 | params := state.AddMachineParams{ |
747 | @@ -84,7 +86,7 @@ | |||
748 | 84 | } | 86 | } |
749 | 85 | m, err := s.State.AddMachineWithConstraints(¶ms) | 87 | m, err := s.State.AddMachineWithConstraints(¶ms) |
750 | 86 | c.Assert(err, gc.IsNil) | 88 | c.Assert(err, gc.IsNil) |
752 | 87 | c.Assert(m.IsStateServer(), gc.Equals, test.isStateServer) | 89 | c.Assert(m.IsManager(), gc.Equals, test.isStateServer) |
753 | 88 | } | 90 | } |
754 | 89 | } | 91 | } |
755 | 90 | 92 | ||
756 | @@ -94,6 +96,8 @@ | |||
757 | 94 | c.Assert(err, gc.IsNil) | 96 | c.Assert(err, gc.IsNil) |
758 | 95 | err = m.Destroy() | 97 | err = m.Destroy() |
759 | 96 | c.Assert(err, gc.ErrorMatches, "machine 1 is required by the environment") | 98 | c.Assert(err, gc.ErrorMatches, "machine 1 is required by the environment") |
760 | 99 | err = m.ForceDestroy() | ||
761 | 100 | c.Assert(err, gc.ErrorMatches, "machine 1 is required by the environment") | ||
762 | 97 | err = m.EnsureDead() | 101 | err = m.EnsureDead() |
763 | 98 | c.Assert(err, gc.ErrorMatches, "machine 1 is required by the environment") | 102 | c.Assert(err, gc.ErrorMatches, "machine 1 is required by the environment") |
764 | 99 | } | 103 | } |
765 | @@ -218,41 +222,6 @@ | |||
766 | 218 | c.Assert(err, gc.IsNil) | 222 | c.Assert(err, gc.IsNil) |
767 | 219 | } | 223 | } |
768 | 220 | 224 | ||
769 | 221 | func (s *MachineSuite) TestDestroyMachines(c *gc.C) { | ||
770 | 222 | m0 := s.machine | ||
771 | 223 | m1, err := s.State.AddMachine("quantal", state.JobManageEnviron) | ||
772 | 224 | c.Assert(err, gc.IsNil) | ||
773 | 225 | m2, err := s.State.AddMachine("quantal", state.JobHostUnits) | ||
774 | 226 | c.Assert(err, gc.IsNil) | ||
775 | 227 | |||
776 | 228 | sch := s.AddTestingCharm(c, "wordpress") | ||
777 | 229 | wordpress, err := s.State.AddService("wordpress", sch) | ||
778 | 230 | c.Assert(err, gc.IsNil) | ||
779 | 231 | u, err := wordpress.AddUnit() | ||
780 | 232 | c.Assert(err, gc.IsNil) | ||
781 | 233 | err = u.AssignToMachine(m0) | ||
782 | 234 | c.Assert(err, gc.IsNil) | ||
783 | 235 | |||
784 | 236 | err = s.State.DestroyMachines("0", "1", "2") | ||
785 | 237 | c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 0 has unit "wordpress/0" assigned; machine 1 is required by the environment`) | ||
786 | 238 | assertLife := func(m *state.Machine, life state.Life) { | ||
787 | 239 | err := m.Refresh() | ||
788 | 240 | c.Assert(err, gc.IsNil) | ||
789 | 241 | c.Assert(m.Life(), gc.Equals, life) | ||
790 | 242 | } | ||
791 | 243 | assertLife(m0, state.Alive) | ||
792 | 244 | assertLife(m1, state.Alive) | ||
793 | 245 | assertLife(m2, state.Dying) | ||
794 | 246 | |||
795 | 247 | err = u.UnassignFromMachine() | ||
796 | 248 | c.Assert(err, gc.IsNil) | ||
797 | 249 | err = s.State.DestroyMachines("0", "1", "2") | ||
798 | 250 | c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 1 is required by the environment`) | ||
799 | 251 | assertLife(m0, state.Dying) | ||
800 | 252 | assertLife(m1, state.Alive) | ||
801 | 253 | assertLife(m2, state.Dying) | ||
802 | 254 | } | ||
803 | 255 | |||
804 | 256 | func (s *MachineSuite) TestMachineSetAgentAlive(c *gc.C) { | 225 | func (s *MachineSuite) TestMachineSetAgentAlive(c *gc.C) { |
805 | 257 | alive, err := s.machine.AgentAlive() | 226 | alive, err := s.machine.AgentAlive() |
806 | 258 | c.Assert(err, gc.IsNil) | 227 | c.Assert(err, gc.IsNil) |
807 | @@ -627,11 +596,11 @@ | |||
808 | 627 | func (s *MachineSuite) TestWatchDiesOnStateClose(c *gc.C) { | 596 | func (s *MachineSuite) TestWatchDiesOnStateClose(c *gc.C) { |
809 | 628 | // This test is testing logic in watcher.entityWatcher, which | 597 | // This test is testing logic in watcher.entityWatcher, which |
810 | 629 | // is also used by: | 598 | // is also used by: |
816 | 630 | // Machine.WatchHardwareCharacteristics | 599 | // Machine.WatchHardwareCharacteristics |
817 | 631 | // Service.Watch | 600 | // Service.Watch |
818 | 632 | // Unit.Watch | 601 | // Unit.Watch |
819 | 633 | // State.WatchForEnvironConfigChanges | 602 | // State.WatchForEnvironConfigChanges |
820 | 634 | // Unit.WatchConfigSettings | 603 | // Unit.WatchConfigSettings |
821 | 635 | testWatcherDiesWhenStateCloses(c, func(c *gc.C, st *state.State) waiter { | 604 | testWatcherDiesWhenStateCloses(c, func(c *gc.C, st *state.State) waiter { |
822 | 636 | m, err := st.Machine(s.machine.Id()) | 605 | m, err := st.Machine(s.machine.Id()) |
823 | 637 | c.Assert(err, gc.IsNil) | 606 | c.Assert(err, gc.IsNil) |
824 | 638 | 607 | ||
825 | === modified file 'state/service_test.go' | |||
826 | --- state/service_test.go 2013-09-27 19:20:20 +0000 | |||
827 | +++ state/service_test.go 2013-11-12 10:22:12 +0000 | |||
828 | @@ -1090,7 +1090,7 @@ | |||
829 | 1090 | err = s.mysql.Destroy() | 1090 | err = s.mysql.Destroy() |
830 | 1091 | c.Assert(err, gc.IsNil) | 1091 | c.Assert(err, gc.IsNil) |
831 | 1092 | for _, unit := range units { | 1092 | for _, unit := range units { |
833 | 1093 | assertUnitLife(c, unit, state.Alive) | 1093 | assertLife(c, unit, state.Alive) |
834 | 1094 | } | 1094 | } |
835 | 1095 | 1095 | ||
836 | 1096 | // Check a cleanup doc was added. | 1096 | // Check a cleanup doc was added. |
837 | @@ -1103,9 +1103,9 @@ | |||
838 | 1103 | c.Assert(err, gc.IsNil) | 1103 | c.Assert(err, gc.IsNil) |
839 | 1104 | for i, unit := range units { | 1104 | for i, unit := range units { |
840 | 1105 | if i%2 != 0 { | 1105 | if i%2 != 0 { |
842 | 1106 | assertUnitLife(c, unit, state.Dying) | 1106 | assertLife(c, unit, state.Dying) |
843 | 1107 | } else { | 1107 | } else { |
845 | 1108 | assertUnitRemoved(c, unit) | 1108 | assertRemoved(c, unit) |
846 | 1109 | } | 1109 | } |
847 | 1110 | } | 1110 | } |
848 | 1111 | 1111 | ||
849 | 1112 | 1112 | ||
850 | === modified file 'state/state.go' | |||
851 | --- state/state.go 2013-11-08 16:15:10 +0000 | |||
852 | +++ state/state.go 2013-11-12 10:22:12 +0000 | |||
853 | @@ -1051,73 +1051,6 @@ | |||
854 | 1051 | return newUnit(st, &doc), nil | 1051 | return newUnit(st, &doc), nil |
855 | 1052 | } | 1052 | } |
856 | 1053 | 1053 | ||
857 | 1054 | // DestroyUnits destroys the units with the specified names. | ||
858 | 1055 | func (st *State) DestroyUnits(names ...string) (err error) { | ||
859 | 1056 | // TODO(rog) make this a transaction? | ||
860 | 1057 | var errs []string | ||
861 | 1058 | for _, name := range names { | ||
862 | 1059 | unit, err := st.Unit(name) | ||
863 | 1060 | switch { | ||
864 | 1061 | case errors.IsNotFoundError(err): | ||
865 | 1062 | err = fmt.Errorf("unit %q does not exist", name) | ||
866 | 1063 | case err != nil: | ||
867 | 1064 | case unit.Life() != Alive: | ||
868 | 1065 | continue | ||
869 | 1066 | case unit.IsPrincipal(): | ||
870 | 1067 | err = unit.Destroy() | ||
871 | 1068 | default: | ||
872 | 1069 | err = fmt.Errorf("unit %q is a subordinate", name) | ||
873 | 1070 | } | ||
874 | 1071 | if err != nil { | ||
875 | 1072 | errs = append(errs, err.Error()) | ||
876 | 1073 | } | ||
877 | 1074 | } | ||
878 | 1075 | return destroyErr("units", names, errs) | ||
879 | 1076 | } | ||
880 | 1077 | |||
881 | 1078 | // DestroyMachines destroys the machines with the specified ids. | ||
882 | 1079 | func (st *State) DestroyMachines(ids ...string) (err error) { | ||
883 | 1080 | var errs []string | ||
884 | 1081 | for _, id := range ids { | ||
885 | 1082 | machine, err := st.Machine(id) | ||
886 | 1083 | switch { | ||
887 | 1084 | case errors.IsNotFoundError(err): | ||
888 | 1085 | err = fmt.Errorf("machine %s does not exist", id) | ||
889 | 1086 | case err != nil: | ||
890 | 1087 | case machine.Life() != Alive: | ||
891 | 1088 | continue | ||
892 | 1089 | default: | ||
893 | 1090 | err = machine.Destroy() | ||
894 | 1091 | } | ||
895 | 1092 | if err != nil { | ||
896 | 1093 | errs = append(errs, err.Error()) | ||
897 | 1094 | } | ||
898 | 1095 | } | ||
899 | 1096 | return destroyErr("machines", ids, errs) | ||
900 | 1097 | } | ||
901 | 1098 | |||
902 | 1099 | func destroyErr(desc string, ids, errs []string) error { | ||
903 | 1100 | if len(errs) == 0 { | ||
904 | 1101 | return nil | ||
905 | 1102 | } | ||
906 | 1103 | msg := "some %s were not destroyed" | ||
907 | 1104 | if len(errs) == len(ids) { | ||
908 | 1105 | msg = "no %s were destroyed" | ||
909 | 1106 | } | ||
910 | 1107 | msg = fmt.Sprintf(msg, desc) | ||
911 | 1108 | return fmt.Errorf("%s: %s", msg, strings.Join(errs, "; ")) | ||
912 | 1109 | } | ||
913 | 1110 | |||
914 | 1111 | // ForceDestroyMachines queues the machines with the specified ids for | ||
915 | 1112 | // complete removal, including the destruction of all units on the machine. | ||
916 | 1113 | func (st *State) ForceDestroyMachines(ids ...string) error { | ||
917 | 1114 | var ops []txn.Op | ||
918 | 1115 | for _, id := range ids { | ||
919 | 1116 | ops = append(ops, st.newCleanupOp("machine", id)) | ||
920 | 1117 | } | ||
921 | 1118 | return st.runTransaction(ops) | ||
922 | 1119 | } | ||
923 | 1120 | |||
924 | 1121 | // AssignUnit places the unit on a machine. Depending on the policy, and the | 1054 | // AssignUnit places the unit on a machine. Depending on the policy, and the |
925 | 1122 | // state of the environment, this may lead to new instances being launched | 1055 | // state of the environment, this may lead to new instances being launched |
926 | 1123 | // within the environment. | 1056 | // within the environment. |
927 | 1124 | 1057 | ||
928 | === modified file 'state/unit_test.go' | |||
929 | --- state/unit_test.go 2013-11-07 17:30:38 +0000 | |||
930 | +++ state/unit_test.go 2013-11-12 10:22:12 +0000 | |||
931 | @@ -435,49 +435,12 @@ | |||
932 | 435 | c.Assert(err, gc.ErrorMatches, `unit "wordpress/0" is dead`) | 435 | c.Assert(err, gc.ErrorMatches, `unit "wordpress/0" is dead`) |
933 | 436 | } | 436 | } |
934 | 437 | 437 | ||
935 | 438 | func (s *UnitSuite) TestDestroyPrincipalUnits(c *gc.C) { | ||
936 | 439 | preventUnitDestroyRemove(c, s.unit) | ||
937 | 440 | for i := 0; i < 4; i++ { | ||
938 | 441 | unit, err := s.service.AddUnit() | ||
939 | 442 | c.Assert(err, gc.IsNil) | ||
940 | 443 | preventUnitDestroyRemove(c, unit) | ||
941 | 444 | } | ||
942 | 445 | |||
943 | 446 | // Destroy 2 of them; check they become Dying. | ||
944 | 447 | err := s.State.DestroyUnits("wordpress/0", "wordpress/1") | ||
945 | 448 | c.Assert(err, gc.IsNil) | ||
946 | 449 | s.assertUnitLife(c, "wordpress/0", state.Dying) | ||
947 | 450 | s.assertUnitLife(c, "wordpress/1", state.Dying) | ||
948 | 451 | |||
949 | 452 | // Try to destroy an Alive one and a Dying one; check | ||
950 | 453 | // it destroys the Alive one and ignores the Dying one. | ||
951 | 454 | err = s.State.DestroyUnits("wordpress/2", "wordpress/0") | ||
952 | 455 | c.Assert(err, gc.IsNil) | ||
953 | 456 | s.assertUnitLife(c, "wordpress/2", state.Dying) | ||
954 | 457 | |||
955 | 458 | // Try to destroy an Alive one along with a nonexistent one; check that | ||
956 | 459 | // the valid instruction is followed but the invalid one is warned about. | ||
957 | 460 | err = s.State.DestroyUnits("boojum/123", "wordpress/3") | ||
958 | 461 | c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "boojum/123" does not exist`) | ||
959 | 462 | s.assertUnitLife(c, "wordpress/3", state.Dying) | ||
960 | 463 | |||
961 | 464 | // Make one Dead, and destroy an Alive one alongside it; check no errors. | ||
962 | 465 | wp0, err := s.State.Unit("wordpress/0") | ||
963 | 466 | c.Assert(err, gc.IsNil) | ||
964 | 467 | err = wp0.EnsureDead() | ||
965 | 468 | c.Assert(err, gc.IsNil) | ||
966 | 469 | err = s.State.DestroyUnits("wordpress/0", "wordpress/4") | ||
967 | 470 | c.Assert(err, gc.IsNil) | ||
968 | 471 | s.assertUnitLife(c, "wordpress/0", state.Dead) | ||
969 | 472 | s.assertUnitLife(c, "wordpress/4", state.Dying) | ||
970 | 473 | } | ||
971 | 474 | |||
972 | 475 | func (s *UnitSuite) TestDestroySetStatusRetry(c *gc.C) { | 438 | func (s *UnitSuite) TestDestroySetStatusRetry(c *gc.C) { |
973 | 476 | defer state.SetRetryHooks(c, s.State, func() { | 439 | defer state.SetRetryHooks(c, s.State, func() { |
974 | 477 | err := s.unit.SetStatus(params.StatusStarted, "", nil) | 440 | err := s.unit.SetStatus(params.StatusStarted, "", nil) |
975 | 478 | c.Assert(err, gc.IsNil) | 441 | c.Assert(err, gc.IsNil) |
976 | 479 | }, func() { | 442 | }, func() { |
978 | 480 | assertUnitLife(c, s.unit, state.Dying) | 443 | assertLife(c, s.unit, state.Dying) |
979 | 481 | }).Check() | 444 | }).Check() |
980 | 482 | err := s.unit.Destroy() | 445 | err := s.unit.Destroy() |
981 | 483 | c.Assert(err, gc.IsNil) | 446 | c.Assert(err, gc.IsNil) |
982 | @@ -488,7 +451,7 @@ | |||
983 | 488 | err := s.unit.SetCharmURL(s.charm.URL()) | 451 | err := s.unit.SetCharmURL(s.charm.URL()) |
984 | 489 | c.Assert(err, gc.IsNil) | 452 | c.Assert(err, gc.IsNil) |
985 | 490 | }, func() { | 453 | }, func() { |
987 | 491 | assertUnitRemoved(c, s.unit) | 454 | assertRemoved(c, s.unit) |
988 | 492 | }).Check() | 455 | }).Check() |
989 | 493 | err := s.unit.Destroy() | 456 | err := s.unit.Destroy() |
990 | 494 | c.Assert(err, gc.IsNil) | 457 | c.Assert(err, gc.IsNil) |
991 | @@ -505,7 +468,7 @@ | |||
992 | 505 | err := s.unit.SetCharmURL(newCharm.URL()) | 468 | err := s.unit.SetCharmURL(newCharm.URL()) |
993 | 506 | c.Assert(err, gc.IsNil) | 469 | c.Assert(err, gc.IsNil) |
994 | 507 | }, func() { | 470 | }, func() { |
996 | 508 | assertUnitRemoved(c, s.unit) | 471 | assertRemoved(c, s.unit) |
997 | 509 | }).Check() | 472 | }).Check() |
998 | 510 | err = s.unit.Destroy() | 473 | err = s.unit.Destroy() |
999 | 511 | c.Assert(err, gc.IsNil) | 474 | c.Assert(err, gc.IsNil) |
1000 | @@ -519,7 +482,7 @@ | |||
1001 | 519 | err := s.unit.AssignToMachine(machine) | 482 | err := s.unit.AssignToMachine(machine) |
1002 | 520 | c.Assert(err, gc.IsNil) | 483 | c.Assert(err, gc.IsNil) |
1003 | 521 | }, func() { | 484 | }, func() { |
1005 | 522 | assertUnitRemoved(c, s.unit) | 485 | assertRemoved(c, s.unit) |
1006 | 523 | // Also check the unit ref was properly removed from the machine doc -- | 486 | // Also check the unit ref was properly removed from the machine doc -- |
1007 | 524 | // if it weren't, we wouldn't be able to make the machine Dead. | 487 | // if it weren't, we wouldn't be able to make the machine Dead. |
1008 | 525 | err := machine.EnsureDead() | 488 | err := machine.EnsureDead() |
1009 | @@ -539,7 +502,7 @@ | |||
1010 | 539 | err := s.unit.UnassignFromMachine() | 502 | err := s.unit.UnassignFromMachine() |
1011 | 540 | c.Assert(err, gc.IsNil) | 503 | c.Assert(err, gc.IsNil) |
1012 | 541 | }, func() { | 504 | }, func() { |
1014 | 542 | assertUnitRemoved(c, s.unit) | 505 | assertRemoved(c, s.unit) |
1015 | 543 | }).Check() | 506 | }).Check() |
1016 | 544 | err = s.unit.Destroy() | 507 | err = s.unit.Destroy() |
1017 | 545 | c.Assert(err, gc.IsNil) | 508 | c.Assert(err, gc.IsNil) |
1018 | @@ -550,7 +513,7 @@ | |||
1019 | 550 | err := s.unit.Destroy() | 513 | err := s.unit.Destroy() |
1020 | 551 | c.Assert(err, gc.IsNil) | 514 | c.Assert(err, gc.IsNil) |
1021 | 552 | c.Assert(s.unit.Life(), gc.Equals, state.Dying) | 515 | c.Assert(s.unit.Life(), gc.Equals, state.Dying) |
1023 | 553 | assertUnitRemoved(c, s.unit) | 516 | assertRemoved(c, s.unit) |
1024 | 554 | } | 517 | } |
1025 | 555 | 518 | ||
1026 | 556 | func (s *UnitSuite) TestCannotShortCircuitDestroyWithSubordinates(c *gc.C) { | 519 | func (s *UnitSuite) TestCannotShortCircuitDestroyWithSubordinates(c *gc.C) { |
1027 | @@ -568,7 +531,7 @@ | |||
1028 | 568 | err = s.unit.Destroy() | 531 | err = s.unit.Destroy() |
1029 | 569 | c.Assert(err, gc.IsNil) | 532 | c.Assert(err, gc.IsNil) |
1030 | 570 | c.Assert(s.unit.Life(), gc.Equals, state.Dying) | 533 | c.Assert(s.unit.Life(), gc.Equals, state.Dying) |
1032 | 571 | assertUnitLife(c, s.unit, state.Dying) | 534 | assertLife(c, s.unit, state.Dying) |
1033 | 572 | } | 535 | } |
1034 | 573 | 536 | ||
1035 | 574 | func (s *UnitSuite) TestCannotShortCircuitDestroyWithStatus(c *gc.C) { | 537 | func (s *UnitSuite) TestCannotShortCircuitDestroyWithStatus(c *gc.C) { |
1036 | @@ -592,7 +555,7 @@ | |||
1037 | 592 | err = unit.Destroy() | 555 | err = unit.Destroy() |
1038 | 593 | c.Assert(err, gc.IsNil) | 556 | c.Assert(err, gc.IsNil) |
1039 | 594 | c.Assert(unit.Life(), gc.Equals, state.Dying) | 557 | c.Assert(unit.Life(), gc.Equals, state.Dying) |
1041 | 595 | assertUnitLife(c, unit, state.Dying) | 558 | assertLife(c, unit, state.Dying) |
1042 | 596 | } | 559 | } |
1043 | 597 | } | 560 | } |
1044 | 598 | 561 | ||
1045 | @@ -610,56 +573,25 @@ | |||
1046 | 610 | err = s.unit.Destroy() | 573 | err = s.unit.Destroy() |
1047 | 611 | c.Assert(err, gc.IsNil) | 574 | c.Assert(err, gc.IsNil) |
1048 | 612 | c.Assert(s.unit.Life(), gc.Equals, state.Dying) | 575 | c.Assert(s.unit.Life(), gc.Equals, state.Dying) |
1092 | 613 | assertUnitRemoved(c, s.unit) | 576 | assertRemoved(c, s.unit) |
1093 | 614 | } | 577 | } |
1094 | 615 | 578 | ||
1095 | 616 | func (s *UnitSuite) TestDestroySubordinateUnits(c *gc.C) { | 579 | func assertLife(c *gc.C, entity state.Living, life state.Life) { |
1096 | 617 | lgsch := s.AddTestingCharm(c, "logging") | 580 | c.Assert(entity.Refresh(), gc.IsNil) |
1097 | 618 | _, err := s.State.AddService("logging", lgsch) | 581 | c.Assert(entity.Life(), gc.Equals, life) |
1098 | 619 | c.Assert(err, gc.IsNil) | 582 | } |
1099 | 620 | eps, err := s.State.InferEndpoints([]string{"logging", "wordpress"}) | 583 | |
1100 | 621 | c.Assert(err, gc.IsNil) | 584 | func assertRemoved(c *gc.C, entity state.Living) { |
1101 | 622 | rel, err := s.State.AddRelation(eps...) | 585 | err := entity.Refresh() |
1059 | 623 | c.Assert(err, gc.IsNil) | ||
1060 | 624 | ru, err := rel.Unit(s.unit) | ||
1061 | 625 | c.Assert(err, gc.IsNil) | ||
1062 | 626 | err = ru.EnterScope(nil) | ||
1063 | 627 | c.Assert(err, gc.IsNil) | ||
1064 | 628 | |||
1065 | 629 | // Try to destroy the subordinate alone; check it fails. | ||
1066 | 630 | err = s.State.DestroyUnits("logging/0") | ||
1067 | 631 | c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`) | ||
1068 | 632 | s.assertUnitLife(c, "logging/0", state.Alive) | ||
1069 | 633 | |||
1070 | 634 | // Try to destroy the principal and the subordinate together; check it warns | ||
1071 | 635 | // about the subordinate, but destroys the one it can. (The principal unit | ||
1072 | 636 | // agent will be resposible for destroying the subordinate.) | ||
1073 | 637 | err = s.State.DestroyUnits("wordpress/0", "logging/0") | ||
1074 | 638 | c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "logging/0" is a subordinate`) | ||
1075 | 639 | s.assertUnitLife(c, "wordpress/0", state.Dying) | ||
1076 | 640 | s.assertUnitLife(c, "logging/0", state.Alive) | ||
1077 | 641 | } | ||
1078 | 642 | |||
1079 | 643 | func (s *UnitSuite) assertUnitLife(c *gc.C, name string, life state.Life) { | ||
1080 | 644 | unit, err := s.State.Unit(name) | ||
1081 | 645 | c.Assert(err, gc.IsNil) | ||
1082 | 646 | assertUnitLife(c, unit, life) | ||
1083 | 647 | } | ||
1084 | 648 | |||
1085 | 649 | func assertUnitLife(c *gc.C, unit *state.Unit, life state.Life) { | ||
1086 | 650 | c.Assert(unit.Refresh(), gc.IsNil) | ||
1087 | 651 | c.Assert(unit.Life(), gc.Equals, life) | ||
1088 | 652 | } | ||
1089 | 653 | |||
1090 | 654 | func assertUnitRemoved(c *gc.C, unit *state.Unit) { | ||
1091 | 655 | err := unit.Refresh() | ||
1102 | 656 | c.Assert(err, jc.Satisfies, errors.IsNotFoundError) | 586 | c.Assert(err, jc.Satisfies, errors.IsNotFoundError) |
1109 | 657 | err = unit.Destroy() | 587 | err = entity.Destroy() |
1110 | 658 | c.Assert(err, gc.IsNil) | 588 | c.Assert(err, gc.IsNil) |
1111 | 659 | err = unit.EnsureDead() | 589 | if entity, ok := entity.(state.AgentLiving); ok { |
1112 | 660 | c.Assert(err, gc.IsNil) | 590 | err = entity.EnsureDead() |
1113 | 661 | err = unit.Remove() | 591 | c.Assert(err, gc.IsNil) |
1114 | 662 | c.Assert(err, gc.IsNil) | 592 | err = entity.Remove() |
1115 | 593 | c.Assert(err, gc.IsNil) | ||
1116 | 594 | } | ||
1117 | 663 | } | 595 | } |
1118 | 664 | 596 | ||
1119 | 665 | func (s *UnitSuite) TestTag(c *gc.C) { | 597 | func (s *UnitSuite) TestTag(c *gc.C) { |
Reviewers: mp+194764_ code.launchpad. net,
Message:
Please take a look.
Description:
fix lp:1089291
...which involved a lot more churn than I wanted, mainly because those
DestroyWhatevers methods really shouldn't ever have been on state in the
first place, and moving them turned out to be a ridiculous hassle.
Despite the mess, this branch really just involves:
1) moving multi-Destroy methods off state and onto the api moving/ renaming test things
2) fixing/
3) hooking up a --force flag to destroy-machine
4) oh, and making the relevant api.Client methods a teeny tiny bit
consistent.
https:/ /code.launchpad .net/~fwereade/ juju-core/ api-force- destroy- machines/ +merge/ 194764
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/24790044/
Affected files (+363, -284 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 test.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
M state/cleanup_
M state/life.go
M state/life_test.go
M state/machine.go
M state/machine_
M state/service_
M state/state.go
M state/unit_test.go