go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gce/appengine/backend/instances_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 backend 16 17 import ( 18 "context" 19 "net/http" 20 "reflect" 21 "testing" 22 "time" 23 24 "google.golang.org/api/compute/v1" 25 "google.golang.org/api/option" 26 27 "go.chromium.org/luci/appengine/tq" 28 "go.chromium.org/luci/appengine/tq/tqtesting" 29 "go.chromium.org/luci/common/tsmon" 30 "go.chromium.org/luci/gae/impl/memory" 31 "go.chromium.org/luci/gae/service/datastore" 32 33 "go.chromium.org/luci/gce/api/tasks/v1" 34 "go.chromium.org/luci/gce/appengine/backend/internal/metrics" 35 "go.chromium.org/luci/gce/appengine/model" 36 "go.chromium.org/luci/gce/appengine/testing/roundtripper" 37 38 . "github.com/smartystreets/goconvey/convey" 39 . "go.chromium.org/luci/common/testing/assertions" 40 ) 41 42 func TestCreate(t *testing.T) { 43 t.Parallel() 44 45 Convey("createInstance", t, func() { 46 dsp := &tq.Dispatcher{} 47 registerTasks(dsp) 48 rt := &roundtripper.JSONRoundTripper{} 49 gce, err := compute.New(&http.Client{Transport: rt}) 50 So(err, ShouldBeNil) 51 c, _ := tsmon.WithDummyInMemory(memory.Use(context.Background())) 52 c = withCompute(withDispatcher(c, dsp), ComputeService{Stable: gce}) 53 tqt := tqtesting.GetTestable(c, dsp) 54 tqt.CreateQueues() 55 s := tsmon.Store(c) 56 57 Convey("invalid", func() { 58 Convey("nil", func() { 59 err := createInstance(c, nil) 60 So(err, ShouldErrLike, "unexpected payload") 61 }) 62 63 Convey("empty", func() { 64 err := createInstance(c, &tasks.CreateInstance{}) 65 So(err, ShouldErrLike, "ID is required") 66 }) 67 68 Convey("missing", func() { 69 err := createInstance(c, &tasks.CreateInstance{ 70 Id: "id", 71 }) 72 So(err, ShouldErrLike, "failed to fetch VM") 73 }) 74 }) 75 76 Convey("valid", func() { 77 Convey("exists", func() { 78 datastore.Put(c, &model.VM{ 79 ID: "id", 80 Hostname: "name", 81 URL: "url", 82 }) 83 err := createInstance(c, &tasks.CreateInstance{ 84 Id: "id", 85 }) 86 So(err, ShouldBeNil) 87 }) 88 89 Convey("drained", func() { 90 rt.Handler = func(req any) (int, any) { 91 inst, ok := req.(*compute.Instance) 92 So(ok, ShouldBeTrue) 93 So(inst.Name, ShouldEqual, "name") 94 return http.StatusOK, &compute.Operation{} 95 } 96 rt.Type = reflect.TypeOf(compute.Instance{}) 97 datastore.Put(c, &model.VM{ 98 ID: "id", 99 Drained: true, 100 Hostname: "name", 101 }) 102 err := createInstance(c, &tasks.CreateInstance{ 103 Id: "id", 104 }) 105 So(err, ShouldBeNil) 106 }) 107 108 Convey("error", func() { 109 Convey("http", func() { 110 Convey("transient", func() { 111 rt.Handler = func(req any) (int, any) { 112 return http.StatusInternalServerError, nil 113 } 114 rt.Type = reflect.TypeOf(compute.Instance{}) 115 datastore.Put(c, &model.VM{ 116 ID: "id", 117 Hostname: "name", 118 }) 119 err := createInstance(c, &tasks.CreateInstance{ 120 Id: "id", 121 }) 122 So(err, ShouldErrLike, "transiently failed to create instance") 123 v := &model.VM{ 124 ID: "id", 125 } 126 So(datastore.Get(c, v), ShouldBeNil) 127 So(v.Hostname, ShouldEqual, "name") 128 }) 129 130 Convey("permanent", func() { 131 rt.Handler = func(req any) (int, any) { 132 return http.StatusConflict, nil 133 } 134 rt.Type = reflect.TypeOf(compute.Instance{}) 135 datastore.Put(c, &model.VM{ 136 ID: "id", 137 Hostname: "name", 138 }) 139 err := createInstance(c, &tasks.CreateInstance{ 140 Id: "id", 141 }) 142 So(err, ShouldErrLike, "failed to create instance") 143 v := &model.VM{ 144 ID: "id", 145 } 146 So(datastore.Get(c, v), ShouldEqual, datastore.ErrNoSuchEntity) 147 }) 148 }) 149 150 Convey("operation", func() { 151 rt.Handler = func(req any) (int, any) { 152 return http.StatusOK, &compute.Operation{ 153 Error: &compute.OperationError{ 154 Errors: []*compute.OperationErrorErrors{ 155 {}, 156 }, 157 }, 158 } 159 } 160 rt.Type = reflect.TypeOf(compute.Instance{}) 161 datastore.Put(c, &model.VM{ 162 ID: "id", 163 Hostname: "name", 164 }) 165 err := createInstance(c, &tasks.CreateInstance{ 166 Id: "id", 167 }) 168 So(err, ShouldErrLike, "failed to create instance") 169 v := &model.VM{ 170 ID: "id", 171 } 172 So(datastore.Get(c, v), ShouldEqual, datastore.ErrNoSuchEntity) 173 }) 174 }) 175 176 Convey("created", func() { 177 confFields := []any{"", "", "", "name"} 178 So(s.Get(c, metrics.CreatedInstanceChecked, time.Time{}, confFields), ShouldBeNil) 179 Convey("pending", func() { 180 rt.Handler = func(req any) (int, any) { 181 inst, ok := req.(*compute.Instance) 182 So(ok, ShouldBeTrue) 183 So(inst.Name, ShouldEqual, "name") 184 return http.StatusOK, &compute.Operation{} 185 } 186 rt.Type = reflect.TypeOf(compute.Instance{}) 187 datastore.Put(c, &model.VM{ 188 ID: "id", 189 Hostname: "name", 190 }) 191 err := createInstance(c, &tasks.CreateInstance{ 192 Id: "id", 193 }) 194 So(err, ShouldBeNil) 195 v := &model.VM{ 196 ID: "id", 197 } 198 So(datastore.Get(c, v), ShouldBeNil) 199 So(v.Created, ShouldEqual, 0) 200 So(v.URL, ShouldBeEmpty) 201 }) 202 203 Convey("done", func() { 204 rt.Handler = func(req any) (int, any) { 205 switch rt.Type { 206 case reflect.TypeOf(compute.Instance{}): 207 // First call, to create the instance. 208 inst, ok := req.(*compute.Instance) 209 So(ok, ShouldBeTrue) 210 So(inst.Name, ShouldEqual, "name") 211 rt.Type = reflect.TypeOf(map[string]string{}) 212 return http.StatusOK, &compute.Operation{ 213 EndTime: "2018-12-14T15:07:48.200-08:00", 214 Status: "DONE", 215 TargetLink: "url", 216 } 217 default: 218 // Second call, to check the reason for the conflict. 219 // This request should have no body. 220 So(*(req.(*map[string]string)), ShouldHaveLength, 0) 221 return http.StatusOK, &compute.Instance{ 222 CreationTimestamp: "2018-12-14T15:07:48.200-08:00", 223 NetworkInterfaces: []*compute.NetworkInterface{ 224 { 225 NetworkIP: "0.0.0.1", 226 }, 227 { 228 AccessConfigs: []*compute.AccessConfig{ 229 { 230 NatIP: "2.0.0.0", 231 }, 232 }, 233 NetworkIP: "0.0.0.2", 234 }, 235 { 236 AccessConfigs: []*compute.AccessConfig{ 237 { 238 NatIP: "3.0.0.0", 239 }, 240 { 241 NatIP: "3.0.0.1", 242 }, 243 }, 244 NetworkIP: "0.0.0.3", 245 }, 246 }, 247 SelfLink: "url", 248 } 249 } 250 } 251 rt.Type = reflect.TypeOf(compute.Instance{}) 252 datastore.Put(c, &model.VM{ 253 ID: "id", 254 Hostname: "name", 255 }) 256 err := createInstance(c, &tasks.CreateInstance{ 257 Id: "id", 258 }) 259 So(err, ShouldBeNil) 260 v := &model.VM{ 261 ID: "id", 262 } 263 So(datastore.Get(c, v), ShouldBeNil) 264 So(v.Created, ShouldNotEqual, 0) 265 So(v.NetworkInterfaces, ShouldResemble, []model.NetworkInterface{ 266 { 267 InternalIP: "0.0.0.1", 268 }, 269 { 270 ExternalIP: "2.0.0.0", 271 InternalIP: "0.0.0.2", 272 }, 273 { 274 ExternalIP: "3.0.0.0", 275 InternalIP: "0.0.0.3", 276 }, 277 }) 278 So(v.URL, ShouldEqual, "url") 279 So(s.Get(c, metrics.CreatedInstanceChecked, time.Time{}, confFields), ShouldEqual, 1) 280 }) 281 }) 282 }) 283 }) 284 } 285 286 func TestDestroyInstance(t *testing.T) { 287 t.Parallel() 288 289 Convey("destroyInstance", t, func() { 290 dsp := &tq.Dispatcher{} 291 registerTasks(dsp) 292 rt := &roundtripper.JSONRoundTripper{} 293 gce, err := compute.New(&http.Client{Transport: rt}) 294 So(err, ShouldBeNil) 295 c := withCompute(withDispatcher(memory.Use(context.Background()), dsp), ComputeService{Stable: gce}) 296 tqt := tqtesting.GetTestable(c, dsp) 297 tqt.CreateQueues() 298 299 Convey("invalid", func() { 300 Convey("nil", func() { 301 err := destroyInstance(c, nil) 302 So(err, ShouldErrLike, "unexpected payload") 303 So(tqt.GetScheduledTasks(), ShouldBeEmpty) 304 }) 305 306 Convey("empty", func() { 307 err := destroyInstance(c, &tasks.DestroyInstance{}) 308 So(err, ShouldErrLike, "ID is required") 309 So(tqt.GetScheduledTasks(), ShouldBeEmpty) 310 }) 311 312 Convey("url", func() { 313 err := destroyInstance(c, &tasks.DestroyInstance{ 314 Id: "id", 315 }) 316 So(err, ShouldErrLike, "URL is required") 317 So(tqt.GetScheduledTasks(), ShouldBeEmpty) 318 }) 319 }) 320 321 Convey("valid", func() { 322 Convey("missing", func() { 323 err := destroyInstance(c, &tasks.DestroyInstance{ 324 Id: "id", 325 Url: "url", 326 }) 327 So(err, ShouldBeNil) 328 So(tqt.GetScheduledTasks(), ShouldBeEmpty) 329 }) 330 331 Convey("replaced", func() { 332 datastore.Put(c, &model.VM{ 333 ID: "id", 334 URL: "new", 335 }) 336 err := destroyInstance(c, &tasks.DestroyInstance{ 337 Id: "id", 338 Url: "old", 339 }) 340 So(err, ShouldBeNil) 341 v := &model.VM{ 342 ID: "id", 343 } 344 So(datastore.Get(c, v), ShouldBeNil) 345 So(v.URL, ShouldEqual, "new") 346 }) 347 348 Convey("error", func() { 349 Convey("http", func() { 350 rt.Handler = func(req any) (int, any) { 351 return http.StatusInternalServerError, nil 352 } 353 datastore.Put(c, &model.VM{ 354 ID: "id", 355 URL: "url", 356 }) 357 err := destroyInstance(c, &tasks.DestroyInstance{ 358 Id: "id", 359 Url: "url", 360 }) 361 So(err, ShouldErrLike, "failed to destroy instance") 362 v := &model.VM{ 363 ID: "id", 364 } 365 datastore.Get(c, v) 366 So(v.URL, ShouldEqual, "url") 367 So(tqt.GetScheduledTasks(), ShouldBeEmpty) 368 }) 369 370 Convey("operation", func() { 371 rt.Handler = func(req any) (int, any) { 372 return http.StatusOK, &compute.Operation{ 373 Error: &compute.OperationError{ 374 Errors: []*compute.OperationErrorErrors{ 375 {}, 376 }, 377 }, 378 } 379 } 380 datastore.Put(c, &model.VM{ 381 ID: "id", 382 URL: "url", 383 }) 384 err := destroyInstance(c, &tasks.DestroyInstance{ 385 Id: "id", 386 Url: "url", 387 }) 388 So(err, ShouldErrLike, "failed to destroy instance") 389 v := &model.VM{ 390 ID: "id", 391 } 392 datastore.Get(c, v) 393 So(v.URL, ShouldEqual, "url") 394 So(tqt.GetScheduledTasks(), ShouldBeEmpty) 395 }) 396 }) 397 398 Convey("destroys", func() { 399 Convey("pending", func() { 400 rt.Handler = func(req any) (int, any) { 401 return http.StatusOK, &compute.Operation{} 402 } 403 datastore.Put(c, &model.VM{ 404 ID: "id", 405 Created: 1, 406 Hostname: "name", 407 URL: "url", 408 }) 409 err := destroyInstance(c, &tasks.DestroyInstance{ 410 Id: "id", 411 Url: "url", 412 }) 413 So(err, ShouldBeNil) 414 v := &model.VM{ 415 ID: "id", 416 } 417 datastore.Get(c, v) 418 So(v.Created, ShouldEqual, 1) 419 So(v.Hostname, ShouldEqual, "name") 420 So(v.URL, ShouldEqual, "url") 421 So(tqt.GetScheduledTasks(), ShouldBeEmpty) 422 }) 423 424 Convey("done", func() { 425 rt.Handler = func(req any) (int, any) { 426 return http.StatusOK, &compute.Operation{ 427 Status: "DONE", 428 TargetLink: "url", 429 } 430 } 431 datastore.Put(c, &model.VM{ 432 ID: "id", 433 Created: 1, 434 Hostname: "name", 435 URL: "url", 436 }) 437 err := destroyInstance(c, &tasks.DestroyInstance{ 438 Id: "id", 439 Url: "url", 440 }) 441 So(err, ShouldBeNil) 442 v := &model.VM{ 443 ID: "id", 444 } 445 datastore.Get(c, v) 446 So(v.Created, ShouldEqual, 1) 447 So(v.Hostname, ShouldEqual, "name") 448 So(v.URL, ShouldEqual, "url") 449 So(tqt.GetScheduledTasks(), ShouldHaveLength, 1) 450 }) 451 }) 452 }) 453 }) 454 } 455 456 func TestAuditInstanceInZone(t *testing.T) { 457 t.Parallel() 458 459 Convey("auditInstanceInZone", t, func() { 460 dsp := &tq.Dispatcher{} 461 registerTasks(dsp) 462 rt := &roundtripper.JSONRoundTripper{} 463 c := context.Background() 464 gce, err := compute.NewService(c, option.WithHTTPClient(&http.Client{Transport: rt})) 465 So(err, ShouldBeNil) 466 c = withCompute(withDispatcher(memory.Use(c), dsp), ComputeService{Stable: gce}) 467 datastore.GetTestable(c).Consistent(true) 468 tqt := tqtesting.GetTestable(c, dsp) 469 tqt.CreateQueues() 470 471 Convey("invalid", func() { 472 Convey("nil", func() { 473 err := auditInstanceInZone(c, nil) 474 So(err, ShouldErrLike, "Unexpected payload") 475 So(tqt.GetScheduledTasks(), ShouldBeEmpty) 476 }) 477 478 Convey("empty", func() { 479 err := auditInstanceInZone(c, &tasks.AuditProject{}) 480 So(err, ShouldErrLike, "Project is required") 481 So(tqt.GetScheduledTasks(), ShouldBeEmpty) 482 }) 483 484 Convey("empty region", func() { 485 err := auditInstanceInZone(c, &tasks.AuditProject{ 486 Project: "libreboot", 487 }) 488 So(err, ShouldErrLike, "Zone is required") 489 So(tqt.GetScheduledTasks(), ShouldBeEmpty) 490 }) 491 }) 492 493 Convey("valid", func() { 494 Convey("VM entry exists", func() { 495 count := 0 496 // The first request must be to the List API. Should not 497 // do any consequent requests 498 rt.Handler = func(req any) (int, any) { 499 switch count { 500 case 0: 501 count += 1 502 return http.StatusOK, &compute.InstanceList{ 503 Items: []*compute.Instance{{ 504 Name: "double-11-puts", 505 }}, 506 } 507 default: 508 count += 1 509 return http.StatusInternalServerError, nil 510 } 511 } 512 err := datastore.Put(c, &model.VM{ 513 ID: "double-11", 514 Hostname: "double-11-puts", 515 Prefix: "double", 516 }) 517 So(err, ShouldBeNil) 518 err = auditInstanceInZone(c, &tasks.AuditProject{ 519 Project: "libreboot", 520 Zone: "us-mex-1", 521 }) 522 So(err, ShouldBeNil) 523 So(count, ShouldEqual, 1) 524 So(tqt.GetScheduledTasks(), ShouldBeEmpty) 525 }) 526 527 Convey("VM entry exists double, page token", func() { 528 count := 0 529 rt.Handler = func(req any) (int, any) { 530 switch count { 531 case 0: 532 count += 1 533 return http.StatusOK, &compute.InstanceList{ 534 Items: []*compute.Instance{{ 535 Name: "double-11-puts", 536 }, { 537 Name: "thes-1-puts", 538 }}, 539 NextPageToken: "next-page", 540 } 541 default: 542 count += 1 543 return http.StatusInternalServerError, nil 544 } 545 } 546 err := datastore.Put(c, &model.VM{ 547 ID: "double-11", 548 Hostname: "double-11-puts", 549 Prefix: "double", 550 }) 551 So(err, ShouldBeNil) 552 err = datastore.Put(c, &model.VM{ 553 ID: "thes-1", 554 Hostname: "thes-1-puts", 555 Prefix: "thes", 556 }) 557 So(err, ShouldBeNil) 558 err = auditInstanceInZone(c, &tasks.AuditProject{ 559 Project: "libreboot", 560 Zone: "us-mex-1", 561 }) 562 So(err, ShouldBeNil) 563 So(count, ShouldEqual, 1) 564 // The next token should schedule a job 565 So(tqt.GetScheduledTasks(), ShouldHaveLength, 1) 566 }) 567 568 Convey("VM leaked (single)", func() { 569 count := 0 570 rt.Handler = func(req any) (int, any) { 571 switch count { 572 case 0: 573 count += 1 574 return http.StatusOK, &compute.InstanceList{ 575 Items: []*compute.Instance{{ 576 Name: "double-11-acrd", 577 }}, 578 } 579 case 1: 580 count += 1 581 return http.StatusOK, &compute.Operation{} 582 default: 583 count += 1 584 return http.StatusInternalServerError, nil 585 } 586 } 587 err := datastore.Put(c, &model.VM{ 588 ID: "double-11", 589 Hostname: "double-11-puts", 590 Prefix: "double", 591 }) 592 So(err, ShouldBeNil) 593 err = auditInstanceInZone(c, &tasks.AuditProject{ 594 Project: "libreboot", 595 Zone: "us-mex-1", 596 }) 597 So(err, ShouldBeNil) 598 So(count, ShouldEqual, 2) 599 So(tqt.GetScheduledTasks(), ShouldBeEmpty) 600 }) 601 602 Convey("VM leaked (double)", func() { 603 count := 0 604 rt.Handler = func(req any) (int, any) { 605 switch count { 606 case 0: 607 count += 1 608 return http.StatusOK, &compute.InstanceList{ 609 Items: []*compute.Instance{{ 610 Name: "double-12-acrd", 611 }, { 612 Name: "thes-1-acrd", 613 }}, 614 } 615 case 1: 616 count += 1 617 return http.StatusOK, &compute.Operation{} 618 case 2: 619 count += 1 620 return http.StatusOK, &compute.Operation{} 621 default: 622 count += 1 623 return http.StatusInternalServerError, nil 624 } 625 } 626 err := datastore.Put(c, &model.VM{ 627 ID: "double-11", 628 Hostname: "double-11-puts", 629 Prefix: "double", 630 }) 631 So(err, ShouldBeNil) 632 err = datastore.Put(c, &model.VM{ 633 ID: "thes-1", 634 Hostname: "thes-1-puts", 635 Prefix: "thes", 636 }) 637 So(err, ShouldBeNil) 638 err = auditInstanceInZone(c, &tasks.AuditProject{ 639 Project: "libreboot", 640 Zone: "us-mex-1", 641 }) 642 So(count, ShouldEqual, 3) 643 So(err, ShouldBeNil) 644 So(tqt.GetScheduledTasks(), ShouldBeEmpty) 645 }) 646 647 Convey("VM leaked (mix)", func() { 648 count := 0 649 rt.Handler = func(req any) (int, any) { 650 switch count { 651 case 0: 652 count += 1 653 return http.StatusOK, &compute.InstanceList{ 654 Items: []*compute.Instance{{ 655 Name: "double-12-acrd", 656 }, { 657 Name: "thes-1-puts", 658 }}, 659 } 660 case 1: 661 count += 1 662 return http.StatusOK, &compute.Operation{ 663 Status: "Done", 664 } 665 default: 666 count += 1 667 return http.StatusInternalServerError, nil 668 } 669 } 670 err := datastore.Put(c, &model.VM{ 671 ID: "double-11", 672 Hostname: "double-11-puts", 673 Prefix: "double", 674 }) 675 err = auditInstanceInZone(c, &tasks.AuditProject{ 676 Project: "libreboot", 677 Zone: "us-mex-1", 678 }) 679 So(count, ShouldEqual, 2) 680 So(err, ShouldBeNil) 681 So(tqt.GetScheduledTasks(), ShouldBeEmpty) 682 }) 683 }) 684 685 Convey("error", func() { 686 Convey("list failure", func() { 687 rt.Handler = func(req any) (int, any) { 688 return http.StatusInternalServerError, nil 689 } 690 err := auditInstanceInZone(c, &tasks.AuditProject{ 691 Project: "libreboot", 692 Zone: "us-mex-1", 693 }) 694 So(err, ShouldErrLike, "failed to list") 695 So(tqt.GetScheduledTasks(), ShouldBeEmpty) 696 }) 697 Convey("VM delete failure", func() { 698 count := 0 699 rt.Handler = func(req any) (int, any) { 700 switch count { 701 case 0: 702 count += 1 703 return http.StatusOK, &compute.InstanceList{ 704 Items: []*compute.Instance{{ 705 Name: "double-12-acrd", 706 }}, 707 } 708 case 1: 709 count += 1 710 return http.StatusOK, &compute.Operation{ 711 Error: &compute.OperationError{ 712 Errors: []*compute.OperationErrorErrors{ 713 {}, 714 }, 715 }, 716 } 717 default: 718 count += 1 719 return http.StatusInternalServerError, nil 720 } 721 } 722 err := datastore.Put(c, &model.VM{ 723 ID: "double-11", 724 Hostname: "double-11-puts", 725 Prefix: "double", 726 }) 727 So(err, ShouldBeNil) 728 err = auditInstanceInZone(c, &tasks.AuditProject{ 729 Project: "libreboot", 730 Zone: "us-mex-1", 731 }) 732 So(count, ShouldEqual, 2) 733 So(err, ShouldBeNil) 734 So(tqt.GetScheduledTasks(), ShouldBeEmpty) 735 }) 736 }) 737 }) 738 } 739 740 func TestIsLeakHuerestic(t *testing.T) { 741 t.Parallel() 742 743 Convey("isLeakHuerestic", t, func() { 744 c := context.Background() 745 c = memory.Use(c) 746 datastore.GetTestable(c).Consistent(true) 747 748 err := datastore.Put(c, &model.VM{ 749 ID: "host-vm-test-time-10", 750 Hostname: "host-vm-test-time-10-xyz3", 751 Prefix: "host-vm-test-time", 752 }) 753 So(err, ShouldBeNil) 754 err = datastore.Put(c, &model.VM{ 755 ID: "host-vm-test-time-11", 756 Hostname: "host-vm-test-time-11-xwz2", 757 Prefix: "host-vm-test-time", 758 }) 759 So(err, ShouldBeNil) 760 Convey("positive results", func() { 761 Convey("valid leak with replacement", func() { 762 // Leak replaced by host-vm-test-time-10-xyz3 763 leak := isLeakHuerestic(c, "host-vm-test-time-10-ijk1", "project", "us-numba-1") 764 So(leak, ShouldBeTrue) 765 }) 766 Convey("valid leak resized pool", func() { 767 // Leak and pool resized 768 leak := isLeakHuerestic(c, "host-vm-test-time-12-abc2", "project", "us-numba-1") 769 So(leak, ShouldBeTrue) 770 }) 771 }) 772 Convey("negative results", func() { 773 Convey("non gce-provider instance", func() { 774 // gce-provider didn't create this instance 775 leak := isLeakHuerestic(c, "sha512-collision-detect", "project", "us-numba-1") 776 So(leak, ShouldBeFalse) 777 }) 778 Convey("instance without a current config", func() { 779 // Config is deleted for this prefix 780 leak := isLeakHuerestic(c, "dut-12-abc2", "project", "us-numba-1") 781 So(leak, ShouldBeFalse) 782 }) 783 }) 784 }) 785 }