1
// Copyright 2013 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
4
package constraints_test
9
jc "github.com/juju/testing/checkers"
10
gc "gopkg.in/check.v1"
12
"github.com/juju/juju/constraints"
15
type validationSuite struct{}
17
var _ = gc.Suite(&validationSuite{})
19
var validationTests = []struct {
22
vocab map[string][]interface{}
28
cons: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4",
31
cons: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo",
32
unsupported: []string{"tags"},
35
cons: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 instance-type=foo",
36
unsupported: []string{"cpu-power", "instance-type"},
39
// Ambiguous constraint errors take precedence over unsupported errors.
40
cons: "root-disk=8G mem=4G cpu-cores=4 instance-type=foo",
41
reds: []string{"mem", "arch"},
42
blues: []string{"instance-type"},
43
unsupported: []string{"cpu-cores"},
44
err: `ambiguous constraints: "instance-type" overlaps with "mem"`,
47
cons: "root-disk=8G mem=4G arch=amd64 cpu-cores=4 instance-type=foo",
48
reds: []string{"mem", "arch"},
52
cons: "root-disk=8G mem=4G arch=amd64 cpu-cores=4 instance-type=foo",
53
blues: []string{"mem", "arch"},
57
cons: "root-disk=8G mem=4G arch=amd64 cpu-cores=4 instance-type=foo",
58
reds: []string{"mem", "arch"},
59
blues: []string{"instance-type"},
60
err: `ambiguous constraints: "arch" overlaps with "instance-type"`,
63
cons: "root-disk=8G mem=4G arch=amd64 cpu-cores=4 instance-type=foo",
64
reds: []string{"instance-type"},
65
blues: []string{"mem", "arch"},
66
err: `ambiguous constraints: "arch" overlaps with "instance-type"`,
69
cons: "root-disk=8G mem=4G cpu-cores=4 instance-type=foo",
70
reds: []string{"mem", "arch"},
71
blues: []string{"instance-type"},
72
err: `ambiguous constraints: "instance-type" overlaps with "mem"`,
75
cons: "arch=amd64 mem=4G cpu-cores=4",
76
vocab: map[string][]interface{}{"arch": {"amd64", "i386"}},
79
cons: "mem=4G cpu-cores=4",
80
vocab: map[string][]interface{}{"cpu-cores": {2, 4, 8}},
83
cons: "mem=4G instance-type=foo",
84
vocab: map[string][]interface{}{"instance-type": {"foo", "bar"}},
87
cons: "mem=4G tags=foo,bar",
88
vocab: map[string][]interface{}{"tags": {"foo", "bar", "another"}},
91
cons: "arch=i386 mem=4G cpu-cores=4",
92
vocab: map[string][]interface{}{"arch": {"amd64"}},
93
err: "invalid constraint value: arch=i386\nvalid values are:.*",
96
cons: "mem=4G cpu-cores=5",
97
vocab: map[string][]interface{}{"cpu-cores": {2, 4, 8}},
98
err: "invalid constraint value: cpu-cores=5\nvalid values are:.*",
101
cons: "mem=4G instance-type=foo",
102
vocab: map[string][]interface{}{"instance-type": {"bar"}},
103
err: "invalid constraint value: instance-type=foo\nvalid values are:.*",
106
cons: "mem=4G tags=foo,other",
107
vocab: map[string][]interface{}{"tags": {"foo", "bar", "another"}},
108
err: "invalid constraint value: tags=other\nvalid values are:.*",
111
cons: "arch=i386 mem=4G instance-type=foo",
112
vocab: map[string][]interface{}{
113
"instance-type": {"foo", "bar"},
114
"arch": {"amd64", "i386"}},
117
cons: "virt-type=bar",
118
vocab: map[string][]interface{}{"virt-type": {"bar"}},
122
func (s *validationSuite) TestValidation(c *gc.C) {
123
for i, t := range validationTests {
125
validator := constraints.NewValidator()
126
validator.RegisterUnsupported(t.unsupported)
127
validator.RegisterConflicts(t.reds, t.blues)
128
for a, v := range t.vocab {
129
validator.RegisterVocabulary(a, v)
131
cons := constraints.MustParse(t.cons)
132
unsupported, err := validator.Validate(cons)
134
c.Assert(err, jc.ErrorIsNil)
135
c.Assert(unsupported, jc.SameContents, t.unsupported)
137
c.Assert(err, gc.ErrorMatches, t.err)
142
var mergeTests = []struct {
152
desc: "empty all round",
154
desc: "container with empty fallback",
155
cons: "container=lxd",
156
expected: "container=lxd",
158
desc: "container from fallback",
159
consFallback: "container=lxd",
160
expected: "container=lxd",
162
desc: "arch with empty fallback",
164
expected: "arch=amd64",
166
desc: "arch with ignored fallback",
168
consFallback: "arch=i386",
169
expected: "arch=amd64",
171
desc: "arch from fallback",
172
consFallback: "arch=i386",
173
expected: "arch=i386",
175
desc: "instance type with empty fallback",
176
cons: "instance-type=foo",
177
expected: "instance-type=foo",
179
desc: "instance type with ignored fallback",
180
cons: "instance-type=foo",
181
consFallback: "instance-type=bar",
182
expected: "instance-type=foo",
184
desc: "instance type from fallback",
185
consFallback: "instance-type=foo",
186
expected: "instance-type=foo",
188
desc: "cpu-cores with empty fallback",
190
expected: "cpu-cores=2",
192
desc: "cpu-cores with ignored fallback",
194
consFallback: "cpu-cores=8",
195
expected: "cpu-cores=4",
197
desc: "cpu-cores from fallback",
198
consFallback: "cpu-cores=8",
199
expected: "cpu-cores=8",
201
desc: "cpu-power with empty fallback",
202
cons: "cpu-power=100",
203
expected: "cpu-power=100",
205
desc: "cpu-power with ignored fallback",
206
cons: "cpu-power=100",
207
consFallback: "cpu-power=200",
208
expected: "cpu-power=100",
210
desc: "cpu-power from fallback",
211
consFallback: "cpu-power=200",
212
expected: "cpu-power=200",
214
desc: "tags with empty fallback",
215
cons: "tags=foo,bar",
216
expected: "tags=foo,bar",
218
desc: "tags with ignored fallback",
219
cons: "tags=foo,bar",
220
consFallback: "tags=baz",
221
expected: "tags=foo,bar",
223
desc: "tags from fallback",
224
consFallback: "tags=foo,bar",
225
expected: "tags=foo,bar",
227
desc: "tags inital empty",
229
consFallback: "tags=foo,bar",
232
desc: "mem with empty fallback",
236
desc: "mem with ignored fallback",
238
consFallback: "mem=8G",
241
desc: "mem from fallback",
242
consFallback: "mem=8G",
245
desc: "root-disk with empty fallback",
246
cons: "root-disk=4G",
247
expected: "root-disk=4G",
249
desc: "root-disk with ignored fallback",
250
cons: "root-disk=4G",
251
consFallback: "root-disk=8G",
252
expected: "root-disk=4G",
254
desc: "root-disk from fallback",
255
consFallback: "root-disk=8G",
256
expected: "root-disk=8G",
258
desc: "non-overlapping mix",
259
cons: "root-disk=8G mem=4G arch=amd64",
260
consFallback: "cpu-power=1000 cpu-cores=4",
261
expected: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4",
263
desc: "overlapping mix",
264
cons: "root-disk=8G mem=4G arch=amd64",
265
consFallback: "cpu-power=1000 cpu-cores=4 mem=8G",
266
expected: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4",
268
desc: "fallback only, no conflicts",
269
consFallback: "root-disk=8G cpu-cores=4 instance-type=foo",
270
reds: []string{"mem", "arch"},
271
blues: []string{"instance-type"},
272
expected: "root-disk=8G cpu-cores=4 instance-type=foo",
274
desc: "no fallback, no conflicts",
275
cons: "root-disk=8G cpu-cores=4 instance-type=foo",
276
reds: []string{"mem", "arch"},
277
blues: []string{"instance-type"},
278
expected: "root-disk=8G cpu-cores=4 instance-type=foo",
280
desc: "conflict value from override",
281
consFallback: "root-disk=8G instance-type=foo",
282
cons: "root-disk=8G cpu-cores=4 instance-type=bar",
283
reds: []string{"mem", "arch"},
284
blues: []string{"instance-type"},
285
expected: "root-disk=8G cpu-cores=4 instance-type=bar",
287
desc: "unsupported attributes ignored",
288
consFallback: "root-disk=8G instance-type=foo",
289
cons: "root-disk=8G cpu-cores=4 instance-type=bar",
290
reds: []string{"mem", "arch"},
291
blues: []string{"instance-type"},
292
unsupported: []string{"instance-type"},
293
expected: "root-disk=8G cpu-cores=4 instance-type=bar",
295
desc: "red conflict masked from fallback",
296
consFallback: "root-disk=8G mem=4G",
297
cons: "root-disk=8G cpu-cores=4 instance-type=bar",
298
reds: []string{"mem", "arch"},
299
blues: []string{"instance-type"},
300
expected: "root-disk=8G cpu-cores=4 instance-type=bar",
302
desc: "second red conflict masked from fallback",
303
consFallback: "root-disk=8G arch=amd64",
304
cons: "root-disk=8G cpu-cores=4 instance-type=bar",
305
reds: []string{"mem", "arch"},
306
blues: []string{"instance-type"},
307
expected: "root-disk=8G cpu-cores=4 instance-type=bar",
309
desc: "blue conflict masked from fallback",
310
consFallback: "root-disk=8G cpu-cores=4 instance-type=bar",
311
cons: "root-disk=8G mem=4G",
312
reds: []string{"mem", "arch"},
313
blues: []string{"instance-type"},
314
expected: "root-disk=8G cpu-cores=4 mem=4G",
316
desc: "both red conflicts used, blue mased from fallback",
317
consFallback: "root-disk=8G cpu-cores=4 instance-type=bar",
318
cons: "root-disk=8G arch=amd64 mem=4G",
319
reds: []string{"mem", "arch"},
320
blues: []string{"instance-type"},
321
expected: "root-disk=8G cpu-cores=4 arch=amd64 mem=4G",
325
func (s *validationSuite) TestMerge(c *gc.C) {
326
for i, t := range mergeTests {
327
c.Logf("test %d: %s", i, t.desc)
328
validator := constraints.NewValidator()
329
validator.RegisterConflicts(t.reds, t.blues)
330
consFallback := constraints.MustParse(t.consFallback)
331
cons := constraints.MustParse(t.cons)
332
merged, err := validator.Merge(consFallback, cons)
333
c.Assert(err, jc.ErrorIsNil)
334
expected := constraints.MustParse(t.expected)
335
c.Check(merged, gc.DeepEquals, expected)
339
func (s *validationSuite) TestMergeError(c *gc.C) {
340
validator := constraints.NewValidator()
341
validator.RegisterConflicts([]string{"instance-type"}, []string{"mem"})
342
consFallback := constraints.MustParse("instance-type=foo mem=4G")
343
cons := constraints.MustParse("cpu-cores=2")
344
_, err := validator.Merge(consFallback, cons)
345
c.Assert(err, gc.ErrorMatches, `ambiguous constraints: "instance-type" overlaps with "mem"`)
346
_, err = validator.Merge(cons, consFallback)
347
c.Assert(err, gc.ErrorMatches, `ambiguous constraints: "instance-type" overlaps with "mem"`)
350
func (s *validationSuite) TestUpdateVocabulary(c *gc.C) {
351
validator := constraints.NewValidator()
352
attributeName := "arch"
353
originalValues := []string{"amd64"}
354
validator.RegisterVocabulary(attributeName, originalValues)
356
cons := constraints.MustParse("arch=amd64")
357
_, err := validator.Validate(cons)
358
c.Assert(err, jc.ErrorIsNil)
360
cons2 := constraints.MustParse("arch=ppc64el")
361
_, err = validator.Validate(cons2)
362
c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(`invalid constraint value: arch=ppc64el
363
valid values are: [amd64]`))
365
additionalValues := []string{"ppc64el"}
366
validator.UpdateVocabulary(attributeName, additionalValues)
368
_, err = validator.Validate(cons)
369
c.Assert(err, jc.ErrorIsNil)
370
_, err = validator.Validate(cons2)
371
c.Assert(err, jc.ErrorIsNil)