Merge lp:~axwalk/juju-core/lp1246983-cli-api-removeagents into lp:~go-bot/juju-core/trunk

Proposed by Andrew Wilkins
Status: Merged
Merged at revision: 2131
Proposed branch: lp:~axwalk/juju-core/lp1246983-cli-api-removeagents
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 936 lines (+564/-49)
19 files modified
cmd/juju/destroyenvironment.go (+12/-4)
cmd/jujud/machine.go (+3/-0)
environs/manual/provisioner.go (+1/-1)
provider/dummy/environs.go (+3/-1)
provider/null/environ.go (+1/-1)
state/api/client.go (+6/-0)
state/api/machiner/environ.go (+41/-0)
state/api/machiner/machine.go (+2/-20)
state/api/machiner/machiner.go (+39/-3)
state/api/params/internal.go (+6/-0)
state/apiserver/client/destroyjuju.go (+126/-0)
state/apiserver/client/destroyjuju_test.go (+118/-0)
state/apiserver/machine/machiner.go (+31/-3)
state/environ.go (+64/-12)
state/interface.go (+1/-0)
state/state.go (+23/-2)
state/state_test.go (+36/-0)
state/watcher.go (+5/-0)
worker/machiner/machiner.go (+46/-2)
To merge this branch: bzr merge lp:~axwalk/juju-core/lp1246983-cli-api-removeagents
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+194801@code.launchpad.net

Description of the change

Migrate juju destroy-environment to use API

This change introduces a new client API
method, DestroyJuju. This method does the
following, server-side, in order:
 - marks the environment as Dying, preventing
   the addition of machines and services to
   state
 - ensures there are no non-manager manually-
   provisioned machines in state
 - marks all services as Dying, thus prevening
   units from being added
 - stops all non-manager machine instances
 - marks the environment as Dead

Machine agents watch, via the machiner API, for
changes to the environment's lifecycle, and
terminate themselves when the environment
becomes Dead. This is how we terminate manual
bootstrap nodes.

A followup will update agent termination logic
to uninstall mongo on state servers, and also
remove the data-dir/log-dir.

Fixes #1218688
Fixes #1246343

https://codereview.appspot.com/22870045/

To post a comment you must log in.
Revision history for this message
Andrew Wilkins (axwalk) wrote :

Reviewers: mp+194801_code.launchpad.net,

Message:
Please take a look.

Description:
Migrate juju destroy-environment to use API

