Merge lp:~wallyworld/juju-core/instance-type-constraint into lp:~go-bot/juju-core/trunk
- instance-type-constraint
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Ian Booth | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 2659 | ||||
Proposed branch: | lp:~wallyworld/juju-core/instance-type-constraint | ||||
Merge into: | lp:~go-bot/juju-core/trunk | ||||
Prerequisite: | lp:~wallyworld/juju-core/constraints-validation-merge | ||||
Diff against target: |
1085 lines (+562/-52) 31 files modified
cmd/juju/bootstrap.go (+8/-0) cmd/juju/bootstrap_test.go (+8/-0) environs/interface.go (+4/-0) environs/statepolicy.go (+9/-0) provider/azure/environ.go (+12/-0) provider/azure/environ_test.go (+9/-0) provider/dummy/environs.go (+8/-0) provider/ec2/ec2.go (+14/-0) provider/ec2/local_test.go (+19/-0) provider/joyent/environ_instance.go (+13/-0) provider/joyent/local_test.go (+9/-0) provider/local/environ.go (+14/-0) provider/local/environ_test.go (+11/-0) provider/maas/environ.go (+12/-0) provider/maas/environ_test.go (+12/-0) provider/manual/environ.go (+13/-0) provider/manual/environ_test.go (+9/-0) provider/openstack/local_test.go (+19/-0) provider/openstack/provider.go (+15/-0) state/addmachine.go (+18/-22) state/conn_test.go (+12/-3) state/constraints.go (+24/-21) state/constraintsvalidation_test.go (+104/-0) state/export_test.go (+5/-0) state/machine.go (+7/-0) state/machine_test.go (+37/-0) state/policy.go (+51/-0) state/service.go (+9/-6) state/service_test.go (+33/-0) state/state.go (+7/-0) state/state_test.go (+37/-0) |
||||
To merge this branch: | bzr merge lp:~wallyworld/juju-core/instance-type-constraint | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+216244@code.launchpad.net |
Commit message
Add constraints validation to providers
Each provider has a constraints validator which
is used when setting constraints on a machine or
service, as well as when constraints are merged.
The validation step allows conflicting constraints
like instance-type and mem to be rejected, and also
unsupported constraints to be logged with a warning.
The merge step allows things like instance-type to
mask other incompatible constraints like mem or arch,
and visa versa.
Description of the change
Add constraints validation to providers
Each provider has a constraints validator which
is used when setting constraints on a machine or
service, as well as when constraints are merged.
The validation step allows conflicting constraints
like instance-type and mem to be rejected, and also
unsupported constraints to be logged with a warning.
The merge step allows things like instance-type to
mask other incompatible constraints like mem or arch,
and visa versa.
Ian Booth (wallyworld) wrote : | # |
Ian Booth (wallyworld) wrote : | # |
Please take a look.
Ian Booth (wallyworld) wrote : | # |
Please take a look.
William Reade (fwereade) wrote : | # |
I think there's a SetEnvironConst
validated, and I'm a bit uncertain about some of the what-we-support
choices; but this is generally looking very solid. Thanks.
I also think we need some more integration with the Prechecker -- those
probably ought to check the constraints internally -- but that's fine as
a followup.
https:/
File cmd/juju/
https:/
cmd/juju/
Not for this CL, but we really do need a channel by which we can pass
this sort of warning back to a user on the other side of the API
(bootstrap is fine, I know, but all the other times....).
https:/
File provider/ec2/ec2.go (right):
https:/
provider/
constraints.
CpuPower too, I think
https:/
File provider/
https:/
provider/
constraints.
Ideally instance-type's value will itself be validated here -- such that
cc2.x8large is rejected in the regions that don't provide them, etc etc.
That'd be reasonable as a followup, possibly alongside the API that
lists available instance-type values (also maybe good to do the same for
arch?).
https:/
File provider/
https:/
provider/
Really, mem and cpu-cores are basically unsupported too (even if we
*could* do something with them in some cases).
This seems fine as it is (progress not perfection) but we might want a
followup that massages some of these a bit.
https:/
File provider/
https:/
provider/
Not sure about this. Shouldn't these all be supported? Constraints for
add-machine are a bit weird, but constraints for deploying service units
on particular machines absolutely should be accepted and matched against
the machine's hardware characteristics, I think.
https:/
File state/machine.go (right):
https:/
state/machine.
%q: %v", m.Tag(), err)
m.Id()?
Ian Booth (wallyworld) wrote : | # |
On 2014/04/18 08:25:04, fwereade wrote:
> I think there's a SetEnvironConst
validated,
> and I'm a bit uncertain about some of the what-we-support choices; but
this is
> generally looking very solid. Thanks.
I added checks to the SetEnvironConst
> I also think we need some more integration with the Prechecker --
those probably
> ought to check the constraints internally -- but that's fine as a
followup.
Next branch adds stuff to Prechecker.
Ian Booth (wallyworld) wrote : | # |
Please take a look.
https:/
File provider/ec2/ec2.go (right):
https:/
provider/
constraints.
On 2014/04/18 08:25:04, fwereade wrote:
> CpuPower too, I think
Done.
https:/
File provider/
https:/
provider/
constraints.
On 2014/04/18 08:25:04, fwereade wrote:
> Ideally instance-type's value will itself be validated here -- such
that
> cc2.x8large is rejected in the regions that don't provide them, etc
etc. That'd
> be reasonable as a followup, possibly alongside the API that lists
available
> instance-type values (also maybe good to do the same for arch?).
The instance-type check is done in the next branch as part of the
PrecheckInstance() step. I thought about doing it on the
constraints.
specific behaviour into the constraints package and we already have the
pre check stuff that will do the job, and will cause an error before a
new machine record is added to state. It won't prevent service
constraints from being set with a bad instance type however. But I
thought the implementation chosen is a viable approach nonetheless.
https:/
File provider/
https:/
provider/
On 2014/04/18 08:25:04, fwereade wrote:
> Really, mem and cpu-cores are basically unsupported too (even if we
*could* do
> something with them in some cases).
> This seems fine as it is (progress not perfection) but we might want a
followup
> that massages some of these a bit.
I think we should support mem, and I recall that we actually use it to
set the container size (or were going to). I'll add cpu cores to the
list though.
https:/
File provider/
https:/
provider/
On 2014/04/18 08:25:04, fwereade wrote:
> Not sure about this. Shouldn't these all be supported? Constraints for
> add-machine are a bit weird, but constraints for deploying service
units on
> particular machines absolutely should be accepted and matched against
the
> machine's hardware characteristics, I think.
Tags I don't think we need, nor instance-type or cpu-power. I'll remove
the rest.
https:/
File state/machine.go (right):
https:/
state/machine.
%q: %v", m.Tag(), err)
On 2014...
William Reade (fwereade) wrote : | # |
LGTM if you promise to do a followup that implements constraint
vocabularies ;).
https:/
File provider/
https:/
provider/
constraints.
On 2014/04/18 13:51:19, wallyworld wrote:
> On 2014/04/18 08:25:04, fwereade wrote:
> > Ideally instance-type's value will itself be validated here -- such
that
> > cc2.x8large is rejected in the regions that don't provide them, etc
etc.
> That'd
> > be reasonable as a followup, possibly alongside the API that lists
available
> > instance-type values (also maybe good to do the same for arch?).
> The instance-type check is done in the next branch as part of the
> PrecheckInstance() step. I thought about doing it on the
constraints.
> step but this would involve plugging provider specific behaviour into
the
> constraints package and we already have the pre check stuff that will
do the
> job, and will cause an error before a new machine record is added to
state. It
> won't prevent service constraints from being set with a bad instance
type
> however. But I thought the implementation chosen is a viable approach
> nonetheless.
Hmm. ISTM that there are several, uh, vocab-constrained constraints --
arch, instance-type, and tags -- and each of them vary by cloud, and
it'll almost certainly be to our benefit to be able to reject the crazy
ones before they get into state anywhere at all -- I'm really quite
concerned about the delay between the bad specification and the actual
error reporting.
It can go into a followup, this clearly remains a good direction, but I
think we do need to be able to fail fast on those values. Some of them
are potentially tricky, ofc -- the set of arches available depends on
the juju tools available, and the images, but we've at least partly
addressed that issue already iirc.
https:/
File state/service.go (right):
https:/
state/service.
I'm also bothered that we don't have any way to communicate this back to
the user [who isn't running debug-log, at least]. Out of scope, but
irritating.
Go Bot (go-bot) wrote : | # |
The attempt to merge lp:~wallyworld/juju-core/instance-type-constraint into lp:juju-core failed. Below is the output from the failed tests.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
? launchpad.
? launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
? launchpad.
Preview Diff
1 | === modified file 'cmd/juju/bootstrap.go' |
2 | --- cmd/juju/bootstrap.go 2014-04-09 15:48:45 +0000 |
3 | +++ cmd/juju/bootstrap.go 2014-04-21 23:25:35 +0000 |
4 | @@ -101,6 +101,14 @@ |
5 | if err != nil { |
6 | return err |
7 | } |
8 | + validator := environ.ConstraintsValidator() |
9 | + unsupported, err := validator.Validate(c.Constraints) |
10 | + if len(unsupported) > 0 { |
11 | + logger.Warningf("unsupported constraints: %v", err) |
12 | + } else if err != nil { |
13 | + return err |
14 | + } |
15 | + |
16 | defer cleanup() |
17 | if err := bootstrap.EnsureNotBootstrapped(environ); err != nil { |
18 | return err |
19 | |
20 | === modified file 'cmd/juju/bootstrap_test.go' |
21 | --- cmd/juju/bootstrap_test.go 2014-04-17 12:57:32 +0000 |
22 | +++ cmd/juju/bootstrap_test.go 2014-04-21 23:25:35 +0000 |
23 | @@ -295,6 +295,10 @@ |
24 | args: []string{"--constraints", "bad=wrong"}, |
25 | err: `invalid value "bad=wrong" for flag --constraints: unknown constraint "bad"`, |
26 | }, { |
27 | + info: "conflicting --constraints", |
28 | + args: []string{"--constraints", "instance-type=foo mem=4G"}, |
29 | + err: `ambiguous constraints: "mem" overlaps with "instance-type"`, |
30 | +}, { |
31 | info: "bad --series", |
32 | args: []string{"--series", "1bad1"}, |
33 | err: `invalid value "1bad1" for flag --series: invalid series name "1bad1"`, |
34 | @@ -312,6 +316,10 @@ |
35 | args: []string{"--constraints", "mem=4G cpu-cores=4"}, |
36 | constraints: constraints.MustParse("mem=4G cpu-cores=4"), |
37 | }, { |
38 | + info: "unsupported constraint passed through but no error", |
39 | + args: []string{"--constraints", "mem=4G cpu-cores=4 cpu-power=10"}, |
40 | + constraints: constraints.MustParse("mem=4G cpu-cores=4 cpu-power=10"), |
41 | +}, { |
42 | info: "--upload-tools picks all reasonable series", |
43 | version: "1.2.3-saucy-amd64", |
44 | args: []string{"--upload-tools"}, |
45 | |
46 | === modified file 'environs/interface.go' |
47 | --- environs/interface.go 2014-04-03 03:37:45 +0000 |
48 | +++ environs/interface.go 2014-04-21 23:25:35 +0000 |
49 | @@ -113,6 +113,10 @@ |
50 | // EnvironCapability allows access to this environment's capabilities. |
51 | state.EnvironCapability |
52 | |
53 | + // ConstraintsValidator returns a Validator instance which |
54 | + // is used to validate and merge constraints. |
55 | + ConstraintsValidator() constraints.Validator |
56 | + |
57 | // SetConfig updates the Environ's configuration. |
58 | // |
59 | // Calls to SetConfig do not affect the configuration of |
60 | |
61 | === modified file 'environs/statepolicy.go' |
62 | --- environs/statepolicy.go 2014-04-03 03:37:45 +0000 |
63 | +++ environs/statepolicy.go 2014-04-21 23:25:35 +0000 |
64 | @@ -4,6 +4,7 @@ |
65 | package environs |
66 | |
67 | import ( |
68 | + "launchpad.net/juju-core/constraints" |
69 | "launchpad.net/juju-core/environs/config" |
70 | "launchpad.net/juju-core/state" |
71 | ) |
72 | @@ -35,3 +36,11 @@ |
73 | // Environ implements state.EnvironCapability. |
74 | return New(cfg) |
75 | } |
76 | + |
77 | +func (environStatePolicy) ConstraintsValidator(cfg *config.Config) (constraints.Validator, error) { |
78 | + env, err := New(cfg) |
79 | + if err != nil { |
80 | + return nil, err |
81 | + } |
82 | + return env.ConstraintsValidator(), nil |
83 | +} |
84 | |
85 | === modified file 'provider/azure/environ.go' |
86 | --- provider/azure/environ.go 2014-04-18 16:37:28 +0000 |
87 | +++ provider/azure/environ.go 2014-04-21 23:25:35 +0000 |
88 | @@ -422,6 +422,18 @@ |
89 | return spec.InstanceType.Id, spec.Image.Id, nil |
90 | } |
91 | |
92 | +var unsupportedConstraints = []string{ |
93 | + constraints.CpuPower, |
94 | + constraints.Tags, |
95 | +} |
96 | + |
97 | +// ConstraintsValidator is defined on the Environs interface. |
98 | +func (environ *azureEnviron) ConstraintsValidator() constraints.Validator { |
99 | + validator := constraints.NewValidator() |
100 | + validator.RegisterUnsupported(unsupportedConstraints) |
101 | + return validator |
102 | +} |
103 | + |
104 | // createInstance creates all of the Azure entities necessary for a |
105 | // new instance. This includes Cloud Service, Deployment and Role. |
106 | // |
107 | |
108 | === modified file 'provider/azure/environ_test.go' |
109 | --- provider/azure/environ_test.go 2014-04-11 17:51:58 +0000 |
110 | +++ provider/azure/environ_test.go 2014-04-21 23:25:35 +0000 |
111 | @@ -1518,3 +1518,12 @@ |
112 | _, stateServer = s.startInstance(c) |
113 | c.Assert(stateServer, jc.IsTrue) |
114 | } |
115 | + |
116 | +func (s *environSuite) TestConstraintsValidator(c *gc.C) { |
117 | + env := makeEnviron(c) |
118 | + validator := env.ConstraintsValidator() |
119 | + cons := constraints.MustParse("arch=amd64 tags=bar cpu-power=10") |
120 | + unsupported, err := validator.Validate(cons) |
121 | + c.Assert(err, gc.IsNil) |
122 | + c.Assert(unsupported, gc.DeepEquals, []string{"cpu-power", "tags"}) |
123 | +} |
124 | |
125 | === modified file 'provider/dummy/environs.go' |
126 | --- provider/dummy/environs.go 2014-04-18 16:37:28 +0000 |
127 | +++ provider/dummy/environs.go 2014-04-21 23:25:35 +0000 |
128 | @@ -693,6 +693,14 @@ |
129 | return nil |
130 | } |
131 | |
132 | +// ConstraintsValidator is defined on the Environs interface. |
133 | +func (e *environ) ConstraintsValidator() constraints.Validator { |
134 | + validator := constraints.NewValidator() |
135 | + validator.RegisterUnsupported([]string{constraints.CpuPower}) |
136 | + validator.RegisterConflicts([]string{constraints.InstanceType}, []string{constraints.Mem}) |
137 | + return validator |
138 | +} |
139 | + |
140 | // StartInstance is specified in the InstanceBroker interface. |
141 | func (e *environ) StartInstance(args environs.StartInstanceParams) (instance.Instance, *instance.HardwareCharacteristics, []network.Info, error) { |
142 | |
143 | |
144 | === modified file 'provider/ec2/ec2.go' |
145 | --- provider/ec2/ec2.go 2014-04-18 16:37:28 +0000 |
146 | +++ provider/ec2/ec2.go 2014-04-21 23:25:35 +0000 |
147 | @@ -358,6 +358,20 @@ |
148 | return false |
149 | } |
150 | |
151 | +var unsupportedConstraints = []string{ |
152 | + constraints.Tags, |
153 | +} |
154 | + |
155 | +// ConstraintsValidator is defined on the Environs interface. |
156 | +func (e *environ) ConstraintsValidator() constraints.Validator { |
157 | + validator := constraints.NewValidator() |
158 | + validator.RegisterConflicts( |
159 | + []string{constraints.InstanceType}, |
160 | + []string{constraints.Mem, constraints.CpuCores, constraints.CpuPower}) |
161 | + validator.RegisterUnsupported(unsupportedConstraints) |
162 | + return validator |
163 | +} |
164 | + |
165 | // MetadataLookupParams returns parameters which are used to query simplestreams metadata. |
166 | func (e *environ) MetadataLookupParams(region string) (*simplestreams.MetadataLookupParams, error) { |
167 | if region == "" { |
168 | |
169 | === modified file 'provider/ec2/local_test.go' |
170 | --- provider/ec2/local_test.go 2014-04-09 16:36:12 +0000 |
171 | +++ provider/ec2/local_test.go 2014-04-21 23:25:35 +0000 |
172 | @@ -359,6 +359,25 @@ |
173 | } |
174 | } |
175 | |
176 | +func (t *localServerSuite) TestConstraintsValidator(c *gc.C) { |
177 | + env := t.Prepare(c) |
178 | + validator := env.ConstraintsValidator() |
179 | + cons := constraints.MustParse("arch=amd64 tags=foo") |
180 | + unsupported, err := validator.Validate(cons) |
181 | + c.Assert(err, gc.IsNil) |
182 | + c.Assert(unsupported, gc.DeepEquals, []string{"tags"}) |
183 | +} |
184 | + |
185 | +func (t *localServerSuite) TestConstraintsMerge(c *gc.C) { |
186 | + env := t.Prepare(c) |
187 | + validator := env.ConstraintsValidator() |
188 | + consA := constraints.MustParse("arch=amd64 mem=1G cpu-power=10 cpu-cores=2 tags=bar") |
189 | + consB := constraints.MustParse("arch=i386 instance-type=foo") |
190 | + cons, err := validator.Merge(consA, consB) |
191 | + c.Assert(err, gc.IsNil) |
192 | + c.Assert(cons, gc.DeepEquals, constraints.MustParse("arch=i386 instance-type=foo tags=bar")) |
193 | +} |
194 | + |
195 | func (t *localServerSuite) TestValidateImageMetadata(c *gc.C) { |
196 | env := t.Prepare(c) |
197 | params, err := env.(simplestreams.MetadataValidator).MetadataLookupParams("test") |
198 | |
199 | === modified file 'provider/joyent/environ_instance.go' |
200 | --- provider/joyent/environ_instance.go 2014-04-18 16:37:28 +0000 |
201 | +++ provider/joyent/environ_instance.go 2014-04-21 23:25:35 +0000 |
202 | @@ -12,6 +12,7 @@ |
203 | "github.com/joyent/gocommon/client" |
204 | "github.com/joyent/gosdc/cloudapi" |
205 | |
206 | + "launchpad.net/juju-core/constraints" |
207 | "launchpad.net/juju-core/environs" |
208 | "launchpad.net/juju-core/environs/imagemetadata" |
209 | "launchpad.net/juju-core/environs/instances" |
210 | @@ -51,6 +52,18 @@ |
211 | return fmt.Sprintf("juju-%s-%s", env.Name(), names.MachineTag(machineId)) |
212 | } |
213 | |
214 | +var unsupportedConstraints = []string{ |
215 | + constraints.CpuPower, |
216 | + constraints.Tags, |
217 | +} |
218 | + |
219 | +// ConstraintsValidator is defined on the Environs interface. |
220 | +func (env *joyentEnviron) ConstraintsValidator() constraints.Validator { |
221 | + validator := constraints.NewValidator() |
222 | + validator.RegisterUnsupported(unsupportedConstraints) |
223 | + return validator |
224 | +} |
225 | + |
226 | func (env *joyentEnviron) StartInstance(args environs.StartInstanceParams) (instance.Instance, *instance.HardwareCharacteristics, []network.Info, error) { |
227 | |
228 | if args.MachineConfig.HasNetworks() { |
229 | |
230 | === modified file 'provider/joyent/local_test.go' |
231 | --- provider/joyent/local_test.go 2014-04-02 11:35:49 +0000 |
232 | +++ provider/joyent/local_test.go 2014-04-21 23:25:35 +0000 |
233 | @@ -421,3 +421,12 @@ |
234 | _, err = storage.Get(stor, "ab") |
235 | c.Assert(err, gc.NotNil) |
236 | } |
237 | + |
238 | +func (s *localServerSuite) TestConstraintsValidator(c *gc.C) { |
239 | + env := s.Prepare(c) |
240 | + validator := env.ConstraintsValidator() |
241 | + cons := constraints.MustParse("arch=amd64 tags=bar cpu-power=10") |
242 | + unsupported, err := validator.Validate(cons) |
243 | + c.Assert(err, gc.IsNil) |
244 | + c.Assert(unsupported, gc.DeepEquals, []string{"cpu-power", "tags"}) |
245 | +} |
246 | |
247 | === modified file 'provider/local/environ.go' |
248 | --- provider/local/environ.go 2014-04-18 16:37:28 +0000 |
249 | +++ provider/local/environ.go 2014-04-21 23:25:35 +0000 |
250 | @@ -301,6 +301,20 @@ |
251 | return nil |
252 | } |
253 | |
254 | +var unsupportedConstraints = []string{ |
255 | + constraints.CpuCores, |
256 | + constraints.CpuPower, |
257 | + constraints.InstanceType, |
258 | + constraints.Tags, |
259 | +} |
260 | + |
261 | +// ConstraintsValidator is defined on the Environs interface. |
262 | +func (env *localEnviron) ConstraintsValidator() constraints.Validator { |
263 | + validator := constraints.NewValidator() |
264 | + validator.RegisterUnsupported(unsupportedConstraints) |
265 | + return validator |
266 | +} |
267 | + |
268 | // StartInstance is specified in the InstanceBroker interface. |
269 | func (env *localEnviron) StartInstance(args environs.StartInstanceParams) (instance.Instance, *instance.HardwareCharacteristics, []network.Info, error) { |
270 | if args.MachineConfig.HasNetworks() { |
271 | |
272 | === modified file 'provider/local/environ_test.go' |
273 | --- provider/local/environ_test.go 2014-04-08 16:27:27 +0000 |
274 | +++ provider/local/environ_test.go 2014-04-21 23:25:35 +0000 |
275 | @@ -308,3 +308,14 @@ |
276 | c.Assert(cloudInitOutputLog, jc.DoesNotExist) |
277 | c.Assert(filepath.Join(rootDir, "log"), jc.IsSymlink) |
278 | } |
279 | + |
280 | +func (s *localJujuTestSuite) TestConstraintsValidator(c *gc.C) { |
281 | + ctx := coretesting.Context(c) |
282 | + env, err := local.Provider.Prepare(ctx, minimalConfig(c)) |
283 | + c.Assert(err, gc.IsNil) |
284 | + validator := env.ConstraintsValidator() |
285 | + cons := constraints.MustParse("arch=amd64 instance-type=foo tags=bar cpu-power=10 cpu-cores=2") |
286 | + unsupported, err := validator.Validate(cons) |
287 | + c.Assert(err, gc.IsNil) |
288 | + c.Assert(unsupported, gc.DeepEquals, []string{"cpu-cores", "cpu-power", "instance-type", "tags"}) |
289 | +} |
290 | |
291 | === modified file 'provider/maas/environ.go' |
292 | --- provider/maas/environ.go 2014-04-18 16:37:28 +0000 |
293 | +++ provider/maas/environ.go 2014-04-21 23:25:35 +0000 |
294 | @@ -332,6 +332,18 @@ |
295 | return `sed -i "s/iface eth0 inet dhcp/source \/etc\/network\/eth0.config/" /etc/network/interfaces` |
296 | } |
297 | |
298 | +var unsupportedConstraints = []string{ |
299 | + constraints.CpuPower, |
300 | + constraints.InstanceType, |
301 | +} |
302 | + |
303 | +// ConstraintsValidator is defined on the Environs interface. |
304 | +func (environ *maasEnviron) ConstraintsValidator() constraints.Validator { |
305 | + validator := constraints.NewValidator() |
306 | + validator.RegisterUnsupported(unsupportedConstraints) |
307 | + return validator |
308 | +} |
309 | + |
310 | // setupNetworks prepares a []network.Info for the given instance. |
311 | func (environ *maasEnviron) setupNetworks(inst instance.Instance) ([]network.Info, error) { |
312 | // Get the instance network interfaces first. |
313 | |
314 | === modified file 'provider/maas/environ_test.go' |
315 | --- provider/maas/environ_test.go 2014-04-18 16:37:28 +0000 |
316 | +++ provider/maas/environ_test.go 2014-04-21 23:25:35 +0000 |
317 | @@ -10,6 +10,7 @@ |
318 | gc "launchpad.net/gocheck" |
319 | "launchpad.net/gomaasapi" |
320 | |
321 | + "launchpad.net/juju-core/constraints" |
322 | "launchpad.net/juju-core/environs/config" |
323 | "launchpad.net/juju-core/environs/network" |
324 | envtesting "launchpad.net/juju-core/environs/testing" |
325 | @@ -243,3 +244,14 @@ |
326 | "ifup eth3.123", |
327 | }) |
328 | } |
329 | + |
330 | +func (s *environSuite) TestConstraintsValidator(c *gc.C) { |
331 | + cfg := getSimpleTestConfig(c, nil) |
332 | + env, err := maas.NewEnviron(cfg) |
333 | + c.Assert(err, gc.IsNil) |
334 | + validator := env.ConstraintsValidator() |
335 | + cons := constraints.MustParse("arch=amd64 cpu-power=10 instance-type=foo") |
336 | + unsupported, err := validator.Validate(cons) |
337 | + c.Assert(err, gc.IsNil) |
338 | + c.Assert(unsupported, gc.DeepEquals, []string{"cpu-power", "instance-type"}) |
339 | +} |
340 | |
341 | === modified file 'provider/manual/environ.go' |
342 | --- provider/manual/environ.go 2014-04-18 16:37:28 +0000 |
343 | +++ provider/manual/environ.go 2014-04-21 23:25:35 +0000 |
344 | @@ -266,6 +266,19 @@ |
345 | return errors.New(`use "juju add-machine ssh:[user@]<host>" to provision machines`) |
346 | } |
347 | |
348 | +var unsupportedConstraints = []string{ |
349 | + constraints.CpuPower, |
350 | + constraints.InstanceType, |
351 | + constraints.Tags, |
352 | +} |
353 | + |
354 | +// ConstraintsValidator is defined on the Environs interface. |
355 | +func (e *manualEnviron) ConstraintsValidator() constraints.Validator { |
356 | + validator := constraints.NewValidator() |
357 | + validator.RegisterUnsupported(unsupportedConstraints) |
358 | + return validator |
359 | +} |
360 | + |
361 | func (e *manualEnviron) OpenPorts(ports []instance.Port) error { |
362 | return nil |
363 | } |
364 | |
365 | === modified file 'provider/manual/environ_test.go' |
366 | --- provider/manual/environ_test.go 2014-04-10 21:33:04 +0000 |
367 | +++ provider/manual/environ_test.go 2014-04-21 23:25:35 +0000 |
368 | @@ -10,6 +10,7 @@ |
369 | jc "github.com/juju/testing/checkers" |
370 | gc "launchpad.net/gocheck" |
371 | |
372 | + "launchpad.net/juju-core/constraints" |
373 | "launchpad.net/juju-core/environs" |
374 | "launchpad.net/juju-core/environs/manual" |
375 | "launchpad.net/juju-core/environs/storage" |
376 | @@ -150,3 +151,11 @@ |
377 | func (s *environSuite) TestSupportNetworks(c *gc.C) { |
378 | c.Assert(s.env.SupportNetworks(), jc.IsFalse) |
379 | } |
380 | + |
381 | +func (s *environSuite) TestConstraintsValidator(c *gc.C) { |
382 | + validator := s.env.ConstraintsValidator() |
383 | + cons := constraints.MustParse("arch=amd64 instance-type=foo tags=bar cpu-power=10 cpu-cores=2 mem=1G") |
384 | + unsupported, err := validator.Validate(cons) |
385 | + c.Assert(err, gc.IsNil) |
386 | + c.Assert(unsupported, gc.DeepEquals, []string{"cpu-power", "instance-type", "tags"}) |
387 | +} |
388 | |
389 | === modified file 'provider/openstack/local_test.go' |
390 | --- provider/openstack/local_test.go 2014-04-17 13:21:44 +0000 |
391 | +++ provider/openstack/local_test.go 2014-04-21 23:25:35 +0000 |
392 | @@ -675,6 +675,25 @@ |
393 | c.Assert(err, gc.ErrorMatches, `no "saucy" images in some-region with arches \[amd64\]`) |
394 | } |
395 | |
396 | +func (s *localServerSuite) TestConstraintsValidator(c *gc.C) { |
397 | + env := s.Open(c) |
398 | + validator := env.ConstraintsValidator() |
399 | + cons := constraints.MustParse("arch=amd64 cpu-power=10") |
400 | + unsupported, err := validator.Validate(cons) |
401 | + c.Assert(err, gc.IsNil) |
402 | + c.Assert(unsupported, gc.DeepEquals, []string{"cpu-power"}) |
403 | +} |
404 | + |
405 | +func (s *localServerSuite) TestConstraintsMerge(c *gc.C) { |
406 | + env := s.Open(c) |
407 | + validator := env.ConstraintsValidator() |
408 | + consA := constraints.MustParse("arch=amd64 mem=1G root-disk=10G") |
409 | + consB := constraints.MustParse("instance-type=foo") |
410 | + cons, err := validator.Merge(consA, consB) |
411 | + c.Assert(err, gc.IsNil) |
412 | + c.Assert(cons, gc.DeepEquals, constraints.MustParse("instance-type=foo")) |
413 | +} |
414 | + |
415 | func (s *localServerSuite) TestValidateImageMetadata(c *gc.C) { |
416 | env := s.Open(c) |
417 | params, err := env.(simplestreams.MetadataValidator).MetadataLookupParams("some-region") |
418 | |
419 | === modified file 'provider/openstack/provider.go' |
420 | --- provider/openstack/provider.go 2014-04-18 16:37:28 +0000 |
421 | +++ provider/openstack/provider.go 2014-04-21 23:25:35 +0000 |
422 | @@ -522,6 +522,21 @@ |
423 | return false |
424 | } |
425 | |
426 | +var unsupportedConstraints = []string{ |
427 | + constraints.Tags, |
428 | + constraints.CpuPower, |
429 | +} |
430 | + |
431 | +// ConstraintsValidator is defined on the Environs interface. |
432 | +func (e *environ) ConstraintsValidator() constraints.Validator { |
433 | + validator := constraints.NewValidator() |
434 | + validator.RegisterConflicts( |
435 | + []string{constraints.InstanceType}, |
436 | + []string{constraints.Mem, constraints.Arch, constraints.RootDisk, constraints.CpuCores}) |
437 | + validator.RegisterUnsupported(unsupportedConstraints) |
438 | + return validator |
439 | +} |
440 | + |
441 | func (e *environ) Storage() storage.Storage { |
442 | e.ecfgMutex.Lock() |
443 | stor := e.storageUnlocked |
444 | |
445 | === modified file 'state/addmachine.go' |
446 | --- state/addmachine.go 2014-04-21 22:57:03 +0000 |
447 | +++ state/addmachine.go 2014-04-21 23:25:35 +0000 |
448 | @@ -183,18 +183,22 @@ |
449 | // valid and combines it with values from the state |
450 | // to produce a resulting template that more accurately |
451 | // represents the data that will be inserted into the state. |
452 | -func (st *State) effectiveMachineTemplate(p MachineTemplate, allowStateServer bool) (MachineTemplate, error) { |
453 | +func (st *State) effectiveMachineTemplate(p MachineTemplate, allowStateServer bool) (tmpl MachineTemplate, err error) { |
454 | + // First check for obvious errors. |
455 | if p.Series == "" { |
456 | - return MachineTemplate{}, fmt.Errorf("no series specified") |
457 | - } |
458 | - cons, err := st.EnvironConstraints() |
459 | - if err != nil { |
460 | - return MachineTemplate{}, err |
461 | - } |
462 | - validator := constraints.NewValidator() |
463 | - p.Constraints, err = validator.Merge(cons, p.Constraints) |
464 | - if err != nil { |
465 | - return MachineTemplate{}, err |
466 | + return tmpl, fmt.Errorf("no series specified") |
467 | + } |
468 | + if p.InstanceId != "" { |
469 | + if p.Nonce == "" { |
470 | + return tmpl, fmt.Errorf("cannot add a machine with an instance id and no nonce") |
471 | + } |
472 | + } else if p.Nonce != "" { |
473 | + return tmpl, fmt.Errorf("cannot specify a nonce without an instance id") |
474 | + } |
475 | + |
476 | + p.Constraints, err = st.resolveConstraints(p.Constraints) |
477 | + if err != nil { |
478 | + return tmpl, err |
479 | } |
480 | // Machine constraints do not use a container constraint value. |
481 | // Both provisioning and deployment constraints use the same |
482 | @@ -204,7 +208,7 @@ |
483 | p.Constraints.Container = nil |
484 | |
485 | if len(p.Jobs) == 0 { |
486 | - return MachineTemplate{}, fmt.Errorf("no jobs specified") |
487 | + return tmpl, fmt.Errorf("no jobs specified") |
488 | } |
489 | jset := make(map[MachineJob]bool) |
490 | for _, j := range p.Jobs { |
491 | @@ -215,16 +219,8 @@ |
492 | } |
493 | if jset[JobManageEnviron] { |
494 | if !allowStateServer { |
495 | - return MachineTemplate{}, errStateServerNotAllowed |
496 | - } |
497 | - } |
498 | - |
499 | - if p.InstanceId != "" { |
500 | - if p.Nonce == "" { |
501 | - return MachineTemplate{}, fmt.Errorf("cannot add a machine with an instance id and no nonce") |
502 | - } |
503 | - } else if p.Nonce != "" { |
504 | - return MachineTemplate{}, fmt.Errorf("cannot specify a nonce without an instance id") |
505 | + return tmpl, errStateServerNotAllowed |
506 | + } |
507 | } |
508 | return p, nil |
509 | } |
510 | |
511 | === modified file 'state/conn_test.go' |
512 | --- state/conn_test.go 2014-04-14 12:36:13 +0000 |
513 | +++ state/conn_test.go 2014-04-21 23:25:35 +0000 |
514 | @@ -9,6 +9,7 @@ |
515 | "labix.org/v2/mgo" |
516 | gc "launchpad.net/gocheck" |
517 | |
518 | + "launchpad.net/juju-core/constraints" |
519 | "launchpad.net/juju-core/environs/config" |
520 | "launchpad.net/juju-core/errors" |
521 | "launchpad.net/juju-core/state" |
522 | @@ -101,9 +102,10 @@ |
523 | } |
524 | |
525 | type mockPolicy struct { |
526 | - getPrechecker func(*config.Config) (state.Prechecker, error) |
527 | - getConfigValidator func(string) (state.ConfigValidator, error) |
528 | - getEnvironCapability func(*config.Config) (state.EnvironCapability, error) |
529 | + getPrechecker func(*config.Config) (state.Prechecker, error) |
530 | + getConfigValidator func(string) (state.ConfigValidator, error) |
531 | + getEnvironCapability func(*config.Config) (state.EnvironCapability, error) |
532 | + getConstraintsValidator func(*config.Config) (constraints.Validator, error) |
533 | } |
534 | |
535 | func (p *mockPolicy) Prechecker(cfg *config.Config) (state.Prechecker, error) { |
536 | @@ -126,3 +128,10 @@ |
537 | } |
538 | return nil, errors.NotImplementedf("EnvironCapability") |
539 | } |
540 | + |
541 | +func (p *mockPolicy) ConstraintsValidator(cfg *config.Config) (constraints.Validator, error) { |
542 | + if p.getConstraintsValidator != nil { |
543 | + return p.getConstraintsValidator(cfg) |
544 | + } |
545 | + return nil, errors.NewNotImplemented(nil, "ConstraintsValidator") |
546 | +} |
547 | |
548 | === modified file 'state/constraints.go' |
549 | --- state/constraints.go 2014-04-08 05:09:40 +0000 |
550 | +++ state/constraints.go 2014-04-21 23:25:35 +0000 |
551 | @@ -17,36 +17,39 @@ |
552 | |
553 | // constraintsDoc is the mongodb representation of a constraints.Value. |
554 | type constraintsDoc struct { |
555 | - Arch *string |
556 | - CpuCores *uint64 |
557 | - CpuPower *uint64 |
558 | - Mem *uint64 |
559 | - RootDisk *uint64 |
560 | - Container *instance.ContainerType |
561 | - Tags *[]string `bson:",omitempty"` |
562 | + Arch *string |
563 | + CpuCores *uint64 |
564 | + CpuPower *uint64 |
565 | + Mem *uint64 |
566 | + RootDisk *uint64 |
567 | + InstanceType *string |
568 | + Container *instance.ContainerType |
569 | + Tags *[]string `bson:",omitempty"` |
570 | } |
571 | |
572 | func (doc constraintsDoc) value() constraints.Value { |
573 | return constraints.Value{ |
574 | - Arch: doc.Arch, |
575 | - CpuCores: doc.CpuCores, |
576 | - CpuPower: doc.CpuPower, |
577 | - Mem: doc.Mem, |
578 | - RootDisk: doc.RootDisk, |
579 | - Container: doc.Container, |
580 | - Tags: doc.Tags, |
581 | + Arch: doc.Arch, |
582 | + CpuCores: doc.CpuCores, |
583 | + CpuPower: doc.CpuPower, |
584 | + Mem: doc.Mem, |
585 | + RootDisk: doc.RootDisk, |
586 | + Container: doc.Container, |
587 | + Tags: doc.Tags, |
588 | + InstanceType: doc.InstanceType, |
589 | } |
590 | } |
591 | |
592 | func newConstraintsDoc(cons constraints.Value) constraintsDoc { |
593 | return constraintsDoc{ |
594 | - Arch: cons.Arch, |
595 | - CpuCores: cons.CpuCores, |
596 | - CpuPower: cons.CpuPower, |
597 | - Mem: cons.Mem, |
598 | - RootDisk: cons.RootDisk, |
599 | - Container: cons.Container, |
600 | - Tags: cons.Tags, |
601 | + Arch: cons.Arch, |
602 | + CpuCores: cons.CpuCores, |
603 | + CpuPower: cons.CpuPower, |
604 | + Mem: cons.Mem, |
605 | + RootDisk: cons.RootDisk, |
606 | + Container: cons.Container, |
607 | + Tags: cons.Tags, |
608 | + InstanceType: cons.InstanceType, |
609 | } |
610 | } |
611 | |
612 | |
613 | === added file 'state/constraintsvalidation_test.go' |
614 | --- state/constraintsvalidation_test.go 1970-01-01 00:00:00 +0000 |
615 | +++ state/constraintsvalidation_test.go 2014-04-21 23:25:35 +0000 |
616 | @@ -0,0 +1,104 @@ |
617 | +// Copyright 2014 Canonical Ltd. |
618 | +// Licensed under the AGPLv3, see LICENCE file for details. |
619 | + |
620 | +package state_test |
621 | + |
622 | +import ( |
623 | + gc "launchpad.net/gocheck" |
624 | + |
625 | + "launchpad.net/juju-core/constraints" |
626 | + "launchpad.net/juju-core/environs/config" |
627 | + "launchpad.net/juju-core/state" |
628 | +) |
629 | + |
630 | +type constraintsValidationSuite struct { |
631 | + ConnSuite |
632 | +} |
633 | + |
634 | +var _ = gc.Suite(&constraintsValidationSuite{}) |
635 | + |
636 | +func (s *constraintsValidationSuite) SetUpTest(c *gc.C) { |
637 | + s.ConnSuite.SetUpTest(c) |
638 | + s.policy.getConstraintsValidator = func(*config.Config) (constraints.Validator, error) { |
639 | + validator := constraints.NewValidator() |
640 | + validator.RegisterConflicts([]string{constraints.InstanceType}, []string{constraints.Mem, constraints.Arch}) |
641 | + validator.RegisterUnsupported([]string{constraints.CpuPower}) |
642 | + return validator, nil |
643 | + } |
644 | +} |
645 | + |
646 | +func (s *constraintsValidationSuite) addOneMachine(c *gc.C, cons constraints.Value) (*state.Machine, error) { |
647 | + return s.State.AddOneMachine(state.MachineTemplate{ |
648 | + Series: "quantal", |
649 | + Jobs: []state.MachineJob{state.JobHostUnits}, |
650 | + Constraints: cons, |
651 | + }) |
652 | +} |
653 | + |
654 | +var setConstraintsTests = []struct { |
655 | + consFallback string |
656 | + cons string |
657 | + expected string |
658 | +}{ |
659 | + { |
660 | + cons: "root-disk=8G mem=4G arch=amd64", |
661 | + consFallback: "cpu-power=1000 cpu-cores=4", |
662 | + expected: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4", |
663 | + }, { |
664 | + cons: "root-disk=8G mem=4G arch=amd64", |
665 | + consFallback: "cpu-power=1000 cpu-cores=4 mem=8G", |
666 | + expected: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4", |
667 | + }, { |
668 | + consFallback: "root-disk=8G cpu-cores=4 instance-type=foo", |
669 | + expected: "root-disk=8G cpu-cores=4 instance-type=foo", |
670 | + }, { |
671 | + cons: "root-disk=8G cpu-cores=4 instance-type=foo", |
672 | + expected: "root-disk=8G cpu-cores=4 instance-type=foo", |
673 | + }, { |
674 | + consFallback: "root-disk=8G instance-type=foo", |
675 | + cons: "root-disk=8G cpu-cores=4 instance-type=bar", |
676 | + expected: "root-disk=8G cpu-cores=4 instance-type=bar", |
677 | + }, { |
678 | + consFallback: "root-disk=8G mem=4G", |
679 | + cons: "root-disk=8G cpu-cores=4 instance-type=bar", |
680 | + expected: "root-disk=8G cpu-cores=4 instance-type=bar", |
681 | + }, { |
682 | + consFallback: "root-disk=8G cpu-cores=4 instance-type=bar", |
683 | + cons: "root-disk=8G mem=4G", |
684 | + expected: "root-disk=8G cpu-cores=4 mem=4G", |
685 | + }, { |
686 | + consFallback: "root-disk=8G cpu-cores=4 instance-type=bar", |
687 | + cons: "root-disk=8G arch=amd64 mem=4G", |
688 | + expected: "root-disk=8G cpu-cores=4 arch=amd64 mem=4G", |
689 | + }, |
690 | +} |
691 | + |
692 | +func (s *constraintsValidationSuite) TestMachineConstraints(c *gc.C) { |
693 | + for i, t := range setConstraintsTests { |
694 | + c.Logf("test %d", i) |
695 | + err := s.State.SetEnvironConstraints(constraints.MustParse(t.consFallback)) |
696 | + c.Check(err, gc.IsNil) |
697 | + m, err := s.addOneMachine(c, constraints.MustParse(t.cons)) |
698 | + c.Check(err, gc.IsNil) |
699 | + cons, err := m.Constraints() |
700 | + c.Check(err, gc.IsNil) |
701 | + c.Check(cons, gc.DeepEquals, constraints.MustParse(t.expected)) |
702 | + } |
703 | +} |
704 | + |
705 | +func (s *constraintsValidationSuite) TestServiceConstraints(c *gc.C) { |
706 | + charm := s.AddTestingCharm(c, "wordpress") |
707 | + service := s.AddTestingService(c, "wordpress", charm) |
708 | + for i, t := range setConstraintsTests { |
709 | + c.Logf("test %d", i) |
710 | + err := s.State.SetEnvironConstraints(constraints.MustParse(t.consFallback)) |
711 | + c.Check(err, gc.IsNil) |
712 | + err = service.SetConstraints(constraints.MustParse(t.cons)) |
713 | + c.Check(err, gc.IsNil) |
714 | + u, err := service.AddUnit() |
715 | + c.Check(err, gc.IsNil) |
716 | + cons, err := state.UnitConstraints(u) |
717 | + c.Check(err, gc.IsNil) |
718 | + c.Check(*cons, gc.DeepEquals, constraints.MustParse(t.expected)) |
719 | + } |
720 | +} |
721 | |
722 | === modified file 'state/export_test.go' |
723 | --- state/export_test.go 2014-04-18 11:59:59 +0000 |
724 | +++ state/export_test.go 2014-04-21 23:25:35 +0000 |
725 | @@ -15,6 +15,7 @@ |
726 | gc "launchpad.net/gocheck" |
727 | |
728 | "launchpad.net/juju-core/charm" |
729 | + "launchpad.net/juju-core/constraints" |
730 | "launchpad.net/juju-core/environs/config" |
731 | "launchpad.net/juju-core/instance" |
732 | "launchpad.net/juju-core/testing" |
733 | @@ -261,3 +262,7 @@ |
734 | } |
735 | |
736 | var StateServerAvailable = &stateServerAvailable |
737 | + |
738 | +func UnitConstraints(u *Unit) (*constraints.Value, error) { |
739 | + return u.constraints() |
740 | +} |
741 | |
742 | === modified file 'state/machine.go' |
743 | --- state/machine.go 2014-04-18 16:46:35 +0000 |
744 | +++ state/machine.go 2014-04-21 23:25:35 +0000 |
745 | @@ -1101,6 +1101,13 @@ |
746 | // is already provisioned. |
747 | func (m *Machine) SetConstraints(cons constraints.Value) (err error) { |
748 | defer errors.Maskf(&err, "cannot set constraints") |
749 | + unsupported, err := m.st.validateConstraints(cons) |
750 | + if len(unsupported) > 0 { |
751 | + logger.Warningf( |
752 | + "setting constraints on machine %q: unsupported constraints: %v", m.Id(), strings.Join(unsupported, ",")) |
753 | + } else if err != nil { |
754 | + return err |
755 | + } |
756 | notSetYet := bson.D{{"nonce", ""}} |
757 | ops := []txn.Op{ |
758 | { |
759 | |
760 | === modified file 'state/machine_test.go' |
761 | --- state/machine_test.go 2014-04-18 16:37:28 +0000 |
762 | +++ state/machine_test.go 2014-04-21 23:25:35 +0000 |
763 | @@ -7,11 +7,13 @@ |
764 | "sort" |
765 | "strings" |
766 | |
767 | + "github.com/juju/loggo" |
768 | jc "github.com/juju/testing/checkers" |
769 | "labix.org/v2/mgo/bson" |
770 | gc "launchpad.net/gocheck" |
771 | |
772 | "launchpad.net/juju-core/constraints" |
773 | + "launchpad.net/juju-core/environs/config" |
774 | "launchpad.net/juju-core/environs/network" |
775 | "launchpad.net/juju-core/errors" |
776 | "launchpad.net/juju-core/instance" |
777 | @@ -32,6 +34,12 @@ |
778 | |
779 | func (s *MachineSuite) SetUpTest(c *gc.C) { |
780 | s.ConnSuite.SetUpTest(c) |
781 | + s.policy.getConstraintsValidator = func(*config.Config) (constraints.Validator, error) { |
782 | + validator := constraints.NewValidator() |
783 | + validator.RegisterConflicts([]string{constraints.InstanceType}, []string{constraints.Mem}) |
784 | + validator.RegisterUnsupported([]string{constraints.CpuPower}) |
785 | + return validator, nil |
786 | + } |
787 | var err error |
788 | s.machine0, err = s.State.AddMachine("quantal", state.JobManageEnviron) |
789 | s.machine, err = s.State.AddMachine("quantal", state.JobHostUnits) |
790 | @@ -1252,6 +1260,35 @@ |
791 | c.Assert(mcons, gc.DeepEquals, cons1) |
792 | } |
793 | |
794 | +func (s *MachineSuite) TestSetAmbiguousConstraints(c *gc.C) { |
795 | + machine, err := s.State.AddMachine("quantal", state.JobHostUnits) |
796 | + c.Assert(err, gc.IsNil) |
797 | + cons := constraints.MustParse("mem=4G instance-type=foo") |
798 | + err = machine.SetConstraints(cons) |
799 | + c.Assert(err, gc.ErrorMatches, `cannot set constraints: ambiguous constraints: "mem" overlaps with "instance-type"`) |
800 | +} |
801 | + |
802 | +func (s *MachineSuite) TestSetUnsupportedConstraintsWarning(c *gc.C) { |
803 | + defer loggo.ResetWriters() |
804 | + logger := loggo.GetLogger("test") |
805 | + logger.SetLogLevel(loggo.DEBUG) |
806 | + tw := &loggo.TestWriter{} |
807 | + c.Assert(loggo.RegisterWriter("constraints-tester", tw, loggo.DEBUG), gc.IsNil) |
808 | + |
809 | + machine, err := s.State.AddMachine("quantal", state.JobHostUnits) |
810 | + c.Assert(err, gc.IsNil) |
811 | + cons := constraints.MustParse("mem=4G cpu-power=10") |
812 | + err = machine.SetConstraints(cons) |
813 | + c.Assert(err, gc.IsNil) |
814 | + c.Assert(tw.Log, jc.LogMatches, jc.SimpleMessages{{ |
815 | + loggo.WARNING, |
816 | + `setting constraints on machine "2": unsupported constraints: cpu-power`}, |
817 | + }) |
818 | + mcons, err := machine.Constraints() |
819 | + c.Assert(err, gc.IsNil) |
820 | + c.Assert(mcons, gc.DeepEquals, cons) |
821 | +} |
822 | + |
823 | func (s *MachineSuite) TestConstraintsLifecycle(c *gc.C) { |
824 | cons := constraints.MustParse("mem=1G") |
825 | cannotSet := `cannot set constraints: not found or not alive` |
826 | |
827 | === modified file 'state/policy.go' |
828 | --- state/policy.go 2014-04-14 12:36:13 +0000 |
829 | +++ state/policy.go 2014-04-21 23:25:35 +0000 |
830 | @@ -32,6 +32,10 @@ |
831 | // EnvironCapability takes a *config.Config and returns |
832 | // a (possibly nil) EnvironCapability or an error. |
833 | EnvironCapability(*config.Config) (EnvironCapability, error) |
834 | + |
835 | + // ConstraintsValidator takes a *config.Config and returns |
836 | + // a (possibly nil) constraints.Validator or an error. |
837 | + ConstraintsValidator(*config.Config) (constraints.Validator, error) |
838 | } |
839 | |
840 | // Prechecker is a policy interface that is provided to State |
841 | @@ -94,6 +98,53 @@ |
842 | return prechecker.PrecheckInstance(series, cons) |
843 | } |
844 | |
845 | +func (st *State) constraintsValidator() (constraints.Validator, error) { |
846 | + // Default behaviour is to simply use a standard validator with |
847 | + // no environment specific behaviour built in. |
848 | + defaultValidator := constraints.NewValidator() |
849 | + if st.policy == nil { |
850 | + return defaultValidator, nil |
851 | + } |
852 | + cfg, err := st.EnvironConfig() |
853 | + if err != nil { |
854 | + return nil, err |
855 | + } |
856 | + validator, err := st.policy.ConstraintsValidator(cfg) |
857 | + if errors.IsNotImplemented(err) { |
858 | + return defaultValidator, nil |
859 | + } else if err != nil { |
860 | + return nil, err |
861 | + } |
862 | + if validator == nil { |
863 | + return nil, fmt.Errorf("policy returned nil constraints validator without an error") |
864 | + } |
865 | + return validator, nil |
866 | +} |
867 | + |
868 | +// resolveConstraints combines the given constraints with the environ constraints to get |
869 | +// a constraints which will be used to create a new instance. |
870 | +func (st *State) resolveConstraints(cons constraints.Value) (constraints.Value, error) { |
871 | + validator, err := st.constraintsValidator() |
872 | + if err != nil { |
873 | + return constraints.Value{}, err |
874 | + } |
875 | + envCons, err := st.EnvironConstraints() |
876 | + if err != nil { |
877 | + return constraints.Value{}, err |
878 | + } |
879 | + return validator.Merge(envCons, cons) |
880 | +} |
881 | + |
882 | +// validateConstraints returns an error if the given constraints are not valid for the |
883 | +// current environment, and also any unsupported attributes. |
884 | +func (st *State) validateConstraints(cons constraints.Value) ([]string, error) { |
885 | + validator, err := st.constraintsValidator() |
886 | + if err != nil { |
887 | + return nil, err |
888 | + } |
889 | + return validator.Validate(cons) |
890 | +} |
891 | + |
892 | // validate calls the state's assigned policy, if non-nil, to obtain |
893 | // a ConfigValidator, and calls Validate if a non-nil ConfigValidator is |
894 | // returned. |
895 | |
896 | === modified file 'state/service.go' |
897 | --- state/service.go 2014-04-21 22:57:03 +0000 |
898 | +++ state/service.go 2014-04-21 23:25:35 +0000 |
899 | @@ -8,6 +8,7 @@ |
900 | "fmt" |
901 | "sort" |
902 | "strconv" |
903 | + "strings" |
904 | |
905 | "labix.org/v2/mgo" |
906 | "labix.org/v2/mgo/bson" |
907 | @@ -594,12 +595,7 @@ |
908 | if err != nil { |
909 | return "", nil, err |
910 | } |
911 | - econs, err := s.st.EnvironConstraints() |
912 | - if err != nil { |
913 | - return "", nil, err |
914 | - } |
915 | - validator := constraints.NewValidator() |
916 | - cons, err := validator.Merge(econs, scons) |
917 | + cons, err := s.st.resolveConstraints(scons) |
918 | if err != nil { |
919 | return "", nil, err |
920 | } |
921 | @@ -807,6 +803,13 @@ |
922 | |
923 | // SetConstraints replaces the current service constraints. |
924 | func (s *Service) SetConstraints(cons constraints.Value) (err error) { |
925 | + unsupported, err := s.st.validateConstraints(cons) |
926 | + if len(unsupported) > 0 { |
927 | + logger.Warningf( |
928 | + "setting constraints on service %q: unsupported constraints: %v", s.Name(), strings.Join(unsupported, ",")) |
929 | + } else if err != nil { |
930 | + return err |
931 | + } |
932 | if s.doc.Subordinate { |
933 | return ErrSubordinateConstraints |
934 | } |
935 | |
936 | === modified file 'state/service_test.go' |
937 | --- state/service_test.go 2014-04-14 12:36:13 +0000 |
938 | +++ state/service_test.go 2014-04-21 23:25:35 +0000 |
939 | @@ -7,12 +7,14 @@ |
940 | "fmt" |
941 | "sort" |
942 | |
943 | + "github.com/juju/loggo" |
944 | jc "github.com/juju/testing/checkers" |
945 | "labix.org/v2/mgo" |
946 | gc "launchpad.net/gocheck" |
947 | |
948 | "launchpad.net/juju-core/charm" |
949 | "launchpad.net/juju-core/constraints" |
950 | + "launchpad.net/juju-core/environs/config" |
951 | "launchpad.net/juju-core/errors" |
952 | "launchpad.net/juju-core/state" |
953 | "launchpad.net/juju-core/state/testing" |
954 | @@ -28,6 +30,12 @@ |
955 | |
956 | func (s *ServiceSuite) SetUpTest(c *gc.C) { |
957 | s.ConnSuite.SetUpTest(c) |
958 | + s.policy.getConstraintsValidator = func(*config.Config) (constraints.Validator, error) { |
959 | + validator := constraints.NewValidator() |
960 | + validator.RegisterConflicts([]string{constraints.InstanceType}, []string{constraints.Mem}) |
961 | + validator.RegisterUnsupported([]string{constraints.CpuPower}) |
962 | + return validator, nil |
963 | + } |
964 | s.charm = s.AddTestingCharm(c, "mysql") |
965 | s.mysql = s.AddTestingService(c, "mysql", s.charm) |
966 | } |
967 | @@ -1153,6 +1161,31 @@ |
968 | c.Assert(&cons6, jc.Satisfies, constraints.IsEmpty) |
969 | } |
970 | |
971 | +func (s *ServiceSuite) TestSetInvalidConstraints(c *gc.C) { |
972 | + cons := constraints.MustParse("mem=4G instance-type=foo") |
973 | + err := s.mysql.SetConstraints(cons) |
974 | + c.Assert(err, gc.ErrorMatches, `ambiguous constraints: "mem" overlaps with "instance-type"`) |
975 | +} |
976 | + |
977 | +func (s *ServiceSuite) TestSetUnsupportedConstraintsWarning(c *gc.C) { |
978 | + defer loggo.ResetWriters() |
979 | + logger := loggo.GetLogger("test") |
980 | + logger.SetLogLevel(loggo.DEBUG) |
981 | + tw := &loggo.TestWriter{} |
982 | + c.Assert(loggo.RegisterWriter("constraints-tester", tw, loggo.DEBUG), gc.IsNil) |
983 | + |
984 | + cons := constraints.MustParse("mem=4G cpu-power=10") |
985 | + err := s.mysql.SetConstraints(cons) |
986 | + c.Assert(err, gc.IsNil) |
987 | + c.Assert(tw.Log, jc.LogMatches, jc.SimpleMessages{{ |
988 | + loggo.WARNING, |
989 | + `setting constraints on service "mysql": unsupported constraints: cpu-power`}, |
990 | + }) |
991 | + scons, err := s.mysql.Constraints() |
992 | + c.Assert(err, gc.IsNil) |
993 | + c.Assert(scons, gc.DeepEquals, cons) |
994 | +} |
995 | + |
996 | func (s *ServiceSuite) TestConstraintsLifecycle(c *gc.C) { |
997 | // Dying. |
998 | unit, err := s.mysql.AddUnit() |
999 | |
1000 | === modified file 'state/state.go' |
1001 | --- state/state.go 2014-04-18 15:31:56 +0000 |
1002 | +++ state/state.go 2014-04-21 23:25:35 +0000 |
1003 | @@ -333,6 +333,13 @@ |
1004 | |
1005 | // SetEnvironConstraints replaces the current environment constraints. |
1006 | func (st *State) SetEnvironConstraints(cons constraints.Value) error { |
1007 | + unsupported, err := st.validateConstraints(cons) |
1008 | + if len(unsupported) > 0 { |
1009 | + logger.Warningf( |
1010 | + "setting environment constraints: unsupported constraints: %v", strings.Join(unsupported, ",")) |
1011 | + } else if err != nil { |
1012 | + return err |
1013 | + } |
1014 | return writeConstraints(st, environGlobalKey, cons) |
1015 | } |
1016 | |
1017 | |
1018 | === modified file 'state/state_test.go' |
1019 | --- state/state_test.go 2014-04-18 15:31:56 +0000 |
1020 | +++ state/state_test.go 2014-04-21 23:25:35 +0000 |
1021 | @@ -10,6 +10,7 @@ |
1022 | "strings" |
1023 | "time" |
1024 | |
1025 | + "github.com/juju/loggo" |
1026 | jc "github.com/juju/testing/checkers" |
1027 | "labix.org/v2/mgo" |
1028 | "labix.org/v2/mgo/bson" |
1029 | @@ -49,6 +50,16 @@ |
1030 | |
1031 | var _ = gc.Suite(&StateSuite{}) |
1032 | |
1033 | +func (s *StateSuite) SetUpTest(c *gc.C) { |
1034 | + s.ConnSuite.SetUpTest(c) |
1035 | + s.policy.getConstraintsValidator = func(*config.Config) (constraints.Validator, error) { |
1036 | + validator := constraints.NewValidator() |
1037 | + validator.RegisterConflicts([]string{constraints.InstanceType}, []string{constraints.Mem}) |
1038 | + validator.RegisterUnsupported([]string{constraints.CpuPower}) |
1039 | + return validator, nil |
1040 | + } |
1041 | +} |
1042 | + |
1043 | func (s *StateSuite) TestDialAgain(c *gc.C) { |
1044 | // Ensure idempotent operations on Dial are working fine. |
1045 | for i := 0; i < 2; i++ { |
1046 | @@ -756,6 +767,7 @@ |
1047 | Series: "quantal", |
1048 | Jobs: []state.MachineJob{state.JobHostUnits, state.JobHostUnits}, |
1049 | InstanceId: "i-foo", |
1050 | + Nonce: "nonce", |
1051 | }) |
1052 | c.Check(err, gc.ErrorMatches, fmt.Sprintf("cannot add a new machine: duplicate job: %s", state.JobHostUnits)) |
1053 | |
1054 | @@ -1349,6 +1361,31 @@ |
1055 | c.Assert(cons5, gc.DeepEquals, cons4) |
1056 | } |
1057 | |
1058 | +func (s *StateSuite) TestSetInvalidConstraints(c *gc.C) { |
1059 | + cons := constraints.MustParse("mem=4G instance-type=foo") |
1060 | + err := s.State.SetEnvironConstraints(cons) |
1061 | + c.Assert(err, gc.ErrorMatches, `ambiguous constraints: "mem" overlaps with "instance-type"`) |
1062 | +} |
1063 | + |
1064 | +func (s *StateSuite) TestSetUnsupportedConstraintsWarning(c *gc.C) { |
1065 | + defer loggo.ResetWriters() |
1066 | + logger := loggo.GetLogger("test") |
1067 | + logger.SetLogLevel(loggo.DEBUG) |
1068 | + tw := &loggo.TestWriter{} |
1069 | + c.Assert(loggo.RegisterWriter("constraints-tester", tw, loggo.DEBUG), gc.IsNil) |
1070 | + |
1071 | + cons := constraints.MustParse("mem=4G cpu-power=10") |
1072 | + err := s.State.SetEnvironConstraints(cons) |
1073 | + c.Assert(err, gc.IsNil) |
1074 | + c.Assert(tw.Log, jc.LogMatches, jc.SimpleMessages{{ |
1075 | + loggo.WARNING, |
1076 | + `setting environment constraints: unsupported constraints: cpu-power`}, |
1077 | + }) |
1078 | + econs, err := s.State.EnvironConstraints() |
1079 | + c.Assert(err, gc.IsNil) |
1080 | + c.Assert(econs, gc.DeepEquals, cons) |
1081 | +} |
1082 | + |
1083 | func (s *StateSuite) TestWatchServicesBulkEvents(c *gc.C) { |
1084 | // Alive service... |
1085 | dummyCharm := s.AddTestingCharm(c, "dummy") |
Reviewers: mp+216244_ code.launchpad. net,
Message:
Please take a look.
Description:
Add constraints validation to providers
Each provider has a constraints validator which
is used when setting constraints on a machine or
service, as well as when constraints are merged.
The validation step allows conflicting constraints
like instance-type and mem to be rejected, and also
unsupported constraints to be logged with a warning.
The merge step allows things like instance-type to
mask other incompatible constraints like mem or arch,
and visa versa.
https:/ /code.launchpad .net/~wallyworl d/juju- core/instance- type-constraint /+merge/ 216244
Requires: /code.launchpad .net/~wallyworl d/juju- core/constraint s-validation- merge/+ merge/215807
https:/
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/88780043/
Affected files (+529, -61 lines): instances/ image.go interface. go statepolicy. go azure/environ. go azure/environ_ test.go dummy/environs. go ec2/local_ test.go joyent/ environ_ instance. go joyent/ local_test. go local/environ. go local/environ_ test.go maas/environ. go maas/environ_ test.go manual/ environ. go manual/ environ_ test.go openstack/ local_test. go openstack/ provider. go ts.go tsvalidation_ test.go test.go test.go test.go
A [revision details]
M environs/
M environs/
M environs/
M provider/
M provider/
M provider/
M provider/ec2/ec2.go
M provider/
M provider/
M provider/
M provider/
M provider/
M provider/
M provider/
M provider/
M provider/
M provider/
M provider/
M state/addmachine.go
M state/conn_test.go
M state/constrain
A state/constrain
M state/export_
M state/machine.go
M state/machine_
M state/policy.go
M state/service.go
M state/service_
M state/state_test.go