Merge lp:~thumper/juju-core/provisioner-reget-state-info into lp:~juju/juju-core/trunk

Proposed by Tim Penhey
Status: Superseded
Proposed branch: lp:~thumper/juju-core/provisioner-reget-state-info
Merge into: lp:~juju/juju-core/trunk
Diff against target: 2421 lines (+589/-351)
33 files modified
.lbox.check (+1/-1)
cmd/juju/plugin_test.go (+3/-2)
cmd/juju/status.go (+88/-45)
cmd/juju/status_test.go (+97/-0)
environs/azure/environ.go (+6/-5)
environs/azure/instance.go (+2/-2)
environs/dummy/environs.go (+20/-19)
environs/ec2/ec2.go (+29/-28)
environs/ec2/export_test.go (+6/-5)
environs/ec2/live_test.go (+7/-6)
environs/interface.go (+6/-35)
environs/jujutest/livetests.go (+9/-8)
environs/jujutest/tests.go (+2/-1)
environs/maas/environ.go (+12/-11)
environs/maas/environ_test.go (+5/-4)
environs/maas/instance.go (+2/-2)
environs/openstack/local_test.go (+3/-2)
environs/openstack/provider.go (+29/-28)
environs/openstack/provider_test.go (+4/-4)
instance/instance.go (+41/-0)
juju/testing/conn.go (+2/-1)
state/container.go (+15/-3)
state/export_test.go (+4/-0)
state/machine.go (+13/-2)
state/machine_test.go (+9/-0)
state/state.go (+43/-4)
state/state_test.go (+64/-0)
worker/firewaller/firewaller_test.go (+3/-3)
worker/provisioner/broker.go (+4/-4)
worker/provisioner/export_test.go (+2/-2)
worker/provisioner/provisioner.go (+11/-15)
worker/provisioner/provisioner_task.go (+43/-32)
worker/provisioner/provisioner_test.go (+4/-77)
To merge this branch: bzr merge lp:~thumper/juju-core/provisioner-reget-state-info
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+168562@code.launchpad.net

This proposal has been superseded by a proposal from 2013-06-17.

Description of the change

Provisioner gets addresses for each new machine.

The addresses are retrieved each time we attempt to start a machine. This
allows the provisioner to take advantage of any new api or state servers that
are started as part of a block of machines being provisioned at one time.

There is some saving of addresses at this stage to make the existing tests
pass, even though this goes a little against what William has mentioned
before. If we have invalid config, the tests assume that we continue with old
info, whereas William has suggested that we pause provisioning while the
config is broken.

One behavioural tweak in this branch. If we do fail to setup authentication,
we no longer kill the task, but instead log and continue. We will attempt to
start it again next time through the loop.

https://codereview.appspot.com/9824047/

To post a comment you must log in.
Revision history for this message
Tim Penhey (thumper) wrote :

Reviewers: mp+168562_code.launchpad.net,

Message:
Please take a look.

Description:
Provisioner gets addresses for each new machine.

The addresses are retrieved each time we attempt to start a machine.
This
allows the provisioner to take advantage of any new api or state servers
that
are started as part of a block of machines being provisioned at one
time.

There is some saving of addresses at this stage to make the existing
tests
pass, even though this goes a little against what William has mentioned
before. If we have invalid config, the tests assume that we continue
with old
info, whereas William has suggested that we pause provisioning while the
config is broken.

One behavioural tweak in this branch. If we do fail to setup
authentication,
we no longer kill the task, but instead log and continue. We will
attempt to
start it again next time through the loop.

https://code.launchpad.net/~thumper/juju-core/provisioner-reget-state-info/+merge/168562

(do not edit description out of merge proposal)

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

Affected files:
   A [revision details]
   M worker/provisioner/export_test.go
   M worker/provisioner/provisioner.go
   M worker/provisioner/provisioner_task.go

Revision history for this message
William Reade (fwereade) wrote :

Strongly -1 on the clever bits here. Just barf on failure and I'll be
happy :).

https://codereview.appspot.com/9824047/diff/1/worker/provisioner/provisioner_task.go
File worker/provisioner/provisioner_task.go (right):

https://codereview.appspot.com/9824047/diff/1/worker/provisioner/provisioner_task.go#newcode304
worker/provisioner/provisioner_task.go:304: // Don't return a real
error, just try again next time.
This is problematic, because the watcher won't report that machine again
until it changes -- which it won't. For this to work, we need the
provisioner to be restarted regularly... but I'm hoping for a
provisioner that can run for weeks without breaking sweat.

https://codereview.appspot.com/9824047/diff/1/worker/provisioner/provisioner_task.go#newcode360
worker/provisioner/provisioner_task.go:360: // info if config becomes
invalid.
Honestly, I think this is sufficient justification to nuke the whole
provisioner; it'll be restarted in a few seconds, and for now I'd rather
not even try to provision a machine (which can be expected to cost our
users actual money) unless I'm as sure as I can be that I'll be able to
start a functioning machine agent...

https://codereview.appspot.com/9824047/

Revision history for this message
William Reade (fwereade) wrote :

Re what we do without a valid environment... hmm. Thinking aloud:

* we certainly start off with an invalid environ config, and only get a
valid one once whoever started the environment has connected for the
first time.

* today, on first startup, there will be one machine present; but it'll
be marked as provisioned already, so it won't cause env access directly;
and if we're tolerant of list-instances failures, we'll be able to
complete the first processMachines without difficulty. But I don't think
we can necessarily depend on that forever...

* in theory, it is impossible to set an invalid environ config in state,
except via the special mechanism at bootstrap time.

* in practice, an environ config can most certainly become invalid --
you can (1) replace the config with *any* valid config, which might ofc
be invalid in this context (changed environ type, for example, will
screw us most comprehensively) and (2) config update is racy anyway,
such that two users making valid changes concurrently can end up with an
environment config that reflects neither of their desires... and I bet
there's a way for that to cause invalidity. (oh, and, on inspection,
(3): it looks like values can't be dependably unset anyway. we probably
need that too. grar.)

* BUT the consequences of an invalid environ affect many more things
than just the provisioner.

* AND SO it would be folly to spend time and effort hardening the
provisioner against catastrophic environ failure when we could be fixing
the root cause.

* AND THUS you should (1) wait for a valid config at startup before
processing machines and (2) proceed otherwise as though the environment
configuration were always guaranteed consistent and valid from a juju
perspective and (3) use the time thereby saved to fix SetEnvironConfig.

* BUT ALSO remember that a valid *environ config* does not necessarily
contain valid *provider credentials*. So issues can always arise at
runtime anyway, and should I think be dealt with as follows:

   * StartInstance: record on machine via SetInstanceError.
   * AllInstances: log and ignore -- I don't think there are negative
consequences (out-of-date
     instance list is always possible anyway).
   * StopInstances: log and ignore, they'll be tried again next time we
processMachines. (We should
     add a periodic processMachines call so that we do at least handle
them in something resembling
     a timely fashion. Not too hard, I think?)

https://codereview.appspot.com/9824047/

Revision history for this message
Tim Penhey (thumper) wrote :

Please take a look.

https://codereview.appspot.com/9824047/diff/1/worker/provisioner/provisioner_task.go
File worker/provisioner/provisioner_task.go (right):

https://codereview.appspot.com/9824047/diff/1/worker/provisioner/provisioner_task.go#newcode304
worker/provisioner/provisioner_task.go:304: // Don't return a real
error, just try again next time.
On 2013/06/11 09:08:21, fwereade wrote:
> This is problematic, because the watcher won't report that machine
again until
> it changes -- which it won't. For this to work, we need the
provisioner to be
> restarted regularly... but I'm hoping for a provisioner that can run
for weeks
> without breaking sweat.

OK, reverted.

https://codereview.appspot.com/9824047/diff/1/worker/provisioner/provisioner_task.go#newcode360
worker/provisioner/provisioner_task.go:360: // info if config becomes
invalid.
On 2013/06/11 09:08:21, fwereade wrote:
> Honestly, I think this is sufficient justification to nuke the whole
> provisioner; it'll be restarted in a few seconds, and for now I'd
rather not
> even try to provision a machine (which can be expected to cost our
users actual
> money) unless I'm as sure as I can be that I'll be able to start a
functioning
> machine agent...

OK, nuking in progress.

https://codereview.appspot.com/9824047/

