go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gce/appengine/model/model_test.go (about) 1 // Copyright 2018 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package model 16 17 import ( 18 "context" 19 "testing" 20 21 "go.chromium.org/luci/gae/impl/memory" 22 "go.chromium.org/luci/gae/service/datastore" 23 computealpha "google.golang.org/api/compute/v0.alpha" 24 "google.golang.org/api/compute/v1" 25 "google.golang.org/protobuf/testing/protocmp" 26 27 "go.chromium.org/luci/gce/api/config/v1" 28 "go.chromium.org/luci/gce/api/projects/v1" 29 30 "github.com/google/go-cmp/cmp" 31 "github.com/google/go-cmp/cmp/cmpopts" 32 . "github.com/smartystreets/goconvey/convey" 33 . "go.chromium.org/luci/common/testing/assertions" 34 ) 35 36 func TestConfig(t *testing.T) { 37 t.Parallel() 38 39 Convey("Config", t, func() { 40 c := memory.Use(context.Background()) 41 cfg := &Config{ID: "id"} 42 err := datastore.Get(c, cfg) 43 So(err, ShouldEqual, datastore.ErrNoSuchEntity) 44 45 err = datastore.Put(c, &Config{ 46 ID: "id", 47 Config: &config.Config{ 48 Attributes: &config.VM{ 49 Disk: []*config.Disk{ 50 { 51 Image: "image", 52 }, 53 }, 54 Project: "project", 55 }, 56 Prefix: "prefix", 57 }, 58 }) 59 So(err, ShouldBeNil) 60 61 err = datastore.Get(c, cfg) 62 So(err, ShouldBeNil) 63 So(cmp.Diff(cfg, &Config{ 64 ID: "id", 65 Config: &config.Config{ 66 Attributes: &config.VM{ 67 Disk: []*config.Disk{ 68 { 69 Image: "image", 70 }, 71 }, 72 Project: "project", 73 }, 74 Prefix: "prefix", 75 }, 76 }, cmpopts.IgnoreUnexported(*cfg), protocmp.Transform()), ShouldBeEmpty) 77 }) 78 } 79 80 func TestProject(t *testing.T) { 81 t.Parallel() 82 83 Convey("Project", t, func() { 84 c := memory.Use(context.Background()) 85 p := &Project{ID: "id"} 86 err := datastore.Get(c, p) 87 So(err, ShouldEqual, datastore.ErrNoSuchEntity) 88 89 err = datastore.Put(c, &Project{ 90 ID: "id", 91 Config: &projects.Config{ 92 Metric: []string{ 93 "metric-1", 94 "metric-2", 95 }, 96 Project: "project", 97 Region: []string{ 98 "region-1", 99 "region-2", 100 }, 101 }, 102 }) 103 So(err, ShouldBeNil) 104 105 err = datastore.Get(c, p) 106 So(err, ShouldBeNil) 107 108 So(p.Config, ShouldResembleProto, &projects.Config{ 109 Metric: []string{ 110 "metric-1", 111 "metric-2", 112 }, 113 Project: "project", 114 Region: []string{ 115 "region-1", 116 "region-2", 117 }, 118 }) 119 }) 120 } 121 122 func TestVM(t *testing.T) { 123 t.Parallel() 124 125 Convey("VM", t, func() { 126 c := memory.Use(context.Background()) 127 v := &VM{ 128 ID: "id", 129 } 130 err := datastore.Get(c, v) 131 So(err, ShouldEqual, datastore.ErrNoSuchEntity) 132 133 err = datastore.Put(c, &VM{ 134 ID: "id", 135 Attributes: config.VM{ 136 Project: "project", 137 }, 138 }) 139 So(err, ShouldBeNil) 140 141 So(datastore.Get(c, v), ShouldBeNil) 142 143 So(cmp.Diff(v, &VM{ 144 ID: "id", 145 Attributes: config.VM{ 146 Project: "project", 147 }, 148 }, cmpopts.IgnoreUnexported(VM{}), protocmp.Transform()), ShouldBeEmpty) 149 150 Convey("IndexAttributes", func() { 151 v.IndexAttributes() 152 So(v.AttributesIndexed, ShouldBeEmpty) 153 154 v := &VM{ 155 ID: "id", 156 Attributes: config.VM{ 157 Disk: []*config.Disk{ 158 { 159 Image: "global/images/image-1", 160 }, 161 { 162 Image: "projects/project/global/images/image-2", 163 }, 164 }, 165 }, 166 } 167 v.IndexAttributes() 168 So(v.AttributesIndexed, ShouldResemble, []string{"disk.image:image-1", "disk.image:image-2"}) 169 }) 170 }) 171 172 Convey("getConfidentialInstanceConfig", t, func() { 173 Convey("zero", func() { 174 Convey("empty", func() { 175 v := &VM{} 176 c := v.getConfidentialInstanceConfig() 177 So(c, ShouldBeNil) 178 s := v.getScheduling() 179 So(s, ShouldBeNil) 180 }) 181 }) 182 Convey("EnableConfidentialCompute", func() { 183 v := &VM{ 184 Attributes: config.VM{ 185 EnableConfidentialCompute: true, 186 }, 187 } 188 c := v.getConfidentialInstanceConfig() 189 So(c, ShouldResemble, &compute.ConfidentialInstanceConfig{ 190 EnableConfidentialCompute: true, 191 }) 192 s := v.getScheduling() 193 So(s, ShouldResemble, &compute.Scheduling{ 194 NodeAffinities: []*compute.SchedulingNodeAffinity{}, 195 OnHostMaintenance: "TERMINATE", 196 }) 197 }) 198 }) 199 200 Convey("getDisks", t, func() { 201 Convey("zero", func() { 202 Convey("nil", func() { 203 v := &VM{} 204 d := v.getDisks() 205 So(d, ShouldHaveLength, 0) 206 }) 207 208 Convey("empty", func() { 209 v := &VM{ 210 Attributes: config.VM{ 211 Disk: []*config.Disk{}, 212 }, 213 } 214 d := v.getDisks() 215 So(d, ShouldHaveLength, 0) 216 }) 217 }) 218 219 Convey("non-zero", func() { 220 Convey("empty", func() { 221 v := &VM{ 222 Attributes: config.VM{ 223 Disk: []*config.Disk{ 224 {}, 225 }, 226 }, 227 } 228 d := v.getDisks() 229 So(d, ShouldHaveLength, 1) 230 So(d[0].AutoDelete, ShouldBeTrue) 231 So(d[0].Boot, ShouldBeTrue) 232 So(d[0].InitializeParams.DiskSizeGb, ShouldEqual, 0) 233 }) 234 235 Convey("non-empty", func() { 236 v := &VM{ 237 Attributes: config.VM{ 238 Disk: []*config.Disk{ 239 { 240 Image: "image", 241 }, 242 }, 243 }, 244 } 245 d := v.getDisks() 246 So(d, ShouldHaveLength, 1) 247 So(d[0].InitializeParams.SourceImage, ShouldEqual, "image") 248 }) 249 250 Convey("multi", func() { 251 v := &VM{ 252 Attributes: config.VM{ 253 Disk: []*config.Disk{ 254 {}, 255 {}, 256 }, 257 }, 258 } 259 d := v.getDisks() 260 So(d, ShouldHaveLength, 2) 261 So(d[0].Boot, ShouldBeTrue) 262 So(d[1].Boot, ShouldBeFalse) 263 }) 264 265 Convey("scratch", func() { 266 v := &VM{ 267 Attributes: config.VM{ 268 Disk: []*config.Disk{ 269 { 270 Type: "zones/zone/diskTypes/pd-ssd", 271 }, 272 { 273 Type: "zones/zone/diskTypes/local-ssd", 274 }, 275 { 276 Type: "zones/zone/diskTypes/pd-standard", 277 }, 278 }, 279 }, 280 } 281 d := v.getDisks() 282 So(d, ShouldHaveLength, 3) 283 So(d[0].Type, ShouldEqual, "") 284 So(d[1].Type, ShouldEqual, "SCRATCH") 285 So(d[2].Type, ShouldEqual, "") 286 }) 287 }) 288 }) 289 290 Convey("getMetadata", t, func() { 291 Convey("nil", func() { 292 v := &VM{} 293 m := v.getMetadata() 294 So(m, ShouldBeNil) 295 }) 296 297 Convey("empty", func() { 298 v := &VM{ 299 Attributes: config.VM{ 300 Metadata: []*config.Metadata{}, 301 }, 302 } 303 m := v.getMetadata() 304 So(m, ShouldBeNil) 305 }) 306 307 Convey("non-empty", func() { 308 Convey("empty-nil", func() { 309 v := &VM{ 310 Attributes: config.VM{ 311 Metadata: []*config.Metadata{ 312 {}, 313 }, 314 }, 315 } 316 m := v.getMetadata() 317 So(m.Items, ShouldHaveLength, 1) 318 So(m.Items[0].Key, ShouldEqual, "") 319 So(m.Items[0].Value, ShouldBeNil) 320 }) 321 322 Convey("key-nil", func() { 323 v := &VM{ 324 Attributes: config.VM{ 325 Metadata: []*config.Metadata{ 326 { 327 Metadata: &config.Metadata_FromText{ 328 FromText: "key", 329 }, 330 }, 331 }, 332 }, 333 } 334 m := v.getMetadata() 335 So(m.Items, ShouldHaveLength, 1) 336 So(m.Items[0].Key, ShouldEqual, "key") 337 So(m.Items[0].Value, ShouldBeNil) 338 }) 339 340 Convey("key-empty", func() { 341 v := &VM{ 342 Attributes: config.VM{ 343 Metadata: []*config.Metadata{ 344 { 345 Metadata: &config.Metadata_FromText{ 346 FromText: "key:", 347 }, 348 }, 349 }, 350 }, 351 } 352 m := v.getMetadata() 353 So(m.Items, ShouldHaveLength, 1) 354 So(m.Items[0].Key, ShouldEqual, "key") 355 So(*m.Items[0].Value, ShouldEqual, "") 356 }) 357 358 Convey("key-value", func() { 359 v := &VM{ 360 Attributes: config.VM{ 361 Metadata: []*config.Metadata{ 362 { 363 Metadata: &config.Metadata_FromText{ 364 FromText: "key:value", 365 }, 366 }, 367 }, 368 }, 369 } 370 m := v.getMetadata() 371 So(m.Items, ShouldHaveLength, 1) 372 So(m.Items[0].Key, ShouldEqual, "key") 373 So(*m.Items[0].Value, ShouldEqual, "value") 374 }) 375 376 Convey("empty-value", func() { 377 v := &VM{ 378 Attributes: config.VM{ 379 Metadata: []*config.Metadata{ 380 { 381 Metadata: &config.Metadata_FromText{ 382 FromText: ":value", 383 }, 384 }, 385 }, 386 }, 387 } 388 m := v.getMetadata() 389 So(m.Items, ShouldHaveLength, 1) 390 So(m.Items[0].Key, ShouldEqual, "") 391 So(*m.Items[0].Value, ShouldEqual, "value") 392 }) 393 394 Convey("from file", func() { 395 v := &VM{ 396 Attributes: config.VM{ 397 Metadata: []*config.Metadata{ 398 { 399 Metadata: &config.Metadata_FromFile{ 400 FromFile: "key:file", 401 }, 402 }, 403 }, 404 }, 405 } 406 m := v.getMetadata() 407 So(m.Items, ShouldHaveLength, 1) 408 So(m.Items[0].Key, ShouldEqual, "") 409 So(m.Items[0].Value, ShouldBeNil) 410 }) 411 412 Convey("empty with dut", func() { 413 v := &VM{ 414 Attributes: config.VM{ 415 Metadata: []*config.Metadata{}, 416 }, 417 DUT: "dut-1", 418 } 419 m := v.getMetadata() 420 So(m.Items, ShouldHaveLength, 1) 421 So(m.Items[0].Key, ShouldEqual, "dut") 422 So(*m.Items[0].Value, ShouldEqual, "dut-1") 423 }) 424 425 Convey("non-empty with dut", func() { 426 v := &VM{ 427 Attributes: config.VM{ 428 Metadata: []*config.Metadata{ 429 { 430 Metadata: &config.Metadata_FromText{ 431 FromText: "key:file", 432 }, 433 }, 434 }, 435 }, 436 DUT: "dut-1", 437 } 438 m := v.getMetadata() 439 So(m.Items, ShouldHaveLength, 2) 440 So(m.Items[0].Key, ShouldEqual, "key") 441 So(*m.Items[0].Value, ShouldEqual, "file") 442 So(m.Items[1].Key, ShouldEqual, "dut") 443 So(*m.Items[1].Value, ShouldEqual, "dut-1") 444 }) 445 }) 446 }) 447 448 Convey("getNetworkInterfaces", t, func() { 449 Convey("zero", func() { 450 Convey("nil", func() { 451 v := &VM{} 452 n := v.getNetworkInterfaces() 453 So(n, ShouldHaveLength, 0) 454 }) 455 456 Convey("empty", func() { 457 v := &VM{ 458 Attributes: config.VM{ 459 NetworkInterface: []*config.NetworkInterface{}, 460 }, 461 } 462 n := v.getNetworkInterfaces() 463 So(n, ShouldHaveLength, 0) 464 }) 465 }) 466 467 Convey("non-zero", func() { 468 Convey("empty", func() { 469 v := &VM{ 470 Attributes: config.VM{ 471 NetworkInterface: []*config.NetworkInterface{ 472 {}, 473 }, 474 }, 475 } 476 n := v.getNetworkInterfaces() 477 So(n, ShouldHaveLength, 1) 478 So(n[0].AccessConfigs, ShouldHaveLength, 0) 479 So(n[0].Network, ShouldEqual, "") 480 }) 481 482 Convey("non-empty", func() { 483 Convey("network and subnetwork", func() { 484 v := &VM{ 485 Attributes: config.VM{ 486 NetworkInterface: []*config.NetworkInterface{ 487 { 488 AccessConfig: []*config.AccessConfig{}, 489 Network: "network", 490 Subnetwork: "subnetwork", 491 }, 492 }, 493 }, 494 } 495 n := v.getNetworkInterfaces() 496 So(n, ShouldHaveLength, 1) 497 So(n[0].AccessConfigs, ShouldBeNil) 498 So(n[0].Network, ShouldEqual, "network") 499 So(n[0].Subnetwork, ShouldEqual, "subnetwork") 500 }) 501 502 Convey("network", func() { 503 v := &VM{ 504 Attributes: config.VM{ 505 NetworkInterface: []*config.NetworkInterface{ 506 { 507 AccessConfig: []*config.AccessConfig{}, 508 Network: "network", 509 }, 510 }, 511 }, 512 } 513 n := v.getNetworkInterfaces() 514 So(n, ShouldHaveLength, 1) 515 So(n[0].AccessConfigs, ShouldBeNil) 516 So(n[0].Network, ShouldEqual, "network") 517 }) 518 519 Convey("subnetwork", func() { 520 v := &VM{ 521 Attributes: config.VM{ 522 NetworkInterface: []*config.NetworkInterface{ 523 { 524 AccessConfig: []*config.AccessConfig{}, 525 Subnetwork: "subnetwork", 526 }, 527 }, 528 }, 529 } 530 n := v.getNetworkInterfaces() 531 So(n, ShouldHaveLength, 1) 532 So(n[0].AccessConfigs, ShouldBeNil) 533 So(n[0].Subnetwork, ShouldEqual, "subnetwork") 534 }) 535 536 Convey("access configs", func() { 537 v := &VM{ 538 Attributes: config.VM{ 539 NetworkInterface: []*config.NetworkInterface{ 540 { 541 AccessConfig: []*config.AccessConfig{ 542 { 543 Type: config.AccessConfigType_ONE_TO_ONE_NAT, 544 }, 545 }, 546 }, 547 }, 548 }, 549 } 550 n := v.getNetworkInterfaces() 551 So(n, ShouldHaveLength, 1) 552 So(n[0].AccessConfigs, ShouldHaveLength, 1) 553 So(n[0].AccessConfigs[0].Type, ShouldEqual, "ONE_TO_ONE_NAT") 554 }) 555 }) 556 }) 557 }) 558 559 Convey("getServiceAccounts", t, func() { 560 Convey("zero", func() { 561 Convey("nil", func() { 562 v := &VM{} 563 s := v.getServiceAccounts() 564 So(s, ShouldHaveLength, 0) 565 }) 566 567 Convey("empty", func() { 568 v := &VM{ 569 Attributes: config.VM{ 570 ServiceAccount: []*config.ServiceAccount{}, 571 }, 572 } 573 s := v.getServiceAccounts() 574 So(s, ShouldHaveLength, 0) 575 }) 576 }) 577 578 Convey("non-zero", func() { 579 Convey("empty", func() { 580 v := &VM{ 581 Attributes: config.VM{ 582 ServiceAccount: []*config.ServiceAccount{ 583 {}, 584 }, 585 }, 586 } 587 s := v.getServiceAccounts() 588 So(s, ShouldHaveLength, 1) 589 So(s[0].Email, ShouldEqual, "") 590 So(s[0].Scopes, ShouldHaveLength, 0) 591 }) 592 593 Convey("non-empty", func() { 594 Convey("email", func() { 595 v := &VM{ 596 Attributes: config.VM{ 597 ServiceAccount: []*config.ServiceAccount{ 598 { 599 Email: "email", 600 Scope: []string{}, 601 }, 602 }, 603 }, 604 } 605 s := v.getServiceAccounts() 606 So(s, ShouldHaveLength, 1) 607 So(s[0].Email, ShouldEqual, "email") 608 So(s[0].Scopes, ShouldHaveLength, 0) 609 }) 610 611 Convey("scopes", func() { 612 v := &VM{ 613 Attributes: config.VM{ 614 ServiceAccount: []*config.ServiceAccount{ 615 { 616 Scope: []string{ 617 "scope", 618 }, 619 }, 620 }, 621 }, 622 } 623 s := v.getServiceAccounts() 624 So(s, ShouldHaveLength, 1) 625 So(s[0].Email, ShouldEqual, "") 626 So(s[0].Scopes, ShouldHaveLength, 1) 627 So(s[0].Scopes[0], ShouldEqual, "scope") 628 }) 629 }) 630 }) 631 632 }) 633 634 Convey("getScheduling", t, func() { 635 Convey("zero", func() { 636 Convey("nil", func() { 637 v := &VM{} 638 s := v.getScheduling() 639 So(s, ShouldBeNil) 640 }) 641 642 Convey("empty", func() { 643 v := &VM{ 644 Attributes: config.VM{ 645 Scheduling: &config.Scheduling{ 646 NodeAffinity: []*config.NodeAffinity{}, 647 }, 648 }, 649 } 650 s := v.getScheduling() 651 So(s, ShouldBeNil) 652 }) 653 Convey("empty & Confidential", func() { 654 v := &VM{ 655 Attributes: config.VM{ 656 EnableConfidentialCompute: true, 657 }, 658 } 659 s := v.getScheduling() 660 So(s, ShouldResemble, &compute.Scheduling{ 661 NodeAffinities: []*compute.SchedulingNodeAffinity{}, 662 OnHostMaintenance: "TERMINATE", 663 }) 664 }) 665 Convey("empty & Terminatable", func() { 666 v := &VM{ 667 Attributes: config.VM{ 668 TerminateOnMaintenance: true, 669 }, 670 } 671 s := v.getScheduling() 672 So(s, ShouldResemble, &compute.Scheduling{ 673 NodeAffinities: []*compute.SchedulingNodeAffinity{}, 674 OnHostMaintenance: "TERMINATE", 675 }) 676 }) 677 Convey("empty & Confidential & Terminatable", func() { 678 v := &VM{ 679 Attributes: config.VM{ 680 EnableConfidentialCompute: true, 681 TerminateOnMaintenance: true, 682 }, 683 } 684 s := v.getScheduling() 685 So(s, ShouldResemble, &compute.Scheduling{ 686 NodeAffinities: []*compute.SchedulingNodeAffinity{}, 687 OnHostMaintenance: "TERMINATE", 688 }) 689 }) 690 }) 691 692 Convey("non-zero", func() { 693 Convey("empty", func() { 694 v := &VM{ 695 Attributes: config.VM{ 696 Scheduling: &config.Scheduling{ 697 NodeAffinity: []*config.NodeAffinity{ 698 {}, 699 }, 700 }, 701 }, 702 } 703 s := v.getScheduling() 704 So(s, ShouldNotBeNil) 705 So(s.NodeAffinities, ShouldHaveLength, 1) 706 So(s.NodeAffinities[0].Key, ShouldEqual, "") 707 So(s.NodeAffinities[0].Operator, ShouldEqual, "OPERATOR_UNSPECIFIED") 708 So(s.NodeAffinities[0].Values, ShouldHaveLength, 0) 709 }) 710 711 Convey("non-empty", func() { 712 Convey("key", func() { 713 v := &VM{ 714 Attributes: config.VM{ 715 Scheduling: &config.Scheduling{ 716 NodeAffinity: []*config.NodeAffinity{ 717 { 718 Key: "node-affinity-key", 719 }, 720 }, 721 }, 722 }, 723 } 724 s := v.getScheduling() 725 So(s.NodeAffinities, ShouldHaveLength, 1) 726 So(s.NodeAffinities[0].Key, ShouldEqual, "node-affinity-key") 727 }) 728 Convey("operator", func() { 729 v := &VM{ 730 Attributes: config.VM{ 731 Scheduling: &config.Scheduling{ 732 NodeAffinity: []*config.NodeAffinity{ 733 { 734 Operator: config.NodeAffinityOperator_IN, 735 }, 736 }, 737 }, 738 }, 739 } 740 s := v.getScheduling() 741 So(s.NodeAffinities, ShouldHaveLength, 1) 742 So(s.NodeAffinities[0].Operator, ShouldEqual, "IN") 743 }) 744 Convey("values", func() { 745 v := &VM{ 746 Attributes: config.VM{ 747 Scheduling: &config.Scheduling{ 748 NodeAffinity: []*config.NodeAffinity{ 749 { 750 Values: []string{"node-affinity-value"}, 751 }, 752 }, 753 }, 754 }, 755 } 756 s := v.getScheduling() 757 So(s.NodeAffinities, ShouldHaveLength, 1) 758 So(s.NodeAffinities[0].Values, ShouldHaveLength, 1) 759 So(s.NodeAffinities[0].Values[0], ShouldEqual, "node-affinity-value") 760 }) 761 Convey("not-empty other cases", func() { 762 inScheduling := &config.Scheduling{ 763 NodeAffinity: []*config.NodeAffinity{{Key: "node-affinity-key"}}, 764 } 765 Convey("Confidential", func() { 766 v := &VM{ 767 Attributes: config.VM{ 768 Scheduling: inScheduling, 769 EnableConfidentialCompute: true, 770 }, 771 } 772 s := v.getScheduling() 773 So(s.NodeAffinities, ShouldHaveLength, 1) 774 So(s.NodeAffinities[0].Key, ShouldEqual, "node-affinity-key") 775 So(s.OnHostMaintenance, ShouldEqual, "TERMINATE") 776 }) 777 Convey("Terminatable", func() { 778 v := &VM{ 779 Attributes: config.VM{ 780 Scheduling: inScheduling, 781 TerminateOnMaintenance: true, 782 }, 783 } 784 s := v.getScheduling() 785 So(s.NodeAffinities, ShouldHaveLength, 1) 786 So(s.NodeAffinities[0].Key, ShouldEqual, "node-affinity-key") 787 So(s.OnHostMaintenance, ShouldEqual, "TERMINATE") 788 }) 789 Convey("Confidential & Terminatable", func() { 790 v := &VM{ 791 Attributes: config.VM{ 792 Scheduling: inScheduling, 793 EnableConfidentialCompute: true, 794 TerminateOnMaintenance: true, 795 }, 796 } 797 s := v.getScheduling() 798 So(s.NodeAffinities, ShouldHaveLength, 1) 799 So(s.NodeAffinities[0].Key, ShouldEqual, "node-affinity-key") 800 So(s.OnHostMaintenance, ShouldEqual, "TERMINATE") 801 }) 802 }) 803 }) 804 }) 805 }) 806 807 Convey("getShieldedInstanceConfig", t, func() { 808 Convey("zero", func() { 809 Convey("empty", func() { 810 v := &VM{} 811 c := v.getShieldedInstanceConfig() 812 So(c, ShouldBeNil) 813 }) 814 }) 815 Convey("non-zero", func() { 816 Convey("disableIntegrityMonitoring", func() { 817 v := &VM{ 818 Attributes: config.VM{ 819 DisableIntegrityMonitoring: true, 820 }, 821 } 822 c := v.getShieldedInstanceConfig() 823 So(c, ShouldResemble, &compute.ShieldedInstanceConfig{ 824 EnableIntegrityMonitoring: false, 825 EnableSecureBoot: false, 826 EnableVtpm: true, 827 }) 828 }) 829 Convey("enableSecureBoot", func() { 830 v := &VM{ 831 Attributes: config.VM{ 832 EnableSecureBoot: true, 833 }, 834 } 835 c := v.getShieldedInstanceConfig() 836 So(c, ShouldResemble, &compute.ShieldedInstanceConfig{ 837 EnableIntegrityMonitoring: true, 838 EnableSecureBoot: true, 839 EnableVtpm: true, 840 }) 841 }) 842 Convey("disablevTPM", func() { 843 v := &VM{ 844 Attributes: config.VM{ 845 DisableVtpm: true, 846 }, 847 } 848 c := v.getShieldedInstanceConfig() 849 So(c, ShouldResemble, &compute.ShieldedInstanceConfig{ 850 EnableIntegrityMonitoring: true, 851 EnableSecureBoot: false, 852 EnableVtpm: false, 853 }) 854 }) 855 }) 856 }) 857 858 Convey("getTags", t, func() { 859 Convey("zero", func() { 860 Convey("nil", func() { 861 v := &VM{} 862 t := v.getTags() 863 So(t, ShouldBeNil) 864 }) 865 866 Convey("empty", func() { 867 v := &VM{ 868 Attributes: config.VM{ 869 Tag: []string{}, 870 }, 871 } 872 t := v.getTags() 873 So(t, ShouldBeNil) 874 }) 875 }) 876 877 Convey("non-zero", func() { 878 v := &VM{ 879 Attributes: config.VM{ 880 Tag: []string{ 881 "tag", 882 }, 883 }, 884 } 885 t := v.getTags() 886 So(t.Items, ShouldHaveLength, 1) 887 So(t.Items[0], ShouldEqual, "tag") 888 }) 889 }) 890 891 Convey("GetLabels", t, func() { 892 Convey("zero", func() { 893 Convey("nil", func() { 894 v := &VM{} 895 l := v.getLabels() 896 So(l, ShouldBeEmpty) 897 }) 898 899 Convey("empty", func() { 900 v := &VM{ 901 Attributes: config.VM{ 902 Label: map[string]string{}, 903 }, 904 } 905 l := v.getLabels() 906 So(l, ShouldBeEmpty) 907 }) 908 }) 909 910 Convey("non-zero", func() { 911 v := &VM{ 912 Attributes: config.VM{ 913 Label: map[string]string{"key1": "value1"}, 914 }, 915 } 916 l := v.getLabels() 917 So(l, ShouldHaveLength, 1) 918 So(l, ShouldContainKey, "key1") 919 So(l["key1"], ShouldEqual, "value1") 920 }) 921 }) 922 923 Convey("GetInstance", t, func() { 924 Convey("empty", func() { 925 v := &VM{} 926 i := v.GetInstance().Stable 927 So(i.Disks, ShouldHaveLength, 0) 928 So(i.MachineType, ShouldEqual, "") 929 So(i.Metadata, ShouldBeNil) 930 So(i.MinCpuPlatform, ShouldEqual, "") 931 So(i.NetworkInterfaces, ShouldHaveLength, 0) 932 So(i.Scheduling, ShouldBeNil) 933 So(i.ServiceAccounts, ShouldBeNil) 934 So(i.ShieldedInstanceConfig, ShouldBeNil) 935 So(i.Tags, ShouldBeNil) 936 So(i.Labels, ShouldBeNil) 937 So(i.ForceSendFields, ShouldBeNil) 938 }) 939 940 Convey("non-empty", func() { 941 v := &VM{ 942 Attributes: config.VM{ 943 Disk: []*config.Disk{ 944 { 945 Image: "image", 946 Size: 100, 947 }, 948 }, 949 EnableSecureBoot: true, 950 MachineType: "type", 951 MinCpuPlatform: "plat", 952 NetworkInterface: []*config.NetworkInterface{ 953 { 954 AccessConfig: []*config.AccessConfig{ 955 {}, 956 }, 957 Network: "network", 958 }, 959 }, 960 Scheduling: &config.Scheduling{ 961 NodeAffinity: []*config.NodeAffinity{ 962 {}, 963 }, 964 }, 965 ForceSendFields: []string{ 966 "a", 967 "b", 968 "c", 969 }, 970 NullFields: []string{ 971 "e", 972 }, 973 }, 974 } 975 i := v.GetInstance().Stable 976 So(i.Disks, ShouldHaveLength, 1) 977 So(i.MachineType, ShouldEqual, "type") 978 So(i.Metadata, ShouldBeNil) 979 So(i.MinCpuPlatform, ShouldEqual, "plat") 980 So(i.NetworkInterfaces, ShouldHaveLength, 1) 981 So(i.ServiceAccounts, ShouldBeNil) 982 So(i.Scheduling, ShouldNotBeNil) 983 So(i.Scheduling.NodeAffinities, ShouldHaveLength, 1) 984 So(i.ShieldedInstanceConfig, ShouldNotBeNil) 985 So(i.Tags, ShouldBeNil) 986 So(i.Labels, ShouldBeNil) 987 So(i.ForceSendFields, ShouldNotBeNil) 988 So(i.NullFields, ShouldNotBeNil) 989 }) 990 }) 991 } 992 993 // TestGetInstanceReturnsAlpha tests the conversion of various input VMs to the 994 // alpha GCP API. 995 func TestGetInstanceReturnsAlpha(t *testing.T) { 996 t.Parallel() 997 998 cases := []struct { 999 name string 1000 input *VM 1001 output *computealpha.Instance 1002 }{ 1003 { 1004 name: "only alpha", 1005 input: &VM{ 1006 Attributes: config.VM{ 1007 GcpChannel: config.GCPChannel_GCP_CHANNEL_ALPHA, 1008 }, 1009 }, 1010 output: &computealpha.Instance{}, 1011 }, 1012 { 1013 name: "hostname", 1014 input: &VM{ 1015 Hostname: "awesome-host", 1016 Attributes: config.VM{ 1017 GcpChannel: config.GCPChannel_GCP_CHANNEL_ALPHA, 1018 }, 1019 }, 1020 output: &computealpha.Instance{ 1021 Name: "awesome-host", 1022 }, 1023 }, 1024 } 1025 1026 for _, tt := range cases { 1027 tt := tt 1028 t.Run(tt.name, func(t *testing.T) { 1029 t.Parallel() 1030 expected := tt.output 1031 actual := tt.input.GetInstance().Alpha 1032 1033 if diff := cmp.Diff(expected, actual, protocmp.Transform()); diff != "" { 1034 t.Errorf("unexpected diff (-want +got): %s", diff) 1035 } 1036 }) 1037 } 1038 } 1039 1040 // LegacyConfigV1 is the type of an old Config struct. 1041 // This type is used to ensure that the current (as of 2023-09-25) struct and 1042 // the former version are compatible within datastore, i.e. records can be written 1043 // or read with the old or new schema. 1044 type LegacyConfigV1 struct { 1045 _extra datastore.PropertyMap `gae:"-,extra"` 1046 _kind string `gae:"$kind,Config"` 1047 ID string `gae:"$id"` 1048 Config config.Config `gae:"binary_config,noindex"` 1049 } 1050 1051 // TestReadLegacyConfigV1AsConfig tests writing a record to in-memory datastore as a 1052 // LegacyConfigV1 and reading it out as a Config. 1053 // 1054 // This test is more valuable than just being a leaf test. 1055 // Basically, the old Config field, which is a config.Config not a *config.Config, 1056 // copies mutex fields inside a proto. We are getting rid of this by changing the type 1057 // of the field to be a pointer and changing some of the annotations. 1058 // 1059 // This kind of schema change should be transparent and harmless, but I don't know that for sure. 1060 // 1061 // This test's purpose is to catch problems with updating datastore schemas in go projects, so 1062 // if we encounter a problem in prod, we will go back and update this test in such a way that it 1063 // would have caught the issue. 1064 func TestReadLegacyConfigV1AsConfig(t *testing.T) { 1065 t.Parallel() 1066 ctx := memory.Use(context.Background()) 1067 input := &LegacyConfigV1{ 1068 ID: "a", 1069 Config: config.Config{ 1070 Swarming: "ed18ba13-bfe7-46bc-ae77-77ec70f2c22c", 1071 }, 1072 } 1073 output := &Config{ 1074 ID: "a", 1075 Config: &config.Config{ 1076 Swarming: "ed18ba13-bfe7-46bc-ae77-77ec70f2c22c", 1077 }, 1078 } 1079 1080 if err := datastore.Put(ctx, input); err != nil { 1081 t.Errorf("error when inserting record to datastore: %s", err) 1082 } 1083 1084 expected := output 1085 actual := &Config{ 1086 ID: "a", 1087 } 1088 if err := datastore.Get(ctx, actual); err != nil { 1089 t.Errorf("error when retrieving record from datastore: %s", err) 1090 } 1091 1092 if diff := cmp.Diff(expected, actual, protocmp.Transform(), cmpopts.IgnoreUnexported(Config{})); diff != "" { 1093 t.Errorf("unexpected diff (-want +got): %s", diff) 1094 } 1095 }