github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/core/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 11 jc "github.com/juju/testing/checkers" 12 gc "gopkg.in/check.v1" 13 goyaml "gopkg.in/yaml.v2" 14 15 "github.com/juju/juju/core/constraints" 16 "github.com/juju/juju/core/instance" 17 ) 18 19 type ConstraintsSuite struct{} 20 21 var _ = gc.Suite(&ConstraintsSuite{}) 22 23 var parseConstraintsTests = []struct { 24 summary string 25 args []string 26 err string 27 result *constraints.Value 28 }{ 29 // Simple errors. 30 { 31 summary: "nothing at all", 32 }, { 33 summary: "empty", 34 args: []string{" "}, 35 }, { 36 summary: "complete nonsense", 37 args: []string{"cheese"}, 38 err: `malformed constraint "cheese"`, 39 }, { 40 summary: "missing name", 41 args: []string{"=cheese"}, 42 err: `malformed constraint "=cheese"`, 43 }, { 44 summary: "unknown constraint", 45 args: []string{"cheese=edam"}, 46 err: `unknown constraint "cheese"`, 47 }, 48 49 // "container" in detail. 50 { 51 summary: "set container empty", 52 args: []string{"container="}, 53 }, { 54 summary: "set container to none", 55 args: []string{"container=none"}, 56 }, { 57 summary: "set container lxd", 58 args: []string{"container=lxd"}, 59 }, { 60 summary: "set nonsense container", 61 args: []string{"container=foo"}, 62 err: `bad "container" constraint: invalid container type "foo"`, 63 }, { 64 summary: "double set container together", 65 args: []string{"container=lxd container=lxd"}, 66 err: `bad "container" constraint: already set`, 67 }, { 68 summary: "double set container separately", 69 args: []string{"container=lxd", "container="}, 70 err: `bad "container" constraint: already set`, 71 }, 72 73 // "arch" in detail. 74 { 75 summary: "set arch empty", 76 args: []string{"arch="}, 77 }, { 78 summary: "set arch amd64", 79 args: []string{"arch=amd64"}, 80 }, { 81 summary: "set arch arm64", 82 args: []string{"arch=arm64"}, 83 }, { 84 summary: "set nonsense arch 1", 85 args: []string{"arch=cheese"}, 86 err: `bad "arch" constraint: "cheese" not recognized`, 87 }, { 88 summary: "set nonsense arch 2", 89 args: []string{"arch=123.45"}, 90 err: `bad "arch" constraint: "123.45" not recognized`, 91 }, { 92 summary: "double set arch together", 93 args: []string{"arch=amd64 arch=amd64"}, 94 err: `bad "arch" constraint: already set`, 95 }, { 96 summary: "double set arch separately", 97 args: []string{"arch=arm64", "arch="}, 98 err: `bad "arch" constraint: already set`, 99 }, 100 101 // "cores" in detail. 102 { 103 summary: "set cores empty", 104 args: []string{"cores="}, 105 }, { 106 summary: "set cores zero", 107 args: []string{"cores=0"}, 108 }, { 109 summary: "set cores", 110 args: []string{"cores=4"}, 111 }, { 112 summary: "set nonsense cores 1", 113 args: []string{"cores=cheese"}, 114 err: `bad "cores" constraint: must be a non-negative integer`, 115 }, { 116 summary: "set nonsense cores 2", 117 args: []string{"cores=-1"}, 118 err: `bad "cores" constraint: must be a non-negative integer`, 119 }, { 120 summary: "set nonsense cores 3", 121 args: []string{"cores=123.45"}, 122 err: `bad "cores" constraint: must be a non-negative integer`, 123 }, { 124 summary: "double set cores together", 125 args: []string{"cores=128 cores=1"}, 126 err: `bad "cores" constraint: already set`, 127 }, { 128 summary: "double set cores separately", 129 args: []string{"cores=128", "cores=1"}, 130 err: `bad "cores" constraint: already set`, 131 }, 132 133 // "cpu-cores" 134 { 135 summary: "set cpu-cores", 136 args: []string{"cpu-cores=4"}, 137 }, 138 139 // "cpu-power" in detail. 140 { 141 summary: "set cpu-power empty", 142 args: []string{"cpu-power="}, 143 }, { 144 summary: "set cpu-power zero", 145 args: []string{"cpu-power=0"}, 146 }, { 147 summary: "set cpu-power", 148 args: []string{"cpu-power=44"}, 149 }, { 150 summary: "set nonsense cpu-power 1", 151 args: []string{"cpu-power=cheese"}, 152 err: `bad "cpu-power" constraint: must be a non-negative integer`, 153 }, { 154 summary: "set nonsense cpu-power 2", 155 args: []string{"cpu-power=-1"}, 156 err: `bad "cpu-power" constraint: must be a non-negative integer`, 157 }, { 158 summary: "double set cpu-power together", 159 args: []string{" cpu-power=300 cpu-power=1700 "}, 160 err: `bad "cpu-power" constraint: already set`, 161 }, { 162 summary: "double set cpu-power separately", 163 args: []string{"cpu-power=300 ", " cpu-power=1700"}, 164 err: `bad "cpu-power" constraint: already set`, 165 }, 166 167 // "mem" in detail. 168 { 169 summary: "set mem empty", 170 args: []string{"mem="}, 171 }, { 172 summary: "set mem zero", 173 args: []string{"mem=0"}, 174 }, { 175 summary: "set mem without suffix", 176 args: []string{"mem=512"}, 177 }, { 178 summary: "set mem with M suffix", 179 args: []string{"mem=512M"}, 180 }, { 181 summary: "set mem with G suffix", 182 args: []string{"mem=1.5G"}, 183 }, { 184 summary: "set mem with T suffix", 185 args: []string{"mem=36.2T"}, 186 }, { 187 summary: "set mem with P suffix", 188 args: []string{"mem=18.9P"}, 189 }, { 190 summary: "set nonsense mem 1", 191 args: []string{"mem=cheese"}, 192 err: `bad "mem" constraint: must be a non-negative float with optional M/G/T/P suffix`, 193 }, { 194 summary: "set nonsense mem 2", 195 args: []string{"mem=-1"}, 196 err: `bad "mem" constraint: must be a non-negative float with optional M/G/T/P suffix`, 197 }, { 198 summary: "set nonsense mem 3", 199 args: []string{"mem=32Y"}, 200 err: `bad "mem" constraint: must be a non-negative float with optional M/G/T/P suffix`, 201 }, { 202 summary: "double set mem together", 203 args: []string{"mem=1G mem=2G"}, 204 err: `bad "mem" constraint: already set`, 205 }, { 206 summary: "double set mem separately", 207 args: []string{"mem=1G", "mem=2G"}, 208 err: `bad "mem" constraint: already set`, 209 }, 210 211 // "root-disk" in detail. 212 { 213 summary: "set root-disk empty", 214 args: []string{"root-disk="}, 215 }, { 216 summary: "set root-disk zero", 217 args: []string{"root-disk=0"}, 218 }, { 219 summary: "set root-disk without suffix", 220 args: []string{"root-disk=512"}, 221 }, { 222 summary: "set root-disk with M suffix", 223 args: []string{"root-disk=512M"}, 224 }, { 225 summary: "set root-disk with G suffix", 226 args: []string{"root-disk=1.5G"}, 227 }, { 228 summary: "set root-disk with T suffix", 229 args: []string{"root-disk=36.2T"}, 230 }, { 231 summary: "set root-disk with P suffix", 232 args: []string{"root-disk=18.9P"}, 233 }, { 234 summary: "set nonsense root-disk 1", 235 args: []string{"root-disk=cheese"}, 236 err: `bad "root-disk" constraint: must be a non-negative float with optional M/G/T/P suffix`, 237 }, { 238 summary: "set nonsense root-disk 2", 239 args: []string{"root-disk=-1"}, 240 err: `bad "root-disk" constraint: must be a non-negative float with optional M/G/T/P suffix`, 241 }, { 242 summary: "set nonsense root-disk 3", 243 args: []string{"root-disk=32Y"}, 244 err: `bad "root-disk" constraint: must be a non-negative float with optional M/G/T/P suffix`, 245 }, { 246 summary: "double set root-disk together", 247 args: []string{"root-disk=1G root-disk=2G"}, 248 err: `bad "root-disk" constraint: already set`, 249 }, { 250 summary: "double set root-disk separately", 251 args: []string{"root-disk=1G", "root-disk=2G"}, 252 err: `bad "root-disk" constraint: already set`, 253 }, 254 255 // root-disk-source in detail. 256 { 257 summary: "set root-disk-source empty", 258 args: []string{"root-disk-source="}, 259 }, { 260 summary: "set root-disk-source to a value", 261 args: []string{"root-disk-source=sourcename"}, 262 }, { 263 summary: "double set root-disk-source together", 264 args: []string{"root-disk-source=sourcename root-disk-source=somethingelse"}, 265 err: `bad "root-disk-source" constraint: already set`, 266 }, { 267 summary: "double set root-disk-source separately", 268 args: []string{"root-disk-source=sourcename", "root-disk-source=somethingelse"}, 269 err: `bad "root-disk-source" constraint: already set`, 270 }, 271 272 // tags 273 { 274 summary: "single tag", 275 args: []string{"tags=foo"}, 276 }, { 277 summary: "multiple tags", 278 args: []string{"tags=foo,bar"}, 279 }, { 280 summary: "no tags", 281 args: []string{"tags="}, 282 }, 283 284 // spaces 285 { 286 summary: "single space", 287 args: []string{"spaces=space1"}, 288 }, { 289 summary: "multiple spaces - positive", 290 args: []string{"spaces=space1,space2"}, 291 }, { 292 summary: "multiple spaces - negative", 293 args: []string{"spaces=^dmz,^public"}, 294 }, { 295 summary: "multiple spaces - positive and negative", 296 args: []string{"spaces=admin,^area52,dmz,^public"}, 297 }, { 298 summary: "no spaces", 299 args: []string{"spaces="}, 300 }, 301 302 // instance roles 303 { 304 summary: "set instance role", 305 args: []string{"instance-role=foobarir"}, 306 }, { 307 summary: "instance role empty", 308 args: []string{"instance-role="}, 309 }, { 310 summary: "instance role auto", 311 args: []string{"instance-role=auto"}, 312 }, 313 314 // instance type 315 { 316 summary: "set instance type", 317 args: []string{"instance-type=foo"}, 318 }, { 319 summary: "instance type empty", 320 args: []string{"instance-type="}, 321 }, { 322 summary: "instance type with slash-escaped spaces", 323 args: []string{`instance-type=something\ with\ spaces`}, 324 }, 325 326 // "virt-type" in detail. 327 { 328 summary: "set virt-type empty", 329 args: []string{"virt-type="}, 330 }, { 331 summary: "set virt-type kvm", 332 args: []string{"virt-type=kvm"}, 333 }, { 334 summary: "set virt-type lxd", 335 args: []string{"virt-type=lxd"}, 336 }, { 337 summary: "double set virt-type together", 338 args: []string{"virt-type=kvm virt-type=kvm"}, 339 err: `bad "virt-type" constraint: already set`, 340 }, { 341 summary: "double set virt-type separately", 342 args: []string{"virt-type=kvm", "virt-type="}, 343 err: `bad "virt-type" constraint: already set`, 344 }, 345 346 // Zones 347 { 348 summary: "single zone", 349 args: []string{"zones=az1"}, 350 }, { 351 summary: "multiple zones", 352 args: []string{"zones=az1,az2"}, 353 }, { 354 summary: "zones with slash-escaped spaces", 355 args: []string{`zones=Availability\ zone\ 1`}, 356 }, { 357 summary: "Multiple zones with slash-escaped spaces", 358 args: []string{`zones=Availability\ zone\ 1,Availability\ zone\ 2,az2`}, 359 }, { 360 summary: "no zones", 361 args: []string{"zones="}, 362 }, 363 364 // AllocatePublicIP 365 { 366 summary: "set allocate-public-ip", 367 args: []string{"allocate-public-ip=true"}, 368 }, { 369 summary: "set nonsense allocate-public-ip", 370 args: []string{"allocate-public-ip=fred"}, 371 err: `bad "allocate-public-ip" constraint: must be 'true' or 'false'`, 372 }, { 373 summary: "try to set allocate-public-ip twice", 374 args: []string{"allocate-public-ip=true allocate-public-ip=false"}, 375 err: `bad "allocate-public-ip" constraint: already set`, 376 }, 377 378 // ImageID 379 { 380 summary: "set image-id", 381 args: []string{"image-id=ubuntu-bf2"}, 382 }, 383 { 384 summary: "set image-id", 385 args: []string{"image-id="}, 386 }, 387 { 388 summary: "set image-id", 389 args: []string{"image-id=ubuntu-bf2 image-id=ubuntu-bf1"}, 390 err: `bad "image-id" constraint: already set`, 391 }, 392 393 // Everything at once. 394 { 395 summary: "kitchen sink together", 396 args: []string{ 397 "root-disk=8G mem=2T arch=arm64 cores=4096 cpu-power=9001 container=lxd " + 398 "tags=foo,bar spaces=space1,^space2 instance-type=foo " + 399 "instance-role=foo1", 400 "virt-type=kvm zones=az1,az2 allocate-public-ip=true root-disk-source=sourcename image-id=ubuntu-bf2"}, 401 result: &constraints.Value{ 402 Arch: strp("arm64"), 403 Container: (*instance.ContainerType)(strp("lxd")), 404 CpuCores: uint64p(4096), 405 CpuPower: uint64p(9001), 406 Mem: uint64p(2 * 1024 * 1024), 407 RootDisk: uint64p(8192), 408 RootDiskSource: strp("sourcename"), 409 Tags: &[]string{"foo", "bar"}, 410 Spaces: &[]string{"space1", "^space2"}, 411 InstanceRole: strp("foo1"), 412 InstanceType: strp("foo"), 413 VirtType: strp("kvm"), 414 Zones: &[]string{"az1", "az2"}, 415 AllocatePublicIP: boolp(true), 416 ImageID: strp("ubuntu-bf2"), 417 }, 418 }, { 419 summary: "kitchen sink separately", 420 args: []string{ 421 "root-disk=8G", "mem=2T", "cores=4096", "cpu-power=9001", "arch=arm64", 422 "container=lxd", "tags=foo,bar", "spaces=space1,^space2", 423 "instance-type=foo", "virt-type=kvm", "zones=az1,az2", "allocate-public-ip=false", 424 "instance-role=foo2"}, 425 result: &constraints.Value{ 426 Arch: strp("arm64"), 427 Container: (*instance.ContainerType)(strp("lxd")), 428 CpuCores: uint64p(4096), 429 CpuPower: uint64p(9001), 430 Mem: uint64p(2 * 1024 * 1024), 431 RootDisk: uint64p(8192), 432 Tags: &[]string{"foo", "bar"}, 433 Spaces: &[]string{"space1", "^space2"}, 434 InstanceRole: strp("foo2"), 435 InstanceType: strp("foo"), 436 VirtType: strp("kvm"), 437 Zones: &[]string{"az1", "az2"}, 438 AllocatePublicIP: boolp(false), 439 }, 440 }, { 441 summary: "kitchen sink together with spaced zones", 442 args: []string{ 443 `root-disk=8G mem=2T arch=arm64 cores=4096 zones=Availability\ zone\ 1 cpu-power=9001 container=lxd ` + 444 "tags=foo,bar spaces=space1,^space2 instance-type=foo instance-role=foo3", 445 "virt-type=kvm"}, 446 result: &constraints.Value{ 447 Arch: strp("arm64"), 448 Container: (*instance.ContainerType)(strp("lxd")), 449 CpuCores: uint64p(4096), 450 CpuPower: uint64p(9001), 451 Mem: uint64p(2 * 1024 * 1024), 452 RootDisk: uint64p(8192), 453 Tags: &[]string{"foo", "bar"}, 454 Spaces: &[]string{"space1", "^space2"}, 455 InstanceRole: strp("foo3"), 456 InstanceType: strp("foo"), 457 VirtType: strp("kvm"), 458 Zones: &[]string{"Availability zone 1"}, 459 }, 460 }, 461 } 462 463 func (s *ConstraintsSuite) TestParseConstraints(c *gc.C) { 464 // TODO(dimitern): This test is inadequate and needs to check for 465 // more than just the reparsed output of String() matches the 466 // expected. 467 for i, t := range parseConstraintsTests { 468 c.Logf("test %d: %s", i, t.summary) 469 cons0, err := constraints.Parse(t.args...) 470 if t.err == "" { 471 c.Assert(err, jc.ErrorIsNil) 472 } else { 473 c.Assert(err, gc.ErrorMatches, t.err) 474 continue 475 } 476 if t.result != nil { 477 c.Check(cons0, gc.DeepEquals, *t.result) 478 } 479 cons1, err := constraints.Parse(cons0.String()) 480 c.Check(err, jc.ErrorIsNil) 481 c.Check(cons1, gc.DeepEquals, cons0) 482 } 483 } 484 485 func (s *ConstraintsSuite) TestParseAliases(c *gc.C) { 486 v, aliases, err := constraints.ParseWithAliases("cpu-cores=5 arch=amd64") 487 c.Assert(err, jc.ErrorIsNil) 488 c.Assert(v, gc.DeepEquals, constraints.Value{ 489 CpuCores: uint64p(5), 490 Arch: strp("amd64"), 491 }) 492 c.Assert(aliases, gc.DeepEquals, map[string]string{ 493 "cpu-cores": "cores", 494 }) 495 } 496 497 func (s *ConstraintsSuite) TestMerge(c *gc.C) { 498 con1 := constraints.MustParse("arch=amd64 mem=4G") 499 con2 := constraints.MustParse("cores=42") 500 con3 := constraints.MustParse( 501 "root-disk=8G container=lxd spaces=space1,^space2 image-id=ubuntu-bf2", 502 ) 503 merged, err := constraints.Merge(con1, con2) 504 c.Assert(err, jc.ErrorIsNil) 505 c.Assert(merged, jc.DeepEquals, constraints.MustParse("arch=amd64 mem=4G cores=42")) 506 merged, err = constraints.Merge(con1) 507 c.Assert(err, jc.ErrorIsNil) 508 c.Assert(merged, jc.DeepEquals, con1) 509 merged, err = constraints.Merge(con1, con2, con3) 510 c.Assert(err, jc.ErrorIsNil) 511 c.Assert(merged, jc.DeepEquals, constraints. 512 MustParse("arch=amd64 mem=4G cores=42 root-disk=8G container=lxd spaces=space1,^space2 image-id=ubuntu-bf2"), 513 ) 514 merged, err = constraints.Merge() 515 c.Assert(err, jc.ErrorIsNil) 516 c.Assert(merged, jc.DeepEquals, constraints.Value{}) 517 foo := "foo" 518 merged, err = constraints.Merge(constraints.Value{Arch: &foo}, con2) 519 c.Assert(err, gc.NotNil) 520 c.Assert(err, gc.ErrorMatches, `bad "arch" constraint: "foo" not recognized`) 521 c.Assert(merged, jc.DeepEquals, constraints.Value{}) 522 merged, err = constraints.Merge(con1, con1) 523 c.Assert(err, gc.NotNil) 524 c.Assert(err, gc.ErrorMatches, `bad "arch" constraint: already set`) 525 c.Assert(merged, jc.DeepEquals, constraints.Value{}) 526 } 527 528 func (s *ConstraintsSuite) TestParseInstanceTypeWithSpaces(c *gc.C) { 529 con := constraints.MustParse( 530 `arch=amd64 instance-type=with\ spaces cores=1`, 531 ) 532 c.Assert(con.Arch, gc.Not(gc.IsNil)) 533 c.Assert(con.InstanceType, gc.Not(gc.IsNil)) 534 c.Assert(con.CpuCores, gc.Not(gc.IsNil)) 535 c.Check(*con.Arch, gc.Equals, "amd64") 536 c.Check(*con.InstanceType, gc.Equals, "with spaces") 537 c.Check(*con.CpuCores, gc.Equals, uint64(1)) 538 } 539 540 func (s *ConstraintsSuite) TestParseMissingTagsAndSpaces(c *gc.C) { 541 con := constraints.MustParse("arch=amd64 mem=4G cores=1 root-disk=8G") 542 c.Check(con.Tags, gc.IsNil) 543 c.Check(con.Spaces, gc.IsNil) 544 } 545 546 func (s *ConstraintsSuite) TestParseNoTagsNoSpaces(c *gc.C) { 547 con := constraints.MustParse( 548 "arch=amd64 mem=4G cores=1 root-disk=8G tags= spaces=", 549 ) 550 c.Assert(con.Tags, gc.Not(gc.IsNil)) 551 c.Assert(con.Spaces, gc.Not(gc.IsNil)) 552 c.Check(*con.Tags, gc.HasLen, 0) 553 c.Check(*con.Spaces, gc.HasLen, 0) 554 } 555 556 func (s *ConstraintsSuite) TestIncludeExcludeAndHasSpaces(c *gc.C) { 557 con := constraints.MustParse("spaces=space1,^space2,space3,^space4") 558 c.Assert(con.Spaces, gc.Not(gc.IsNil)) 559 c.Check(*con.Spaces, gc.HasLen, 4) 560 c.Check(con.IncludeSpaces(), jc.SameContents, []string{"space1", "space3"}) 561 c.Check(con.ExcludeSpaces(), jc.SameContents, []string{"space2", "space4"}) 562 c.Check(con.HasSpaces(), jc.IsTrue) 563 con = constraints.MustParse("mem=4G") 564 c.Check(con.HasSpaces(), jc.IsFalse) 565 con = constraints.MustParse("mem=4G spaces=space-foo,^space-bar") 566 c.Check(con.IncludeSpaces(), jc.SameContents, []string{"space-foo"}) 567 c.Check(con.ExcludeSpaces(), jc.SameContents, []string{"space-bar"}) 568 c.Check(con.HasSpaces(), jc.IsTrue) 569 } 570 571 func (s *ConstraintsSuite) TestInvalidSpaces(c *gc.C) { 572 invalidNames := []string{ 573 "%$pace", "^foo#2", "+", "tcp:ip", 574 "^^myspace", "^^^^^^^^", "space^x", 575 "&-foo", "space/3", "^bar=4", "&#!", 576 } 577 for _, name := range invalidNames { 578 con, err := constraints.Parse("spaces=" + name) 579 expectName := strings.TrimPrefix(name, "^") 580 expectErr := fmt.Sprintf(`bad "spaces" constraint: %q is not a valid space name`, expectName) 581 c.Check(err, gc.NotNil) 582 c.Check(err.Error(), gc.Equals, expectErr) 583 c.Check(con, jc.DeepEquals, constraints.Value{}) 584 } 585 } 586 587 func (s *ConstraintsSuite) TestHasZones(c *gc.C) { 588 con := constraints.MustParse("zones=az1,az2,az3") 589 c.Assert(con.Zones, gc.Not(gc.IsNil)) 590 c.Check(*con.Zones, gc.HasLen, 3) 591 c.Check(con.HasZones(), jc.IsTrue) 592 593 con = constraints.MustParse("zones=") 594 c.Check(con.HasZones(), jc.IsFalse) 595 596 con = constraints.MustParse("spaces=space1,^space2") 597 c.Check(con.HasZones(), jc.IsFalse) 598 } 599 600 func (s *ConstraintsSuite) TestHasAllocatePublicIP(c *gc.C) { 601 con := constraints.MustParse("allocate-public-ip=true") 602 c.Assert(con.AllocatePublicIP, gc.Not(gc.IsNil)) 603 c.Check(con.HasAllocatePublicIP(), jc.IsTrue) 604 605 con = constraints.MustParse("allocate-public-ip=") 606 c.Check(con.HasAllocatePublicIP(), jc.IsFalse) 607 608 con = constraints.MustParse("spaces=space1,^space2") 609 c.Check(con.HasAllocatePublicIP(), jc.IsFalse) 610 } 611 612 func (s *ConstraintsSuite) TestHasRootDiskSource(c *gc.C) { 613 con := constraints.MustParse("root-disk-source=pilgrim") 614 c.Check(con.HasRootDiskSource(), jc.IsTrue) 615 con = constraints.MustParse("root-disk-source=") 616 c.Check(con.HasRootDiskSource(), jc.IsFalse) 617 con = constraints.MustParse("root-disk=32G") 618 c.Check(con.HasRootDiskSource(), jc.IsFalse) 619 } 620 621 func (s *ConstraintsSuite) TestHasRootDisk(c *gc.C) { 622 con := constraints.MustParse("root-disk=32G") 623 c.Check(con.HasRootDisk(), jc.IsTrue) 624 con = constraints.MustParse("root-disk=") 625 c.Check(con.HasRootDisk(), jc.IsFalse) 626 con = constraints.MustParse("root-disk-source=pilgrim") 627 c.Check(con.HasRootDisk(), jc.IsFalse) 628 } 629 630 func (s *ConstraintsSuite) TestHasImageID(c *gc.C) { 631 con := constraints.MustParse("image-id=ubuntu-bf2") 632 c.Check(con.HasImageID(), jc.IsTrue) 633 con = constraints.MustParse("image-id=") 634 c.Check(con.HasImageID(), jc.IsFalse) 635 con = constraints.MustParse("spaces=space1,^space2") 636 c.Check(con.HasImageID(), jc.IsFalse) 637 } 638 639 func (s *ConstraintsSuite) TestIsEmpty(c *gc.C) { 640 con := constraints.Value{} 641 c.Check(&con, jc.Satisfies, constraints.IsEmpty) 642 con = constraints.MustParse("arch=amd64") 643 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 644 con = constraints.MustParse("") 645 c.Check(&con, jc.Satisfies, constraints.IsEmpty) 646 con = constraints.MustParse("tags=") 647 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 648 con = constraints.MustParse("spaces=") 649 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 650 con = constraints.MustParse("mem=") 651 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 652 con = constraints.MustParse("arch=") 653 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 654 con = constraints.MustParse("root-disk=") 655 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 656 con = constraints.MustParse("cpu-power=") 657 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 658 con = constraints.MustParse("cores=") 659 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 660 con = constraints.MustParse("container=") 661 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 662 con = constraints.MustParse("instance-role=") 663 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 664 con = constraints.MustParse("instance-type=") 665 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 666 con = constraints.MustParse("zones=") 667 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 668 con = constraints.MustParse("allocate-public-ip=") 669 c.Check(&con, jc.Satisfies, constraints.IsEmpty) 670 con = constraints.MustParse("image-id=") 671 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 672 } 673 674 func boolp(b bool) *bool { 675 return &b 676 } 677 678 func uint64p(i uint64) *uint64 { 679 return &i 680 } 681 682 func strp(s string) *string { 683 return &s 684 } 685 686 func ctypep(ctype string) *instance.ContainerType { 687 res := instance.ContainerType(ctype) 688 return &res 689 } 690 691 type roundTrip struct { 692 Name string 693 Value constraints.Value 694 } 695 696 var constraintsRoundtripTests = []roundTrip{ 697 {"empty", constraints.Value{}}, 698 {"Arch1", constraints.Value{Arch: strp("")}}, 699 {"Arch2", constraints.Value{Arch: strp("amd64")}}, 700 {"Container1", constraints.Value{Container: ctypep("")}}, 701 {"Container2", constraints.Value{Container: ctypep("lxd")}}, 702 {"Container3", constraints.Value{Container: nil}}, 703 {"CpuCores1", constraints.Value{CpuCores: nil}}, 704 {"CpuCores2", constraints.Value{CpuCores: uint64p(0)}}, 705 {"CpuCores3", constraints.Value{CpuCores: uint64p(128)}}, 706 {"CpuPower1", constraints.Value{CpuPower: nil}}, 707 {"CpuPower2", constraints.Value{CpuPower: uint64p(0)}}, 708 {"CpuPower3", constraints.Value{CpuPower: uint64p(250)}}, 709 {"Mem1", constraints.Value{Mem: nil}}, 710 {"Mem2", constraints.Value{Mem: uint64p(0)}}, 711 {"Mem3", constraints.Value{Mem: uint64p(98765)}}, 712 {"RootDisk1", constraints.Value{RootDisk: nil}}, 713 {"RootDisk2", constraints.Value{RootDisk: uint64p(0)}}, 714 {"RootDisk2", constraints.Value{RootDisk: uint64p(109876)}}, 715 {"RootDiskSource1", constraints.Value{RootDiskSource: nil}}, 716 {"RootDiskSource2", constraints.Value{RootDiskSource: strp("identikit")}}, 717 {"Tags1", constraints.Value{Tags: nil}}, 718 {"Tags2", constraints.Value{Tags: &[]string{}}}, 719 {"Tags3", constraints.Value{Tags: &[]string{"foo", "bar"}}}, 720 {"Spaces1", constraints.Value{Spaces: nil}}, 721 {"Spaces2", constraints.Value{Spaces: &[]string{}}}, 722 {"Spaces3", constraints.Value{Spaces: &[]string{"space1", "^space2"}}}, 723 {"InstanceRole1", constraints.Value{InstanceRole: strp("")}}, 724 {"InstanceRole2", constraints.Value{InstanceRole: strp("foo")}}, 725 {"InstanceType1", constraints.Value{InstanceType: strp("")}}, 726 {"InstanceType2", constraints.Value{InstanceType: strp("foo")}}, 727 {"Zones1", constraints.Value{Zones: nil}}, 728 {"Zones2", constraints.Value{Zones: &[]string{}}}, 729 {"Zones3", constraints.Value{Zones: &[]string{"az1", "az2"}}}, 730 {"AllocatePublicIP1", constraints.Value{AllocatePublicIP: nil}}, 731 {"AllocatePublicIP2", constraints.Value{AllocatePublicIP: boolp(true)}}, 732 {"ImageID1", constraints.Value{ImageID: nil}}, 733 {"ImageID1", constraints.Value{ImageID: strp("")}}, 734 {"ImageID1", constraints.Value{ImageID: strp("ubuntu-bf2")}}, 735 {"All", constraints.Value{ 736 Arch: strp("arm64"), 737 Container: ctypep("lxd"), 738 CpuCores: uint64p(4096), 739 CpuPower: uint64p(9001), 740 Mem: uint64p(18000000000), 741 RootDisk: uint64p(24000000000), 742 RootDiskSource: strp("cave"), 743 Tags: &[]string{"foo", "bar"}, 744 Spaces: &[]string{"space1", "^space2"}, 745 InstanceType: strp("foo"), 746 Zones: &[]string{"az1", "az2"}, 747 AllocatePublicIP: boolp(true), 748 ImageID: strp("ubuntu-bf2"), 749 }}, 750 } 751 752 func (s *ConstraintsSuite) TestRoundtripGnuflagValue(c *gc.C) { 753 for _, t := range constraintsRoundtripTests { 754 c.Logf("test %s", t.Name) 755 var cons constraints.Value 756 val := constraints.ConstraintsValue{&cons} 757 err := val.Set(t.Value.String()) 758 c.Check(err, jc.ErrorIsNil) 759 c.Check(cons, jc.DeepEquals, t.Value) 760 } 761 } 762 763 func (s *ConstraintsSuite) TestRoundtripString(c *gc.C) { 764 for _, t := range constraintsRoundtripTests { 765 c.Logf("test %s", t.Name) 766 cons, err := constraints.Parse(t.Value.String()) 767 c.Check(err, jc.ErrorIsNil) 768 c.Check(cons, jc.DeepEquals, t.Value) 769 } 770 } 771 772 func (s *ConstraintsSuite) TestRoundtripJson(c *gc.C) { 773 for _, t := range constraintsRoundtripTests { 774 c.Logf("test %s", t.Name) 775 data, err := json.Marshal(t.Value) 776 c.Assert(err, jc.ErrorIsNil) 777 var cons constraints.Value 778 err = json.Unmarshal(data, &cons) 779 c.Check(err, jc.ErrorIsNil) 780 c.Check(cons, jc.DeepEquals, t.Value) 781 } 782 } 783 784 func (s *ConstraintsSuite) TestRoundtripYaml(c *gc.C) { 785 for _, t := range constraintsRoundtripTests { 786 c.Logf("test %s", t.Name) 787 data, err := goyaml.Marshal(t.Value) 788 c.Assert(err, jc.ErrorIsNil) 789 var cons constraints.Value 790 err = goyaml.Unmarshal(data, &cons) 791 c.Check(err, jc.ErrorIsNil) 792 c.Check(cons, jc.DeepEquals, t.Value) 793 } 794 } 795 796 var hasContainerTests = []struct { 797 constraints string 798 hasContainer bool 799 }{ 800 { 801 hasContainer: false, 802 }, { 803 constraints: "container=lxd", 804 hasContainer: true, 805 }, { 806 constraints: "container=none", 807 hasContainer: false, 808 }, 809 } 810 811 func (s *ConstraintsSuite) TestHasContainer(c *gc.C) { 812 for i, t := range hasContainerTests { 813 c.Logf("test %d", i) 814 cons := constraints.MustParse(t.constraints) 815 c.Check(cons.HasContainer(), gc.Equals, t.hasContainer) 816 } 817 } 818 819 func (s *ConstraintsSuite) TestHasInstanceType(c *gc.C) { 820 cons := constraints.MustParse("arch=amd64") 821 c.Check(cons.HasInstanceType(), jc.IsFalse) 822 cons = constraints.MustParse("arch=amd64 instance-type=foo") 823 c.Check(cons.HasInstanceType(), jc.IsTrue) 824 } 825 826 const initialWithoutCons = "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4 spaces=space1,^space2 tags=foo " + 827 "container=lxd instance-type=bar zones=az1,az2 allocate-public-ip=true image-id=ubuntu-bf2" 828 829 var withoutTests = []struct { 830 initial string 831 without []string 832 final string 833 }{{ 834 initial: initialWithoutCons, 835 without: []string{"root-disk"}, 836 final: "mem=4G arch=amd64 cpu-power=1000 cores=4 tags=foo spaces=space1,^space2 container=lxd instance-type=bar zones=az1,az2 allocate-public-ip=true image-id=ubuntu-bf2", 837 }, { 838 initial: initialWithoutCons, 839 without: []string{"mem"}, 840 final: "root-disk=8G arch=amd64 cpu-power=1000 cores=4 tags=foo spaces=space1,^space2 container=lxd instance-type=bar zones=az1,az2 allocate-public-ip=true image-id=ubuntu-bf2", 841 }, { 842 initial: initialWithoutCons, 843 without: []string{"arch"}, 844 final: "root-disk=8G mem=4G cpu-power=1000 cores=4 tags=foo spaces=space1,^space2 container=lxd instance-type=bar zones=az1,az2 allocate-public-ip=true image-id=ubuntu-bf2", 845 }, { 846 initial: initialWithoutCons, 847 without: []string{"cpu-power"}, 848 final: "root-disk=8G mem=4G arch=amd64 cores=4 tags=foo spaces=space1,^space2 container=lxd instance-type=bar zones=az1,az2 allocate-public-ip=true image-id=ubuntu-bf2", 849 }, { 850 initial: initialWithoutCons, 851 without: []string{"cores"}, 852 final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 tags=foo spaces=space1,^space2 container=lxd instance-type=bar zones=az1,az2 allocate-public-ip=true image-id=ubuntu-bf2", 853 }, { 854 initial: initialWithoutCons, 855 without: []string{"tags"}, 856 final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4 spaces=space1,^space2 container=lxd instance-type=bar zones=az1,az2 allocate-public-ip=true image-id=ubuntu-bf2", 857 }, { 858 initial: initialWithoutCons, 859 without: []string{"spaces"}, 860 final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4 tags=foo container=lxd instance-type=bar zones=az1,az2 allocate-public-ip=true image-id=ubuntu-bf2", 861 }, { 862 initial: initialWithoutCons, 863 without: []string{"container"}, 864 final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4 tags=foo spaces=space1,^space2 instance-type=bar zones=az1,az2 allocate-public-ip=true image-id=ubuntu-bf2", 865 }, { 866 initial: initialWithoutCons, 867 without: []string{"instance-type"}, 868 final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4 tags=foo spaces=space1,^space2 container=lxd zones=az1,az2 allocate-public-ip=true image-id=ubuntu-bf2", 869 }, { 870 initial: initialWithoutCons, 871 without: []string{"root-disk", "mem", "arch"}, 872 final: "cpu-power=1000 cores=4 tags=foo spaces=space1,^space2 container=lxd instance-type=bar zones=az1,az2 allocate-public-ip=true image-id=ubuntu-bf2", 873 }, { 874 initial: initialWithoutCons, 875 without: []string{"zones"}, 876 final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4 spaces=space1,^space2 tags=foo container=lxd instance-type=bar allocate-public-ip=true image-id=ubuntu-bf2", 877 }, { 878 initial: initialWithoutCons, 879 without: []string{"allocate-public-ip"}, 880 final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4 spaces=space1,^space2 tags=foo container=lxd instance-type=bar zones=az1,az2 image-id=ubuntu-bf2", 881 }, { 882 initial: initialWithoutCons, 883 without: []string{"image-id"}, 884 final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4 spaces=space1,^space2 tags=foo container=lxd instance-type=bar zones=az1,az2 allocate-public-ip=true", 885 }} 886 887 func (s *ConstraintsSuite) TestWithout(c *gc.C) { 888 for i, t := range withoutTests { 889 c.Logf("test %d", i) 890 initial := constraints.MustParse(t.initial) 891 final := constraints.Without(initial, t.without...) 892 c.Check(final, jc.DeepEquals, constraints.MustParse(t.final)) 893 } 894 } 895 896 var hasAnyTests = []struct { 897 cons string 898 attrs []string 899 expected []string 900 }{ 901 { 902 cons: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 spaces=space1,^space2 cores=4", 903 attrs: []string{"root-disk", "tags", "mem", "spaces"}, 904 expected: []string{"root-disk", "mem", "spaces"}, 905 }, 906 { 907 cons: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4 image-id=ubuntu-bf2", 908 attrs: []string{"root-disk", "tags", "mem", "image-id"}, 909 expected: []string{"root-disk", "mem", "image-id"}, 910 }, 911 { 912 cons: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4", 913 attrs: []string{"tags", "spaces"}, 914 expected: []string{}, 915 }, 916 } 917 918 func (s *ConstraintsSuite) TestHasAny(c *gc.C) { 919 for i, t := range hasAnyTests { 920 c.Logf("test %d", i) 921 cons := constraints.MustParse(t.cons) 922 obtained := constraints.HasAny(cons, t.attrs...) 923 c.Check(obtained, jc.DeepEquals, t.expected) 924 } 925 }