(This is a prototype, hence lacking tests.
Please review as such, and if it's in the
right direction I'll update with tests.)

This change introduces a new client API
method, DestroyJuju. This method (server-side)
marks the environment as Dying, destroys (and
waits on death of) all units, and then marks
the environment as Dead. When the environment
is marked as Dying, addition of units, services
and machines is prevented.

Machine agents watch, via the machiner API, for
changes to the environment's lifecycle, and
terminate themselves when the environment
becomes Dead.

A followup will update agent termination logic
to uninstall mongo on state servers, and also
remove the data-dir/log-dir.

Fixes #1218688
Fixes #1246343

https://code.launchpad.net/~axwalk/juju-core/lp1246983-cli-api-removeagents/+merge/194801

(do not edit description out of merge proposal)

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

Affected files (+425, -45 lines):
   A [revision details]
   M cmd/juju/destroyenvironment.go
   M cmd/jujud/machine.go
   M provider/null/environ.go
   M state/api/client.go
   A state/api/machiner/environ.go
   M state/api/machiner/machine.go
   M state/api/machiner/machiner.go
   M state/api/params/internal.go
   M state/api/params/params.go
   A state/apiserver/client/destroyjuju.go
   M state/apiserver/machine/machiner.go
   M state/environ.go
   M state/interface.go
   M state/service.go
   M state/service_test.go
   M state/state.go
   M state/watcher.go
   M worker/machiner/machiner.go

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

some thougts. we did most of the discussion on IRC, I think.

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

https://codereview.appspot.com/22870045/diff/1/cmd/juju/destroyenvironment.go#newcode83
cmd/juju/destroyenvironment.go:83: if err =
conn.State.Client().DestroyJuju(removeAgentsTimeout); err != nil {
I'm intrigued that you pass in the timeout.I was wondering if we need it
configable (I know my manual machines take at least 2min to shutdown
cleanly). At least we can expose it on the CLI if we find it is
necessary.

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

https://codereview.appspot.com/22870045/diff/1/state/api/machiner/machiner.go#newcode58
state/api/machiner/machiner.go:58: func (st *State)
Environment(machineTag string) (*Environment, error) {
I don't quite understand why you take a machineTag for an environment.
Is this just a copy and paste that didn't finish renaming things?

But the MachineEnvironmentResult is still here, too.

If it *does* need a Machine, then I'm not sure why this is on State
rather than Machine. (though to be fair, this is Machine.State and not
state.State which is a personal source of much confusion).

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

https://codereview.appspot.com/22870045/diff/1/state/apiserver/client/destroyjuju.go#newcode49
state/apiserver/client/destroyjuju.go:49: func destroyUnits(st
*state.State, attemptStrategy utils.AttemptStrategy) error {
It is surprising to me that destroyUnits would be triggered on the
Client side, rather than on the API server in response to the
env.Destroy request.

https://codereview.appspot.com/22870045/diff/1/state/apiserver/machine/machiner.go
File state/apiserver/machine/machiner.go (right):

https://codereview.appspot.com/22870045/diff/1/state/apiserver/machine/machiner.go#newcode61
state/apiserver/machine/machiner.go:61: // the corresponding machine's
environment.
I don't think we want to design multi-tenancy into the API today.

I'm willing to be corrected by someone else, but we should keep in mind
the future, but only build what we need right now. (Because we're likely
to get it wrong until we're actually building the next thing anyway.)

https://codereview.appspot.com/22870045/

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Download full text (3.6 KiB)

So, based on conversations on IRC, I'm going to rework this. From
fwereade:

"(1) abort if manual machines exist (2) destroy services, set destroying
flag (3) directly StopInstances for non-managers in the API server (4)
environ.Destroy in the CLI"

Sounds fair, I'll do that.

A follow-up will deal with manual machines, and will do the two-phase
start/wait business with a watcher and timeout in between.

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

https://codereview.appspot.com/22870045/diff/1/cmd/juju/destroyenvironment.go#newcode83
cmd/juju/destroyenvironment.go:83: if err =
conn.State.Client().DestroyJuju(removeAgentsTimeout); err != nil {
On 2013/11/12 08:39:26, jameinel wrote:
> I'm intrigued that you pass in the timeout.I was wondering if we need
it
> configable (I know my manual machines take at least 2min to shutdown
cleanly).
> At least we can expose it on the CLI if we find it is necessary.

rogpeppe has suggested we break the API into two (in the usual async
pattern): one that starts, one that finalises. The client can use the
all-watcher to observe machine/unit destruction, and provide feedback
along the way. Despite the added complexity, I'm in favour.

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

https://codereview.appspot.com/22870045/diff/1/state/api/machiner/machiner.go#newcode58
state/api/machiner/machiner.go:58: func (st *State)
Environment(machineTag string) (*Environment, error) {
On 2013/11/12 08:39:26, jameinel wrote:
> I don't quite understand why you take a machineTag for an environment.
> Is this just a copy and paste that didn't finish renaming things?

> But the MachineEnvironmentResult is still here, too.

> If it *does* need a Machine, then I'm not sure why this is on State
rather than
> Machine. (though to be fair, this is Machine.State and not state.State
which is
> a personal source of much confusion).

It needs a Machine for when we have multi-tenancy. The machine-agent
doesn't know its environment name at the point of call, hence this. I'm
open to suggestions, I know it's a bit weird.

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

https://codereview.appspot.com/22870045/diff/1/state/apiserver/client/destroyjuju.go#newcode49
state/apiserver/client/destroyjuju.go:49: func destroyUnits(st
*state.State, attemptStrategy utils.AttemptStrategy) error {
On 2013/11/12 08:39:26, jameinel wrote:
> It is surprising to me that destroyUnits would be triggered on the
Client side,
> rather than on the API server in response to the env.Destroy request.

This is happening on the API server side. This is just the "Client API",
as used by the CLI and GUI. Am I misunderstanding your comment?

https://codereview.appspot.com/22870045/diff/1/state/apiserver/machine/machiner.go
File state/apiserver/machine/machiner.go (right):

https://codereview.appspot.com/22870045/diff/1/state/apiserver/machine/machiner.go#newcode61
state/apiserver/machine/machiner.go:61: // the corresponding machin...

Read more...

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Revision history for this message
William Reade (fwereade) wrote :
Download full text (7.0 KiB)

Some good bits, some I'ma bit alarmed about. Can we do a single CL for
the state changes, please, and leave the API bits and additional workers
and so on out for the moment?

There are a couple of open questions in the state code, it's true, but
resolving and reproposing those will be the quickest way to get some of
this landed.

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

https://codereview.appspot.com/22870045/diff/1/state/api/machiner/machiner.go#newcode58
state/api/machiner/machiner.go:58: func (st *State)
Environment(machineTag string) (*Environment, error) {
On 2013/11/12 10:03:08, axw wrote:
> On 2013/11/12 08:39:26, jameinel wrote:
> > I don't quite understand why you take a machineTag for an
environment.
> > Is this just a copy and paste that didn't finish renaming things?
> >
> > But the MachineEnvironmentResult is still here, too.
> >
> > If it *does* need a Machine, then I'm not sure why this is on State
rather
> than
> > Machine. (though to be fair, this is Machine.State and not
state.State which
> is
> > a personal source of much confusion).

> It needs a Machine for when we have multi-tenancy. The machine-agent
doesn't
> know its environment name at the point of call, hence this. I'm open
to
> suggestions, I know it's a bit weird.

I don't think it is necessary, actually, because we will surely know
what environment we're connected to -- and I don't think it'd work
*anyway*, because the machine-tag doesn't identify an environment
anyway.

https://codereview.appspot.com/22870045/diff/20001/state/api/machiner/environ.go
File state/api/machiner/environ.go (right):

https://codereview.appspot.com/22870045/diff/20001/state/api/machiner/environ.go#newcode39
state/api/machiner/environ.go:39: func (e *Environment) Watch()
(watcher.NotifyWatcher, error) {
Environment?

https://codereview.appspot.com/22870045/diff/20001/state/api/machiner/machiner.go
File state/api/machiner/machiner.go (right):

https://codereview.appspot.com/22870045/diff/20001/state/api/machiner/machiner.go#newcode65
state/api/machiner/machiner.go:65: tag: result.EnvironmentTag,
Whoa. I hope this doesn't include a name -- the moment we start messing
around with environment tags we need to un-screw-up this situation
(environment names are not globally unique -- any time we use it we must
be aware it's only an alias).

Regardless I don't think there's a justification for talking about more
than one environment at this level anyway.

https://codereview.appspot.com/22870045/diff/20001/state/apiserver/client/destroyjuju.go
File state/apiserver/client/destroyjuju.go (right):

https://codereview.appspot.com/22870045/diff/20001/state/apiserver/client/destroyjuju.go#newcode97
state/apiserver/client/destroyjuju.go:97: return
env.StopInstances(instances)
There's a wrinkle here in that some machines may be missing for random
stupid provider reasons, and those won't end up getting stopped. I'm
becoming convinced that it's really stupid to have StopInstances take
Instances instead of ids -- it means that we need to go through all the
hassle of finding out everything about one (ie hitting the provider...

Read more...

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Download full text (7.5 KiB)

I'll create a new CL for the state bits, as requested.

https://codereview.appspot.com/22870045/diff/20001/state/api/machiner/environ.go
File state/api/machiner/environ.go (right):

https://codereview.appspot.com/22870045/diff/20001/state/api/machiner/environ.go#newcode39
state/api/machiner/environ.go:39: func (e *Environment) Watch()
(watcher.NotifyWatcher, error) {
On 2013/11/18 14:43:37, william.reade wrote:
> Environment?

Are you talking about the comment? Fixed.

https://codereview.appspot.com/22870045/diff/20001/state/api/machiner/machiner.go
File state/api/machiner/machiner.go (right):

https://codereview.appspot.com/22870045/diff/20001/state/api/machiner/machiner.go#newcode65
state/api/machiner/machiner.go:65: tag: result.EnvironmentTag,
On 2013/11/18 14:43:37, william.reade wrote:
> Whoa. I hope this doesn't include a name -- the moment we start
messing around
> with environment tags we need to un-screw-up this situation
(environment names
> are not globally unique -- any time we use it we must be aware it's
only an
> alias).

> Regardless I don't think there's a justification for talking about
more than one
> environment at this level anyway.

I'm not sure what you mean by "talking about more than one environment".
That tag is just a handle so we can get a watcher, and is always
expected to be the same.

Do you have a preferred API here?

https://codereview.appspot.com/22870045/diff/20001/state/apiserver/client/destroyjuju.go
File state/apiserver/client/destroyjuju.go (right):

https://codereview.appspot.com/22870045/diff/20001/state/apiserver/client/destroyjuju.go#newcode97
state/apiserver/client/destroyjuju.go:97: return
env.StopInstances(instances)
On 2013/11/18 14:43:37, william.reade wrote:
> There's a wrinkle here in that some machines may be missing for random
stupid
> provider reasons, and those won't end up getting stopped. I'm becoming
convinced
> that it's really stupid to have StopInstances take Instances instead
of ids --
> it means that we need to go through all the hassle of finding out
everything
> about one (ie hitting the provider api, potentially triggering rate
limiting,
> etc) before we can just simply stop them.

Agreed.

https://codereview.appspot.com/22870045/diff/20001/state/apiserver/client/destroyjuju.go#newcode124
state/apiserver/client/destroyjuju.go:124: // Due to an import loop in
tests, we cannot import manual.
On 2013/11/18 14:43:37, william.reade wrote:
> In tests? I'm surprised by that.

The environs/manual tests import something that imports state/apiserver.
Thus, importing environs/manual here prevents running the
environs/manual tests.

https://codereview.appspot.com/22870045/diff/20001/state/apiserver/machine/machiner.go
File state/apiserver/machine/machiner.go (right):

https://codereview.appspot.com/22870045/diff/20001/state/apiserver/machine/machiner.go#newcode60
state/apiserver/machine/machiner.go:60: var result
params.MachineEnvironmentResult
On 2013/11/18 14:43:37, william.reade wrote:
> Why do we persist in naming things after the entities that call them?
This is
> surely an EnvironmentResult.

The idea here was "give me the environment which this machine belongs
to", not an arbitrary...

Read more...

Preview Diff

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

Subscribers

People subscribed via source and target branches

to status/vote changes: