Merge lp:~axwalk/juju-core/state-environcapability 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: 2552
Proposed branch: lp:~axwalk/juju-core/state-environcapability
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 546 lines (+263/-33)
17 files modified
agent/bootstrap_test.go (+5/-2)
environs/interface.go (+2/-16)
environs/statepolicy.go (+8/-10)
provider/azure/environ.go (+3/-0)
provider/common/policies.go (+30/-0)
provider/dummy/environs.go (+3/-0)
provider/ec2/ec2.go (+3/-0)
provider/joyent/environ.go (+3/-0)
provider/local/environ.go (+3/-0)
provider/maas/environ.go (+3/-0)
provider/manual/environ.go (+2/-0)
provider/openstack/provider.go (+3/-0)
state/addmachine.go (+15/-0)
state/conn_test.go (+10/-2)
state/environcapability_test.go (+116/-0)
state/policy.go (+49/-3)
state/unit.go (+5/-0)
To merge this branch: bzr merge lp:~axwalk/juju-core/state-environcapability
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+211749@code.launchpad.net

Commit message

Add EnvironCapability to state.Policy

environs.EnvironCapability is moved to the
state package, extended with the
SupportsUnitPlacement method, and added to
the state.Policy interface.

Methods for creating machines and assigning
units to them are now modified to use the
SupportsUnitPlacement method to enable
policy implementations to block adding of
empty machines (juju add-machine) and to
block unit placement (deploy/add-unit --to).

Also, a new EnvironBase type is added to
provider/common, which implements some
default behaviour common to all providers
such as the new SupportsUnitPlacement
method.

A followup will override the default
behaviour of SupportsUnitPlacement in the
Azure provider, blocking placement if in
availability-sets-enabled mode.

https://codereview.appspot.com/77820044/

Description of the change

Add EnvironCapability to state.Policy

environs.EnvironCapability is moved to the
state package, extended with the
SupportsUnitPlacement method, and added to
the state.Policy interface.

Methods for creating machines and assigning
units to them are now modified to use the
SupportsUnitPlacement method to enable
policy implementations to block adding of
empty machines (juju add-machine) and to
block unit placement (deploy/add-unit --to).

Also, a new EnvironBase type is added to
provider/common, which implements some
default behaviour common to all providers
such as the new SupportsUnitPlacement
method.

A followup will override the default
behaviour of SupportsUnitPlacement in the
Azure provider, blocking placement if in
availability-sets-enabled mode.

https://codereview.appspot.com/77820044/

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

Reviewers: mp+211749_code.launchpad.net,

Message:
Please take a look.

Description:
Add EnvironCapability to state.Policy

environs.EnvironCapability is moved to the
state package, extended with the
SupportsUnitPlacement method, and added to
the state.Policy interface.

Methods for creating machines and assigning
units to them are now modified to use the
SupportsUnitPlacement method to enable
policy implementations to block adding of
empty machines (juju add-machine) and to
block unit placement (deploy/add-unit --to).

Also, a new EnvironBase type is added to
provider/common, which implements some
default behaviour common to all providers
such as the new SupportsUnitPlacement
method.

A followup will override the default
behaviour of SupportsUnitPlacement in the
Azure provider, blocking placement if in
availability-sets-enabled mode.

https://code.launchpad.net/~axwalk/juju-core/state-environcapability/+merge/211749

(do not edit description out of merge proposal)

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

Affected files (+232, -32 lines):
   A [revision details]
   M environs/interface.go
   M environs/statepolicy.go
   M provider/azure/environ.go
   A provider/common/environbase.go
   M provider/dummy/environs.go
   M provider/ec2/ec2.go
   M provider/joyent/environ.go
   M provider/local/environ.go
   M provider/maas/environ.go
   M provider/manual/environ.go
   M provider/openstack/provider.go
   M state/addmachine.go
   M state/conn_test.go
   A state/environcapability_test.go
   M state/policy.go
   M state/unit.go

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

LGTM. Don't quite love the "Base" name but I guess its semantic payload
is close enough to reality that I'm not going to fight it. If you can
think of something better, though...

https://codereview.appspot.com/77820044/

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

On 2014/03/19 15:15:17, fwereade wrote:
> LGTM. Don't quite love the "Base" name but I guess its semantic
payload is close
> enough to reality that I'm not going to fight it. If you can think of
something
> better, though...

You could user Common instead of Base altough I am not sure if it
represents correctly reality

https://codereview.appspot.com/77820044/

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

On 2014/03/19 18:37:43, hduran wrote:
> On 2014/03/19 15:15:17, fwereade wrote:
> > LGTM. Don't quite love the "Base" name but I guess its semantic
payload is
> close
> > enough to reality that I'm not going to fight it. If you can think
of
> something
> > better, though...

> You could user Common instead of Base altough I am not sure if it
represents
> correctly reality

Kind of, but not really. Some providers may override the behaviour.

After discussing with rogpeppe on IRC, I decided to replace EnvironBase
with two types: NopPrechecker and DoesSupportUnitPlacement. They're
more specific, and have non-generic names to imply non-changing
behaviour.
If we just use "DefaultPrechecker" then someone may change the default
behaviour without considering how it affects one of the providers.

https://codereview.appspot.com/77820044/

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

LGTM modulo further naming quibbles which I will leave to your judgment.

https://codereview.appspot.com/77820044/diff/20001/provider/azure/environ.go
File provider/azure/environ.go (right):

https://codereview.appspot.com/77820044/diff/20001/provider/azure/environ.go#newcode55
provider/azure/environ.go:55: common.DoesSupportUnitPlacement
Not honestly loving these names either (although they're certainly
better). Would you be OK tacking "Policy" onto those names to make their
intent a little clearer to the reader? Alternatively, tell me that's a
stupid idea, and go ahead with what you have.

https://codereview.appspot.com/77820044/

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

Please take a look.

https://codereview.appspot.com/77820044/diff/20001/provider/azure/environ.go
File provider/azure/environ.go (right):

https://codereview.appspot.com/77820044/diff/20001/provider/azure/environ.go#newcode55
provider/azure/environ.go:55: common.DoesSupportUnitPlacement
On 2014/03/24 09:45:57, fwereade wrote:
> Not honestly loving these names either (although they're certainly
better).
> Would you be OK tacking "Policy" onto those names to make their intent
a little
> clearer to the reader? Alternatively, tell me that's a stupid idea,
and go ahead
> with what you have.

Renamed NopPrechecker to NopPrecheckerPolicy, DoesSupportUnitPlacement
to SupportsUnitPlacementPolicy.

https://codereview.appspot.com/77820044/

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

On 2014/03/24 14:46:38, axw wrote:
> Please take a look.

https://codereview.appspot.com/77820044/diff/20001/provider/azure/environ.go
> File provider/azure/environ.go (right):

https://codereview.appspot.com/77820044/diff/20001/provider/azure/environ.go#newcode55
> provider/azure/environ.go:55: common.DoesSupportUnitPlacement
> On 2014/03/24 09:45:57, fwereade wrote:
> > Not honestly loving these names either (although they're certainly
better).
> > Would you be OK tacking "Policy" onto those names to make their
intent a
> little
> > clearer to the reader? Alternatively, tell me that's a stupid idea,
and go
> ahead
> > with what you have.

> Renamed NopPrechecker to NopPrecheckerPolicy, DoesSupportUnitPlacement
to
> SupportsUnitPlacementPolicy.

If that's all you did this still LGTM

https://codereview.appspot.com/77820044/

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 'agent/bootstrap_test.go'
2--- agent/bootstrap_test.go 2014-04-02 15:30:58 +0000
3+++ agent/bootstrap_test.go 2014-04-03 03:38:42 +0000
4@@ -12,6 +12,7 @@
5 "launchpad.net/juju-core/environs"
6 "launchpad.net/juju-core/environs/config"
7 "launchpad.net/juju-core/instance"
8+ "launchpad.net/juju-core/provider/dummy"
9 "launchpad.net/juju-core/state"
10 "launchpad.net/juju-core/state/api/params"
11 "launchpad.net/juju-core/testing"
12@@ -81,8 +82,9 @@
13 Characteristics: expectHW,
14 SharedSecret: "abc123",
15 }
16- envAttrs := testing.FakeConfig().Delete("admin-secret").Merge(testing.Attrs{
17+ envAttrs := dummy.SampleConfig().Delete("admin-secret").Merge(testing.Attrs{
18 "agent-version": version.Current.Number.String(),
19+ "state-id": "1", // needed so policy can Open config
20 })
21 envCfg, err := config.New(config.NoDefaults, envAttrs)
22 c.Assert(err, gc.IsNil)
23@@ -193,8 +195,9 @@
24 InstanceId: "i-bootstrap",
25 Characteristics: expectHW,
26 }
27- envAttrs := testing.FakeConfig().Delete("admin-secret").Merge(testing.Attrs{
28+ envAttrs := dummy.SampleConfig().Delete("admin-secret").Merge(testing.Attrs{
29 "agent-version": version.Current.Number.String(),
30+ "state-id": "1", // needed so policy can Open config
31 })
32 envCfg, err := config.New(config.NoDefaults, envAttrs)
33 c.Assert(err, gc.IsNil)
34
35=== modified file 'environs/interface.go'
36--- environs/interface.go 2014-04-02 11:35:49 +0000
37+++ environs/interface.go 2014-04-03 03:38:42 +0000
38@@ -15,18 +15,6 @@
39 "launchpad.net/juju-core/state/api"
40 )
41
42-// EnvironCapability implements access to metadata about the capabilities
43-// of an environment.
44-type EnvironCapability interface {
45- // SupportedArchitectures returns the image architectures which can
46- // be hosted by this environment.
47- SupportedArchitectures() ([]string, error)
48-
49- // SupportNetworks returns whether the environment has support to
50- // specify networks for services and machines.
51- SupportNetworks() bool
52-}
53-
54 // A EnvironProvider represents a computing and storage provider.
55 type EnvironProvider interface {
56 // Prepare prepares an environment for use. Any additional
57@@ -123,7 +111,7 @@
58 ConfigGetter
59
60 // EnvironCapability allows access to this environment's capabilities.
61- EnvironCapability
62+ state.EnvironCapability
63
64 // SetConfig updates the Environ's configuration.
65 //
66@@ -168,9 +156,7 @@
67 // Provider returns the EnvironProvider that created this Environ.
68 Provider() EnvironProvider
69
70- // TODO(axw) 2014-02-11 #pending-review
71- // Embed state.Prechecker, and introduce an EnvironBase
72- // that embeds a no-op prechecker implementation.
73+ state.Prechecker
74 }
75
76 // BootstrapContext is an interface that is passed to
77
78=== modified file 'environs/statepolicy.go'
79--- environs/statepolicy.go 2014-03-25 13:01:16 +0000
80+++ environs/statepolicy.go 2014-04-03 03:38:42 +0000
81@@ -5,7 +5,6 @@
82
83 import (
84 "launchpad.net/juju-core/environs/config"
85- "launchpad.net/juju-core/errors"
86 "launchpad.net/juju-core/state"
87 )
88
89@@ -23,17 +22,16 @@
90 }
91
92 func (environStatePolicy) Prechecker(cfg *config.Config) (state.Prechecker, error) {
93- env, err := New(cfg)
94- if err != nil {
95- return nil, err
96- }
97-
98- if p, ok := env.(state.Prechecker); ok {
99- return p, nil
100- }
101- return nil, errors.NewNotImplementedError("Prechecker")
102+ // Environ implements state.Prechecker.
103+ return New(cfg)
104 }
105
106 func (environStatePolicy) ConfigValidator(providerType string) (state.ConfigValidator, error) {
107+ // EnvironProvider implements state.ConfigValidator.
108 return Provider(providerType)
109 }
110+
111+func (environStatePolicy) EnvironCapability(cfg *config.Config) (state.EnvironCapability, error) {
112+ // Environ implements state.EnvironCapability.
113+ return New(cfg)
114+}
115
116=== modified file 'provider/azure/environ.go'
117--- provider/azure/environ.go 2014-04-02 07:44:08 +0000
118+++ provider/azure/environ.go 2014-04-03 03:38:42 +0000
119@@ -51,6 +51,9 @@
120 )
121
122 type azureEnviron struct {
123+ common.NopPrecheckerPolicy
124+ common.SupportsUnitPlacementPolicy
125+
126 // Except where indicated otherwise, all fields in this object should
127 // only be accessed using a lock or a snapshot.
128 sync.Mutex
129
130=== added file 'provider/common/policies.go'
131--- provider/common/policies.go 1970-01-01 00:00:00 +0000
132+++ provider/common/policies.go 2014-04-03 03:38:42 +0000
133@@ -0,0 +1,30 @@
134+// Copyright 2014 Canonical Ltd.
135+// Licensed under the AGPLv3, see LICENCE file for details.
136+
137+package common
138+
139+import (
140+ "launchpad.net/juju-core/constraints"
141+ "launchpad.net/juju-core/state"
142+)
143+
144+// SupportsUnitPlacementPolicy provides an
145+// implementation of SupportsUnitPlacement
146+// that never returns an error, and is
147+// intended for embedding in environs.Environ
148+// implementations.
149+type SupportsUnitPlacementPolicy struct{}
150+
151+func (*SupportsUnitPlacementPolicy) SupportsUnitPlacement() error {
152+ return nil
153+}
154+
155+// NopPrecheckerPolicy provides an implementation of the
156+// state.Prechecker interface that passes all checks.
157+type NopPrecheckerPolicy struct{}
158+
159+var _ state.Prechecker = (*NopPrecheckerPolicy)(nil)
160+
161+func (*NopPrecheckerPolicy) PrecheckInstance(series string, cons constraints.Value) error {
162+ return nil
163+}
164
165=== modified file 'provider/dummy/environs.go'
166--- provider/dummy/environs.go 2014-04-02 11:35:49 +0000
167+++ provider/dummy/environs.go 2014-04-03 03:38:42 +0000
168@@ -182,6 +182,9 @@
169 // environ represents a client's connection to a given environment's
170 // state.
171 type environ struct {
172+ common.NopPrecheckerPolicy
173+ common.SupportsUnitPlacementPolicy
174+
175 name string
176 ecfgMutex sync.Mutex
177 ecfgUnlocked *environConfig
178
179=== modified file 'provider/ec2/ec2.go'
180--- provider/ec2/ec2.go 2014-04-02 11:35:49 +0000
181+++ provider/ec2/ec2.go 2014-04-03 03:38:42 +0000
182@@ -47,6 +47,9 @@
183 var providerInstance environProvider
184
185 type environ struct {
186+ common.NopPrecheckerPolicy
187+ common.SupportsUnitPlacementPolicy
188+
189 name string
190
191 // archMutex gates access to supportedArchitectures
192
193=== modified file 'provider/joyent/environ.go'
194--- provider/joyent/environ.go 2014-04-02 11:35:49 +0000
195+++ provider/joyent/environ.go 2014-04-03 03:38:42 +0000
196@@ -20,6 +20,9 @@
197 // This file contains the core of the Joyent Environ implementation.
198
199 type joyentEnviron struct {
200+ common.NopPrecheckerPolicy
201+ common.SupportsUnitPlacementPolicy
202+
203 name string
204
205 // supportedArchitectures caches the architectures
206
207=== modified file 'provider/local/environ.go'
208--- provider/local/environ.go 2014-04-02 07:44:08 +0000
209+++ provider/local/environ.go 2014-04-03 03:38:42 +0000
210@@ -56,6 +56,9 @@
211 var _ envtools.SupportsCustomSources = (*localEnviron)(nil)
212
213 type localEnviron struct {
214+ common.NopPrecheckerPolicy
215+ common.SupportsUnitPlacementPolicy
216+
217 localMutex sync.Mutex
218 config *environConfig
219 name string
220
221=== modified file 'provider/maas/environ.go'
222--- provider/maas/environ.go 2014-04-02 07:50:15 +0000
223+++ provider/maas/environ.go 2014-04-03 03:38:42 +0000
224@@ -47,6 +47,9 @@
225 }
226
227 type maasEnviron struct {
228+ common.NopPrecheckerPolicy
229+ common.SupportsUnitPlacementPolicy
230+
231 name string
232
233 // archMutex gates access to supportedArchitectures
234
235=== modified file 'provider/manual/environ.go'
236--- provider/manual/environ.go 2014-04-03 02:57:11 +0000
237+++ provider/manual/environ.go 2014-04-03 03:38:42 +0000
238@@ -49,6 +49,8 @@
239 var logger = loggo.GetLogger("juju.provider.manual")
240
241 type manualEnviron struct {
242+ common.SupportsUnitPlacementPolicy
243+
244 cfg *environConfig
245 cfgmutex sync.Mutex
246 storage storage.Storage
247
248=== modified file 'provider/openstack/provider.go'
249--- provider/openstack/provider.go 2014-04-02 11:35:49 +0000
250+++ provider/openstack/provider.go 2014-04-03 03:38:42 +0000
251@@ -275,6 +275,9 @@
252 }
253
254 type environ struct {
255+ common.NopPrecheckerPolicy
256+ common.SupportsUnitPlacementPolicy
257+
258 name string
259
260 // archMutex gates access to supportedArchitectures
261
262=== modified file 'state/addmachine.go'
263--- state/addmachine.go 2014-04-03 02:57:11 +0000
264+++ state/addmachine.go 2014-04-03 03:38:42 +0000
265@@ -133,6 +133,13 @@
266 var ops []txn.Op
267 var mdocs []*machineDoc
268 for _, template := range templates {
269+ // Adding a machine without any principals is
270+ // only permitted unit placement is supported.
271+ if len(template.principals) == 0 {
272+ if err := st.supportsUnitPlacement(); err != nil {
273+ return nil, err
274+ }
275+ }
276 mdoc, addOps, err := st.addMachineOps(template)
277 if err != nil {
278 return nil, err
279@@ -292,6 +299,10 @@
280 if containerType == "" {
281 return nil, nil, fmt.Errorf("no container type specified")
282 }
283+ // Adding a machine within a machine implies add-machine or placement.
284+ if err := st.supportsUnitPlacement(); err != nil {
285+ return nil, nil, err
286+ }
287
288 // If a parent machine is specified, make sure it exists
289 // and can support the requested container type.
290@@ -352,6 +363,10 @@
291 if err := st.precheckInstance(parentTemplate.Series, parentTemplate.Constraints); err != nil {
292 return nil, nil, err
293 }
294+ // Adding a machine within a machine implies add-machine or placement.
295+ if err := st.supportsUnitPlacement(); err != nil {
296+ return nil, nil, err
297+ }
298 }
299
300 parentDoc := machineDocForTemplate(parentTemplate, strconv.Itoa(seq))
301
302=== modified file 'state/conn_test.go'
303--- state/conn_test.go 2014-03-25 14:51:19 +0000
304+++ state/conn_test.go 2014-04-03 03:38:42 +0000
305@@ -101,8 +101,9 @@
306 }
307
308 type mockPolicy struct {
309- getPrechecker func(*config.Config) (state.Prechecker, error)
310- getConfigValidator func(string) (state.ConfigValidator, error)
311+ getPrechecker func(*config.Config) (state.Prechecker, error)
312+ getConfigValidator func(string) (state.ConfigValidator, error)
313+ getEnvironCapability func(*config.Config) (state.EnvironCapability, error)
314 }
315
316 func (p *mockPolicy) Prechecker(cfg *config.Config) (state.Prechecker, error) {
317@@ -118,3 +119,10 @@
318 }
319 return nil, errors.NewNotImplementedError("ConfigValidator")
320 }
321+
322+func (p *mockPolicy) EnvironCapability(cfg *config.Config) (state.EnvironCapability, error) {
323+ if p.getEnvironCapability != nil {
324+ return p.getEnvironCapability(cfg)
325+ }
326+ return nil, errors.NewNotImplementedError("EnvironCapability")
327+}
328
329=== added file 'state/environcapability_test.go'
330--- state/environcapability_test.go 1970-01-01 00:00:00 +0000
331+++ state/environcapability_test.go 2014-04-03 03:38:42 +0000
332@@ -0,0 +1,116 @@
333+// Copyright 2014 Canonical Ltd.
334+// Licensed under the AGPLv3, see LICENCE file for details.
335+
336+package state_test
337+
338+import (
339+ "fmt"
340+
341+ gc "launchpad.net/gocheck"
342+
343+ "launchpad.net/juju-core/environs/config"
344+ "launchpad.net/juju-core/errors"
345+ "launchpad.net/juju-core/instance"
346+ "launchpad.net/juju-core/state"
347+)
348+
349+type EnvironCapabilitySuite struct {
350+ ConnSuite
351+ capability mockEnvironCapability
352+}
353+
354+var _ = gc.Suite(&EnvironCapabilitySuite{})
355+
356+type mockEnvironCapability struct {
357+ supportsUnitPlacementError error
358+}
359+
360+func (p *mockEnvironCapability) SupportedArchitectures() ([]string, error) {
361+ panic("unused")
362+}
363+
364+func (p *mockEnvironCapability) SupportNetworks() bool {
365+ panic("unused")
366+}
367+
368+func (p *mockEnvironCapability) SupportsUnitPlacement() error {
369+ return p.supportsUnitPlacementError
370+}
371+
372+func (s *EnvironCapabilitySuite) SetUpTest(c *gc.C) {
373+ s.ConnSuite.SetUpTest(c)
374+ s.capability = mockEnvironCapability{}
375+ s.policy.getEnvironCapability = func(*config.Config) (state.EnvironCapability, error) {
376+ return &s.capability, nil
377+ }
378+}
379+
380+func (s *EnvironCapabilitySuite) addOneMachine(c *gc.C) (*state.Machine, error) {
381+ return s.State.AddOneMachine(state.MachineTemplate{
382+ Series: "quantal",
383+ Jobs: []state.MachineJob{state.JobHostUnits},
384+ })
385+}
386+
387+func (s *EnvironCapabilitySuite) addMachineInsideNewMachine(c *gc.C) error {
388+ template := state.MachineTemplate{
389+ Series: "quantal",
390+ Jobs: []state.MachineJob{state.JobHostUnits},
391+ }
392+ _, err := s.State.AddMachineInsideNewMachine(template, template, instance.LXC)
393+ return err
394+}
395+
396+func (s *EnvironCapabilitySuite) TestSupportsUnitPlacementAddMachine(c *gc.C) {
397+ // Ensure that AddOneMachine fails when PrecheckInstance returns an error.
398+ s.capability.supportsUnitPlacementError = fmt.Errorf("no add-machine for you")
399+ _, err := s.addOneMachine(c)
400+ c.Assert(err, gc.ErrorMatches, ".*no add-machine for you")
401+ err = s.addMachineInsideNewMachine(c)
402+ c.Assert(err, gc.ErrorMatches, ".*no add-machine for you")
403+ // If the policy's EnvironCapability method fails, that will be returned first.
404+ s.policy.getEnvironCapability = func(*config.Config) (state.EnvironCapability, error) {
405+ return nil, fmt.Errorf("incapable of EnvironCapability")
406+ }
407+ _, err = s.addOneMachine(c)
408+ c.Assert(err, gc.ErrorMatches, ".*incapable of EnvironCapability")
409+}
410+
411+func (s *EnvironCapabilitySuite) TestSupportsUnitPlacementUnitAssignment(c *gc.C) {
412+ m, err := s.addOneMachine(c)
413+ c.Assert(err, gc.IsNil)
414+
415+ charm := s.AddTestingCharm(c, "wordpress")
416+ service := s.AddTestingService(c, "wordpress", charm)
417+ unit, err := service.AddUnit()
418+ c.Assert(err, gc.IsNil)
419+
420+ s.capability.supportsUnitPlacementError = fmt.Errorf("no unit placement for you")
421+ err = unit.AssignToMachine(m)
422+ c.Assert(err, gc.ErrorMatches, ".*no unit placement for you")
423+
424+ err = unit.AssignToNewMachine()
425+ c.Assert(err, gc.IsNil)
426+}
427+
428+func (s *EnvironCapabilitySuite) TestEnvironCapabilityUnimplemented(c *gc.C) {
429+ var capabilityErr error
430+ s.policy.getEnvironCapability = func(*config.Config) (state.EnvironCapability, error) {
431+ return nil, capabilityErr
432+ }
433+ _, err := s.addOneMachine(c)
434+ c.Assert(err, gc.ErrorMatches, "cannot add a new machine: policy returned nil EnvironCapability without an error")
435+ capabilityErr = errors.NewNotImplementedError("EnvironCapability")
436+ _, err = s.addOneMachine(c)
437+ c.Assert(err, gc.IsNil)
438+}
439+
440+func (s *EnvironCapabilitySuite) TestSupportsUnitPlacementNoPolicy(c *gc.C) {
441+ s.policy.getEnvironCapability = func(*config.Config) (state.EnvironCapability, error) {
442+ c.Errorf("should not have been invoked")
443+ return nil, nil
444+ }
445+ state.SetPolicy(s.State, nil)
446+ _, err := s.addOneMachine(c)
447+ c.Assert(err, gc.IsNil)
448+}
449
450=== modified file 'state/policy.go'
451--- state/policy.go 2014-04-03 02:57:11 +0000
452+++ state/policy.go 2014-04-03 03:38:42 +0000
453@@ -25,9 +25,13 @@
454 // a (possibly nil) Prechecker or an error.
455 Prechecker(*config.Config) (Prechecker, error)
456
457- // ConfigValidator takes a string (environ type) and returns
458+ // ConfigValidator takes a provider type name and returns
459 // a (possibly nil) ConfigValidator or an error.
460- ConfigValidator(string) (ConfigValidator, error)
461+ ConfigValidator(providerType string) (ConfigValidator, error)
462+
463+ // EnvironCapability takes a *config.Config and returns
464+ // a (possibly nil) EnvironCapability or an error.
465+ EnvironCapability(*config.Config) (EnvironCapability, error)
466 }
467
468 // Prechecker is a policy interface that is provided to State
469@@ -50,6 +54,24 @@
470 Validate(cfg, old *config.Config) (valid *config.Config, err error)
471 }
472
473+// EnvironCapability implements access to metadata about the capabilities
474+// of an environment.
475+type EnvironCapability interface {
476+ // SupportedArchitectures returns the image architectures which can
477+ // be hosted by this environment.
478+ SupportedArchitectures() ([]string, error)
479+
480+ // SupportNetworks returns whether the environment has support to
481+ // specify networks for services and machines.
482+ SupportNetworks() bool
483+
484+ // SupportsUnitAssignment returns an error which, if non-nil, indicates
485+ // that the environment does not support unit placement. If the environment
486+ // does not support unit placement, then machines may not be created
487+ // without units, and units cannot be placed explcitly.
488+ SupportsUnitPlacement() error
489+}
490+
491 // precheckInstance calls the state's assigned policy, if non-nil, to obtain
492 // a Prechecker, and calls PrecheckInstance if a non-nil Prechecker is returned.
493 func (st *State) precheckInstance(series string, cons constraints.Value) error {
494@@ -73,7 +95,8 @@
495 }
496
497 // validate calls the state's assigned policy, if non-nil, to obtain
498-// a Validator, and calls validate if a non-nil Validator is returned.
499+// a ConfigValidator, and calls Validate if a non-nil ConfigValidator is
500+// returned.
501 func (st *State) validate(cfg, old *config.Config) (valid *config.Config, err error) {
502 if st.policy == nil {
503 return cfg, nil
504@@ -89,3 +112,26 @@
505 }
506 return configValidator.Validate(cfg, old)
507 }
508+
509+// supportsUnitPlacement calls the state's assigned policy, if non-nil,
510+// to obtain an EnvironCapability, and calls SupportsUnitPlacement if a
511+// non-nil EnvironCapability is returned.
512+func (st *State) supportsUnitPlacement() error {
513+ if st.policy == nil {
514+ return nil
515+ }
516+ cfg, err := st.EnvironConfig()
517+ if err != nil {
518+ return err
519+ }
520+ capability, err := st.policy.EnvironCapability(cfg)
521+ if errors.IsNotImplementedError(err) {
522+ return nil
523+ } else if err != nil {
524+ return err
525+ }
526+ if capability == nil {
527+ return fmt.Errorf("policy returned nil EnvironCapability without an error")
528+ }
529+ return capability.SupportsUnitPlacement()
530+}
531
532=== modified file 'state/unit.go'
533--- state/unit.go 2014-04-02 11:35:49 +0000
534+++ state/unit.go 2014-04-03 03:38:42 +0000
535@@ -823,6 +823,11 @@
536 if !canHost {
537 return fmt.Errorf("machine %q cannot host units", m)
538 }
539+ // assignToMachine implies assignment to an existing machine,
540+ // which is only permitted if unit placement is supported.
541+ if err := u.st.supportsUnitPlacement(); err != nil {
542+ return err
543+ }
544 assert := append(isAliveDoc, bson.D{
545 {"$or", []bson.D{
546 {{"machineid", ""}},

Subscribers

People subscribed via source and target branches

to status/vote changes: