github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/constraints/constraints_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 "encoding/json" 8 "fmt" 9 "strings" 10 "testing" 11 12 jc "github.com/juju/testing/checkers" 13 gc "gopkg.in/check.v1" 14 goyaml "gopkg.in/yaml.v2" 15 16 "github.com/juju/juju/constraints" 17 "github.com/juju/juju/instance" 18 ) 19 20 func TestPackage(t *testing.T) { 21 gc.TestingT(t) 22 } 23 24 type ConstraintsSuite struct{} 25 26 var _ = gc.Suite(&ConstraintsSuite{}) 27 28 var parseConstraintsTests = []struct { 29 summary string 30 args []string 31 err string 32 }{ 33 // Simple errors. 34 { 35 summary: "nothing at all", 36 }, { 37 summary: "empty", 38 args: []string{" "}, 39 }, { 40 summary: "complete nonsense", 41 args: []string{"cheese"}, 42 err: `malformed constraint "cheese"`, 43 }, { 44 summary: "missing name", 45 args: []string{"=cheese"}, 46 err: `malformed constraint "=cheese"`, 47 }, { 48 summary: "unknown constraint", 49 args: []string{"cheese=edam"}, 50 err: `unknown constraint "cheese"`, 51 }, 52 53 // "container" in detail. 54 { 55 summary: "set container empty", 56 args: []string{"container="}, 57 }, { 58 summary: "set container to none", 59 args: []string{"container=none"}, 60 }, { 61 summary: "set container lxc", 62 args: []string{"container=lxc"}, 63 }, { 64 summary: "set nonsense container", 65 args: []string{"container=foo"}, 66 err: `bad "container" constraint: invalid container type "foo"`, 67 }, { 68 summary: "double set container together", 69 args: []string{"container=lxc container=lxc"}, 70 err: `bad "container" constraint: already set`, 71 }, { 72 summary: "double set container separately", 73 args: []string{"container=lxc", "container="}, 74 err: `bad "container" constraint: already set`, 75 }, 76 77 // "arch" in detail. 78 { 79 summary: "set arch empty", 80 args: []string{"arch="}, 81 }, { 82 summary: "set arch amd64", 83 args: []string{"arch=amd64"}, 84 }, { 85 summary: "set arch i386", 86 args: []string{"arch=i386"}, 87 }, { 88 summary: "set arch armhf", 89 args: []string{"arch=armhf"}, 90 }, { 91 summary: "set nonsense arch 1", 92 args: []string{"arch=cheese"}, 93 err: `bad "arch" constraint: "cheese" not recognized`, 94 }, { 95 summary: "set nonsense arch 2", 96 args: []string{"arch=123.45"}, 97 err: `bad "arch" constraint: "123.45" not recognized`, 98 }, { 99 summary: "double set arch together", 100 args: []string{"arch=amd64 arch=amd64"}, 101 err: `bad "arch" constraint: already set`, 102 }, { 103 summary: "double set arch separately", 104 args: []string{"arch=armhf", "arch="}, 105 err: `bad "arch" constraint: already set`, 106 }, 107 108 // "cpu-cores" in detail. 109 { 110 summary: "set cpu-cores empty", 111 args: []string{"cpu-cores="}, 112 }, { 113 summary: "set cpu-cores zero", 114 args: []string{"cpu-cores=0"}, 115 }, { 116 summary: "set cpu-cores", 117 args: []string{"cpu-cores=4"}, 118 }, { 119 summary: "set nonsense cpu-cores 1", 120 args: []string{"cpu-cores=cheese"}, 121 err: `bad "cpu-cores" constraint: must be a non-negative integer`, 122 }, { 123 summary: "set nonsense cpu-cores 2", 124 args: []string{"cpu-cores=-1"}, 125 err: `bad "cpu-cores" constraint: must be a non-negative integer`, 126 }, { 127 summary: "set nonsense cpu-cores 3", 128 args: []string{"cpu-cores=123.45"}, 129 err: `bad "cpu-cores" constraint: must be a non-negative integer`, 130 }, { 131 summary: "double set cpu-cores together", 132 args: []string{"cpu-cores=128 cpu-cores=1"}, 133 err: `bad "cpu-cores" constraint: already set`, 134 }, { 135 summary: "double set cpu-cores separately", 136 args: []string{"cpu-cores=128", "cpu-cores=1"}, 137 err: `bad "cpu-cores" constraint: already set`, 138 }, 139 140 // "cpu-power" in detail. 141 { 142 summary: "set cpu-power empty", 143 args: []string{"cpu-power="}, 144 }, { 145 summary: "set cpu-power zero", 146 args: []string{"cpu-power=0"}, 147 }, { 148 summary: "set cpu-power", 149 args: []string{"cpu-power=44"}, 150 }, { 151 summary: "set nonsense cpu-power 1", 152 args: []string{"cpu-power=cheese"}, 153 err: `bad "cpu-power" constraint: must be a non-negative integer`, 154 }, { 155 summary: "set nonsense cpu-power 2", 156 args: []string{"cpu-power=-1"}, 157 err: `bad "cpu-power" constraint: must be a non-negative integer`, 158 }, { 159 summary: "double set cpu-power together", 160 args: []string{" cpu-power=300 cpu-power=1700 "}, 161 err: `bad "cpu-power" constraint: already set`, 162 }, { 163 summary: "double set cpu-power separately", 164 args: []string{"cpu-power=300 ", " cpu-power=1700"}, 165 err: `bad "cpu-power" constraint: already set`, 166 }, 167 168 // "mem" in detail. 169 { 170 summary: "set mem empty", 171 args: []string{"mem="}, 172 }, { 173 summary: "set mem zero", 174 args: []string{"mem=0"}, 175 }, { 176 summary: "set mem without suffix", 177 args: []string{"mem=512"}, 178 }, { 179 summary: "set mem with M suffix", 180 args: []string{"mem=512M"}, 181 }, { 182 summary: "set mem with G suffix", 183 args: []string{"mem=1.5G"}, 184 }, { 185 summary: "set mem with T suffix", 186 args: []string{"mem=36.2T"}, 187 }, { 188 summary: "set mem with P suffix", 189 args: []string{"mem=18.9P"}, 190 }, { 191 summary: "set nonsense mem 1", 192 args: []string{"mem=cheese"}, 193 err: `bad "mem" constraint: must be a non-negative float with optional M/G/T/P suffix`, 194 }, { 195 summary: "set nonsense mem 2", 196 args: []string{"mem=-1"}, 197 err: `bad "mem" constraint: must be a non-negative float with optional M/G/T/P suffix`, 198 }, { 199 summary: "set nonsense mem 3", 200 args: []string{"mem=32Y"}, 201 err: `bad "mem" constraint: must be a non-negative float with optional M/G/T/P suffix`, 202 }, { 203 summary: "double set mem together", 204 args: []string{"mem=1G mem=2G"}, 205 err: `bad "mem" constraint: already set`, 206 }, { 207 summary: "double set mem separately", 208 args: []string{"mem=1G", "mem=2G"}, 209 err: `bad "mem" constraint: already set`, 210 }, 211 212 // "root-disk" in detail. 213 { 214 summary: "set root-disk empty", 215 args: []string{"root-disk="}, 216 }, { 217 summary: "set root-disk zero", 218 args: []string{"root-disk=0"}, 219 }, { 220 summary: "set root-disk without suffix", 221 args: []string{"root-disk=512"}, 222 }, { 223 summary: "set root-disk with M suffix", 224 args: []string{"root-disk=512M"}, 225 }, { 226 summary: "set root-disk with G suffix", 227 args: []string{"root-disk=1.5G"}, 228 }, { 229 summary: "set root-disk with T suffix", 230 args: []string{"root-disk=36.2T"}, 231 }, { 232 summary: "set root-disk with P suffix", 233 args: []string{"root-disk=18.9P"}, 234 }, { 235 summary: "set nonsense root-disk 1", 236 args: []string{"root-disk=cheese"}, 237 err: `bad "root-disk" constraint: must be a non-negative float with optional M/G/T/P suffix`, 238 }, { 239 summary: "set nonsense root-disk 2", 240 args: []string{"root-disk=-1"}, 241 err: `bad "root-disk" constraint: must be a non-negative float with optional M/G/T/P suffix`, 242 }, { 243 summary: "set nonsense root-disk 3", 244 args: []string{"root-disk=32Y"}, 245 err: `bad "root-disk" constraint: must be a non-negative float with optional M/G/T/P suffix`, 246 }, { 247 summary: "double set root-disk together", 248 args: []string{"root-disk=1G root-disk=2G"}, 249 err: `bad "root-disk" constraint: already set`, 250 }, { 251 summary: "double set root-disk separately", 252 args: []string{"root-disk=1G", "root-disk=2G"}, 253 err: `bad "root-disk" constraint: already set`, 254 }, 255 256 // tags 257 { 258 summary: "single tag", 259 args: []string{"tags=foo"}, 260 }, { 261 summary: "multiple tags", 262 args: []string{"tags=foo,bar"}, 263 }, { 264 summary: "no tags", 265 args: []string{"tags="}, 266 }, 267 268 // spaces 269 { 270 summary: "single space", 271 args: []string{"spaces=space1"}, 272 }, { 273 summary: "multiple spaces - positive", 274 args: []string{"spaces=space1,space2"}, 275 }, { 276 summary: "multiple spaces - negative", 277 args: []string{"spaces=^dmz,^public"}, 278 }, { 279 summary: "multiple spaces - positive and negative", 280 args: []string{"spaces=admin,^area52,dmz,^public"}, 281 }, { 282 summary: "no spaces", 283 args: []string{"spaces="}, 284 }, 285 286 // instance type 287 { 288 summary: "set instance type", 289 args: []string{"instance-type=foo"}, 290 }, { 291 summary: "instance type empty", 292 args: []string{"instance-type="}, 293 }, 294 295 // "virt-type" in detail. 296 { 297 summary: "set virt-type empty", 298 args: []string{"virt-type="}, 299 }, { 300 summary: "set virt-type kvm", 301 args: []string{"virt-type=kvm"}, 302 }, { 303 summary: "set virt-type lxd", 304 args: []string{"virt-type=lxd"}, 305 }, { 306 summary: "double set virt-type together", 307 args: []string{"virt-type=kvm virt-type=kvm"}, 308 err: `bad "virt-type" constraint: already set`, 309 }, { 310 summary: "double set virt-type separately", 311 args: []string{"virt-type=kvm", "virt-type="}, 312 err: `bad "virt-type" constraint: already set`, 313 }, 314 315 // Everything at once. 316 { 317 summary: "kitchen sink together", 318 args: []string{ 319 "root-disk=8G mem=2T arch=i386 cpu-cores=4096 cpu-power=9001 container=lxc " + 320 "tags=foo,bar spaces=space1,^space2 instance-type=foo", 321 "virt-type=kvm"}, 322 }, { 323 summary: "kitchen sink separately", 324 args: []string{ 325 "root-disk=8G", "mem=2T", "cpu-cores=4096", "cpu-power=9001", "arch=armhf", 326 "container=lxc", "tags=foo,bar", "spaces=space1,^space2", 327 "instance-type=foo", "virt-type=kvm"}, 328 }, 329 } 330 331 func (s *ConstraintsSuite) TestParseConstraints(c *gc.C) { 332 // TODO(dimitern): This test is inadequate and needs to check for 333 // more than just the reparsed output of String() matches the 334 // expected. 335 for i, t := range parseConstraintsTests { 336 c.Logf("test %d: %s", i, t.summary) 337 cons0, err := constraints.Parse(t.args...) 338 if t.err == "" { 339 c.Assert(err, jc.ErrorIsNil) 340 } else { 341 c.Assert(err, gc.ErrorMatches, t.err) 342 continue 343 } 344 cons1, err := constraints.Parse(cons0.String()) 345 c.Check(err, jc.ErrorIsNil) 346 c.Check(cons1, gc.DeepEquals, cons0) 347 } 348 } 349 350 func (s *ConstraintsSuite) TestMerge(c *gc.C) { 351 con1 := constraints.MustParse("arch=amd64 mem=4G") 352 con2 := constraints.MustParse("cpu-cores=42") 353 con3 := constraints.MustParse( 354 "root-disk=8G container=lxc spaces=space1,^space2", 355 ) 356 merged, err := constraints.Merge(con1, con2) 357 c.Assert(err, jc.ErrorIsNil) 358 c.Assert(merged, jc.DeepEquals, constraints.MustParse("arch=amd64 mem=4G cpu-cores=42")) 359 merged, err = constraints.Merge(con1) 360 c.Assert(err, jc.ErrorIsNil) 361 c.Assert(merged, jc.DeepEquals, con1) 362 merged, err = constraints.Merge(con1, con2, con3) 363 c.Assert(err, jc.ErrorIsNil) 364 c.Assert(merged, jc.DeepEquals, constraints. 365 MustParse("arch=amd64 mem=4G cpu-cores=42 root-disk=8G container=lxc spaces=space1,^space2"), 366 ) 367 merged, err = constraints.Merge() 368 c.Assert(err, jc.ErrorIsNil) 369 c.Assert(merged, jc.DeepEquals, constraints.Value{}) 370 foo := "foo" 371 merged, err = constraints.Merge(constraints.Value{Arch: &foo}, con2) 372 c.Assert(err, gc.NotNil) 373 c.Assert(err, gc.ErrorMatches, `bad "arch" constraint: "foo" not recognized`) 374 c.Assert(merged, jc.DeepEquals, constraints.Value{}) 375 merged, err = constraints.Merge(con1, con1) 376 c.Assert(err, gc.NotNil) 377 c.Assert(err, gc.ErrorMatches, `bad "arch" constraint: already set`) 378 c.Assert(merged, jc.DeepEquals, constraints.Value{}) 379 } 380 381 func (s *ConstraintsSuite) TestParseMissingTagsAndSpaces(c *gc.C) { 382 con := constraints.MustParse("arch=amd64 mem=4G cpu-cores=1 root-disk=8G") 383 c.Check(con.Tags, gc.IsNil) 384 c.Check(con.Spaces, gc.IsNil) 385 } 386 387 func (s *ConstraintsSuite) TestParseNoTagsNoSpaces(c *gc.C) { 388 con := constraints.MustParse( 389 "arch=amd64 mem=4G cpu-cores=1 root-disk=8G tags= spaces=", 390 ) 391 c.Assert(con.Tags, gc.Not(gc.IsNil)) 392 c.Assert(con.Spaces, gc.Not(gc.IsNil)) 393 c.Check(*con.Tags, gc.HasLen, 0) 394 c.Check(*con.Spaces, gc.HasLen, 0) 395 } 396 397 func (s *ConstraintsSuite) TestIncludeExcludeAndHaveSpaces(c *gc.C) { 398 con := constraints.MustParse("spaces=space1,^space2,space3,^space4") 399 c.Assert(con.Spaces, gc.Not(gc.IsNil)) 400 c.Check(*con.Spaces, gc.HasLen, 4) 401 c.Check(con.IncludeSpaces(), jc.SameContents, []string{"space1", "space3"}) 402 c.Check(con.ExcludeSpaces(), jc.SameContents, []string{"space2", "space4"}) 403 c.Check(con.HaveSpaces(), jc.IsTrue) 404 con = constraints.MustParse("mem=4G") 405 c.Check(con.HaveSpaces(), jc.IsFalse) 406 con = constraints.MustParse("mem=4G spaces=space-foo,^space-bar") 407 c.Check(con.IncludeSpaces(), jc.SameContents, []string{"space-foo"}) 408 c.Check(con.ExcludeSpaces(), jc.SameContents, []string{"space-bar"}) 409 c.Check(con.HaveSpaces(), jc.IsTrue) 410 } 411 412 func (s *ConstraintsSuite) TestInvalidSpaces(c *gc.C) { 413 invalidNames := []string{ 414 "%$pace", "^foo#2", "+", "tcp:ip", 415 "^^myspace", "^^^^^^^^", "space^x", 416 "&-foo", "space/3", "^bar=4", "&#!", 417 } 418 for _, name := range invalidNames { 419 con, err := constraints.Parse("spaces=" + name) 420 expectName := strings.TrimPrefix(name, "^") 421 expectErr := fmt.Sprintf(`bad "spaces" constraint: %q is not a valid space name`, expectName) 422 c.Check(err, gc.NotNil) 423 c.Check(err.Error(), gc.Equals, expectErr) 424 c.Check(con, jc.DeepEquals, constraints.Value{}) 425 } 426 } 427 428 func (s *ConstraintsSuite) TestIsEmpty(c *gc.C) { 429 con := constraints.Value{} 430 c.Check(&con, jc.Satisfies, constraints.IsEmpty) 431 con = constraints.MustParse("arch=amd64") 432 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 433 con = constraints.MustParse("") 434 c.Check(&con, jc.Satisfies, constraints.IsEmpty) 435 con = constraints.MustParse("tags=") 436 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 437 con = constraints.MustParse("spaces=") 438 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 439 con = constraints.MustParse("mem=") 440 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 441 con = constraints.MustParse("arch=") 442 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 443 con = constraints.MustParse("root-disk=") 444 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 445 con = constraints.MustParse("cpu-power=") 446 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 447 con = constraints.MustParse("cpu-cores=") 448 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 449 con = constraints.MustParse("container=") 450 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 451 con = constraints.MustParse("instance-type=") 452 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 453 } 454 455 func uint64p(i uint64) *uint64 { 456 return &i 457 } 458 459 func strp(s string) *string { 460 return &s 461 } 462 463 func ctypep(ctype string) *instance.ContainerType { 464 res := instance.ContainerType(ctype) 465 return &res 466 } 467 468 type roundTrip struct { 469 Name string 470 Value constraints.Value 471 } 472 473 var constraintsRoundtripTests = []roundTrip{ 474 {"empty", constraints.Value{}}, 475 {"Arch1", constraints.Value{Arch: strp("")}}, 476 {"Arch2", constraints.Value{Arch: strp("amd64")}}, 477 {"Container1", constraints.Value{Container: ctypep("")}}, 478 {"Container2", constraints.Value{Container: ctypep("lxc")}}, 479 {"Container3", constraints.Value{Container: nil}}, 480 {"CpuCores1", constraints.Value{CpuCores: nil}}, 481 {"CpuCores2", constraints.Value{CpuCores: uint64p(0)}}, 482 {"CpuCores3", constraints.Value{CpuCores: uint64p(128)}}, 483 {"CpuPower1", constraints.Value{CpuPower: nil}}, 484 {"CpuPower2", constraints.Value{CpuPower: uint64p(0)}}, 485 {"CpuPower3", constraints.Value{CpuPower: uint64p(250)}}, 486 {"Mem1", constraints.Value{Mem: nil}}, 487 {"Mem2", constraints.Value{Mem: uint64p(0)}}, 488 {"Mem3", constraints.Value{Mem: uint64p(98765)}}, 489 {"RootDisk1", constraints.Value{RootDisk: nil}}, 490 {"RootDisk2", constraints.Value{RootDisk: uint64p(0)}}, 491 {"RootDisk2", constraints.Value{RootDisk: uint64p(109876)}}, 492 {"Tags1", constraints.Value{Tags: nil}}, 493 {"Tags2", constraints.Value{Tags: &[]string{}}}, 494 {"Tags3", constraints.Value{Tags: &[]string{"foo", "bar"}}}, 495 {"Spaces1", constraints.Value{Spaces: nil}}, 496 {"Spaces2", constraints.Value{Spaces: &[]string{}}}, 497 {"Spaces3", constraints.Value{Spaces: &[]string{"space1", "^space2"}}}, 498 {"InstanceType1", constraints.Value{InstanceType: strp("")}}, 499 {"InstanceType2", constraints.Value{InstanceType: strp("foo")}}, 500 {"All", constraints.Value{ 501 Arch: strp("i386"), 502 Container: ctypep("lxc"), 503 CpuCores: uint64p(4096), 504 CpuPower: uint64p(9001), 505 Mem: uint64p(18000000000), 506 RootDisk: uint64p(24000000000), 507 Tags: &[]string{"foo", "bar"}, 508 Spaces: &[]string{"space1", "^space2"}, 509 InstanceType: strp("foo"), 510 }}, 511 } 512 513 func (s *ConstraintsSuite) TestRoundtripGnuflagValue(c *gc.C) { 514 for _, t := range constraintsRoundtripTests { 515 c.Logf("test %s", t.Name) 516 var cons constraints.Value 517 val := constraints.ConstraintsValue{&cons} 518 err := val.Set(t.Value.String()) 519 c.Check(err, jc.ErrorIsNil) 520 c.Check(cons, jc.DeepEquals, t.Value) 521 } 522 } 523 524 func (s *ConstraintsSuite) TestRoundtripString(c *gc.C) { 525 for _, t := range constraintsRoundtripTests { 526 c.Logf("test %s", t.Name) 527 cons, err := constraints.Parse(t.Value.String()) 528 c.Check(err, jc.ErrorIsNil) 529 c.Check(cons, jc.DeepEquals, t.Value) 530 } 531 } 532 533 func (s *ConstraintsSuite) TestRoundtripJson(c *gc.C) { 534 for _, t := range constraintsRoundtripTests { 535 c.Logf("test %s", t.Name) 536 data, err := json.Marshal(t.Value) 537 c.Assert(err, jc.ErrorIsNil) 538 var cons constraints.Value 539 err = json.Unmarshal(data, &cons) 540 c.Check(err, jc.ErrorIsNil) 541 c.Check(cons, jc.DeepEquals, t.Value) 542 } 543 } 544 545 func (s *ConstraintsSuite) TestRoundtripYaml(c *gc.C) { 546 for _, t := range constraintsRoundtripTests { 547 c.Logf("test %s", t.Name) 548 data, err := goyaml.Marshal(t.Value) 549 c.Assert(err, jc.ErrorIsNil) 550 var cons constraints.Value 551 err = goyaml.Unmarshal(data, &cons) 552 c.Check(err, jc.ErrorIsNil) 553 c.Check(cons, jc.DeepEquals, t.Value) 554 } 555 } 556 557 var hasContainerTests = []struct { 558 constraints string 559 hasContainer bool 560 }{ 561 { 562 hasContainer: false, 563 }, { 564 constraints: "container=lxc", 565 hasContainer: true, 566 }, { 567 constraints: "container=none", 568 hasContainer: false, 569 }, 570 } 571 572 func (s *ConstraintsSuite) TestHasContainer(c *gc.C) { 573 for i, t := range hasContainerTests { 574 c.Logf("test %d", i) 575 cons := constraints.MustParse(t.constraints) 576 c.Check(cons.HasContainer(), gc.Equals, t.hasContainer) 577 } 578 } 579 580 func (s *ConstraintsSuite) TestHasInstanceType(c *gc.C) { 581 cons := constraints.MustParse("arch=amd64") 582 c.Check(cons.HasInstanceType(), jc.IsFalse) 583 cons = constraints.MustParse("arch=amd64 instance-type=foo") 584 c.Check(cons.HasInstanceType(), jc.IsTrue) 585 } 586 587 const initialWithoutCons = "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 spaces=space1,^space2 tags=foo container=lxc instance-type=bar" 588 589 var withoutTests = []struct { 590 initial string 591 without []string 592 final string 593 }{{ 594 initial: initialWithoutCons, 595 without: []string{"root-disk"}, 596 final: "mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo spaces=space1,^space2 container=lxc instance-type=bar", 597 }, { 598 initial: initialWithoutCons, 599 without: []string{"mem"}, 600 final: "root-disk=8G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo spaces=space1,^space2 container=lxc instance-type=bar", 601 }, { 602 initial: initialWithoutCons, 603 without: []string{"arch"}, 604 final: "root-disk=8G mem=4G cpu-power=1000 cpu-cores=4 tags=foo spaces=space1,^space2 container=lxc instance-type=bar", 605 }, { 606 initial: initialWithoutCons, 607 without: []string{"cpu-power"}, 608 final: "root-disk=8G mem=4G arch=amd64 cpu-cores=4 tags=foo spaces=space1,^space2 container=lxc instance-type=bar", 609 }, { 610 initial: initialWithoutCons, 611 without: []string{"cpu-cores"}, 612 final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 tags=foo spaces=space1,^space2 container=lxc instance-type=bar", 613 }, { 614 initial: initialWithoutCons, 615 without: []string{"tags"}, 616 final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 spaces=space1,^space2 container=lxc instance-type=bar", 617 }, { 618 initial: initialWithoutCons, 619 without: []string{"spaces"}, 620 final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo container=lxc instance-type=bar", 621 }, { 622 initial: initialWithoutCons, 623 without: []string{"container"}, 624 final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo spaces=space1,^space2 instance-type=bar", 625 }, { 626 initial: initialWithoutCons, 627 without: []string{"instance-type"}, 628 final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo spaces=space1,^space2 container=lxc", 629 }, { 630 initial: initialWithoutCons, 631 without: []string{"root-disk", "mem", "arch"}, 632 final: "cpu-power=1000 cpu-cores=4 tags=foo spaces=space1,^space2 container=lxc instance-type=bar", 633 }} 634 635 func (s *ConstraintsSuite) TestWithout(c *gc.C) { 636 for i, t := range withoutTests { 637 c.Logf("test %d", i) 638 initial := constraints.MustParse(t.initial) 639 final, err := constraints.Without(initial, t.without...) 640 c.Assert(err, jc.ErrorIsNil) 641 c.Check(final, jc.DeepEquals, constraints.MustParse(t.final)) 642 } 643 } 644 645 func (s *ConstraintsSuite) TestWithoutError(c *gc.C) { 646 cons := constraints.MustParse("mem=4G") 647 _, err := constraints.Without(cons, "foo") 648 c.Assert(err, gc.ErrorMatches, `unknown constraint "foo"`) 649 } 650 651 func (s *ConstraintsSuite) TestAttributesWithValues(c *gc.C) { 652 for i, consStr := range []string{ 653 "", 654 "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 instance-type=foo tags=foo,bar spaces=space1,^space2", 655 } { 656 c.Logf("test %d", i) 657 cons := constraints.MustParse(consStr) 658 obtained := constraints.AttributesWithValues(cons) 659 assertMissing := func(attrName string) { 660 _, ok := obtained[attrName] 661 c.Check(ok, jc.IsFalse) 662 } 663 if cons.Arch != nil { 664 c.Check(obtained["arch"], gc.Equals, *cons.Arch) 665 } else { 666 assertMissing("arch") 667 } 668 if cons.Mem != nil { 669 c.Check(obtained["mem"], gc.Equals, *cons.Mem) 670 } else { 671 assertMissing("mem") 672 } 673 if cons.CpuCores != nil { 674 c.Check(obtained["cpu-cores"], gc.Equals, *cons.CpuCores) 675 } else { 676 assertMissing("cpu-cores") 677 } 678 if cons.CpuPower != nil { 679 c.Check(obtained["cpu-power"], gc.Equals, *cons.CpuPower) 680 } else { 681 assertMissing("cpu-power") 682 } 683 if cons.RootDisk != nil { 684 c.Check(obtained["root-disk"], gc.Equals, *cons.RootDisk) 685 } else { 686 assertMissing("root-disk") 687 } 688 if cons.Tags != nil { 689 c.Check(obtained["tags"], gc.DeepEquals, *cons.Tags) 690 } else { 691 assertMissing("tags") 692 } 693 if cons.Spaces != nil { 694 c.Check(obtained["spaces"], gc.DeepEquals, *cons.Spaces) 695 } else { 696 assertMissing("spaces") 697 } 698 if cons.InstanceType != nil { 699 c.Check(obtained["instance-type"], gc.Equals, *cons.InstanceType) 700 } else { 701 assertMissing("instance-type") 702 } 703 } 704 } 705 706 var hasAnyTests = []struct { 707 cons string 708 attrs []string 709 expected []string 710 }{ 711 { 712 cons: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 spaces=space1,^space2 cpu-cores=4", 713 attrs: []string{"root-disk", "tags", "mem", "spaces"}, 714 expected: []string{"root-disk", "mem", "spaces"}, 715 }, 716 { 717 cons: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4", 718 attrs: []string{"root-disk", "tags", "mem"}, 719 expected: []string{"root-disk", "mem"}, 720 }, 721 { 722 cons: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4", 723 attrs: []string{"tags", "spaces"}, 724 expected: []string{}, 725 }, 726 } 727 728 func (s *ConstraintsSuite) TestHasAny(c *gc.C) { 729 for i, t := range hasAnyTests { 730 c.Logf("test %d", i) 731 cons := constraints.MustParse(t.cons) 732 obtained := constraints.HasAny(cons, t.attrs...) 733 c.Check(obtained, jc.DeepEquals, t.expected) 734 } 735 }