Merge lp:~axwalk/juju-core/provider-unit-assignment-policy into lp:~go-bot/juju-core/trunk

Proposed by Andrew Wilkins
Status: Merged
Approved by: Andrew Wilkins
Approved revision: no longer in the source branch.
Merged at revision: 2812
Proposed branch: lp:~axwalk/juju-core/provider-unit-assignment-policy
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 544 lines (+383/-50)
8 files modified
environs/statepolicy.go (+13/-0)
state/apiserver/provisioner/provisioner.go (+6/-27)
state/conn_test.go (+8/-0)
state/distribution.go (+77/-0)
state/distribution_test.go (+185/-0)
state/policy.go (+30/-8)
state/service.go (+7/-3)
state/unit.go (+57/-12)
To merge this branch: bzr merge lp:~axwalk/juju-core/provider-unit-assignment-policy
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+221481@code.launchpad.net

Commit message

Introduce InstanceDistributor policy

This change introduces an InstanceDistributor
policy, which will be used to implement automatic
Availability Zone management for AWS and OpenStack.

When an attempt is made to assign a unit to a clean
machine, we gather the instances that have already
been provisioned, and provide those and the unit's
distribution group to the provider. The provider
may select as many or as few of the candidates as it
wishes to allow; the assignment policy will then
choose one of them for assigning to. If no candidates
are returned, the unit will be assigned to a new
machine.

There are currently no implementations of this policy;
they will be forthcoming when manual AZ support lands
in the amazon/openstack providers.

https://codereview.appspot.com/99660047/

Description of the change

Introduce InstanceDistributor policy

This change introduces an InstanceDistributor
policy, which will be used to implement automatic
Availability Zone management for AWS and OpenStack.

When an attempt is made to assign a unit to a clean
machine, we gather the instances that have already
been provisioned, and provide those and the unit's
distribution group to the provider. The provider
may select as many or as few of the candidates as it
wishes to allow; the assignment policy will then
choose one of them for assigning to. If no candidates
are returned, the unit will be assigned to a new
machine.

There are currently no implementations of this policy;
they will be forthcoming when manual AZ support lands
in the amazon/openstack providers.

https://codereview.appspot.com/99660047/

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

Reviewers: mp+221481_code.launchpad.net,

Message:
Please take a look.

Description:
Introduce InstanceDistributor policy

This change introduces an InstanceDistributor
policy, which will be used to implement automatic
Availability Zone management for AWS and OpenStack.

When an attempt is made to assign a unit to a clean
machine, we gather the instances that have already
been provisioned, and provide those and the unit's
distribution group to the provider. The provider
may select as many or as few of the candidates as it
wishes to allow; the assignment policy will then
choose one of them for assigning to. If no candidates
are returned, the unit will be assigned to a new
machine.

There are currently no implementations of this policy;
they will be forthcoming when manual AZ support lands
in the amazon/openstack providers.

https://code.launchpad.net/~axwalk/juju-core/provider-unit-assignment-policy/+merge/221481

(do not edit description out of merge proposal)

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

Affected files (+379, -40 lines):
   A [revision details]
   M environs/statepolicy.go
   M state/apiserver/provisioner/provisioner.go
   M state/conn_test.go
   A state/distribution.go
   A state/distribution_test.go
   M state/policy.go
   M state/unit.go

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

LGTM with a bit of mindful rearrangement.

https://codereview.appspot.com/99660047/diff/1/state/distribution.go
File state/distribution.go (right):

https://codereview.appspot.com/99660047/diff/1/state/distribution.go#newcode42
state/distribution.go:42: distributionGroup, err :=
service.ServiceInstances()
fwiw, this doesn't feel *quite* right because we don't actually ever
need/use the *Service directly -- the AllUnits could be figured out from
the unit name alone, so grabbing the service doc is just an unnecessary
db hit (I think?).

But it's a balance: using the methods we already have for
familarity/clarity's sake, vs writing more primitives that operate more
on ids and less on instantiated objects and *might* eventually lead us
in a leaner/cleaner direction. I have a slight preference for the latter
but I'll leave it to your judgment.

https://codereview.appspot.com/99660047/diff/1/state/distribution.go#newcode54
state/distribution.go:54: func (service *Service) ServiceInstances()
([]instance.Id, error) {
Suggestion: ServiceInstances(*state.Service). I retain a mild discomfort
over spreading the methods on a particular type across files, but just
making the receiver a regular param pretty much eliminates that feeling
of ick. I'm not sure it's *entirely* rational, but...

Anyway, same applies to Unit.distribute above. Doesn't feel like it's
quite in the right place.

https://codereview.appspot.com/99660047/diff/1/state/policy.go
File state/policy.go (right):

https://codereview.appspot.com/99660047/diff/1/state/policy.go#newcode204
state/policy.go:204: // TODO(axw) move this comment
where to? ;p

https://codereview.appspot.com/99660047/

Revision history for this message
Andrew Wilkins (axwalk) wrote :

Please take a look.

https://codereview.appspot.com/99660047/diff/1/state/distribution.go
File state/distribution.go (right):

https://codereview.appspot.com/99660047/diff/1/state/distribution.go#newcode42
state/distribution.go:42: distributionGroup, err :=
service.ServiceInstances()
On 2014/05/30 08:51:12, fwereade wrote:
> fwiw, this doesn't feel *quite* right because we don't actually ever
need/use
> the *Service directly -- the AllUnits could be figured out from the
unit name
> alone, so grabbing the service doc is just an unnecessary db hit (I
think?).

Indeed it is unnecessary. I've changed ServiceInstances to take a
service name, and extracted the logic of Service.AllUnits out to a free
function which takes a service name.

> But it's a balance: using the methods we already have for
familarity/clarity's
> sake, vs writing more primitives that operate more on ids and less on
> instantiated objects and *might* eventually lead us in a
leaner/cleaner
> direction. I have a slight preference for the latter but I'll leave it
to your
> judgment.

https://codereview.appspot.com/99660047/diff/1/state/distribution.go#newcode54
state/distribution.go:54: func (service *Service) ServiceInstances()
([]instance.Id, error) {
On 2014/05/30 08:51:12, fwereade wrote:
> Suggestion: ServiceInstances(*state.Service). I retain a mild
discomfort over
> spreading the methods on a particular type across files, but just
making the
> receiver a regular param pretty much eliminates that feeling of ick.
I'm not
> sure it's *entirely* rational, but...

> Anyway, same applies to Unit.distribute above. Doesn't feel like it's
quite in
> the right place.

Changed both to free functions.

https://codereview.appspot.com/99660047/diff/1/state/policy.go
File state/policy.go (right):

https://codereview.appspot.com/99660047/diff/1/state/policy.go#newcode204
state/policy.go:204: // TODO(axw) move this comment
On 2014/05/30 08:51:12, fwereade wrote:
> where to? ;p

Yeah, it's probably best just left here.

https://codereview.appspot.com/99660047/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'environs/statepolicy.go'
2--- environs/statepolicy.go 2014-04-24 12:33:19 +0000
3+++ environs/statepolicy.go 2014-05-30 12:33:30 +0000
4@@ -4,6 +4,8 @@
5 package environs
6
7 import (
8+ "github.com/juju/errors"
9+
10 "launchpad.net/juju-core/constraints"
11 "launchpad.net/juju-core/environs/config"
12 "launchpad.net/juju-core/state"
13@@ -44,3 +46,14 @@
14 }
15 return env.ConstraintsValidator()
16 }
17+
18+func (environStatePolicy) InstanceDistributor(cfg *config.Config) (state.InstanceDistributor, error) {
19+ env, err := New(cfg)
20+ if err != nil {
21+ return nil, err
22+ }
23+ if p, ok := env.(state.InstanceDistributor); ok {
24+ return p, nil
25+ }
26+ return nil, errors.NotImplementedf("InstanceDistributor")
27+}
28
29=== modified file 'state/apiserver/provisioner/provisioner.go'
30--- state/apiserver/provisioner/provisioner.go 2014-05-13 12:57:53 +0000
31+++ state/apiserver/provisioner/provisioner.go 2014-05-30 12:33:30 +0000
32@@ -416,33 +416,12 @@
33 if !unit.IsPrincipal() {
34 continue
35 }
36- service, err := unit.Service()
37- if err != nil {
38- return nil, err
39- }
40- allUnits, err := service.AllUnits()
41- if err != nil {
42- return nil, err
43- }
44- for _, unit := range allUnits {
45- machineId, err := unit.AssignedMachineId()
46- if state.IsNotAssigned(err) {
47- continue
48- } else if err != nil {
49- return nil, err
50- }
51- machine, err := st.Machine(machineId)
52- if err != nil {
53- return nil, err
54- }
55- instanceId, err := machine.InstanceId()
56- if err == nil {
57- instanceIdSet.Add(string(instanceId))
58- } else if state.IsNotProvisionedError(err) {
59- continue
60- } else {
61- return nil, err
62- }
63+ instanceIds, err := state.ServiceInstances(st, unit.ServiceName())
64+ if err != nil {
65+ return nil, err
66+ }
67+ for _, instanceId := range instanceIds {
68+ instanceIdSet.Add(string(instanceId))
69 }
70 }
71 instanceIds := make([]instance.Id, instanceIdSet.Size())
72
73=== modified file 'state/conn_test.go'
74--- state/conn_test.go 2014-05-20 04:27:02 +0000
75+++ state/conn_test.go 2014-05-30 12:33:30 +0000
76@@ -105,6 +105,7 @@
77 getConfigValidator func(string) (state.ConfigValidator, error)
78 getEnvironCapability func(*config.Config) (state.EnvironCapability, error)
79 getConstraintsValidator func(*config.Config) (constraints.Validator, error)
80+ getInstanceDistributor func(*config.Config) (state.InstanceDistributor, error)
81 }
82
83 func (p *mockPolicy) Prechecker(cfg *config.Config) (state.Prechecker, error) {
84@@ -134,3 +135,10 @@
85 }
86 return nil, errors.NewNotImplemented(nil, "ConstraintsValidator")
87 }
88+
89+func (p *mockPolicy) InstanceDistributor(cfg *config.Config) (state.InstanceDistributor, error) {
90+ if p.getInstanceDistributor != nil {
91+ return p.getInstanceDistributor(cfg)
92+ }
93+ return nil, errors.NewNotImplemented(nil, "InstanceDistributor")
94+}
95
96=== added file 'state/distribution.go'
97--- state/distribution.go 1970-01-01 00:00:00 +0000
98+++ state/distribution.go 2014-05-30 12:33:30 +0000
99@@ -0,0 +1,77 @@
100+// Copyright 2014 Canonical Ltd.
101+// Licensed under the AGPLv3, see LICENCE file for details.
102+
103+package state
104+
105+import (
106+ "fmt"
107+
108+ "github.com/juju/errors"
109+
110+ "launchpad.net/juju-core/instance"
111+)
112+
113+// distributeuUnit takes a unit and set of clean, possibly empty, instances
114+// and asks the InstanceDistributor policy (if any) which ones are suitable
115+// for assigning the unit to. If there is no InstanceDistributor, or the
116+// distribution group is empty, then all of the candidates will be returned.
117+func distributeUnit(u *Unit, candidates []instance.Id) ([]instance.Id, error) {
118+ if len(candidates) == 0 {
119+ return nil, nil
120+ }
121+ if u.st.policy == nil {
122+ return candidates, nil
123+ }
124+ cfg, err := u.st.EnvironConfig()
125+ if err != nil {
126+ return nil, err
127+ }
128+ distributor, err := u.st.policy.InstanceDistributor(cfg)
129+ if errors.IsNotImplemented(err) {
130+ return candidates, nil
131+ } else if err != nil {
132+ return nil, err
133+ }
134+ if distributor == nil {
135+ return nil, fmt.Errorf("policy returned nil instance distributor without an error")
136+ }
137+ distributionGroup, err := ServiceInstances(u.st, u.doc.Service)
138+ if err != nil {
139+ return nil, err
140+ }
141+ if len(distributionGroup) == 0 {
142+ return candidates, nil
143+ }
144+ return distributor.DistributeInstances(candidates, distributionGroup)
145+}
146+
147+// ServiceInstances returns the instance IDs of provisioned
148+// machines that are assigned units of the specified service.
149+func ServiceInstances(st *State, service string) ([]instance.Id, error) {
150+ units, err := allUnits(st, service)
151+ if err != nil {
152+ return nil, err
153+ }
154+ instanceIds := make([]instance.Id, 0, len(units))
155+ for _, unit := range units {
156+ machineId, err := unit.AssignedMachineId()
157+ if IsNotAssigned(err) {
158+ continue
159+ } else if err != nil {
160+ return nil, err
161+ }
162+ machine, err := st.Machine(machineId)
163+ if err != nil {
164+ return nil, err
165+ }
166+ instanceId, err := machine.InstanceId()
167+ if err == nil {
168+ instanceIds = append(instanceIds, instanceId)
169+ } else if IsNotProvisionedError(err) {
170+ continue
171+ } else {
172+ return nil, err
173+ }
174+ }
175+ return instanceIds, nil
176+}
177
178=== added file 'state/distribution_test.go'
179--- state/distribution_test.go 1970-01-01 00:00:00 +0000
180+++ state/distribution_test.go 2014-05-30 12:33:30 +0000
181@@ -0,0 +1,185 @@
182+// Copyright 2014 Canonical Ltd.
183+// Licensed under the AGPLv3, see LICENCE file for details.
184+
185+package state_test
186+
187+import (
188+ "fmt"
189+
190+ "github.com/juju/errors"
191+ jc "github.com/juju/testing/checkers"
192+ gc "launchpad.net/gocheck"
193+
194+ "launchpad.net/juju-core/environs/config"
195+ "launchpad.net/juju-core/instance"
196+ "launchpad.net/juju-core/state"
197+)
198+
199+type InstanceDistributorSuite struct {
200+ ConnSuite
201+ distributor mockInstanceDistributor
202+ wordpress *state.Service
203+ machines []*state.Machine
204+}
205+
206+var _ = gc.Suite(&InstanceDistributorSuite{})
207+
208+type mockInstanceDistributor struct {
209+ candidates []instance.Id
210+ distributionGroup []instance.Id
211+ result []instance.Id
212+ err error
213+}
214+
215+func (p *mockInstanceDistributor) DistributeInstances(candidates, distributionGroup []instance.Id) ([]instance.Id, error) {
216+ p.candidates = candidates
217+ p.distributionGroup = distributionGroup
218+ result := p.result
219+ if result == nil {
220+ result = candidates
221+ }
222+ return result, p.err
223+}
224+
225+func (s *InstanceDistributorSuite) SetUpTest(c *gc.C) {
226+ s.ConnSuite.SetUpTest(c)
227+ s.distributor = mockInstanceDistributor{}
228+ s.policy.getInstanceDistributor = func(*config.Config) (state.InstanceDistributor, error) {
229+ return &s.distributor, nil
230+ }
231+ s.wordpress = s.AddTestingServiceWithNetworks(
232+ c,
233+ "wordpress",
234+ s.AddTestingCharm(c, "wordpress"),
235+ []string{"net1", "net2"},
236+ []string{"net3", "net4"},
237+ )
238+ s.machines = make([]*state.Machine, 3)
239+ for i := range s.machines {
240+ var err error
241+ s.machines[i], err = s.State.AddOneMachine(state.MachineTemplate{
242+ Series: "quantal",
243+ Jobs: []state.MachineJob{state.JobHostUnits},
244+ })
245+ c.Assert(err, gc.IsNil)
246+ }
247+}
248+
249+func (s *InstanceDistributorSuite) setupScenario(c *gc.C) {
250+ // Assign a unit so we have a non-empty distribution group, and
251+ // provision all instances so we have candidates.
252+ unit, err := s.wordpress.AddUnit()
253+ c.Assert(err, gc.IsNil)
254+ err = unit.AssignToMachine(s.machines[0])
255+ c.Assert(err, gc.IsNil)
256+ for i, m := range s.machines {
257+ instId := instance.Id(fmt.Sprintf("i-blah-%d", i))
258+ err = m.SetProvisioned(instId, "fake-nonce", nil)
259+ c.Assert(err, gc.IsNil)
260+ }
261+}
262+
263+func (s *InstanceDistributorSuite) TestDistributeInstances(c *gc.C) {
264+ s.setupScenario(c)
265+ unit, err := s.wordpress.AddUnit()
266+ c.Assert(err, gc.IsNil)
267+ _, err = unit.AssignToCleanMachine()
268+ c.Assert(err, gc.IsNil)
269+ c.Assert(s.distributor.candidates, jc.SameContents, []instance.Id{"i-blah-1", "i-blah-2"})
270+ c.Assert(s.distributor.distributionGroup, jc.SameContents, []instance.Id{"i-blah-0"})
271+ s.distributor.result = []instance.Id{}
272+ _, err = unit.AssignToCleanMachine()
273+ c.Assert(err, gc.ErrorMatches, eligibleMachinesInUse)
274+}
275+
276+func (s *InstanceDistributorSuite) TestDistributeInstancesInvalidInstances(c *gc.C) {
277+ s.setupScenario(c)
278+ unit, err := s.wordpress.AddUnit()
279+ c.Assert(err, gc.IsNil)
280+ s.distributor.result = []instance.Id{"notthere"}
281+ _, err = unit.AssignToCleanMachine()
282+ c.Assert(err, gc.ErrorMatches, `cannot assign unit "wordpress/1" to clean machine: invalid instance returned: notthere`)
283+}
284+
285+func (s *InstanceDistributorSuite) TestDistributeInstancesNoEmptyMachines(c *gc.C) {
286+ for i := range s.machines {
287+ // Assign a unit so we have a non-empty distribution group.
288+ unit, err := s.wordpress.AddUnit()
289+ c.Assert(err, gc.IsNil)
290+ m, err := unit.AssignToCleanMachine()
291+ c.Assert(err, gc.IsNil)
292+ instId := instance.Id(fmt.Sprintf("i-blah-%d", i))
293+ err = m.SetProvisioned(instId, "fake-nonce", nil)
294+ c.Assert(err, gc.IsNil)
295+ }
296+
297+ // InstanceDistributor is not called if there are no empty instances.
298+ s.distributor.err = fmt.Errorf("no assignment for you")
299+ unit, err := s.wordpress.AddUnit()
300+ c.Assert(err, gc.IsNil)
301+ _, err = unit.AssignToCleanMachine()
302+ c.Assert(err, gc.ErrorMatches, eligibleMachinesInUse)
303+}
304+
305+func (s *InstanceDistributorSuite) TestDistributeInstancesErrors(c *gc.C) {
306+ s.setupScenario(c)
307+ unit, err := s.wordpress.AddUnit()
308+ c.Assert(err, gc.IsNil)
309+
310+ // Ensure that assignment fails when DistributeInstances returns an error.
311+ s.distributor.err = fmt.Errorf("no assignment for you")
312+ _, err = unit.AssignToCleanMachine()
313+ c.Assert(err, gc.ErrorMatches, ".*no assignment for you")
314+ _, err = unit.AssignToCleanEmptyMachine()
315+ c.Assert(err, gc.ErrorMatches, ".*no assignment for you")
316+ // If the policy's InstanceDistributor method fails, that will be returned first.
317+ s.policy.getInstanceDistributor = func(*config.Config) (state.InstanceDistributor, error) {
318+ return nil, fmt.Errorf("incapable of InstanceDistributor")
319+ }
320+ _, err = unit.AssignToCleanMachine()
321+ c.Assert(err, gc.ErrorMatches, ".*incapable of InstanceDistributor")
322+}
323+
324+func (s *InstanceDistributorSuite) TestDistributeInstancesEmptyDistributionGroup(c *gc.C) {
325+ s.distributor.err = fmt.Errorf("no assignment for you")
326+
327+ // InstanceDistributor is not called if the distribution group is empty.
328+ unit0, err := s.wordpress.AddUnit()
329+ c.Assert(err, gc.IsNil)
330+ _, err = unit0.AssignToCleanMachine()
331+ c.Assert(err, gc.IsNil)
332+
333+ // Distribution group is still empty, because the machine assigned to has
334+ // not been provisioned.
335+ unit1, err := s.wordpress.AddUnit()
336+ c.Assert(err, gc.IsNil)
337+ _, err = unit1.AssignToCleanMachine()
338+ c.Assert(err, gc.IsNil)
339+}
340+
341+func (s *InstanceDistributorSuite) TestInstanceDistributorUnimplemented(c *gc.C) {
342+ s.setupScenario(c)
343+ var distributorErr error
344+ s.policy.getInstanceDistributor = func(*config.Config) (state.InstanceDistributor, error) {
345+ return nil, distributorErr
346+ }
347+ unit, err := s.wordpress.AddUnit()
348+ c.Assert(err, gc.IsNil)
349+ _, err = unit.AssignToCleanMachine()
350+ c.Assert(err, gc.ErrorMatches, `cannot assign unit "wordpress/1" to clean machine: policy returned nil instance distributor without an error`)
351+ distributorErr = errors.NotImplementedf("InstanceDistributor")
352+ _, err = unit.AssignToCleanMachine()
353+ c.Assert(err, gc.IsNil)
354+}
355+
356+func (s *InstanceDistributorSuite) TestDistributeInstancesNoPolicy(c *gc.C) {
357+ s.policy.getInstanceDistributor = func(*config.Config) (state.InstanceDistributor, error) {
358+ c.Errorf("should not have been invoked")
359+ return nil, nil
360+ }
361+ state.SetPolicy(s.State, nil)
362+ unit, err := s.wordpress.AddUnit()
363+ c.Assert(err, gc.IsNil)
364+ _, err = unit.AssignToCleanMachine()
365+ c.Assert(err, gc.IsNil)
366+}
367
368=== modified file 'state/policy.go'
369--- state/policy.go 2014-05-13 04:30:48 +0000
370+++ state/policy.go 2014-05-30 12:33:30 +0000
371@@ -10,6 +10,7 @@
372
373 "launchpad.net/juju-core/constraints"
374 "launchpad.net/juju-core/environs/config"
375+ "launchpad.net/juju-core/instance"
376 )
377
378 // Policy is an interface provided to State that may
379@@ -22,21 +23,24 @@
380 // be ignored. Any other error will cause an error
381 // in the use of the policy.
382 type Policy interface {
383- // Prechecker takes a *config.Config and returns
384- // a (possibly nil) Prechecker or an error.
385+ // Prechecker takes a *config.Config and returns a Prechecker or an error.
386 Prechecker(*config.Config) (Prechecker, error)
387
388- // ConfigValidator takes a provider type name and returns
389- // a (possibly nil) ConfigValidator or an error.
390+ // ConfigValidator takes a provider type name and returns a ConfigValidator
391+ // or an error.
392 ConfigValidator(providerType string) (ConfigValidator, error)
393
394- // EnvironCapability takes a *config.Config and returns
395- // a (possibly nil) EnvironCapability or an error.
396+ // EnvironCapability takes a *config.Config and returns an EnvironCapability
397+ // or an error.
398 EnvironCapability(*config.Config) (EnvironCapability, error)
399
400- // ConstraintsValidator takes a *config.Config and returns
401- // a (possibly nil) constraints.Validator or an error.
402+ // ConstraintsValidator takes a *config.Config and returns a
403+ // constraints.Validator or an error.
404 ConstraintsValidator(*config.Config) (constraints.Validator, error)
405+
406+ // InstanceDistributor takes a *config.Config and returns an
407+ // InstanceDistributor or an error.
408+ InstanceDistributor(*config.Config) (InstanceDistributor, error)
409 }
410
411 // Prechecker is a policy interface that is provided to State
412@@ -187,3 +191,21 @@
413 }
414 return capability.SupportsUnitPlacement()
415 }
416+
417+// InstanceDistributor is a policy interface that is provided
418+// to State to perform distribution of units across instances
419+// for high availability.
420+type InstanceDistributor interface {
421+ // DistributeInstance takes a set of clean, empty
422+ // instances, and a distribution group, and returns
423+ // the subset of candidates which the policy will
424+ // allow entry into the distribution group.
425+ //
426+ // The AssignClean and AssignCleanEmpty unit
427+ // assignment policies will attempt to assign a
428+ // unit to each of the resulting instances until
429+ // one is successful. If no instances can be assigned
430+ // to (e.g. because of concurrent deployments), then
431+ // a new machine will be allocated.
432+ DistributeInstances(candidates, distributionGroup []instance.Id) ([]instance.Id, error)
433+}
434
435=== modified file 'state/service.go'
436--- state/service.go 2014-05-26 21:16:46 +0000
437+++ state/service.go 2014-05-30 12:33:30 +0000
438@@ -723,13 +723,17 @@
439
440 // AllUnits returns all units of the service.
441 func (s *Service) AllUnits() (units []*Unit, err error) {
442+ return allUnits(s.st, s.doc.Name)
443+}
444+
445+func allUnits(st *State, service string) (units []*Unit, err error) {
446 docs := []unitDoc{}
447- err = s.st.units.Find(bson.D{{"service", s.doc.Name}}).All(&docs)
448+ err = st.units.Find(bson.D{{"service", service}}).All(&docs)
449 if err != nil {
450- return nil, fmt.Errorf("cannot get all units from service %q: %v", s, err)
451+ return nil, fmt.Errorf("cannot get all units from service %q: %v", service, err)
452 }
453 for i := range docs {
454- units = append(units, newUnit(s.st, &docs[i]))
455+ units = append(units, newUnit(st, &docs[i]))
456 }
457 return units, nil
458 }
459
460=== modified file 'state/unit.go'
461--- state/unit.go 2014-05-27 05:36:00 +0000
462+++ state/unit.go 2014-05-30 12:33:30 +0000
463@@ -1277,14 +1277,63 @@
464 return nil, err
465 }
466
467- // TODO(rog) Fix so this is more efficient when there are concurrent uses.
468- // Possible solution: pick the highest and the smallest id of all
469- // unused machines, and try to assign to the first one >= a random id in the
470- // middle.
471- iter := query.Batch(1).Prefetch(0).Iter()
472- var mdoc machineDoc
473- for iter.Next(&mdoc) {
474- m := newMachine(u.st, &mdoc)
475+ // Find all of the candidate machines, and associated
476+ // instances for those that are provisioned. Instances
477+ // will be distributed across in preference to
478+ // unprovisioned machines.
479+ var mdocs []*machineDoc
480+ if err := query.All(&mdocs); err != nil {
481+ assignContextf(&err, u, context)
482+ return nil, err
483+ }
484+ var unprovisioned []*Machine
485+ var instances []instance.Id
486+ instanceMachines := make(map[instance.Id]*Machine)
487+ for _, mdoc := range mdocs {
488+ m := newMachine(u.st, mdoc)
489+ instance, err := m.InstanceId()
490+ if IsNotProvisionedError(err) {
491+ unprovisioned = append(unprovisioned, m)
492+ } else if err != nil {
493+ assignContextf(&err, u, context)
494+ return nil, err
495+ } else {
496+ instances = append(instances, instance)
497+ instanceMachines[instance] = m
498+ }
499+ }
500+
501+ // Filter the list of instances that are suitable for
502+ // distribution, and then map them back to machines.
503+ //
504+ // TODO(axw) 2014-05-30 #1324904
505+ // Shuffle machines to reduce likelihood of collisions.
506+ // The partition of provisioned/unprovisioned machines
507+ // must be maintained.
508+ if instances, err = distributeUnit(u, instances); err != nil {
509+ assignContextf(&err, u, context)
510+ return nil, err
511+ }
512+ machines := make([]*Machine, len(instances), len(instances)+len(unprovisioned))
513+ for i, instance := range instances {
514+ m, ok := instanceMachines[instance]
515+ if !ok {
516+ err := fmt.Errorf("invalid instance returned: %v", instance)
517+ assignContextf(&err, u, context)
518+ return nil, err
519+ }
520+ machines[i] = m
521+ }
522+ machines = append(machines, unprovisioned...)
523+
524+ // TODO(axw) 2014-05-30 #1253704
525+ // We should not select a machine that is in the process
526+ // of being provisioned. There's no point asserting that
527+ // the machine hasn't been provisioned, as there'll still
528+ // be a period of time during which the machine may be
529+ // provisioned without the fact having yet been recorded
530+ // in state.
531+ for _, m := range machines {
532 err := u.assignToMachine(m, true)
533 if err == nil {
534 return m, nil
535@@ -1294,10 +1343,6 @@
536 return nil, err
537 }
538 }
539- if err := iter.Err(); err != nil {
540- assignContextf(&err, u, context)
541- return nil, err
542- }
543 return nil, noCleanMachines
544 }
545

Subscribers

People subscribed via source and target branches

to status/vote changes: