github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/constraints/validation_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package constraints_test 5 6 import ( 7 "regexp" 8 9 jc "github.com/juju/testing/checkers" 10 gc "gopkg.in/check.v1" 11 12 "github.com/juju/juju/constraints" 13 ) 14 15 type validationSuite struct{} 16 17 var _ = gc.Suite(&validationSuite{}) 18 19 var validationTests = []struct { 20 cons string 21 unsupported []string 22 vocab map[string][]interface{} 23 reds []string 24 blues []string 25 err string 26 }{ 27 { 28 cons: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4", 29 }, 30 { 31 cons: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo", 32 unsupported: []string{"tags"}, 33 }, 34 { 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"}, 37 }, 38 { 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"`, 45 }, 46 { 47 cons: "root-disk=8G mem=4G arch=amd64 cpu-cores=4 instance-type=foo", 48 reds: []string{"mem", "arch"}, 49 err: "", 50 }, 51 { 52 cons: "root-disk=8G mem=4G arch=amd64 cpu-cores=4 instance-type=foo", 53 blues: []string{"mem", "arch"}, 54 err: "", 55 }, 56 { 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"`, 61 }, 62 { 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"`, 67 }, 68 { 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"`, 73 }, 74 { 75 cons: "arch=amd64 mem=4G cpu-cores=4", 76 vocab: map[string][]interface{}{"arch": {"amd64", "i386"}}, 77 }, 78 { 79 cons: "mem=4G cpu-cores=4", 80 vocab: map[string][]interface{}{"cpu-cores": {2, 4, 8}}, 81 }, 82 { 83 cons: "mem=4G instance-type=foo", 84 vocab: map[string][]interface{}{"instance-type": {"foo", "bar"}}, 85 }, 86 { 87 cons: "mem=4G tags=foo,bar", 88 vocab: map[string][]interface{}{"tags": {"foo", "bar", "another"}}, 89 }, 90 { 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:.*", 94 }, 95 { 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:.*", 99 }, 100 { 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:.*", 104 }, 105 { 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:.*", 109 }, 110 { 111 cons: "arch=i386 mem=4G instance-type=foo", 112 vocab: map[string][]interface{}{ 113 "instance-type": {"foo", "bar"}, 114 "arch": {"amd64", "i386"}}, 115 }, 116 { 117 cons: "virt-type=bar", 118 vocab: map[string][]interface{}{"virt-type": {"bar"}}, 119 }, 120 } 121 122 func (s *validationSuite) TestValidation(c *gc.C) { 123 for i, t := range validationTests { 124 c.Logf("test %d", i) 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) 130 } 131 cons := constraints.MustParse(t.cons) 132 unsupported, err := validator.Validate(cons) 133 if t.err == "" { 134 c.Assert(err, jc.ErrorIsNil) 135 c.Assert(unsupported, jc.SameContents, t.unsupported) 136 } else { 137 c.Assert(err, gc.ErrorMatches, t.err) 138 } 139 } 140 } 141 142 var mergeTests = []struct { 143 desc string 144 consFallback string 145 cons string 146 unsupported []string 147 reds []string 148 blues []string 149 expected string 150 }{ 151 { 152 desc: "empty all round", 153 }, { 154 desc: "container with empty fallback", 155 cons: "container=lxc", 156 expected: "container=lxc", 157 }, { 158 desc: "container from fallback", 159 consFallback: "container=lxc", 160 expected: "container=lxc", 161 }, { 162 desc: "arch with empty fallback", 163 cons: "arch=amd64", 164 expected: "arch=amd64", 165 }, { 166 desc: "arch with ignored fallback", 167 cons: "arch=amd64", 168 consFallback: "arch=i386", 169 expected: "arch=amd64", 170 }, { 171 desc: "arch from fallback", 172 consFallback: "arch=i386", 173 expected: "arch=i386", 174 }, { 175 desc: "instance type with empty fallback", 176 cons: "instance-type=foo", 177 expected: "instance-type=foo", 178 }, { 179 desc: "instance type with ignored fallback", 180 cons: "instance-type=foo", 181 consFallback: "instance-type=bar", 182 expected: "instance-type=foo", 183 }, { 184 desc: "instance type from fallback", 185 consFallback: "instance-type=foo", 186 expected: "instance-type=foo", 187 }, { 188 desc: "cpu-cores with empty fallback", 189 cons: "cpu-cores=2", 190 expected: "cpu-cores=2", 191 }, { 192 desc: "cpu-cores with ignored fallback", 193 cons: "cpu-cores=4", 194 consFallback: "cpu-cores=8", 195 expected: "cpu-cores=4", 196 }, { 197 desc: "cpu-cores from fallback", 198 consFallback: "cpu-cores=8", 199 expected: "cpu-cores=8", 200 }, { 201 desc: "cpu-power with empty fallback", 202 cons: "cpu-power=100", 203 expected: "cpu-power=100", 204 }, { 205 desc: "cpu-power with ignored fallback", 206 cons: "cpu-power=100", 207 consFallback: "cpu-power=200", 208 expected: "cpu-power=100", 209 }, { 210 desc: "cpu-power from fallback", 211 consFallback: "cpu-power=200", 212 expected: "cpu-power=200", 213 }, { 214 desc: "tags with empty fallback", 215 cons: "tags=foo,bar", 216 expected: "tags=foo,bar", 217 }, { 218 desc: "tags with ignored fallback", 219 cons: "tags=foo,bar", 220 consFallback: "tags=baz", 221 expected: "tags=foo,bar", 222 }, { 223 desc: "tags from fallback", 224 consFallback: "tags=foo,bar", 225 expected: "tags=foo,bar", 226 }, { 227 desc: "tags inital empty", 228 cons: "tags=", 229 consFallback: "tags=foo,bar", 230 expected: "tags=", 231 }, { 232 desc: "mem with empty fallback", 233 cons: "mem=4G", 234 expected: "mem=4G", 235 }, { 236 desc: "mem with ignored fallback", 237 cons: "mem=4G", 238 consFallback: "mem=8G", 239 expected: "mem=4G", 240 }, { 241 desc: "mem from fallback", 242 consFallback: "mem=8G", 243 expected: "mem=8G", 244 }, { 245 desc: "root-disk with empty fallback", 246 cons: "root-disk=4G", 247 expected: "root-disk=4G", 248 }, { 249 desc: "root-disk with ignored fallback", 250 cons: "root-disk=4G", 251 consFallback: "root-disk=8G", 252 expected: "root-disk=4G", 253 }, { 254 desc: "root-disk from fallback", 255 consFallback: "root-disk=8G", 256 expected: "root-disk=8G", 257 }, { 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", 262 }, { 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", 267 }, { 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", 273 }, { 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", 279 }, { 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", 286 }, { 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", 294 }, { 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", 301 }, { 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", 308 }, { 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", 315 }, { 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", 322 }, 323 } 324 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) 336 } 337 } 338 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"`) 348 } 349 350 func (s *validationSuite) TestUpdateVocabulary(c *gc.C) { 351 validator := constraints.NewValidator() 352 attributeName := "arch" 353 originalValues := []string{"amd64"} 354 validator.RegisterVocabulary(attributeName, originalValues) 355 356 cons := constraints.MustParse("arch=amd64") 357 _, err := validator.Validate(cons) 358 c.Assert(err, jc.ErrorIsNil) 359 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]`)) 364 365 additionalValues := []string{"ppc64el"} 366 validator.UpdateVocabulary(attributeName, additionalValues) 367 368 _, err = validator.Validate(cons) 369 c.Assert(err, jc.ErrorIsNil) 370 _, err = validator.Validate(cons2) 371 c.Assert(err, jc.ErrorIsNil) 372 }