Revision history for this message
William Reade (fwereade) wrote :
Revision history for this message
Ian Booth (wallyworld) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.lbox.check'
2--- .lbox.check 2013-04-08 13:46:30 +0000
3+++ .lbox.check 2013-06-17 04:13:30 +0000
4@@ -2,7 +2,7 @@
5
6 set -e
7
8-BADFMT=`find * -name '*.go' | xargs gofmt -l`
9+BADFMT=`find * -name '*.go' -not -name '.#*' | xargs gofmt -l`
10 if [ -n "$BADFMT" ]; then
11 BADFMT=`echo "$BADFMT" | sed "s/^/ /"`
12 echo -e "gofmt is sad:\n\n$BADFMT"
13
14=== modified file 'cmd/juju/plugin_test.go'
15--- cmd/juju/plugin_test.go 2013-06-13 10:56:08 +0000
16+++ cmd/juju/plugin_test.go 2013-06-17 04:13:30 +0000
17@@ -17,6 +17,7 @@
18 )
19
20 type PluginSuite struct {
21+ testing.LoggingSuite
22 oldPath string
23 home *testing.FakeHome
24 }
25@@ -24,6 +25,7 @@
26 var _ = Suite(&PluginSuite{})
27
28 func (suite *PluginSuite) SetUpTest(c *C) {
29+ suite.LoggingSuite.SetUpTest(c)
30 suite.oldPath = os.Getenv("PATH")
31 suite.home = testing.MakeSampleHome(c)
32 os.Setenv("PATH", "/bin:"+testing.HomePath())
33@@ -32,6 +34,7 @@
34 func (suite *PluginSuite) TearDownTest(c *C) {
35 suite.home.Restore()
36 os.Setenv("PATH", suite.oldPath)
37+ suite.LoggingSuite.TearDownTest(c)
38 }
39
40 func (*PluginSuite) TestFindPlugins(c *C) {
41@@ -195,8 +198,6 @@
42 fi
43 echo "{{.Name}} description"
44 exit {{.ExitStatus}}
45-else
46- echo "No --description" >2
47 fi
48
49 if [ "$1" = "--help" ]; then
50
51=== modified file 'cmd/juju/status.go'
52--- cmd/juju/status.go 2013-06-04 11:50:12 +0000
53+++ cmd/juju/status.go 2013-06-17 04:13:30 +0000
54@@ -10,6 +10,7 @@
55 "launchpad.net/juju-core/charm"
56 "launchpad.net/juju-core/cmd"
57 "launchpad.net/juju-core/environs"
58+ "launchpad.net/juju-core/instance"
59 "launchpad.net/juju-core/juju"
60 "launchpad.net/juju-core/state"
61 "launchpad.net/juju-core/state/api/params"
62@@ -42,8 +43,8 @@
63 }
64
65 type statusContext struct {
66- instances map[state.InstanceId]environs.Instance
67- machines map[string]*state.Machine
68+ instances map[state.InstanceId]instance.Instance
69+ machines map[string][]*state.Machine
70 services map[string]*state.Service
71 units map[string]map[string]*state.Unit
72 }
73@@ -55,14 +56,14 @@
74 }
75 defer conn.Close()
76
77- var ctxt statusContext
78- if ctxt.machines, err = fetchAllMachines(conn.State); err != nil {
79- return err
80- }
81- if ctxt.services, ctxt.units, err = fetchAllServicesAndUnits(conn.State); err != nil {
82- return err
83- }
84- ctxt.instances, err = fetchAllInstances(conn.Environ)
85+ var context statusContext
86+ if context.machines, err = fetchAllMachines(conn.State); err != nil {
87+ return err
88+ }
89+ if context.services, context.units, err = fetchAllServicesAndUnits(conn.State); err != nil {
90+ return err
91+ }
92+ context.instances, err = fetchAllInstances(conn.Environ)
93 if err != nil {
94 // We cannot see instances from the environment, but
95 // there's still lots of potentially useful info to print.
96@@ -72,15 +73,15 @@
97 Machines map[string]machineStatus `json:"machines"`
98 Services map[string]serviceStatus `json:"services"`
99 }{
100- Machines: ctxt.processMachines(),
101- Services: ctxt.processServices(),
102+ Machines: context.processMachines(),
103+ Services: context.processServices(),
104 }
105 return c.out.Write(ctx, result)
106 }
107
108 // fetchAllInstances returns a map from instance id to instance.
109-func fetchAllInstances(env environs.Environ) (map[state.InstanceId]environs.Instance, error) {
110- m := make(map[state.InstanceId]environs.Instance)
111+func fetchAllInstances(env environs.Environ) (map[state.InstanceId]instance.Instance, error) {
112+ m := make(map[state.InstanceId]instance.Instance)
113 insts, err := env.AllInstances()
114 if err != nil {
115 return nil, err
116@@ -91,15 +92,29 @@
117 return m, nil
118 }
119
120-// fetchAllMachines returns a map from machine id to machine.
121-func fetchAllMachines(st *state.State) (map[string]*state.Machine, error) {
122- v := make(map[string]*state.Machine)
123+// fetchAllMachines returns a map from top level machine id to machines, where machines[0] is the host
124+// machine and machines[1..n] are any containers (including nested ones).
125+func fetchAllMachines(st *state.State) (map[string][]*state.Machine, error) {
126+ v := make(map[string][]*state.Machine)
127 machines, err := st.AllMachines()
128 if err != nil {
129 return nil, err
130 }
131+ // AllMachines gives us machines sorted by id.
132 for _, m := range machines {
133- v[m.Id()] = m
134+ parentId, ok := m.ParentId()
135+ if !ok {
136+ // Only top level host machines go directly into the machine map.
137+ v[m.Id()] = []*state.Machine{m}
138+ } else {
139+ topParentId := state.TopParentId(m.Id())
140+ machines, ok := v[topParentId]
141+ if !ok {
142+ panic(fmt.Errorf("unexpected machine id %q", parentId))
143+ }
144+ machines = append(machines, m)
145+ v[topParentId] = machines
146+ }
147 }
148 return v, nil
149 }
150@@ -128,15 +143,40 @@
151 return svcMap, unitMap, nil
152 }
153
154-func (ctxt *statusContext) processMachines() map[string]machineStatus {
155+func (context *statusContext) processMachines() map[string]machineStatus {
156 machinesMap := make(map[string]machineStatus)
157- for _, m := range ctxt.machines {
158- machinesMap[m.Id()] = ctxt.processMachine(m)
159+ for id, machines := range context.machines {
160+ hostStatus := context.makeMachineStatus(machines[0])
161+ context.processMachine(machines, &hostStatus, 0)
162+ machinesMap[id] = hostStatus
163 }
164 return machinesMap
165 }
166
167-func (ctxt *statusContext) processMachine(machine *state.Machine) (status machineStatus) {
168+func (context *statusContext) processMachine(machines []*state.Machine, host *machineStatus, startIndex int) (nextIndex int) {
169+ nextIndex = startIndex + 1
170+ currentHost := host
171+ var previousContainer *machineStatus
172+ for nextIndex < len(machines) {
173+ machine := machines[nextIndex]
174+ container := context.makeMachineStatus(machine)
175+ if currentHost.Id == state.ParentId(machine.Id()) {
176+ currentHost.Containers[machine.Id()] = container
177+ previousContainer = &container
178+ nextIndex++
179+ } else {
180+ if state.NestingLevel(machine.Id()) > state.NestingLevel(previousContainer.Id) {
181+ nextIndex = context.processMachine(machines, previousContainer, nextIndex-1)
182+ } else {
183+ break
184+ }
185+ }
186+ }
187+ return
188+}
189+
190+func (context *statusContext) makeMachineStatus(machine *state.Machine) (status machineStatus) {
191+ status.Id = machine.Id()
192 status.Life,
193 status.AgentVersion,
194 status.AgentState,
195@@ -146,7 +186,7 @@
196 instid, ok := machine.InstanceId()
197 if ok {
198 status.InstanceId = instid
199- instance, ok := ctxt.instances[instid]
200+ instance, ok := context.instances[instid]
201 if ok {
202 status.DNSName, _ = instance.DNSName()
203 } else {
204@@ -163,43 +203,44 @@
205 // in the output.
206 status.AgentState = ""
207 }
208+ status.Containers = make(map[string]machineStatus)
209 return
210 }
211
212-func (ctxt *statusContext) processServices() map[string]serviceStatus {
213+func (context *statusContext) processServices() map[string]serviceStatus {
214 servicesMap := make(map[string]serviceStatus)
215- for _, s := range ctxt.services {
216- servicesMap[s.Name()] = ctxt.processService(s)
217+ for _, s := range context.services {
218+ servicesMap[s.Name()] = context.processService(s)
219 }
220 return servicesMap
221 }
222
223-func (ctxt *statusContext) processService(service *state.Service) (status serviceStatus) {
224+func (context *statusContext) processService(service *state.Service) (status serviceStatus) {
225 url, _ := service.CharmURL()
226 status.Charm = url.String()
227 status.Exposed = service.IsExposed()
228 status.Life = processLife(service)
229 var err error
230- status.Relations, status.SubordinateTo, err = ctxt.processRelations(service)
231+ status.Relations, status.SubordinateTo, err = context.processRelations(service)
232 if err != nil {
233 status.Err = err
234 return
235 }
236 if service.IsPrincipal() {
237- status.Units = ctxt.processUnits(ctxt.units[service.Name()])
238+ status.Units = context.processUnits(context.units[service.Name()])
239 }
240 return status
241 }
242
243-func (ctxt *statusContext) processUnits(units map[string]*state.Unit) map[string]unitStatus {
244+func (context *statusContext) processUnits(units map[string]*state.Unit) map[string]unitStatus {
245 unitsMap := make(map[string]unitStatus)
246 for _, unit := range units {
247- unitsMap[unit.Name()] = ctxt.processUnit(unit)
248+ unitsMap[unit.Name()] = context.processUnit(unit)
249 }
250 return unitsMap
251 }
252
253-func (ctxt *statusContext) processUnit(unit *state.Unit) (status unitStatus) {
254+func (context *statusContext) processUnit(unit *state.Unit) (status unitStatus) {
255 status.PublicAddress, _ = unit.PublicAddress()
256 if unit.IsPrincipal() {
257 status.Machine, _ = unit.AssignedMachineId()
258@@ -212,16 +253,16 @@
259 if subUnits := unit.SubordinateNames(); len(subUnits) > 0 {
260 status.Subordinates = make(map[string]unitStatus)
261 for _, name := range subUnits {
262- subUnit := ctxt.unitByName(name)
263- status.Subordinates[name] = ctxt.processUnit(subUnit)
264+ subUnit := context.unitByName(name)
265+ status.Subordinates[name] = context.processUnit(subUnit)
266 }
267 }
268 return
269 }
270
271-func (ctxt *statusContext) unitByName(name string) *state.Unit {
272+func (context *statusContext) unitByName(name string) *state.Unit {
273 serviceName := strings.Split(name, "/")[0]
274- return ctxt.units[serviceName][name]
275+ return context.units[serviceName][name]
276 }
277
278 func (*statusContext) processRelations(service *state.Service) (related map[string][]string, subord []string, err error) {
279@@ -311,15 +352,17 @@
280 }
281
282 type machineStatus struct {
283- Err error `json:"-" yaml:",omitempty"`
284- AgentState params.Status `json:"agent-state,omitempty" yaml:"agent-state,omitempty"`
285- AgentStateInfo string `json:"agent-state-info,omitempty" yaml:"agent-state-info,omitempty"`
286- AgentVersion string `json:"agent-version,omitempty" yaml:"agent-version,omitempty"`
287- DNSName string `json:"dns-name,omitempty" yaml:"dns-name,omitempty"`
288- InstanceId state.InstanceId `json:"instance-id,omitempty" yaml:"instance-id,omitempty"`
289- InstanceState string `json:"instance-state,omitempty" yaml:"instance-state,omitempty"`
290- Life string `json:"life,omitempty" yaml:"life,omitempty"`
291- Series string `json:"series,omitempty" yaml:"series,omitempty"`
292+ Err error `json:"-" yaml:",omitempty"`
293+ AgentState params.Status `json:"agent-state,omitempty" yaml:"agent-state,omitempty"`
294+ AgentStateInfo string `json:"agent-state-info,omitempty" yaml:"agent-state-info,omitempty"`
295+ AgentVersion string `json:"agent-version,omitempty" yaml:"agent-version,omitempty"`
296+ DNSName string `json:"dns-name,omitempty" yaml:"dns-name,omitempty"`
297+ InstanceId state.InstanceId `json:"instance-id,omitempty" yaml:"instance-id,omitempty"`
298+ InstanceState string `json:"instance-state,omitempty" yaml:"instance-state,omitempty"`
299+ Life string `json:"life,omitempty" yaml:"life,omitempty"`
300+ Series string `json:"series,omitempty" yaml:"series,omitempty"`
301+ Id string `json:"-" yaml:"-"`
302+ Containers map[string]machineStatus `json:"containers,omitempty" yaml:"containers,omitempty"`
303 }
304
305 // A goyaml bug means we can't declare these types
306
307=== modified file 'cmd/juju/status_test.go'
308--- cmd/juju/status_test.go 2013-05-27 03:00:31 +0000
309+++ cmd/juju/status_test.go 2013-06-17 04:13:30 +0000
310@@ -117,6 +117,32 @@
311 "instance-id": "dummyenv-4",
312 "series": "series",
313 }
314+ machine1WithContainers = M{
315+ "agent-state": "started",
316+ "containers": M{
317+ "1/lxc/0": M{
318+ "agent-state": "started",
319+ "containers": M{
320+ "1/lxc/0/lxc/0": M{
321+ "agent-state": "started",
322+ "dns-name": "dummyenv-3.dns",
323+ "instance-id": "dummyenv-3",
324+ "series": "series",
325+ },
326+ },
327+ "dns-name": "dummyenv-2.dns",
328+ "instance-id": "dummyenv-2",
329+ "series": "series",
330+ },
331+ "1/lxc/1": M{
332+ "instance-id": "pending",
333+ "series": "series",
334+ },
335+ },
336+ "dns-name": "dummyenv-1.dns",
337+ "instance-id": "dummyenv-1",
338+ "series": "series",
339+ }
340 unexposedService = M{
341 "charm": "local:series/dummy-1",
342 "exposed": false,
343@@ -716,6 +742,59 @@
344 },
345 },
346 },
347+ ), test(
348+ "machines with containers",
349+ addMachine{"0", state.JobManageEnviron},
350+ startAliveMachine{"0"},
351+ setMachineStatus{"0", params.StatusStarted, ""},
352+ addCharm{"mysql"},
353+ addService{"mysql", "mysql"},
354+ setServiceExposed{"mysql", true},
355+
356+ addMachine{"1", state.JobHostUnits},
357+ startAliveMachine{"1"},
358+ setMachineStatus{"1", params.StatusStarted, ""},
359+ addAliveUnit{"mysql", "1"},
360+ setUnitStatus{"mysql/0", params.StatusStarted, ""},
361+
362+ // A container on machine 1.
363+ addContainer{"1", "1/lxc/0", state.JobHostUnits},
364+ startAliveMachine{"1/lxc/0"},
365+ setMachineStatus{"1/lxc/0", params.StatusStarted, ""},
366+ addAliveUnit{"mysql", "1/lxc/0"},
367+ setUnitStatus{"mysql/1", params.StatusStarted, ""},
368+ addContainer{"1", "1/lxc/1", state.JobHostUnits},
369+
370+ // A nested container.
371+ addContainer{"1/lxc/0", "1/lxc/0/lxc/0", state.JobHostUnits},
372+ startAliveMachine{"1/lxc/0/lxc/0"},
373+ setMachineStatus{"1/lxc/0/lxc/0", params.StatusStarted, ""},
374+
375+ expect{
376+ "machines with nested containers",
377+ M{
378+ "machines": M{
379+ "0": machine0,
380+ "1": machine1WithContainers,
381+ },
382+ "services": M{
383+ "mysql": M{
384+ "charm": "local:series/mysql-1",
385+ "exposed": true,
386+ "units": M{
387+ "mysql/0": M{
388+ "machine": "1",
389+ "agent-state": "started",
390+ },
391+ "mysql/1": M{
392+ "machine": "1/lxc/0",
393+ "agent-state": "started",
394+ },
395+ },
396+ },
397+ },
398+ },
399+ },
400 ),
401 }
402
403@@ -732,6 +811,24 @@
404 c.Assert(m.Id(), Equals, am.machineId)
405 }
406
407+type addContainer struct {
408+ parentId string
409+ machineId string
410+ job state.MachineJob
411+}
412+
413+func (ac addContainer) step(c *C, ctx *context) {
414+ params := &state.AddMachineParams{
415+ ParentId: ac.parentId,
416+ ContainerType: state.LXC,
417+ Series: "series",
418+ Jobs: []state.MachineJob{ac.job},
419+ }
420+ m, err := ctx.st.AddMachineWithConstraints(params)
421+ c.Assert(err, IsNil)
422+ c.Assert(m.Id(), Equals, ac.machineId)
423+}
424+
425 type startMachine struct {
426 machineId string
427 }
428
429=== modified file 'environs/azure/environ.go'
430--- environs/azure/environ.go 2013-06-11 12:41:10 +0000
431+++ environs/azure/environ.go 2013-06-17 04:13:30 +0000
432@@ -7,6 +7,7 @@
433 "launchpad.net/juju-core/constraints"
434 "launchpad.net/juju-core/environs"
435 "launchpad.net/juju-core/environs/config"
436+ "launchpad.net/juju-core/instance"
437 "launchpad.net/juju-core/state"
438 "launchpad.net/juju-core/state/api"
439 "launchpad.net/juju-core/state/api/params"
440@@ -43,22 +44,22 @@
441 }
442
443 // StartInstance is specified in the Environ interface.
444-func (env *azureEnviron) StartInstance(machineId, machineNonce string, series string, cons constraints.Value, info *state.Info, apiInfo *api.Info) (environs.Instance, error) {
445+func (env *azureEnviron) StartInstance(machineId, machineNonce string, series string, cons constraints.Value, info *state.Info, apiInfo *api.Info) (instance.Instance, error) {
446 panic("unimplemented")
447 }
448
449 // StopInstances is specified in the Environ interface.
450-func (env *azureEnviron) StopInstances([]environs.Instance) error {
451+func (env *azureEnviron) StopInstances([]instance.Instance) error {
452 panic("unimplemented")
453 }
454
455 // Instances is specified in the Environ interface.
456-func (env *azureEnviron) Instances(ids []state.InstanceId) ([]environs.Instance, error) {
457+func (env *azureEnviron) Instances(ids []state.InstanceId) ([]instance.Instance, error) {
458 panic("unimplemented")
459 }
460
461 // AllInstances is specified in the Environ interface.
462-func (env *azureEnviron) AllInstances() ([]environs.Instance, error) {
463+func (env *azureEnviron) AllInstances() ([]instance.Instance, error) {
464 panic("unimplemented")
465 }
466
467@@ -73,7 +74,7 @@
468 }
469
470 // Destroy is specified in the Environ interface.
471-func (env *azureEnviron) Destroy(insts []environs.Instance) error {
472+func (env *azureEnviron) Destroy(insts []instance.Instance) error {
473 panic("unimplemented")
474 }
475
476
477=== modified file 'environs/azure/instance.go'
478--- environs/azure/instance.go 2013-06-11 12:41:10 +0000
479+++ environs/azure/instance.go 2013-06-17 04:13:30 +0000
480@@ -4,7 +4,7 @@
481 package azure
482
483 import (
484- "launchpad.net/juju-core/environs"
485+ "launchpad.net/juju-core/instance"
486 "launchpad.net/juju-core/state"
487 "launchpad.net/juju-core/state/api/params"
488 )
489@@ -12,7 +12,7 @@
490 type azureInstance struct{}
491
492 // azureInstance implements Instance.
493-var _ environs.Instance = (*azureInstance)(nil)
494+var _ instance.Instance = (*azureInstance)(nil)
495
496 // Id is specified in the Instance interface.
497 func (instance *azureInstance) Id() state.InstanceId {
498
499=== modified file 'environs/dummy/environs.go'
500--- environs/dummy/environs.go 2013-05-28 08:05:49 +0000
501+++ environs/dummy/environs.go 2013-06-17 04:13:30 +0000
502@@ -29,6 +29,7 @@
503 "launchpad.net/juju-core/environs"
504 "launchpad.net/juju-core/environs/config"
505 envtesting "launchpad.net/juju-core/environs/testing"
506+ "launchpad.net/juju-core/instance"
507 "launchpad.net/juju-core/log"
508 "launchpad.net/juju-core/schema"
509 "launchpad.net/juju-core/state"
510@@ -75,7 +76,7 @@
511 Env string
512 MachineId string
513 MachineNonce string
514- Instance environs.Instance
515+ Instance instance.Instance
516 Constraints constraints.Value
517 Info *state.Info
518 APIInfo *api.Info
519@@ -84,7 +85,7 @@
520
521 type OpStopInstances struct {
522 Env string
523- Instances []environs.Instance
524+ Instances []instance.Instance
525 }
526
527 type OpOpenPorts struct {
528@@ -122,7 +123,7 @@
529 ops chan<- Operation
530 mu sync.Mutex
531 maxId int // maximum instance id allocated so far.
532- insts map[state.InstanceId]*instance
533+ insts map[state.InstanceId]*dummyInstance
534 globalPorts map[params.Port]bool
535 firewallMode config.FirewallMode
536 bootstrapped bool
537@@ -224,7 +225,7 @@
538 s := &environState{
539 name: name,
540 ops: ops,
541- insts: make(map[state.InstanceId]*instance),
542+ insts: make(map[state.InstanceId]*dummyInstance),
543 globalPorts: make(map[params.Port]bool),
544 firewallMode: fwmode,
545 }
546@@ -515,7 +516,7 @@
547 return nil
548 }
549
550-func (e *environ) Destroy([]environs.Instance) error {
551+func (e *environ) Destroy([]instance.Instance) error {
552 defer delay()
553 if err := e.checkBroken("Destroy"); err != nil {
554 return err
555@@ -527,7 +528,7 @@
556 return nil
557 }
558
559-func (e *environ) StartInstance(machineId, machineNonce string, series string, cons constraints.Value, info *state.Info, apiInfo *api.Info) (environs.Instance, error) {
560+func (e *environ) StartInstance(machineId, machineNonce string, series string, cons constraints.Value, info *state.Info, apiInfo *api.Info) (instance.Instance, error) {
561 defer delay()
562 log.Infof("environs/dummy: dummy startinstance, machine %s", machineId)
563 if err := e.checkBroken("StartInstance"); err != nil {
564@@ -552,7 +553,7 @@
565 if apiInfo.Tag != state.MachineTag(machineId) {
566 return nil, fmt.Errorf("entity tag must match started machine")
567 }
568- i := &instance{
569+ i := &dummyInstance{
570 state: e.state,
571 id: state.InstanceId(fmt.Sprintf("%s-%d", e.state.name, e.state.maxId)),
572 ports: make(map[params.Port]bool),
573@@ -574,7 +575,7 @@
574 return i, nil
575 }
576
577-func (e *environ) StopInstances(is []environs.Instance) error {
578+func (e *environ) StopInstances(is []instance.Instance) error {
579 defer delay()
580 if err := e.checkBroken("StopInstance"); err != nil {
581 return err
582@@ -582,7 +583,7 @@
583 e.state.mu.Lock()
584 defer e.state.mu.Unlock()
585 for _, i := range is {
586- delete(e.state.insts, i.(*instance).id)
587+ delete(e.state.insts, i.(*dummyInstance).id)
588 }
589 e.state.ops <- OpStopInstances{
590 Env: e.state.name,
591@@ -591,7 +592,7 @@
592 return nil
593 }
594
595-func (e *environ) Instances(ids []state.InstanceId) (insts []environs.Instance, err error) {
596+func (e *environ) Instances(ids []state.InstanceId) (insts []instance.Instance, err error) {
597 defer delay()
598 if err := e.checkBroken("Instances"); err != nil {
599 return nil, err
600@@ -616,12 +617,12 @@
601 return
602 }
603
604-func (e *environ) AllInstances() ([]environs.Instance, error) {
605+func (e *environ) AllInstances() ([]instance.Instance, error) {
606 defer delay()
607 if err := e.checkBroken("AllInstances"); err != nil {
608 return nil, err
609 }
610- var insts []environs.Instance
611+ var insts []instance.Instance
612 e.state.mu.Lock()
613 defer e.state.mu.Unlock()
614 for _, v := range e.state.insts {
615@@ -674,7 +675,7 @@
616 return &providerInstance
617 }
618
619-type instance struct {
620+type dummyInstance struct {
621 state *environState
622 ports map[params.Port]bool
623 id state.InstanceId
624@@ -682,20 +683,20 @@
625 series string
626 }
627
628-func (inst *instance) Id() state.InstanceId {
629+func (inst *dummyInstance) Id() state.InstanceId {
630 return inst.id
631 }
632
633-func (inst *instance) DNSName() (string, error) {
634+func (inst *dummyInstance) DNSName() (string, error) {
635 defer delay()
636 return string(inst.id) + ".dns", nil
637 }
638
639-func (inst *instance) WaitDNSName() (string, error) {
640+func (inst *dummyInstance) WaitDNSName() (string, error) {
641 return inst.DNSName()
642 }
643
644-func (inst *instance) OpenPorts(machineId string, ports []params.Port) error {
645+func (inst *dummyInstance) OpenPorts(machineId string, ports []params.Port) error {
646 defer delay()
647 log.Infof("environs/dummy: openPorts %s, %#v", machineId, ports)
648 if inst.state.firewallMode != config.FwInstance {
649@@ -719,7 +720,7 @@
650 return nil
651 }
652
653-func (inst *instance) ClosePorts(machineId string, ports []params.Port) error {
654+func (inst *dummyInstance) ClosePorts(machineId string, ports []params.Port) error {
655 defer delay()
656 if inst.state.firewallMode != config.FwInstance {
657 return fmt.Errorf("invalid firewall mode for closing ports on instance: %q",
658@@ -742,7 +743,7 @@
659 return nil
660 }
661
662-func (inst *instance) Ports(machineId string) (ports []params.Port, err error) {
663+func (inst *dummyInstance) Ports(machineId string) (ports []params.Port, err error) {
664 defer delay()
665 if inst.state.firewallMode != config.FwInstance {
666 return nil, fmt.Errorf("invalid firewall mode for retrieving ports from instance: %q",
667
668=== modified file 'environs/ec2/ec2.go'
669--- environs/ec2/ec2.go 2013-06-10 10:30:13 +0000
670+++ environs/ec2/ec2.go 2013-06-17 04:13:30 +0000
671@@ -17,6 +17,7 @@
672 "launchpad.net/juju-core/environs/instances"
673 "launchpad.net/juju-core/environs/tools"
674 "launchpad.net/juju-core/errors"
675+ "launchpad.net/juju-core/instance"
676 "launchpad.net/juju-core/log"
677 "launchpad.net/juju-core/state"
678 "launchpad.net/juju-core/state/api"
679@@ -65,22 +66,22 @@
680
681 var _ environs.Environ = (*environ)(nil)
682
683-type instance struct {
684+type ec2Instance struct {
685 e *environ
686 *ec2.Instance
687 }
688
689-func (inst *instance) String() string {
690+func (inst *ec2Instance) String() string {
691 return inst.InstanceId
692 }
693
694-var _ environs.Instance = (*instance)(nil)
695+var _ instance.Instance = (*ec2Instance)(nil)
696
697-func (inst *instance) Id() state.InstanceId {
698+func (inst *ec2Instance) Id() state.InstanceId {
699 return state.InstanceId(inst.InstanceId)
700 }
701
702-func (inst *instance) DNSName() (string, error) {
703+func (inst *ec2Instance) DNSName() (string, error) {
704 if inst.Instance.DNSName != "" {
705 return inst.Instance.DNSName, nil
706 }
707@@ -90,18 +91,18 @@
708 if err != nil {
709 return "", err
710 }
711- freshInst := insts[0].(*instance).Instance
712+ freshInst := insts[0].(*ec2Instance).Instance
713 if freshInst.DNSName == "" {
714- return "", environs.ErrNoDNSName
715+ return "", instance.ErrNoDNSName
716 }
717 inst.Instance.DNSName = freshInst.DNSName
718 return freshInst.DNSName, nil
719 }
720
721-func (inst *instance) WaitDNSName() (string, error) {
722+func (inst *ec2Instance) WaitDNSName() (string, error) {
723 for a := longAttempt.Start(); a.Next(); {
724 name, err := inst.DNSName()
725- if err == nil || err != environs.ErrNoDNSName {
726+ if err == nil || err != instance.ErrNoDNSName {
727 return name, err
728 }
729 }
730@@ -278,7 +279,7 @@
731 if err != nil {
732 // ignore error on StopInstance because the previous error is
733 // more important.
734- e.StopInstances([]environs.Instance{inst})
735+ e.StopInstances([]instance.Instance{inst})
736 return fmt.Errorf("cannot save state: %v", err)
737 }
738 // TODO make safe in the case of racing Bootstraps
739@@ -313,7 +314,7 @@
740 if inst == nil {
741 continue
742 }
743- name := inst.(*instance).Instance.DNSName
744+ name := inst.(*ec2Instance).Instance.DNSName
745 if name != "" {
746 statePortSuffix := fmt.Sprintf(":%d", config.StatePort())
747 apiPortSuffix := fmt.Sprintf(":%d", config.APIPort())
748@@ -340,7 +341,7 @@
749 return []string{imagemetadata.DefaultBaseURL}, nil
750 }
751
752-func (e *environ) StartInstance(machineId, machineNonce string, series string, cons constraints.Value, info *state.Info, apiInfo *api.Info) (environs.Instance, error) {
753+func (e *environ) StartInstance(machineId, machineNonce string, series string, cons constraints.Value, info *state.Info, apiInfo *api.Info) (instance.Instance, error) {
754 possibleTools, err := environs.FindInstanceTools(e, series, cons)
755 if err != nil {
756 return nil, err
757@@ -397,7 +398,7 @@
758
759 // startInstance is the internal version of StartInstance, used by Bootstrap
760 // as well as via StartInstance itself.
761-func (e *environ) startInstance(scfg *startInstanceParams) (environs.Instance, error) {
762+func (e *environ) startInstance(scfg *startInstanceParams) (instance.Instance, error) {
763 series := scfg.possibleTools.Series()
764 if len(series) != 1 {
765 return nil, fmt.Errorf("expected single series, got %v", series)
766@@ -455,15 +456,15 @@
767 if len(instances.Instances) != 1 {
768 return nil, fmt.Errorf("expected 1 started instance, got %d", len(instances.Instances))
769 }
770- inst := &instance{e, &instances.Instances[0]}
771+ inst := &ec2Instance{e, &instances.Instances[0]}
772 log.Infof("environs/ec2: started instance %q", inst.Id())
773 return inst, nil
774 }
775
776-func (e *environ) StopInstances(insts []environs.Instance) error {
777+func (e *environ) StopInstances(insts []instance.Instance) error {
778 ids := make([]state.InstanceId, len(insts))
779 for i, inst := range insts {
780- ids[i] = inst.(*instance).Id()
781+ ids[i] = inst.(*ec2Instance).Id()
782 }
783 return e.terminateInstances(ids)
784 }
785@@ -472,7 +473,7 @@
786 // id whose corresponding insts slot is nil.
787 // It returns environs.ErrPartialInstances if the insts
788 // slice has not been completely filled.
789-func (e *environ) gatherInstances(ids []state.InstanceId, insts []environs.Instance) error {
790+func (e *environ) gatherInstances(ids []state.InstanceId, insts []instance.Instance) error {
791 var need []string
792 for i, inst := range insts {
793 if inst == nil {
794@@ -502,7 +503,7 @@
795 for k := range r.Instances {
796 if r.Instances[k].InstanceId == string(id) {
797 inst := r.Instances[k]
798- insts[i] = &instance{e, &inst}
799+ insts[i] = &ec2Instance{e, &inst}
800 n++
801 }
802 }
803@@ -514,11 +515,11 @@
804 return nil
805 }
806
807-func (e *environ) Instances(ids []state.InstanceId) ([]environs.Instance, error) {
808+func (e *environ) Instances(ids []state.InstanceId) ([]instance.Instance, error) {
809 if len(ids) == 0 {
810 return nil, nil
811 }
812- insts := make([]environs.Instance, len(ids))
813+ insts := make([]instance.Instance, len(ids))
814 // Make a series of requests to cope with eventual consistency.
815 // Each request will attempt to add more instances to the requested
816 // set.
817@@ -543,7 +544,7 @@
818 return insts, nil
819 }
820
821-func (e *environ) AllInstances() ([]environs.Instance, error) {
822+func (e *environ) AllInstances() ([]instance.Instance, error) {
823 filter := ec2.NewFilter()
824 filter.Add("instance-state-name", "pending", "running")
825 filter.Add("group-name", e.jujuGroupName())
826@@ -551,17 +552,17 @@
827 if err != nil {
828 return nil, err
829 }
830- var insts []environs.Instance
831+ var insts []instance.Instance
832 for _, r := range resp.Reservations {
833 for i := range r.Instances {
834 inst := r.Instances[i]
835- insts = append(insts, &instance{e, &inst})
836+ insts = append(insts, &ec2Instance{e, &inst})
837 }
838 }
839 return insts, nil
840 }
841
842-func (e *environ) Destroy(ensureInsts []environs.Instance) error {
843+func (e *environ) Destroy(ensureInsts []instance.Instance) error {
844 log.Infof("environs/ec2: destroying environment %q", e.name)
845 insts, err := e.AllInstances()
846 if err != nil {
847@@ -577,7 +578,7 @@
848 // Add any instances we've been told about but haven't yet shown
849 // up in the instance list.
850 for _, inst := range ensureInsts {
851- id := state.InstanceId(inst.(*instance).InstanceId)
852+ id := state.InstanceId(inst.(*ec2Instance).InstanceId)
853 if !found[id] {
854 ids = append(ids, id)
855 found[id] = true
856@@ -761,7 +762,7 @@
857 return "juju-" + e.name
858 }
859
860-func (inst *instance) OpenPorts(machineId string, ports []params.Port) error {
861+func (inst *ec2Instance) OpenPorts(machineId string, ports []params.Port) error {
862 if inst.e.Config().FirewallMode() != config.FwInstance {
863 return fmt.Errorf("invalid firewall mode for opening ports on instance: %q",
864 inst.e.Config().FirewallMode())
865@@ -774,7 +775,7 @@
866 return nil
867 }
868
869-func (inst *instance) ClosePorts(machineId string, ports []params.Port) error {
870+func (inst *ec2Instance) ClosePorts(machineId string, ports []params.Port) error {
871 if inst.e.Config().FirewallMode() != config.FwInstance {
872 return fmt.Errorf("invalid firewall mode for closing ports on instance: %q",
873 inst.e.Config().FirewallMode())
874@@ -787,7 +788,7 @@
875 return nil
876 }
877
878-func (inst *instance) Ports(machineId string) ([]params.Port, error) {
879+func (inst *ec2Instance) Ports(machineId string) ([]params.Port, error) {
880 if inst.e.Config().FirewallMode() != config.FwInstance {
881 return nil, fmt.Errorf("invalid firewall mode for retrieving ports from instance: %q",
882 inst.e.Config().FirewallMode())
883
884=== modified file 'environs/ec2/export_test.go'
885--- environs/ec2/export_test.go 2013-05-30 05:39:33 +0000
886+++ environs/ec2/export_test.go 2013-06-17 04:13:30 +0000
887@@ -11,6 +11,7 @@
888 "launchpad.net/juju-core/environs"
889 "launchpad.net/juju-core/environs/imagemetadata"
890 "launchpad.net/juju-core/environs/jujutest"
891+ "launchpad.net/juju-core/instance"
892 "launchpad.net/juju-core/state"
893 "launchpad.net/juju-core/utils"
894 "net/http"
895@@ -48,8 +49,8 @@
896 return s.(*storage).deleteAll()
897 }
898
899-func InstanceEC2(inst environs.Instance) *ec2.Instance {
900- return inst.(*instance).Instance
901+func InstanceEC2(inst instance.Instance) *ec2.Instance {
902+ return inst.(*ec2Instance).Instance
903 }
904
905 // BucketStorage returns a storage instance addressing
906@@ -147,9 +148,9 @@
907
908 // FabricateInstance creates a new fictitious instance
909 // given an existing instance and a new id.
910-func FabricateInstance(inst environs.Instance, newId string) environs.Instance {
911- oldi := inst.(*instance)
912- newi := &instance{oldi.e, &ec2.Instance{}}
913+func FabricateInstance(inst instance.Instance, newId string) instance.Instance {
914+ oldi := inst.(*ec2Instance)
915+ newi := &ec2Instance{oldi.e, &ec2.Instance{}}
916 *newi.Instance = *oldi.Instance
917 newi.InstanceId = newId
918 return newi
919
920=== modified file 'environs/ec2/live_test.go'
921--- environs/ec2/live_test.go 2013-05-30 00:47:30 +0000
922+++ environs/ec2/live_test.go 2013-06-17 04:13:30 +0000
923@@ -17,6 +17,7 @@
924 "launchpad.net/juju-core/environs/jujutest"
925 envtesting "launchpad.net/juju-core/environs/testing"
926 "launchpad.net/juju-core/errors"
927+ "launchpad.net/juju-core/instance"
928 "launchpad.net/juju-core/juju/testing"
929 "launchpad.net/juju-core/state"
930 coretesting "launchpad.net/juju-core/testing"
931@@ -113,7 +114,7 @@
932
933 func (t *LiveTests) TestInstanceAttributes(c *C) {
934 inst := testing.StartInstance(c, t.Env, "30")
935- defer t.Env.StopInstances([]environs.Instance{inst})
936+ defer t.Env.StopInstances([]instance.Instance{inst})
937 dns, err := inst.WaitDNSName()
938 // TODO(niemeyer): This assert sometimes fails with "no instances found"
939 c.Assert(err, IsNil)
940@@ -132,7 +133,7 @@
941 cons := constraints.MustParse("mem=2G")
942 inst, err := t.Env.StartInstance("31", "fake_nonce", config.DefaultSeries, cons, testing.InvalidStateInfo("31"), testing.InvalidAPIInfo("31"))
943 c.Assert(err, IsNil)
944- defer t.Env.StopInstances([]environs.Instance{inst})
945+ defer t.Env.StopInstances([]instance.Instance{inst})
946 ec2inst := ec2.InstanceEC2(inst)
947 c.Assert(ec2inst.InstanceType, Equals, "m1.medium")
948 }
949@@ -173,14 +174,14 @@
950 c.Assert(err, IsNil)
951
952 inst0 := testing.StartInstance(c, t.Env, "98")
953- defer t.Env.StopInstances([]environs.Instance{inst0})
954+ defer t.Env.StopInstances([]instance.Instance{inst0})
955
956 // Create a same-named group for the second instance
957 // before starting it, to check that it's reused correctly.
958 oldMachineGroup := createGroup(c, ec2conn, groups[2].Name, "old machine group")
959
960 inst1 := testing.StartInstance(c, t.Env, "99")
961- defer t.Env.StopInstances([]environs.Instance{inst1})
962+ defer t.Env.StopInstances([]instance.Instance{inst1})
963
964 groupsResp, err := ec2conn.SecurityGroups(groups, nil)
965 c.Assert(err, IsNil)
966@@ -314,10 +315,10 @@
967 inst1 := ec2.FabricateInstance(inst0, "i-aaaaaaaa")
968 inst2 := testing.StartInstance(c, t.Env, "41")
969
970- err := t.Env.StopInstances([]environs.Instance{inst0, inst1, inst2})
971+ err := t.Env.StopInstances([]instance.Instance{inst0, inst1, inst2})
972 c.Check(err, IsNil)
973
974- var insts []environs.Instance
975+ var insts []instance.Instance
976
977 // We need the retry logic here because we are waiting
978 // for Instances to return an error, and it will not retry
979
980=== modified file 'environs/interface.go'
981--- environs/interface.go 2013-06-10 10:30:13 +0000
982+++ environs/interface.go 2013-06-17 04:13:30 +0000
983@@ -8,6 +8,7 @@
984 "io"
985 "launchpad.net/juju-core/constraints"
986 "launchpad.net/juju-core/environs/config"
987+ "launchpad.net/juju-core/instance"
988 "launchpad.net/juju-core/state"
989 "launchpad.net/juju-core/state/api"
990 "launchpad.net/juju-core/state/api/params"
991@@ -49,36 +50,6 @@
992 InstanceId() (state.InstanceId, error)
993 }
994
995-var ErrNoDNSName = errors.New("DNS name not allocated")
996-
997-// Instance represents the provider-specific notion of a machine.
998-type Instance interface {
999- // Id returns a provider-generated identifier for the Instance.
1000- Id() state.InstanceId
1001-
1002- // DNSName returns the DNS name for the instance.
1003- // If the name is not yet allocated, it will return
1004- // an ErrNoDNSName error.
1005- DNSName() (string, error)
1006-
1007- // WaitDNSName returns the DNS name for the instance,
1008- // waiting until it is allocated if necessary.
1009- WaitDNSName() (string, error)
1010-
1011- // OpenPorts opens the given ports on the instance, which
1012- // should have been started with the given machine id.
1013- OpenPorts(machineId string, ports []params.Port) error
1014-
1015- // ClosePorts closes the given ports on the instance, which
1016- // should have been started with the given machine id.
1017- ClosePorts(machineId string, ports []params.Port) error
1018-
1019- // Ports returns the set of ports open on the instance, which
1020- // should have been started with the given machine id.
1021- // The ports are returned as sorted by state.SortPorts.
1022- Ports(machineId string) ([]params.Port, error)
1023-}
1024-
1025 var ErrNoInstances = errors.New("no instances found")
1026 var ErrPartialInstances = errors.New("only some instances were found")
1027
1028@@ -164,10 +135,10 @@
1029 // which must be unique within an environment, is used by juju to
1030 // protect against the consequences of multiple instances being
1031 // started with the same machine id.
1032- StartInstance(machineId, machineNonce string, series string, cons constraints.Value, info *state.Info, apiInfo *api.Info) (Instance, error)
1033+ StartInstance(machineId, machineNonce string, series string, cons constraints.Value, info *state.Info, apiInfo *api.Info) (instance.Instance, error)
1034
1035 // StopInstances shuts down the given instances.
1036- StopInstances([]Instance) error
1037+ StopInstances([]instance.Instance) error
1038
1039 // Instances returns a slice of instances corresponding to the
1040 // given instance ids. If no instances were found, but there
1041@@ -175,11 +146,11 @@
1042 // some but not all the instances were found, the returned slice
1043 // will have some nil slots, and an ErrPartialInstances error
1044 // will be returned.
1045- Instances(ids []state.InstanceId) ([]Instance, error)
1046+ Instances(ids []state.InstanceId) ([]instance.Instance, error)
1047
1048 // AllInstances returns all instances currently known to the
1049 // environment.
1050- AllInstances() ([]Instance, error)
1051+ AllInstances() ([]instance.Instance, error)
1052
1053 // Storage returns storage specific to the environment.
1054 Storage() Storage
1055@@ -196,7 +167,7 @@
1056 //
1057 // When Destroy has been called, any Environ referring to the
1058 // same remote environment may become invalid
1059- Destroy(insts []Instance) error
1060+ Destroy(insts []instance.Instance) error
1061
1062 // OpenPorts opens the given ports for the whole environment.
1063 // Must only be used if the environment was setup with the
1064
1065=== modified file 'environs/jujutest/livetests.go'
1066--- environs/jujutest/livetests.go 2013-05-30 00:47:30 +0000
1067+++ environs/jujutest/livetests.go 2013-06-17 04:13:30 +0000
1068@@ -14,6 +14,7 @@
1069 "launchpad.net/juju-core/environs/config"
1070 "launchpad.net/juju-core/environs/tools"
1071 "launchpad.net/juju-core/errors"
1072+ "launchpad.net/juju-core/instance"
1073 "launchpad.net/juju-core/juju"
1074 "launchpad.net/juju-core/juju/testing"
1075 "launchpad.net/juju-core/state"
1076@@ -142,7 +143,7 @@
1077 c.Check(insts[0].Id(), Equals, id0)
1078 c.Check(insts[1], IsNil)
1079
1080- err = t.Env.StopInstances([]environs.Instance{inst})
1081+ err = t.Env.StopInstances([]instance.Instance{inst})
1082 c.Assert(err, IsNil)
1083
1084 // The machine may not be marked as shutting down
1085@@ -160,7 +161,7 @@
1086 func (t *LiveTests) TestPorts(c *C) {
1087 inst1 := testing.StartInstance(c, t.Env, "1")
1088 c.Assert(inst1, NotNil)
1089- defer t.Env.StopInstances([]environs.Instance{inst1})
1090+ defer t.Env.StopInstances([]instance.Instance{inst1})
1091 ports, err := inst1.Ports("1")
1092 c.Assert(err, IsNil)
1093 c.Assert(ports, HasLen, 0)
1094@@ -170,7 +171,7 @@
1095 ports, err = inst2.Ports("2")
1096 c.Assert(err, IsNil)
1097 c.Assert(ports, HasLen, 0)
1098- defer t.Env.StopInstances([]environs.Instance{inst2})
1099+ defer t.Env.StopInstances([]instance.Instance{inst2})
1100
1101 // Open some ports and check they're there.
1102 err = inst1.OpenPorts("1", []params.Port{{"udp", 67}, {"tcp", 45}})
1103@@ -260,7 +261,7 @@
1104
1105 // Create instances and check open ports on both instances.
1106 inst1 := testing.StartInstance(c, t.Env, "1")
1107- defer t.Env.StopInstances([]environs.Instance{inst1})
1108+ defer t.Env.StopInstances([]instance.Instance{inst1})
1109 ports, err := t.Env.Ports()
1110 c.Assert(err, IsNil)
1111 c.Assert(ports, HasLen, 0)
1112@@ -269,7 +270,7 @@
1113 ports, err = t.Env.Ports()
1114 c.Assert(err, IsNil)
1115 c.Assert(ports, HasLen, 0)
1116- defer t.Env.StopInstances([]environs.Instance{inst2})
1117+ defer t.Env.StopInstances([]instance.Instance{inst2})
1118
1119 err = t.Env.OpenPorts([]params.Port{{"udp", 67}, {"tcp", 45}, {"tcp", 89}, {"tcp", 99}})
1120 c.Assert(err, IsNil)
1121@@ -630,7 +631,7 @@
1122 // assertInstanceId asserts that the machine has an instance id
1123 // that matches that of the given instance. If the instance is nil,
1124 // It asserts that the instance id is unset.
1125-func assertInstanceId(c *C, m *state.Machine, inst environs.Instance) {
1126+func assertInstanceId(c *C, m *state.Machine, inst instance.Instance) {
1127 var wantId, gotId state.InstanceId
1128 var err error
1129 if inst != nil {
1130@@ -700,7 +701,7 @@
1131 func (t *LiveTests) TestStartInstanceOnUnknownPlatform(c *C) {
1132 inst, err := t.Env.StartInstance("4", "fake_nonce", "unknownseries", constraints.Value{}, testing.InvalidStateInfo("4"), testing.InvalidAPIInfo("4"))
1133 if inst != nil {
1134- err := t.Env.StopInstances([]environs.Instance{inst})
1135+ err := t.Env.StopInstances([]instance.Instance{inst})
1136 c.Check(err, IsNil)
1137 }
1138 c.Assert(inst, IsNil)
1139@@ -713,7 +714,7 @@
1140 func (t *LiveTests) TestStartInstanceWithEmptyNonceFails(c *C) {
1141 inst, err := t.Env.StartInstance("4", "", config.DefaultSeries, constraints.Value{}, testing.InvalidStateInfo("4"), testing.InvalidAPIInfo("4"))
1142 if inst != nil {
1143- err := t.Env.StopInstances([]environs.Instance{inst})
1144+ err := t.Env.StopInstances([]instance.Instance{inst})
1145 c.Check(err, IsNil)
1146 }
1147 c.Assert(inst, IsNil)
1148
1149=== modified file 'environs/jujutest/tests.go'
1150--- environs/jujutest/tests.go 2013-05-30 00:47:30 +0000
1151+++ environs/jujutest/tests.go 2013-06-17 04:13:30 +0000
1152@@ -12,6 +12,7 @@
1153 "launchpad.net/juju-core/environs"
1154 envtesting "launchpad.net/juju-core/environs/testing"
1155 "launchpad.net/juju-core/errors"
1156+ "launchpad.net/juju-core/instance"
1157 "launchpad.net/juju-core/juju/testing"
1158 "launchpad.net/juju-core/state"
1159 coretesting "launchpad.net/juju-core/testing"
1160@@ -107,7 +108,7 @@
1161 c.Assert(insts, HasLen, 2)
1162 c.Assert(insts[0].Id(), Not(Equals), insts[1].Id())
1163
1164- err = e.StopInstances([]environs.Instance{inst0})
1165+ err = e.StopInstances([]instance.Instance{inst0})
1166 c.Assert(err, IsNil)
1167
1168 insts, err = e.Instances([]state.InstanceId{id0, id1})
1169
1170=== modified file 'environs/maas/environ.go'
1171--- environs/maas/environ.go 2013-06-10 10:30:13 +0000
1172+++ environs/maas/environ.go 2013-06-17 04:13:30 +0000
1173@@ -13,6 +13,7 @@
1174 "launchpad.net/juju-core/environs/cloudinit"
1175 "launchpad.net/juju-core/environs/config"
1176 "launchpad.net/juju-core/environs/tools"
1177+ "launchpad.net/juju-core/instance"
1178 "launchpad.net/juju-core/log"
1179 "launchpad.net/juju-core/state"
1180 "launchpad.net/juju-core/state/api"
1181@@ -84,7 +85,7 @@
1182 }
1183
1184 // startBootstrapNode starts the juju bootstrap node for this environment.
1185-func (env *maasEnviron) startBootstrapNode(cons constraints.Value) (environs.Instance, error) {
1186+func (env *maasEnviron) startBootstrapNode(cons constraints.Value) (instance.Instance, error) {
1187 // The bootstrap instance gets machine id "0". This is not related to
1188 // instance ids or MAAS system ids. Juju assigns the machine ID.
1189 const machineID = "0"
1190@@ -352,7 +353,7 @@
1191 }
1192
1193 // StartInstance is specified in the Environ interface.
1194-func (environ *maasEnviron) StartInstance(machineID, machineNonce string, series string, cons constraints.Value, stateInfo *state.Info, apiInfo *api.Info) (environs.Instance, error) {
1195+func (environ *maasEnviron) StartInstance(machineID, machineNonce string, series string, cons constraints.Value, stateInfo *state.Info, apiInfo *api.Info) (instance.Instance, error) {
1196 possibleTools, err := environs.FindInstanceTools(environ, series, cons)
1197 if err != nil {
1198 return nil, err
1199@@ -362,7 +363,7 @@
1200 }
1201
1202 // StopInstances is specified in the Environ interface.
1203-func (environ *maasEnviron) StopInstances(instances []environs.Instance) error {
1204+func (environ *maasEnviron) StopInstances(instances []instance.Instance) error {
1205 // Shortcut to exit quickly if 'instances' is an empty slice or nil.
1206 if len(instances) == 0 {
1207 return nil
1208@@ -381,7 +382,7 @@
1209 }
1210
1211 // releaseInstance releases a single instance.
1212-func (environ *maasEnviron) releaseInstance(inst environs.Instance) error {
1213+func (environ *maasEnviron) releaseInstance(inst instance.Instance) error {
1214 maasInst := inst.(*maasInstance)
1215 maasObj := maasInst.maasObject
1216 _, err := maasObj.CallPost("release", nil)
1217@@ -391,10 +392,10 @@
1218 return err
1219 }
1220
1221-// Instances returns the environs.Instance objects corresponding to the given
1222+// Instances returns the instance.Instance objects corresponding to the given
1223 // slice of state.InstanceId. Similar to what the ec2 provider does,
1224 // Instances returns nil if the given slice is empty or nil.
1225-func (environ *maasEnviron) Instances(ids []state.InstanceId) ([]environs.Instance, error) {
1226+func (environ *maasEnviron) Instances(ids []state.InstanceId) ([]instance.Instance, error) {
1227 if len(ids) == 0 {
1228 return nil, nil
1229 }
1230@@ -406,7 +407,7 @@
1231 // If the some of the intances could not be found, it returns the instance
1232 // that could be found plus the error environs.ErrPartialInstances in the error
1233 // return.
1234-func (environ *maasEnviron) instances(ids []state.InstanceId) ([]environs.Instance, error) {
1235+func (environ *maasEnviron) instances(ids []state.InstanceId) ([]instance.Instance, error) {
1236 nodeListing := environ.getMAASClient().GetSubObject("nodes")
1237 filter := getSystemIdValues(ids)
1238 listNodeObjects, err := nodeListing.CallGet("list", filter)
1239@@ -417,7 +418,7 @@
1240 if err != nil {
1241 return nil, err
1242 }
1243- instances := make([]environs.Instance, len(listNodes))
1244+ instances := make([]instance.Instance, len(listNodes))
1245 for index, nodeObj := range listNodes {
1246 node, err := nodeObj.GetMAASObject()
1247 if err != nil {
1248@@ -434,8 +435,8 @@
1249 return instances, nil
1250 }
1251
1252-// AllInstances returns all the environs.Instance in this provider.
1253-func (environ *maasEnviron) AllInstances() ([]environs.Instance, error) {
1254+// AllInstances returns all the instance.Instance in this provider.
1255+func (environ *maasEnviron) AllInstances() ([]instance.Instance, error) {
1256 return environ.instances(nil)
1257 }
1258
1259@@ -452,7 +453,7 @@
1260 return environs.EmptyStorage
1261 }
1262
1263-func (environ *maasEnviron) Destroy(ensureInsts []environs.Instance) error {
1264+func (environ *maasEnviron) Destroy(ensureInsts []instance.Instance) error {
1265 log.Debugf("environs/maas: destroying environment %q", environ.name)
1266 insts, err := environ.AllInstances()
1267 if err != nil {
1268
1269=== modified file 'environs/maas/environ_test.go'
1270--- environs/maas/environ_test.go 2013-06-10 10:30:13 +0000
1271+++ environs/maas/environ_test.go 2013-06-17 04:13:30 +0000
1272@@ -15,6 +15,7 @@
1273 envtesting "launchpad.net/juju-core/environs/testing"
1274 "launchpad.net/juju-core/environs/tools"
1275 "launchpad.net/juju-core/errors"
1276+ "launchpad.net/juju-core/instance"
1277 "launchpad.net/juju-core/state"
1278 "launchpad.net/juju-core/testing"
1279 "launchpad.net/juju-core/utils"
1280@@ -344,7 +345,7 @@
1281 func (suite *EnvironSuite) TestStopInstancesReturnsIfParameterEmpty(c *C) {
1282 suite.getInstance("test1")
1283
1284- err := suite.environ.StopInstances([]environs.Instance{})
1285+ err := suite.environ.StopInstances([]instance.Instance{})
1286 c.Check(err, IsNil)
1287 operations := suite.testMAASObject.TestServer.NodeOperations()
1288 c.Check(operations, DeepEquals, map[string][]string{})
1289@@ -354,7 +355,7 @@
1290 instance1 := suite.getInstance("test1")
1291 instance2 := suite.getInstance("test2")
1292 suite.getInstance("test3")
1293- instances := []environs.Instance{instance1, instance2}
1294+ instances := []instance.Instance{instance1, instance2}
1295
1296 err := suite.environ.StopInstances(instances)
1297
1298@@ -394,12 +395,12 @@
1299 func (suite *EnvironSuite) TestDestroy(c *C) {
1300 env := suite.makeEnviron()
1301 suite.getInstance("test1")
1302- instance := suite.getInstance("test2")
1303+ testInstance := suite.getInstance("test2")
1304 data := makeRandomBytes(10)
1305 suite.testMAASObject.TestServer.NewFile("filename", data)
1306 storage := env.Storage()
1307
1308- err := env.Destroy([]environs.Instance{instance})
1309+ err := env.Destroy([]instance.Instance{testInstance})
1310
1311 c.Check(err, IsNil)
1312 // Instances have been stopped.
1313
1314=== modified file 'environs/maas/instance.go'
1315--- environs/maas/instance.go 2013-05-02 15:55:42 +0000
1316+++ environs/maas/instance.go 2013-06-17 04:13:30 +0000
1317@@ -5,7 +5,7 @@
1318
1319 import (
1320 "launchpad.net/gomaasapi"
1321- "launchpad.net/juju-core/environs"
1322+ "launchpad.net/juju-core/instance"
1323 "launchpad.net/juju-core/log"
1324 "launchpad.net/juju-core/state"
1325 "launchpad.net/juju-core/state/api/params"
1326@@ -16,7 +16,7 @@
1327 environ *maasEnviron
1328 }
1329
1330-var _ environs.Instance = (*maasInstance)(nil)
1331+var _ instance.Instance = (*maasInstance)(nil)
1332
1333 func (instance *maasInstance) Id() state.InstanceId {
1334 // Use the node's 'resource_uri' value.
1335
1336=== modified file 'environs/openstack/local_test.go'
1337--- environs/openstack/local_test.go 2013-05-28 08:05:49 +0000
1338+++ environs/openstack/local_test.go 2013-06-17 04:13:30 +0000
1339@@ -17,6 +17,7 @@
1340 "launchpad.net/juju-core/environs/jujutest"
1341 "launchpad.net/juju-core/environs/openstack"
1342 envtesting "launchpad.net/juju-core/environs/testing"
1343+ "launchpad.net/juju-core/instance"
1344 "launchpad.net/juju-core/juju/testing"
1345 "launchpad.net/juju-core/state"
1346 coretesting "launchpad.net/juju-core/testing"
1347@@ -281,7 +282,7 @@
1348 err = environs.Bootstrap(env, constraints.Value{})
1349 c.Assert(err, IsNil)
1350 inst := testing.StartInstance(c, env, "100")
1351- err = s.Env.StopInstances([]environs.Instance{inst})
1352+ err = s.Env.StopInstances([]instance.Instance{inst})
1353 c.Assert(err, IsNil)
1354 }
1355
1356@@ -338,7 +339,7 @@
1357 inst1 := testing.StartInstance(c, s.Env, "101")
1358 id1 := inst1.Id()
1359 defer func() {
1360- err := s.Env.StopInstances([]environs.Instance{inst0, inst1})
1361+ err := s.Env.StopInstances([]instance.Instance{inst0, inst1})
1362 c.Assert(err, IsNil)
1363 }()
1364
1365
1366=== modified file 'environs/openstack/provider.go'
1367--- environs/openstack/provider.go 2013-06-10 10:30:13 +0000
1368+++ environs/openstack/provider.go 2013-06-17 04:13:30 +0000
1369@@ -23,6 +23,7 @@
1370 "launchpad.net/juju-core/environs/instances"
1371 "launchpad.net/juju-core/environs/tools"
1372 coreerrors "launchpad.net/juju-core/errors"
1373+ "launchpad.net/juju-core/instance"
1374 "launchpad.net/juju-core/log"
1375 "launchpad.net/juju-core/state"
1376 "launchpad.net/juju-core/state/api"
1377@@ -270,19 +271,19 @@
1378
1379 var _ environs.Environ = (*environ)(nil)
1380
1381-type instance struct {
1382+type openstackInstance struct {
1383 e *environ
1384 *nova.ServerDetail
1385 address string
1386 }
1387
1388-func (inst *instance) String() string {
1389+func (inst *openstackInstance) String() string {
1390 return inst.ServerDetail.Id
1391 }
1392
1393-var _ environs.Instance = (*instance)(nil)
1394+var _ instance.Instance = (*openstackInstance)(nil)
1395
1396-func (inst *instance) Id() state.InstanceId {
1397+func (inst *openstackInstance) Id() state.InstanceId {
1398 return state.InstanceId(inst.ServerDetail.Id)
1399 }
1400
1401@@ -319,12 +320,12 @@
1402 public = private
1403 }
1404 if public == "" {
1405- return "", environs.ErrNoDNSName
1406+ return "", instance.ErrNoDNSName
1407 }
1408 return public, nil
1409 }
1410
1411-func (inst *instance) DNSName() (string, error) {
1412+func (inst *openstackInstance) DNSName() (string, error) {
1413 if inst.address != "" {
1414 return inst.address, nil
1415 }
1416@@ -341,10 +342,10 @@
1417 return inst.address, nil
1418 }
1419
1420-func (inst *instance) WaitDNSName() (string, error) {
1421+func (inst *openstackInstance) WaitDNSName() (string, error) {
1422 for a := longAttempt.Start(); a.Next(); {
1423 addr, err := inst.DNSName()
1424- if err == nil || err != environs.ErrNoDNSName {
1425+ if err == nil || err != instance.ErrNoDNSName {
1426 return addr, err
1427 }
1428 }
1429@@ -353,7 +354,7 @@
1430
1431 // TODO: following 30 lines nearly verbatim from environs/ec2
1432
1433-func (inst *instance) OpenPorts(machineId string, ports []params.Port) error {
1434+func (inst *openstackInstance) OpenPorts(machineId string, ports []params.Port) error {
1435 if inst.e.Config().FirewallMode() != config.FwInstance {
1436 return fmt.Errorf("invalid firewall mode for opening ports on instance: %q",
1437 inst.e.Config().FirewallMode())
1438@@ -366,7 +367,7 @@
1439 return nil
1440 }
1441
1442-func (inst *instance) ClosePorts(machineId string, ports []params.Port) error {
1443+func (inst *openstackInstance) ClosePorts(machineId string, ports []params.Port) error {
1444 if inst.e.Config().FirewallMode() != config.FwInstance {
1445 return fmt.Errorf("invalid firewall mode for closing ports on instance: %q",
1446 inst.e.Config().FirewallMode())
1447@@ -379,7 +380,7 @@
1448 return nil
1449 }
1450
1451-func (inst *instance) Ports(machineId string) ([]params.Port, error) {
1452+func (inst *openstackInstance) Ports(machineId string) ([]params.Port, error) {
1453 if inst.e.Config().FirewallMode() != config.FwInstance {
1454 return nil, fmt.Errorf("invalid firewall mode for retrieving ports from instance: %q",
1455 inst.e.Config().FirewallMode())
1456@@ -507,7 +508,7 @@
1457 if err != nil {
1458 // ignore error on StopInstance because the previous error is
1459 // more important.
1460- e.StopInstances([]environs.Instance{inst})
1461+ e.StopInstances([]instance.Instance{inst})
1462 return fmt.Errorf("cannot save state: %v", err)
1463 }
1464 // TODO make safe in the case of racing Bootstraps
1465@@ -545,7 +546,7 @@
1466 if inst == nil {
1467 continue
1468 }
1469- name, err := inst.(*instance).DNSName()
1470+ name, err := inst.(*openstackInstance).DNSName()
1471 if err != nil {
1472 continue
1473 }
1474@@ -657,7 +658,7 @@
1475 return e.imageBaseURLs, nil
1476 }
1477
1478-func (e *environ) StartInstance(machineId, machineNonce string, series string, cons constraints.Value, info *state.Info, apiInfo *api.Info) (environs.Instance, error) {
1479+func (e *environ) StartInstance(machineId, machineNonce string, series string, cons constraints.Value, info *state.Info, apiInfo *api.Info) (instance.Instance, error) {
1480 possibleTools, err := environs.FindInstanceTools(e, series, cons)
1481 if err != nil {
1482 return nil, err
1483@@ -767,7 +768,7 @@
1484
1485 // startInstance is the internal version of StartInstance, used by Bootstrap
1486 // as well as via StartInstance itself.
1487-func (e *environ) startInstance(scfg *startInstanceParams) (environs.Instance, error) {
1488+func (e *environ) startInstance(scfg *startInstanceParams) (instance.Instance, error) {
1489 series := scfg.possibleTools.Series()
1490 if len(series) != 1 {
1491 return nil, fmt.Errorf("expected single series, got %v", series)
1492@@ -832,7 +833,7 @@
1493 if err != nil {
1494 return nil, fmt.Errorf("cannot get started instance: %v", err)
1495 }
1496- inst := &instance{e, detail, ""}
1497+ inst := &openstackInstance{e, detail, ""}
1498 log.Infof("environs/openstack: started instance %q", inst.Id())
1499 if scfg.withPublicIP {
1500 if err := e.assignPublicIP(publicIP, string(inst.Id())); err != nil {
1501@@ -847,12 +848,12 @@
1502 return inst, nil
1503 }
1504
1505-func (e *environ) StopInstances(insts []environs.Instance) error {
1506+func (e *environ) StopInstances(insts []instance.Instance) error {
1507 ids := make([]state.InstanceId, len(insts))
1508 for i, inst := range insts {
1509- instanceValue, ok := inst.(*instance)
1510+ instanceValue, ok := inst.(*openstackInstance)
1511 if !ok {
1512- return errors.New("Incompatible environs.Instance supplied")
1513+ return errors.New("Incompatible instance.Instance supplied")
1514 }
1515 ids[i] = instanceValue.Id()
1516 }
1517@@ -863,7 +864,7 @@
1518 // collectInstances tries to get information on each instance id in ids.
1519 // It fills the slots in the given map for known servers with status
1520 // either ACTIVE or BUILD. Returns a list of missing ids.
1521-func (e *environ) collectInstances(ids []state.InstanceId, out map[state.InstanceId]environs.Instance) []state.InstanceId {
1522+func (e *environ) collectInstances(ids []state.InstanceId, out map[state.InstanceId]instance.Instance) []state.InstanceId {
1523 var err error
1524 serversById := make(map[string]nova.ServerDetail)
1525 if len(ids) == 1 {
1526@@ -887,7 +888,7 @@
1527 for _, id := range ids {
1528 if server, found := serversById[string(id)]; found {
1529 if server.Status == nova.StatusActive || server.Status == nova.StatusBuild {
1530- out[id] = &instance{e, &server, ""}
1531+ out[id] = &openstackInstance{e, &server, ""}
1532 }
1533 continue
1534 }
1535@@ -896,12 +897,12 @@
1536 return missing
1537 }
1538
1539-func (e *environ) Instances(ids []state.InstanceId) ([]environs.Instance, error) {
1540+func (e *environ) Instances(ids []state.InstanceId) ([]instance.Instance, error) {
1541 if len(ids) == 0 {
1542 return nil, nil
1543 }
1544 missing := ids
1545- found := make(map[state.InstanceId]environs.Instance)
1546+ found := make(map[state.InstanceId]instance.Instance)
1547 // Make a series of requests to cope with eventual consistency.
1548 // Each request will attempt to add more instances to the requested
1549 // set.
1550@@ -913,7 +914,7 @@
1551 if len(found) == 0 {
1552 return nil, environs.ErrNoInstances
1553 }
1554- insts := make([]environs.Instance, len(ids))
1555+ insts := make([]instance.Instance, len(ids))
1556 var err error
1557 for i, id := range ids {
1558 if inst := found[id]; inst != nil {
1559@@ -925,7 +926,7 @@
1560 return insts, err
1561 }
1562
1563-func (e *environ) AllInstances() (insts []environs.Instance, err error) {
1564+func (e *environ) AllInstances() (insts []instance.Instance, err error) {
1565 servers, err := e.nova().ListServersDetail(e.machinesFilter())
1566 if err != nil {
1567 return nil, err
1568@@ -933,13 +934,13 @@
1569 for _, server := range servers {
1570 if server.Status == nova.StatusActive || server.Status == nova.StatusBuild {
1571 var s = server
1572- insts = append(insts, &instance{e, &s, ""})
1573+ insts = append(insts, &openstackInstance{e, &s, ""})
1574 }
1575 }
1576 return insts, err
1577 }
1578
1579-func (e *environ) Destroy(ensureInsts []environs.Instance) error {
1580+func (e *environ) Destroy(ensureInsts []instance.Instance) error {
1581 log.Infof("environs/openstack: destroying environment %q", e.name)
1582 insts, err := e.AllInstances()
1583 if err != nil {
1584@@ -955,7 +956,7 @@
1585 // Add any instances we've been told about but haven't yet shown
1586 // up in the instance list.
1587 for _, inst := range ensureInsts {
1588- id := state.InstanceId(inst.(*instance).Id())
1589+ id := state.InstanceId(inst.(*openstackInstance).Id())
1590 if !found[id] {
1591 ids = append(ids, id)
1592 found[id] = true
1593
1594=== modified file 'environs/openstack/provider_test.go'
1595--- environs/openstack/provider_test.go 2013-05-20 00:10:30 +0000
1596+++ environs/openstack/provider_test.go 2013-06-17 04:13:30 +0000
1597@@ -8,8 +8,8 @@
1598 . "launchpad.net/gocheck"
1599 "launchpad.net/goose/identity"
1600 "launchpad.net/goose/nova"
1601- "launchpad.net/juju-core/environs"
1602 "launchpad.net/juju-core/environs/openstack"
1603+ "launchpad.net/juju-core/instance"
1604 "testing"
1605 )
1606
1607@@ -44,14 +44,14 @@
1608 {
1609 summary: "missing",
1610 expected: "",
1611- failure: environs.ErrNoDNSName,
1612+ failure: instance.ErrNoDNSName,
1613 },
1614 {
1615 summary: "empty",
1616 private: []nova.IPAddress{},
1617 networks: []string{"private"},
1618 expected: "",
1619- failure: environs.ErrNoDNSName,
1620+ failure: instance.ErrNoDNSName,
1621 },
1622 {
1623 summary: "private only",
1624@@ -110,7 +110,7 @@
1625 private: []nova.IPAddress{{6, "::dead:beef:f00d"}},
1626 networks: []string{"private"},
1627 expected: "",
1628- failure: environs.ErrNoDNSName,
1629+ failure: instance.ErrNoDNSName,
1630 },
1631 }
1632
1633
1634=== added directory 'instance'
1635=== added file 'instance/instance.go'
1636--- instance/instance.go 1970-01-01 00:00:00 +0000
1637+++ instance/instance.go 2013-06-17 04:13:30 +0000
1638@@ -0,0 +1,41 @@
1639+// Copyright 2013 Canonical Ltd.
1640+// Licensed under the AGPLv3, see LICENCE file for details.
1641+
1642+package instance
1643+
1644+import (
1645+ "errors"
1646+
1647+ "launchpad.net/juju-core/state"
1648+ "launchpad.net/juju-core/state/api/params"
1649+)
1650+
1651+var ErrNoDNSName = errors.New("DNS name not allocated")
1652+
1653+// Instance represents the the realization of a machine in state.
1654+type Instance interface {
1655+ // Id returns a provider-generated identifier for the Instance.
1656+ Id() state.InstanceId
1657+
1658+ // DNSName returns the DNS name for the instance.
1659+ // If the name is not yet allocated, it will return
1660+ // an ErrNoDNSName error.
1661+ DNSName() (string, error)
1662+
1663+ // WaitDNSName returns the DNS name for the instance,
1664+ // waiting until it is allocated if necessary.
1665+ WaitDNSName() (string, error)
1666+
1667+ // OpenPorts opens the given ports on the instance, which
1668+ // should have been started with the given machine id.
1669+ OpenPorts(machineId string, ports []params.Port) error
1670+
1671+ // ClosePorts closes the given ports on the instance, which
1672+ // should have been started with the given machine id.
1673+ ClosePorts(machineId string, ports []params.Port) error
1674+
1675+ // Ports returns the set of ports open on the instance, which
1676+ // should have been started with the given machine id.
1677+ // The ports are returned as sorted by state.SortPorts.
1678+ Ports(machineId string) ([]params.Port, error)
1679+}
1680
1681=== modified file 'juju/testing/conn.go'
1682--- juju/testing/conn.go 2013-05-02 15:55:42 +0000
1683+++ juju/testing/conn.go 2013-06-17 04:13:30 +0000
1684@@ -12,6 +12,7 @@
1685 "launchpad.net/juju-core/environs"
1686 "launchpad.net/juju-core/environs/config"
1687 "launchpad.net/juju-core/environs/dummy"
1688+ "launchpad.net/juju-core/instance"
1689 "launchpad.net/juju-core/juju"
1690 "launchpad.net/juju-core/state"
1691 "launchpad.net/juju-core/state/api"
1692@@ -77,7 +78,7 @@
1693
1694 // StartInstance is a test helper function that starts an instance on the
1695 // environment using the current series and invalid info states.
1696-func StartInstance(c *C, env environs.Environ, machineId string) environs.Instance {
1697+func StartInstance(c *C, env environs.Environ, machineId string) instance.Instance {
1698 series := config.DefaultSeries
1699 inst, err := env.StartInstance(
1700 machineId,
1701
1702=== modified file 'state/container.go'
1703--- state/container.go 2013-06-11 22:40:34 +0000
1704+++ state/container.go 2013-06-17 04:13:30 +0000
1705@@ -88,9 +88,9 @@
1706 return ops
1707 }
1708
1709-// parentId returns the id of the host machine if machineId a container id, or ""
1710+// ParentId returns the id of the host machine if machineId a container id, or ""
1711 // if machineId is not for a container.
1712-func parentId(machineId string) string {
1713+func ParentId(machineId string) string {
1714 idParts := strings.Split(machineId, "/")
1715 if len(idParts) < 3 {
1716 return ""
1717@@ -98,6 +98,18 @@
1718 return strings.Join(idParts[:len(idParts)-2], "/")
1719 }
1720
1721+// NestingLevel returns how many levels of nesting exist for a machine id.
1722+func NestingLevel(machineId string) int {
1723+ idParts := strings.Split(machineId, "/")
1724+ return (len(idParts) - 1) / 2
1725+}
1726+
1727+// TopParentId returns the id of the top level host machine for a container id.
1728+func TopParentId(machineId string) string {
1729+ idParts := strings.Split(machineId, "/")
1730+ return idParts[0]
1731+}
1732+
1733 // removeContainerRefOps returns the txn.Op's necessary to remove a machine container record.
1734 // These include removing the record itself and updating the host machine's children property.
1735 func removeContainerRefOps(st *State, machineId string) []txn.Op {
1736@@ -108,7 +120,7 @@
1737 Remove: true,
1738 }
1739 // If the machine is a container, figure out it's parent host.
1740- parentId := parentId(machineId)
1741+ parentId := ParentId(machineId)
1742 if parentId == "" {
1743 return []txn.Op{removeRefOp}
1744 }
1745
1746=== modified file 'state/export_test.go'
1747--- state/export_test.go 2013-05-02 15:55:42 +0000
1748+++ state/export_test.go 2013-06-17 04:13:30 +0000
1749@@ -93,6 +93,10 @@
1750 return sch
1751 }
1752
1753+func MachineIdLessThan(id1, id2 string) bool {
1754+ return machineIdLessThan(id1, id2)
1755+}
1756+
1757 func init() {
1758 logSize = logSizeTests
1759 }
1760
1761=== modified file 'state/machine.go'
1762--- state/machine.go 2013-06-13 14:39:31 +0000
1763+++ state/machine.go 2013-06-17 04:13:30 +0000
1764@@ -107,15 +107,26 @@
1765 return machineGlobalKey(m.doc.Id)
1766 }
1767
1768+const machineTagPrefix = "machine-"
1769+
1770 // MachineTag returns the tag for the
1771 // machine with the given id.
1772 func MachineTag(id string) string {
1773- tag := fmt.Sprintf("machine-%s", id)
1774+ tag := fmt.Sprintf("%s%s", machineTagPrefix, id)
1775 // Containers require "/" to be replaced by "-".
1776 tag = strings.Replace(tag, "/", "-", -1)
1777 return tag
1778 }
1779
1780+// MachineIdFromTag returns the machine id that was used to create the tag.
1781+func MachineIdFromTag(tag string) string {
1782+ // Strip off the "machine-" prefix.
1783+ id := tag[len(machineTagPrefix):]
1784+ // Put the slashes back.
1785+ id = strings.Replace(id, "-", "/", -1)
1786+ return id
1787+}
1788+
1789 // Tag returns a name identifying the machine that is safe to use
1790 // as a file name. The returned name will be different from other
1791 // Tag values returned by any other entities from the same state.
1792@@ -240,7 +251,7 @@
1793
1794 // ParentId returns the Id of the host machine if this machine is a container.
1795 func (m *Machine) ParentId() (string, bool) {
1796- parentId := parentId(m.Id())
1797+ parentId := ParentId(m.Id())
1798 return parentId, parentId != ""
1799 }
1800
1801
1802=== modified file 'state/machine_test.go'
1803--- state/machine_test.go 2013-06-12 00:01:40 +0000
1804+++ state/machine_test.go 2013-06-17 04:13:30 +0000
1805@@ -192,6 +192,15 @@
1806 c.Assert(state.MachineTag("10/lxc/1"), Equals, "machine-10-lxc-1")
1807 }
1808
1809+func (s *MachineSuite) TestMachineIdFromTag(c *C) {
1810+ c.Assert(state.MachineIdFromTag("machine-10"), Equals, "10")
1811+ // Check a container id.
1812+ c.Assert(state.MachineIdFromTag("machine-10-lxc-1"), Equals, "10/lxc/1")
1813+ // Check reversability.
1814+ nested := "2/kvm/0/lxc/3"
1815+ c.Assert(state.MachineIdFromTag(state.MachineTag(nested)), Equals, nested)
1816+}
1817+
1818 func (s *MachineSuite) TestSetMongoPassword(c *C) {
1819 testSetMongoPassword(c, func(st *state.State) (entity, error) {
1820 return st.Machine(s.machine.Id())
1821
1822=== modified file 'state/state.go'
1823--- state/state.go 2013-06-13 14:39:31 +0000
1824+++ state/state.go 2013-06-17 04:13:30 +0000
1825@@ -378,10 +378,49 @@
1826 func (ms machineDocSlice) Len() int { return len(ms) }
1827 func (ms machineDocSlice) Swap(i, j int) { ms[i], ms[j] = ms[j], ms[i] }
1828 func (ms machineDocSlice) Less(i, j int) bool {
1829- // There's nothing we can do with errors at this point.
1830- m1, _ := strconv.Atoi(ms[i].Id)
1831- m2, _ := strconv.Atoi(ms[j].Id)
1832- return m1 < m2
1833+ return machineIdLessThan(ms[i].Id, ms[j].Id)
1834+}
1835+
1836+// machineIdLessThan returns true if id1 < id2, false otherwise.
1837+// Machine ids may include "/" separators if they are for a container so
1838+// the comparison is done by comparing the id component values from
1839+// left to right (most significant part to least significant). Ids for
1840+// host machines are always less than ids for their containers.
1841+func machineIdLessThan(id1, id2 string) bool {
1842+ // Most times, we are dealing with host machines and not containers, so we will
1843+ // try interpreting the ids as ints - this will be faster than dealing with the
1844+ // container ids below.
1845+ mint1, err1 := strconv.Atoi(id1)
1846+ mint2, err2 := strconv.Atoi(id2)
1847+ if err1 == nil && err2 == nil {
1848+ return mint1 < mint2
1849+ }
1850+ // We have at least one container id so it gets complicated.
1851+ idParts1 := strings.Split(id1, "/")
1852+ idParts2 := strings.Split(id2, "/")
1853+ nrParts1 := len(idParts1)
1854+ nrParts2 := len(idParts2)
1855+ minLen := nrParts1
1856+ if nrParts2 < minLen {
1857+ minLen = nrParts2
1858+ }
1859+ for x := 0; x < minLen; x++ {
1860+ m1 := idParts1[x]
1861+ m2 := idParts2[x]
1862+ if m1 == m2 {
1863+ continue
1864+ }
1865+ // See if the id part is a container type, and if so compare directly.
1866+ if x%2 == 1 {
1867+ return m1 < m2
1868+ }
1869+ // Compare the integer ids.
1870+ // There's nothing we can do with errors at this point.
1871+ mint1, _ := strconv.Atoi(m1)
1872+ mint2, _ := strconv.Atoi(m2)
1873+ return mint1 < mint2
1874+ }
1875+ return nrParts1 < nrParts2
1876 }
1877
1878 // Machine returns the machine with the given id.
1879
1880=== modified file 'state/state_test.go'
1881--- state/state_test.go 2013-06-13 14:39:31 +0000
1882+++ state/state_test.go 2013-06-17 04:13:30 +0000
1883@@ -331,6 +331,39 @@
1884 c.Assert(m.CheckProvisioned(state.BootstrapNonce), Equals, true)
1885 }
1886
1887+func (s *StateSuite) TestAddContainerToInjectedMachine(c *C) {
1888+ oneJob := []state.MachineJob{state.JobHostUnits}
1889+ m0, err := s.State.InjectMachine("series", emptyCons, state.InstanceId("i-mindustrious"), state.JobHostUnits, state.JobManageEnviron)
1890+ c.Assert(err, IsNil)
1891+
1892+ // Add first container.
1893+ params := state.AddMachineParams{
1894+ ParentId: "0",
1895+ ContainerType: state.LXC,
1896+ Series: "series",
1897+ Jobs: []state.MachineJob{state.JobHostUnits},
1898+ }
1899+ m, err := s.State.AddMachineWithConstraints(&params)
1900+ c.Assert(err, IsNil)
1901+ c.Assert(m.Id(), Equals, "0/lxc/0")
1902+ c.Assert(m.Series(), Equals, "series")
1903+ c.Assert(m.ContainerType(), Equals, state.LXC)
1904+ mcons, err := m.Constraints()
1905+ c.Assert(err, IsNil)
1906+ c.Assert(mcons, DeepEquals, emptyCons)
1907+ c.Assert(m.Jobs(), DeepEquals, oneJob)
1908+ s.assertMachineContainers(c, m0, []string{"0/lxc/0"})
1909+
1910+ // Add second container.
1911+ m, err = s.State.AddMachineWithConstraints(&params)
1912+ c.Assert(err, IsNil)
1913+ c.Assert(m.Id(), Equals, "0/lxc/1")
1914+ c.Assert(m.Series(), Equals, "series")
1915+ c.Assert(m.ContainerType(), Equals, state.LXC)
1916+ c.Assert(m.Jobs(), DeepEquals, oneJob)
1917+ s.assertMachineContainers(c, m0, []string{"0/lxc/0", "0/lxc/1"})
1918+}
1919+
1920 func (s *StateSuite) TestReadMachine(c *C) {
1921 machine, err := s.State.AddMachine("series", state.JobHostUnits)
1922 c.Assert(err, IsNil)
1923@@ -346,6 +379,19 @@
1924 c.Assert(errors.IsNotFoundError(err), Equals, true)
1925 }
1926
1927+func (s *StateSuite) TestMachineIdLessThan(c *C) {
1928+ c.Assert(state.MachineIdLessThan("0", "0"), Equals, false)
1929+ c.Assert(state.MachineIdLessThan("0", "1"), Equals, true)
1930+ c.Assert(state.MachineIdLessThan("1", "0"), Equals, false)
1931+ c.Assert(state.MachineIdLessThan("10", "2"), Equals, false)
1932+ c.Assert(state.MachineIdLessThan("0", "0/lxc/0"), Equals, true)
1933+ c.Assert(state.MachineIdLessThan("0/lxc/0", "0"), Equals, false)
1934+ c.Assert(state.MachineIdLessThan("1", "0/lxc/0"), Equals, false)
1935+ c.Assert(state.MachineIdLessThan("0/lxc/0", "1"), Equals, true)
1936+ c.Assert(state.MachineIdLessThan("0/lxc/0/lxc/1", "0/lxc/0"), Equals, false)
1937+ c.Assert(state.MachineIdLessThan("0/kvm/0", "0/lxc/0"), Equals, true)
1938+}
1939+
1940 func (s *StateSuite) TestAllMachines(c *C) {
1941 numInserts := 42
1942 for i := 0; i < numInserts; i++ {
1943@@ -1559,3 +1605,21 @@
1944 }
1945 assertNoCleanupChange(c, st, cw)
1946 }
1947+
1948+func (s *StateSuite) TestNestingLevel(c *C) {
1949+ c.Assert(state.NestingLevel("0"), Equals, 0)
1950+ c.Assert(state.NestingLevel("0/lxc/1"), Equals, 1)
1951+ c.Assert(state.NestingLevel("0/lxc/1/kvm/0"), Equals, 2)
1952+}
1953+
1954+func (s *StateSuite) TestTopParentId(c *C) {
1955+ c.Assert(state.TopParentId("0"), Equals, "0")
1956+ c.Assert(state.TopParentId("0/lxc/1"), Equals, "0")
1957+ c.Assert(state.TopParentId("0/lxc/1/kvm/2"), Equals, "0")
1958+}
1959+
1960+func (s *StateSuite) TestParentId(c *C) {
1961+ c.Assert(state.ParentId("0"), Equals, "")
1962+ c.Assert(state.ParentId("0/lxc/1"), Equals, "0")
1963+ c.Assert(state.ParentId("0/lxc/1/kvm/0"), Equals, "0/lxc/1")
1964+}
1965
1966=== modified file 'worker/firewaller/firewaller_test.go'
1967--- worker/firewaller/firewaller_test.go 2013-05-27 03:00:31 +0000
1968+++ worker/firewaller/firewaller_test.go 2013-06-17 04:13:30 +0000
1969@@ -5,9 +5,9 @@
1970
1971 import (
1972 . "launchpad.net/gocheck"
1973- "launchpad.net/juju-core/environs"
1974 "launchpad.net/juju-core/environs/config"
1975 "launchpad.net/juju-core/environs/dummy"
1976+ "launchpad.net/juju-core/instance"
1977 "launchpad.net/juju-core/juju/testing"
1978 "launchpad.net/juju-core/state"
1979 "launchpad.net/juju-core/state/api/params"
1980@@ -33,7 +33,7 @@
1981
1982 // assertPorts retrieves the open ports of the instance and compares them
1983 // to the expected.
1984-func (s *FirewallerSuite) assertPorts(c *C, inst environs.Instance, machineId string, expected []params.Port) {
1985+func (s *FirewallerSuite) assertPorts(c *C, inst instance.Instance, machineId string, expected []params.Port) {
1986 s.State.StartSync()
1987 start := time.Now()
1988 for {
1989@@ -133,7 +133,7 @@
1990 }
1991
1992 // startInstance starts a new instance for the given machine.
1993-func (s *FirewallerSuite) startInstance(c *C, m *state.Machine) environs.Instance {
1994+func (s *FirewallerSuite) startInstance(c *C, m *state.Machine) instance.Instance {
1995 inst := testing.StartInstance(c, s.Conn.Environ, m.Id())
1996 err := m.SetProvisioned(inst.Id(), "fake_nonce")
1997 c.Assert(err, IsNil)
1998
1999=== modified file 'worker/provisioner/broker.go'
2000--- worker/provisioner/broker.go 2013-06-07 01:36:48 +0000
2001+++ worker/provisioner/broker.go 2013-06-17 04:13:30 +0000
2002@@ -5,7 +5,7 @@
2003
2004 import (
2005 "launchpad.net/juju-core/constraints"
2006- "launchpad.net/juju-core/environs"
2007+ "launchpad.net/juju-core/instance"
2008 "launchpad.net/juju-core/state"
2009 "launchpad.net/juju-core/state/api"
2010 )
2011@@ -17,11 +17,11 @@
2012 // unique within an environment, is used by juju to protect against the
2013 // consequences of multiple instances being started with the same machine
2014 // id.
2015- StartInstance(machineId, machineNonce string, series string, cons constraints.Value, info *state.Info, apiInfo *api.Info) (environs.Instance, error)
2016+ StartInstance(machineId, machineNonce string, series string, cons constraints.Value, info *state.Info, apiInfo *api.Info) (instance.Instance, error)
2017
2018 // StopInstances shuts down the given instances.
2019- StopInstances([]environs.Instance) error
2020+ StopInstances([]instance.Instance) error
2021
2022 // AllInstances returns all instances currently known to the broker.
2023- AllInstances() ([]environs.Instance, error)
2024+ AllInstances() ([]instance.Instance, error)
2025 }
2026
2027=== modified file 'worker/provisioner/export_test.go'
2028--- worker/provisioner/export_test.go 2013-05-02 15:55:42 +0000
2029+++ worker/provisioner/export_test.go 2013-06-17 04:13:30 +0000
2030@@ -11,13 +11,13 @@
2031 // exported so we can manually close the Provisioners underlying
2032 // state connection.
2033 func (p *Provisioner) CloseState() error {
2034- return p.st.Close()
2035+ return p.state.Close()
2036 }
2037
2038 // exported so we can discover all machines visible to the
2039 // Provisioners state connection.
2040 func (p *Provisioner) AllMachines() ([]*state.Machine, error) {
2041- return p.st.AllMachines()
2042+ return p.state.AllMachines()
2043 }
2044
2045 func (o *configObserver) SetObserver(observer chan<- *config.Config) {
2046
2047=== modified file 'worker/provisioner/provisioner.go'
2048--- worker/provisioner/provisioner.go 2013-06-11 02:15:31 +0000
2049+++ worker/provisioner/provisioner.go 2013-06-17 04:13:30 +0000
2050@@ -19,7 +19,7 @@
2051
2052 // Provisioner represents a running provisioning worker.
2053 type Provisioner struct {
2054- st *state.State
2055+ state *state.State
2056 machineId string // Which machine runs the provisioner.
2057 environ environs.Environ
2058 tomb tomb.Tomb
2059@@ -46,7 +46,7 @@
2060 // and allocates them to the new machines.
2061 func NewProvisioner(st *state.State, machineId string) *Provisioner {
2062 p := &Provisioner{
2063- st: st,
2064+ state: st,
2065 machineId: machineId,
2066 }
2067 go func() {
2068@@ -57,7 +57,7 @@
2069 }
2070
2071 func (p *Provisioner) loop() error {
2072- environWatcher := p.st.WatchEnvironConfig()
2073+ environWatcher := p.state.WatchEnvironConfig()
2074 defer watcher.Stop(environWatcher, &p.tomb)
2075
2076 var err error
2077@@ -66,27 +66,18 @@
2078 return err
2079 }
2080
2081- // Get a new StateInfo from the environment: the one used to
2082- // launch the agent may refer to localhost, which will be
2083- // unhelpful when attempting to run an agent on a new machine.
2084- stateInfo, apiInfo, err := p.environ.StateInfo()
2085- if err != nil {
2086- return err
2087- }
2088-
2089 // Start a new worker for the environment provider.
2090
2091 // Start responding to changes in machines, and to any further updates
2092 // to the environment config.
2093- machinesWatcher := p.st.WatchEnvironMachines()
2094+ machinesWatcher := p.state.WatchEnvironMachines()
2095 environmentBroker := newEnvironBroker(p.environ)
2096 environmentProvisioner := newProvisionerTask(
2097 p.machineId,
2098- p.st,
2099+ p.state,
2100 machinesWatcher,
2101 environmentBroker,
2102- stateInfo,
2103- apiInfo)
2104+ )
2105 defer watcher.Stop(environmentProvisioner, &p.tomb)
2106
2107 for {
2108@@ -103,6 +94,11 @@
2109 }
2110 if err := p.setConfig(cfg); err != nil {
2111 logger.Errorf("loaded invalid environment configuration: %v", err)
2112+ // We *shouldn't* ever get into a state here where we have an
2113+ // invalid config. If we do, stop the task and let jujud
2114+ // restart the provisioner, which will then wait for valid
2115+ // config.
2116+ return err
2117 }
2118 }
2119 }
2120
2121=== modified file 'worker/provisioner/provisioner_task.go'
2122--- worker/provisioner/provisioner_task.go 2013-06-10 04:25:51 +0000
2123+++ worker/provisioner/provisioner_task.go 2013-06-17 04:13:30 +0000
2124@@ -6,8 +6,8 @@
2125 import (
2126 "fmt"
2127
2128- "launchpad.net/juju-core/environs"
2129 "launchpad.net/juju-core/errors"
2130+ "launchpad.net/juju-core/instance"
2131 "launchpad.net/juju-core/state"
2132 "launchpad.net/juju-core/state/api"
2133 "launchpad.net/juju-core/state/api/params"
2134@@ -30,25 +30,17 @@
2135 Changes() <-chan []string
2136 }
2137
2138-type MachineGetter interface {
2139- Machine(id string) (*state.Machine, error)
2140-}
2141-
2142 func newProvisionerTask(
2143 machineId string,
2144- machineGetter MachineGetter,
2145+ st *state.State,
2146 watcher Watcher,
2147 broker Broker,
2148- stateInfo *state.Info,
2149- apiInfo *api.Info,
2150 ) ProvisionerTask {
2151 task := &provisionerTask{
2152 machineId: machineId,
2153- machineGetter: machineGetter,
2154+ state: st,
2155 machineWatcher: watcher,
2156 broker: broker,
2157- stateInfo: stateInfo,
2158- apiInfo: apiInfo,
2159 machines: make(map[string]*state.Machine),
2160 }
2161 go func() {
2162@@ -60,15 +52,13 @@
2163
2164 type provisionerTask struct {
2165 machineId string
2166- machineGetter MachineGetter
2167+ state *state.State
2168 machineWatcher Watcher
2169 broker Broker
2170 tomb tomb.Tomb
2171- stateInfo *state.Info
2172- apiInfo *api.Info
2173
2174 // instance id -> instance
2175- instances map[state.InstanceId]environs.Instance
2176+ instances map[state.InstanceId]instance.Instance
2177 // machine id -> machine
2178 machines map[string]*state.Machine
2179 }
2180@@ -159,7 +149,7 @@
2181 }
2182
2183 func (task *provisionerTask) populateMachineMaps(ids []string) error {
2184- task.instances = make(map[state.InstanceId]environs.Instance)
2185+ task.instances = make(map[state.InstanceId]instance.Instance)
2186
2187 instances, err := task.broker.AllInstances()
2188 if err != nil {
2189@@ -174,7 +164,7 @@
2190 // change list.
2191 // TODO(thumper): update for API server later to get all machines in one go.
2192 for _, id := range ids {
2193- machine, err := task.machineGetter.Machine(id)
2194+ machine, err := task.state.Machine(id)
2195 switch {
2196 case errors.IsNotFoundError(err):
2197 logger.Debugf("machine %q not found in state", id)
2198@@ -238,9 +228,9 @@
2199 }
2200
2201 // findUnknownInstances finds instances which are not associated with a machine.
2202-func (task *provisionerTask) findUnknownInstances() ([]environs.Instance, error) {
2203+func (task *provisionerTask) findUnknownInstances() ([]instance.Instance, error) {
2204 // Make a copy of the instances we know about.
2205- instances := make(map[state.InstanceId]environs.Instance)
2206+ instances := make(map[state.InstanceId]instance.Instance)
2207 for k, v := range task.instances {
2208 instances[k] = v
2209 }
2210@@ -250,7 +240,7 @@
2211 delete(instances, instId)
2212 }
2213 }
2214- var unknown []environs.Instance
2215+ var unknown []instance.Instance
2216 for _, i := range instances {
2217 unknown = append(unknown, i)
2218 }
2219@@ -258,11 +248,11 @@
2220 return unknown, nil
2221 }
2222
2223-// instancesForMachines returns a list of environs.Instance that represent
2224+// instancesForMachines returns a list of instance.Instance that represent
2225 // the list of machines running in the provider. Missing machines are
2226 // omitted from the list.
2227-func (task *provisionerTask) instancesForMachines(machines []*state.Machine) []environs.Instance {
2228- var instances []environs.Instance
2229+func (task *provisionerTask) instancesForMachines(machines []*state.Machine) []instance.Instance {
2230+ var instances []instance.Instance
2231 for _, machine := range machines {
2232 instId, ok := machine.InstanceId()
2233 if ok {
2234@@ -276,7 +266,7 @@
2235 return instances
2236 }
2237
2238-func (task *provisionerTask) stopInstances(instances []environs.Instance) error {
2239+func (task *provisionerTask) stopInstances(instances []instance.Instance) error {
2240 // Although calling StopInstance with an empty slice should produce no change in the
2241 // provider, environs like dummy do not consider this a noop.
2242 if len(instances) == 0 {
2243@@ -306,7 +296,7 @@
2244 // state.Info as the PA.
2245 stateInfo, apiInfo, err := task.setupAuthentication(machine)
2246 if err != nil {
2247- logger.Errorf("failed to setup authentication: %v", err)
2248+ logger.Warningf("failed to setup authentication for machine %q: %v", machine.Id(), err)
2249 return err
2250 }
2251 cons, err := machine.Constraints()
2252@@ -355,6 +345,22 @@
2253 }
2254
2255 func (task *provisionerTask) setupAuthentication(machine *state.Machine) (*state.Info, *api.Info, error) {
2256+ // Grab a new list of state and api addresses each time along with the
2257+ // cert, as these can change during a processing loop. If for example the
2258+ // provisioner was starting 100 machines in one go, some of those may be
2259+ // new api servers. We should take advantage of those ASAP.
2260+ stateAddresses, err := task.state.Addresses()
2261+ if err != nil {
2262+ // This will only return an error if the config becomes invalid. In
2263+ // those situations, the provisioner bombs out and is restarted.
2264+ return nil, nil, fmt.Errorf("cannot get addresses from state: %v", err)
2265+ }
2266+ apiAddresses, err := task.state.APIAddresses()
2267+ if err != nil {
2268+ // Same for the api addresses. We technically shouldn't get here if
2269+ // we didn't fail before, but best to check.
2270+ return nil, nil, fmt.Errorf("cannot get api addresses from state: %v", err)
2271+ }
2272 password, err := utils.RandomPassword()
2273 if err != nil {
2274 return nil, nil, fmt.Errorf("cannot make password for machine %v: %v", machine, err)
2275@@ -362,11 +368,16 @@
2276 if err := machine.SetMongoPassword(password); err != nil {
2277 return nil, nil, fmt.Errorf("cannot set password for machine %v: %v", machine, err)
2278 }
2279- stateInfo := *task.stateInfo
2280- stateInfo.Tag = machine.Tag()
2281- stateInfo.Password = password
2282- apiInfo := *task.apiInfo
2283- apiInfo.Tag = machine.Tag()
2284- apiInfo.Password = password
2285- return &stateInfo, &apiInfo, nil
2286+ cert := task.state.CACert()
2287+ return &state.Info{
2288+ Addrs: stateAddresses,
2289+ CACert: cert,
2290+ Tag: machine.Tag(),
2291+ Password: password,
2292+ }, &api.Info{
2293+ Addrs: apiAddresses,
2294+ CACert: cert,
2295+ Tag: machine.Tag(),
2296+ Password: password,
2297+ }, nil
2298 }
2299
2300=== modified file 'worker/provisioner/provisioner_test.go'
2301--- worker/provisioner/provisioner_test.go 2013-06-11 02:15:31 +0000
2302+++ worker/provisioner/provisioner_test.go 2013-06-17 04:13:30 +0000
2303@@ -12,10 +12,10 @@
2304 "labix.org/v2/mgo/bson"
2305 . "launchpad.net/gocheck"
2306 "launchpad.net/juju-core/constraints"
2307- "launchpad.net/juju-core/environs"
2308 "launchpad.net/juju-core/environs/config"
2309 "launchpad.net/juju-core/environs/dummy"
2310 "launchpad.net/juju-core/errors"
2311+ "launchpad.net/juju-core/instance"
2312 "launchpad.net/juju-core/juju/testing"
2313 "launchpad.net/juju-core/state"
2314 "launchpad.net/juju-core/state/api/params"
2315@@ -100,11 +100,11 @@
2316 c.Assert(s.Stop(), IsNil)
2317 }
2318
2319-func (s *ProvisionerSuite) checkStartInstance(c *C, m *state.Machine) environs.Instance {
2320+func (s *ProvisionerSuite) checkStartInstance(c *C, m *state.Machine) instance.Instance {
2321 return s.checkStartInstanceCustom(c, m, "pork", constraints.Value{})
2322 }
2323
2324-func (s *ProvisionerSuite) checkStartInstanceCustom(c *C, m *state.Machine, secret string, cons constraints.Value) (instance environs.Instance) {
2325+func (s *ProvisionerSuite) checkStartInstanceCustom(c *C, m *state.Machine, secret string, cons constraints.Value) (instance instance.Instance) {
2326 s.State.StartSync()
2327 for {
2328 select {
2329@@ -160,7 +160,7 @@
2330 }
2331
2332 // checkStopInstances checks that an instance has been stopped.
2333-func (s *ProvisionerSuite) checkStopInstances(c *C, instances ...environs.Instance) {
2334+func (s *ProvisionerSuite) checkStopInstances(c *C, instances ...instance.Instance) {
2335 s.State.StartSync()
2336 instanceIds := set.NewStrings()
2337 for _, instance := range instances {
2338@@ -361,27 +361,6 @@
2339 s.checkStartInstance(c, m)
2340 }
2341
2342-func (s *ProvisionerSuite) TestProvisioningDoesOccurAfterInvalidEnvironmentPublished(c *C) {
2343- p := provisioner.NewProvisioner(s.State, "0")
2344- defer stop(c, p)
2345-
2346- // place a new machine into the state
2347- m, err := s.State.AddMachine(config.DefaultSeries, state.JobHostUnits)
2348- c.Assert(err, IsNil)
2349-
2350- s.checkStartInstance(c, m)
2351-
2352- err = s.invalidateEnvironment(c)
2353- c.Assert(err, IsNil)
2354-
2355- // create a second machine
2356- m, err = s.State.AddMachine(config.DefaultSeries, state.JobHostUnits)
2357- c.Assert(err, IsNil)
2358-
2359- // the PA should create it using the old environment
2360- s.checkStartInstance(c, m)
2361-}
2362-
2363 func (s *ProvisionerSuite) TestProvisioningDoesNotProvisionTheSameMachineAfterRestart(c *C) {
2364 p := provisioner.NewProvisioner(s.State, "0")
2365 defer stop(c, p)
2366@@ -466,55 +445,3 @@
2367 c.Assert(err, IsNil)
2368 c.Assert(m0.Life(), Equals, state.Dying)
2369 }
2370-
2371-func (s *ProvisionerSuite) TestProvisioningRecoversAfterInvalidEnvironmentPublished(c *C) {
2372- p := provisioner.NewProvisioner(s.State, "0")
2373- defer stop(c, p)
2374-
2375- // place a new machine into the state
2376- m, err := s.State.AddMachine(config.DefaultSeries, state.JobHostUnits)
2377- c.Assert(err, IsNil)
2378- s.checkStartInstance(c, m)
2379-
2380- err = s.invalidateEnvironment(c)
2381- c.Assert(err, IsNil)
2382- s.State.StartSync()
2383-
2384- // create a second machine
2385- m, err = s.State.AddMachine(config.DefaultSeries, state.JobHostUnits)
2386- c.Assert(err, IsNil)
2387-
2388- // the PA should create it using the old environment
2389- s.checkStartInstance(c, m)
2390-
2391- err = s.fixEnvironment()
2392- c.Assert(err, IsNil)
2393-
2394- // insert our observer
2395- cfgObserver := make(chan *config.Config, 1)
2396- p.SetObserver(cfgObserver)
2397-
2398- cfg, err := s.State.EnvironConfig()
2399- c.Assert(err, IsNil)
2400- attrs := cfg.AllAttrs()
2401- attrs["secret"] = "beef"
2402- cfg, err = config.New(attrs)
2403- c.Assert(err, IsNil)
2404- err = s.State.SetEnvironConfig(cfg)
2405-
2406- s.State.StartSync()
2407-
2408- // wait for the PA to load the new configuration
2409- select {
2410- case <-cfgObserver:
2411- case <-time.After(200 * time.Millisecond):
2412- c.Fatalf("PA did not action config change")
2413- }
2414-
2415- // create a third machine
2416- m, err = s.State.AddMachine(config.DefaultSeries, state.JobHostUnits)
2417- c.Assert(err, IsNil)
2418-
2419- // the PA should create it using the new environment
2420- s.checkStartInstanceCustom(c, m, "beef", constraints.Value{})
2421-}

Subscribers

People subscribed via source and target branches