Merge lp:~fwereade/juju-core/api-force-destroy-machines into lp:~go-bot/juju-core/trunk

Proposed by William Reade
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
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/moving/renaming test things
3) hooking up a --force flag to destroy-machine
4) oh, and making the relevant api.Client methods a teeny tiny bit
   consistent.

https://codereview.appspot.com/24790044/

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/moving/renaming test things
3) hooking up a --force flag to destroy-machine
4) oh, and making the relevant api.Client methods a teeny tiny bit
   consistent.

https://codereview.appspot.com/24790044/

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

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
2) fixing/moving/renaming test things
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):
   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
   M state/cleanup_test.go
   M state/life.go
   M state/life_test.go
   M state/machine.go
   M state/machine_test.go
   M state/service_test.go
   M state/state.go
   M state/unit_test.go

Revision history for this message
Ian Booth (wallyworld) wrote :
Download full text (3.4 KiB)

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://codereview.appspot.com/24790044/diff/1/cmd/juju/destroymachine.go
File cmd/juju/destroymachine.go (right):

https://codereview.appspot.com/24790044/diff/1/cmd/juju/destroymachine.go#newcode34
cmd/juju/destroymachine.go:34: f.BoolVar(&c.Force, "force", false,
"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://codereview.appspot.com/24790044/diff/1/cmd/juju/destroymachine_test.go
File cmd/juju/destroymachine_test.go (right):

https://codereview.appspot.com/24790044/diff/1/cmd/juju/destroymachine_test.go#newcode48
cmd/juju/destroymachine_test.go:48: // is warned about.
The above comment needs rewording now the test has been split up

https://codereview.appspot.com/24790044/diff/1/state/api/client.go
File state/api/client.go (right):

https://codereview.appspot.com/24790044/diff/1/state/api/client.go#newcode154
state/api/client.go:154: func (c *Client) ForceDestroyMachines(machines
...string) error {
It makes me sad that we need to add a whole new endpoint because Go
doesn't support default parameter values :-(

https://codereview.appspot.com/24790044/diff/1/state/apiserver/client/client_test.go
File state/apiserver/client/client_test.go (right):

https://codereview.appspot.com/24790044/diff/1/state/apiserver/client/client_test.go#newcode549
state/apiserver/client/client_test.go:549: assertLife(c, m0,
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://codereview.appspot.com/24790044/diff/1/state/cleanup_test.go
File state/cleanup_test.go (right):

https://codereview.appspot.com/24790044/diff/1/state/cleanup_test.go#newcode93
state/cleanup_test.go:93: func (s *CleanupSuite)
TestForceDestroyMachineErrors(c *gc.C) {
The code in this test can definitely be extracted out into a helper

func (s *CleanupSuite) assertForceDestroyMachineError(c *gc.C, job
state.MachineJob) {
     m, err := s.State.AddMachine("quantal", job)
     c.Assert(err, gc.IsNil)
     err = environManager.ForceDestroy()
     c.Assert(err, gc.ErrorMatches, "machine [0-9] is required by the
environment")
     s.assertDoesNotNeedCleanup(c)
     assertLife(c, m, state.Alive)
}

https://codereview.appspot.com/24790044/diff/1/state/machine.go
File state/machin...

Read more...

Revision history for this message
John A Meinel (jameinel) wrote :

just some of my thoughts, not a full review.

https://codereview.appspot.com/24790044/diff/1/state/api/client.go
File state/api/client.go (right):

https://codereview.appspot.com/24790044/diff/1/state/api/client.go#newcode154
state/api/client.go:154: func (c *Client) ForceDestroyMachines(machines
...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) DestroyServiceUnits(unitNames []string) error {
+func (c *Client) DestroyServiceUnits(unitNames ...string) error {

was an API break, but this is all just within the client process.

so we *could* spell it:

DestroyMachines(force bool, machines ...string)

I'm guessing it was a bit ackward and William wanted to avoid bool in
APIs especially for a Force sort of function.

https://codereview.appspot.com/24790044/diff/1/state/apiserver/client/client_test.go
File state/apiserver/client/client_test.go (right):

https://codereview.appspot.com/24790044/diff/1/state/apiserver/client/client_test.go#newcode549
state/apiserver/client/client_test.go:549: assertLife(c, m0,
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.

https://codereview.appspot.com/24790044/

Revision history for this message
William Reade (fwereade) wrote :
Download full text (4.9 KiB)

Machine lifecycle is out of scope; SOA concepts demand more discussion
in a separate forum; I think I've addressed the rest.

https://codereview.appspot.com/24790044/diff/1/cmd/juju/destroymachine.go
File cmd/juju/destroymachine.go (right):

https://codereview.appspot.com/24790044/diff/1/cmd/juju/destroymachine.go#newcode34
cmd/juju/destroymachine.go:34: f.BoolVar(&c.Force, "force", false,
"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://codereview.appspot.com/24790044/diff/1/cmd/juju/destroymachine_test.go
File cmd/juju/destroymachine_test.go (right):

https://codereview.appspot.com/24790044/diff/1/cmd/juju/destroymachine_test.go#newcode48
cmd/juju/destroymachine_test.go:48: // is warned about.
On 2013/11/12 00:43:42, wallyworld wrote:
> The above comment needs rewording now the test has been split up

Whoops, thanks.

https://codereview.appspot.com/24790044/diff/1/state/api/client.go
File state/api/client.go (right):

https://codereview.appspot.com/24790044/diff/1/state/api/client.go#newcode154
state/api/client.go:154: func (c *Client) ForceDestroyMachines(machines
...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) DestroyServiceUnits(unitNames []string) error {
> +func (c *Client) DestroyServiceUnits(unitNames ...string) error {

> was an API break, but this is all just within the client process.

> so we *could* spell it:

> DestroyMachines(force bool, machines ...string)

> 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://codereview.appspot.com/24790044/diff/1/state/apiserver/client/client_test.go
File state/apiserver/client/client_test.go (right):

https://codereview.appspot.com/24790044/diff/1/state/apiserver/client/client_test.go#newcode549
state/apiserver/client/client_test.go:549: assertLife(c, m0,
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 ...

Read more...

Revision history for this message
William Reade (fwereade) wrote :
Revision history for this message
Ian Booth (wallyworld) wrote :
Revision history for this message
John A Meinel (jameinel) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'cmd/juju/destroymachine.go'
--- cmd/juju/destroymachine.go 2013-11-01 06:53:54 +0000
+++ cmd/juju/destroymachine.go 2013-11-12 10:22:12 +0000
@@ -6,6 +6,8 @@
6import (6import (
7 "fmt"7 "fmt"
88
9 "launchpad.net/gnuflag"
10
9 "launchpad.net/juju-core/cmd"11 "launchpad.net/juju-core/cmd"
10 "launchpad.net/juju-core/juju"12 "launchpad.net/juju-core/juju"
11 "launchpad.net/juju-core/names"13 "launchpad.net/juju-core/names"
@@ -15,18 +17,30 @@
15type DestroyMachineCommand struct {17type DestroyMachineCommand struct {
16 cmd.EnvCommandBase18 cmd.EnvCommandBase
17 MachineIds []string19 MachineIds []string
20 Force bool
18}21}
1922
23const destroyMachineDoc = `
24Machines that are responsible for the environment cannot be destroyed. Machines
25running units or containers can only be destroyed with the --force flag; doing
26so will also destroy all those units and containers without giving them any
27opportunity to shut down cleanly.
28`
29
20func (c *DestroyMachineCommand) Info() *cmd.Info {30func (c *DestroyMachineCommand) Info() *cmd.Info {
21 return &cmd.Info{31 return &cmd.Info{
22 Name: "destroy-machine",32 Name: "destroy-machine",
23 Args: "<machine> ...",33 Args: "<machine> ...",
24 Purpose: "destroy machines",34 Purpose: "destroy machines",
25 Doc: "Machines that have assigned units, or are responsible for the environment, cannot be destroyed.",35 Doc: "",
26 Aliases: []string{"terminate-machine"},36 Aliases: []string{"terminate-machine"},
27 }37 }
28}38}
2939
40func (c *DestroyMachineCommand) SetFlags(f *gnuflag.FlagSet) {
41 f.BoolVar(&c.Force, "force", false, "completely remove machine and all dependencies")
42}
43
30func (c *DestroyMachineCommand) Init(args []string) error {44func (c *DestroyMachineCommand) Init(args []string) error {
31 if len(args) == 0 {45 if len(args) == 0 {
32 return fmt.Errorf("no machines specified")46 return fmt.Errorf("no machines specified")
@@ -46,5 +60,8 @@
46 return err60 return err
47 }61 }
48 defer apiclient.Close()62 defer apiclient.Close()
63 if c.Force {
64 return apiclient.ForceDestroyMachines(c.MachineIds...)
65 }
49 return apiclient.DestroyMachines(c.MachineIds...)66 return apiclient.DestroyMachines(c.MachineIds...)
50}67}
5168
=== modified file 'cmd/juju/destroymachine_test.go'
--- cmd/juju/destroymachine_test.go 2013-09-27 07:05:45 +0000
+++ cmd/juju/destroymachine_test.go 2013-11-12 10:22:12 +0000
@@ -6,9 +6,11 @@
6import (6import (
7 gc "launchpad.net/gocheck"7 gc "launchpad.net/gocheck"
88
9 "launchpad.net/juju-core/errors"
9 jujutesting "launchpad.net/juju-core/juju/testing"10 jujutesting "launchpad.net/juju-core/juju/testing"
10 "launchpad.net/juju-core/state"11 "launchpad.net/juju-core/state"
11 "launchpad.net/juju-core/testing"12 "launchpad.net/juju-core/testing"
13 jc "launchpad.net/juju-core/testing/checkers"
12)14)
1315
14type DestroyMachineSuite struct {16type DestroyMachineSuite struct {
@@ -22,7 +24,7 @@
22 return err24 return err
23}25}
2426
25func (s *DestroyMachineSuite) TestDestroyMachine(c *gc.C) {27func (s *DestroyMachineSuite) TestDestroyMachineWithUnit(c *gc.C) {
26 // Create a machine running a unit.28 // Create a machine running a unit.
27 testing.Charms.BundlePath(s.SeriesPath, "riak")29 testing.Charms.BundlePath(s.SeriesPath, "riak")
28 err := runDeploy(c, "local:riak", "riak")30 err := runDeploy(c, "local:riak", "riak")
@@ -38,19 +40,16 @@
38 // Try to destroy the machine and fail.40 // Try to destroy the machine and fail.
39 err = runDestroyMachine(c, "0")41 err = runDestroyMachine(c, "0")
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`)
43}
4144
42 // Remove the unit, and try to destroy the machine along with another that45func (s *DestroyMachineSuite) TestDestroyEmptyMachine(c *gc.C) {
43 // doesn't exist; check that the machine is destroyed, but the missing one46 // Destroy an empty machine alongside a state server; only the empty machine
44 // is warned about.47 // gets destroyed.
45 err = u.Destroy()48 m0, err := s.State.AddMachine("quantal", state.JobHostUnits)
46 c.Assert(err, gc.IsNil)
47 err = u.EnsureDead()
48 c.Assert(err, gc.IsNil)
49 err = u.Remove()
50 c.Assert(err, gc.IsNil)49 c.Assert(err, gc.IsNil)
51 err = runDestroyMachine(c, "0", "1")50 err = runDestroyMachine(c, "0", "1")
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`)
53 m0, err := s.State.Machine("0")52 err = m0.Refresh()
54 c.Assert(err, gc.IsNil)53 c.Assert(err, gc.IsNil)
55 c.Assert(m0.Life(), gc.Equals, state.Dying)54 c.Assert(m0.Life(), gc.Equals, state.Dying)
5655
@@ -60,9 +59,13 @@
60 err = m0.Refresh()59 err = m0.Refresh()
61 c.Assert(err, gc.IsNil)60 c.Assert(err, gc.IsNil)
62 c.Assert(m0.Life(), gc.Equals, state.Dying)61 c.Assert(m0.Life(), gc.Equals, state.Dying)
62}
6363
64 // As is destroying a Dead machine; destroying it alongside a JobManageEnviron64func (s *DestroyMachineSuite) TestDestroyDeadMachine(c *gc.C) {
65 // machine complains only about the JMA machine.65 // Destroying a Dead machine is a no-op; destroying it alongside a JobManageEnviron
66 // machine complains only about the JME machine.
67 m0, err := s.State.AddMachine("quantal", state.JobHostUnits)
68 c.Assert(err, gc.IsNil)
66 err = m0.EnsureDead()69 err = m0.EnsureDead()
67 c.Assert(err, gc.IsNil)70 c.Assert(err, gc.IsNil)
68 m1, err := s.State.AddMachine("quantal", state.JobManageEnviron)71 m1, err := s.State.AddMachine("quantal", state.JobManageEnviron)
@@ -75,9 +78,43 @@
75 err = m1.Refresh()78 err = m1.Refresh()
76 c.Assert(err, gc.IsNil)79 c.Assert(err, gc.IsNil)
77 c.Assert(m1.Life(), gc.Equals, state.Alive)80 c.Assert(m1.Life(), gc.Equals, state.Alive)
7881}
82
83func (s *DestroyMachineSuite) TestForce(c *gc.C) {
84 // Create a machine running a unit.
85 testing.Charms.BundlePath(s.SeriesPath, "riak")
86 err := runDeploy(c, "local:riak", "riak")
87 c.Assert(err, gc.IsNil)
88
89 // Get the state entities to allow sane testing.
90 u, err := s.State.Unit("riak/0")
91 c.Assert(err, gc.IsNil)
92 m0, err := s.State.Machine("0")
93 c.Assert(err, gc.IsNil)
94
95 // Create a manager machine.
96 m1, err := s.State.AddMachine("quantal", state.JobManageEnviron)
97 c.Assert(err, gc.IsNil)
98
99 // Try to force-destroy the machines.
100 err = runDestroyMachine(c, "0", "1", "--force")
101 c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 1 is required by the environment`)
102
103 // Clean up, check state.
104 err = s.State.Cleanup()
105 c.Assert(err, gc.IsNil)
106 err = m0.Refresh()
107 c.Assert(err, jc.Satisfies, errors.IsNotFoundError)
108 err = u.Refresh()
109 c.Assert(err, jc.Satisfies, errors.IsNotFoundError)
110 err = m1.Refresh()
111 c.Assert(err, gc.IsNil)
112 c.Assert(m1.Life(), gc.Equals, state.Alive)
113}
114
115func (s *DestroyMachineSuite) TestBadArgs(c *gc.C) {
79 // Check invalid args.116 // Check invalid args.
80 err = runDestroyMachine(c)117 err := runDestroyMachine(c)
81 c.Assert(err, gc.ErrorMatches, `no machines specified`)118 c.Assert(err, gc.ErrorMatches, `no machines specified`)
82 err = runDestroyMachine(c, "1", "2", "nonsense", "rubbish")119 err = runDestroyMachine(c, "1", "2", "nonsense", "rubbish")
83 c.Assert(err, gc.ErrorMatches, `invalid machine id "nonsense"`)120 c.Assert(err, gc.ErrorMatches, `invalid machine id "nonsense"`)
84121
=== modified file 'cmd/juju/destroyunit.go'
--- cmd/juju/destroyunit.go 2013-11-05 01:22:35 +0000
+++ cmd/juju/destroyunit.go 2013-11-12 10:22:12 +0000
@@ -48,5 +48,5 @@
48 return err48 return err
49 }49 }
50 defer client.Close()50 defer client.Close()
51 return client.DestroyServiceUnits(c.UnitNames)51 return client.DestroyServiceUnits(c.UnitNames...)
52}52}
5353
=== modified file 'cmd/jujud/machine_test.go'
--- cmd/jujud/machine_test.go 2013-11-11 10:57:06 +0000
+++ cmd/jujud/machine_test.go 2013-11-12 10:22:12 +0000
@@ -78,7 +78,7 @@
78 err = m.SetPassword(initialMachinePassword)78 err = m.SetPassword(initialMachinePassword)
79 c.Assert(err, gc.IsNil)79 c.Assert(err, gc.IsNil)
80 tag := names.MachineTag(m.Id())80 tag := names.MachineTag(m.Id())
81 if m.IsStateServer() {81 if m.IsManager() {
82 err = m.SetMongoPassword(initialMachinePassword)82 err = m.SetMongoPassword(initialMachinePassword)
83 c.Assert(err, gc.IsNil)83 c.Assert(err, gc.IsNil)
84 config, tools = s.agentSuite.primeStateAgent(c, tag, initialMachinePassword)84 config, tools = s.agentSuite.primeStateAgent(c, tag, initialMachinePassword)
8585
=== modified file 'state/api/client.go'
--- state/api/client.go 2013-11-08 06:36:55 +0000
+++ state/api/client.go 2013-11-12 10:22:12 +0000
@@ -150,6 +150,12 @@
150 return c.st.Call("Client", "", "DestroyMachines", params, nil)150 return c.st.Call("Client", "", "DestroyMachines", params, nil)
151}151}
152152
153// ForceDestroyMachines removes a given set of machines and all associated units.
154func (c *Client) ForceDestroyMachines(machines ...string) error {
155 params := params.DestroyMachines{Force: true, MachineNames: machines}
156 return c.st.Call("Client", "", "DestroyMachines", params, nil)
157}
158
153// ServiceExpose changes the juju-managed firewall to expose any ports that159// ServiceExpose changes the juju-managed firewall to expose any ports that
154// were also explicitly marked by units as open.160// were also explicitly marked by units as open.
155func (c *Client) ServiceExpose(service string) error {161func (c *Client) ServiceExpose(service string) error {
@@ -207,7 +213,7 @@
207}213}
208214
209// DestroyServiceUnits decreases the number of units dedicated to a service.215// DestroyServiceUnits decreases the number of units dedicated to a service.
210func (c *Client) DestroyServiceUnits(unitNames []string) error {216func (c *Client) DestroyServiceUnits(unitNames ...string) error {
211 params := params.DestroyServiceUnits{unitNames}217 params := params.DestroyServiceUnits{unitNames}
212 return c.st.Call("Client", "", "DestroyServiceUnits", params, nil)218 return c.st.Call("Client", "", "DestroyServiceUnits", params, nil)
213}219}
214220
=== modified file 'state/api/params/params.go'
--- state/api/params/params.go 2013-11-08 06:36:55 +0000
+++ state/api/params/params.go 2013-11-12 10:22:12 +0000
@@ -93,6 +93,7 @@
93// DestroyMachines holds parameters for the DestroyMachines call.93// DestroyMachines holds parameters for the DestroyMachines call.
94type DestroyMachines struct {94type DestroyMachines struct {
95 MachineNames []string95 MachineNames []string
96 Force bool
96}97}
9798
98// ServiceDeploy holds the parameters for making the ServiceDeploy call.99// ServiceDeploy holds the parameters for making the ServiceDeploy call.
99100
=== modified file 'state/apiserver/client/client.go'
--- state/apiserver/client/client.go 2013-11-08 06:36:55 +0000
+++ state/apiserver/client/client.go 2013-11-12 10:22:12 +0000
@@ -6,11 +6,13 @@
6import (6import (
7 "errors"7 "errors"
8 "fmt"8 "fmt"
9 "strings"
910
10 "launchpad.net/loggo"11 "launchpad.net/loggo"
1112
12 "launchpad.net/juju-core/charm"13 "launchpad.net/juju-core/charm"
13 "launchpad.net/juju-core/environs"14 "launchpad.net/juju-core/environs"
15 coreerrors "launchpad.net/juju-core/errors"
14 "launchpad.net/juju-core/instance"16 "launchpad.net/juju-core/instance"
15 "launchpad.net/juju-core/juju"17 "launchpad.net/juju-core/juju"
16 "launchpad.net/juju-core/names"18 "launchpad.net/juju-core/names"
@@ -413,7 +415,25 @@
413415
414// DestroyServiceUnits removes a given set of service units.416// DestroyServiceUnits removes a given set of service units.
415func (c *Client) DestroyServiceUnits(args params.DestroyServiceUnits) error {417func (c *Client) DestroyServiceUnits(args params.DestroyServiceUnits) error {
416 return c.api.state.DestroyUnits(args.UnitNames...)418 var errs []string
419 for _, name := range args.UnitNames {
420 unit, err := c.api.state.Unit(name)
421 switch {
422 case coreerrors.IsNotFoundError(err):
423 err = fmt.Errorf("unit %q does not exist", name)
424 case err != nil:
425 case unit.Life() != state.Alive:
426 continue
427 case unit.IsPrincipal():
428 err = unit.Destroy()
429 default:
430 err = fmt.Errorf("unit %q is a subordinate", name)
431 }
432 if err != nil {
433 errs = append(errs, err.Error())
434 }
435 }
436 return destroyErr("units", args.UnitNames, errs)
417}437}
418438
419// ServiceDestroy destroys a given service.439// ServiceDestroy destroys a given service.
@@ -625,7 +645,25 @@
625645
626// DestroyMachines removes a given set of machines.646// DestroyMachines removes a given set of machines.
627func (c *Client) DestroyMachines(args params.DestroyMachines) error {647func (c *Client) DestroyMachines(args params.DestroyMachines) error {
628 return c.api.state.DestroyMachines(args.MachineNames...)648 var errs []string
649 for _, id := range args.MachineNames {
650 machine, err := c.api.state.Machine(id)
651 switch {
652 case coreerrors.IsNotFoundError(err):
653 err = fmt.Errorf("machine %s does not exist", id)
654 case err != nil:
655 case args.Force:
656 err = machine.ForceDestroy()
657 case machine.Life() != state.Alive:
658 continue
659 default:
660 err = machine.Destroy()
661 }
662 if err != nil {
663 errs = append(errs, err.Error())
664 }
665 }
666 return destroyErr("machines", args.MachineNames, errs)
629}667}
630668
631// CharmInfo returns information about the requested charm.669// CharmInfo returns information about the requested charm.
@@ -786,3 +824,15 @@
786 // Now try to apply the new validated config.824 // Now try to apply the new validated config.
787 return c.api.state.SetEnvironConfig(newProviderConfig)825 return c.api.state.SetEnvironConfig(newProviderConfig)
788}826}
827
828func destroyErr(desc string, ids, errs []string) error {
829 if len(errs) == 0 {
830 return nil
831 }
832 msg := "some %s were not destroyed"
833 if len(errs) == len(ids) {
834 msg = "no %s were destroyed"
835 }
836 msg = fmt.Sprintf(msg, desc)
837 return fmt.Errorf("%s: %s", msg, strings.Join(errs, "; "))
838}
789839
=== modified file 'state/apiserver/client/client_test.go'
--- state/apiserver/client/client_test.go 2013-11-08 06:36:55 +0000
+++ state/apiserver/client/client_test.go 2013-11-12 10:22:12 +0000
@@ -493,38 +493,143 @@
493 c.Assert(service.Life(), gc.Not(gc.Equals), state.Alive)493 c.Assert(service.Life(), gc.Not(gc.Equals), state.Alive)
494}494}
495495
496func (s *clientSuite) TestClientDestroyMachines(c *gc.C) {496func assertLife(c *gc.C, entity state.Living, life state.Life) {
497 s.setUpScenario(c)497 err := entity.Refresh()
498 err := s.APIState.Client().DestroyMachines("1")498 c.Assert(err, gc.IsNil)
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)
500 m, err := s.State.AddMachine("trusty", state.JobHostUnits)500}
501 c.Assert(err, gc.IsNil)501
502 err = s.APIState.Client().DestroyMachines(m.Id())502func assertRemoved(c *gc.C, entity state.Living) {
503 c.Assert(err, gc.IsNil)503 err := entity.Refresh()
504 err = m.Refresh()504 c.Assert(err, jc.Satisfies, errors.IsNotFoundError)
505 c.Assert(err, gc.IsNil)505}
506 c.Assert(m.Life(), gc.Not(gc.Equals), state.Alive)506
507}507func (s *clientSuite) setupDestroyMachinesTest(c *gc.C) (*state.Machine, *state.Machine, *state.Machine, *state.Unit) {
508508 m0, err := s.State.AddMachine("quantal", state.JobHostUnits)
509func (s *clientSuite) TestClientDestroyUnits(c *gc.C) {509 c.Assert(err, gc.IsNil)
510 // Setup:510 m1, err := s.State.AddMachine("quantal", state.JobManageEnviron)
511 s.setUpScenario(c)511 c.Assert(err, gc.IsNil)
512 service, err := s.State.Service("wordpress")512 m2, err := s.State.AddMachine("quantal", state.JobHostUnits)
513 c.Assert(err, gc.IsNil)513 c.Assert(err, gc.IsNil)
514 _, err = service.AddUnit()514
515 c.Assert(err, gc.IsNil)515 sch := s.AddTestingCharm(c, "wordpress")
516 // Destroy some units and check the result.516 wordpress, err := s.State.AddService("wordpress", sch)
517 err = s.APIState.Client().DestroyServiceUnits([]string{"wordpress/1", "wordpress/2"})517 c.Assert(err, gc.IsNil)
518 c.Assert(err, gc.IsNil)518 u, err := wordpress.AddUnit()
519 units, err := service.AllUnits()519 c.Assert(err, gc.IsNil)
520 c.Assert(err, gc.IsNil)520 err = u.AssignToMachine(m0)
521 for i, u := range units {521 c.Assert(err, gc.IsNil)
522 if i == 0 {522
523 c.Assert(u.Life(), gc.Equals, state.Alive)523 return m0, m1, m2, u
524 } else {524}
525 c.Assert(u.Life(), gc.Equals, state.Dying)525
526 }526func (s *clientSuite) TestDestroyMachines(c *gc.C) {
527 m0, m1, m2, u := s.setupDestroyMachinesTest(c)
528
529 err := s.APIState.Client().DestroyMachines("0", "1", "2")
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`)
531 assertLife(c, m0, state.Alive)
532 assertLife(c, m1, state.Alive)
533 assertLife(c, m2, state.Dying)
534
535 err = u.UnassignFromMachine()
536 c.Assert(err, gc.IsNil)
537 err = s.APIState.Client().DestroyMachines("0", "1", "2")
538 c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 1 is required by the environment`)
539 assertLife(c, m0, state.Dying)
540 assertLife(c, m1, state.Alive)
541 assertLife(c, m2, state.Dying)
542}
543
544func (s *clientSuite) TestForceDestroyMachines(c *gc.C) {
545 m0, m1, m2, u := s.setupDestroyMachinesTest(c)
546
547 err := s.APIState.Client().ForceDestroyMachines("0", "1", "2")
548 c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 1 is required by the environment`)
549 assertLife(c, m0, state.Alive)
550 assertLife(c, m1, state.Alive)
551 assertLife(c, m2, state.Alive)
552 assertLife(c, u, state.Alive)
553
554 err = s.State.Cleanup()
555 c.Assert(err, gc.IsNil)
556 assertRemoved(c, m0)
557 assertLife(c, m1, state.Alive)
558 assertRemoved(c, m2)
559 assertRemoved(c, u)
560}
561
562func (s *clientSuite) TestDestroyPrincipalUnits(c *gc.C) {
563 wordpress, err := s.State.AddService("wordpress", s.AddTestingCharm(c, "wordpress"))
564 c.Assert(err, gc.IsNil)
565 units := make([]*state.Unit, 5)
566 for i := range units {
567 unit, err := wordpress.AddUnit()
568 c.Assert(err, gc.IsNil)
569 err = unit.SetStatus(params.StatusStarted, "", nil)
570 c.Assert(err, gc.IsNil)
571 units[i] = unit
527 }572 }
573
574 // Destroy 2 of them; check they become Dying.
575 err = s.APIState.Client().DestroyServiceUnits("wordpress/0", "wordpress/1")
576 c.Assert(err, gc.IsNil)
577 assertLife(c, units[0], state.Dying)
578 assertLife(c, units[1], state.Dying)
579
580 // Try to destroy an Alive one and a Dying one; check
581 // it destroys the Alive one and ignores the Dying one.
582 err = s.APIState.Client().DestroyServiceUnits("wordpress/2", "wordpress/0")
583 c.Assert(err, gc.IsNil)
584 assertLife(c, units[2], state.Dying)
585
586 // Try to destroy an Alive one along with a nonexistent one; check that
587 // the valid instruction is followed but the invalid one is warned about.
588 err = s.APIState.Client().DestroyServiceUnits("boojum/123", "wordpress/3")
589 c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "boojum/123" does not exist`)
590 assertLife(c, units[3], state.Dying)
591
592 // Make one Dead, and destroy an Alive one alongside it; check no errors.
593 wp0, err := s.State.Unit("wordpress/0")
594 c.Assert(err, gc.IsNil)
595 err = wp0.EnsureDead()
596 c.Assert(err, gc.IsNil)
597 err = s.APIState.Client().DestroyServiceUnits("wordpress/0", "wordpress/4")
598 c.Assert(err, gc.IsNil)
599 assertLife(c, units[0], state.Dead)
600 assertLife(c, units[4], state.Dying)
601}
602
603func (s *clientSuite) TestDestroySubordinateUnits(c *gc.C) {
604 wordpress, err := s.State.AddService("wordpress", s.AddTestingCharm(c, "wordpress"))
605 c.Assert(err, gc.IsNil)
606 wordpress0, err := wordpress.AddUnit()
607 c.Assert(err, gc.IsNil)
608 _, err = s.State.AddService("logging", s.AddTestingCharm(c, "logging"))
609 c.Assert(err, gc.IsNil)
610 eps, err := s.State.InferEndpoints([]string{"logging", "wordpress"})
611 c.Assert(err, gc.IsNil)
612 rel, err := s.State.AddRelation(eps...)
613 c.Assert(err, gc.IsNil)
614 ru, err := rel.Unit(wordpress0)
615 c.Assert(err, gc.IsNil)
616 err = ru.EnterScope(nil)
617 c.Assert(err, gc.IsNil)
618 logging0, err := s.State.Unit("logging/0")
619 c.Assert(err, gc.IsNil)
620
621 // Try to destroy the subordinate alone; check it fails.
622 err = s.APIState.Client().DestroyServiceUnits("logging/0")
623 c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`)
624 assertLife(c, logging0, state.Alive)
625
626 // Try to destroy the principal and the subordinate together; check it warns
627 // about the subordinate, but destroys the one it can. (The principal unit
628 // agent will be resposible for destroying the subordinate.)
629 err = s.APIState.Client().DestroyServiceUnits("wordpress/0", "logging/0")
630 c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "logging/0" is a subordinate`)
631 assertLife(c, wordpress0, state.Dying)
632 assertLife(c, logging0, state.Alive)
528}633}
529634
530func (s *clientSuite) testClientUnitResolved(c *gc.C, retry bool, expectedResolvedMode state.ResolvedMode) {635func (s *clientSuite) testClientUnitResolved(c *gc.C, retry bool, expectedResolvedMode state.ResolvedMode) {
531636
=== modified file 'state/apiserver/client/perm_test.go'
--- state/apiserver/client/perm_test.go 2013-11-06 10:51:34 +0000
+++ state/apiserver/client/perm_test.go 2013-11-12 10:22:12 +0000
@@ -348,7 +348,7 @@
348}348}
349349
350func opClientDestroyServiceUnits(c *gc.C, st *api.State, mst *state.State) (func(), error) {350func opClientDestroyServiceUnits(c *gc.C, st *api.State, mst *state.State) (func(), error) {
351 err := st.Client().DestroyServiceUnits([]string{"wordpress/99"})351 err := st.Client().DestroyServiceUnits("wordpress/99")
352 if err != nil && strings.HasPrefix(err.Error(), "no units were destroyed") {352 if err != nil && strings.HasPrefix(err.Error(), "no units were destroyed") {
353 err = nil353 err = nil
354 }354 }
355355
=== modified file 'state/cleanup_test.go'
--- state/cleanup_test.go 2013-11-11 15:45:06 +0000
+++ state/cleanup_test.go 2013-11-12 10:22:12 +0000
@@ -1,6 +1,8 @@
1package state_test1package state_test
22
3import (3import (
4 "fmt"
5
4 gc "launchpad.net/gocheck"6 gc "launchpad.net/gocheck"
57
6 "launchpad.net/juju-core/charm"8 "launchpad.net/juju-core/charm"
@@ -90,6 +92,22 @@
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`)
91}93}
9294
95func (s *CleanupSuite) testForceDestroyManagerError(c *gc.C, job state.MachineJob) {
96 manager, err := s.State.AddMachine("quantal", job)
97 c.Assert(err, gc.IsNil)
98 s.assertDoesNotNeedCleanup(c)
99 err = manager.ForceDestroy()
100 expect := fmt.Sprintf("machine %s is required by the environment", manager.Id())
101 c.Assert(err, gc.ErrorMatches, expect)
102 s.assertDoesNotNeedCleanup(c)
103 assertLife(c, manager, state.Alive)
104}
105
106func (s *CleanupSuite) TestForceDestroyMachineErrors(c *gc.C) {
107 s.testForceDestroyManagerError(c, state.JobManageState)
108 s.testForceDestroyManagerError(c, state.JobManageEnviron)
109}
110
93func (s *CleanupSuite) TestCleanupForceDestroyedMachineUnit(c *gc.C) {111func (s *CleanupSuite) TestCleanupForceDestroyedMachineUnit(c *gc.C) {
94 s.assertDoesNotNeedCleanup(c)112 s.assertDoesNotNeedCleanup(c)
95113
@@ -106,7 +124,7 @@
106 s.assertDoesNotNeedCleanup(c)124 s.assertDoesNotNeedCleanup(c)
107125
108 // Force machine destruction, check cleanup queued.126 // Force machine destruction, check cleanup queued.
109 err = s.State.ForceDestroyMachines(machine.Id())127 err = machine.ForceDestroy()
110 c.Assert(err, gc.IsNil)128 c.Assert(err, gc.IsNil)
111 s.assertNeedsCleanup(c)129 s.assertNeedsCleanup(c)
112130
@@ -117,7 +135,7 @@
117 c.Assert(err, jc.Satisfies, errors.IsNotFoundError)135 c.Assert(err, jc.Satisfies, errors.IsNotFoundError)
118136
119 // ...and so has the unit...137 // ...and so has the unit...
120 assertUnitRemoved(c, pr.u0)138 assertRemoved(c, pr.u0)
121139
122 // ...and the unit has departed relation scope.140 // ...and the unit has departed relation scope.
123 assertNotInScope(c, pr.ru0)141 assertNotInScope(c, pr.ru0)
@@ -156,13 +174,13 @@
156 s.assertDoesNotNeedCleanup(c)174 s.assertDoesNotNeedCleanup(c)
157175
158 // Force removal of the top-level machine.176 // Force removal of the top-level machine.
159 err = s.State.ForceDestroyMachines(machine.Id())177 err = machine.ForceDestroy()
160 c.Assert(err, gc.IsNil)178 c.Assert(err, gc.IsNil)
161 s.assertNeedsCleanup(c)179 s.assertNeedsCleanup(c)
162180
163 // And do it again, just to check that the second cleanup doc for the same181 // And do it again, just to check that the second cleanup doc for the same
164 // machine doesn't cause problems down the line.182 // machine doesn't cause problems down the line.
165 err = s.State.ForceDestroyMachines(machine.Id())183 err = machine.ForceDestroy()
166 c.Assert(err, gc.IsNil)184 c.Assert(err, gc.IsNil)
167 s.assertNeedsCleanup(c)185 s.assertNeedsCleanup(c)
168186
@@ -175,10 +193,10 @@
175 c.Assert(err, jc.Satisfies, errors.IsNotFoundError)193 c.Assert(err, jc.Satisfies, errors.IsNotFoundError)
176194
177 // ...and so have all the units...195 // ...and so have all the units...
178 assertUnitRemoved(c, prr.pu0)196 assertRemoved(c, prr.pu0)
179 assertUnitRemoved(c, prr.pu1)197 assertRemoved(c, prr.pu1)
180 assertUnitRemoved(c, prr.ru0)198 assertRemoved(c, prr.ru0)
181 assertUnitRemoved(c, prr.ru1)199 assertRemoved(c, prr.ru1)
182200
183 // ...and none of the units have left relation scopes occupied.201 // ...and none of the units have left relation scopes occupied.
184 assertNotInScope(c, prr.pru0)202 assertNotInScope(c, prr.pru0)
185203
=== modified file 'state/life.go'
--- state/life.go 2013-07-09 10:32:23 +0000
+++ state/life.go 2013-11-12 10:22:12 +0000
@@ -38,8 +38,15 @@
38type Living interface {38type Living interface {
39 Life() Life39 Life() Life
40 Destroy() error40 Destroy() error
41 Refresh() error
42}
43
44// AgentLiving describes state entities with a lifecycle and an agent that
45// manages it.
46type AgentLiving interface {
47 Living
41 EnsureDead() error48 EnsureDead() error
42 Refresh() error49 Remove() error
43}50}
4451
45func isAlive(coll *mgo.Collection, id interface{}) (bool, error) {52func isAlive(coll *mgo.Collection, id interface{}) (bool, error) {
4653
=== modified file 'state/life_test.go'
--- state/life_test.go 2013-09-27 07:05:45 +0000
+++ state/life_test.go 2013-11-12 10:22:12 +0000
@@ -81,8 +81,7 @@
8181
82type lifeFixture interface {82type lifeFixture interface {
83 id() (coll string, id interface{})83 id() (coll string, id interface{})
84 setup(s *LifeSuite, c *gc.C) state.Living84 setup(s *LifeSuite, c *gc.C) state.AgentLiving
85 teardown(s *LifeSuite, c *gc.C)
86}85}
8786
88type unitLife struct {87type unitLife struct {
@@ -93,7 +92,7 @@
93 return "units", l.unit.Name()92 return "units", l.unit.Name()
94}93}
9594
96func (l *unitLife) setup(s *LifeSuite, c *gc.C) state.Living {95func (l *unitLife) setup(s *LifeSuite, c *gc.C) state.AgentLiving {
97 unit, err := s.svc.AddUnit()96 unit, err := s.svc.AddUnit()
98 c.Assert(err, gc.IsNil)97 c.Assert(err, gc.IsNil)
99 preventUnitDestroyRemove(c, unit)98 preventUnitDestroyRemove(c, unit)
@@ -101,11 +100,6 @@
101 return l.unit100 return l.unit
102}101}
103102
104func (l *unitLife) teardown(s *LifeSuite, c *gc.C) {
105 err := l.unit.Remove()
106 c.Assert(err, gc.IsNil)
107}
108
109type machineLife struct {103type machineLife struct {
110 machine *state.Machine104 machine *state.Machine
111}105}
@@ -114,18 +108,13 @@
114 return "machines", l.machine.Id()108 return "machines", l.machine.Id()
115}109}
116110
117func (l *machineLife) setup(s *LifeSuite, c *gc.C) state.Living {111func (l *machineLife) setup(s *LifeSuite, c *gc.C) state.AgentLiving {
118 var err error112 var err error
119 l.machine, err = s.State.AddMachine("quantal", state.JobHostUnits)113 l.machine, err = s.State.AddMachine("quantal", state.JobHostUnits)
120 c.Assert(err, gc.IsNil)114 c.Assert(err, gc.IsNil)
121 return l.machine115 return l.machine
122}116}
123117
124func (l *machineLife) teardown(s *LifeSuite, c *gc.C) {
125 err := l.machine.Remove()
126 c.Assert(err, gc.IsNil)
127}
128
129func (s *LifeSuite) prepareFixture(living state.Living, lfix lifeFixture, cached, dbinitial state.Life, c *gc.C) {118func (s *LifeSuite) prepareFixture(living state.Living, lfix lifeFixture, cached, dbinitial state.Life, c *gc.C) {
130 collName, id := lfix.id()119 collName, id := lfix.id()
131 coll := s.MgoSuite.Session.DB("juju").C(collName)120 coll := s.MgoSuite.Session.DB("juju").C(collName)
@@ -165,7 +154,8 @@
165 c.Assert(living.Life(), gc.Equals, v.dbfinal)154 c.Assert(living.Life(), gc.Equals, v.dbfinal)
166 err = living.EnsureDead()155 err = living.EnsureDead()
167 c.Assert(err, gc.IsNil)156 c.Assert(err, gc.IsNil)
168 lfix.teardown(s, c)157 err = living.Remove()
158 c.Assert(err, gc.IsNil)
169 }159 }
170 }160 }
171}161}
172162
=== modified file 'state/machine.go'
--- state/machine.go 2013-11-11 23:42:49 +0000
+++ state/machine.go 2013-11-12 10:22:12 +0000
@@ -195,10 +195,11 @@
195 return m.doc.Jobs195 return m.doc.Jobs
196}196}
197197
198// IsStateServer returns true if the machine has a JobManageState job.198// IsManager returns true if the machine has JobManageState or JobManageEnviron.
199func (m *Machine) IsStateServer() bool {199func (m *Machine) IsManager() bool {
200 for _, job := range m.doc.Jobs {200 for _, job := range m.doc.Jobs {
201 if job == JobManageState {201 switch job {
202 case JobManageEnviron, JobManageState:
202 return true203 return true
203 }204 }
204 }205 }
@@ -314,6 +315,22 @@
314 return m.advanceLifecycle(Dying)315 return m.advanceLifecycle(Dying)
315}316}
316317
318// ForceDestroy queues the machine for complete removal, including the
319// destruction of all units and containers on the machine.
320func (m *Machine) ForceDestroy() error {
321 if !m.IsManager() {
322 ops := []txn.Op{{
323 C: m.st.machines.Name,
324 Id: m.doc.Id,
325 Assert: D{{"jobs", D{{"$nin", []MachineJob{JobManageState}}}}},
326 }, m.st.newCleanupOp("machine", m.doc.Id)}
327 if err := m.st.runTransaction(ops); err != txn.ErrAborted {
328 return err
329 }
330 }
331 return fmt.Errorf("machine %s is required by the environment", m.doc.Id)
332}
333
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.
318// It does nothing otherwise. EnsureDead will fail if the machine has335// It does nothing otherwise. EnsureDead will fail if the machine has
319// principal units assigned, or if the machine has JobManageEnviron.336// principal units assigned, or if the machine has JobManageEnviron.
320337
=== modified file 'state/machine_test.go'
--- state/machine_test.go 2013-11-12 01:59:52 +0000
+++ state/machine_test.go 2013-11-12 10:22:12 +0000
@@ -67,15 +67,17 @@
67 c.Assert(ok, gc.Equals, true)67 c.Assert(ok, gc.Equals, true)
68}68}
6969
70func (s *MachineSuite) TestMachineIsStateServer(c *gc.C) {70func (s *MachineSuite) TestMachineIsManager(c *gc.C) {
71 tests := []struct {71 tests := []struct {
72 isStateServer bool72 isStateServer bool
73 jobs []state.MachineJob73 jobs []state.MachineJob
74 }{74 }{
75 {false, []state.MachineJob{state.JobHostUnits}},75 {false, []state.MachineJob{state.JobHostUnits}},
76 {false, []state.MachineJob{state.JobHostUnits, state.JobManageEnviron}},76 {true, []state.MachineJob{state.JobManageState}},
77 {true, []state.MachineJob{state.JobManageEnviron}},
78 {true, []state.MachineJob{state.JobHostUnits, state.JobManageState}},
79 {true, []state.MachineJob{state.JobHostUnits, state.JobManageEnviron}},
77 {true, []state.MachineJob{state.JobHostUnits, state.JobManageState, state.JobManageEnviron}},80 {true, []state.MachineJob{state.JobHostUnits, state.JobManageState, state.JobManageEnviron}},
78 {true, []state.MachineJob{state.JobManageState}},
79 }81 }
80 for _, test := range tests {82 for _, test := range tests {
81 params := state.AddMachineParams{83 params := state.AddMachineParams{
@@ -84,7 +86,7 @@
84 }86 }
85 m, err := s.State.AddMachineWithConstraints(&params)87 m, err := s.State.AddMachineWithConstraints(&params)
86 c.Assert(err, gc.IsNil)88 c.Assert(err, gc.IsNil)
87 c.Assert(m.IsStateServer(), gc.Equals, test.isStateServer)89 c.Assert(m.IsManager(), gc.Equals, test.isStateServer)
88 }90 }
89}91}
9092
@@ -94,6 +96,8 @@
94 c.Assert(err, gc.IsNil)96 c.Assert(err, gc.IsNil)
95 err = m.Destroy()97 err = m.Destroy()
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")
99 err = m.ForceDestroy()
100 c.Assert(err, gc.ErrorMatches, "machine 1 is required by the environment")
97 err = m.EnsureDead()101 err = m.EnsureDead()
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")
99}103}
@@ -218,41 +222,6 @@
218 c.Assert(err, gc.IsNil)222 c.Assert(err, gc.IsNil)
219}223}
220224
221func (s *MachineSuite) TestDestroyMachines(c *gc.C) {
222 m0 := s.machine
223 m1, err := s.State.AddMachine("quantal", state.JobManageEnviron)
224 c.Assert(err, gc.IsNil)
225 m2, err := s.State.AddMachine("quantal", state.JobHostUnits)
226 c.Assert(err, gc.IsNil)
227
228 sch := s.AddTestingCharm(c, "wordpress")
229 wordpress, err := s.State.AddService("wordpress", sch)
230 c.Assert(err, gc.IsNil)
231 u, err := wordpress.AddUnit()
232 c.Assert(err, gc.IsNil)
233 err = u.AssignToMachine(m0)
234 c.Assert(err, gc.IsNil)
235
236 err = s.State.DestroyMachines("0", "1", "2")
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`)
238 assertLife := func(m *state.Machine, life state.Life) {
239 err := m.Refresh()
240 c.Assert(err, gc.IsNil)
241 c.Assert(m.Life(), gc.Equals, life)
242 }
243 assertLife(m0, state.Alive)
244 assertLife(m1, state.Alive)
245 assertLife(m2, state.Dying)
246
247 err = u.UnassignFromMachine()
248 c.Assert(err, gc.IsNil)
249 err = s.State.DestroyMachines("0", "1", "2")
250 c.Assert(err, gc.ErrorMatches, `some machines were not destroyed: machine 1 is required by the environment`)
251 assertLife(m0, state.Dying)
252 assertLife(m1, state.Alive)
253 assertLife(m2, state.Dying)
254}
255
256func (s *MachineSuite) TestMachineSetAgentAlive(c *gc.C) {225func (s *MachineSuite) TestMachineSetAgentAlive(c *gc.C) {
257 alive, err := s.machine.AgentAlive()226 alive, err := s.machine.AgentAlive()
258 c.Assert(err, gc.IsNil)227 c.Assert(err, gc.IsNil)
@@ -627,11 +596,11 @@
627func (s *MachineSuite) TestWatchDiesOnStateClose(c *gc.C) {596func (s *MachineSuite) TestWatchDiesOnStateClose(c *gc.C) {
628 // This test is testing logic in watcher.entityWatcher, which597 // This test is testing logic in watcher.entityWatcher, which
629 // is also used by:598 // is also used by:
630 // Machine.WatchHardwareCharacteristics599 // Machine.WatchHardwareCharacteristics
631 // Service.Watch600 // Service.Watch
632 // Unit.Watch601 // Unit.Watch
633 // State.WatchForEnvironConfigChanges602 // State.WatchForEnvironConfigChanges
634 // Unit.WatchConfigSettings603 // Unit.WatchConfigSettings
635 testWatcherDiesWhenStateCloses(c, func(c *gc.C, st *state.State) waiter {604 testWatcherDiesWhenStateCloses(c, func(c *gc.C, st *state.State) waiter {
636 m, err := st.Machine(s.machine.Id())605 m, err := st.Machine(s.machine.Id())
637 c.Assert(err, gc.IsNil)606 c.Assert(err, gc.IsNil)
638607
=== modified file 'state/service_test.go'
--- state/service_test.go 2013-09-27 19:20:20 +0000
+++ state/service_test.go 2013-11-12 10:22:12 +0000
@@ -1090,7 +1090,7 @@
1090 err = s.mysql.Destroy()1090 err = s.mysql.Destroy()
1091 c.Assert(err, gc.IsNil)1091 c.Assert(err, gc.IsNil)
1092 for _, unit := range units {1092 for _, unit := range units {
1093 assertUnitLife(c, unit, state.Alive)1093 assertLife(c, unit, state.Alive)
1094 }1094 }
10951095
1096 // Check a cleanup doc was added.1096 // Check a cleanup doc was added.
@@ -1103,9 +1103,9 @@
1103 c.Assert(err, gc.IsNil)1103 c.Assert(err, gc.IsNil)
1104 for i, unit := range units {1104 for i, unit := range units {
1105 if i%2 != 0 {1105 if i%2 != 0 {
1106 assertUnitLife(c, unit, state.Dying)1106 assertLife(c, unit, state.Dying)
1107 } else {1107 } else {
1108 assertUnitRemoved(c, unit)1108 assertRemoved(c, unit)
1109 }1109 }
1110 }1110 }
11111111
11121112
=== modified file 'state/state.go'
--- state/state.go 2013-11-08 16:15:10 +0000
+++ state/state.go 2013-11-12 10:22:12 +0000
@@ -1051,73 +1051,6 @@
1051 return newUnit(st, &doc), nil1051 return newUnit(st, &doc), nil
1052}1052}
10531053
1054// DestroyUnits destroys the units with the specified names.
1055func (st *State) DestroyUnits(names ...string) (err error) {
1056 // TODO(rog) make this a transaction?
1057 var errs []string
1058 for _, name := range names {
1059 unit, err := st.Unit(name)
1060 switch {
1061 case errors.IsNotFoundError(err):
1062 err = fmt.Errorf("unit %q does not exist", name)
1063 case err != nil:
1064 case unit.Life() != Alive:
1065 continue
1066 case unit.IsPrincipal():
1067 err = unit.Destroy()
1068 default:
1069 err = fmt.Errorf("unit %q is a subordinate", name)
1070 }
1071 if err != nil {
1072 errs = append(errs, err.Error())
1073 }
1074 }
1075 return destroyErr("units", names, errs)
1076}
1077
1078// DestroyMachines destroys the machines with the specified ids.
1079func (st *State) DestroyMachines(ids ...string) (err error) {
1080 var errs []string
1081 for _, id := range ids {
1082 machine, err := st.Machine(id)
1083 switch {
1084 case errors.IsNotFoundError(err):
1085 err = fmt.Errorf("machine %s does not exist", id)
1086 case err != nil:
1087 case machine.Life() != Alive:
1088 continue
1089 default:
1090 err = machine.Destroy()
1091 }
1092 if err != nil {
1093 errs = append(errs, err.Error())
1094 }
1095 }
1096 return destroyErr("machines", ids, errs)
1097}
1098
1099func destroyErr(desc string, ids, errs []string) error {
1100 if len(errs) == 0 {
1101 return nil
1102 }
1103 msg := "some %s were not destroyed"
1104 if len(errs) == len(ids) {
1105 msg = "no %s were destroyed"
1106 }
1107 msg = fmt.Sprintf(msg, desc)
1108 return fmt.Errorf("%s: %s", msg, strings.Join(errs, "; "))
1109}
1110
1111// ForceDestroyMachines queues the machines with the specified ids for
1112// complete removal, including the destruction of all units on the machine.
1113func (st *State) ForceDestroyMachines(ids ...string) error {
1114 var ops []txn.Op
1115 for _, id := range ids {
1116 ops = append(ops, st.newCleanupOp("machine", id))
1117 }
1118 return st.runTransaction(ops)
1119}
1120
1121// AssignUnit places the unit on a machine. Depending on the policy, and the1054// AssignUnit places the unit on a machine. Depending on the policy, and the
1122// state of the environment, this may lead to new instances being launched1055// state of the environment, this may lead to new instances being launched
1123// within the environment.1056// within the environment.
11241057
=== modified file 'state/unit_test.go'
--- state/unit_test.go 2013-11-07 17:30:38 +0000
+++ state/unit_test.go 2013-11-12 10:22:12 +0000
@@ -435,49 +435,12 @@
435 c.Assert(err, gc.ErrorMatches, `unit "wordpress/0" is dead`)435 c.Assert(err, gc.ErrorMatches, `unit "wordpress/0" is dead`)
436}436}
437437
438func (s *UnitSuite) TestDestroyPrincipalUnits(c *gc.C) {
439 preventUnitDestroyRemove(c, s.unit)
440 for i := 0; i < 4; i++ {
441 unit, err := s.service.AddUnit()
442 c.Assert(err, gc.IsNil)
443 preventUnitDestroyRemove(c, unit)
444 }
445
446 // Destroy 2 of them; check they become Dying.
447 err := s.State.DestroyUnits("wordpress/0", "wordpress/1")
448 c.Assert(err, gc.IsNil)
449 s.assertUnitLife(c, "wordpress/0", state.Dying)
450 s.assertUnitLife(c, "wordpress/1", state.Dying)
451
452 // Try to destroy an Alive one and a Dying one; check
453 // it destroys the Alive one and ignores the Dying one.
454 err = s.State.DestroyUnits("wordpress/2", "wordpress/0")
455 c.Assert(err, gc.IsNil)
456 s.assertUnitLife(c, "wordpress/2", state.Dying)
457
458 // Try to destroy an Alive one along with a nonexistent one; check that
459 // the valid instruction is followed but the invalid one is warned about.
460 err = s.State.DestroyUnits("boojum/123", "wordpress/3")
461 c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "boojum/123" does not exist`)
462 s.assertUnitLife(c, "wordpress/3", state.Dying)
463
464 // Make one Dead, and destroy an Alive one alongside it; check no errors.
465 wp0, err := s.State.Unit("wordpress/0")
466 c.Assert(err, gc.IsNil)
467 err = wp0.EnsureDead()
468 c.Assert(err, gc.IsNil)
469 err = s.State.DestroyUnits("wordpress/0", "wordpress/4")
470 c.Assert(err, gc.IsNil)
471 s.assertUnitLife(c, "wordpress/0", state.Dead)
472 s.assertUnitLife(c, "wordpress/4", state.Dying)
473}
474
475func (s *UnitSuite) TestDestroySetStatusRetry(c *gc.C) {438func (s *UnitSuite) TestDestroySetStatusRetry(c *gc.C) {
476 defer state.SetRetryHooks(c, s.State, func() {439 defer state.SetRetryHooks(c, s.State, func() {
477 err := s.unit.SetStatus(params.StatusStarted, "", nil)440 err := s.unit.SetStatus(params.StatusStarted, "", nil)
478 c.Assert(err, gc.IsNil)441 c.Assert(err, gc.IsNil)
479 }, func() {442 }, func() {
480 assertUnitLife(c, s.unit, state.Dying)443 assertLife(c, s.unit, state.Dying)
481 }).Check()444 }).Check()
482 err := s.unit.Destroy()445 err := s.unit.Destroy()
483 c.Assert(err, gc.IsNil)446 c.Assert(err, gc.IsNil)
@@ -488,7 +451,7 @@
488 err := s.unit.SetCharmURL(s.charm.URL())451 err := s.unit.SetCharmURL(s.charm.URL())
489 c.Assert(err, gc.IsNil)452 c.Assert(err, gc.IsNil)
490 }, func() {453 }, func() {
491 assertUnitRemoved(c, s.unit)454 assertRemoved(c, s.unit)
492 }).Check()455 }).Check()
493 err := s.unit.Destroy()456 err := s.unit.Destroy()
494 c.Assert(err, gc.IsNil)457 c.Assert(err, gc.IsNil)
@@ -505,7 +468,7 @@
505 err := s.unit.SetCharmURL(newCharm.URL())468 err := s.unit.SetCharmURL(newCharm.URL())
506 c.Assert(err, gc.IsNil)469 c.Assert(err, gc.IsNil)
507 }, func() {470 }, func() {
508 assertUnitRemoved(c, s.unit)471 assertRemoved(c, s.unit)
509 }).Check()472 }).Check()
510 err = s.unit.Destroy()473 err = s.unit.Destroy()
511 c.Assert(err, gc.IsNil)474 c.Assert(err, gc.IsNil)
@@ -519,7 +482,7 @@
519 err := s.unit.AssignToMachine(machine)482 err := s.unit.AssignToMachine(machine)
520 c.Assert(err, gc.IsNil)483 c.Assert(err, gc.IsNil)
521 }, func() {484 }, func() {
522 assertUnitRemoved(c, s.unit)485 assertRemoved(c, s.unit)
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 --
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.
525 err := machine.EnsureDead()488 err := machine.EnsureDead()
@@ -539,7 +502,7 @@
539 err := s.unit.UnassignFromMachine()502 err := s.unit.UnassignFromMachine()
540 c.Assert(err, gc.IsNil)503 c.Assert(err, gc.IsNil)
541 }, func() {504 }, func() {
542 assertUnitRemoved(c, s.unit)505 assertRemoved(c, s.unit)
543 }).Check()506 }).Check()
544 err = s.unit.Destroy()507 err = s.unit.Destroy()
545 c.Assert(err, gc.IsNil)508 c.Assert(err, gc.IsNil)
@@ -550,7 +513,7 @@
550 err := s.unit.Destroy()513 err := s.unit.Destroy()
551 c.Assert(err, gc.IsNil)514 c.Assert(err, gc.IsNil)
552 c.Assert(s.unit.Life(), gc.Equals, state.Dying)515 c.Assert(s.unit.Life(), gc.Equals, state.Dying)
553 assertUnitRemoved(c, s.unit)516 assertRemoved(c, s.unit)
554}517}
555518
556func (s *UnitSuite) TestCannotShortCircuitDestroyWithSubordinates(c *gc.C) {519func (s *UnitSuite) TestCannotShortCircuitDestroyWithSubordinates(c *gc.C) {
@@ -568,7 +531,7 @@
568 err = s.unit.Destroy()531 err = s.unit.Destroy()
569 c.Assert(err, gc.IsNil)532 c.Assert(err, gc.IsNil)
570 c.Assert(s.unit.Life(), gc.Equals, state.Dying)533 c.Assert(s.unit.Life(), gc.Equals, state.Dying)
571 assertUnitLife(c, s.unit, state.Dying)534 assertLife(c, s.unit, state.Dying)
572}535}
573536
574func (s *UnitSuite) TestCannotShortCircuitDestroyWithStatus(c *gc.C) {537func (s *UnitSuite) TestCannotShortCircuitDestroyWithStatus(c *gc.C) {
@@ -592,7 +555,7 @@
592 err = unit.Destroy()555 err = unit.Destroy()
593 c.Assert(err, gc.IsNil)556 c.Assert(err, gc.IsNil)
594 c.Assert(unit.Life(), gc.Equals, state.Dying)557 c.Assert(unit.Life(), gc.Equals, state.Dying)
595 assertUnitLife(c, unit, state.Dying)558 assertLife(c, unit, state.Dying)
596 }559 }
597}560}
598561
@@ -610,56 +573,25 @@
610 err = s.unit.Destroy()573 err = s.unit.Destroy()
611 c.Assert(err, gc.IsNil)574 c.Assert(err, gc.IsNil)
612 c.Assert(s.unit.Life(), gc.Equals, state.Dying)575 c.Assert(s.unit.Life(), gc.Equals, state.Dying)
613 assertUnitRemoved(c, s.unit)576 assertRemoved(c, s.unit)
614}577}
615578
616func (s *UnitSuite) TestDestroySubordinateUnits(c *gc.C) {579func assertLife(c *gc.C, entity state.Living, life state.Life) {
617 lgsch := s.AddTestingCharm(c, "logging")580 c.Assert(entity.Refresh(), gc.IsNil)
618 _, err := s.State.AddService("logging", lgsch)581 c.Assert(entity.Life(), gc.Equals, life)
619 c.Assert(err, gc.IsNil)582}
620 eps, err := s.State.InferEndpoints([]string{"logging", "wordpress"})583
621 c.Assert(err, gc.IsNil)584func assertRemoved(c *gc.C, entity state.Living) {
622 rel, err := s.State.AddRelation(eps...)585 err := entity.Refresh()
623 c.Assert(err, gc.IsNil)
624 ru, err := rel.Unit(s.unit)
625 c.Assert(err, gc.IsNil)
626 err = ru.EnterScope(nil)
627 c.Assert(err, gc.IsNil)
628
629 // Try to destroy the subordinate alone; check it fails.
630 err = s.State.DestroyUnits("logging/0")
631 c.Assert(err, gc.ErrorMatches, `no units were destroyed: unit "logging/0" is a subordinate`)
632 s.assertUnitLife(c, "logging/0", state.Alive)
633
634 // Try to destroy the principal and the subordinate together; check it warns
635 // about the subordinate, but destroys the one it can. (The principal unit
636 // agent will be resposible for destroying the subordinate.)
637 err = s.State.DestroyUnits("wordpress/0", "logging/0")
638 c.Assert(err, gc.ErrorMatches, `some units were not destroyed: unit "logging/0" is a subordinate`)
639 s.assertUnitLife(c, "wordpress/0", state.Dying)
640 s.assertUnitLife(c, "logging/0", state.Alive)
641}
642
643func (s *UnitSuite) assertUnitLife(c *gc.C, name string, life state.Life) {
644 unit, err := s.State.Unit(name)
645 c.Assert(err, gc.IsNil)
646 assertUnitLife(c, unit, life)
647}
648
649func assertUnitLife(c *gc.C, unit *state.Unit, life state.Life) {
650 c.Assert(unit.Refresh(), gc.IsNil)
651 c.Assert(unit.Life(), gc.Equals, life)
652}
653
654func assertUnitRemoved(c *gc.C, unit *state.Unit) {
655 err := unit.Refresh()
656 c.Assert(err, jc.Satisfies, errors.IsNotFoundError)586 c.Assert(err, jc.Satisfies, errors.IsNotFoundError)
657 err = unit.Destroy()587 err = entity.Destroy()
658 c.Assert(err, gc.IsNil)588 c.Assert(err, gc.IsNil)
659 err = unit.EnsureDead()589 if entity, ok := entity.(state.AgentLiving); ok {
660 c.Assert(err, gc.IsNil)590 err = entity.EnsureDead()
661 err = unit.Remove()591 c.Assert(err, gc.IsNil)
662 c.Assert(err, gc.IsNil)592 err = entity.Remove()
593 c.Assert(err, gc.IsNil)
594 }
663}595}
664596
665func (s *UnitSuite) TestTag(c *gc.C) {597func (s *UnitSuite) TestTag(c *gc.C) {

Subscribers

People subscribed via source and target branches

to status/vote changes: