github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/core/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/core/constraints" 13 ) 14 15 type validationSuite struct{} 16 17 var _ = gc.Suite(&validationSuite{}) 18 19 var validationTests = []struct { 20 desc string 21 cons string 22 unsupported []string 23 vocab map[string][]interface{} 24 reds []string 25 blues []string 26 err string 27 }{ 28 { 29 desc: "base good", 30 cons: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4", 31 }, 32 { 33 desc: "unsupported", 34 cons: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4 tags=foo", 35 unsupported: []string{"tags"}, 36 }, 37 { 38 desc: "multiple unsupported", 39 cons: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4 instance-type=foo", 40 unsupported: []string{"cpu-power", "instance-type"}, 41 }, 42 { 43 desc: "Ambiguous constraint errors take precedence over unsupported errors.", 44 cons: "root-disk=8G mem=4G cores=4 instance-type=foo", 45 reds: []string{"mem", "arch"}, 46 blues: []string{"instance-type"}, 47 unsupported: []string{"cores"}, 48 err: `ambiguous constraints: "instance-type" overlaps with "mem"`, 49 }, 50 { 51 desc: "red conflicts", 52 cons: "root-disk=8G mem=4G arch=amd64 cores=4 instance-type=foo", 53 reds: []string{"mem", "arch"}, 54 }, 55 { 56 desc: "blue conflicts", 57 cons: "root-disk=8G mem=4G arch=amd64 cores=4 instance-type=foo", 58 blues: []string{"mem", "arch"}, 59 }, 60 { 61 desc: "red and blue conflicts", 62 cons: "root-disk=8G mem=4G arch=amd64 cores=4 instance-type=foo", 63 reds: []string{"mem", "arch"}, 64 blues: []string{"instance-type"}, 65 err: `ambiguous constraints: "arch" overlaps with "instance-type"`, 66 }, 67 { 68 desc: "ambiguous constraints red to blue", 69 cons: "root-disk=8G mem=4G arch=amd64 cores=4 instance-type=foo", 70 reds: []string{"instance-type"}, 71 blues: []string{"mem", "arch"}, 72 err: `ambiguous constraints: "arch" overlaps with "instance-type"`, 73 }, 74 { 75 desc: "ambiguous constraints blue to red", 76 cons: "root-disk=8G mem=4G cores=4 instance-type=foo", 77 reds: []string{"mem", "arch"}, 78 blues: []string{"instance-type"}, 79 err: `ambiguous constraints: "instance-type" overlaps with "mem"`, 80 }, 81 { 82 desc: "arch vocab", 83 cons: "arch=amd64 mem=4G cores=4", 84 vocab: map[string][]interface{}{"arch": {"amd64", "i386"}}, 85 }, 86 { 87 desc: "cores vocab", 88 cons: "mem=4G cores=4", 89 vocab: map[string][]interface{}{"cores": {2, 4, 8}}, 90 }, 91 { 92 desc: "instance-type vocab", 93 cons: "mem=4G instance-type=foo", 94 vocab: map[string][]interface{}{"instance-type": {"foo", "bar"}}, 95 }, 96 { 97 desc: "tags vocab", 98 cons: "mem=4G tags=foo,bar", 99 vocab: map[string][]interface{}{"tags": {"foo", "bar", "another"}}, 100 }, 101 { 102 desc: "invalid arch vocab", 103 cons: "arch=i386 mem=4G cores=4", 104 vocab: map[string][]interface{}{"arch": {"amd64"}}, 105 err: "invalid constraint value: arch=i386\nvalid values are:.*", 106 }, 107 { 108 desc: "invalid cores vocab", 109 cons: "mem=4G cores=5", 110 vocab: map[string][]interface{}{"cores": {2, 4, 8}}, 111 err: "invalid constraint value: cores=5\nvalid values are:.*", 112 }, 113 { 114 desc: "invalid instance-type vocab", 115 cons: "mem=4G instance-type=foo", 116 vocab: map[string][]interface{}{"instance-type": {"bar"}}, 117 err: "invalid constraint value: instance-type=foo\nvalid values are:.*", 118 }, 119 { 120 desc: "invalid tags vocab", 121 cons: "mem=4G tags=foo,other", 122 vocab: map[string][]interface{}{"tags": {"foo", "bar", "another"}}, 123 err: "invalid constraint value: tags=other\nvalid values are:.*", 124 }, 125 { 126 desc: "instance-type and arch", 127 cons: "arch=i386 mem=4G instance-type=foo", 128 vocab: map[string][]interface{}{ 129 "instance-type": {"foo", "bar"}, 130 "arch": {"amd64", "i386"}}, 131 }, 132 { 133 desc: "virt-type", 134 cons: "virt-type=bar", 135 vocab: map[string][]interface{}{"virt-type": {"bar"}}, 136 }, 137 } 138 139 func (s *validationSuite) TestValidation(c *gc.C) { 140 for i, t := range validationTests { 141 c.Logf("test %d: %s", i, t.desc) 142 validator := constraints.NewValidator() 143 validator.RegisterUnsupported(t.unsupported) 144 validator.RegisterConflicts(t.reds, t.blues) 145 for a, v := range t.vocab { 146 validator.RegisterVocabulary(a, v) 147 } 148 cons := constraints.MustParse(t.cons) 149 unsupported, err := validator.Validate(cons) 150 if t.err == "" { 151 c.Assert(err, jc.ErrorIsNil) 152 c.Assert(unsupported, jc.SameContents, t.unsupported) 153 } else { 154 c.Assert(err, gc.ErrorMatches, t.err) 155 } 156 } 157 } 158 159 var mergeTests = []struct { 160 desc string 161 consFallback string 162 cons string 163 unsupported []string 164 reds []string 165 blues []string 166 expected string 167 }{ 168 { 169 desc: "empty all round", 170 }, { 171 desc: "container with empty fallback", 172 cons: "container=lxd", 173 expected: "container=lxd", 174 }, { 175 desc: "container from fallback", 176 consFallback: "container=lxd", 177 expected: "container=lxd", 178 }, { 179 desc: "arch with empty fallback", 180 cons: "arch=amd64", 181 expected: "arch=amd64", 182 }, { 183 desc: "arch with ignored fallback", 184 cons: "arch=amd64", 185 consFallback: "arch=i386", 186 expected: "arch=amd64", 187 }, { 188 desc: "arch from fallback", 189 consFallback: "arch=i386", 190 expected: "arch=i386", 191 }, { 192 desc: "instance type with empty fallback", 193 cons: "instance-type=foo", 194 expected: "instance-type=foo", 195 }, { 196 desc: "instance type with ignored fallback", 197 cons: "instance-type=foo", 198 consFallback: "instance-type=bar", 199 expected: "instance-type=foo", 200 }, { 201 desc: "instance type from fallback", 202 consFallback: "instance-type=foo", 203 expected: "instance-type=foo", 204 }, { 205 desc: "cores with empty fallback", 206 cons: "cores=2", 207 expected: "cores=2", 208 }, { 209 desc: "cores with ignored fallback", 210 cons: "cores=4", 211 consFallback: "cores=8", 212 expected: "cores=4", 213 }, { 214 desc: "cores from fallback", 215 consFallback: "cores=8", 216 expected: "cores=8", 217 }, { 218 desc: "cpu-power with empty fallback", 219 cons: "cpu-power=100", 220 expected: "cpu-power=100", 221 }, { 222 desc: "cpu-power with ignored fallback", 223 cons: "cpu-power=100", 224 consFallback: "cpu-power=200", 225 expected: "cpu-power=100", 226 }, { 227 desc: "cpu-power from fallback", 228 consFallback: "cpu-power=200", 229 expected: "cpu-power=200", 230 }, { 231 desc: "tags with empty fallback", 232 cons: "tags=foo,bar", 233 expected: "tags=foo,bar", 234 }, { 235 desc: "tags with ignored fallback", 236 cons: "tags=foo,bar", 237 consFallback: "tags=baz", 238 expected: "tags=foo,bar", 239 }, { 240 desc: "tags from fallback", 241 consFallback: "tags=foo,bar", 242 expected: "tags=foo,bar", 243 }, { 244 desc: "tags initial empty", 245 cons: "tags=", 246 consFallback: "tags=foo,bar", 247 expected: "tags=", 248 }, { 249 desc: "mem with empty fallback", 250 cons: "mem=4G", 251 expected: "mem=4G", 252 }, { 253 desc: "mem with ignored fallback", 254 cons: "mem=4G", 255 consFallback: "mem=8G", 256 expected: "mem=4G", 257 }, { 258 desc: "mem from fallback", 259 consFallback: "mem=8G", 260 expected: "mem=8G", 261 }, { 262 desc: "root-disk with empty fallback", 263 cons: "root-disk=4G", 264 expected: "root-disk=4G", 265 }, { 266 desc: "root-disk with ignored fallback", 267 cons: "root-disk=4G", 268 consFallback: "root-disk=8G", 269 expected: "root-disk=4G", 270 }, { 271 desc: "root-disk from fallback", 272 consFallback: "root-disk=8G", 273 expected: "root-disk=8G", 274 }, { 275 desc: "non-overlapping mix", 276 cons: "root-disk=8G mem=4G arch=amd64", 277 consFallback: "cpu-power=1000 cores=4", 278 expected: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4", 279 }, { 280 desc: "overlapping mix", 281 cons: "root-disk=8G mem=4G arch=amd64", 282 consFallback: "cpu-power=1000 cores=4 mem=8G", 283 expected: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4", 284 }, { 285 desc: "fallback only, no conflicts", 286 consFallback: "root-disk=8G cores=4 instance-type=foo", 287 reds: []string{"mem", "arch"}, 288 blues: []string{"instance-type"}, 289 expected: "root-disk=8G cores=4 instance-type=foo", 290 }, { 291 desc: "no fallback, no conflicts", 292 cons: "root-disk=8G cores=4 instance-type=foo", 293 reds: []string{"mem", "arch"}, 294 blues: []string{"instance-type"}, 295 expected: "root-disk=8G cores=4 instance-type=foo", 296 }, { 297 desc: "conflict value from override", 298 consFallback: "root-disk=8G instance-type=foo", 299 cons: "root-disk=8G cores=4 instance-type=bar", 300 reds: []string{"mem", "arch"}, 301 blues: []string{"instance-type"}, 302 expected: "root-disk=8G cores=4 instance-type=bar", 303 }, { 304 desc: "unsupported attributes ignored", 305 consFallback: "root-disk=8G instance-type=foo", 306 cons: "root-disk=8G cores=4 instance-type=bar", 307 reds: []string{"mem", "arch"}, 308 blues: []string{"instance-type"}, 309 unsupported: []string{"instance-type"}, 310 expected: "root-disk=8G cores=4 instance-type=bar", 311 }, { 312 desc: "red conflict masked from fallback", 313 consFallback: "root-disk=8G mem=4G", 314 cons: "root-disk=8G cores=4 instance-type=bar", 315 reds: []string{"mem", "arch"}, 316 blues: []string{"instance-type"}, 317 expected: "root-disk=8G cores=4 instance-type=bar", 318 }, { 319 desc: "second red conflict masked from fallback", 320 consFallback: "root-disk=8G arch=amd64", 321 cons: "root-disk=8G cores=4 instance-type=bar", 322 reds: []string{"mem", "arch"}, 323 blues: []string{"instance-type"}, 324 expected: "root-disk=8G cores=4 instance-type=bar", 325 }, { 326 desc: "blue conflict masked from fallback", 327 consFallback: "root-disk=8G cores=4 instance-type=bar", 328 cons: "root-disk=8G mem=4G", 329 reds: []string{"mem", "arch"}, 330 blues: []string{"instance-type"}, 331 expected: "root-disk=8G cores=4 mem=4G", 332 }, { 333 desc: "both red conflicts used, blue mased from fallback", 334 consFallback: "root-disk=8G cores=4 instance-type=bar", 335 cons: "root-disk=8G arch=amd64 mem=4G", 336 reds: []string{"mem", "arch"}, 337 blues: []string{"instance-type"}, 338 expected: "root-disk=8G cores=4 arch=amd64 mem=4G", 339 }, 340 } 341 342 func (s *validationSuite) TestMerge(c *gc.C) { 343 for i, t := range mergeTests { 344 c.Logf("test %d: %s", i, t.desc) 345 validator := constraints.NewValidator() 346 validator.RegisterConflicts(t.reds, t.blues) 347 consFallback := constraints.MustParse(t.consFallback) 348 cons := constraints.MustParse(t.cons) 349 merged, err := validator.Merge(consFallback, cons) 350 c.Assert(err, jc.ErrorIsNil) 351 expected := constraints.MustParse(t.expected) 352 c.Check(merged, gc.DeepEquals, expected) 353 } 354 } 355 356 func (s *validationSuite) TestMergeError(c *gc.C) { 357 validator := constraints.NewValidator() 358 validator.RegisterConflicts([]string{"instance-type"}, []string{"mem"}) 359 consFallback := constraints.MustParse("instance-type=foo mem=4G") 360 cons := constraints.MustParse("cores=2") 361 _, err := validator.Merge(consFallback, cons) 362 c.Assert(err, gc.ErrorMatches, `ambiguous constraints: "instance-type" overlaps with "mem"`) 363 _, err = validator.Merge(cons, consFallback) 364 c.Assert(err, gc.ErrorMatches, `ambiguous constraints: "instance-type" overlaps with "mem"`) 365 } 366 367 func (s *validationSuite) TestUpdateVocabulary(c *gc.C) { 368 validator := constraints.NewValidator() 369 attributeName := "arch" 370 originalValues := []string{"amd64"} 371 validator.RegisterVocabulary(attributeName, originalValues) 372 373 cons := constraints.MustParse("arch=amd64") 374 _, err := validator.Validate(cons) 375 c.Assert(err, jc.ErrorIsNil) 376 377 cons2 := constraints.MustParse("arch=ppc64el") 378 _, err = validator.Validate(cons2) 379 c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(`invalid constraint value: arch=ppc64el 380 valid values are: [amd64]`)) 381 382 additionalValues := []string{"ppc64el"} 383 validator.UpdateVocabulary(attributeName, additionalValues) 384 385 _, err = validator.Validate(cons) 386 c.Assert(err, jc.ErrorIsNil) 387 _, err = validator.Validate(cons2) 388 c.Assert(err, jc.ErrorIsNil) 389 }