Merge lp:~axwalk/juju-core/startinstance-principalunit 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: 2551
Proposed branch: lp:~axwalk/juju-core/startinstance-principalunit
Merge into: lp:~go-bot/juju-core/trunk
Prerequisite: lp:~axwalk/juju-core/startinstance-param-struct
Diff against target: 430 lines (+325/-4)
8 files modified
environs/broker.go (+9/-1)
provider/manual/environ.go (+1/-0)
state/api/params/params.go (+13/-0)
state/api/provisioner/machine.go (+23/-0)
state/api/provisioner/provisioner_test.go (+46/-0)
state/apiserver/provisioner/provisioner.go (+102/-0)
state/apiserver/provisioner/provisioner_test.go (+127/-0)
worker/provisioner/provisioner_task.go (+4/-3)
To merge this branch: bzr merge lp:~axwalk/juju-core/startinstance-principalunit
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+210746@code.launchpad.net

Commit message

Add DistributionGroup to StartInstanceParams

DistributionGroup returns instances that
StartInstance should consider when provisioning
for high availability (i.e. the new instance +
those in DistributionGroup should be distributed
for high availability.)

DistributionGroup is implemented in terms of
a new provisioner API method of the same name.
This API returns different instances depending
on the machine passed in. For an environ manager,
all other environ manager instances are returned;
for a non-environ manager, all other instances
with services in common are returned.

This is in preparation for modifying the Azure
provider to group instances into the same cloud
service when in availability-sets-enabled mode.

https://codereview.appspot.com/75250043/

Description of the change

Add DistributionGroup to StartInstanceParams

DistributionGroup returns instances that
StartInstance should consider when provisioning
for high availability (i.e. the new instance +
those in DistributionGroup should be distributed
for high availability.)

DistributionGroup is implemented in terms of
a new provisioner API method of the same name.
This API returns different instances depending
on the machine passed in. For an environ manager,
all other environ manager instances are returned;
for a non-environ manager, all other instances
with services in common are returned.

This is in preparation for modifying the Azure
provider to group instances into the same cloud
service when in availability-sets-enabled mode.

(Note: the previous patch included a change to
PrecheckInstance; this has been reverted, and
will be implemented differently in another CL.)

https://codereview.appspot.com/75250043/

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

Reviewers: mp+210746_code.launchpad.net,

Message:
Please take a look.

Description:
Add PrincipalUnits to StartInstanceParams

Also, pass to principalUnits to PrecheckInstance.

This is in preparation for modifying the Azure
provider to prevent "juju add-machine" when in
availability set mode, and use the principal unit's
service name to decide on which Cloud Service to
allocate the instance to.

https://code.launchpad.net/~axwalk/juju-core/startinstance-principalunit/+merge/210746

Requires:
https://code.launchpad.net/~axwalk/juju-core/startinstance-param-struct/+merge/210738

(do not edit description out of merge proposal)

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

Affected files (+139, -16 lines):
   A [revision details]
   M environs/broker.go
   M environs/jujutest/livetests.go
   M provider/manual/environ.go
   M state/addmachine.go
   M state/api/provisioner/machine.go
   M state/apiserver/provisioner/provisioner.go
   M state/apiserver/provisioner/provisioner_test.go
   M state/policy.go
   M state/prechecker_test.go
   M worker/provisioner/provisioner_task.go

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

Looks ok pending William's +1.
But there's a missing test for the client PrincipalUnits API call in
state/api/provisioner

https://codereview.appspot.com/75250043/

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Revision history for this message
Andrew Wilkins (axwalk) wrote :
Revision history for this message
Horacio Durán (hduran-8) wrote :

https://codereview.appspot.com/75250043/diff/40001/state/api/provisioner/provisioner_test.go
File state/api/provisioner/provisioner_test.go (right):

https://codereview.appspot.com/75250043/diff/40001/state/api/provisioner/provisioner_test.go#newcode235
state/api/provisioner/provisioner_test.go:235:
In CommonServiceInstances there are four return statements, three of
them with error states yet this test only sees the possibility of a
successful run of Call, is there no way to test the other three possible
outcomes?

https://codereview.appspot.com/75250043/

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

Please take a look.

https://codereview.appspot.com/75250043/diff/40001/state/api/provisioner/provisioner_test.go
File state/api/provisioner/provisioner_test.go (right):

https://codereview.appspot.com/75250043/diff/40001/state/api/provisioner/provisioner_test.go#newcode235
state/api/provisioner/provisioner_test.go:235:
On 2014/03/19 17:31:42, hduran wrote:
> In CommonServiceInstances there are four return statements, three of
them with
> error states yet this test only sees the possibility of a successful
run of
> Call, is there no way to test the other three possible outcomes?

Testing the first two errors is not possible without creating a custom
API server. Kind of a cop out, but precisely none of the other API
methods test them either.

The third error can be tested, and in fact is tested in
apiserver/provisioner_test.go. I will add a test here, too, though.

https://codereview.appspot.com/75250043/

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

Looks really good. I'm open to arguments re notfound/unauth; the only
thing I'm not sure about is handling for container provisioners. Have
these been directly considered?

https://codereview.appspot.com/75250043/diff/80001/state/apiserver/provisioner/provisioner_test.go
File state/apiserver/provisioner/provisioner_test.go (right):

https://codereview.appspot.com/75250043/diff/80001/state/apiserver/provisioner/provisioner_test.go#newcode568
state/apiserver/provisioner/provisioner_test.go:568: // show up in the
results, but it will have a blank instance.Id.
I'm not 100% sure about that. Feels better to filter them out at API
level than to make every provider have to deal with empty instances in
the group.

https://codereview.appspot.com/75250043/diff/80001/state/apiserver/provisioner/provisioner_test.go#newcode607
state/apiserver/provisioner/provisioner_test.go:607: {Error:
apiservertesting.NotFoundError("machine 42")},
I'm wondering about notfound vs unauthorized. For an env provisioner, I
suspect that the Right Thing is to NotFound unknown top-level machines,
and to Unauthorized all hosted machines regardless of existence; and for
container provisioners, to Unauthorized everything except precisely
those containers for which the provisioner is directly responsible; but
that's a pile of tedious complexity, and if we just use unauth instead
of notfound across the board we might end up with simpler code and less
theoretical info leakage. Thoughts?

https://codereview.appspot.com/75250043/diff/80001/state/state_test.go
File state/state_test.go (right):

https://codereview.appspot.com/75250043/diff/80001/state/state_test.go#newcode969
state/state_test.go:969: err := s.State.EnsureAvailability(3,
constraints.Value{}, "quantal")
fwiw, it makes me ever so happy to see this just casually being called
in tests :)

https://codereview.appspot.com/75250043/

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

WIP for last round of changes (then probably WIP again until 1.19)

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

Please take a look.

https://codereview.appspot.com/75250043/diff/80001/state/apiserver/provisioner/provisioner_test.go
File state/apiserver/provisioner/provisioner_test.go (right):

https://codereview.appspot.com/75250043/diff/80001/state/apiserver/provisioner/provisioner_test.go#newcode568
state/apiserver/provisioner/provisioner_test.go:568: // show up in the
results, but it will have a blank instance.Id.
On 2014/03/20 09:23:53, fwereade wrote:
> I'm not 100% sure about that. Feels better to filter them out at API
level than
> to make every provider have to deal with empty instances in the group.

Sorry, that's actually a leftover comment from a previous
implementation. It doesn't do that anymore. I've fixed up the comments.

https://codereview.appspot.com/75250043/diff/80001/state/apiserver/provisioner/provisioner_test.go#newcode607
state/apiserver/provisioner/provisioner_test.go:607: {Error:
apiservertesting.NotFoundError("machine 42")},
On 2014/03/20 09:23:53, fwereade wrote:
> I'm wondering about notfound vs unauthorized. For an env provisioner,
I suspect
> that the Right Thing is to NotFound unknown top-level machines, and to
> Unauthorized all hosted machines regardless of existence; and for
container
> provisioners, to Unauthorized everything except precisely those
containers for
> which the provisioner is directly responsible; but that's a pile of
tedious
> complexity, and if we just use unauth instead of notfound across the
board we
> might end up with simpler code and less theoretical info leakage.
Thoughts?

At the moment, the auth function allows the env provisioner to access; a
machine agent may access its own machine; and hosted machines are only
accessible to the machine agent that contains it. That seems sane to me.
The tests don't reflect this, as there's no containers. I'll update the
tests.

However... I don't plan to use DistributionGroup on container managers,
at least not yet. You can't really change how a container is
distributed, since its host is fixed before we get to this point.

https://codereview.appspot.com/75250043/

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

LGTM. Taking DG into account when choosing pre-existing instances is not
in scope for this CL, but please make sure it's on your list of
necessary-features-before-done-done.

https://codereview.appspot.com/75250043/diff/80001/state/apiserver/provisioner/provisioner_test.go
File state/apiserver/provisioner/provisioner_test.go (right):

https://codereview.appspot.com/75250043/diff/80001/state/apiserver/provisioner/provisioner_test.go#newcode607
state/apiserver/provisioner/provisioner_test.go:607: {Error:
apiservertesting.NotFoundError("machine 42")},
On 2014/03/21 07:55:56, axw wrote:
> However... I don't plan to use DistributionGroup on container
managers, at least
> not yet. You can't really change how a container is distributed, since
its host
> is fixed before we get to this point.

That's fine, I think, although we still need to pay some attention to DG
in containers in *some* way -- ie, if we have a containerised service,
we still want to distribute its units' containers across real instances
that are themselves distributed. That's a different piece of code
though.

https://codereview.appspot.com/75250043/diff/100001/state/apiserver/provisioner/provisioner.go
File state/apiserver/provisioner/provisioner.go (right):

https://codereview.appspot.com/75250043/diff/100001/state/apiserver/provisioner/provisioner.go#newcode334
state/apiserver/provisioner/provisioner.go:334: // Sort values to
simplify testing.
copy(instanceIds, instanceIdSet.SortedValues())

?

https://codereview.appspot.com/75250043/diff/100001/state/apiserver/provisioner/provisioner_test.go
File state/apiserver/provisioner/provisioner_test.go (right):

https://codereview.appspot.com/75250043/diff/100001/state/apiserver/provisioner/provisioner_test.go#newcode563
state/apiserver/provisioner/provisioner_test.go:563: // Unassign
wordpress/1 from machine-1.
blank lines before comments for ease of reading please

https://codereview.appspot.com/75250043/diff/100001/state/state.go
File state/state.go (right):

https://codereview.appspot.com/75250043/diff/100001/state/state.go#newcode323
state/state.go:323: func (st *State) ManagerMachines() (machines
[]*Machine, err error) {
Please liaise with rog, this may need to change and I want to be sure he
knows about it.

https://codereview.appspot.com/75250043/

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

Please take a look.

https://codereview.appspot.com/75250043/diff/100001/state/apiserver/provisioner/provisioner.go
File state/apiserver/provisioner/provisioner.go (right):

https://codereview.appspot.com/75250043/diff/100001/state/apiserver/provisioner/provisioner.go#newcode334
state/apiserver/provisioner/provisioner.go:334: // Sort values to
simplify testing.
On 2014/03/24 10:12:20, fwereade wrote:
> copy(instanceIds, instanceIdSet.SortedValues())

> ?

Nope, different types: []instance.Id/[]string.

https://codereview.appspot.com/75250043/diff/100001/state/apiserver/provisioner/provisioner_test.go
File state/apiserver/provisioner/provisioner_test.go (right):

https://codereview.appspot.com/75250043/diff/100001/state/apiserver/provisioner/provisioner_test.go#newcode563
state/apiserver/provisioner/provisioner_test.go:563: // Unassign
wordpress/1 from machine-1.
On 2014/03/24 10:12:20, fwereade wrote:
> blank lines before comments for ease of reading please

Done.

https://codereview.appspot.com/75250043/diff/100001/state/state.go
File state/state.go (right):

https://codereview.appspot.com/75250043/diff/100001/state/state.go#newcode323
state/state.go:323: func (st *State) ManagerMachines() (machines
[]*Machine, err error) {
On 2014/03/24 10:12:20, fwereade wrote:
> Please liaise with rog, this may need to change and I want to be sure
he knows
> about it.

Rog brought State.StateServerInfo to my attention, which does what I
need. There'll be up to 7 (replicaset.Max) additional State.Machine
calls, but this is low volume so I think that's fine.

https://codereview.appspot.com/75250043/

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'environs/broker.go'
2--- environs/broker.go 2014-04-01 16:27:22 +0000
3+++ environs/broker.go 2014-04-03 02:58:17 +0000
4@@ -11,7 +11,7 @@
5 )
6
7 // StartInstanceParams holds parameters for the
8-// InstanceBroker.StartInstace method.
9+// InstanceBroker.StartInstance method.
10 type StartInstanceParams struct {
11 // Constraints is a set of constraints on
12 // the kind of instance to create.
13@@ -23,6 +23,14 @@
14
15 // MachineConfig describes the machine's configuration.
16 MachineConfig *cloudinit.MachineConfig
17+
18+ // DistributionGroup, if non-nil, is a function
19+ // that returns a slice of instance.Ids that belong
20+ // to the same distribution group as the machine
21+ // being provisioned. The InstanceBroker may use
22+ // this information to distribute instances for
23+ // high availability.
24+ DistributionGroup func() ([]instance.Id, error)
25 }
26
27 // TODO(wallyworld) - we want this in the environs/instance package but import loops
28
29=== modified file 'provider/manual/environ.go'
30--- provider/manual/environ.go 2014-03-28 13:27:45 +0000
31+++ provider/manual/environ.go 2014-04-03 02:58:17 +0000
32@@ -57,6 +57,7 @@
33 }
34
35 var _ envtools.SupportsCustomSources = (*manualEnviron)(nil)
36+var _ state.Prechecker = (*manualEnviron)(nil)
37
38 var errNoStartInstance = errors.New("manual provider cannot start instances")
39 var errNoStopInstance = errors.New("manual provider cannot stop instances")
40
41=== modified file 'state/api/params/params.go'
42--- state/api/params/params.go 2014-04-02 15:46:57 +0000
43+++ state/api/params/params.go 2014-04-03 02:58:17 +0000
44@@ -647,6 +647,19 @@
45 CACert []byte
46 }
47
48+// DistributionGroupResult contains the result of
49+// the DistributionGroup provisioner API call.
50+type DistributionGroupResult struct {
51+ Error *Error
52+ Result []instance.Id
53+}
54+
55+// DistributionGroupResults is the bulk form of
56+// DistributionGroupResult.
57+type DistributionGroupResults struct {
58+ Results []DistributionGroupResult
59+}
60+
61 // APIHostPortsResult holds the result of an APIHostPorts
62 // call. Each element in the top level slice holds
63 // the addresses for one API server.
64
65=== modified file 'state/api/provisioner/machine.go'
66--- state/api/provisioner/machine.go 2014-03-31 11:59:16 +0000
67+++ state/api/provisioner/machine.go 2014-04-03 02:58:17 +0000
68@@ -182,6 +182,29 @@
69 return result.Result, nil
70 }
71
72+// DistributionGroup returns a slice of instance.Ids
73+// that belong to the same distribution group as this
74+// Machine. The provisioner may use this information
75+// to distribute instances for high availability.
76+func (m *Machine) DistributionGroup() ([]instance.Id, error) {
77+ var results params.DistributionGroupResults
78+ args := params.Entities{
79+ Entities: []params.Entity{{Tag: m.tag}},
80+ }
81+ err := m.st.caller.Call("Provisioner", "", "DistributionGroup", args, &results)
82+ if err != nil {
83+ return nil, err
84+ }
85+ if len(results.Results) != 1 {
86+ return nil, fmt.Errorf("expected one result, got %d", len(results.Results))
87+ }
88+ result := results.Results[0]
89+ if result.Error != nil {
90+ return nil, result.Error
91+ }
92+ return result.Result, nil
93+}
94+
95 // SetProvisioned sets the provider specific machine id, nonce and also metadata for
96 // this machine. Once set, the instance id cannot be changed.
97 func (m *Machine) SetProvisioned(id instance.Id, nonce string, characteristics *instance.HardwareCharacteristics) error {
98
99=== modified file 'state/api/provisioner/provisioner_test.go'
100--- state/api/provisioner/provisioner_test.go 2014-04-02 10:36:23 +0000
101+++ state/api/provisioner/provisioner_test.go 2014-04-03 02:58:17 +0000
102@@ -246,6 +246,52 @@
103 c.Assert(series, gc.Equals, "quantal")
104 }
105
106+func (s *provisionerSuite) TestDistributionGroup(c *gc.C) {
107+ apiMachine, err := s.provisioner.Machine(s.machine.Tag())
108+ c.Assert(err, gc.IsNil)
109+ instances, err := apiMachine.DistributionGroup()
110+ c.Assert(err, gc.IsNil)
111+ c.Assert(instances, gc.DeepEquals, []instance.Id{"i-manager"})
112+
113+ machine1, err := s.State.AddMachine("quantal", state.JobHostUnits)
114+ c.Assert(err, gc.IsNil)
115+ apiMachine, err = s.provisioner.Machine(machine1.Tag())
116+ c.Assert(err, gc.IsNil)
117+ wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
118+
119+ err = apiMachine.SetProvisioned("i-d", "fake", nil)
120+ c.Assert(err, gc.IsNil)
121+ instances, err = apiMachine.DistributionGroup()
122+ c.Assert(err, gc.IsNil)
123+ c.Assert(instances, gc.HasLen, 0) // no units assigned
124+
125+ var unitNames []string
126+ for i := 0; i < 3; i++ {
127+ unit, err := wordpress.AddUnit()
128+ c.Assert(err, gc.IsNil)
129+ unitNames = append(unitNames, unit.Name())
130+ err = unit.AssignToMachine(machine1)
131+ c.Assert(err, gc.IsNil)
132+ instances, err := apiMachine.DistributionGroup()
133+ c.Assert(err, gc.IsNil)
134+ c.Assert(instances, gc.DeepEquals, []instance.Id{"i-d"})
135+ }
136+}
137+
138+func (s *provisionerSuite) TestDistributionGroupMachineNotFound(c *gc.C) {
139+ stateMachine, err := s.State.AddMachine("quantal", state.JobHostUnits)
140+ c.Assert(err, gc.IsNil)
141+ apiMachine, err := s.provisioner.Machine(stateMachine.Tag())
142+ c.Assert(err, gc.IsNil)
143+ err = apiMachine.EnsureDead()
144+ c.Assert(err, gc.IsNil)
145+ err = apiMachine.Remove()
146+ c.Assert(err, gc.IsNil)
147+ _, err = apiMachine.DistributionGroup()
148+ c.Assert(err, gc.ErrorMatches, "machine 1 not found")
149+ c.Assert(err, jc.Satisfies, params.IsCodeNotFound)
150+}
151+
152 func (s *provisionerSuite) TestConstraints(c *gc.C) {
153 // Create a fresh machine with some constraints.
154 template := state.MachineTemplate{
155
156=== modified file 'state/apiserver/provisioner/provisioner.go'
157--- state/apiserver/provisioner/provisioner.go 2014-03-31 11:59:16 +0000
158+++ state/apiserver/provisioner/provisioner.go 2014-04-03 02:58:17 +0000
159@@ -11,6 +11,7 @@
160 "launchpad.net/juju-core/state/api/params"
161 "launchpad.net/juju-core/state/apiserver/common"
162 "launchpad.net/juju-core/state/watcher"
163+ "launchpad.net/juju-core/utils/set"
164 )
165
166 // ProvisionerAPI provides access to the Provisioner API facade.
167@@ -282,6 +283,107 @@
168 return result, nil
169 }
170
171+// DistributionGroup returns, for each given machine entity,
172+// a slice of instance.Ids that belong to the same distribution
173+// group as that machine. This information may be used to
174+// distribute instances for high availability.
175+func (p *ProvisionerAPI) DistributionGroup(args params.Entities) (params.DistributionGroupResults, error) {
176+ result := params.DistributionGroupResults{
177+ Results: make([]params.DistributionGroupResult, len(args.Entities)),
178+ }
179+ canAccess, err := p.getAuthFunc()
180+ if err != nil {
181+ return result, err
182+ }
183+ for i, entity := range args.Entities {
184+ machine, err := p.getMachine(canAccess, entity.Tag)
185+ if err == nil {
186+ // If the machine is an environment manager, return
187+ // environment manager instances. Otherwise, return
188+ // instances with services in common with the machine
189+ // being provisioned.
190+ if machine.IsManager() {
191+ result.Results[i].Result, err = environManagerInstances(p.st)
192+ } else {
193+ result.Results[i].Result, err = commonServiceInstances(p.st, machine)
194+ }
195+ }
196+ result.Results[i].Error = common.ServerError(err)
197+ }
198+ return result, nil
199+}
200+
201+// environManagerInstances returns all environ manager instances.
202+func environManagerInstances(st *state.State) ([]instance.Id, error) {
203+ info, err := st.StateServerInfo()
204+ if err != nil {
205+ return nil, err
206+ }
207+ instances := make([]instance.Id, 0, len(info.MachineIds))
208+ for _, id := range info.MachineIds {
209+ machine, err := st.Machine(id)
210+ if err != nil {
211+ return nil, err
212+ }
213+ instanceId, err := machine.InstanceId()
214+ if err == nil {
215+ instances = append(instances, instanceId)
216+ } else if !state.IsNotProvisionedError(err) {
217+ return nil, err
218+ }
219+ }
220+ return instances, nil
221+}
222+
223+// commonServiceInstances returns instances with
224+// services in common with the specified machine.
225+func commonServiceInstances(st *state.State, m *state.Machine) ([]instance.Id, error) {
226+ units, err := m.Units()
227+ if err != nil {
228+ return nil, err
229+ }
230+ var instanceIdSet set.Strings
231+ for _, unit := range units {
232+ if !unit.IsPrincipal() {
233+ continue
234+ }
235+ service, err := unit.Service()
236+ if err != nil {
237+ return nil, err
238+ }
239+ allUnits, err := service.AllUnits()
240+ if err != nil {
241+ return nil, err
242+ }
243+ for _, unit := range allUnits {
244+ machineId, err := unit.AssignedMachineId()
245+ if state.IsNotAssigned(err) {
246+ continue
247+ } else if err != nil {
248+ return nil, err
249+ }
250+ machine, err := st.Machine(machineId)
251+ if err != nil {
252+ return nil, err
253+ }
254+ instanceId, err := machine.InstanceId()
255+ if err == nil {
256+ instanceIdSet.Add(string(instanceId))
257+ } else if state.IsNotProvisionedError(err) {
258+ continue
259+ } else {
260+ return nil, err
261+ }
262+ }
263+ }
264+ instanceIds := make([]instance.Id, instanceIdSet.Size())
265+ // Sort values to simplify testing.
266+ for i, instanceId := range instanceIdSet.SortedValues() {
267+ instanceIds[i] = instance.Id(instanceId)
268+ }
269+ return instanceIds, nil
270+}
271+
272 // Constraints returns the constraints for each given machine entity.
273 func (p *ProvisionerAPI) Constraints(args params.Entities) (params.ConstraintsResults, error) {
274 result := params.ConstraintsResults{
275
276=== modified file 'state/apiserver/provisioner/provisioner_test.go'
277--- state/apiserver/provisioner/provisioner_test.go 2014-04-02 10:36:23 +0000
278+++ state/apiserver/provisioner/provisioner_test.go 2014-04-03 02:58:17 +0000
279@@ -598,6 +598,133 @@
280 })
281 }
282
283+func (s *withoutStateServerSuite) TestDistributionGroup(c *gc.C) {
284+ addUnits := func(name string, machines ...*state.Machine) (units []*state.Unit) {
285+ svc := s.AddTestingService(c, name, s.AddTestingCharm(c, name))
286+ for _, m := range machines {
287+ unit, err := svc.AddUnit()
288+ c.Assert(err, gc.IsNil)
289+ err = unit.AssignToMachine(m)
290+ c.Assert(err, gc.IsNil)
291+ units = append(units, unit)
292+ }
293+ return units
294+ }
295+ setProvisioned := func(id string) {
296+ m, err := s.State.Machine(id)
297+ c.Assert(err, gc.IsNil)
298+ err = m.SetProvisioned(instance.Id("machine-"+id+"-inst"), "nonce", nil)
299+ c.Assert(err, gc.IsNil)
300+ }
301+
302+ mysqlUnit := addUnits("mysql", s.machines[0], s.machines[3])[0]
303+ wordpressUnits := addUnits("wordpress", s.machines[0], s.machines[1], s.machines[2])
304+
305+ // Unassign wordpress/1 from machine-1.
306+ // The unit should not show up in the results.
307+ err := wordpressUnits[1].UnassignFromMachine()
308+ c.Assert(err, gc.IsNil)
309+
310+ // Provision machines 1, 2 and 3. Machine-0 remains
311+ // unprovisioned, and machine-1 has no units, and so
312+ // neither will show up in the results.
313+ setProvisioned("1")
314+ setProvisioned("2")
315+ setProvisioned("3")
316+
317+ // Add a few state servers, provision two of them.
318+ err = s.State.EnsureAvailability(3, constraints.Value{}, "quantal")
319+ c.Assert(err, gc.IsNil)
320+ setProvisioned("5")
321+ setProvisioned("7")
322+
323+ // Create a logging service, subordinate to mysql.
324+ s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging"))
325+ eps, err := s.State.InferEndpoints([]string{"mysql", "logging"})
326+ c.Assert(err, gc.IsNil)
327+ rel, err := s.State.AddRelation(eps...)
328+ c.Assert(err, gc.IsNil)
329+ ru, err := rel.Unit(mysqlUnit)
330+ c.Assert(err, gc.IsNil)
331+ err = ru.EnterScope(nil)
332+ c.Assert(err, gc.IsNil)
333+
334+ args := params.Entities{Entities: []params.Entity{
335+ {Tag: s.machines[0].Tag()},
336+ {Tag: s.machines[1].Tag()},
337+ {Tag: s.machines[2].Tag()},
338+ {Tag: s.machines[3].Tag()},
339+ {Tag: "machine-5"},
340+ }}
341+ result, err := s.provisioner.DistributionGroup(args)
342+ c.Assert(err, gc.IsNil)
343+ c.Assert(result, gc.DeepEquals, params.DistributionGroupResults{
344+ Results: []params.DistributionGroupResult{
345+ {Result: []instance.Id{"machine-2-inst", "machine-3-inst"}},
346+ {Result: []instance.Id{}},
347+ {Result: []instance.Id{"machine-2-inst"}},
348+ {Result: []instance.Id{"machine-3-inst"}},
349+ {Result: []instance.Id{"machine-5-inst", "machine-7-inst"}},
350+ },
351+ })
352+}
353+
354+func (s *withoutStateServerSuite) TestDistributionGroupEnvironManagerAuth(c *gc.C) {
355+ args := params.Entities{Entities: []params.Entity{
356+ {Tag: "machine-0"},
357+ {Tag: "machine-42"},
358+ {Tag: "machine-0-lxc-99"},
359+ {Tag: "unit-foo-0"},
360+ {Tag: "service-bar"},
361+ }}
362+ result, err := s.provisioner.DistributionGroup(args)
363+ c.Assert(err, gc.IsNil)
364+ c.Assert(result, gc.DeepEquals, params.DistributionGroupResults{
365+ Results: []params.DistributionGroupResult{
366+ // environ manager may access any top-level machines.
367+ {Result: []instance.Id{}},
368+ {Error: apiservertesting.NotFoundError("machine 42")},
369+ // only a machine agent for the container or its
370+ // parent may access it.
371+ {Error: apiservertesting.ErrUnauthorized},
372+ // non-machines always unauthorized
373+ {Error: apiservertesting.ErrUnauthorized},
374+ {Error: apiservertesting.ErrUnauthorized},
375+ },
376+ })
377+}
378+
379+func (s *withoutStateServerSuite) TestDistributionGroupMachineAgentAuth(c *gc.C) {
380+ anAuthorizer := s.authorizer
381+ anAuthorizer.Tag = "machine-1"
382+ anAuthorizer.EnvironManager = false
383+ anAuthorizer.MachineAgent = true
384+ provisioner, err := provisioner.NewProvisionerAPI(s.State, s.resources, anAuthorizer)
385+ c.Check(err, gc.IsNil)
386+ args := params.Entities{Entities: []params.Entity{
387+ {Tag: "machine-0"},
388+ {Tag: "machine-1"},
389+ {Tag: "machine-42"},
390+ {Tag: "machine-0-lxc-99"},
391+ {Tag: "machine-1-lxc-99"},
392+ {Tag: "machine-1-lxc-99-lxc-100"},
393+ }}
394+ result, err := provisioner.DistributionGroup(args)
395+ c.Assert(err, gc.IsNil)
396+ c.Assert(result, gc.DeepEquals, params.DistributionGroupResults{
397+ Results: []params.DistributionGroupResult{
398+ {Error: apiservertesting.ErrUnauthorized},
399+ {Result: []instance.Id{}},
400+ {Error: apiservertesting.ErrUnauthorized},
401+ // only a machine agent for the container or its
402+ // parent may access it.
403+ {Error: apiservertesting.ErrUnauthorized},
404+ {Error: apiservertesting.NotFoundError("machine 1/lxc/99")},
405+ {Error: apiservertesting.ErrUnauthorized},
406+ },
407+ })
408+}
409+
410 func (s *withoutStateServerSuite) TestConstraints(c *gc.C) {
411 // Add a machine with some constraints.
412 template := state.MachineTemplate{
413
414=== modified file 'worker/provisioner/provisioner_task.go'
415--- worker/provisioner/provisioner_task.go 2014-04-02 11:35:49 +0000
416+++ worker/provisioner/provisioner_task.go 2014-04-03 02:58:17 +0000
417@@ -426,9 +426,10 @@
418 return err
419 }
420 inst, metadata, err := task.broker.StartInstance(environs.StartInstanceParams{
421- Constraints: cons,
422- Tools: possibleTools,
423- MachineConfig: machineConfig,
424+ Constraints: cons,
425+ Tools: possibleTools,
426+ MachineConfig: machineConfig,
427+ DistributionGroup: machine.DistributionGroup,
428 })
429 if err != nil {
430 // Set the state to error, so the machine will be skipped next

Subscribers

People subscribed via source and target branches

to status/vote changes: