go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/buildbucket/appengine/rpc/schedule_build_test.go (about) 1 // Copyright 2020 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 rpc 16 17 import ( 18 "context" 19 "math/rand" 20 "sort" 21 "strconv" 22 "strings" 23 "testing" 24 "time" 25 26 "github.com/golang/mock/gomock" 27 "google.golang.org/grpc/codes" 28 "google.golang.org/grpc/metadata" 29 grpcStatus "google.golang.org/grpc/status" 30 "google.golang.org/protobuf/types/known/durationpb" 31 "google.golang.org/protobuf/types/known/fieldmaskpb" 32 "google.golang.org/protobuf/types/known/structpb" 33 "google.golang.org/protobuf/types/known/timestamppb" 34 35 "go.chromium.org/luci/auth/identity" 36 "go.chromium.org/luci/common/clock/testclock" 37 "go.chromium.org/luci/common/data/rand/mathrand" 38 "go.chromium.org/luci/common/data/stringset" 39 "go.chromium.org/luci/common/errors" 40 "go.chromium.org/luci/common/logging" 41 "go.chromium.org/luci/common/logging/memlogger" 42 luciCmProto "go.chromium.org/luci/common/proto" 43 "go.chromium.org/luci/common/tsmon" 44 "go.chromium.org/luci/gae/filter/txndefer" 45 "go.chromium.org/luci/gae/impl/memory" 46 "go.chromium.org/luci/gae/service/datastore" 47 rdbPb "go.chromium.org/luci/resultdb/proto/v1" 48 "go.chromium.org/luci/server/auth" 49 "go.chromium.org/luci/server/auth/authtest" 50 "go.chromium.org/luci/server/caching" 51 "go.chromium.org/luci/server/caching/cachingtest" 52 "go.chromium.org/luci/server/tq" 53 "go.chromium.org/luci/server/tq/tqtesting" 54 55 bb "go.chromium.org/luci/buildbucket" 56 "go.chromium.org/luci/buildbucket/appengine/internal/buildtoken" 57 "go.chromium.org/luci/buildbucket/appengine/internal/clients" 58 "go.chromium.org/luci/buildbucket/appengine/internal/config" 59 "go.chromium.org/luci/buildbucket/appengine/internal/metrics" 60 "go.chromium.org/luci/buildbucket/appengine/internal/resultdb" 61 "go.chromium.org/luci/buildbucket/appengine/model" 62 "go.chromium.org/luci/buildbucket/appengine/rpc/testutil" 63 taskdefs "go.chromium.org/luci/buildbucket/appengine/tasks/defs" 64 "go.chromium.org/luci/buildbucket/bbperms" 65 pb "go.chromium.org/luci/buildbucket/proto" 66 67 . "github.com/smartystreets/goconvey/convey" 68 . "go.chromium.org/luci/common/testing/assertions" 69 ) 70 71 func fv(vs ...any) []any { 72 ret := []any{"luci.project.bucket", "builder"} 73 return append(ret, vs...) 74 } 75 76 func TestScheduleBuild(t *testing.T) { 77 t.Parallel() 78 79 // Note: request deduplication IDs depend on a hash of this value. 80 const userID = identity.Identity("user:caller@example.com") 81 82 Convey("builderMatches", t, func() { 83 Convey("nil", func() { 84 So(builderMatches("", nil), ShouldBeTrue) 85 So(builderMatches("project/bucket/builder", nil), ShouldBeTrue) 86 }) 87 88 Convey("empty", func() { 89 p := &pb.BuilderPredicate{} 90 So(builderMatches("", p), ShouldBeTrue) 91 So(builderMatches("project/bucket/builder", p), ShouldBeTrue) 92 }) 93 94 Convey("regex", func() { 95 p := &pb.BuilderPredicate{ 96 Regex: []string{ 97 "project/bucket/.+", 98 }, 99 } 100 So(builderMatches("", p), ShouldBeFalse) 101 So(builderMatches("project/bucket/builder", p), ShouldBeTrue) 102 So(builderMatches("project/other/builder", p), ShouldBeFalse) 103 }) 104 105 Convey("regex exclude", func() { 106 p := &pb.BuilderPredicate{ 107 RegexExclude: []string{ 108 "project/bucket/.+", 109 }, 110 } 111 So(builderMatches("", p), ShouldBeTrue) 112 So(builderMatches("project/bucket/builder", p), ShouldBeFalse) 113 So(builderMatches("project/other/builder", p), ShouldBeTrue) 114 }) 115 116 Convey("regex exclude > regex", func() { 117 p := &pb.BuilderPredicate{ 118 Regex: []string{ 119 "project/bucket/.+", 120 }, 121 RegexExclude: []string{ 122 "project/bucket/builder", 123 }, 124 } 125 So(builderMatches("", p), ShouldBeFalse) 126 So(builderMatches("project/bucket/builder", p), ShouldBeFalse) 127 So(builderMatches("project/bucket/other", p), ShouldBeTrue) 128 }) 129 }) 130 131 Convey("fetchBuilderConfigs", t, func() { 132 ctx := metrics.WithServiceInfo(memory.Use(context.Background()), "svc", "job", "ins") 133 datastore.GetTestable(ctx).AutoIndex(true) 134 datastore.GetTestable(ctx).Consistent(true) 135 136 testutil.PutBuilder(ctx, "project", "bucket 1", "builder 1", "") 137 testutil.PutBuilder(ctx, "project", "bucket 1", "builder 2", "") 138 testutil.PutBucket(ctx, "project", "bucket 1", &pb.Bucket{ 139 Swarming: &pb.Swarming{}, 140 Shadow: "bucket 2", 141 }) 142 testutil.PutBucket(ctx, "project", "bucket 2", &pb.Bucket{DynamicBuilderTemplate: &pb.Bucket_DynamicBuilderTemplate{}}) 143 144 Convey("bucket not found", func() { 145 bldrIDs := []*pb.BuilderID{ 146 { 147 Project: "project", 148 Bucket: "bucket 3", 149 Builder: "builder 1", 150 }, 151 } 152 bldrs, _, _, err := fetchBuilderConfigs(ctx, bldrIDs) 153 So(len(err.(errors.MultiError)), ShouldEqual, len(bldrIDs)) 154 So(err, ShouldErrLike, "bucket not found") 155 So(bldrs["project/bucket 3"]["builder 1"], ShouldBeNil) 156 }) 157 158 Convey("builder not found", func() { 159 bldrIDs := []*pb.BuilderID{ 160 { 161 Project: "project", 162 Bucket: "bucket 1", 163 Builder: "builder 3", 164 }, 165 } 166 bldrs, _, _, err := fetchBuilderConfigs(ctx, bldrIDs) 167 So(len(err.(errors.MultiError)), ShouldEqual, len(bldrIDs)) 168 So(err, ShouldErrLike, "builder not found") 169 So(bldrs["project/bucket 3"]["builder 1"], ShouldBeNil) 170 }) 171 172 Convey("one found and the other not found", func() { 173 bldrIDs := []*pb.BuilderID{ 174 { 175 Project: "project", 176 Bucket: "bucket 1", 177 Builder: "builder 1", 178 }, 179 { 180 Project: "project", 181 Bucket: "bucket 1", 182 Builder: "builder 100", 183 }, 184 } 185 bldrs, _, shadowMap, err := fetchBuilderConfigs(ctx, bldrIDs) 186 So(err.(errors.MultiError)[1], ShouldErrLike, "builder not found") 187 So(bldrs["project/bucket 3"]["builder 1"], ShouldBeNil) 188 So(bldrs["project/bucket 1"]["builder 1"], ShouldResembleProto, &pb.BuilderConfig{ 189 Name: "builder 1", 190 SwarmingHost: "host", 191 }) 192 So(shadowMap, ShouldResemble, map[string]string{"project/bucket 1": "bucket 2"}) 193 }) 194 195 Convey("dynamic", func() { 196 bldrIDs := []*pb.BuilderID{ 197 { 198 Project: "project", 199 Bucket: "bucket 2", 200 Builder: "builder 1", 201 }, 202 } 203 bldrs, dynamicBuckets, shadowMap, err := fetchBuilderConfigs(ctx, bldrIDs) 204 So(err, ShouldBeNil) 205 So(bldrs["project/bucket 2"]["builder 1"], ShouldBeNil) 206 So(len(dynamicBuckets), ShouldEqual, 1) 207 So(dynamicBuckets["project/bucket 2"], ShouldResembleProto, &pb.Bucket{ 208 Name: "bucket 2", 209 DynamicBuilderTemplate: &pb.Bucket_DynamicBuilderTemplate{}, 210 }) 211 So(shadowMap, ShouldResemble, map[string]string{"project/bucket 2": ""}) 212 }) 213 214 Convey("one", func() { 215 bldrIDs := []*pb.BuilderID{ 216 { 217 Project: "project", 218 Bucket: "bucket 1", 219 Builder: "builder 1", 220 }, 221 } 222 bldrs, _, _, err := fetchBuilderConfigs(ctx, bldrIDs) 223 So(err, ShouldBeNil) 224 So(bldrs["project/bucket 1"]["builder 1"], ShouldResembleProto, &pb.BuilderConfig{ 225 Name: "builder 1", 226 SwarmingHost: "host", 227 }) 228 }) 229 230 Convey("many", func() { 231 bldrIDs := []*pb.BuilderID{ 232 { 233 Project: "project", 234 Bucket: "bucket 1", 235 Builder: "builder 1", 236 }, 237 { 238 Project: "project", 239 Bucket: "bucket 1", 240 Builder: "builder 2", 241 }, 242 { 243 Project: "project", 244 Bucket: "bucket 2", 245 Builder: "builder 1", 246 }, 247 } 248 bldrs, _, _, err := fetchBuilderConfigs(ctx, bldrIDs) 249 So(err, ShouldBeNil) 250 So(bldrs["project/bucket 1"]["builder 1"], ShouldResembleProto, &pb.BuilderConfig{ 251 Name: "builder 1", 252 SwarmingHost: "host", 253 }) 254 So(bldrs["project/bucket 1"]["builder 2"], ShouldResembleProto, &pb.BuilderConfig{ 255 Name: "builder 2", 256 SwarmingHost: "host", 257 }) 258 So(bldrs["project/bucket 2"]["builder 1"], ShouldBeNil) 259 }) 260 }) 261 262 Convey("generateBuildNumbers", t, func() { 263 ctx := metrics.WithServiceInfo(memory.Use(context.Background()), "svc", "job", "ins") 264 datastore.GetTestable(ctx).AutoIndex(true) 265 datastore.GetTestable(ctx).Consistent(true) 266 267 Convey("one", func() { 268 blds := []*model.Build{ 269 { 270 Proto: &pb.Build{ 271 Builder: &pb.BuilderID{ 272 Project: "project", 273 Bucket: "bucket", 274 Builder: "builder", 275 }, 276 }, 277 }, 278 } 279 err := generateBuildNumbers(ctx, blds) 280 So(err, ShouldBeNil) 281 So(blds, ShouldResemble, []*model.Build{ 282 { 283 Proto: &pb.Build{ 284 Builder: &pb.BuilderID{ 285 Project: "project", 286 Bucket: "bucket", 287 Builder: "builder", 288 }, 289 Number: 1, 290 }, 291 Tags: []string{ 292 "build_address:luci.project.bucket/builder/1", 293 }, 294 }, 295 }) 296 }) 297 298 Convey("many", func() { 299 blds := []*model.Build{ 300 { 301 Proto: &pb.Build{ 302 Builder: &pb.BuilderID{ 303 Project: "project", 304 Bucket: "bucket", 305 Builder: "builder1", 306 }, 307 }, 308 }, 309 { 310 Proto: &pb.Build{ 311 Builder: &pb.BuilderID{ 312 Project: "project", 313 Bucket: "bucket", 314 Builder: "builder2", 315 }, 316 }, 317 }, 318 { 319 Proto: &pb.Build{ 320 Builder: &pb.BuilderID{ 321 Project: "project", 322 Bucket: "bucket", 323 Builder: "builder1", 324 }, 325 }, 326 }, 327 } 328 err := generateBuildNumbers(ctx, blds) 329 So(err, ShouldBeNil) 330 So(blds, ShouldResemble, []*model.Build{ 331 { 332 Proto: &pb.Build{ 333 Builder: &pb.BuilderID{ 334 Project: "project", 335 Bucket: "bucket", 336 Builder: "builder1", 337 }, 338 Number: 1, 339 }, 340 Tags: []string{ 341 "build_address:luci.project.bucket/builder1/1", 342 }, 343 }, 344 { 345 Proto: &pb.Build{ 346 Builder: &pb.BuilderID{ 347 Project: "project", 348 Bucket: "bucket", 349 Builder: "builder2", 350 }, 351 Number: 1, 352 }, 353 Tags: []string{ 354 "build_address:luci.project.bucket/builder2/1", 355 }, 356 }, 357 { 358 Proto: &pb.Build{ 359 Builder: &pb.BuilderID{ 360 Project: "project", 361 Bucket: "bucket", 362 Builder: "builder1", 363 }, 364 Number: 2, 365 }, 366 Tags: []string{ 367 "build_address:luci.project.bucket/builder1/2", 368 }, 369 }, 370 }) 371 }) 372 }) 373 374 Convey("scheduleBuilds", t, func() { 375 ctx := txndefer.FilterRDS(memory.Use(context.Background())) 376 ctx = metrics.WithServiceInfo(ctx, "svc", "job", "ins") 377 ctx = mathrand.Set(ctx, rand.New(rand.NewSource(0))) 378 ctx, _ = testclock.UseTime(ctx, testclock.TestRecentTimeUTC) 379 ctx, sch := tq.TestingContext(ctx, nil) 380 datastore.GetTestable(ctx).AutoIndex(true) 381 datastore.GetTestable(ctx).Consistent(true) 382 ctx, _ = tsmon.WithDummyInMemory(ctx) 383 ctx = installTestSecret(ctx) 384 385 store := tsmon.Store(ctx) 386 globalCfg := &pb.SettingsCfg{ 387 Resultdb: &pb.ResultDBSettings{ 388 Hostname: "rdbHost", 389 }, 390 Swarming: &pb.SwarmingSettings{ 391 BbagentPackage: &pb.SwarmingSettings_Package{ 392 PackageName: "bbagent", 393 Version: "bbagent-version", 394 }, 395 KitchenPackage: &pb.SwarmingSettings_Package{ 396 PackageName: "kitchen", 397 Version: "kitchen-version", 398 }, 399 }, 400 } 401 402 // stripProtos strips the Proto field from each of the given *model.Builds, 403 // returning a slice whose ith index is the stripped *pb.Build value. 404 // Needed because model.Build.Proto can only be compared with ShouldResembleProto 405 // while model.Build can only be compared with ShouldResemble. 406 stripProtos := func(builds []*model.Build) []*pb.Build { 407 ret := make([]*pb.Build, len(builds)) 408 for i, b := range builds { 409 if b == nil { 410 ret[i] = nil 411 } else { 412 ret[i] = b.Proto 413 b.Proto = nil 414 } 415 } 416 return ret 417 } 418 419 Convey("builder not found", func() { 420 Convey("error", func() { 421 req := &pb.ScheduleBuildRequest{ 422 Builder: &pb.BuilderID{ 423 Project: "project", 424 Bucket: "bucket", 425 Builder: "builder", 426 }, 427 } 428 429 blds, err := scheduleBuilds(ctx, globalCfg, req) 430 So(err, ShouldHaveLength, 1) 431 So(err.(errors.MultiError), ShouldErrLike, "error fetching builders") 432 So(blds, ShouldHaveLength, 1) 433 So(blds[0], ShouldBeNil) 434 So(sch.Tasks(), ShouldBeEmpty) 435 So(store.Get(ctx, metrics.V1.BuildCountCreated, time.Time{}, fv("")), ShouldBeNil) 436 }) 437 438 Convey("dynamic", func() { 439 testutil.PutBucket(ctx, "project", "bucket", nil) 440 req := &pb.ScheduleBuildRequest{ 441 Builder: &pb.BuilderID{ 442 Project: "project", 443 Bucket: "bucket", 444 Builder: "builder", 445 }, 446 DryRun: true, 447 } 448 449 blds, err := scheduleBuilds(ctx, globalCfg, req) 450 So(err, ShouldBeNil) 451 So(stripProtos(blds), ShouldResembleProto, []*pb.Build{ 452 { 453 Builder: &pb.BuilderID{ 454 Project: "project", 455 Bucket: "bucket", 456 Builder: "builder", 457 }, 458 Exe: &pb.Executable{ 459 Cmd: []string{"recipes"}, 460 }, 461 ExecutionTimeout: &durationpb.Duration{ 462 Seconds: 10800, 463 }, 464 GracePeriod: &durationpb.Duration{ 465 Seconds: 30, 466 }, 467 Infra: &pb.BuildInfra{ 468 Bbagent: &pb.BuildInfra_BBAgent{ 469 CacheDir: "cache", 470 PayloadPath: "kitchen-checkout", 471 }, 472 Buildbucket: &pb.BuildInfra_Buildbucket{ 473 Hostname: "app.appspot.com", 474 Agent: &pb.BuildInfra_Buildbucket_Agent{ 475 Input: &pb.BuildInfra_Buildbucket_Agent_Input{}, 476 Purposes: map[string]pb.BuildInfra_Buildbucket_Agent_Purpose{ 477 "kitchen-checkout": pb.BuildInfra_Buildbucket_Agent_PURPOSE_EXE_PAYLOAD, 478 }, 479 }, 480 }, 481 Logdog: &pb.BuildInfra_LogDog{ 482 Project: "project", 483 }, 484 Resultdb: &pb.BuildInfra_ResultDB{ 485 Hostname: "rdbHost", 486 }, 487 Swarming: &pb.BuildInfra_Swarming{ 488 Caches: []*pb.BuildInfra_Swarming_CacheEntry{ 489 { 490 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 491 Path: "builder", 492 WaitForWarmCache: &durationpb.Duration{ 493 Seconds: 240, 494 }, 495 }, 496 }, 497 Priority: 30, 498 }, 499 }, 500 Input: &pb.Build_Input{ 501 Properties: &structpb.Struct{}, 502 }, 503 SchedulingTimeout: &durationpb.Duration{ 504 Seconds: 21600, 505 }, 506 Tags: []*pb.StringPair{ 507 { 508 Key: "builder", 509 Value: "builder", 510 }, 511 }, 512 }, 513 }) 514 So(sch.Tasks(), ShouldBeEmpty) 515 }) 516 }) 517 518 Convey("dry run", func() { 519 testutil.PutBuilder(ctx, "project", "bucket", "builder", "") 520 testutil.PutBucket(ctx, "project", "bucket", nil) 521 522 Convey("mixed", func() { 523 reqs := []*pb.ScheduleBuildRequest{ 524 { 525 Builder: &pb.BuilderID{ 526 Project: "project", 527 Bucket: "bucket", 528 Builder: "builder", 529 }, 530 }, 531 { 532 Builder: &pb.BuilderID{ 533 Project: "project", 534 Bucket: "bucket", 535 Builder: "builder", 536 }, 537 DryRun: true, 538 }, 539 { 540 Builder: &pb.BuilderID{ 541 Project: "project", 542 Bucket: "bucket", 543 Builder: "builder", 544 }, 545 DryRun: false, 546 }, 547 } 548 549 blds, err := scheduleBuilds(ctx, globalCfg, reqs...) 550 _, ok := err.(errors.MultiError) 551 So(ok, ShouldBeFalse) 552 So(err, ShouldErrLike, "all requests must have the same dry_run value") 553 So(blds, ShouldBeNil) 554 So(sch.Tasks(), ShouldBeEmpty) 555 556 // dry-run should not increase the build creation counter metric. 557 So(store.Get(ctx, metrics.V1.BuildCountCreated, time.Time{}, fv("")), ShouldBeNil) 558 }) 559 560 Convey("one", func() { 561 req := &pb.ScheduleBuildRequest{ 562 Builder: &pb.BuilderID{ 563 Project: "project", 564 Bucket: "bucket", 565 Builder: "builder", 566 }, 567 DryRun: true, 568 } 569 570 blds, err := scheduleBuilds(ctx, globalCfg, req) 571 So(err, ShouldBeNil) 572 So(stripProtos(blds), ShouldResembleProto, []*pb.Build{ 573 { 574 Builder: &pb.BuilderID{ 575 Project: "project", 576 Bucket: "bucket", 577 Builder: "builder", 578 }, 579 Exe: &pb.Executable{ 580 Cmd: []string{"recipes"}, 581 }, 582 ExecutionTimeout: &durationpb.Duration{ 583 Seconds: 10800, 584 }, 585 GracePeriod: &durationpb.Duration{ 586 Seconds: 30, 587 }, 588 Infra: &pb.BuildInfra{ 589 Bbagent: &pb.BuildInfra_BBAgent{ 590 CacheDir: "cache", 591 PayloadPath: "kitchen-checkout", 592 }, 593 Buildbucket: &pb.BuildInfra_Buildbucket{ 594 Hostname: "app.appspot.com", 595 Agent: &pb.BuildInfra_Buildbucket_Agent{ 596 Input: &pb.BuildInfra_Buildbucket_Agent_Input{}, 597 Purposes: map[string]pb.BuildInfra_Buildbucket_Agent_Purpose{ 598 "kitchen-checkout": pb.BuildInfra_Buildbucket_Agent_PURPOSE_EXE_PAYLOAD, 599 }, 600 }, 601 }, 602 Logdog: &pb.BuildInfra_LogDog{ 603 Project: "project", 604 }, 605 Resultdb: &pb.BuildInfra_ResultDB{ 606 Hostname: "rdbHost", 607 }, 608 Swarming: &pb.BuildInfra_Swarming{ 609 Hostname: "host", 610 Caches: []*pb.BuildInfra_Swarming_CacheEntry{ 611 { 612 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 613 Path: "builder", 614 WaitForWarmCache: &durationpb.Duration{ 615 Seconds: 240, 616 }, 617 }, 618 }, 619 Priority: 30, 620 }, 621 }, 622 Input: &pb.Build_Input{ 623 Properties: &structpb.Struct{}, 624 }, 625 SchedulingTimeout: &durationpb.Duration{ 626 Seconds: 21600, 627 }, 628 Tags: []*pb.StringPair{ 629 { 630 Key: "builder", 631 Value: "builder", 632 }, 633 }, 634 }, 635 }) 636 So(sch.Tasks(), ShouldBeEmpty) 637 }) 638 }) 639 640 Convey("zero", func() { 641 blds, err := scheduleBuilds(ctx, nil) 642 So(err, ShouldBeNil) 643 So(blds, ShouldBeEmpty) 644 So(sch.Tasks(), ShouldBeEmpty) 645 }) 646 647 Convey("one", func() { 648 req := &pb.ScheduleBuildRequest{ 649 Builder: &pb.BuilderID{ 650 Project: "project", 651 Bucket: "bucket", 652 Builder: "builder", 653 }, 654 Notify: &pb.NotificationConfig{ 655 PubsubTopic: "topic", 656 UserData: []byte("data"), 657 }, 658 Tags: []*pb.StringPair{ 659 { 660 Key: "buildset", 661 Value: "buildset", 662 }, 663 { 664 Key: "user_agent", 665 Value: "gerrit", 666 }, 667 }, 668 } 669 testutil.PutBuilder(ctx, "project", "bucket", "builder", "") 670 testutil.PutBucket(ctx, "project", "bucket", nil) 671 672 blds, err := scheduleBuilds(ctx, globalCfg, req) 673 So(err, ShouldBeNil) 674 So(store.Get(ctx, metrics.V1.BuildCountCreated, time.Time{}, fv("gerrit")), ShouldEqual, 1) 675 So(stripProtos(blds), ShouldResembleProto, []*pb.Build{ 676 { 677 Builder: &pb.BuilderID{ 678 Project: "project", 679 Bucket: "bucket", 680 Builder: "builder", 681 }, 682 CreatedBy: "anonymous:anonymous", 683 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 684 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 685 Exe: &pb.Executable{ 686 Cmd: []string{"recipes"}, 687 }, 688 ExecutionTimeout: &durationpb.Duration{ 689 Seconds: 10800, 690 }, 691 GracePeriod: &durationpb.Duration{ 692 Seconds: 30, 693 }, 694 Id: 9021868963221667745, 695 Infra: &pb.BuildInfra{ 696 Bbagent: &pb.BuildInfra_BBAgent{ 697 CacheDir: "cache", 698 PayloadPath: "kitchen-checkout", 699 }, 700 Buildbucket: &pb.BuildInfra_Buildbucket{ 701 Hostname: "app.appspot.com", 702 Agent: &pb.BuildInfra_Buildbucket_Agent{ 703 Input: &pb.BuildInfra_Buildbucket_Agent_Input{}, 704 Purposes: map[string]pb.BuildInfra_Buildbucket_Agent_Purpose{ 705 "kitchen-checkout": pb.BuildInfra_Buildbucket_Agent_PURPOSE_EXE_PAYLOAD, 706 }, 707 }, 708 }, 709 Logdog: &pb.BuildInfra_LogDog{ 710 Prefix: "buildbucket/app/9021868963221667745", 711 Project: "project", 712 }, 713 Resultdb: &pb.BuildInfra_ResultDB{ 714 Hostname: "rdbHost", 715 }, 716 Swarming: &pb.BuildInfra_Swarming{ 717 Hostname: "host", 718 Caches: []*pb.BuildInfra_Swarming_CacheEntry{ 719 { 720 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 721 Path: "builder", 722 WaitForWarmCache: &durationpb.Duration{ 723 Seconds: 240, 724 }, 725 }, 726 }, 727 Priority: 30, 728 }, 729 }, 730 Input: &pb.Build_Input{ 731 Properties: &structpb.Struct{}, 732 }, 733 SchedulingTimeout: &durationpb.Duration{ 734 Seconds: 21600, 735 }, 736 Status: pb.Status_SCHEDULED, 737 Tags: []*pb.StringPair{ 738 { 739 Key: "builder", 740 Value: "builder", 741 }, 742 { 743 Key: "buildset", 744 Value: "buildset", 745 }, 746 { 747 Key: "user_agent", 748 Value: "gerrit", 749 }, 750 }, 751 }, 752 }) 753 So(blds, ShouldResemble, []*model.Build{ 754 { 755 ID: 9021868963221667745, 756 BucketID: "project/bucket", 757 BuilderID: "project/bucket/builder", 758 CreatedBy: "anonymous:anonymous", 759 CreateTime: testclock.TestRecentTimeUTC, 760 StatusChangedTime: testclock.TestRecentTimeUTC, 761 Experiments: nil, 762 Incomplete: true, 763 IsLuci: true, 764 Status: pb.Status_SCHEDULED, 765 Tags: []string{ 766 "builder:builder", 767 "buildset:buildset", 768 "user_agent:gerrit", 769 }, 770 Project: "project", 771 PubSubCallback: model.PubSubCallback{ 772 Topic: "topic", 773 UserData: []byte("data"), 774 }, 775 LegacyProperties: model.LegacyProperties{ 776 Status: model.Scheduled, 777 }, 778 }, 779 }) 780 tasks := sch.Tasks() 781 So(tasks, ShouldHaveLength, 4) 782 sortTasksByClassName(tasks) 783 So(tasks.Payloads()[0], ShouldResembleProto, &taskdefs.CreateSwarmingTask{ 784 BuildId: 9021868963221667745, 785 }) 786 // for `builds` topic. 787 So(tasks.Payloads()[1], ShouldResembleProto, &taskdefs.NotifyPubSub{ 788 BuildId: 9021868963221667745, 789 }) 790 // for topic in build.PubSubCallback.topic field. 791 So(tasks.Payloads()[2], ShouldResembleProto, &taskdefs.NotifyPubSubGo{ 792 BuildId: 9021868963221667745, 793 Topic: &pb.BuildbucketCfg_Topic{ 794 Name: "topic", 795 }, 796 Callback: true, 797 }) 798 // for `bulids_v2` topic 799 So(tasks.Payloads()[3], ShouldResembleProto, &taskdefs.NotifyPubSubGoProxy{ 800 BuildId: 9021868963221667745, 801 Project: "project", 802 }) 803 804 So(datastore.Get(ctx, blds), ShouldBeNil) 805 806 ind, err := model.SearchTagIndex(ctx, "buildset", "buildset") 807 So(err, ShouldBeNil) 808 So(ind, ShouldResemble, []*model.TagIndexEntry{ 809 { 810 BuildID: 9021868963221667745, 811 BucketID: "project/bucket", 812 CreatedTime: datastore.RoundTime(testclock.TestRecentTimeUTC), 813 }, 814 }) 815 }) 816 817 Convey("many", func() { 818 reqs := []*pb.ScheduleBuildRequest{ 819 { 820 Builder: &pb.BuilderID{ 821 Project: "project", 822 Bucket: "static bucket", 823 Builder: "static builder", 824 }, 825 Critical: pb.Trinary_UNSET, 826 Retriable: pb.Trinary_UNSET, 827 }, 828 { 829 Builder: &pb.BuilderID{ 830 Project: "project", 831 Bucket: "static bucket", 832 Builder: "static builder", 833 }, 834 Critical: pb.Trinary_YES, 835 Retriable: pb.Trinary_YES, 836 }, 837 { 838 Builder: &pb.BuilderID{ 839 Project: "project", 840 Bucket: "dynamic bucket", 841 Builder: "dynamic builder", 842 }, 843 Critical: pb.Trinary_NO, 844 Retriable: pb.Trinary_NO, 845 }, 846 } 847 testutil.PutBuilder(ctx, "project", "static bucket", "static builder", "") 848 testutil.PutBucket(ctx, "project", "static bucket", &pb.Bucket{Swarming: &pb.Swarming{}}) 849 testutil.PutBucket(ctx, "project", "dynamic bucket", &pb.Bucket{DynamicBuilderTemplate: &pb.Bucket_DynamicBuilderTemplate{}}) 850 851 blds, err := scheduleBuilds(ctx, globalCfg, reqs...) 852 So(err, ShouldBeNil) 853 854 fvs := []any{"luci.project.static bucket", "static builder", ""} 855 So(store.Get(ctx, metrics.V1.BuildCountCreated, time.Time{}, fvs), ShouldEqual, 2) 856 fvs = []any{"luci.project.dynamic bucket", "dynamic builder", ""} 857 So(store.Get(ctx, metrics.V1.BuildCountCreated, time.Time{}, fvs), ShouldEqual, 1) 858 859 So(stripProtos(blds), ShouldResembleProto, []*pb.Build{ 860 { 861 Builder: &pb.BuilderID{ 862 Project: "project", 863 Bucket: "static bucket", 864 Builder: "static builder", 865 }, 866 CreatedBy: "anonymous:anonymous", 867 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 868 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 869 Exe: &pb.Executable{ 870 Cmd: []string{"recipes"}, 871 }, 872 ExecutionTimeout: &durationpb.Duration{ 873 Seconds: 10800, 874 }, 875 GracePeriod: &durationpb.Duration{ 876 Seconds: 30, 877 }, 878 Id: 9021868963221610337, 879 Infra: &pb.BuildInfra{ 880 Bbagent: &pb.BuildInfra_BBAgent{ 881 CacheDir: "cache", 882 PayloadPath: "kitchen-checkout", 883 }, 884 Buildbucket: &pb.BuildInfra_Buildbucket{ 885 Hostname: "app.appspot.com", 886 Agent: &pb.BuildInfra_Buildbucket_Agent{ 887 Input: &pb.BuildInfra_Buildbucket_Agent_Input{}, 888 Purposes: map[string]pb.BuildInfra_Buildbucket_Agent_Purpose{ 889 "kitchen-checkout": pb.BuildInfra_Buildbucket_Agent_PURPOSE_EXE_PAYLOAD, 890 }, 891 }, 892 }, 893 Logdog: &pb.BuildInfra_LogDog{ 894 Prefix: "buildbucket/app/9021868963221610337", 895 Project: "project", 896 }, 897 Resultdb: &pb.BuildInfra_ResultDB{ 898 Hostname: "rdbHost", 899 }, 900 Swarming: &pb.BuildInfra_Swarming{ 901 Hostname: "host", 902 Caches: []*pb.BuildInfra_Swarming_CacheEntry{ 903 { 904 Name: "builder_943d53aa636f1497a9367662af111471018b08dcd116ae5405ff9fab3b2d5682_v2", 905 Path: "builder", 906 WaitForWarmCache: &durationpb.Duration{ 907 Seconds: 240, 908 }, 909 }, 910 }, 911 Priority: 30, 912 }, 913 }, 914 Input: &pb.Build_Input{ 915 Properties: &structpb.Struct{}, 916 }, 917 SchedulingTimeout: &durationpb.Duration{ 918 Seconds: 21600, 919 }, 920 Status: pb.Status_SCHEDULED, 921 Tags: []*pb.StringPair{ 922 { 923 Key: "builder", 924 Value: "static builder", 925 }, 926 }, 927 }, 928 { 929 Builder: &pb.BuilderID{ 930 Project: "project", 931 Bucket: "static bucket", 932 Builder: "static builder", 933 }, 934 CreatedBy: "anonymous:anonymous", 935 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 936 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 937 Exe: &pb.Executable{ 938 Cmd: []string{"recipes"}, 939 }, 940 Critical: pb.Trinary_YES, 941 Retriable: pb.Trinary_YES, 942 ExecutionTimeout: &durationpb.Duration{ 943 Seconds: 10800, 944 }, 945 GracePeriod: &durationpb.Duration{ 946 Seconds: 30, 947 }, 948 Id: 9021868963221610321, 949 Infra: &pb.BuildInfra{ 950 Bbagent: &pb.BuildInfra_BBAgent{ 951 CacheDir: "cache", 952 PayloadPath: "kitchen-checkout", 953 }, 954 Buildbucket: &pb.BuildInfra_Buildbucket{ 955 Hostname: "app.appspot.com", 956 Agent: &pb.BuildInfra_Buildbucket_Agent{ 957 Input: &pb.BuildInfra_Buildbucket_Agent_Input{}, 958 Purposes: map[string]pb.BuildInfra_Buildbucket_Agent_Purpose{ 959 "kitchen-checkout": pb.BuildInfra_Buildbucket_Agent_PURPOSE_EXE_PAYLOAD, 960 }, 961 }, 962 }, 963 Logdog: &pb.BuildInfra_LogDog{ 964 Prefix: "buildbucket/app/9021868963221610321", 965 Project: "project", 966 }, 967 Resultdb: &pb.BuildInfra_ResultDB{ 968 Hostname: "rdbHost", 969 }, 970 Swarming: &pb.BuildInfra_Swarming{ 971 Hostname: "host", 972 Caches: []*pb.BuildInfra_Swarming_CacheEntry{ 973 { 974 Name: "builder_943d53aa636f1497a9367662af111471018b08dcd116ae5405ff9fab3b2d5682_v2", 975 Path: "builder", 976 WaitForWarmCache: &durationpb.Duration{ 977 Seconds: 240, 978 }, 979 }, 980 }, 981 Priority: 30, 982 }, 983 }, 984 Input: &pb.Build_Input{ 985 Properties: &structpb.Struct{}, 986 }, 987 SchedulingTimeout: &durationpb.Duration{ 988 Seconds: 21600, 989 }, 990 Status: pb.Status_SCHEDULED, 991 Tags: []*pb.StringPair{ 992 { 993 Key: "builder", 994 Value: "static builder", 995 }, 996 }, 997 }, 998 { 999 Builder: &pb.BuilderID{ 1000 Project: "project", 1001 Bucket: "dynamic bucket", 1002 Builder: "dynamic builder", 1003 }, 1004 CreatedBy: "anonymous:anonymous", 1005 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 1006 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 1007 Exe: &pb.Executable{ 1008 Cmd: []string{"recipes"}, 1009 }, 1010 Critical: pb.Trinary_NO, 1011 Retriable: pb.Trinary_NO, 1012 ExecutionTimeout: &durationpb.Duration{ 1013 Seconds: 10800, 1014 }, 1015 GracePeriod: &durationpb.Duration{ 1016 Seconds: 30, 1017 }, 1018 Id: 9021868963221610305, 1019 Infra: &pb.BuildInfra{ 1020 Bbagent: &pb.BuildInfra_BBAgent{ 1021 CacheDir: "cache", 1022 PayloadPath: "kitchen-checkout", 1023 }, 1024 Buildbucket: &pb.BuildInfra_Buildbucket{ 1025 Hostname: "app.appspot.com", 1026 Agent: &pb.BuildInfra_Buildbucket_Agent{ 1027 Input: &pb.BuildInfra_Buildbucket_Agent_Input{}, 1028 Purposes: map[string]pb.BuildInfra_Buildbucket_Agent_Purpose{ 1029 "kitchen-checkout": pb.BuildInfra_Buildbucket_Agent_PURPOSE_EXE_PAYLOAD, 1030 }, 1031 }, 1032 }, 1033 Logdog: &pb.BuildInfra_LogDog{ 1034 Prefix: "buildbucket/app/9021868963221610305", 1035 Project: "project", 1036 }, 1037 Resultdb: &pb.BuildInfra_ResultDB{ 1038 Hostname: "rdbHost", 1039 }, 1040 Swarming: &pb.BuildInfra_Swarming{ 1041 Caches: []*pb.BuildInfra_Swarming_CacheEntry{ 1042 { 1043 Name: "builder_e229fa0169afaeb5fa8340560ffb3c5fe529169e0207f7378bd115cd74977bd2_v2", 1044 Path: "builder", 1045 WaitForWarmCache: &durationpb.Duration{ 1046 Seconds: 240, 1047 }, 1048 }, 1049 }, 1050 Priority: 30, 1051 }, 1052 }, 1053 Input: &pb.Build_Input{ 1054 Properties: &structpb.Struct{}, 1055 }, 1056 SchedulingTimeout: &durationpb.Duration{ 1057 Seconds: 21600, 1058 }, 1059 Status: pb.Status_SCHEDULED, 1060 Tags: []*pb.StringPair{ 1061 { 1062 Key: "builder", 1063 Value: "dynamic builder", 1064 }, 1065 }, 1066 }, 1067 }) 1068 1069 So(blds, ShouldResemble, []*model.Build{ 1070 { 1071 ID: 9021868963221610337, 1072 BucketID: "project/static bucket", 1073 BuilderID: "project/static bucket/static builder", 1074 CreatedBy: "anonymous:anonymous", 1075 CreateTime: testclock.TestRecentTimeUTC, 1076 StatusChangedTime: testclock.TestRecentTimeUTC, 1077 Incomplete: true, 1078 IsLuci: true, 1079 Status: pb.Status_SCHEDULED, 1080 Tags: []string{ 1081 "builder:static builder", 1082 }, 1083 Project: "project", 1084 LegacyProperties: model.LegacyProperties{ 1085 Status: model.Scheduled, 1086 }, 1087 }, 1088 { 1089 ID: 9021868963221610321, 1090 BucketID: "project/static bucket", 1091 BuilderID: "project/static bucket/static builder", 1092 CreatedBy: "anonymous:anonymous", 1093 CreateTime: testclock.TestRecentTimeUTC, 1094 StatusChangedTime: testclock.TestRecentTimeUTC, 1095 Incomplete: true, 1096 IsLuci: true, 1097 Status: pb.Status_SCHEDULED, 1098 Tags: []string{ 1099 "builder:static builder", 1100 }, 1101 Project: "project", 1102 LegacyProperties: model.LegacyProperties{ 1103 Status: model.Scheduled, 1104 }, 1105 }, 1106 { 1107 ID: 9021868963221610305, 1108 BucketID: "project/dynamic bucket", 1109 BuilderID: "project/dynamic bucket/dynamic builder", 1110 CreatedBy: "anonymous:anonymous", 1111 CreateTime: testclock.TestRecentTimeUTC, 1112 StatusChangedTime: testclock.TestRecentTimeUTC, 1113 Incomplete: true, 1114 IsLuci: true, 1115 Status: pb.Status_SCHEDULED, 1116 Tags: []string{ 1117 "builder:dynamic builder", 1118 }, 1119 Project: "project", 1120 LegacyProperties: model.LegacyProperties{ 1121 Status: model.Scheduled, 1122 }, 1123 }, 1124 }) 1125 1126 So(sch.Tasks(), ShouldHaveLength, 6) 1127 So(datastore.Get(ctx, blds), ShouldBeNil) 1128 }) 1129 1130 Convey("one success and one failure", func() { 1131 reqs := []*pb.ScheduleBuildRequest{ 1132 { 1133 Builder: &pb.BuilderID{ 1134 Project: "project", 1135 Bucket: "bucket", 1136 Builder: "builder", 1137 }, 1138 Notify: &pb.NotificationConfig{ 1139 PubsubTopic: "topic", 1140 UserData: []byte("data"), 1141 }, 1142 Tags: []*pb.StringPair{ 1143 { 1144 Key: "buildset", 1145 Value: "buildset", 1146 }, 1147 { 1148 Key: "user_agent", 1149 Value: "gerrit", 1150 }, 1151 }, 1152 }, 1153 { 1154 RequestId: "dupReqIdWithoutBuildAssociated", 1155 Builder: &pb.BuilderID{ 1156 Project: "project", 1157 Bucket: "bucket", 1158 Builder: "builder", 1159 }, 1160 Notify: &pb.NotificationConfig{ 1161 PubsubTopic: "topic", 1162 UserData: []byte("data"), 1163 }, 1164 Tags: []*pb.StringPair{ 1165 { 1166 Key: "buildset", 1167 Value: "buildset", 1168 }, 1169 { 1170 Key: "user_agent", 1171 Value: "gerrit", 1172 }, 1173 }, 1174 }, 1175 } 1176 r := model.NewRequestID(ctx, 0, time.Time{}, "dupReqIdWithoutBuildAssociated") 1177 So(datastore.Put(ctx, 1178 r, 1179 &model.Builder{ 1180 Parent: model.BucketKey(ctx, "project", "bucket"), 1181 ID: "builder", 1182 Config: &pb.BuilderConfig{ 1183 Name: "builder", 1184 SwarmingHost: "host", 1185 }, 1186 }), ShouldBeNil) 1187 testutil.PutBucket(ctx, "project", "bucket", nil) 1188 1189 blds, err := scheduleBuilds(ctx, globalCfg, reqs...) 1190 So(err.(errors.MultiError), ShouldHaveLength, 2) 1191 So(err.(errors.MultiError)[1], ShouldErrLike, "failed to fetch deduplicated build") 1192 So(store.Get(ctx, metrics.V1.BuildCountCreated, time.Time{}, fv("gerrit")), ShouldEqual, 1) 1193 So(stripProtos(blds), ShouldResembleProto, []*pb.Build{ 1194 { 1195 Builder: &pb.BuilderID{ 1196 Project: "project", 1197 Bucket: "bucket", 1198 Builder: "builder", 1199 }, 1200 CreatedBy: "anonymous:anonymous", 1201 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 1202 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 1203 Exe: &pb.Executable{ 1204 Cmd: []string{"recipes"}, 1205 }, 1206 ExecutionTimeout: &durationpb.Duration{ 1207 Seconds: 10800, 1208 }, 1209 GracePeriod: &durationpb.Duration{ 1210 Seconds: 30, 1211 }, 1212 Id: 9021868963222163313, 1213 Infra: &pb.BuildInfra{ 1214 Bbagent: &pb.BuildInfra_BBAgent{ 1215 CacheDir: "cache", 1216 PayloadPath: "kitchen-checkout", 1217 }, 1218 Buildbucket: &pb.BuildInfra_Buildbucket{ 1219 Hostname: "app.appspot.com", 1220 Agent: &pb.BuildInfra_Buildbucket_Agent{ 1221 Input: &pb.BuildInfra_Buildbucket_Agent_Input{}, 1222 Purposes: map[string]pb.BuildInfra_Buildbucket_Agent_Purpose{ 1223 "kitchen-checkout": pb.BuildInfra_Buildbucket_Agent_PURPOSE_EXE_PAYLOAD, 1224 }, 1225 }, 1226 }, 1227 Logdog: &pb.BuildInfra_LogDog{ 1228 Prefix: "buildbucket/app/9021868963222163313", 1229 Project: "project", 1230 }, 1231 Resultdb: &pb.BuildInfra_ResultDB{ 1232 Hostname: "rdbHost", 1233 }, 1234 Swarming: &pb.BuildInfra_Swarming{ 1235 Hostname: "host", 1236 Caches: []*pb.BuildInfra_Swarming_CacheEntry{ 1237 { 1238 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 1239 Path: "builder", 1240 WaitForWarmCache: &durationpb.Duration{ 1241 Seconds: 240, 1242 }, 1243 }, 1244 }, 1245 Priority: 30, 1246 }, 1247 }, 1248 Input: &pb.Build_Input{ 1249 Properties: &structpb.Struct{}, 1250 }, 1251 SchedulingTimeout: &durationpb.Duration{ 1252 Seconds: 21600, 1253 }, 1254 Status: pb.Status_SCHEDULED, 1255 Tags: []*pb.StringPair{ 1256 { 1257 Key: "builder", 1258 Value: "builder", 1259 }, 1260 { 1261 Key: "buildset", 1262 Value: "buildset", 1263 }, 1264 { 1265 Key: "user_agent", 1266 Value: "gerrit", 1267 }, 1268 }, 1269 }, 1270 nil, 1271 }) 1272 So(blds, ShouldResemble, []*model.Build{ 1273 { 1274 ID: 9021868963222163313, 1275 BucketID: "project/bucket", 1276 BuilderID: "project/bucket/builder", 1277 CreatedBy: "anonymous:anonymous", 1278 CreateTime: testclock.TestRecentTimeUTC, 1279 StatusChangedTime: testclock.TestRecentTimeUTC, 1280 Incomplete: true, 1281 IsLuci: true, 1282 Status: pb.Status_SCHEDULED, 1283 Tags: []string{ 1284 "builder:builder", 1285 "buildset:buildset", 1286 "user_agent:gerrit", 1287 }, 1288 Project: "project", 1289 PubSubCallback: model.PubSubCallback{ 1290 Topic: "topic", 1291 UserData: []byte("data"), 1292 }, 1293 LegacyProperties: model.LegacyProperties{ 1294 Status: model.Scheduled, 1295 }, 1296 }, 1297 nil, 1298 }) 1299 So(sch.Tasks(), ShouldHaveLength, 4) 1300 So(datastore.Get(ctx, blds[0]), ShouldBeNil) 1301 1302 ind, err := model.SearchTagIndex(ctx, "buildset", "buildset") 1303 So(err, ShouldBeNil) 1304 // TagIndexEntry for the 2nd req should exist but its build entity shouldn't. 1305 // Because an error was thrown in the build creation transaction which is after the TagIndex update. 1306 So(ind, ShouldResemble, []*model.TagIndexEntry{ 1307 { 1308 BuildID: 9021868963222163313, 1309 BucketID: "project/bucket", 1310 CreatedTime: datastore.RoundTime(testclock.TestRecentTimeUTC), 1311 }, 1312 { 1313 BuildID: 9021868963222163297, 1314 BucketID: "project/bucket", 1315 CreatedTime: datastore.RoundTime(testclock.TestRecentTimeUTC), 1316 }, 1317 }) 1318 So(datastore.Get(ctx, &model.Build{ID: 9021868963222163297}), ShouldErrLike, "no such entity") 1319 }) 1320 1321 Convey("one with parent", func() { 1322 tk, err := buildtoken.GenerateToken(ctx, 1, pb.TokenBody_BUILD) 1323 So(err, ShouldBeNil) 1324 1325 testutil.PutBuilder(ctx, "project", "bucket", "builder", "") 1326 testutil.PutBuilder(ctx, "project", "bucket", "builder", "") 1327 testutil.PutBucket(ctx, "project", "bucket", nil) 1328 1329 So(datastore.Put(ctx, &model.Build{ 1330 Proto: &pb.Build{ 1331 Id: 1, 1332 Builder: &pb.BuilderID{ 1333 Project: "project", 1334 Bucket: "bucket", 1335 Builder: "builder", 1336 }, 1337 Status: pb.Status_STARTED, 1338 AncestorIds: []int64{2, 3}, 1339 }, 1340 UpdateToken: tk, 1341 }), ShouldBeNil) 1342 1343 bld := &model.Build{ID: 1} 1344 So(datastore.Get(ctx, bld), ShouldBeNil) 1345 1346 key := datastore.KeyForObj(ctx, bld) 1347 So(datastore.Put(ctx, &model.BuildInfra{ 1348 Build: key, 1349 Proto: &pb.BuildInfra{ 1350 Swarming: &pb.BuildInfra_Swarming{ 1351 TaskId: "544239050", 1352 }, 1353 }, 1354 }), ShouldBeNil) 1355 1356 req := &pb.ScheduleBuildRequest{ 1357 Builder: &pb.BuilderID{ 1358 Project: "project", 1359 Bucket: "bucket", 1360 Builder: "builder", 1361 }, 1362 Notify: &pb.NotificationConfig{ 1363 PubsubTopic: "topic", 1364 UserData: []byte("data"), 1365 }, 1366 Tags: []*pb.StringPair{ 1367 { 1368 Key: "buildset", 1369 Value: "buildset", 1370 }, 1371 { 1372 Key: "buildset", 1373 Value: "buildset", 1374 }, 1375 { 1376 Key: "user_agent", 1377 Value: "gerrit", 1378 }, 1379 }, 1380 CanOutliveParent: pb.Trinary_NO, 1381 } 1382 ctx := metadata.NewIncomingContext(ctx, metadata.Pairs(bb.BuildbucketTokenHeader, tk)) 1383 blds, err := scheduleBuilds(ctx, globalCfg, req) 1384 So(err, ShouldBeNil) 1385 So(store.Get(ctx, metrics.V1.BuildCountCreated, time.Time{}, fv("gerrit")), ShouldEqual, 1) 1386 So(stripProtos(blds), ShouldResembleProto, []*pb.Build{ 1387 { 1388 Builder: &pb.BuilderID{ 1389 Project: "project", 1390 Bucket: "bucket", 1391 Builder: "builder", 1392 }, 1393 CreatedBy: "anonymous:anonymous", 1394 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 1395 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 1396 Exe: &pb.Executable{ 1397 Cmd: []string{"recipes"}, 1398 }, 1399 ExecutionTimeout: &durationpb.Duration{ 1400 Seconds: 10800, 1401 }, 1402 GracePeriod: &durationpb.Duration{ 1403 Seconds: 30, 1404 }, 1405 Id: 9021868963221667745, 1406 Infra: &pb.BuildInfra{ 1407 Bbagent: &pb.BuildInfra_BBAgent{ 1408 CacheDir: "cache", 1409 PayloadPath: "kitchen-checkout", 1410 }, 1411 Buildbucket: &pb.BuildInfra_Buildbucket{ 1412 Hostname: "app.appspot.com", 1413 Agent: &pb.BuildInfra_Buildbucket_Agent{ 1414 Input: &pb.BuildInfra_Buildbucket_Agent_Input{}, 1415 Purposes: map[string]pb.BuildInfra_Buildbucket_Agent_Purpose{ 1416 "kitchen-checkout": pb.BuildInfra_Buildbucket_Agent_PURPOSE_EXE_PAYLOAD, 1417 }, 1418 }, 1419 }, 1420 Logdog: &pb.BuildInfra_LogDog{ 1421 Prefix: "buildbucket/app/9021868963221667745", 1422 Project: "project", 1423 }, 1424 Resultdb: &pb.BuildInfra_ResultDB{ 1425 Hostname: "rdbHost", 1426 }, 1427 Swarming: &pb.BuildInfra_Swarming{ 1428 Hostname: "host", 1429 Caches: []*pb.BuildInfra_Swarming_CacheEntry{ 1430 { 1431 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 1432 Path: "builder", 1433 WaitForWarmCache: &durationpb.Duration{ 1434 Seconds: 240, 1435 }, 1436 }, 1437 }, 1438 Priority: 30, 1439 }, 1440 }, 1441 Input: &pb.Build_Input{ 1442 Properties: &structpb.Struct{}, 1443 }, 1444 SchedulingTimeout: &durationpb.Duration{ 1445 Seconds: 21600, 1446 }, 1447 Status: pb.Status_SCHEDULED, 1448 Tags: []*pb.StringPair{ 1449 { 1450 Key: "builder", 1451 Value: "builder", 1452 }, 1453 { 1454 Key: "buildset", 1455 Value: "buildset", 1456 }, 1457 { 1458 Key: "buildset", 1459 Value: "buildset", 1460 }, 1461 { 1462 Key: "parent_task_id", 1463 Value: "544239051", 1464 }, 1465 { 1466 Key: "user_agent", 1467 Value: "gerrit", 1468 }, 1469 }, 1470 CanOutliveParent: false, 1471 AncestorIds: []int64{2, 3, 1}, 1472 }, 1473 }) 1474 So(blds, ShouldResemble, []*model.Build{ 1475 { 1476 ID: 9021868963221667745, 1477 BucketID: "project/bucket", 1478 BuilderID: "project/bucket/builder", 1479 CreatedBy: "anonymous:anonymous", 1480 CreateTime: testclock.TestRecentTimeUTC, 1481 StatusChangedTime: testclock.TestRecentTimeUTC, 1482 Experiments: nil, 1483 Incomplete: true, 1484 IsLuci: true, 1485 Status: pb.Status_SCHEDULED, 1486 Tags: []string{ 1487 "builder:builder", 1488 "buildset:buildset", 1489 "parent_task_id:544239051", 1490 "user_agent:gerrit", 1491 }, 1492 Project: "project", 1493 PubSubCallback: model.PubSubCallback{ 1494 Topic: "topic", 1495 UserData: []byte("data"), 1496 }, 1497 LegacyProperties: model.LegacyProperties{ 1498 Status: model.Scheduled, 1499 }, 1500 AncestorIds: []int64{2, 3, 1}, 1501 ParentID: 1, 1502 }, 1503 }) 1504 So(sch.Tasks(), ShouldHaveLength, 4) 1505 So(datastore.Get(ctx, blds), ShouldBeNil) 1506 }) 1507 1508 Convey("one shadow, one original, and one with no shadow bucket", func() { 1509 testutil.PutBucket(ctx, "project", "bucket", &pb.Bucket{Swarming: &pb.Swarming{}, Shadow: "bucket.shadow"}) 1510 testutil.PutBucket(ctx, "project", "bucket.shadow", &pb.Bucket{DynamicBuilderTemplate: &pb.Bucket_DynamicBuilderTemplate{}}) 1511 So(datastore.Put(ctx, &model.Builder{ 1512 Parent: model.BucketKey(ctx, "project", "bucket"), 1513 ID: "builder", 1514 Config: &pb.BuilderConfig{ 1515 Name: "builder", 1516 ServiceAccount: "sa@chops-service-accounts.iam.gserviceaccount.com", 1517 Dimensions: []string{"pool:pool1"}, 1518 Properties: `{"a":"b","b":"b"}`, 1519 ShadowBuilderAdjustments: &pb.BuilderConfig_ShadowBuilderAdjustments{ 1520 ServiceAccount: "shadow@chops-service-accounts.iam.gserviceaccount.com", 1521 Pool: "pool2", 1522 Properties: `{"a":"b2","c":"c"}`, 1523 Dimensions: []string{ 1524 "pool:pool2", 1525 }, 1526 }, 1527 }, 1528 }), ShouldBeNil) 1529 1530 tk, err := buildtoken.GenerateToken(ctx, 1, pb.TokenBody_BUILD) 1531 ctx := metadata.NewIncomingContext(ctx, metadata.Pairs(bb.BuildbucketTokenHeader, tk)) 1532 So(err, ShouldBeNil) 1533 // This is the parent led build of the newly requested led build. 1534 So(datastore.Put(ctx, &model.Build{ 1535 Proto: &pb.Build{ 1536 Id: 1, 1537 Builder: &pb.BuilderID{ 1538 Project: "project", 1539 Bucket: "bucket.shadow", 1540 Builder: "builder", 1541 }, 1542 Status: pb.Status_STARTED, 1543 Exe: &pb.Executable{ 1544 Cmd: []string{"recipes"}, 1545 }, 1546 }, 1547 UpdateToken: tk, 1548 }), ShouldBeNil) 1549 So(datastore.Put(ctx, &model.BuildInfra{ 1550 Build: datastore.MakeKey(ctx, "Build", 1), 1551 Proto: &pb.BuildInfra{ 1552 Buildbucket: &pb.BuildInfra_Buildbucket{ 1553 Agent: &pb.BuildInfra_Buildbucket_Agent{ 1554 Input: &pb.BuildInfra_Buildbucket_Agent_Input{ 1555 Data: map[string]*pb.InputDataRef{ 1556 "cipd_bin_packages": { 1557 DataType: &pb.InputDataRef_Cipd{ 1558 Cipd: &pb.InputDataRef_CIPD{ 1559 Server: "cipd server", 1560 Specs: []*pb.InputDataRef_CIPD_PkgSpec{ 1561 {Package: "include", Version: "canary-version"}, 1562 {Package: "include_experiment", Version: "version"}, 1563 }, 1564 }, 1565 }, 1566 OnPath: []string{"cipd_bin_packages", "cipd_bin_packages/bin"}, 1567 }, 1568 "kitchen-checkout": { 1569 DataType: &pb.InputDataRef_Cas{ 1570 Cas: &pb.InputDataRef_CAS{ 1571 CasInstance: "projects/project/instances/instance", 1572 Digest: &pb.InputDataRef_CAS_Digest{ 1573 Hash: "hash", 1574 SizeBytes: 1, 1575 }, 1576 }, 1577 }, 1578 }, 1579 }, 1580 }, 1581 Source: &pb.BuildInfra_Buildbucket_Agent_Source{ 1582 DataType: &pb.BuildInfra_Buildbucket_Agent_Source_Cipd{ 1583 Cipd: &pb.BuildInfra_Buildbucket_Agent_Source_CIPD{ 1584 Package: "infra/tools/luci/bbagent/${platform}", 1585 Version: "canary-version", 1586 Server: "cipd server", 1587 }, 1588 }, 1589 }, 1590 Purposes: map[string]pb.BuildInfra_Buildbucket_Agent_Purpose{ 1591 "kitchen-checkout": pb.BuildInfra_Buildbucket_Agent_PURPOSE_EXE_PAYLOAD, 1592 }, 1593 }, 1594 }, 1595 }, 1596 }), ShouldBeNil) 1597 reqs := []*pb.ScheduleBuildRequest{ 1598 { 1599 Builder: &pb.BuilderID{ 1600 Project: "project", 1601 Bucket: "bucket", 1602 Builder: "builder", 1603 }, 1604 ShadowInput: &pb.ScheduleBuildRequest_ShadowInput{}, 1605 }, 1606 { 1607 Builder: &pb.BuilderID{ 1608 Project: "project", 1609 Bucket: "bucket", 1610 Builder: "builder", 1611 }, 1612 }, 1613 { 1614 Builder: &pb.BuilderID{ 1615 Project: "project", 1616 Bucket: "bucket.shadow", 1617 Builder: "builder", 1618 }, 1619 ShadowInput: &pb.ScheduleBuildRequest_ShadowInput{}, 1620 }, 1621 } 1622 blds, err := scheduleBuilds(ctx, globalCfg, reqs...) 1623 So(err, ShouldNotBeNil) 1624 So(err, ShouldErrLike, "scheduling a shadow build in the original bucket is not allowed") 1625 So(stripProtos(blds), ShouldResembleProto, []*pb.Build{ 1626 { 1627 Builder: &pb.BuilderID{ 1628 Project: "project", 1629 Bucket: "bucket.shadow", 1630 Builder: "builder", 1631 }, 1632 CreatedBy: "anonymous:anonymous", 1633 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 1634 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 1635 Exe: &pb.Executable{ 1636 Cmd: []string{"recipes"}, 1637 }, 1638 ExecutionTimeout: &durationpb.Duration{ 1639 Seconds: 10800, 1640 }, 1641 GracePeriod: &durationpb.Duration{ 1642 Seconds: 30, 1643 }, 1644 Id: 9021868963221610337, 1645 Infra: &pb.BuildInfra{ 1646 Bbagent: &pb.BuildInfra_BBAgent{ 1647 CacheDir: "cache", 1648 PayloadPath: "kitchen-checkout", 1649 }, 1650 Buildbucket: &pb.BuildInfra_Buildbucket{ 1651 Hostname: "app.appspot.com", 1652 Agent: &pb.BuildInfra_Buildbucket_Agent{ 1653 // Inherited from its parent. 1654 Input: &pb.BuildInfra_Buildbucket_Agent_Input{ 1655 Data: map[string]*pb.InputDataRef{ 1656 "cipd_bin_packages": { 1657 DataType: &pb.InputDataRef_Cipd{ 1658 Cipd: &pb.InputDataRef_CIPD{ 1659 Server: "cipd server", 1660 Specs: []*pb.InputDataRef_CIPD_PkgSpec{ 1661 {Package: "include", Version: "canary-version"}, 1662 {Package: "include_experiment", Version: "version"}, 1663 }, 1664 }, 1665 }, 1666 OnPath: []string{"cipd_bin_packages", "cipd_bin_packages/bin"}, 1667 }, 1668 "kitchen-checkout": { 1669 DataType: &pb.InputDataRef_Cas{ 1670 Cas: &pb.InputDataRef_CAS{ 1671 CasInstance: "projects/project/instances/instance", 1672 Digest: &pb.InputDataRef_CAS_Digest{ 1673 Hash: "hash", 1674 SizeBytes: 1, 1675 }, 1676 }, 1677 }, 1678 }, 1679 }, 1680 }, 1681 Purposes: map[string]pb.BuildInfra_Buildbucket_Agent_Purpose{ 1682 "kitchen-checkout": pb.BuildInfra_Buildbucket_Agent_PURPOSE_EXE_PAYLOAD, 1683 }, 1684 Source: &pb.BuildInfra_Buildbucket_Agent_Source{ 1685 DataType: &pb.BuildInfra_Buildbucket_Agent_Source_Cipd{ 1686 Cipd: &pb.BuildInfra_Buildbucket_Agent_Source_CIPD{ 1687 Package: "infra/tools/luci/bbagent/${platform}", 1688 Version: "canary-version", 1689 Server: "cipd server", 1690 }, 1691 }, 1692 }, 1693 CipdPackagesCache: &pb.CacheEntry{ 1694 Name: "cipd_cache_60bbd3834a15dabe356b6b277007f73bc1b4bdb8dff69da7db09d155463f8f75", 1695 Path: "cipd_cache", 1696 }, 1697 }, 1698 }, 1699 Logdog: &pb.BuildInfra_LogDog{ 1700 Prefix: "buildbucket/app/9021868963221610337", 1701 Project: "project", 1702 }, 1703 Resultdb: &pb.BuildInfra_ResultDB{ 1704 Hostname: "rdbHost", 1705 }, 1706 Swarming: &pb.BuildInfra_Swarming{ 1707 Caches: []*pb.BuildInfra_Swarming_CacheEntry{ 1708 { 1709 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 1710 Path: "builder", 1711 WaitForWarmCache: &durationpb.Duration{ 1712 Seconds: 240, 1713 }, 1714 }, 1715 }, 1716 Priority: 30, 1717 TaskServiceAccount: "shadow@chops-service-accounts.iam.gserviceaccount.com", 1718 TaskDimensions: []*pb.RequestedDimension{ 1719 { 1720 Key: "pool", 1721 Value: "pool2", 1722 }, 1723 }, 1724 }, 1725 Led: &pb.BuildInfra_Led{ 1726 ShadowedBucket: "bucket", 1727 }, 1728 }, 1729 Input: &pb.Build_Input{ 1730 Properties: &structpb.Struct{ 1731 Fields: map[string]*structpb.Value{ 1732 "$recipe_engine/led": { 1733 Kind: &structpb.Value_StructValue{ 1734 StructValue: &structpb.Struct{ 1735 Fields: map[string]*structpb.Value{ 1736 "shadowed_bucket": { 1737 Kind: &structpb.Value_StringValue{ 1738 StringValue: "bucket", 1739 }, 1740 }, 1741 }, 1742 }, 1743 }, 1744 }, 1745 "a": { 1746 Kind: &structpb.Value_StringValue{ 1747 StringValue: "b2", 1748 }, 1749 }, 1750 "b": { 1751 Kind: &structpb.Value_StringValue{ 1752 StringValue: "b", 1753 }, 1754 }, 1755 "c": { 1756 Kind: &structpb.Value_StringValue{ 1757 StringValue: "c", 1758 }, 1759 }, 1760 }, 1761 }, 1762 }, 1763 SchedulingTimeout: &durationpb.Duration{ 1764 Seconds: 21600, 1765 }, 1766 Status: pb.Status_SCHEDULED, 1767 Tags: []*pb.StringPair{ 1768 { 1769 Key: "builder", 1770 Value: "builder", 1771 }, 1772 }, 1773 AncestorIds: []int64{1}, 1774 CanOutliveParent: true, 1775 }, 1776 { 1777 Builder: &pb.BuilderID{ 1778 Project: "project", 1779 Bucket: "bucket", 1780 Builder: "builder", 1781 }, 1782 CreatedBy: "anonymous:anonymous", 1783 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 1784 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 1785 Exe: &pb.Executable{ 1786 Cmd: []string{"recipes"}, 1787 }, 1788 ExecutionTimeout: &durationpb.Duration{ 1789 Seconds: 10800, 1790 }, 1791 GracePeriod: &durationpb.Duration{ 1792 Seconds: 30, 1793 }, 1794 Id: 9021868963221610321, 1795 Infra: &pb.BuildInfra{ 1796 Bbagent: &pb.BuildInfra_BBAgent{ 1797 CacheDir: "cache", 1798 PayloadPath: "kitchen-checkout", 1799 }, 1800 Buildbucket: &pb.BuildInfra_Buildbucket{ 1801 Hostname: "app.appspot.com", 1802 Agent: &pb.BuildInfra_Buildbucket_Agent{ 1803 Input: &pb.BuildInfra_Buildbucket_Agent_Input{}, 1804 Purposes: map[string]pb.BuildInfra_Buildbucket_Agent_Purpose{ 1805 "kitchen-checkout": pb.BuildInfra_Buildbucket_Agent_PURPOSE_EXE_PAYLOAD, 1806 }, 1807 }, 1808 }, 1809 Logdog: &pb.BuildInfra_LogDog{ 1810 Prefix: "buildbucket/app/9021868963221610321", 1811 Project: "project", 1812 }, 1813 Resultdb: &pb.BuildInfra_ResultDB{ 1814 Hostname: "rdbHost", 1815 }, 1816 Swarming: &pb.BuildInfra_Swarming{ 1817 Caches: []*pb.BuildInfra_Swarming_CacheEntry{ 1818 { 1819 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 1820 Path: "builder", 1821 WaitForWarmCache: &durationpb.Duration{ 1822 Seconds: 240, 1823 }, 1824 }, 1825 }, 1826 Priority: 30, 1827 TaskServiceAccount: "sa@chops-service-accounts.iam.gserviceaccount.com", 1828 TaskDimensions: []*pb.RequestedDimension{ 1829 { 1830 Key: "pool", 1831 Value: "pool1", 1832 }, 1833 }, 1834 }, 1835 }, 1836 Input: &pb.Build_Input{ 1837 Properties: &structpb.Struct{ 1838 Fields: map[string]*structpb.Value{ 1839 "a": { 1840 Kind: &structpb.Value_StringValue{ 1841 StringValue: "b", 1842 }, 1843 }, 1844 "b": { 1845 Kind: &structpb.Value_StringValue{ 1846 StringValue: "b", 1847 }, 1848 }, 1849 }, 1850 }, 1851 }, 1852 SchedulingTimeout: &durationpb.Duration{ 1853 Seconds: 21600, 1854 }, 1855 Status: pb.Status_SCHEDULED, 1856 Tags: []*pb.StringPair{ 1857 { 1858 Key: "builder", 1859 Value: "builder", 1860 }, 1861 }, 1862 AncestorIds: []int64{1}, 1863 CanOutliveParent: true, 1864 }, 1865 nil, 1866 }) 1867 }) 1868 }) 1869 1870 Convey("scheduleRequestFromTemplate", t, func() { 1871 ctx := metrics.WithServiceInfo(memory.Use(context.Background()), "svc", "job", "ins") 1872 datastore.GetTestable(ctx).AutoIndex(true) 1873 datastore.GetTestable(ctx).Consistent(true) 1874 ctx = auth.WithState(ctx, &authtest.FakeState{ 1875 Identity: userID, 1876 FakeDB: authtest.NewFakeDB( 1877 authtest.MockPermission(userID, "project:bucket", bbperms.BuildsGet), 1878 ), 1879 }) 1880 1881 testutil.PutBucket(ctx, "project", "bucket", nil) 1882 1883 Convey("nil", func() { 1884 ret, err := scheduleRequestFromTemplate(ctx, nil) 1885 So(err, ShouldBeNil) 1886 So(ret, ShouldBeNil) 1887 }) 1888 1889 Convey("empty", func() { 1890 req := &pb.ScheduleBuildRequest{} 1891 ret, err := scheduleRequestFromTemplate(ctx, req) 1892 So(err, ShouldBeNil) 1893 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{}) 1894 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{}) 1895 }) 1896 1897 Convey("not found", func() { 1898 req := &pb.ScheduleBuildRequest{ 1899 TemplateBuildId: 1, 1900 } 1901 ret, err := scheduleRequestFromTemplate(ctx, req) 1902 So(err, ShouldErrLike, "not found") 1903 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 1904 TemplateBuildId: 1, 1905 }) 1906 So(ret, ShouldBeNil) 1907 }) 1908 1909 Convey("permission denied", func() { 1910 ctx = auth.WithState(ctx, &authtest.FakeState{ 1911 Identity: "user:unauthorized@example.com", 1912 }) 1913 So(datastore.Put(ctx, &model.Build{ 1914 Proto: &pb.Build{ 1915 Id: 1, 1916 Builder: &pb.BuilderID{ 1917 Project: "project", 1918 Bucket: "bucket", 1919 Builder: "builder", 1920 }, 1921 }, 1922 }), ShouldBeNil) 1923 req := &pb.ScheduleBuildRequest{ 1924 TemplateBuildId: 1, 1925 } 1926 ret, err := scheduleRequestFromTemplate(ctx, req) 1927 So(err, ShouldErrLike, "not found") 1928 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 1929 TemplateBuildId: 1, 1930 }) 1931 So(ret, ShouldBeNil) 1932 }) 1933 1934 Convey("canary", func() { 1935 Convey("false default", func() { 1936 So(datastore.Put(ctx, &model.Build{ 1937 Proto: &pb.Build{ 1938 Id: 1, 1939 Builder: &pb.BuilderID{ 1940 Project: "project", 1941 Bucket: "bucket", 1942 Builder: "builder", 1943 }, 1944 }, 1945 Experiments: []string{ 1946 "-" + bb.ExperimentBBCanarySoftware, 1947 }, 1948 }), ShouldBeNil) 1949 1950 Convey("merge", func() { 1951 req := &pb.ScheduleBuildRequest{ 1952 TemplateBuildId: 1, 1953 Experiments: map[string]bool{bb.ExperimentBBCanarySoftware: true}, 1954 } 1955 ret, err := scheduleRequestFromTemplate(ctx, req) 1956 So(err, ShouldBeNil) 1957 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 1958 TemplateBuildId: 1, 1959 Experiments: map[string]bool{bb.ExperimentBBCanarySoftware: true}, 1960 }) 1961 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 1962 Builder: &pb.BuilderID{ 1963 Project: "project", 1964 Bucket: "bucket", 1965 Builder: "builder", 1966 }, 1967 Experiments: map[string]bool{ 1968 bb.ExperimentBBCanarySoftware: true, 1969 bb.ExperimentNonProduction: false, 1970 }, 1971 }) 1972 }) 1973 1974 Convey("ok", func() { 1975 req := &pb.ScheduleBuildRequest{ 1976 TemplateBuildId: 1, 1977 } 1978 ret, err := scheduleRequestFromTemplate(ctx, req) 1979 So(err, ShouldBeNil) 1980 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 1981 TemplateBuildId: 1, 1982 }) 1983 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 1984 Builder: &pb.BuilderID{ 1985 Project: "project", 1986 Bucket: "bucket", 1987 Builder: "builder", 1988 }, 1989 Experiments: map[string]bool{ 1990 bb.ExperimentBBCanarySoftware: false, 1991 bb.ExperimentNonProduction: false, 1992 }, 1993 }) 1994 }) 1995 }) 1996 1997 Convey("true default", func() { 1998 So(datastore.Put(ctx, &model.Build{ 1999 Proto: &pb.Build{ 2000 Id: 1, 2001 Builder: &pb.BuilderID{ 2002 Project: "project", 2003 Bucket: "bucket", 2004 Builder: "builder", 2005 }, 2006 }, 2007 Experiments: []string{ 2008 "+" + bb.ExperimentBBCanarySoftware, 2009 }, 2010 }), ShouldBeNil) 2011 2012 Convey("merge", func() { 2013 req := &pb.ScheduleBuildRequest{ 2014 TemplateBuildId: 1, 2015 Experiments: map[string]bool{ 2016 bb.ExperimentBBCanarySoftware: false, 2017 }, 2018 } 2019 ret, err := scheduleRequestFromTemplate(ctx, req) 2020 So(err, ShouldBeNil) 2021 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2022 TemplateBuildId: 1, 2023 Experiments: map[string]bool{ 2024 bb.ExperimentBBCanarySoftware: false, 2025 }, 2026 }) 2027 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2028 Builder: &pb.BuilderID{ 2029 Project: "project", 2030 Bucket: "bucket", 2031 Builder: "builder", 2032 }, 2033 Experiments: map[string]bool{ 2034 bb.ExperimentBBCanarySoftware: false, 2035 bb.ExperimentNonProduction: false, 2036 }, 2037 }) 2038 }) 2039 2040 Convey("ok", func() { 2041 req := &pb.ScheduleBuildRequest{ 2042 TemplateBuildId: 1, 2043 } 2044 ret, err := scheduleRequestFromTemplate(ctx, req) 2045 So(err, ShouldBeNil) 2046 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2047 TemplateBuildId: 1, 2048 }) 2049 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2050 Builder: &pb.BuilderID{ 2051 Project: "project", 2052 Bucket: "bucket", 2053 Builder: "builder", 2054 }, 2055 Experiments: map[string]bool{ 2056 bb.ExperimentBBCanarySoftware: true, 2057 bb.ExperimentNonProduction: false, 2058 }, 2059 }) 2060 }) 2061 }) 2062 }) 2063 2064 Convey("critical", func() { 2065 So(datastore.Put(ctx, &model.Build{ 2066 Proto: &pb.Build{ 2067 Id: 1, 2068 Builder: &pb.BuilderID{ 2069 Project: "project", 2070 Bucket: "bucket", 2071 Builder: "builder", 2072 }, 2073 Critical: pb.Trinary_YES, 2074 }, 2075 Experiments: []string{"-" + bb.ExperimentBBCanarySoftware}, 2076 }), ShouldBeNil) 2077 2078 Convey("merge", func() { 2079 req := &pb.ScheduleBuildRequest{ 2080 TemplateBuildId: 1, 2081 Critical: pb.Trinary_NO, 2082 } 2083 ret, err := scheduleRequestFromTemplate(ctx, req) 2084 So(err, ShouldBeNil) 2085 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2086 TemplateBuildId: 1, 2087 Critical: pb.Trinary_NO, 2088 }) 2089 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2090 Builder: &pb.BuilderID{ 2091 Project: "project", 2092 Bucket: "bucket", 2093 Builder: "builder", 2094 }, 2095 Critical: pb.Trinary_NO, 2096 Experiments: map[string]bool{ 2097 bb.ExperimentBBCanarySoftware: false, 2098 bb.ExperimentNonProduction: false, 2099 }, 2100 }) 2101 }) 2102 2103 Convey("ok", func() { 2104 req := &pb.ScheduleBuildRequest{ 2105 TemplateBuildId: 1, 2106 } 2107 ret, err := scheduleRequestFromTemplate(ctx, req) 2108 So(err, ShouldBeNil) 2109 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2110 TemplateBuildId: 1, 2111 }) 2112 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2113 Builder: &pb.BuilderID{ 2114 Project: "project", 2115 Bucket: "bucket", 2116 Builder: "builder", 2117 }, 2118 Critical: pb.Trinary_YES, 2119 Experiments: map[string]bool{ 2120 bb.ExperimentBBCanarySoftware: false, 2121 bb.ExperimentNonProduction: false, 2122 }, 2123 }) 2124 }) 2125 }) 2126 2127 Convey("exe", func() { 2128 So(datastore.Put(ctx, &model.Build{ 2129 Proto: &pb.Build{ 2130 Id: 1, 2131 Builder: &pb.BuilderID{ 2132 Project: "project", 2133 Bucket: "bucket", 2134 Builder: "builder", 2135 }, 2136 Exe: &pb.Executable{ 2137 CipdPackage: "package", 2138 CipdVersion: "version", 2139 }, 2140 }, 2141 Experiments: []string{"-" + bb.ExperimentBBCanarySoftware}, 2142 }), ShouldBeNil) 2143 2144 Convey("merge", func() { 2145 Convey("empty", func() { 2146 req := &pb.ScheduleBuildRequest{ 2147 TemplateBuildId: 1, 2148 Exe: &pb.Executable{}, 2149 } 2150 ret, err := scheduleRequestFromTemplate(ctx, req) 2151 So(err, ShouldBeNil) 2152 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2153 TemplateBuildId: 1, 2154 Exe: &pb.Executable{}, 2155 }) 2156 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2157 Builder: &pb.BuilderID{ 2158 Project: "project", 2159 Bucket: "bucket", 2160 Builder: "builder", 2161 }, 2162 Exe: &pb.Executable{}, 2163 Experiments: map[string]bool{ 2164 bb.ExperimentBBCanarySoftware: false, 2165 bb.ExperimentNonProduction: false, 2166 }, 2167 }) 2168 }) 2169 2170 Convey("non-empty", func() { 2171 req := &pb.ScheduleBuildRequest{ 2172 TemplateBuildId: 1, 2173 Exe: &pb.Executable{ 2174 CipdPackage: "package", 2175 CipdVersion: "new", 2176 }, 2177 } 2178 ret, err := scheduleRequestFromTemplate(ctx, req) 2179 So(err, ShouldBeNil) 2180 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2181 TemplateBuildId: 1, 2182 Exe: &pb.Executable{ 2183 CipdPackage: "package", 2184 CipdVersion: "new", 2185 }, 2186 }) 2187 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2188 Builder: &pb.BuilderID{ 2189 Project: "project", 2190 Bucket: "bucket", 2191 Builder: "builder", 2192 }, 2193 Experiments: map[string]bool{ 2194 bb.ExperimentBBCanarySoftware: false, 2195 bb.ExperimentNonProduction: false, 2196 }, 2197 Exe: &pb.Executable{ 2198 CipdPackage: "package", 2199 CipdVersion: "new", 2200 }, 2201 }) 2202 }) 2203 }) 2204 2205 Convey("ok", func() { 2206 req := &pb.ScheduleBuildRequest{ 2207 TemplateBuildId: 1, 2208 } 2209 ret, err := scheduleRequestFromTemplate(ctx, req) 2210 So(err, ShouldBeNil) 2211 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2212 TemplateBuildId: 1, 2213 }) 2214 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2215 Builder: &pb.BuilderID{ 2216 Project: "project", 2217 Bucket: "bucket", 2218 Builder: "builder", 2219 }, 2220 Experiments: map[string]bool{ 2221 bb.ExperimentBBCanarySoftware: false, 2222 bb.ExperimentNonProduction: false, 2223 }, 2224 Exe: &pb.Executable{ 2225 CipdPackage: "package", 2226 CipdVersion: "version", 2227 }, 2228 }) 2229 }) 2230 }) 2231 2232 Convey("gerrit changes", func() { 2233 So(datastore.Put(ctx, &model.Build{ 2234 Proto: &pb.Build{ 2235 Id: 1, 2236 Builder: &pb.BuilderID{ 2237 Project: "project", 2238 Bucket: "bucket", 2239 Builder: "builder", 2240 }, 2241 Input: &pb.Build_Input{ 2242 GerritChanges: []*pb.GerritChange{ 2243 { 2244 Host: "example.com", 2245 Project: "project", 2246 Change: 1, 2247 Patchset: 1, 2248 }, 2249 }, 2250 }, 2251 }, 2252 Experiments: []string{"-" + bb.ExperimentBBCanarySoftware}, 2253 }), ShouldBeNil) 2254 2255 Convey("merge", func() { 2256 Convey("empty", func() { 2257 req := &pb.ScheduleBuildRequest{ 2258 TemplateBuildId: 1, 2259 GerritChanges: []*pb.GerritChange{}, 2260 } 2261 ret, err := scheduleRequestFromTemplate(ctx, req) 2262 So(err, ShouldBeNil) 2263 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2264 TemplateBuildId: 1, 2265 GerritChanges: []*pb.GerritChange{}, 2266 }) 2267 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2268 Builder: &pb.BuilderID{ 2269 Project: "project", 2270 Bucket: "bucket", 2271 Builder: "builder", 2272 }, 2273 Experiments: map[string]bool{ 2274 bb.ExperimentBBCanarySoftware: false, 2275 bb.ExperimentNonProduction: false, 2276 }, 2277 GerritChanges: []*pb.GerritChange{ 2278 { 2279 Host: "example.com", 2280 Project: "project", 2281 Change: 1, 2282 Patchset: 1, 2283 }, 2284 }, 2285 }) 2286 }) 2287 2288 Convey("non-empty", func() { 2289 req := &pb.ScheduleBuildRequest{ 2290 TemplateBuildId: 1, 2291 GerritChanges: []*pb.GerritChange{ 2292 { 2293 Host: "example.com", 2294 Project: "project", 2295 Change: 1, 2296 Patchset: 2, 2297 }, 2298 }, 2299 } 2300 ret, err := scheduleRequestFromTemplate(ctx, req) 2301 So(err, ShouldBeNil) 2302 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2303 TemplateBuildId: 1, 2304 GerritChanges: []*pb.GerritChange{ 2305 { 2306 Host: "example.com", 2307 Project: "project", 2308 Change: 1, 2309 Patchset: 2, 2310 }, 2311 }, 2312 }) 2313 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2314 Builder: &pb.BuilderID{ 2315 Project: "project", 2316 Bucket: "bucket", 2317 Builder: "builder", 2318 }, 2319 Experiments: map[string]bool{ 2320 bb.ExperimentBBCanarySoftware: false, 2321 bb.ExperimentNonProduction: false, 2322 }, 2323 GerritChanges: []*pb.GerritChange{ 2324 { 2325 Host: "example.com", 2326 Project: "project", 2327 Change: 1, 2328 Patchset: 2, 2329 }, 2330 }, 2331 }) 2332 }) 2333 }) 2334 2335 Convey("ok", func() { 2336 req := &pb.ScheduleBuildRequest{ 2337 TemplateBuildId: 1, 2338 } 2339 ret, err := scheduleRequestFromTemplate(ctx, req) 2340 So(err, ShouldBeNil) 2341 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2342 TemplateBuildId: 1, 2343 }) 2344 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2345 Builder: &pb.BuilderID{ 2346 Project: "project", 2347 Bucket: "bucket", 2348 Builder: "builder", 2349 }, 2350 Experiments: map[string]bool{ 2351 bb.ExperimentBBCanarySoftware: false, 2352 bb.ExperimentNonProduction: false, 2353 }, 2354 GerritChanges: []*pb.GerritChange{ 2355 { 2356 Host: "example.com", 2357 Project: "project", 2358 Change: 1, 2359 Patchset: 1, 2360 }, 2361 }, 2362 }) 2363 }) 2364 }) 2365 2366 Convey("gitiles commit", func() { 2367 So(datastore.Put(ctx, &model.Build{ 2368 Proto: &pb.Build{ 2369 Id: 1, 2370 Builder: &pb.BuilderID{ 2371 Project: "project", 2372 Bucket: "bucket", 2373 Builder: "builder", 2374 }, 2375 Input: &pb.Build_Input{ 2376 GitilesCommit: &pb.GitilesCommit{ 2377 Host: "example.com", 2378 Project: "project", 2379 Ref: "refs/heads/master", 2380 }, 2381 }, 2382 }, 2383 Experiments: []string{"-" + bb.ExperimentBBCanarySoftware}, 2384 }), ShouldBeNil) 2385 req := &pb.ScheduleBuildRequest{ 2386 TemplateBuildId: 1, 2387 } 2388 ret, err := scheduleRequestFromTemplate(ctx, req) 2389 So(err, ShouldBeNil) 2390 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2391 TemplateBuildId: 1, 2392 }) 2393 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2394 Builder: &pb.BuilderID{ 2395 Project: "project", 2396 Bucket: "bucket", 2397 Builder: "builder", 2398 }, 2399 Experiments: map[string]bool{ 2400 bb.ExperimentBBCanarySoftware: false, 2401 bb.ExperimentNonProduction: false, 2402 }, 2403 GitilesCommit: &pb.GitilesCommit{ 2404 Host: "example.com", 2405 Project: "project", 2406 Ref: "refs/heads/master", 2407 }, 2408 }) 2409 }) 2410 2411 Convey("input properties", func() { 2412 So(datastore.Put(ctx, &model.Build{ 2413 Proto: &pb.Build{ 2414 Id: 1, 2415 Builder: &pb.BuilderID{ 2416 Project: "project", 2417 Bucket: "bucket", 2418 Builder: "builder", 2419 }, 2420 }, 2421 Experiments: []string{"-" + bb.ExperimentBBCanarySoftware}, 2422 }), ShouldBeNil) 2423 2424 Convey("empty", func() { 2425 So(datastore.Put(ctx, &model.BuildInputProperties{ 2426 Build: datastore.MakeKey(ctx, "Build", 1), 2427 }), ShouldBeNil) 2428 2429 Convey("merge", func() { 2430 req := &pb.ScheduleBuildRequest{ 2431 TemplateBuildId: 1, 2432 Properties: &structpb.Struct{ 2433 Fields: map[string]*structpb.Value{ 2434 "input": { 2435 Kind: &structpb.Value_StringValue{ 2436 StringValue: "input value", 2437 }, 2438 }, 2439 }, 2440 }, 2441 } 2442 ret, err := scheduleRequestFromTemplate(ctx, req) 2443 So(err, ShouldBeNil) 2444 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2445 TemplateBuildId: 1, 2446 Properties: &structpb.Struct{ 2447 Fields: map[string]*structpb.Value{ 2448 "input": { 2449 Kind: &structpb.Value_StringValue{ 2450 StringValue: "input value", 2451 }, 2452 }, 2453 }, 2454 }, 2455 }) 2456 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2457 Builder: &pb.BuilderID{ 2458 Project: "project", 2459 Bucket: "bucket", 2460 Builder: "builder", 2461 }, 2462 Experiments: map[string]bool{ 2463 bb.ExperimentBBCanarySoftware: false, 2464 bb.ExperimentNonProduction: false, 2465 }, 2466 Properties: &structpb.Struct{ 2467 Fields: map[string]*structpb.Value{ 2468 "input": { 2469 Kind: &structpb.Value_StringValue{ 2470 StringValue: "input value", 2471 }, 2472 }, 2473 }, 2474 }, 2475 }) 2476 }) 2477 2478 Convey("ok", func() { 2479 req := &pb.ScheduleBuildRequest{ 2480 TemplateBuildId: 1, 2481 } 2482 ret, err := scheduleRequestFromTemplate(ctx, req) 2483 So(err, ShouldBeNil) 2484 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2485 TemplateBuildId: 1, 2486 }) 2487 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2488 Builder: &pb.BuilderID{ 2489 Project: "project", 2490 Bucket: "bucket", 2491 Builder: "builder", 2492 }, 2493 Experiments: map[string]bool{ 2494 bb.ExperimentBBCanarySoftware: false, 2495 bb.ExperimentNonProduction: false, 2496 }, 2497 }) 2498 }) 2499 }) 2500 2501 Convey("non-empty", func() { 2502 So(datastore.Put(ctx, &model.BuildInputProperties{ 2503 Build: datastore.MakeKey(ctx, "Build", 1), 2504 Proto: &structpb.Struct{ 2505 Fields: map[string]*structpb.Value{ 2506 "input": { 2507 Kind: &structpb.Value_StringValue{ 2508 StringValue: "input value", 2509 }, 2510 }, 2511 }, 2512 }, 2513 }), ShouldBeNil) 2514 2515 Convey("merge", func() { 2516 Convey("empty", func() { 2517 req := &pb.ScheduleBuildRequest{ 2518 TemplateBuildId: 1, 2519 Properties: &structpb.Struct{}, 2520 } 2521 ret, err := scheduleRequestFromTemplate(ctx, req) 2522 So(err, ShouldBeNil) 2523 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2524 TemplateBuildId: 1, 2525 Properties: &structpb.Struct{}, 2526 }) 2527 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2528 Builder: &pb.BuilderID{ 2529 Project: "project", 2530 Bucket: "bucket", 2531 Builder: "builder", 2532 }, 2533 Experiments: map[string]bool{ 2534 bb.ExperimentBBCanarySoftware: false, 2535 bb.ExperimentNonProduction: false, 2536 }, 2537 Properties: &structpb.Struct{}, 2538 }) 2539 }) 2540 2541 Convey("non-empty", func() { 2542 req := &pb.ScheduleBuildRequest{ 2543 TemplateBuildId: 1, 2544 Properties: &structpb.Struct{ 2545 Fields: map[string]*structpb.Value{ 2546 "other": { 2547 Kind: &structpb.Value_StringValue{ 2548 StringValue: "other value", 2549 }, 2550 }, 2551 }, 2552 }, 2553 } 2554 ret, err := scheduleRequestFromTemplate(ctx, req) 2555 So(err, ShouldBeNil) 2556 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2557 TemplateBuildId: 1, 2558 Properties: &structpb.Struct{ 2559 Fields: map[string]*structpb.Value{ 2560 "other": { 2561 Kind: &structpb.Value_StringValue{ 2562 StringValue: "other value", 2563 }, 2564 }, 2565 }, 2566 }, 2567 }) 2568 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2569 Builder: &pb.BuilderID{ 2570 Project: "project", 2571 Bucket: "bucket", 2572 Builder: "builder", 2573 }, 2574 Experiments: map[string]bool{ 2575 bb.ExperimentBBCanarySoftware: false, 2576 bb.ExperimentNonProduction: false, 2577 }, 2578 Properties: &structpb.Struct{ 2579 Fields: map[string]*structpb.Value{ 2580 "other": { 2581 Kind: &structpb.Value_StringValue{ 2582 StringValue: "other value", 2583 }, 2584 }, 2585 }, 2586 }, 2587 }) 2588 }) 2589 }) 2590 2591 Convey("ok", func() { 2592 req := &pb.ScheduleBuildRequest{ 2593 TemplateBuildId: 1, 2594 } 2595 ret, err := scheduleRequestFromTemplate(ctx, req) 2596 So(err, ShouldBeNil) 2597 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2598 TemplateBuildId: 1, 2599 }) 2600 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2601 Builder: &pb.BuilderID{ 2602 Project: "project", 2603 Bucket: "bucket", 2604 Builder: "builder", 2605 }, 2606 Experiments: map[string]bool{ 2607 bb.ExperimentBBCanarySoftware: false, 2608 bb.ExperimentNonProduction: false, 2609 }, 2610 Properties: &structpb.Struct{ 2611 Fields: map[string]*structpb.Value{ 2612 "input": { 2613 Kind: &structpb.Value_StringValue{ 2614 StringValue: "input value", 2615 }, 2616 }, 2617 }, 2618 }, 2619 }) 2620 }) 2621 }) 2622 }) 2623 2624 Convey("tags", func() { 2625 So(datastore.Put(ctx, &model.Build{ 2626 Proto: &pb.Build{ 2627 Id: 1, 2628 Builder: &pb.BuilderID{ 2629 Project: "project", 2630 Bucket: "bucket", 2631 Builder: "builder", 2632 }, 2633 }, 2634 Tags: []string{ 2635 "key:value", 2636 }, 2637 Experiments: []string{"-" + bb.ExperimentBBCanarySoftware}, 2638 }), ShouldBeNil) 2639 2640 Convey("merge", func() { 2641 Convey("empty", func() { 2642 req := &pb.ScheduleBuildRequest{ 2643 TemplateBuildId: 1, 2644 Tags: []*pb.StringPair{}, 2645 } 2646 ret, err := scheduleRequestFromTemplate(ctx, req) 2647 So(err, ShouldBeNil) 2648 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2649 TemplateBuildId: 1, 2650 Tags: []*pb.StringPair{}, 2651 }) 2652 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2653 Builder: &pb.BuilderID{ 2654 Project: "project", 2655 Bucket: "bucket", 2656 Builder: "builder", 2657 }, 2658 Experiments: map[string]bool{ 2659 bb.ExperimentBBCanarySoftware: false, 2660 bb.ExperimentNonProduction: false, 2661 }, 2662 Tags: []*pb.StringPair{ 2663 { 2664 Key: "key", 2665 Value: "value", 2666 }, 2667 }, 2668 }) 2669 }) 2670 2671 Convey("non-empty", func() { 2672 req := &pb.ScheduleBuildRequest{ 2673 TemplateBuildId: 1, 2674 Tags: []*pb.StringPair{ 2675 { 2676 Key: "other", 2677 Value: "other", 2678 }, 2679 }, 2680 } 2681 ret, err := scheduleRequestFromTemplate(ctx, req) 2682 So(err, ShouldBeNil) 2683 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2684 TemplateBuildId: 1, 2685 Tags: []*pb.StringPair{ 2686 { 2687 Key: "other", 2688 Value: "other", 2689 }, 2690 }, 2691 }) 2692 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2693 Builder: &pb.BuilderID{ 2694 Project: "project", 2695 Bucket: "bucket", 2696 Builder: "builder", 2697 }, 2698 Experiments: map[string]bool{ 2699 bb.ExperimentBBCanarySoftware: false, 2700 bb.ExperimentNonProduction: false, 2701 }, 2702 Tags: []*pb.StringPair{ 2703 { 2704 Key: "other", 2705 Value: "other", 2706 }, 2707 }, 2708 }) 2709 }) 2710 }) 2711 2712 Convey("ok", func() { 2713 req := &pb.ScheduleBuildRequest{ 2714 TemplateBuildId: 1, 2715 } 2716 ret, err := scheduleRequestFromTemplate(ctx, req) 2717 So(err, ShouldBeNil) 2718 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2719 TemplateBuildId: 1, 2720 }) 2721 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2722 Builder: &pb.BuilderID{ 2723 Project: "project", 2724 Bucket: "bucket", 2725 Builder: "builder", 2726 }, 2727 Experiments: map[string]bool{ 2728 bb.ExperimentBBCanarySoftware: false, 2729 bb.ExperimentNonProduction: false, 2730 }, 2731 Tags: []*pb.StringPair{ 2732 { 2733 Key: "key", 2734 Value: "value", 2735 }, 2736 }, 2737 }) 2738 }) 2739 }) 2740 2741 Convey("requested dimensions", func() { 2742 So(datastore.Put(ctx, &model.Build{ 2743 Proto: &pb.Build{ 2744 Id: 1, 2745 Builder: &pb.BuilderID{ 2746 Project: "project", 2747 Bucket: "bucket", 2748 Builder: "builder", 2749 }, 2750 }, 2751 Experiments: []string{"-" + bb.ExperimentBBCanarySoftware}, 2752 }), ShouldBeNil) 2753 So(datastore.Put(ctx, &model.BuildInfra{ 2754 Build: datastore.MakeKey(ctx, "Build", 1), 2755 Proto: &pb.BuildInfra{ 2756 Buildbucket: &pb.BuildInfra_Buildbucket{ 2757 Hostname: "app.appspot.com", 2758 RequestedDimensions: []*pb.RequestedDimension{ 2759 {Key: "key_in_db", Value: "value_in_db"}, 2760 }, 2761 }, 2762 }, 2763 }), ShouldBeNil) 2764 2765 Convey("ok", func() { 2766 req := &pb.ScheduleBuildRequest{ 2767 TemplateBuildId: 1, 2768 } 2769 ret, err := scheduleRequestFromTemplate(ctx, req) 2770 So(err, ShouldBeNil) 2771 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2772 Builder: &pb.BuilderID{ 2773 Project: "project", 2774 Bucket: "bucket", 2775 Builder: "builder", 2776 }, 2777 Dimensions: []*pb.RequestedDimension{ 2778 {Key: "key_in_db", Value: "value_in_db"}, 2779 }, 2780 Experiments: map[string]bool{ 2781 bb.ExperimentBBCanarySoftware: false, 2782 bb.ExperimentNonProduction: false, 2783 }, 2784 }) 2785 }) 2786 2787 Convey("override", func() { 2788 req := &pb.ScheduleBuildRequest{ 2789 TemplateBuildId: 1, 2790 Dimensions: []*pb.RequestedDimension{ 2791 {Key: "key_in_req", Value: "value_in_req"}, 2792 }, 2793 } 2794 ret, err := scheduleRequestFromTemplate(ctx, req) 2795 So(err, ShouldBeNil) 2796 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2797 Builder: &pb.BuilderID{ 2798 Project: "project", 2799 Bucket: "bucket", 2800 Builder: "builder", 2801 }, 2802 Dimensions: []*pb.RequestedDimension{ 2803 {Key: "key_in_req", Value: "value_in_req"}, 2804 }, 2805 Experiments: map[string]bool{ 2806 bb.ExperimentBBCanarySoftware: false, 2807 bb.ExperimentNonProduction: false, 2808 }, 2809 }) 2810 }) 2811 }) 2812 2813 Convey("priority", func() { 2814 So(datastore.Put(ctx, &model.Build{ 2815 Proto: &pb.Build{ 2816 Id: 1, 2817 Builder: &pb.BuilderID{ 2818 Project: "project", 2819 Bucket: "bucket", 2820 Builder: "builder", 2821 }, 2822 }, 2823 Experiments: []string{"-" + bb.ExperimentBBCanarySoftware}, 2824 }), ShouldBeNil) 2825 So(datastore.Put(ctx, &model.BuildInfra{ 2826 Build: datastore.MakeKey(ctx, "Build", 1), 2827 Proto: &pb.BuildInfra{ 2828 Swarming: &pb.BuildInfra_Swarming{ 2829 Priority: int32(30), 2830 }, 2831 }, 2832 }), ShouldBeNil) 2833 2834 Convey("ok", func() { 2835 req := &pb.ScheduleBuildRequest{ 2836 TemplateBuildId: 1, 2837 } 2838 ret, err := scheduleRequestFromTemplate(ctx, req) 2839 So(err, ShouldBeNil) 2840 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2841 Builder: &pb.BuilderID{ 2842 Project: "project", 2843 Bucket: "bucket", 2844 Builder: "builder", 2845 }, 2846 Priority: int32(30), 2847 Experiments: map[string]bool{ 2848 bb.ExperimentBBCanarySoftware: false, 2849 bb.ExperimentNonProduction: false, 2850 }, 2851 }) 2852 }) 2853 2854 Convey("override", func() { 2855 req := &pb.ScheduleBuildRequest{ 2856 TemplateBuildId: 1, 2857 Priority: int32(25), 2858 } 2859 ret, err := scheduleRequestFromTemplate(ctx, req) 2860 So(err, ShouldBeNil) 2861 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2862 Builder: &pb.BuilderID{ 2863 Project: "project", 2864 Bucket: "bucket", 2865 Builder: "builder", 2866 }, 2867 Priority: int32(25), 2868 Experiments: map[string]bool{ 2869 bb.ExperimentBBCanarySoftware: false, 2870 bb.ExperimentNonProduction: false, 2871 }, 2872 }) 2873 }) 2874 }) 2875 2876 Convey("ok", func() { 2877 So(datastore.Put(ctx, &model.Build{ 2878 Proto: &pb.Build{ 2879 Id: 1, 2880 Builder: &pb.BuilderID{ 2881 Project: "project", 2882 Bucket: "bucket", 2883 Builder: "builder", 2884 }, 2885 }, 2886 Experiments: []string{"-" + bb.ExperimentBBCanarySoftware}, 2887 }), ShouldBeNil) 2888 req := &pb.ScheduleBuildRequest{ 2889 TemplateBuildId: 1, 2890 } 2891 ret, err := scheduleRequestFromTemplate(ctx, req) 2892 So(err, ShouldBeNil) 2893 So(req, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2894 TemplateBuildId: 1, 2895 }) 2896 So(ret, ShouldResembleProto, &pb.ScheduleBuildRequest{ 2897 Builder: &pb.BuilderID{ 2898 Project: "project", 2899 Bucket: "bucket", 2900 Builder: "builder", 2901 }, 2902 Experiments: map[string]bool{ 2903 bb.ExperimentBBCanarySoftware: false, 2904 bb.ExperimentNonProduction: false, 2905 }, 2906 }) 2907 }) 2908 }) 2909 2910 Convey("setDimensions", t, func() { 2911 Convey("config", func() { 2912 Convey("omit", func() { 2913 cfg := &pb.BuilderConfig{ 2914 Dimensions: []string{ 2915 "key:", 2916 }, 2917 } 2918 b := &pb.Build{ 2919 Infra: &pb.BuildInfra{ 2920 Swarming: &pb.BuildInfra_Swarming{}, 2921 }, 2922 } 2923 2924 setDimensions(nil, cfg, b, false) 2925 So(b.Infra.Swarming, ShouldResembleProto, &pb.BuildInfra_Swarming{}) 2926 }) 2927 2928 Convey("simple", func() { 2929 cfg := &pb.BuilderConfig{ 2930 Dimensions: []string{ 2931 "key:value", 2932 }, 2933 } 2934 b := &pb.Build{ 2935 Infra: &pb.BuildInfra{ 2936 Swarming: &pb.BuildInfra_Swarming{}, 2937 }, 2938 } 2939 2940 setDimensions(nil, cfg, b, false) 2941 So(b.Infra.Swarming, ShouldResembleProto, &pb.BuildInfra_Swarming{ 2942 TaskDimensions: []*pb.RequestedDimension{ 2943 { 2944 Key: "key", 2945 Value: "value", 2946 }, 2947 }, 2948 }) 2949 }) 2950 2951 Convey("expiration", func() { 2952 cfg := &pb.BuilderConfig{ 2953 Dimensions: []string{ 2954 "1:key:value", 2955 }, 2956 } 2957 b := &pb.Build{ 2958 Infra: &pb.BuildInfra{ 2959 Swarming: &pb.BuildInfra_Swarming{}, 2960 }, 2961 } 2962 2963 setDimensions(nil, cfg, b, false) 2964 So(b.Infra.Swarming, ShouldResembleProto, &pb.BuildInfra_Swarming{ 2965 TaskDimensions: []*pb.RequestedDimension{ 2966 { 2967 Expiration: &durationpb.Duration{ 2968 Seconds: 1, 2969 }, 2970 Key: "key", 2971 Value: "value", 2972 }, 2973 }, 2974 }) 2975 }) 2976 2977 Convey("many", func() { 2978 cfg := &pb.BuilderConfig{ 2979 Dimensions: []string{ 2980 "key:", 2981 "key:value", 2982 "key:value:", 2983 "key:val:ue", 2984 "0:key:", 2985 "0:key:value", 2986 "0:key:value:", 2987 "0:key:val:ue", 2988 "1:key:", 2989 "1:key:value", 2990 "1:key:value:", 2991 "1:key:val:ue", 2992 }, 2993 } 2994 b := &pb.Build{ 2995 Infra: &pb.BuildInfra{ 2996 Swarming: &pb.BuildInfra_Swarming{}, 2997 }, 2998 } 2999 3000 setDimensions(nil, cfg, b, false) 3001 So(b.Infra, ShouldResembleProto, &pb.BuildInfra{ 3002 Swarming: &pb.BuildInfra_Swarming{ 3003 TaskDimensions: []*pb.RequestedDimension{ 3004 { 3005 Key: "key", 3006 Value: "value", 3007 }, 3008 { 3009 Key: "key", 3010 Value: "value:", 3011 }, 3012 { 3013 Key: "key", 3014 Value: "val:ue", 3015 }, 3016 { 3017 Key: "key", 3018 Value: "value", 3019 }, 3020 { 3021 Key: "key", 3022 Value: "value:", 3023 }, 3024 { 3025 Key: "key", 3026 Value: "val:ue", 3027 }, 3028 { 3029 Expiration: &durationpb.Duration{ 3030 Seconds: 1, 3031 }, 3032 Key: "key", 3033 Value: "value", 3034 }, 3035 { 3036 Expiration: &durationpb.Duration{ 3037 Seconds: 1, 3038 }, 3039 Key: "key", 3040 Value: "value:", 3041 }, 3042 { 3043 Expiration: &durationpb.Duration{ 3044 Seconds: 1, 3045 }, 3046 Key: "key", 3047 Value: "val:ue", 3048 }, 3049 }, 3050 }, 3051 }) 3052 }) 3053 3054 Convey("auto builder", func() { 3055 cfg := &pb.BuilderConfig{ 3056 AutoBuilderDimension: pb.Toggle_YES, 3057 Name: "builder", 3058 } 3059 b := &pb.Build{ 3060 Infra: &pb.BuildInfra{ 3061 Swarming: &pb.BuildInfra_Swarming{}, 3062 }, 3063 } 3064 3065 setDimensions(nil, cfg, b, false) 3066 So(b.Infra.Swarming, ShouldResembleProto, &pb.BuildInfra_Swarming{ 3067 TaskDimensions: []*pb.RequestedDimension{ 3068 { 3069 Key: "builder", 3070 Value: "builder", 3071 }, 3072 }, 3073 }) 3074 }) 3075 3076 Convey("builder > auto builder", func() { 3077 cfg := &pb.BuilderConfig{ 3078 AutoBuilderDimension: pb.Toggle_YES, 3079 Dimensions: []string{ 3080 "1:builder:cfg builder", 3081 }, 3082 Name: "auto builder", 3083 } 3084 b := &pb.Build{ 3085 Infra: &pb.BuildInfra{ 3086 Swarming: &pb.BuildInfra_Swarming{}, 3087 }, 3088 } 3089 3090 setDimensions(nil, cfg, b, false) 3091 So(b.Infra.Swarming, ShouldResembleProto, &pb.BuildInfra_Swarming{ 3092 TaskDimensions: []*pb.RequestedDimension{ 3093 { 3094 Expiration: &durationpb.Duration{ 3095 Seconds: 1, 3096 }, 3097 Key: "builder", 3098 Value: "cfg builder", 3099 }, 3100 }, 3101 }) 3102 }) 3103 3104 Convey("omit builder > auto builder", func() { 3105 cfg := &pb.BuilderConfig{ 3106 AutoBuilderDimension: pb.Toggle_YES, 3107 Dimensions: []string{ 3108 "builder:", 3109 }, 3110 Name: "auto builder", 3111 } 3112 b := &pb.Build{ 3113 Infra: &pb.BuildInfra{ 3114 Swarming: &pb.BuildInfra_Swarming{}, 3115 }, 3116 } 3117 3118 setDimensions(nil, cfg, b, false) 3119 So(b.Infra.Swarming, ShouldResembleProto, &pb.BuildInfra_Swarming{}) 3120 }) 3121 }) 3122 3123 Convey("request", func() { 3124 req := &pb.ScheduleBuildRequest{ 3125 Dimensions: []*pb.RequestedDimension{ 3126 { 3127 Expiration: &durationpb.Duration{ 3128 Seconds: 1, 3129 }, 3130 Key: "key", 3131 Value: "value", 3132 }, 3133 }, 3134 } 3135 b := &pb.Build{ 3136 Infra: &pb.BuildInfra{ 3137 Swarming: &pb.BuildInfra_Swarming{}, 3138 }, 3139 } 3140 3141 setDimensions(req, nil, b, false) 3142 So(b.Infra.Swarming, ShouldResembleProto, &pb.BuildInfra_Swarming{ 3143 TaskDimensions: []*pb.RequestedDimension{ 3144 { 3145 Expiration: &durationpb.Duration{ 3146 Seconds: 1, 3147 }, 3148 Key: "key", 3149 Value: "value", 3150 }, 3151 }, 3152 }) 3153 }) 3154 3155 Convey("request > config", func() { 3156 req := &pb.ScheduleBuildRequest{ 3157 Dimensions: []*pb.RequestedDimension{ 3158 { 3159 Expiration: &durationpb.Duration{ 3160 Seconds: 1, 3161 }, 3162 Key: "req only", 3163 Value: "req value", 3164 }, 3165 { 3166 Key: "req only", 3167 Value: "req value", 3168 }, 3169 { 3170 Key: "key", 3171 Value: "req value", 3172 }, 3173 { 3174 Key: "key_to_exclude", 3175 Value: "", 3176 }, 3177 }, 3178 } 3179 cfg := &pb.BuilderConfig{ 3180 AutoBuilderDimension: pb.Toggle_YES, 3181 Dimensions: []string{ 3182 "1:cfg only:cfg value", 3183 "cfg only:cfg value", 3184 "cfg only:", 3185 "1:key:cfg value", 3186 "1:key_to_exclude:cfg value", 3187 }, 3188 Name: "auto builder", 3189 } 3190 b := &pb.Build{ 3191 Infra: &pb.BuildInfra{ 3192 Swarming: &pb.BuildInfra_Swarming{}, 3193 }, 3194 } 3195 3196 setDimensions(req, cfg, b, false) 3197 So(b.Infra.Swarming, ShouldResembleProto, &pb.BuildInfra_Swarming{ 3198 TaskDimensions: []*pb.RequestedDimension{ 3199 { 3200 Key: "builder", 3201 Value: "auto builder", 3202 }, 3203 { 3204 Key: "cfg only", 3205 Value: "cfg value", 3206 }, 3207 { 3208 Expiration: &durationpb.Duration{ 3209 Seconds: 1, 3210 }, 3211 Key: "cfg only", 3212 Value: "cfg value", 3213 }, 3214 { 3215 Key: "key", 3216 Value: "req value", 3217 }, 3218 { 3219 Key: "req only", 3220 Value: "req value", 3221 }, 3222 { 3223 Expiration: &durationpb.Duration{ 3224 Seconds: 1, 3225 }, 3226 Key: "req only", 3227 Value: "req value", 3228 }, 3229 }, 3230 }) 3231 }) 3232 }) 3233 3234 Convey("setExecutable", t, func() { 3235 Convey("nil", func() { 3236 b := &pb.Build{} 3237 3238 setExecutable(nil, nil, b) 3239 So(b.Exe, ShouldResembleProto, &pb.Executable{}) 3240 }) 3241 3242 Convey("request only", func() { 3243 req := &pb.ScheduleBuildRequest{ 3244 Exe: &pb.Executable{ 3245 CipdPackage: "package", 3246 CipdVersion: "version", 3247 Cmd: []string{"command"}, 3248 }, 3249 } 3250 b := &pb.Build{} 3251 3252 setExecutable(req, nil, b) 3253 So(b.Exe, ShouldResembleProto, &pb.Executable{ 3254 CipdVersion: "version", 3255 }) 3256 }) 3257 3258 Convey("config only", func() { 3259 Convey("exe", func() { 3260 cfg := &pb.BuilderConfig{ 3261 Exe: &pb.Executable{ 3262 CipdPackage: "package", 3263 CipdVersion: "version", 3264 Cmd: []string{"command"}, 3265 }, 3266 } 3267 b := &pb.Build{} 3268 3269 setExecutable(nil, cfg, b) 3270 So(b.Exe, ShouldResembleProto, &pb.Executable{ 3271 CipdPackage: "package", 3272 CipdVersion: "version", 3273 Cmd: []string{"command"}, 3274 }) 3275 }) 3276 3277 Convey("recipe", func() { 3278 cfg := &pb.BuilderConfig{ 3279 Exe: &pb.Executable{ 3280 CipdPackage: "package 1", 3281 CipdVersion: "version 1", 3282 Cmd: []string{"command"}, 3283 }, 3284 Recipe: &pb.BuilderConfig_Recipe{ 3285 CipdPackage: "package 2", 3286 CipdVersion: "version 2", 3287 }, 3288 } 3289 b := &pb.Build{} 3290 3291 setExecutable(nil, cfg, b) 3292 So(b.Exe, ShouldResembleProto, &pb.Executable{ 3293 CipdPackage: "package 2", 3294 CipdVersion: "version 2", 3295 Cmd: []string{"command"}, 3296 }) 3297 }) 3298 }) 3299 3300 Convey("request > config", func() { 3301 req := &pb.ScheduleBuildRequest{ 3302 Exe: &pb.Executable{ 3303 CipdPackage: "package 1", 3304 CipdVersion: "version 1", 3305 Cmd: []string{"command 1"}, 3306 }, 3307 } 3308 cfg := &pb.BuilderConfig{ 3309 Exe: &pb.Executable{ 3310 CipdPackage: "package 2", 3311 CipdVersion: "version 2", 3312 Cmd: []string{"command 2"}, 3313 }, 3314 } 3315 b := &pb.Build{} 3316 3317 setExecutable(req, cfg, b) 3318 So(b.Exe, ShouldResembleProto, &pb.Executable{ 3319 CipdPackage: "package 2", 3320 CipdVersion: "version 1", 3321 Cmd: []string{"command 2"}, 3322 }) 3323 }) 3324 }) 3325 3326 Convey("setExperiments", t, func() { 3327 ctx := mathrand.Set(memory.Use(context.Background()), rand.New(rand.NewSource(1))) 3328 ctx = metrics.WithServiceInfo(ctx, "svc", "job", "ins") 3329 3330 // settings.cfg 3331 gCfg := &pb.SettingsCfg{ 3332 Experiment: &pb.ExperimentSettings{}, 3333 } 3334 3335 // builder config 3336 cfg := &pb.BuilderConfig{ 3337 Experiments: map[string]int32{}, 3338 } 3339 3340 // base datastore entity (and embedded Build Proto) 3341 ent := &model.Build{ 3342 Proto: &pb.Build{ 3343 Builder: &pb.BuilderID{ 3344 Project: "project", 3345 Bucket: "bucket", 3346 Builder: "builder", 3347 }, 3348 Exe: &pb.Executable{}, 3349 Infra: &pb.BuildInfra{ 3350 Buildbucket: &pb.BuildInfra_Buildbucket{ 3351 Hostname: "app.appspot.com", 3352 }, 3353 }, 3354 Input: &pb.Build_Input{}, 3355 }, 3356 } 3357 3358 expect := &model.Build{ 3359 Proto: &pb.Build{ 3360 Builder: &pb.BuilderID{ 3361 Project: "project", 3362 Bucket: "bucket", 3363 Builder: "builder", 3364 }, 3365 Exe: &pb.Executable{ 3366 Cmd: []string{"recipes"}, 3367 }, 3368 Infra: &pb.BuildInfra{ 3369 Buildbucket: &pb.BuildInfra_Buildbucket{ 3370 Hostname: "app.appspot.com", 3371 }, 3372 }, 3373 Input: &pb.Build_Input{}, 3374 }, 3375 } 3376 3377 req := &pb.ScheduleBuildRequest{ 3378 Experiments: map[string]bool{}, 3379 } 3380 3381 setExps := func() { 3382 normalizeSchedule(req) 3383 setExperiments(ctx, req, cfg, gCfg, ent.Proto) 3384 setExperimentsFromProto(ent) 3385 } 3386 initReasons := func() map[string]pb.BuildInfra_Buildbucket_ExperimentReason { 3387 er := make(map[string]pb.BuildInfra_Buildbucket_ExperimentReason) 3388 expect.Proto.Infra.Buildbucket.ExperimentReasons = er 3389 return er 3390 } 3391 3392 Convey("nil", func() { 3393 setExps() 3394 So(ent, ShouldResemble, expect) 3395 }) 3396 3397 Convey("dice rolling works", func() { 3398 for i := 0; i < 100; i += 10 { 3399 cfg.Experiments["exp"+strconv.Itoa(i)] = int32(i) 3400 } 3401 setExps() 3402 3403 So(ent.Proto.Input.Experiments, ShouldResemble, []string{ 3404 "exp60", "exp70", "exp80", "exp90", 3405 }) 3406 }) 3407 3408 Convey("command", func() { 3409 Convey("recipes", func() { 3410 req.Experiments[bb.ExperimentBBAgent] = false 3411 setExps() 3412 3413 So(ent.Proto.Exe, ShouldResembleProto, &pb.Executable{ 3414 Cmd: []string{"recipes"}, 3415 }) 3416 So(ent.Proto.Infra.Buildbucket.ExperimentReasons[bb.ExperimentBBAgent], 3417 ShouldEqual, pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_REQUESTED) 3418 }) 3419 3420 Convey("recipes (explicit)", func() { 3421 ent.Proto.Exe.Cmd = []string{"recipes"} 3422 req.Experiments[bb.ExperimentBBAgent] = false 3423 setExps() 3424 3425 So(ent.Proto.Exe, ShouldResembleProto, &pb.Executable{ 3426 Cmd: []string{"recipes"}, 3427 }) 3428 So(ent.Proto.Input.Experiments, ShouldBeEmpty) 3429 So(ent.Proto.Infra.Buildbucket.ExperimentReasons[bb.ExperimentBBAgent], 3430 ShouldEqual, pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_BUILDER_CONFIG) 3431 }) 3432 3433 Convey("luciexe (experiment)", func() { 3434 req.Experiments[bb.ExperimentBBAgent] = true 3435 setExps() 3436 3437 So(ent.Proto.Exe, ShouldResembleProto, &pb.Executable{ 3438 Cmd: []string{"luciexe"}, 3439 }) 3440 So(ent.Proto.Input.Experiments, ShouldContain, bb.ExperimentBBAgent) 3441 So(ent.Experiments, ShouldContain, "+"+bb.ExperimentBBAgent) 3442 So(ent.Proto.Infra.Buildbucket.ExperimentReasons[bb.ExperimentBBAgent], 3443 ShouldEqual, pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_REQUESTED) 3444 }) 3445 3446 Convey("luciexe (explicit)", func() { 3447 ent.Proto.Exe.Cmd = []string{"luciexe"} 3448 setExps() 3449 3450 So(ent.Proto.Exe, ShouldResembleProto, &pb.Executable{ 3451 Cmd: []string{"luciexe"}, 3452 }) 3453 So(ent.Proto.Input.Experiments, ShouldContain, bb.ExperimentBBAgent) 3454 So(ent.Proto.Infra.Buildbucket.ExperimentReasons[bb.ExperimentBBAgent], 3455 ShouldEqual, pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_BUILDER_CONFIG) 3456 }) 3457 3458 Convey("cmd > experiment", func() { 3459 req.Experiments[bb.ExperimentBBAgent] = false 3460 ent.Proto.Exe.Cmd = []string{"command"} 3461 setExps() 3462 3463 So(ent.Proto.Exe, ShouldResembleProto, &pb.Executable{ 3464 Cmd: []string{"command"}, 3465 }) 3466 So(ent.Proto.Input.Experiments, ShouldContain, bb.ExperimentBBAgent) 3467 So(ent.Proto.Infra.Buildbucket.ExperimentReasons[bb.ExperimentBBAgent], 3468 ShouldEqual, pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_BUILDER_CONFIG) 3469 }) 3470 }) 3471 3472 Convey("request only", func() { 3473 req.Experiments["experiment1"] = true 3474 req.Experiments["experiment2"] = false 3475 setExps() 3476 3477 expect.Experiments = []string{ 3478 "+experiment1", 3479 "-experiment2", 3480 } 3481 expect.Proto.Input.Experiments = []string{"experiment1"} 3482 er := initReasons() 3483 er["experiment1"] = pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_REQUESTED 3484 er["experiment2"] = pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_REQUESTED 3485 3486 So(ent, ShouldResemble, expect) 3487 }) 3488 3489 Convey("legacy only", func() { 3490 req.Canary = pb.Trinary_YES 3491 req.Experimental = pb.Trinary_NO 3492 setExps() 3493 3494 expect.Canary = true 3495 expect.Experiments = []string{ 3496 "+" + bb.ExperimentBBCanarySoftware, 3497 "-" + bb.ExperimentNonProduction, 3498 } 3499 expect.Proto.Canary = true 3500 expect.Proto.Input.Experiments = []string{bb.ExperimentBBCanarySoftware} 3501 er := initReasons() 3502 er[bb.ExperimentBBCanarySoftware] = pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_REQUESTED 3503 er[bb.ExperimentNonProduction] = pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_REQUESTED 3504 3505 So(ent, ShouldResemble, expect) 3506 }) 3507 3508 Convey("config only", func() { 3509 cfg.Experiments["experiment1"] = 100 3510 cfg.Experiments["experiment2"] = 0 3511 setExps() 3512 3513 expect.Experiments = []string{ 3514 "+experiment1", 3515 "-experiment2", 3516 } 3517 expect.Proto.Input.Experiments = []string{"experiment1"} 3518 er := initReasons() 3519 er["experiment1"] = pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_BUILDER_CONFIG 3520 er["experiment2"] = pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_BUILDER_CONFIG 3521 3522 So(ent, ShouldResemble, expect) 3523 }) 3524 3525 Convey("override", func() { 3526 Convey("request > legacy", func() { 3527 req.Canary = pb.Trinary_YES 3528 req.Experimental = pb.Trinary_NO 3529 req.Experiments[bb.ExperimentBBCanarySoftware] = false 3530 req.Experiments[bb.ExperimentNonProduction] = true 3531 setExps() 3532 3533 expect.Experiments = []string{ 3534 "+" + bb.ExperimentNonProduction, 3535 "-" + bb.ExperimentBBCanarySoftware, 3536 } 3537 expect.Experimental = true 3538 expect.Proto.Input.Experimental = true 3539 expect.Proto.Input.Experiments = []string{bb.ExperimentNonProduction} 3540 er := initReasons() 3541 er[bb.ExperimentNonProduction] = pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_REQUESTED 3542 er[bb.ExperimentBBCanarySoftware] = pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_REQUESTED 3543 3544 So(ent, ShouldResemble, expect) 3545 }) 3546 3547 Convey("legacy > config", func() { 3548 req.Canary = pb.Trinary_YES 3549 req.Experimental = pb.Trinary_NO 3550 cfg.Experiments[bb.ExperimentBBCanarySoftware] = 0 3551 cfg.Experiments[bb.ExperimentNonProduction] = 100 3552 setExps() 3553 3554 expect.Experiments = []string{ 3555 "+" + bb.ExperimentBBCanarySoftware, 3556 "-" + bb.ExperimentNonProduction, 3557 } 3558 expect.Canary = true 3559 expect.Proto.Canary = true 3560 expect.Proto.Input.Experiments = []string{bb.ExperimentBBCanarySoftware} 3561 er := initReasons() 3562 er[bb.ExperimentNonProduction] = pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_REQUESTED 3563 er[bb.ExperimentBBCanarySoftware] = pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_REQUESTED 3564 3565 So(ent, ShouldResemble, expect) 3566 }) 3567 3568 Convey("request > config", func() { 3569 req.Experiments["experiment1"] = true 3570 req.Experiments["experiment2"] = false 3571 cfg.Experiments["experiment1"] = 0 3572 cfg.Experiments["experiment2"] = 100 3573 setExps() 3574 3575 expect.Experiments = []string{ 3576 "+experiment1", 3577 "-experiment2", 3578 } 3579 expect.Proto.Input.Experiments = []string{"experiment1"} 3580 er := initReasons() 3581 er["experiment1"] = pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_REQUESTED 3582 er["experiment2"] = pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_REQUESTED 3583 3584 So(ent, ShouldResemble, expect) 3585 }) 3586 3587 Convey("request > legacy > config", func() { 3588 req.Canary = pb.Trinary_YES 3589 req.Experimental = pb.Trinary_NO 3590 req.Experiments[bb.ExperimentBBCanarySoftware] = false 3591 req.Experiments[bb.ExperimentNonProduction] = true 3592 req.Experiments["experiment1"] = true 3593 req.Experiments["experiment2"] = false 3594 cfg.Experiments[bb.ExperimentBBCanarySoftware] = 100 3595 cfg.Experiments[bb.ExperimentNonProduction] = 100 3596 cfg.Experiments["experiment1"] = 0 3597 cfg.Experiments["experiment2"] = 0 3598 setExps() 3599 3600 expect.Experiments = []string{ 3601 "+experiment1", 3602 "+" + bb.ExperimentNonProduction, 3603 "-experiment2", 3604 "-" + bb.ExperimentBBCanarySoftware, 3605 } 3606 expect.Experimental = true 3607 expect.Proto.Input.Experimental = true 3608 expect.Proto.Input.Experiments = []string{ 3609 "experiment1", 3610 bb.ExperimentNonProduction, 3611 } 3612 er := initReasons() 3613 er["experiment1"] = pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_REQUESTED 3614 er["experiment2"] = pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_REQUESTED 3615 er[bb.ExperimentBBCanarySoftware] = pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_REQUESTED 3616 er[bb.ExperimentNonProduction] = pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_REQUESTED 3617 3618 So(ent, ShouldResemble, expect) 3619 }) 3620 }) 3621 3622 Convey("global configuration", func() { 3623 addExp := func(name string, dflt, min int32, inactive bool, b *pb.BuilderPredicate) { 3624 gCfg.Experiment.Experiments = append(gCfg.Experiment.Experiments, &pb.ExperimentSettings_Experiment{ 3625 Name: name, 3626 DefaultValue: dflt, 3627 MinimumValue: min, 3628 Builders: b, 3629 Inactive: inactive, 3630 }) 3631 } 3632 3633 Convey("default always", func() { 3634 addExp("always", 100, 0, false, nil) 3635 3636 Convey("will fill in if unset", func() { 3637 setExps() 3638 3639 So(ent.Proto.Input.Experiments, ShouldResemble, []string{"always"}) 3640 So(ent.Proto.Infra.Buildbucket.ExperimentReasons, ShouldResemble, map[string]pb.BuildInfra_Buildbucket_ExperimentReason{ 3641 "always": pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_GLOBAL_DEFAULT, 3642 }) 3643 }) 3644 3645 Convey("can be overridden from request", func() { 3646 req.Experiments["always"] = false 3647 setExps() 3648 3649 So(ent.Proto.Input.Experiments, ShouldBeEmpty) 3650 So(ent.Proto.Infra.Buildbucket.ExperimentReasons, ShouldResemble, map[string]pb.BuildInfra_Buildbucket_ExperimentReason{ 3651 "always": pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_REQUESTED, 3652 }) 3653 }) 3654 }) 3655 3656 Convey("per builder", func() { 3657 addExp("per.builder", 100, 0, false, &pb.BuilderPredicate{ 3658 Regex: []string{"project/bucket/builder"}, 3659 }) 3660 addExp("other.builder", 100, 0, false, &pb.BuilderPredicate{ 3661 Regex: []string{"project/bucket/other"}, 3662 }) 3663 setExps() 3664 3665 So(ent.Proto.Input.Experiments, ShouldResemble, []string{"per.builder"}) 3666 So(ent.Experiments, ShouldContain, "-other.builder") 3667 So(ent.Proto.Infra.Buildbucket.ExperimentReasons, ShouldResemble, map[string]pb.BuildInfra_Buildbucket_ExperimentReason{ 3668 "per.builder": pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_GLOBAL_DEFAULT, 3669 "other.builder": pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_GLOBAL_DEFAULT, 3670 }) 3671 }) 3672 3673 Convey("min value", func() { 3674 // note that default == 0, min == 100 is a bit silly, but works for this 3675 // test. 3676 addExp("min.value", 0, 100, false, nil) 3677 3678 Convey("overrides builder config", func() { 3679 cfg.Experiments["min.value"] = 0 3680 setExps() 3681 3682 So(ent.Proto.Input.Experiments, ShouldResemble, []string{"min.value"}) 3683 So(ent.Proto.Infra.Buildbucket.ExperimentReasons, ShouldResemble, map[string]pb.BuildInfra_Buildbucket_ExperimentReason{ 3684 "min.value": pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_GLOBAL_MINIMUM, 3685 }) 3686 }) 3687 3688 Convey("can be overridden from request", func() { 3689 req.Experiments["min.value"] = false 3690 setExps() 3691 3692 So(ent.Proto.Input.Experiments, ShouldBeEmpty) 3693 So(ent.Proto.Infra.Buildbucket.ExperimentReasons, ShouldResemble, map[string]pb.BuildInfra_Buildbucket_ExperimentReason{ 3694 "min.value": pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_REQUESTED, 3695 }) 3696 }) 3697 }) 3698 3699 Convey("inactive", func() { 3700 addExp("inactive", 30, 30, true, nil) 3701 addExp("other_inactive", 30, 30, true, nil) 3702 cfg.Experiments["inactive"] = 100 3703 setExps() 3704 3705 So(ent.Proto.Input.Experiments, ShouldBeEmpty) 3706 So(ent.Proto.Infra.Buildbucket.ExperimentReasons, ShouldResemble, map[string]pb.BuildInfra_Buildbucket_ExperimentReason{ 3707 "inactive": pb.BuildInfra_Buildbucket_EXPERIMENT_REASON_GLOBAL_INACTIVE, 3708 // Note that other_inactive wasn't requested in the build so it's 3709 // absent here. 3710 }) 3711 }) 3712 }) 3713 }) 3714 3715 Convey("buildFromScheduleRequest", t, func() { 3716 ctx := memory.Use(context.Background()) 3717 Convey("backend is enabled", func() { 3718 s := &pb.SettingsCfg{ 3719 Backends: []*pb.BackendSetting{ 3720 { 3721 Target: "swarming://chromium-swarm", 3722 Hostname: "chromium-swarm.appspot.com", 3723 }, 3724 }, 3725 Cipd: &pb.CipdSettings{ 3726 Server: "cipd_server", 3727 }, 3728 Swarming: &pb.SwarmingSettings{ 3729 BbagentPackage: &pb.SwarmingSettings_Package{ 3730 PackageName: "cipd_pkg/${platform}", 3731 Version: "cipd_vers", 3732 }, 3733 }, 3734 } 3735 bldrCfg := &pb.BuilderConfig{ 3736 Dimensions: []string{ 3737 "key:value", 3738 }, 3739 ServiceAccount: "account", 3740 Backend: &pb.BuilderConfig_Backend{ 3741 Target: "swarming://chromium-swarm", 3742 }, 3743 Experiments: map[string]int32{ 3744 bb.ExperimentBackendAlt: 100, 3745 }, 3746 } 3747 req := &pb.ScheduleBuildRequest{ 3748 Builder: &pb.BuilderID{ 3749 Bucket: "bucket", 3750 Builder: "builder", 3751 Project: "project", 3752 }, 3753 RequestId: "request_id", 3754 Priority: 100, 3755 } 3756 3757 buildResult := buildFromScheduleRequest(ctx, req, nil, "", bldrCfg, s) 3758 expectedBackendConfig := &structpb.Struct{} 3759 expectedBackendConfig.Fields = make(map[string]*structpb.Value) 3760 expectedBackendConfig.Fields["priority"] = &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: 100}} 3761 expectedBackendConfig.Fields["service_account"] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "account"}} 3762 expectedBackendConfig.Fields["agent_binary_cipd_pkg"] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "cipd_pkg/${platform}"}} 3763 expectedBackendConfig.Fields["agent_binary_cipd_vers"] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "cipd_vers"}} 3764 expectedBackendConfig.Fields["agent_binary_cipd_server"] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "cipd_server"}} 3765 expectedBackendConfig.Fields["agent_binary_cipd_filename"] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "bbagent${EXECUTABLE_SUFFIX}"}} 3766 3767 So(buildResult.Infra.Backend, ShouldResembleProto, &pb.BuildInfra_Backend{ 3768 Caches: []*pb.CacheEntry{ 3769 { 3770 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 3771 Path: "builder", 3772 WaitForWarmCache: &durationpb.Duration{Seconds: 240}, 3773 }, 3774 }, 3775 Config: expectedBackendConfig, 3776 Hostname: "chromium-swarm.appspot.com", 3777 Task: &pb.Task{ 3778 Id: &pb.TaskID{ 3779 Target: "swarming://chromium-swarm", 3780 }, 3781 }, 3782 TaskDimensions: []*pb.RequestedDimension{ 3783 { 3784 Key: "key", 3785 Value: "value", 3786 }, 3787 }, 3788 }) 3789 }) 3790 }) 3791 3792 Convey("setInfra", t, func() { 3793 ctx := mathrand.Set(memory.Use(context.Background()), rand.New(rand.NewSource(1))) 3794 ctx = metrics.WithServiceInfo(ctx, "svc", "job", "ins") 3795 Convey("nil", func() { 3796 b := &pb.Build{ 3797 Builder: &pb.BuilderID{ 3798 Project: "project", 3799 Bucket: "bucket", 3800 Builder: "builder", 3801 }, 3802 } 3803 3804 setInfra(ctx, nil, nil, b, nil) 3805 So(b.Infra, ShouldResembleProto, &pb.BuildInfra{ 3806 Bbagent: &pb.BuildInfra_BBAgent{ 3807 CacheDir: "cache", 3808 PayloadPath: "kitchen-checkout", 3809 }, 3810 Buildbucket: &pb.BuildInfra_Buildbucket{ 3811 Hostname: "app.appspot.com", 3812 }, 3813 Logdog: &pb.BuildInfra_LogDog{ 3814 Project: "project", 3815 }, 3816 Resultdb: &pb.BuildInfra_ResultDB{}, 3817 }) 3818 }) 3819 3820 Convey("bbagent", func() { 3821 b := &pb.Build{ 3822 Builder: &pb.BuilderID{ 3823 Project: "project", 3824 Bucket: "bucket", 3825 Builder: "builder", 3826 }, 3827 } 3828 s := &pb.SettingsCfg{ 3829 KnownPublicGerritHosts: []string{ 3830 "host", 3831 }, 3832 } 3833 3834 setInfra(ctx, nil, nil, b, s) 3835 So(b.Infra, ShouldResembleProto, &pb.BuildInfra{ 3836 Bbagent: &pb.BuildInfra_BBAgent{ 3837 CacheDir: "cache", 3838 PayloadPath: "kitchen-checkout", 3839 }, 3840 Buildbucket: &pb.BuildInfra_Buildbucket{ 3841 Hostname: "app.appspot.com", 3842 KnownPublicGerritHosts: []string{ 3843 "host", 3844 }, 3845 }, 3846 Logdog: &pb.BuildInfra_LogDog{ 3847 Project: "project", 3848 }, 3849 Resultdb: &pb.BuildInfra_ResultDB{}, 3850 }) 3851 }) 3852 3853 Convey("logdog", func() { 3854 b := &pb.Build{ 3855 Builder: &pb.BuilderID{ 3856 Project: "project", 3857 Bucket: "bucket", 3858 Builder: "builder", 3859 }, 3860 } 3861 s := &pb.SettingsCfg{ 3862 Logdog: &pb.LogDogSettings{ 3863 Hostname: "host", 3864 }, 3865 } 3866 3867 setInfra(ctx, nil, nil, b, s) 3868 So(b.Infra, ShouldResembleProto, &pb.BuildInfra{ 3869 Bbagent: &pb.BuildInfra_BBAgent{ 3870 CacheDir: "cache", 3871 PayloadPath: "kitchen-checkout", 3872 }, 3873 Buildbucket: &pb.BuildInfra_Buildbucket{ 3874 Hostname: "app.appspot.com", 3875 }, 3876 Logdog: &pb.BuildInfra_LogDog{ 3877 Hostname: "host", 3878 Project: "project", 3879 }, 3880 Resultdb: &pb.BuildInfra_ResultDB{}, 3881 }) 3882 }) 3883 3884 Convey("resultdb", func() { 3885 b := &pb.Build{ 3886 Builder: &pb.BuilderID{ 3887 Project: "project", 3888 Bucket: "bucket", 3889 Builder: "builder", 3890 }, 3891 Id: 1, 3892 } 3893 s := &pb.SettingsCfg{ 3894 Resultdb: &pb.ResultDBSettings{ 3895 Hostname: "host", 3896 }, 3897 } 3898 3899 bqExports := []*rdbPb.BigQueryExport{} 3900 cfg := &pb.BuilderConfig{ 3901 Resultdb: &pb.BuilderConfig_ResultDB{ 3902 Enable: true, 3903 BqExports: bqExports, 3904 }, 3905 } 3906 3907 setInfra(ctx, nil, cfg, b, s) 3908 So(b.Infra, ShouldResembleProto, &pb.BuildInfra{ 3909 Bbagent: &pb.BuildInfra_BBAgent{ 3910 PayloadPath: "kitchen-checkout", 3911 CacheDir: "cache", 3912 }, 3913 Buildbucket: &pb.BuildInfra_Buildbucket{ 3914 Hostname: "app.appspot.com", 3915 }, 3916 Logdog: &pb.BuildInfra_LogDog{ 3917 Hostname: "", 3918 Project: "project", 3919 }, 3920 Resultdb: &pb.BuildInfra_ResultDB{ 3921 Hostname: "host", 3922 Enable: true, 3923 BqExports: bqExports, 3924 }, 3925 }) 3926 }) 3927 3928 Convey("config", func() { 3929 Convey("recipe", func() { 3930 cfg := &pb.BuilderConfig{ 3931 Recipe: &pb.BuilderConfig_Recipe{ 3932 CipdPackage: "package", 3933 Name: "name", 3934 }, 3935 } 3936 b := &pb.Build{ 3937 Builder: &pb.BuilderID{ 3938 Project: "project", 3939 Bucket: "bucket", 3940 Builder: "builder", 3941 }, 3942 } 3943 3944 setInfra(ctx, nil, cfg, b, nil) 3945 So(b.Infra, ShouldResembleProto, &pb.BuildInfra{ 3946 Bbagent: &pb.BuildInfra_BBAgent{ 3947 CacheDir: "cache", 3948 PayloadPath: "kitchen-checkout", 3949 }, 3950 Buildbucket: &pb.BuildInfra_Buildbucket{ 3951 Hostname: "app.appspot.com", 3952 }, 3953 Logdog: &pb.BuildInfra_LogDog{ 3954 Project: "project", 3955 }, 3956 Recipe: &pb.BuildInfra_Recipe{ 3957 CipdPackage: "package", 3958 Name: "name", 3959 }, 3960 Resultdb: &pb.BuildInfra_ResultDB{}, 3961 }) 3962 }) 3963 }) 3964 3965 Convey("request", func() { 3966 Convey("dimensions", func() { 3967 req := &pb.ScheduleBuildRequest{ 3968 Dimensions: []*pb.RequestedDimension{ 3969 { 3970 Expiration: &durationpb.Duration{ 3971 Seconds: 1, 3972 }, 3973 Key: "key", 3974 Value: "value", 3975 }, 3976 }, 3977 } 3978 b := &pb.Build{ 3979 Builder: &pb.BuilderID{ 3980 Project: "project", 3981 Bucket: "bucket", 3982 Builder: "builder", 3983 }, 3984 } 3985 3986 setInfra(ctx, req, nil, b, nil) 3987 So(b.Infra, ShouldResembleProto, &pb.BuildInfra{ 3988 Bbagent: &pb.BuildInfra_BBAgent{ 3989 CacheDir: "cache", 3990 PayloadPath: "kitchen-checkout", 3991 }, 3992 Buildbucket: &pb.BuildInfra_Buildbucket{ 3993 Hostname: "app.appspot.com", 3994 RequestedDimensions: []*pb.RequestedDimension{ 3995 { 3996 Expiration: &durationpb.Duration{ 3997 Seconds: 1, 3998 }, 3999 Key: "key", 4000 Value: "value", 4001 }, 4002 }, 4003 }, 4004 Logdog: &pb.BuildInfra_LogDog{ 4005 Project: "project", 4006 }, 4007 Resultdb: &pb.BuildInfra_ResultDB{}, 4008 }) 4009 }) 4010 4011 Convey("properties", func() { 4012 req := &pb.ScheduleBuildRequest{ 4013 Properties: &structpb.Struct{ 4014 Fields: map[string]*structpb.Value{ 4015 "key": { 4016 Kind: &structpb.Value_StringValue{ 4017 StringValue: "value", 4018 }, 4019 }, 4020 }, 4021 }, 4022 } 4023 b := &pb.Build{ 4024 Builder: &pb.BuilderID{ 4025 Project: "project", 4026 Bucket: "bucket", 4027 Builder: "builder", 4028 }, 4029 } 4030 4031 setInfra(ctx, req, nil, b, nil) 4032 So(b.Infra, ShouldResembleProto, &pb.BuildInfra{ 4033 Bbagent: &pb.BuildInfra_BBAgent{ 4034 CacheDir: "cache", 4035 PayloadPath: "kitchen-checkout", 4036 }, 4037 Buildbucket: &pb.BuildInfra_Buildbucket{ 4038 Hostname: "app.appspot.com", 4039 RequestedProperties: &structpb.Struct{ 4040 Fields: map[string]*structpb.Value{ 4041 "key": { 4042 Kind: &structpb.Value_StringValue{ 4043 StringValue: "value", 4044 }, 4045 }, 4046 }, 4047 }, 4048 }, 4049 Logdog: &pb.BuildInfra_LogDog{ 4050 Project: "project", 4051 }, 4052 Resultdb: &pb.BuildInfra_ResultDB{}, 4053 }) 4054 }) 4055 }) 4056 }) 4057 4058 Convey("setSwarmingOrBackend", t, func() { 4059 ctx := mathrand.Set(memory.Use(context.Background()), rand.New(rand.NewSource(1))) 4060 ctx = metrics.WithServiceInfo(ctx, "svc", "job", "ins") 4061 Convey("nil", func() { 4062 b := &pb.Build{ 4063 Builder: &pb.BuilderID{ 4064 Project: "project", 4065 Bucket: "bucket", 4066 Builder: "builder", 4067 }, 4068 Infra: &pb.BuildInfra{ 4069 Bbagent: &pb.BuildInfra_BBAgent{ 4070 CacheDir: "cache", 4071 PayloadPath: "kitchen-checkout", 4072 }, 4073 Buildbucket: &pb.BuildInfra_Buildbucket{ 4074 Hostname: "app.appspot.com", 4075 }, 4076 Logdog: &pb.BuildInfra_LogDog{ 4077 Hostname: "host", 4078 Project: "project", 4079 }, 4080 Resultdb: &pb.BuildInfra_ResultDB{}, 4081 }, 4082 } 4083 4084 setSwarmingOrBackend(ctx, nil, nil, b, nil) 4085 So(b.Infra, ShouldResembleProto, &pb.BuildInfra{ 4086 Bbagent: &pb.BuildInfra_BBAgent{ 4087 CacheDir: "cache", 4088 PayloadPath: "kitchen-checkout", 4089 }, 4090 Buildbucket: &pb.BuildInfra_Buildbucket{ 4091 Hostname: "app.appspot.com", 4092 }, 4093 Logdog: &pb.BuildInfra_LogDog{ 4094 Hostname: "host", 4095 Project: "project", 4096 }, 4097 Resultdb: &pb.BuildInfra_ResultDB{}, 4098 Swarming: &pb.BuildInfra_Swarming{ 4099 Caches: []*pb.BuildInfra_Swarming_CacheEntry{ 4100 { 4101 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 4102 Path: "builder", 4103 WaitForWarmCache: &durationpb.Duration{ 4104 Seconds: 240, 4105 }, 4106 }, 4107 }, 4108 Priority: 30, 4109 }, 4110 }) 4111 }) 4112 Convey("priority", func() { 4113 b := &pb.Build{ 4114 Builder: &pb.BuilderID{ 4115 Project: "project", 4116 Bucket: "bucket", 4117 Builder: "builder", 4118 }, 4119 Infra: &pb.BuildInfra{ 4120 Bbagent: &pb.BuildInfra_BBAgent{ 4121 CacheDir: "cache", 4122 PayloadPath: "kitchen-checkout", 4123 }, 4124 Buildbucket: &pb.BuildInfra_Buildbucket{ 4125 Hostname: "app.appspot.com", 4126 }, 4127 Logdog: &pb.BuildInfra_LogDog{ 4128 Hostname: "host", 4129 Project: "project", 4130 }, 4131 Resultdb: &pb.BuildInfra_ResultDB{}, 4132 }, 4133 Input: &pb.Build_Input{ 4134 Experiments: []string{}, 4135 }, 4136 } 4137 Convey("default production", func() { 4138 setSwarmingOrBackend(ctx, nil, nil, b, nil) 4139 So(b.Infra.Swarming.Priority, ShouldEqual, 30) 4140 So(b.Input.Experimental, ShouldBeFalse) 4141 }) 4142 4143 Convey("non-production", func() { 4144 b.Input.Experiments = append(b.Input.Experiments, bb.ExperimentNonProduction) 4145 setSwarmingOrBackend(ctx, nil, nil, b, nil) 4146 So(b.Infra.Swarming.Priority, ShouldEqual, 255) 4147 So(b.Input.Experimental, ShouldBeFalse) 4148 }) 4149 4150 Convey("req > experiment", func() { 4151 b.Input.Experiments = append(b.Input.Experiments, bb.ExperimentNonProduction) 4152 req := &pb.ScheduleBuildRequest{ 4153 Priority: 1, 4154 } 4155 setSwarmingOrBackend(ctx, req, nil, b, nil) 4156 So(b.Infra.Swarming.Priority, ShouldEqual, 1) 4157 }) 4158 }) 4159 4160 Convey("swarming", func() { 4161 Convey("no dimensions", func() { 4162 cfg := &pb.BuilderConfig{ 4163 Priority: 1, 4164 ServiceAccount: "account", 4165 SwarmingHost: "host", 4166 } 4167 b := &pb.Build{ 4168 Builder: &pb.BuilderID{ 4169 Project: "project", 4170 Bucket: "bucket", 4171 Builder: "builder", 4172 }, 4173 Infra: &pb.BuildInfra{ 4174 Bbagent: &pb.BuildInfra_BBAgent{ 4175 PayloadPath: "kitchen-checkout", 4176 CacheDir: "cache", 4177 }, 4178 Buildbucket: &pb.BuildInfra_Buildbucket{ 4179 Hostname: "app.appspot.com", 4180 }, 4181 Logdog: &pb.BuildInfra_LogDog{ 4182 Project: "project", 4183 }, 4184 Resultdb: &pb.BuildInfra_ResultDB{}, 4185 }, 4186 } 4187 4188 setSwarmingOrBackend(ctx, nil, cfg, b, nil) 4189 So(b.Infra, ShouldResembleProto, &pb.BuildInfra{ 4190 Bbagent: &pb.BuildInfra_BBAgent{ 4191 PayloadPath: "kitchen-checkout", 4192 CacheDir: "cache", 4193 }, 4194 Buildbucket: &pb.BuildInfra_Buildbucket{ 4195 Hostname: "app.appspot.com", 4196 }, 4197 Logdog: &pb.BuildInfra_LogDog{ 4198 Project: "project", 4199 }, 4200 Resultdb: &pb.BuildInfra_ResultDB{}, 4201 Swarming: &pb.BuildInfra_Swarming{ 4202 Caches: []*pb.BuildInfra_Swarming_CacheEntry{ 4203 { 4204 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 4205 Path: "builder", 4206 WaitForWarmCache: &durationpb.Duration{ 4207 Seconds: 240, 4208 }, 4209 }, 4210 }, 4211 Hostname: "host", 4212 Priority: 1, 4213 TaskServiceAccount: "account", 4214 }, 4215 }) 4216 }) 4217 4218 Convey("caches", func() { 4219 Convey("nil", func() { 4220 b := &pb.Build{ 4221 Builder: &pb.BuilderID{ 4222 Project: "project", 4223 Bucket: "bucket", 4224 Builder: "builder", 4225 }, 4226 Infra: &pb.BuildInfra{ 4227 Bbagent: &pb.BuildInfra_BBAgent{ 4228 PayloadPath: "kitchen-checkout", 4229 CacheDir: "cache", 4230 }, 4231 Buildbucket: &pb.BuildInfra_Buildbucket{ 4232 Hostname: "app.appspot.com", 4233 }, 4234 Logdog: &pb.BuildInfra_LogDog{ 4235 Project: "project", 4236 }, 4237 Resultdb: &pb.BuildInfra_ResultDB{}, 4238 }, 4239 } 4240 4241 setSwarmingOrBackend(ctx, nil, nil, b, nil) 4242 So(b.Infra, ShouldResembleProto, &pb.BuildInfra{ 4243 Bbagent: &pb.BuildInfra_BBAgent{ 4244 CacheDir: "cache", 4245 PayloadPath: "kitchen-checkout", 4246 }, 4247 Buildbucket: &pb.BuildInfra_Buildbucket{ 4248 Hostname: "app.appspot.com", 4249 }, 4250 Logdog: &pb.BuildInfra_LogDog{ 4251 Project: "project", 4252 }, 4253 Resultdb: &pb.BuildInfra_ResultDB{}, 4254 Swarming: &pb.BuildInfra_Swarming{ 4255 Caches: []*pb.BuildInfra_Swarming_CacheEntry{ 4256 { 4257 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 4258 Path: "builder", 4259 WaitForWarmCache: &durationpb.Duration{ 4260 Seconds: 240, 4261 }, 4262 }, 4263 }, 4264 Priority: 30, 4265 }, 4266 }) 4267 }) 4268 4269 Convey("global", func() { 4270 b := &pb.Build{ 4271 Builder: &pb.BuilderID{ 4272 Project: "project", 4273 Bucket: "bucket", 4274 Builder: "builder", 4275 }, 4276 Infra: &pb.BuildInfra{ 4277 Bbagent: &pb.BuildInfra_BBAgent{ 4278 PayloadPath: "kitchen-checkout", 4279 CacheDir: "cache", 4280 }, 4281 Buildbucket: &pb.BuildInfra_Buildbucket{ 4282 Hostname: "app.appspot.com", 4283 }, 4284 Logdog: &pb.BuildInfra_LogDog{ 4285 Project: "project", 4286 }, 4287 Resultdb: &pb.BuildInfra_ResultDB{}, 4288 }, 4289 } 4290 s := &pb.SettingsCfg{ 4291 Swarming: &pb.SwarmingSettings{ 4292 GlobalCaches: []*pb.BuilderConfig_CacheEntry{ 4293 { 4294 Path: "cache", 4295 }, 4296 }, 4297 }, 4298 } 4299 4300 setSwarmingOrBackend(ctx, nil, nil, b, s) 4301 So(b.Infra, ShouldResembleProto, &pb.BuildInfra{ 4302 Bbagent: &pb.BuildInfra_BBAgent{ 4303 CacheDir: "cache", 4304 PayloadPath: "kitchen-checkout", 4305 }, 4306 Buildbucket: &pb.BuildInfra_Buildbucket{ 4307 Hostname: "app.appspot.com", 4308 }, 4309 Logdog: &pb.BuildInfra_LogDog{ 4310 Project: "project", 4311 }, 4312 Resultdb: &pb.BuildInfra_ResultDB{}, 4313 Swarming: &pb.BuildInfra_Swarming{ 4314 Caches: []*pb.BuildInfra_Swarming_CacheEntry{ 4315 { 4316 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 4317 Path: "builder", 4318 WaitForWarmCache: &durationpb.Duration{ 4319 Seconds: 240, 4320 }, 4321 }, 4322 { 4323 Name: "cache", 4324 Path: "cache", 4325 }, 4326 }, 4327 Priority: 30, 4328 }, 4329 }) 4330 }) 4331 4332 Convey("config", func() { 4333 cfg := &pb.BuilderConfig{ 4334 Caches: []*pb.BuilderConfig_CacheEntry{ 4335 { 4336 Path: "cache", 4337 }, 4338 }, 4339 } 4340 b := &pb.Build{ 4341 Builder: &pb.BuilderID{ 4342 Project: "project", 4343 Bucket: "bucket", 4344 Builder: "builder", 4345 }, 4346 Infra: &pb.BuildInfra{ 4347 Bbagent: &pb.BuildInfra_BBAgent{ 4348 PayloadPath: "kitchen-checkout", 4349 CacheDir: "cache", 4350 }, 4351 Buildbucket: &pb.BuildInfra_Buildbucket{ 4352 Hostname: "app.appspot.com", 4353 }, 4354 Logdog: &pb.BuildInfra_LogDog{ 4355 Project: "project", 4356 }, 4357 Resultdb: &pb.BuildInfra_ResultDB{}, 4358 }, 4359 } 4360 4361 setSwarmingOrBackend(ctx, nil, cfg, b, nil) 4362 So(b.Infra, ShouldResembleProto, &pb.BuildInfra{ 4363 Bbagent: &pb.BuildInfra_BBAgent{ 4364 CacheDir: "cache", 4365 PayloadPath: "kitchen-checkout", 4366 }, 4367 Buildbucket: &pb.BuildInfra_Buildbucket{ 4368 Hostname: "app.appspot.com", 4369 }, 4370 Logdog: &pb.BuildInfra_LogDog{ 4371 Project: "project", 4372 }, 4373 Resultdb: &pb.BuildInfra_ResultDB{}, 4374 Swarming: &pb.BuildInfra_Swarming{ 4375 Caches: []*pb.BuildInfra_Swarming_CacheEntry{ 4376 { 4377 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 4378 Path: "builder", 4379 WaitForWarmCache: &durationpb.Duration{ 4380 Seconds: 240, 4381 }, 4382 }, 4383 { 4384 Name: "cache", 4385 Path: "cache", 4386 }, 4387 }, 4388 Priority: 30, 4389 }, 4390 }) 4391 }) 4392 4393 Convey("config > global", func() { 4394 cfg := &pb.BuilderConfig{ 4395 Caches: []*pb.BuilderConfig_CacheEntry{ 4396 { 4397 Name: "builder only name", 4398 Path: "builder only path", 4399 }, 4400 { 4401 Name: "name", 4402 Path: "builder path", 4403 }, 4404 { 4405 Name: "builder name", 4406 Path: "path", 4407 }, 4408 { 4409 EnvVar: "builder env", 4410 Path: "env", 4411 }, 4412 }, 4413 } 4414 b := &pb.Build{ 4415 Builder: &pb.BuilderID{ 4416 Project: "project", 4417 Bucket: "bucket", 4418 Builder: "builder", 4419 }, 4420 Infra: &pb.BuildInfra{ 4421 Bbagent: &pb.BuildInfra_BBAgent{ 4422 PayloadPath: "kitchen-checkout", 4423 CacheDir: "cache", 4424 }, 4425 Buildbucket: &pb.BuildInfra_Buildbucket{ 4426 Hostname: "app.appspot.com", 4427 }, 4428 Logdog: &pb.BuildInfra_LogDog{ 4429 Project: "project", 4430 }, 4431 Resultdb: &pb.BuildInfra_ResultDB{}, 4432 }, 4433 } 4434 s := &pb.SettingsCfg{ 4435 Swarming: &pb.SwarmingSettings{ 4436 GlobalCaches: []*pb.BuilderConfig_CacheEntry{ 4437 { 4438 Name: "global only name", 4439 Path: "global only path", 4440 }, 4441 { 4442 Name: "name", 4443 Path: "global path", 4444 }, 4445 { 4446 Name: "global name", 4447 Path: "path", 4448 }, 4449 { 4450 EnvVar: "global env", 4451 Path: "path", 4452 }, 4453 }, 4454 }, 4455 } 4456 4457 setSwarmingOrBackend(ctx, nil, cfg, b, s) 4458 So(b.Infra, ShouldResembleProto, &pb.BuildInfra{ 4459 Bbagent: &pb.BuildInfra_BBAgent{ 4460 CacheDir: "cache", 4461 PayloadPath: "kitchen-checkout", 4462 }, 4463 Buildbucket: &pb.BuildInfra_Buildbucket{ 4464 Hostname: "app.appspot.com", 4465 }, 4466 Logdog: &pb.BuildInfra_LogDog{ 4467 Project: "project", 4468 }, 4469 Resultdb: &pb.BuildInfra_ResultDB{}, 4470 Swarming: &pb.BuildInfra_Swarming{ 4471 Caches: []*pb.BuildInfra_Swarming_CacheEntry{ 4472 { 4473 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 4474 Path: "builder", 4475 WaitForWarmCache: &durationpb.Duration{ 4476 Seconds: 240, 4477 }, 4478 }, 4479 { 4480 Name: "builder only name", 4481 Path: "builder only path", 4482 }, 4483 { 4484 Name: "name", 4485 Path: "builder path", 4486 }, 4487 { 4488 EnvVar: "builder env", 4489 Name: "env", 4490 Path: "env", 4491 }, 4492 { 4493 Name: "global only name", 4494 Path: "global only path", 4495 }, 4496 { 4497 Name: "builder name", 4498 Path: "path", 4499 }, 4500 }, 4501 Priority: 30, 4502 }, 4503 }) 4504 }) 4505 }) 4506 4507 Convey("parent run id", func() { 4508 req := &pb.ScheduleBuildRequest{ 4509 Swarming: &pb.ScheduleBuildRequest_Swarming{ 4510 ParentRunId: "id", 4511 }, 4512 } 4513 b := &pb.Build{ 4514 Builder: &pb.BuilderID{ 4515 Project: "project", 4516 Bucket: "bucket", 4517 Builder: "builder", 4518 }, 4519 Infra: &pb.BuildInfra{ 4520 Bbagent: &pb.BuildInfra_BBAgent{ 4521 PayloadPath: "kitchen-checkout", 4522 CacheDir: "cache", 4523 }, 4524 Buildbucket: &pb.BuildInfra_Buildbucket{ 4525 Hostname: "app.appspot.com", 4526 }, 4527 Logdog: &pb.BuildInfra_LogDog{ 4528 Project: "project", 4529 }, 4530 Resultdb: &pb.BuildInfra_ResultDB{}, 4531 }, 4532 } 4533 4534 setSwarmingOrBackend(ctx, req, nil, b, nil) 4535 So(b.Infra, ShouldResembleProto, &pb.BuildInfra{ 4536 Bbagent: &pb.BuildInfra_BBAgent{ 4537 CacheDir: "cache", 4538 PayloadPath: "kitchen-checkout", 4539 }, 4540 Buildbucket: &pb.BuildInfra_Buildbucket{ 4541 Hostname: "app.appspot.com", 4542 }, 4543 Logdog: &pb.BuildInfra_LogDog{ 4544 Project: "project", 4545 }, 4546 Resultdb: &pb.BuildInfra_ResultDB{}, 4547 Swarming: &pb.BuildInfra_Swarming{ 4548 Caches: []*pb.BuildInfra_Swarming_CacheEntry{ 4549 { 4550 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 4551 Path: "builder", 4552 WaitForWarmCache: &durationpb.Duration{ 4553 Seconds: 240, 4554 }, 4555 }, 4556 }, 4557 ParentRunId: "id", 4558 Priority: 30, 4559 }, 4560 }) 4561 }) 4562 4563 Convey("priority", func() { 4564 req := &pb.ScheduleBuildRequest{ 4565 Priority: 1, 4566 } 4567 b := &pb.Build{ 4568 Builder: &pb.BuilderID{ 4569 Project: "project", 4570 Bucket: "bucket", 4571 Builder: "builder", 4572 }, 4573 Infra: &pb.BuildInfra{ 4574 Bbagent: &pb.BuildInfra_BBAgent{ 4575 PayloadPath: "kitchen-checkout", 4576 CacheDir: "cache", 4577 }, 4578 Buildbucket: &pb.BuildInfra_Buildbucket{ 4579 Hostname: "app.appspot.com", 4580 }, 4581 Logdog: &pb.BuildInfra_LogDog{ 4582 Project: "project", 4583 }, 4584 Resultdb: &pb.BuildInfra_ResultDB{}, 4585 }, 4586 } 4587 4588 setSwarmingOrBackend(ctx, req, nil, b, nil) 4589 So(b.Infra, ShouldResembleProto, &pb.BuildInfra{ 4590 Bbagent: &pb.BuildInfra_BBAgent{ 4591 CacheDir: "cache", 4592 PayloadPath: "kitchen-checkout", 4593 }, 4594 Buildbucket: &pb.BuildInfra_Buildbucket{ 4595 Hostname: "app.appspot.com", 4596 }, 4597 Logdog: &pb.BuildInfra_LogDog{ 4598 Project: "project", 4599 }, 4600 Resultdb: &pb.BuildInfra_ResultDB{}, 4601 Swarming: &pb.BuildInfra_Swarming{ 4602 Caches: []*pb.BuildInfra_Swarming_CacheEntry{ 4603 { 4604 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 4605 Path: "builder", 4606 WaitForWarmCache: &durationpb.Duration{ 4607 Seconds: 240, 4608 }, 4609 }, 4610 }, 4611 Priority: 1, 4612 }, 4613 }) 4614 }) 4615 }) 4616 4617 Convey("backend", func() { 4618 b := &pb.Build{ 4619 Builder: &pb.BuilderID{ 4620 Project: "project", 4621 Bucket: "bucket", 4622 Builder: "builder", 4623 }, 4624 Infra: &pb.BuildInfra{ 4625 Bbagent: &pb.BuildInfra_BBAgent{ 4626 CacheDir: "cache", 4627 PayloadPath: "kitchen-checkout", 4628 }, 4629 Buildbucket: &pb.BuildInfra_Buildbucket{ 4630 Hostname: "app.appspot.com", 4631 }, 4632 Logdog: &pb.BuildInfra_LogDog{ 4633 Hostname: "host", 4634 Project: "project", 4635 }, 4636 Resultdb: &pb.BuildInfra_ResultDB{}, 4637 }, 4638 } 4639 s := &pb.SettingsCfg{ 4640 Backends: []*pb.BackendSetting{ 4641 { 4642 Target: "swarming://chromium-swarm", 4643 Hostname: "chromium-swarm.appspot.com", 4644 }, 4645 }, 4646 } 4647 bldrCfg := &pb.BuilderConfig{ 4648 ServiceAccount: "account", 4649 Priority: 200, 4650 Backend: &pb.BuilderConfig_Backend{ 4651 Target: "swarming://chromium-swarm", 4652 }, 4653 Experiments: map[string]int32{ 4654 bb.ExperimentBackendAlt: 100, 4655 }, 4656 } 4657 4658 // Need these to be set so that setSwarmingOrBackend can be set. 4659 setExecutable(nil, bldrCfg, b) 4660 setInput(ctx, nil, bldrCfg, b) 4661 setExperiments(ctx, nil, bldrCfg, s, b) 4662 4663 Convey("use builder Priority and ServiceAccount", func() { 4664 4665 setSwarmingOrBackend(ctx, nil, bldrCfg, b, s) 4666 4667 expectedBackendConfig := &structpb.Struct{} 4668 expectedBackendConfig.Fields = make(map[string]*structpb.Value) 4669 expectedBackendConfig.Fields["priority"] = &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: 200}} 4670 expectedBackendConfig.Fields["service_account"] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "account"}} 4671 4672 So(b.Infra.Backend, ShouldResembleProto, &pb.BuildInfra_Backend{ 4673 Caches: []*pb.CacheEntry{ 4674 { 4675 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 4676 Path: "builder", 4677 WaitForWarmCache: &durationpb.Duration{Seconds: 240}, 4678 }, 4679 }, 4680 Config: expectedBackendConfig, 4681 Hostname: "chromium-swarm.appspot.com", 4682 Task: &pb.Task{ 4683 Id: &pb.TaskID{ 4684 Target: "swarming://chromium-swarm", 4685 }, 4686 }, 4687 }) 4688 }) 4689 4690 Convey("use backend priority and ServiceAccount", func() { 4691 bldrCfg.Backend.ConfigJson = "{\"priority\": 2, \"service_account\": \"service_account\"}" 4692 setSwarmingOrBackend(ctx, nil, bldrCfg, b, s) 4693 4694 expectedBackendConfig := &structpb.Struct{} 4695 expectedBackendConfig.Fields = make(map[string]*structpb.Value) 4696 expectedBackendConfig.Fields["priority"] = &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: 2}} 4697 expectedBackendConfig.Fields["service_account"] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "service_account"}} 4698 4699 So(b.Infra.Backend, ShouldResembleProto, &pb.BuildInfra_Backend{ 4700 Caches: []*pb.CacheEntry{ 4701 { 4702 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 4703 Path: "builder", 4704 WaitForWarmCache: &durationpb.Duration{Seconds: 240}, 4705 }, 4706 }, 4707 Config: expectedBackendConfig, 4708 Hostname: "chromium-swarm.appspot.com", 4709 Task: &pb.Task{ 4710 Id: &pb.TaskID{ 4711 Target: "swarming://chromium-swarm", 4712 }, 4713 }, 4714 }) 4715 }) 4716 4717 Convey("use user requested priority", func() { 4718 req := &pb.ScheduleBuildRequest{Priority: 22} 4719 setSwarmingOrBackend(ctx, req, bldrCfg, b, s) 4720 4721 expectedBackendConfig := &structpb.Struct{} 4722 expectedBackendConfig.Fields = make(map[string]*structpb.Value) 4723 expectedBackendConfig.Fields["priority"] = &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: 22}} 4724 expectedBackendConfig.Fields["service_account"] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "account"}} 4725 4726 So(b.Infra.Backend, ShouldResembleProto, &pb.BuildInfra_Backend{ 4727 Caches: []*pb.CacheEntry{ 4728 { 4729 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 4730 Path: "builder", 4731 WaitForWarmCache: &durationpb.Duration{Seconds: 240}, 4732 }, 4733 }, 4734 Config: expectedBackendConfig, 4735 Hostname: "chromium-swarm.appspot.com", 4736 Task: &pb.Task{ 4737 Id: &pb.TaskID{ 4738 Target: "swarming://chromium-swarm", 4739 }, 4740 }, 4741 }) 4742 }) 4743 4744 Convey("backend alt is used", func() { 4745 bldrCfg.BackendAlt = &pb.BuilderConfig_Backend{ 4746 Target: "swarming://chromium-swarm-alt", 4747 } 4748 4749 setSwarmingOrBackend(ctx, nil, bldrCfg, b, s) 4750 4751 expectedBackendConfig := &structpb.Struct{} 4752 expectedBackendConfig.Fields = make(map[string]*structpb.Value) 4753 expectedBackendConfig.Fields["priority"] = &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: 200}} 4754 expectedBackendConfig.Fields["service_account"] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "account"}} 4755 4756 So(b.Infra.Backend, ShouldResembleProto, &pb.BuildInfra_Backend{ 4757 Caches: []*pb.CacheEntry{ 4758 { 4759 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 4760 Path: "builder", 4761 WaitForWarmCache: &durationpb.Duration{Seconds: 240}, 4762 }, 4763 }, 4764 Config: expectedBackendConfig, 4765 Task: &pb.Task{ 4766 Id: &pb.TaskID{ 4767 Target: "swarming://chromium-swarm-alt", 4768 }, 4769 }, 4770 }) 4771 }) 4772 4773 Convey("backend_alt exp is true, derive backend from swarming", func() { 4774 bldrCfg := &pb.BuilderConfig{ 4775 ServiceAccount: "account", 4776 Priority: 200, 4777 Experiments: map[string]int32{ 4778 bb.ExperimentBackendAlt: 100, 4779 }, 4780 SwarmingHost: "chromium-swarming.appspot.com", 4781 } 4782 4783 s.SwarmingBackends = map[string]string{ 4784 "chromium-swarming.appspot.com": "swarming://chromium-swarm", 4785 } 4786 setSwarmingOrBackend(ctx, nil, bldrCfg, b, s) 4787 4788 expectedBackendConfig := &structpb.Struct{} 4789 expectedBackendConfig.Fields = make(map[string]*structpb.Value) 4790 expectedBackendConfig.Fields["priority"] = &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: 200}} 4791 expectedBackendConfig.Fields["service_account"] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "account"}} 4792 4793 So(b.Infra.Swarming, ShouldBeNil) 4794 So(b.Infra.Backend, ShouldResembleProto, &pb.BuildInfra_Backend{ 4795 Caches: []*pb.CacheEntry{ 4796 { 4797 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 4798 Path: "builder", 4799 WaitForWarmCache: &durationpb.Duration{Seconds: 240}, 4800 }, 4801 }, 4802 Config: expectedBackendConfig, 4803 Hostname: "chromium-swarm.appspot.com", 4804 Task: &pb.Task{ 4805 Id: &pb.TaskID{ 4806 Target: "swarming://chromium-swarm", 4807 }, 4808 }, 4809 }) 4810 }) 4811 4812 Convey("backend_alt exp is true but no swarming to backend mapping, so use swarming", func() { 4813 bldrCfg := &pb.BuilderConfig{ 4814 ServiceAccount: "account", 4815 Priority: 200, 4816 Experiments: map[string]int32{ 4817 bb.ExperimentBackendAlt: 100, 4818 }, 4819 } 4820 4821 setSwarmingOrBackend(ctx, nil, bldrCfg, b, s) 4822 4823 expectedBackendConfig := &structpb.Struct{} 4824 expectedBackendConfig.Fields = make(map[string]*structpb.Value) 4825 expectedBackendConfig.Fields["priority"] = &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: 200}} 4826 expectedBackendConfig.Fields["service_account"] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "account"}} 4827 4828 So(b.Infra.Backend, ShouldBeNil) 4829 So(b.Infra.Swarming, ShouldResembleProto, &pb.BuildInfra_Swarming{ 4830 TaskServiceAccount: "account", 4831 Priority: 200, 4832 Caches: []*pb.BuildInfra_Swarming_CacheEntry{ 4833 { 4834 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 4835 Path: "builder", 4836 WaitForWarmCache: &durationpb.Duration{Seconds: 240}, 4837 }, 4838 }, 4839 }) 4840 }) 4841 4842 Convey("swarming is used", func() { 4843 bldrCfg := &pb.BuilderConfig{ 4844 ServiceAccount: "account", 4845 Priority: 200, 4846 } 4847 4848 setSwarmingOrBackend(ctx, nil, bldrCfg, b, s) 4849 4850 expectedBackendConfig := &structpb.Struct{} 4851 expectedBackendConfig.Fields = make(map[string]*structpb.Value) 4852 expectedBackendConfig.Fields["priority"] = &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: 200}} 4853 expectedBackendConfig.Fields["service_account"] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "account"}} 4854 4855 So(b.Infra.Backend, ShouldBeNil) 4856 So(b.Infra.Swarming, ShouldResembleProto, &pb.BuildInfra_Swarming{ 4857 TaskServiceAccount: "account", 4858 Priority: 200, 4859 Caches: []*pb.BuildInfra_Swarming_CacheEntry{ 4860 { 4861 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 4862 Path: "builder", 4863 WaitForWarmCache: &durationpb.Duration{Seconds: 240}, 4864 }, 4865 }, 4866 }) 4867 }) 4868 }) 4869 }) 4870 4871 Convey("setInput", t, func() { 4872 ctx := memlogger.Use(context.Background()) 4873 4874 Convey("nil", func() { 4875 b := &pb.Build{} 4876 4877 setInput(ctx, nil, nil, b) 4878 So(b.Input, ShouldResembleProto, &pb.Build_Input{ 4879 Properties: &structpb.Struct{}, 4880 }) 4881 }) 4882 4883 Convey("request", func() { 4884 Convey("properties", func() { 4885 Convey("empty", func() { 4886 req := &pb.ScheduleBuildRequest{} 4887 b := &pb.Build{} 4888 4889 setInput(ctx, req, nil, b) 4890 So(b.Input, ShouldResembleProto, &pb.Build_Input{ 4891 Properties: &structpb.Struct{}, 4892 }) 4893 }) 4894 4895 Convey("non-empty", func() { 4896 req := &pb.ScheduleBuildRequest{ 4897 Properties: &structpb.Struct{ 4898 Fields: map[string]*structpb.Value{ 4899 "int": { 4900 Kind: &structpb.Value_NumberValue{ 4901 NumberValue: 1, 4902 }, 4903 }, 4904 "str": { 4905 Kind: &structpb.Value_StringValue{ 4906 StringValue: "value", 4907 }, 4908 }, 4909 }, 4910 }, 4911 } 4912 b := &pb.Build{} 4913 4914 setInput(ctx, req, nil, b) 4915 So(b.Input, ShouldResembleProto, &pb.Build_Input{ 4916 Properties: &structpb.Struct{ 4917 Fields: map[string]*structpb.Value{ 4918 "int": { 4919 Kind: &structpb.Value_NumberValue{ 4920 NumberValue: 1, 4921 }, 4922 }, 4923 "str": { 4924 Kind: &structpb.Value_StringValue{ 4925 StringValue: "value", 4926 }, 4927 }, 4928 }, 4929 }, 4930 }) 4931 }) 4932 }) 4933 }) 4934 4935 Convey("config", func() { 4936 Convey("properties", func() { 4937 cfg := &pb.BuilderConfig{ 4938 Properties: "{\"int\": 1, \"str\": \"value\"}", 4939 } 4940 b := &pb.Build{ 4941 Builder: &pb.BuilderID{ 4942 Project: "project", 4943 Bucket: "bucket", 4944 Builder: "builder", 4945 }, 4946 } 4947 4948 setInput(ctx, nil, cfg, b) 4949 So(b.Input, ShouldResembleProto, &pb.Build_Input{ 4950 Properties: &structpb.Struct{ 4951 Fields: map[string]*structpb.Value{ 4952 "int": { 4953 Kind: &structpb.Value_NumberValue{ 4954 NumberValue: 1, 4955 }, 4956 }, 4957 "str": { 4958 Kind: &structpb.Value_StringValue{ 4959 StringValue: "value", 4960 }, 4961 }, 4962 }, 4963 }, 4964 }) 4965 }) 4966 4967 Convey("recipe", func() { 4968 Convey("empty", func() { 4969 cfg := &pb.BuilderConfig{ 4970 Recipe: &pb.BuilderConfig_Recipe{}, 4971 } 4972 b := &pb.Build{ 4973 Builder: &pb.BuilderID{ 4974 Project: "project", 4975 Bucket: "bucket", 4976 Builder: "builder", 4977 }, 4978 } 4979 4980 setInput(ctx, nil, cfg, b) 4981 So(b.Input, ShouldResembleProto, &pb.Build_Input{ 4982 Properties: &structpb.Struct{ 4983 Fields: map[string]*structpb.Value{ 4984 "recipe": { 4985 Kind: &structpb.Value_StringValue{}, 4986 }, 4987 }, 4988 }, 4989 }) 4990 }) 4991 4992 Convey("properties", func() { 4993 cfg := &pb.BuilderConfig{ 4994 Recipe: &pb.BuilderConfig_Recipe{ 4995 Properties: []string{ 4996 "key:value", 4997 }, 4998 }, 4999 } 5000 b := &pb.Build{ 5001 Builder: &pb.BuilderID{ 5002 Project: "project", 5003 Bucket: "bucket", 5004 Builder: "builder", 5005 }, 5006 } 5007 5008 setInput(ctx, nil, cfg, b) 5009 So(b.Input, ShouldResembleProto, &pb.Build_Input{ 5010 Properties: &structpb.Struct{ 5011 Fields: map[string]*structpb.Value{ 5012 "key": { 5013 Kind: &structpb.Value_StringValue{ 5014 StringValue: "value", 5015 }, 5016 }, 5017 "recipe": { 5018 Kind: &structpb.Value_StringValue{ 5019 StringValue: "", 5020 }, 5021 }, 5022 }, 5023 }, 5024 }) 5025 }) 5026 5027 Convey("properties json", func() { 5028 cfg := &pb.BuilderConfig{ 5029 Recipe: &pb.BuilderConfig_Recipe{ 5030 PropertiesJ: []string{ 5031 "str:\"value\"", 5032 "int:1", 5033 }, 5034 }, 5035 } 5036 b := &pb.Build{ 5037 Builder: &pb.BuilderID{ 5038 Project: "project", 5039 Bucket: "bucket", 5040 Builder: "builder", 5041 }, 5042 } 5043 5044 setInput(ctx, nil, cfg, b) 5045 So(b.Input, ShouldResembleProto, &pb.Build_Input{ 5046 Properties: &structpb.Struct{ 5047 Fields: map[string]*structpb.Value{ 5048 "int": { 5049 Kind: &structpb.Value_NumberValue{ 5050 NumberValue: 1, 5051 }, 5052 }, 5053 "recipe": { 5054 Kind: &structpb.Value_StringValue{}, 5055 }, 5056 "str": { 5057 Kind: &structpb.Value_StringValue{ 5058 StringValue: "value", 5059 }, 5060 }, 5061 }, 5062 }, 5063 }) 5064 }) 5065 5066 Convey("recipe", func() { 5067 cfg := &pb.BuilderConfig{ 5068 Recipe: &pb.BuilderConfig_Recipe{ 5069 Name: "recipe", 5070 }, 5071 } 5072 b := &pb.Build{ 5073 Builder: &pb.BuilderID{ 5074 Project: "project", 5075 Bucket: "bucket", 5076 Builder: "builder", 5077 }, 5078 } 5079 5080 setInput(ctx, nil, cfg, b) 5081 So(b.Input, ShouldResembleProto, &pb.Build_Input{ 5082 Properties: &structpb.Struct{ 5083 Fields: map[string]*structpb.Value{ 5084 "recipe": { 5085 Kind: &structpb.Value_StringValue{ 5086 StringValue: "recipe", 5087 }, 5088 }, 5089 }, 5090 }, 5091 }) 5092 }) 5093 5094 Convey("properties json > properties", func() { 5095 cfg := &pb.BuilderConfig{ 5096 Recipe: &pb.BuilderConfig_Recipe{ 5097 Properties: []string{ 5098 "key:value", 5099 }, 5100 PropertiesJ: []string{ 5101 "key:1", 5102 }, 5103 }, 5104 } 5105 b := &pb.Build{ 5106 Builder: &pb.BuilderID{ 5107 Project: "project", 5108 Bucket: "bucket", 5109 Builder: "builder", 5110 }, 5111 } 5112 5113 setInput(ctx, nil, cfg, b) 5114 So(b.Input, ShouldResembleProto, &pb.Build_Input{ 5115 Properties: &structpb.Struct{ 5116 Fields: map[string]*structpb.Value{ 5117 "key": { 5118 Kind: &structpb.Value_NumberValue{ 5119 NumberValue: 1, 5120 }, 5121 }, 5122 "recipe": { 5123 Kind: &structpb.Value_StringValue{ 5124 StringValue: "", 5125 }, 5126 }, 5127 }, 5128 }, 5129 }) 5130 }) 5131 5132 Convey("recipe > properties", func() { 5133 cfg := &pb.BuilderConfig{ 5134 Recipe: &pb.BuilderConfig_Recipe{ 5135 Name: "recipe", 5136 Properties: []string{ 5137 "recipe:value", 5138 }, 5139 }, 5140 } 5141 b := &pb.Build{ 5142 Builder: &pb.BuilderID{ 5143 Project: "project", 5144 Bucket: "bucket", 5145 Builder: "builder", 5146 }, 5147 } 5148 5149 setInput(ctx, nil, cfg, b) 5150 So(b.Input, ShouldResembleProto, &pb.Build_Input{ 5151 Properties: &structpb.Struct{ 5152 Fields: map[string]*structpb.Value{ 5153 "recipe": { 5154 Kind: &structpb.Value_StringValue{ 5155 StringValue: "recipe", 5156 }, 5157 }, 5158 }, 5159 }, 5160 }) 5161 }) 5162 5163 Convey("recipe > properties json", func() { 5164 cfg := &pb.BuilderConfig{ 5165 Recipe: &pb.BuilderConfig_Recipe{ 5166 Name: "recipe", 5167 PropertiesJ: []string{ 5168 "recipe:\"value\"", 5169 }, 5170 }, 5171 } 5172 b := &pb.Build{ 5173 Builder: &pb.BuilderID{ 5174 Project: "project", 5175 Bucket: "bucket", 5176 Builder: "builder", 5177 }, 5178 } 5179 5180 setInput(ctx, nil, cfg, b) 5181 So(b.Input, ShouldResembleProto, &pb.Build_Input{ 5182 Properties: &structpb.Struct{ 5183 Fields: map[string]*structpb.Value{ 5184 "recipe": { 5185 Kind: &structpb.Value_StringValue{ 5186 StringValue: "recipe", 5187 }, 5188 }, 5189 }, 5190 }, 5191 }) 5192 }) 5193 }) 5194 }) 5195 5196 Convey("request > config", func() { 5197 req := &pb.ScheduleBuildRequest{ 5198 Properties: &structpb.Struct{ 5199 Fields: map[string]*structpb.Value{ 5200 "allowed": { 5201 Kind: &structpb.Value_StringValue{ 5202 StringValue: "I'm alright", 5203 }, 5204 }, 5205 "override": { 5206 Kind: &structpb.Value_StringValue{ 5207 StringValue: "req value", 5208 }, 5209 }, 5210 "req key": { 5211 Kind: &structpb.Value_StringValue{ 5212 StringValue: "req value", 5213 }, 5214 }, 5215 }, 5216 }, 5217 } 5218 cfg := &pb.BuilderConfig{ 5219 Properties: "{\"override\": \"cfg value\", \"allowed\": \"stuff\", \"cfg key\": \"cfg value\"}", 5220 AllowedPropertyOverrides: []string{"allowed"}, 5221 } 5222 b := &pb.Build{ 5223 Builder: &pb.BuilderID{ 5224 Project: "project", 5225 Bucket: "bucket", 5226 Builder: "builder", 5227 }, 5228 } 5229 5230 setInput(ctx, req, cfg, b) 5231 So(b.Input, ShouldResembleProto, &pb.Build_Input{ 5232 Properties: &structpb.Struct{ 5233 Fields: map[string]*structpb.Value{ 5234 "allowed": { 5235 Kind: &structpb.Value_StringValue{ 5236 StringValue: "I'm alright", 5237 }, 5238 }, 5239 "cfg key": { 5240 Kind: &structpb.Value_StringValue{ 5241 StringValue: "cfg value", 5242 }, 5243 }, 5244 "override": { 5245 Kind: &structpb.Value_StringValue{ 5246 StringValue: "req value", 5247 }, 5248 }, 5249 "req key": { 5250 Kind: &structpb.Value_StringValue{ 5251 StringValue: "req value", 5252 }, 5253 }, 5254 }, 5255 }, 5256 }) 5257 So(ctx, memlogger.ShouldHaveLog, logging.Warning, "ScheduleBuild: Unpermitted Override for property \"override\"") 5258 So(ctx, memlogger.ShouldNotHaveLog, logging.Warning, "ScheduleBuild: Unpermitted Override for property \"allowed\"") 5259 So(ctx, memlogger.ShouldNotHaveLog, logging.Warning, "ScheduleBuild: Unpermitted Override for property \"cfg key\"") 5260 So(ctx, memlogger.ShouldNotHaveLog, logging.Warning, "ScheduleBuild: Unpermitted Override for property \"req key\"") 5261 }) 5262 }) 5263 5264 Convey("setTags", t, func() { 5265 Convey("nil", func() { 5266 b := &pb.Build{} 5267 5268 setTags(nil, b, "") 5269 So(b.Tags, ShouldResemble, []*pb.StringPair{}) 5270 }) 5271 5272 Convey("request", func() { 5273 req := &pb.ScheduleBuildRequest{ 5274 Tags: []*pb.StringPair{ 5275 { 5276 Key: "key2", 5277 Value: "value2", 5278 }, 5279 { 5280 Key: "key1", 5281 Value: "value1", 5282 }, 5283 }, 5284 } 5285 normalizeSchedule(req) 5286 b := &pb.Build{} 5287 5288 setTags(req, b, "") 5289 So(b.Tags, ShouldResemble, []*pb.StringPair{ 5290 { 5291 Key: "key1", 5292 Value: "value1", 5293 }, 5294 { 5295 Key: "key2", 5296 Value: "value2", 5297 }, 5298 }) 5299 }) 5300 5301 Convey("builder", func() { 5302 req := &pb.ScheduleBuildRequest{ 5303 Builder: &pb.BuilderID{ 5304 Project: "project", 5305 Bucket: "bucket", 5306 Builder: "builder", 5307 }, 5308 } 5309 normalizeSchedule(req) 5310 b := &pb.Build{} 5311 5312 setTags(req, b, "") 5313 So(b.Tags, ShouldResemble, []*pb.StringPair{ 5314 { 5315 Key: "builder", 5316 Value: "builder", 5317 }, 5318 }) 5319 }) 5320 5321 Convey("gitiles commit", func() { 5322 req := &pb.ScheduleBuildRequest{ 5323 GitilesCommit: &pb.GitilesCommit{ 5324 Host: "host", 5325 Project: "project", 5326 Id: "id", 5327 Ref: "ref", 5328 }, 5329 } 5330 normalizeSchedule(req) 5331 b := &pb.Build{} 5332 5333 setTags(req, b, "") 5334 So(b.Tags, ShouldResemble, []*pb.StringPair{ 5335 { 5336 Key: "buildset", 5337 Value: "commit/gitiles/host/project/+/id", 5338 }, 5339 { 5340 Key: "gitiles_ref", 5341 Value: "ref", 5342 }, 5343 }) 5344 }) 5345 5346 Convey("partial gitiles commit", func() { 5347 req := &pb.ScheduleBuildRequest{ 5348 GitilesCommit: &pb.GitilesCommit{ 5349 Host: "host", 5350 Project: "project", 5351 Ref: "ref", 5352 }, 5353 } 5354 normalizeSchedule(req) 5355 b := &pb.Build{} 5356 5357 setTags(req, b, "") 5358 So(b.Tags, ShouldResemble, []*pb.StringPair{ 5359 { 5360 Key: "gitiles_ref", 5361 Value: "ref", 5362 }, 5363 }) 5364 }) 5365 5366 Convey("gerrit changes", func() { 5367 Convey("one", func() { 5368 req := &pb.ScheduleBuildRequest{ 5369 GerritChanges: []*pb.GerritChange{ 5370 { 5371 Host: "host", 5372 Change: 1, 5373 Patchset: 2, 5374 }, 5375 }, 5376 } 5377 normalizeSchedule(req) 5378 b := &pb.Build{} 5379 5380 setTags(req, b, "") 5381 So(b.Tags, ShouldResemble, []*pb.StringPair{ 5382 { 5383 Key: "buildset", 5384 Value: "patch/gerrit/host/1/2", 5385 }, 5386 }) 5387 }) 5388 5389 Convey("many", func() { 5390 req := &pb.ScheduleBuildRequest{ 5391 GerritChanges: []*pb.GerritChange{ 5392 { 5393 Host: "host", 5394 Change: 3, 5395 Patchset: 4, 5396 }, 5397 { 5398 Host: "host", 5399 Change: 1, 5400 Patchset: 2, 5401 }, 5402 }, 5403 } 5404 normalizeSchedule(req) 5405 b := &pb.Build{} 5406 5407 setTags(req, b, "") 5408 So(b.Tags, ShouldResemble, []*pb.StringPair{ 5409 { 5410 Key: "buildset", 5411 Value: "patch/gerrit/host/1/2", 5412 }, 5413 { 5414 Key: "buildset", 5415 Value: "patch/gerrit/host/3/4", 5416 }, 5417 }) 5418 }) 5419 }) 5420 5421 Convey("various", func() { 5422 req := &pb.ScheduleBuildRequest{ 5423 Builder: &pb.BuilderID{ 5424 Project: "project", 5425 Bucket: "bucket", 5426 Builder: "builder", 5427 }, 5428 GerritChanges: []*pb.GerritChange{ 5429 { 5430 Host: "host", 5431 Change: 3, 5432 Patchset: 4, 5433 }, 5434 { 5435 Host: "host", 5436 Change: 1, 5437 Patchset: 2, 5438 }, 5439 }, 5440 GitilesCommit: &pb.GitilesCommit{ 5441 Host: "host", 5442 Project: "project", 5443 Id: "id", 5444 Ref: "ref", 5445 }, 5446 Tags: []*pb.StringPair{ 5447 { 5448 Key: "key2", 5449 Value: "value2", 5450 }, 5451 { 5452 Key: "key1", 5453 Value: "value1", 5454 }, 5455 }, 5456 } 5457 normalizeSchedule(req) 5458 b := &pb.Build{} 5459 5460 setTags(req, b, "") 5461 So(b.Tags, ShouldResemble, []*pb.StringPair{ 5462 { 5463 Key: "builder", 5464 Value: "builder", 5465 }, 5466 { 5467 Key: "buildset", 5468 Value: "commit/gitiles/host/project/+/id", 5469 }, 5470 { 5471 Key: "buildset", 5472 Value: "patch/gerrit/host/1/2", 5473 }, 5474 { 5475 Key: "buildset", 5476 Value: "patch/gerrit/host/3/4", 5477 }, 5478 { 5479 Key: "gitiles_ref", 5480 Value: "ref", 5481 }, 5482 { 5483 Key: "key1", 5484 Value: "value1", 5485 }, 5486 { 5487 Key: "key2", 5488 Value: "value2", 5489 }, 5490 }) 5491 }) 5492 }) 5493 5494 Convey("setTimeouts", t, func() { 5495 Convey("nil", func() { 5496 b := &pb.Build{} 5497 5498 setTimeouts(nil, nil, b) 5499 So(b.ExecutionTimeout, ShouldResembleProto, &durationpb.Duration{ 5500 Seconds: 10800, 5501 }) 5502 So(b.GracePeriod, ShouldResembleProto, &durationpb.Duration{ 5503 Seconds: 30, 5504 }) 5505 So(b.SchedulingTimeout, ShouldResembleProto, &durationpb.Duration{ 5506 Seconds: 21600, 5507 }) 5508 }) 5509 5510 Convey("request only", func() { 5511 req := &pb.ScheduleBuildRequest{ 5512 ExecutionTimeout: &durationpb.Duration{ 5513 Seconds: 1, 5514 }, 5515 GracePeriod: &durationpb.Duration{ 5516 Seconds: 2, 5517 }, 5518 SchedulingTimeout: &durationpb.Duration{ 5519 Seconds: 3, 5520 }, 5521 } 5522 normalizeSchedule(req) 5523 b := &pb.Build{} 5524 5525 setTimeouts(req, nil, b) 5526 So(b.ExecutionTimeout, ShouldResembleProto, &durationpb.Duration{ 5527 Seconds: 1, 5528 }) 5529 So(b.GracePeriod, ShouldResembleProto, &durationpb.Duration{ 5530 Seconds: 2, 5531 }) 5532 So(b.SchedulingTimeout, ShouldResembleProto, &durationpb.Duration{ 5533 Seconds: 3, 5534 }) 5535 }) 5536 5537 Convey("config only", func() { 5538 cfg := &pb.BuilderConfig{ 5539 ExecutionTimeoutSecs: 1, 5540 ExpirationSecs: 3, 5541 GracePeriod: &durationpb.Duration{ 5542 Seconds: 2, 5543 }, 5544 } 5545 b := &pb.Build{} 5546 5547 setTimeouts(nil, cfg, b) 5548 So(b.ExecutionTimeout, ShouldResembleProto, &durationpb.Duration{ 5549 Seconds: 1, 5550 }) 5551 So(b.GracePeriod, ShouldResembleProto, &durationpb.Duration{ 5552 Seconds: 2, 5553 }) 5554 So(b.SchedulingTimeout, ShouldResembleProto, &durationpb.Duration{ 5555 Seconds: 3, 5556 }) 5557 }) 5558 5559 Convey("override", func() { 5560 req := &pb.ScheduleBuildRequest{ 5561 ExecutionTimeout: &durationpb.Duration{ 5562 Seconds: 1, 5563 }, 5564 GracePeriod: &durationpb.Duration{ 5565 Seconds: 2, 5566 }, 5567 SchedulingTimeout: &durationpb.Duration{ 5568 Seconds: 3, 5569 }, 5570 } 5571 normalizeSchedule(req) 5572 cfg := &pb.BuilderConfig{ 5573 ExecutionTimeoutSecs: 4, 5574 ExpirationSecs: 6, 5575 GracePeriod: &durationpb.Duration{ 5576 Seconds: 5, 5577 }, 5578 } 5579 b := &pb.Build{} 5580 5581 setTimeouts(req, cfg, b) 5582 So(b.ExecutionTimeout, ShouldResembleProto, &durationpb.Duration{ 5583 Seconds: 1, 5584 }) 5585 So(b.GracePeriod, ShouldResembleProto, &durationpb.Duration{ 5586 Seconds: 2, 5587 }) 5588 So(b.SchedulingTimeout, ShouldResembleProto, &durationpb.Duration{ 5589 Seconds: 3, 5590 }) 5591 }) 5592 }) 5593 5594 Convey("ScheduleBuild", t, func() { 5595 srv := &Builds{} 5596 ctx := txndefer.FilterRDS(memory.Use(context.Background())) 5597 ctx = metrics.WithServiceInfo(ctx, "svc", "job", "ins") 5598 ctx = mathrand.Set(ctx, rand.New(rand.NewSource(0))) 5599 ctx, _ = testclock.UseTime(ctx, testclock.TestRecentTimeUTC) 5600 ctx, sch := tq.TestingContext(ctx, nil) 5601 datastore.GetTestable(ctx).AutoIndex(true) 5602 datastore.GetTestable(ctx).Consistent(true) 5603 ctx = auth.WithState(ctx, &authtest.FakeState{ 5604 Identity: userID, 5605 }) 5606 5607 So(config.SetTestSettingsCfg(ctx, &pb.SettingsCfg{ 5608 Resultdb: &pb.ResultDBSettings{ 5609 Hostname: "rdbHost", 5610 }, 5611 Swarming: &pb.SwarmingSettings{ 5612 BbagentPackage: &pb.SwarmingSettings_Package{ 5613 PackageName: "bbagent", 5614 Version: "bbagent-version", 5615 }, 5616 KitchenPackage: &pb.SwarmingSettings_Package{ 5617 PackageName: "kitchen", 5618 Version: "kitchen-version", 5619 }, 5620 }, 5621 Backends: []*pb.BackendSetting{ 5622 { 5623 Target: "lite://foo-lite", 5624 Hostname: "foo_hostname", 5625 Mode: &pb.BackendSetting_LiteMode_{}, 5626 }, 5627 }, 5628 }), ShouldBeNil) 5629 5630 Convey("builder", func() { 5631 Convey("not found", func() { 5632 req := &pb.ScheduleBuildRequest{ 5633 Builder: &pb.BuilderID{ 5634 Project: "project", 5635 Bucket: "bucket", 5636 Builder: "builder", 5637 }, 5638 } 5639 rsp, err := srv.ScheduleBuild(ctx, req) 5640 So(err, ShouldErrLike, "not found") 5641 So(rsp, ShouldBeNil) 5642 So(sch.Tasks(), ShouldBeEmpty) 5643 }) 5644 5645 Convey("permission denied", func() { 5646 So(datastore.Put(ctx, &model.Build{ 5647 Proto: &pb.Build{ 5648 Id: 1, 5649 Builder: &pb.BuilderID{ 5650 Project: "project", 5651 Bucket: "bucket", 5652 Builder: "builder", 5653 }, 5654 }, 5655 }), ShouldBeNil) 5656 req := &pb.ScheduleBuildRequest{ 5657 Builder: &pb.BuilderID{ 5658 Project: "project", 5659 Bucket: "bucket", 5660 Builder: "builder", 5661 }, 5662 } 5663 rsp, err := srv.ScheduleBuild(ctx, req) 5664 So(err, ShouldErrLike, "not found") 5665 So(rsp, ShouldBeNil) 5666 So(sch.Tasks(), ShouldBeEmpty) 5667 }) 5668 5669 Convey("directly from dynamic", func() { 5670 ctx = auth.WithState(ctx, &authtest.FakeState{ 5671 Identity: userID, 5672 FakeDB: authtest.NewFakeDB( 5673 authtest.MockPermission(userID, "project:bucket", bbperms.BuildsAdd), 5674 ), 5675 }) 5676 5677 Convey("no template in dynamic_builder_template", func() { 5678 testutil.PutBucket(ctx, "project", "bucket", &pb.Bucket{DynamicBuilderTemplate: &pb.Bucket_DynamicBuilderTemplate{}}) 5679 req := &pb.ScheduleBuildRequest{ 5680 Builder: &pb.BuilderID{ 5681 Project: "project", 5682 Bucket: "bucket", 5683 Builder: "builder", 5684 }, 5685 } 5686 5687 rsp, err := srv.ScheduleBuild(ctx, req) 5688 So(err, ShouldBeNil) 5689 So(rsp, ShouldResembleProto, &pb.Build{ 5690 Builder: &pb.BuilderID{ 5691 Project: "project", 5692 Bucket: "bucket", 5693 Builder: "builder", 5694 }, 5695 CreatedBy: string(userID), 5696 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 5697 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 5698 Id: 9021868963221667745, 5699 Input: &pb.Build_Input{}, 5700 Status: pb.Status_SCHEDULED, 5701 }) 5702 So(sch.Tasks(), ShouldBeEmpty) 5703 }) 5704 5705 Convey("has template in dynamic_builder_template", func() { 5706 testutil.PutBucket(ctx, "project", "bucket", &pb.Bucket{ 5707 DynamicBuilderTemplate: &pb.Bucket_DynamicBuilderTemplate{ 5708 Template: &pb.BuilderConfig{ 5709 Backend: &pb.BuilderConfig_Backend{ 5710 Target: "lite://foo-lite", 5711 }, 5712 Experiments: map[string]int32{ 5713 "luci.buildbucket.backend_alt": 100, 5714 }, 5715 }, 5716 }, 5717 }) 5718 req := &pb.ScheduleBuildRequest{ 5719 Builder: &pb.BuilderID{ 5720 Project: "project", 5721 Bucket: "bucket", 5722 Builder: "builder", 5723 }, 5724 } 5725 5726 rsp, err := srv.ScheduleBuild(ctx, req) 5727 So(err, ShouldBeNil) 5728 So(rsp, ShouldResembleProto, &pb.Build{ 5729 Builder: &pb.BuilderID{ 5730 Project: "project", 5731 Bucket: "bucket", 5732 Builder: "builder", 5733 }, 5734 CreatedBy: string(userID), 5735 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 5736 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 5737 Id: 9021868963221667745, 5738 Input: &pb.Build_Input{}, 5739 Status: pb.Status_SCHEDULED, 5740 }) 5741 5742 buildInDB := &model.Build{ID: 9021868963221667745} 5743 bInfra := &model.BuildInfra{Build: datastore.KeyForObj(ctx, buildInDB)} 5744 So(datastore.Get(ctx, buildInDB, bInfra), ShouldBeNil) 5745 So(bInfra.Proto.Backend, ShouldResembleProto, &pb.BuildInfra_Backend{ 5746 Caches: []*pb.CacheEntry{ 5747 { 5748 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 5749 Path: "builder", 5750 WaitForWarmCache: &durationpb.Duration{Seconds: 240}, 5751 }, 5752 }, 5753 Config: &structpb.Struct{ 5754 Fields: map[string]*structpb.Value{ 5755 "priority": structpb.NewNumberValue(30), 5756 }, 5757 }, 5758 Hostname: "foo_hostname", 5759 Task: &pb.Task{ 5760 Id: &pb.TaskID{ 5761 Target: "lite://foo-lite", 5762 }, 5763 }, 5764 }) 5765 5766 tasks := sch.Tasks() 5767 So(tasks, ShouldHaveLength, 3) 5768 sortTasksByClassName(tasks) 5769 backendTask, ok := tasks.Payloads()[0].(*taskdefs.CreateBackendBuildTask) 5770 So(ok, ShouldBeTrue) 5771 So(backendTask.BuildId, ShouldEqual, 9021868963221667745) 5772 So(tasks.Payloads()[1], ShouldResembleProto, &taskdefs.NotifyPubSub{ 5773 BuildId: 9021868963221667745, 5774 }) 5775 So(tasks.Payloads()[2], ShouldResembleProto, &taskdefs.NotifyPubSubGoProxy{ 5776 BuildId: 9021868963221667745, 5777 Project: "project", 5778 }) 5779 }) 5780 }) 5781 5782 Convey("static", func() { 5783 ctx = auth.WithState(ctx, &authtest.FakeState{ 5784 Identity: userID, 5785 FakeDB: authtest.NewFakeDB( 5786 authtest.MockPermission(userID, "project:bucket", bbperms.BuildsAdd), 5787 ), 5788 }) 5789 5790 testutil.PutBucket(ctx, "project", "bucket", &pb.Bucket{ 5791 Swarming: &pb.Swarming{}, 5792 }) 5793 req := &pb.ScheduleBuildRequest{ 5794 Builder: &pb.BuilderID{ 5795 Project: "project", 5796 Bucket: "bucket", 5797 Builder: "builder", 5798 }, 5799 } 5800 5801 Convey("not found", func() { 5802 rsp, err := srv.ScheduleBuild(ctx, req) 5803 So(err, ShouldErrLike, "error fetching builders") 5804 So(rsp, ShouldBeNil) 5805 So(sch.Tasks(), ShouldBeEmpty) 5806 }) 5807 5808 Convey("exists", func() { 5809 testutil.PutBuilder(ctx, "project", "bucket", "builder", "") 5810 So(datastore.Put(ctx, &model.Build{ 5811 ID: 9021868963221667745, 5812 Proto: &pb.Build{ 5813 Id: 9021868963221667745, 5814 Builder: &pb.BuilderID{ 5815 Project: "project", 5816 Bucket: "bucket", 5817 Builder: "builder", 5818 }, 5819 }, 5820 }), ShouldBeNil) 5821 5822 rsp, err := srv.ScheduleBuild(ctx, req) 5823 So(err, ShouldErrLike, "build already exists") 5824 So(rsp, ShouldBeNil) 5825 So(sch.Tasks(), ShouldBeEmpty) 5826 }) 5827 5828 Convey("ok without backend_go exp", func() { 5829 So(datastore.Put(ctx, &model.Builder{ 5830 Parent: model.BucketKey(ctx, "project", "bucket"), 5831 ID: "builder", 5832 Config: &pb.BuilderConfig{ 5833 BuildNumbers: pb.Toggle_YES, 5834 Name: "builder", 5835 SwarmingHost: "host", 5836 }, 5837 }), ShouldBeNil) 5838 rsp, err := srv.ScheduleBuild(ctx, req) 5839 So(err, ShouldBeNil) 5840 So(rsp, ShouldResembleProto, &pb.Build{ 5841 Builder: &pb.BuilderID{ 5842 Project: "project", 5843 Bucket: "bucket", 5844 Builder: "builder", 5845 }, 5846 CreatedBy: string(userID), 5847 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 5848 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 5849 Id: 9021868963221667745, 5850 Input: &pb.Build_Input{}, 5851 Number: 1, 5852 Status: pb.Status_SCHEDULED, 5853 }) 5854 tasks := sch.Tasks() 5855 So(tasks, ShouldHaveLength, 3) 5856 sortTasksByClassName(tasks) 5857 So(tasks.Payloads()[0], ShouldResembleProto, &taskdefs.CreateSwarmingTask{ 5858 BuildId: 9021868963221667745, 5859 }) 5860 So(tasks.Payloads()[1], ShouldResembleProto, &taskdefs.NotifyPubSub{ 5861 BuildId: 9021868963221667745, 5862 }) 5863 So(tasks.Payloads()[2], ShouldResembleProto, &taskdefs.NotifyPubSubGoProxy{ 5864 BuildId: 9021868963221667745, 5865 Project: "project", 5866 }) 5867 }) 5868 5869 Convey("ok with backend_go exp", func() { 5870 So(datastore.Put(ctx, &model.Builder{ 5871 Parent: model.BucketKey(ctx, "project", "bucket"), 5872 ID: "builder", 5873 Config: &pb.BuilderConfig{ 5874 BuildNumbers: pb.Toggle_YES, 5875 Name: "builder", 5876 Experiments: map[string]int32{bb.ExperimentBackendGo: 100}, 5877 SwarmingHost: "host", 5878 }, 5879 }), ShouldBeNil) 5880 5881 req.Properties = &structpb.Struct{ 5882 Fields: map[string]*structpb.Value{ 5883 "input key": { 5884 Kind: &structpb.Value_StringValue{ 5885 StringValue: "input value", 5886 }, 5887 }, 5888 }, 5889 } 5890 rsp, err := srv.ScheduleBuild(ctx, req) 5891 So(err, ShouldBeNil) 5892 So(rsp, ShouldResembleProto, &pb.Build{ 5893 Builder: &pb.BuilderID{ 5894 Project: "project", 5895 Bucket: "bucket", 5896 Builder: "builder", 5897 }, 5898 CreatedBy: string(userID), 5899 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 5900 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 5901 Id: 9021868963221667745, 5902 Input: &pb.Build_Input{}, 5903 Number: 1, 5904 Status: pb.Status_SCHEDULED, 5905 }) 5906 5907 // check input.properties and infra are stored in their own Datastore 5908 // entities and not in Build entity. 5909 buildInDB := &model.Build{ID: 9021868963221667745} 5910 So(datastore.Get(ctx, buildInDB), ShouldBeNil) 5911 So(buildInDB.Proto.Input.Properties, ShouldBeNil) 5912 So(buildInDB.Proto.Infra, ShouldBeNil) 5913 inProp := &model.BuildInputProperties{Build: datastore.KeyForObj(ctx, buildInDB)} 5914 bInfra := &model.BuildInfra{Build: datastore.KeyForObj(ctx, buildInDB)} 5915 bs := &model.BuildStatus{Build: datastore.KeyForObj(ctx, buildInDB)} 5916 So(datastore.Get(ctx, inProp, bInfra, bs), ShouldBeNil) 5917 So(inProp.Proto, ShouldResembleProto, &structpb.Struct{ 5918 Fields: map[string]*structpb.Value{ 5919 "input key": { 5920 Kind: &structpb.Value_StringValue{ 5921 StringValue: "input value", 5922 }, 5923 }, 5924 }, 5925 }) 5926 So(bInfra.Proto, ShouldNotBeNil) 5927 So(bs.BuildAddress, ShouldEqual, "project/bucket/builder/1") 5928 So(bs.Status, ShouldEqual, pb.Status_SCHEDULED) 5929 5930 tasks := sch.Tasks() 5931 So(tasks, ShouldHaveLength, 3) 5932 sortTasksByClassName(tasks) 5933 So(tasks.Payloads()[0], ShouldResembleProto, &taskdefs.CreateSwarmingBuildTask{ 5934 BuildId: 9021868963221667745, 5935 }) 5936 So(tasks.Payloads()[1], ShouldResembleProto, &taskdefs.NotifyPubSub{ 5937 BuildId: 9021868963221667745, 5938 }) 5939 So(tasks.Payloads()[2], ShouldResembleProto, &taskdefs.NotifyPubSubGoProxy{ 5940 BuildId: 9021868963221667745, 5941 Project: "project", 5942 }) 5943 }) 5944 5945 Convey("dry_run", func() { 5946 So(datastore.Put(ctx, &model.Builder{ 5947 Parent: model.BucketKey(ctx, "project", "bucket"), 5948 ID: "builder", 5949 Config: &pb.BuilderConfig{ 5950 BuildNumbers: pb.Toggle_YES, 5951 Name: "builder", 5952 }, 5953 }), ShouldBeNil) 5954 5955 req.DryRun = true 5956 rsp, err := srv.ScheduleBuild(ctx, req) 5957 So(err, ShouldBeNil) 5958 So(rsp, ShouldResembleProto, &pb.Build{ 5959 Builder: &pb.BuilderID{ 5960 Project: "project", 5961 Bucket: "bucket", 5962 Builder: "builder", 5963 }, 5964 Input: &pb.Build_Input{ 5965 Properties: &structpb.Struct{}, 5966 }, 5967 Tags: []*pb.StringPair{ 5968 { 5969 Key: "builder", 5970 Value: "builder", 5971 }, 5972 }, 5973 Infra: &pb.BuildInfra{ 5974 Bbagent: &pb.BuildInfra_BBAgent{ 5975 CacheDir: "cache", 5976 PayloadPath: "kitchen-checkout", 5977 }, 5978 Buildbucket: &pb.BuildInfra_Buildbucket{ 5979 Hostname: "app.appspot.com", 5980 Agent: &pb.BuildInfra_Buildbucket_Agent{ 5981 Input: &pb.BuildInfra_Buildbucket_Agent_Input{}, 5982 Purposes: map[string]pb.BuildInfra_Buildbucket_Agent_Purpose{ 5983 "kitchen-checkout": pb.BuildInfra_Buildbucket_Agent_PURPOSE_EXE_PAYLOAD, 5984 }, 5985 }, 5986 BuildNumber: true, 5987 }, 5988 Logdog: &pb.BuildInfra_LogDog{ 5989 Project: "project", 5990 }, 5991 Resultdb: &pb.BuildInfra_ResultDB{ 5992 Hostname: "rdbHost", 5993 }, 5994 Swarming: &pb.BuildInfra_Swarming{ 5995 Caches: []*pb.BuildInfra_Swarming_CacheEntry{ 5996 { 5997 Name: "builder_1809c38861a9996b1748e4640234fbd089992359f6f23f62f68deb98528f5f2b_v2", 5998 Path: "builder", 5999 WaitForWarmCache: &durationpb.Duration{ 6000 Seconds: 240, 6001 }, 6002 }, 6003 }, 6004 Priority: 30, 6005 }, 6006 }, 6007 Exe: &pb.Executable{Cmd: []string{"recipes"}}, 6008 SchedulingTimeout: &durationpb.Duration{Seconds: 21600}, 6009 ExecutionTimeout: &durationpb.Duration{Seconds: 10800}, 6010 GracePeriod: &durationpb.Duration{Seconds: 30}, 6011 }) 6012 So(sch.Tasks(), ShouldBeEmpty) 6013 }) 6014 6015 Convey("request ID", func() { 6016 req := &pb.ScheduleBuildRequest{ 6017 Builder: &pb.BuilderID{ 6018 Project: "project", 6019 Bucket: "bucket", 6020 Builder: "builder", 6021 }, 6022 RequestId: "id", 6023 } 6024 testutil.PutBuilder(ctx, "project", "bucket", "builder", "") 6025 6026 Convey("deduplication", func() { 6027 So(datastore.Put(ctx, &model.RequestID{ 6028 ID: "6d03f5c780125e74ac6cb0f25c5e0b6467ff96c96d98bfb41ba382863ba7707a", 6029 BuildID: 1, 6030 }), ShouldBeNil) 6031 6032 Convey("not found", func() { 6033 rsp, err := srv.ScheduleBuild(ctx, req) 6034 So(err, ShouldErrLike, "no such entity") 6035 So(rsp, ShouldBeNil) 6036 So(sch.Tasks(), ShouldBeEmpty) 6037 }) 6038 6039 Convey("ok", func() { 6040 So(datastore.Put(ctx, &model.Build{ 6041 ID: 1, 6042 Proto: &pb.Build{ 6043 Builder: &pb.BuilderID{ 6044 Project: "project", 6045 Bucket: "bucket", 6046 Builder: "builder", 6047 }, 6048 Id: 1, 6049 }, 6050 }), ShouldBeNil) 6051 6052 rsp, err := srv.ScheduleBuild(ctx, req) 6053 So(err, ShouldBeNil) 6054 So(rsp, ShouldResembleProto, &pb.Build{ 6055 Builder: &pb.BuilderID{ 6056 Project: "project", 6057 Bucket: "bucket", 6058 Builder: "builder", 6059 }, 6060 Id: 1, 6061 Input: &pb.Build_Input{}, 6062 }) 6063 So(sch.Tasks(), ShouldBeEmpty) 6064 }) 6065 }) 6066 6067 Convey("ok", func() { 6068 rsp, err := srv.ScheduleBuild(ctx, req) 6069 So(err, ShouldBeNil) 6070 So(rsp, ShouldResembleProto, &pb.Build{ 6071 Builder: &pb.BuilderID{ 6072 Project: "project", 6073 Bucket: "bucket", 6074 Builder: "builder", 6075 }, 6076 CreatedBy: string(userID), 6077 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6078 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6079 Id: 9021868963221667745, 6080 Input: &pb.Build_Input{}, 6081 Status: pb.Status_SCHEDULED, 6082 }) 6083 So(sch.Tasks(), ShouldHaveLength, 3) 6084 6085 r := &model.RequestID{ 6086 ID: "6d03f5c780125e74ac6cb0f25c5e0b6467ff96c96d98bfb41ba382863ba7707a", 6087 } 6088 So(datastore.Get(ctx, r), ShouldBeNil) 6089 So(r, ShouldResemble, &model.RequestID{ 6090 ID: "6d03f5c780125e74ac6cb0f25c5e0b6467ff96c96d98bfb41ba382863ba7707a", 6091 BuildID: 9021868963221667745, 6092 CreatedBy: userID, 6093 CreateTime: datastore.RoundTime(testclock.TestRecentTimeUTC), 6094 RequestID: "id", 6095 }) 6096 }) 6097 6098 Convey("builder description", func() { 6099 So(datastore.Put(ctx, &model.Builder{ 6100 Parent: model.BucketKey(ctx, "project", "bucket"), 6101 ID: "builder", 6102 Config: &pb.BuilderConfig{ 6103 BuildNumbers: pb.Toggle_YES, 6104 Name: "builder", 6105 SwarmingHost: "host", 6106 DescriptionHtml: "test builder description", 6107 }, 6108 }), ShouldBeNil) 6109 req.Mask = &pb.BuildMask{ 6110 Fields: &fieldmaskpb.FieldMask{ 6111 Paths: []string{ 6112 "builder_info", 6113 }, 6114 }, 6115 } 6116 rsp, err := srv.ScheduleBuild(ctx, req) 6117 So(err, ShouldBeNil) 6118 So(rsp.BuilderInfo.Description, ShouldEqual, "test builder description") 6119 }) 6120 }) 6121 }) 6122 }) 6123 6124 Convey("template build ID", func() { 6125 Convey("not found", func() { 6126 req := &pb.ScheduleBuildRequest{ 6127 TemplateBuildId: 1000, 6128 } 6129 rsp, err := srv.ScheduleBuild(ctx, req) 6130 So(err, ShouldErrLike, "not found") 6131 So(rsp, ShouldBeNil) 6132 So(sch.Tasks(), ShouldBeEmpty) 6133 }) 6134 6135 Convey("permission denied", func() { 6136 So(datastore.Put(ctx, &model.Build{ 6137 ID: 1000, 6138 Proto: &pb.Build{ 6139 Id: 1000, 6140 Builder: &pb.BuilderID{ 6141 Project: "project", 6142 Bucket: "bucket", 6143 Builder: "builder", 6144 }, 6145 }, 6146 }), ShouldBeNil) 6147 req := &pb.ScheduleBuildRequest{ 6148 TemplateBuildId: 1, 6149 } 6150 rsp, err := srv.ScheduleBuild(ctx, req) 6151 So(err, ShouldErrLike, "not found") 6152 So(rsp, ShouldBeNil) 6153 So(sch.Tasks(), ShouldBeEmpty) 6154 }) 6155 6156 Convey("not retriable", func() { 6157 ctx = auth.WithState(ctx, &authtest.FakeState{ 6158 Identity: userID, 6159 FakeDB: authtest.NewFakeDB( 6160 authtest.MockPermission(userID, "project:bucket", bbperms.BuildsGet), 6161 authtest.MockPermission(userID, "project:bucket", bbperms.BuildsAdd), 6162 ), 6163 }) 6164 testutil.PutBucket(ctx, "project", "bucket", &pb.Bucket{ 6165 Name: "bucket", 6166 Swarming: &pb.Swarming{}, 6167 }) 6168 So(datastore.Put(ctx, &model.Build{ 6169 ID: 1000, 6170 Proto: &pb.Build{ 6171 Id: 1000, 6172 Builder: &pb.BuilderID{ 6173 Project: "project", 6174 Bucket: "bucket", 6175 Builder: "builder", 6176 }, 6177 Retriable: pb.Trinary_NO, 6178 }, 6179 }), ShouldBeNil) 6180 So(datastore.Put(ctx, &model.Builder{ 6181 Parent: model.BucketKey(ctx, "project", "bucket"), 6182 ID: "builder", 6183 Config: &pb.BuilderConfig{ 6184 BuildNumbers: pb.Toggle_YES, 6185 Name: "builder", 6186 SwarmingHost: "host", 6187 }, 6188 }), ShouldBeNil) 6189 req := &pb.ScheduleBuildRequest{ 6190 TemplateBuildId: 1000, 6191 } 6192 6193 rsp, err := srv.ScheduleBuild(ctx, req) 6194 So(err, ShouldErrLike, "build 1000 is not retriable") 6195 So(rsp, ShouldBeNil) 6196 So(sch.Tasks(), ShouldBeEmpty) 6197 }) 6198 6199 Convey("ok", func() { 6200 ctx = auth.WithState(ctx, &authtest.FakeState{ 6201 Identity: userID, 6202 FakeDB: authtest.NewFakeDB( 6203 authtest.MockPermission(userID, "project:bucket", bbperms.BuildsGet), 6204 authtest.MockPermission(userID, "project:bucket", bbperms.BuildsAdd), 6205 ), 6206 }) 6207 testutil.PutBucket(ctx, "project", "bucket", &pb.Bucket{ 6208 Name: "bucket", 6209 Swarming: &pb.Swarming{}, 6210 }) 6211 So(datastore.Put(ctx, &model.Build{ 6212 ID: 1000, 6213 Proto: &pb.Build{ 6214 Id: 1000, 6215 Builder: &pb.BuilderID{ 6216 Project: "project", 6217 Bucket: "bucket", 6218 Builder: "builder", 6219 }, 6220 }, 6221 }), ShouldBeNil) 6222 req := &pb.ScheduleBuildRequest{ 6223 TemplateBuildId: 1000, 6224 } 6225 6226 Convey("not found", func() { 6227 rsp, err := srv.ScheduleBuild(ctx, req) 6228 So(err, ShouldErrLike, "error fetching builders") 6229 So(rsp, ShouldBeNil) 6230 So(sch.Tasks(), ShouldBeEmpty) 6231 }) 6232 6233 Convey("ok", func() { 6234 So(datastore.Put(ctx, &model.Builder{ 6235 Parent: model.BucketKey(ctx, "project", "bucket"), 6236 ID: "builder", 6237 Config: &pb.BuilderConfig{ 6238 BuildNumbers: pb.Toggle_YES, 6239 Name: "builder", 6240 SwarmingHost: "host", 6241 }, 6242 }), ShouldBeNil) 6243 rsp, err := srv.ScheduleBuild(ctx, req) 6244 So(err, ShouldBeNil) 6245 So(rsp, ShouldResembleProto, &pb.Build{ 6246 Builder: &pb.BuilderID{ 6247 Project: "project", 6248 Bucket: "bucket", 6249 Builder: "builder", 6250 }, 6251 CreatedBy: string(userID), 6252 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6253 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6254 Id: 9021868963221667745, 6255 Input: &pb.Build_Input{}, 6256 Number: 1, 6257 Status: pb.Status_SCHEDULED, 6258 }) 6259 So(sch.Tasks(), ShouldHaveLength, 3) 6260 }) 6261 }) 6262 }) 6263 }) 6264 6265 Convey("scheduleBuilds", t, func() { 6266 srv := &Builds{} 6267 ctx := txndefer.FilterRDS(memory.Use(context.Background())) 6268 ctx = metrics.WithServiceInfo(ctx, "svc", "job", "ins") 6269 ctx = mathrand.Set(ctx, rand.New(rand.NewSource(0))) 6270 ctx, _ = testclock.UseTime(ctx, testclock.TestRecentTimeUTC) 6271 ctx, sch := tq.TestingContext(ctx, nil) 6272 datastore.GetTestable(ctx).AutoIndex(true) 6273 datastore.GetTestable(ctx).Consistent(true) 6274 ctx = auth.WithState(ctx, &authtest.FakeState{ 6275 Identity: userID, 6276 FakeDB: authtest.NewFakeDB( 6277 authtest.MockPermission(userID, "project:bucket", bbperms.BuildsGet), 6278 authtest.MockPermission(userID, "project:bucket", bbperms.BuildsAdd), 6279 ), 6280 }) 6281 globalCfg := &pb.SettingsCfg{ 6282 Resultdb: &pb.ResultDBSettings{ 6283 Hostname: "rdbHost", 6284 }, 6285 Swarming: &pb.SwarmingSettings{ 6286 BbagentPackage: &pb.SwarmingSettings_Package{ 6287 PackageName: "bbagent", 6288 Version: "bbagent-version", 6289 }, 6290 KitchenPackage: &pb.SwarmingSettings_Package{ 6291 PackageName: "kitchen", 6292 Version: "kitchen-version", 6293 }, 6294 }, 6295 } 6296 6297 testutil.PutBucket(ctx, "project", "bucket", &pb.Bucket{ 6298 Name: "bucket", 6299 Swarming: &pb.Swarming{}, 6300 }) 6301 So(datastore.Put(ctx, &model.Build{ 6302 ID: 1000, 6303 Proto: &pb.Build{ 6304 Id: 1000, 6305 Builder: &pb.BuilderID{ 6306 Project: "project", 6307 Bucket: "bucket", 6308 Builder: "builder", 6309 }, 6310 }, 6311 }), ShouldBeNil) 6312 So(datastore.Put(ctx, &model.Builder{ 6313 Parent: model.BucketKey(ctx, "project", "bucket"), 6314 ID: "builder", 6315 Config: &pb.BuilderConfig{ 6316 BuildNumbers: pb.Toggle_YES, 6317 Name: "builder", 6318 SwarmingHost: "host", 6319 }, 6320 }), ShouldBeNil) 6321 6322 Convey("one", func() { 6323 reqs := []*pb.ScheduleBuildRequest{ 6324 { 6325 TemplateBuildId: 1000, 6326 Tags: []*pb.StringPair{ 6327 { 6328 Key: "buildset", 6329 Value: "buildset", 6330 }, 6331 }, 6332 }, 6333 } 6334 6335 rsp, merr := srv.scheduleBuilds(ctx, globalCfg, reqs) 6336 So(merr, ShouldBeNil) 6337 So(rsp, ShouldHaveLength, 1) 6338 So(rsp[0], ShouldResembleProto, &pb.Build{ 6339 Builder: &pb.BuilderID{ 6340 Project: "project", 6341 Bucket: "bucket", 6342 Builder: "builder", 6343 }, 6344 CreatedBy: string(userID), 6345 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6346 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6347 Id: 9021868963221667745, 6348 Input: &pb.Build_Input{}, 6349 Number: 1, 6350 Status: pb.Status_SCHEDULED, 6351 }) 6352 So(sch.Tasks(), ShouldHaveLength, 3) 6353 6354 ind, err := model.SearchTagIndex(ctx, "buildset", "buildset") 6355 So(err, ShouldBeNil) 6356 So(ind, ShouldResemble, []*model.TagIndexEntry{ 6357 { 6358 BuildID: 9021868963221667745, 6359 BucketID: "project/bucket", 6360 CreatedTime: datastore.RoundTime(testclock.TestRecentTimeUTC), 6361 }, 6362 }) 6363 }) 6364 6365 Convey("many", func() { 6366 Convey("one of TemplateBuildId builds not found", func() { 6367 reqs := []*pb.ScheduleBuildRequest{ 6368 { 6369 TemplateBuildId: 1000, 6370 Tags: []*pb.StringPair{ 6371 { 6372 Key: "buildset", 6373 Value: "buildset", 6374 }, 6375 }, 6376 }, 6377 { 6378 TemplateBuildId: 1001, 6379 Tags: []*pb.StringPair{ 6380 { 6381 Key: "buildset", 6382 Value: "buildset", 6383 }, 6384 }, 6385 }, 6386 { 6387 TemplateBuildId: 1000, 6388 Tags: []*pb.StringPair{ 6389 { 6390 Key: "buildset", 6391 Value: "buildset", 6392 }, 6393 }, 6394 }, 6395 } 6396 6397 rsp, err := srv.scheduleBuilds(ctx, globalCfg, reqs) 6398 So(err, ShouldNotBeNil) 6399 So(err[0], ShouldBeNil) 6400 So(err[1], ShouldErrLike, `requested resource not found or "user:caller@example.com" does not have permission to view it`) 6401 So(err[2], ShouldBeNil) 6402 So(rsp, ShouldResembleProto, []*pb.Build{ 6403 { 6404 Id: 9021868963222163313, 6405 Builder: &pb.BuilderID{Project: "project", Bucket: "bucket", Builder: "builder"}, 6406 Number: 1, 6407 CreatedBy: string(userID), 6408 Status: pb.Status_SCHEDULED, 6409 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6410 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6411 Input: &pb.Build_Input{}, 6412 }, 6413 nil, 6414 { 6415 Id: 9021868963222163297, 6416 Builder: &pb.BuilderID{Project: "project", Bucket: "bucket", Builder: "builder"}, 6417 Number: 2, 6418 CreatedBy: string(userID), 6419 Status: pb.Status_SCHEDULED, 6420 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6421 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6422 Input: &pb.Build_Input{}, 6423 }, 6424 }) 6425 }) 6426 6427 Convey("one of builds missing builderCfg", func() { 6428 So(datastore.Put(ctx, &model.Build{ 6429 ID: 1010, 6430 Proto: &pb.Build{ 6431 Id: 1010, 6432 Builder: &pb.BuilderID{ 6433 Project: "project", 6434 Bucket: "bucket", 6435 Builder: "miss_builder_cfg", 6436 }, 6437 }, 6438 }), ShouldBeNil) 6439 6440 reqs := []*pb.ScheduleBuildRequest{ 6441 { 6442 TemplateBuildId: 1000, 6443 Tags: []*pb.StringPair{ 6444 { 6445 Key: "buildset", 6446 Value: "buildset", 6447 }, 6448 }, 6449 }, 6450 { 6451 TemplateBuildId: 1010, 6452 }, 6453 { 6454 TemplateBuildId: 1000, 6455 Tags: []*pb.StringPair{ 6456 { 6457 Key: "buildset", 6458 Value: "buildset", 6459 }, 6460 }, 6461 }, 6462 } 6463 6464 rsp, err := srv.scheduleBuilds(ctx, globalCfg, reqs) 6465 So(err, ShouldNotBeNil) 6466 So(err[0], ShouldBeNil) 6467 So(err[1], ShouldErrLike, `builder not found: "miss_builder_cfg"`) 6468 So(err[2], ShouldBeNil) 6469 So(rsp, ShouldResembleProto, []*pb.Build{ 6470 { 6471 Id: 9021868963222163313, 6472 Builder: &pb.BuilderID{Project: "project", Bucket: "bucket", Builder: "builder"}, 6473 Number: 1, 6474 CreatedBy: string(userID), 6475 Status: pb.Status_SCHEDULED, 6476 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6477 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6478 Input: &pb.Build_Input{}, 6479 }, 6480 nil, 6481 { 6482 Id: 9021868963222163297, 6483 Builder: &pb.BuilderID{Project: "project", Bucket: "bucket", Builder: "builder"}, 6484 Number: 2, 6485 CreatedBy: string(userID), 6486 Status: pb.Status_SCHEDULED, 6487 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6488 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6489 Input: &pb.Build_Input{}, 6490 }, 6491 }) 6492 6493 }) 6494 6495 Convey("one of builds failed in `createBuilds` part", func() { 6496 So(datastore.Put(ctx, &model.Build{ 6497 ID: 1011, 6498 Proto: &pb.Build{ 6499 Id: 1011, 6500 Builder: &pb.BuilderID{ 6501 Project: "project", 6502 Bucket: "bucket", 6503 Builder: "builder_with_rdb", 6504 }, 6505 }, 6506 }), ShouldBeNil) 6507 bqExports := []*rdbPb.BigQueryExport{} 6508 So(datastore.Put(ctx, &model.Builder{ 6509 Parent: model.BucketKey(ctx, "project", "bucket"), 6510 ID: "builder_with_rdb", 6511 Config: &pb.BuilderConfig{ 6512 BuildNumbers: pb.Toggle_YES, 6513 Name: "builder_with_rdb", 6514 SwarmingHost: "host", 6515 Resultdb: &pb.BuilderConfig_ResultDB{ 6516 Enable: true, 6517 BqExports: bqExports, 6518 }, 6519 }, 6520 }), ShouldBeNil) 6521 6522 ctl := gomock.NewController(t) 6523 defer ctl.Finish() 6524 mockRdbClient := rdbPb.NewMockRecorderClient(ctl) 6525 ctx = resultdb.SetMockRecorder(ctx, mockRdbClient) 6526 deadline := testclock.TestRecentTimeUTC.Add(time.Second * 10800).Add(time.Second * 21600) 6527 ctx, _ = testclock.UseTime(ctx, testclock.TestRecentTimeUTC) 6528 mockRdbClient.EXPECT().CreateInvocation(gomock.Any(), luciCmProto.MatcherEqual( 6529 &rdbPb.CreateInvocationRequest{ 6530 InvocationId: "build-9021868963221610321", 6531 Invocation: &rdbPb.Invocation{ 6532 BigqueryExports: bqExports, 6533 ProducerResource: "//app.appspot.com/builds/9021868963221610321", 6534 Realm: "project:bucket", 6535 Deadline: timestamppb.New(deadline), 6536 }, 6537 RequestId: "build-9021868963221610321", 6538 }), gomock.Any()).Return(nil, grpcStatus.Error(codes.Internal, "internal error")) 6539 6540 reqs := []*pb.ScheduleBuildRequest{ 6541 { 6542 TemplateBuildId: 1000, 6543 Tags: []*pb.StringPair{ 6544 { 6545 Key: "buildset", 6546 Value: "buildset", 6547 }, 6548 }, 6549 }, 6550 { 6551 TemplateBuildId: 1011, 6552 Tags: []*pb.StringPair{ 6553 { 6554 Key: "buildset", 6555 Value: "buildset", 6556 }, 6557 }, 6558 }, 6559 { 6560 TemplateBuildId: 1000, 6561 Tags: []*pb.StringPair{ 6562 { 6563 Key: "buildset", 6564 Value: "buildset", 6565 }, 6566 }, 6567 }, 6568 } 6569 6570 rsp, err := srv.scheduleBuilds(ctx, globalCfg, reqs) 6571 So(err, ShouldNotBeNil) 6572 So(err[0], ShouldBeNil) 6573 So(err[1], ShouldErrLike, "failed to create the invocation for build id: 9021868963221610321: rpc error: code = Internal desc = internal error") 6574 So(err[2], ShouldBeNil) 6575 So(rsp, ShouldResembleProto, []*pb.Build{ 6576 { 6577 Id: 9021868963221610337, 6578 Builder: &pb.BuilderID{Project: "project", Bucket: "bucket", Builder: "builder"}, 6579 Number: 1, 6580 CreatedBy: string(userID), 6581 Status: pb.Status_SCHEDULED, 6582 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6583 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6584 Input: &pb.Build_Input{}, 6585 }, 6586 nil, 6587 { 6588 Id: 9021868963221610305, 6589 Builder: &pb.BuilderID{Project: "project", Bucket: "bucket", Builder: "builder"}, 6590 Number: 2, 6591 CreatedBy: string(userID), 6592 Status: pb.Status_SCHEDULED, 6593 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6594 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6595 Input: &pb.Build_Input{}, 6596 }, 6597 }) 6598 }) 6599 6600 Convey("ok", func() { 6601 reqs := []*pb.ScheduleBuildRequest{ 6602 { 6603 TemplateBuildId: 1000, 6604 Tags: []*pb.StringPair{ 6605 { 6606 Key: "buildset", 6607 Value: "buildset", 6608 }, 6609 }, 6610 }, 6611 { 6612 TemplateBuildId: 1000, 6613 Tags: []*pb.StringPair{ 6614 { 6615 Key: "buildset", 6616 Value: "buildset", 6617 }, 6618 }, 6619 }, 6620 } 6621 6622 rsp, merr := srv.scheduleBuilds(ctx, globalCfg, reqs) 6623 So(merr, ShouldBeNil) 6624 So(rsp, ShouldHaveLength, 2) 6625 So(rsp[0], ShouldResembleProto, &pb.Build{ 6626 Builder: &pb.BuilderID{ 6627 Project: "project", 6628 Bucket: "bucket", 6629 Builder: "builder", 6630 }, 6631 CreatedBy: string(userID), 6632 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6633 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6634 Id: 9021868963222163313, 6635 Input: &pb.Build_Input{}, 6636 Number: 1, 6637 Status: pb.Status_SCHEDULED, 6638 }) 6639 So(rsp[1], ShouldResembleProto, &pb.Build{ 6640 Builder: &pb.BuilderID{ 6641 Project: "project", 6642 Bucket: "bucket", 6643 Builder: "builder", 6644 }, 6645 CreatedBy: string(userID), 6646 UpdateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6647 CreateTime: timestamppb.New(testclock.TestRecentTimeUTC), 6648 Id: 9021868963222163297, 6649 Input: &pb.Build_Input{}, 6650 Number: 2, 6651 Status: pb.Status_SCHEDULED, 6652 }) 6653 So(sch.Tasks(), ShouldHaveLength, 6) 6654 6655 ind, err := model.SearchTagIndex(ctx, "buildset", "buildset") 6656 So(err, ShouldBeNil) 6657 So(ind, ShouldResemble, []*model.TagIndexEntry{ 6658 { 6659 BuildID: 9021868963222163313, 6660 BucketID: "project/bucket", 6661 CreatedTime: datastore.RoundTime(testclock.TestRecentTimeUTC), 6662 }, 6663 { 6664 BuildID: 9021868963222163297, 6665 BucketID: "project/bucket", 6666 CreatedTime: datastore.RoundTime(testclock.TestRecentTimeUTC), 6667 }, 6668 }) 6669 }) 6670 }) 6671 6672 Convey("schedule in shadow", func() { 6673 testutil.PutBucket(ctx, "project", "bucket", &pb.Bucket{Swarming: &pb.Swarming{}, Shadow: "bucket.shadow"}) 6674 testutil.PutBucket(ctx, "project", "bucket.shadow", &pb.Bucket{DynamicBuilderTemplate: &pb.Bucket_DynamicBuilderTemplate{}}) 6675 So(datastore.Put(ctx, &model.Builder{ 6676 Parent: model.BucketKey(ctx, "project", "bucket"), 6677 ID: "builder", 6678 Config: &pb.BuilderConfig{ 6679 Name: "builder", 6680 ServiceAccount: "sa@chops-service-accounts.iam.gserviceaccount.com", 6681 Dimensions: []string{"pool:pool1"}, 6682 Properties: `{"a":"b","b":"b"}`, 6683 ShadowBuilderAdjustments: &pb.BuilderConfig_ShadowBuilderAdjustments{ 6684 ServiceAccount: "shadow@chops-service-accounts.iam.gserviceaccount.com", 6685 Pool: "pool2", 6686 Properties: `{"a":"b2","c":"c"}`, 6687 Dimensions: []string{ 6688 "pool:pool2", 6689 }, 6690 }, 6691 }, 6692 }), ShouldBeNil) 6693 Convey("no permission", func() { 6694 ctx = auth.WithState(ctx, &authtest.FakeState{ 6695 Identity: userID, 6696 FakeDB: authtest.NewFakeDB( 6697 authtest.MockPermission(userID, "project:bucket", bbperms.BuildsGet), 6698 authtest.MockPermission(userID, "project:bucket", bbperms.BuildsAdd), 6699 authtest.MockPermission(userID, "project:bucket.shadow", bbperms.BuildersGet), 6700 ), 6701 }) 6702 reqs := []*pb.ScheduleBuildRequest{ 6703 { 6704 Builder: &pb.BuilderID{ 6705 Project: "project", 6706 Bucket: "bucket", 6707 Builder: "builder", 6708 }, 6709 ShadowInput: &pb.ScheduleBuildRequest_ShadowInput{}, 6710 }, 6711 } 6712 _, err := srv.scheduleBuilds(ctx, globalCfg, reqs) 6713 So(err, ShouldErrLike, `does not have permission "buildbucket.builds.add"`) 6714 }) 6715 6716 Convey("one shadow, one original, and one with no shadow bucket", func() { 6717 ctx = auth.WithState(ctx, &authtest.FakeState{ 6718 Identity: userID, 6719 FakeDB: authtest.NewFakeDB( 6720 authtest.MockPermission(userID, "project:bucket", bbperms.BuildsGet), 6721 authtest.MockPermission(userID, "project:bucket", bbperms.BuildsAdd), 6722 authtest.MockPermission(userID, "project:bucket.shadow", bbperms.BuildsGet), 6723 authtest.MockPermission(userID, "project:bucket.shadow", bbperms.BuildsAdd), 6724 ), 6725 }) 6726 reqs := []*pb.ScheduleBuildRequest{ 6727 { 6728 Builder: &pb.BuilderID{ 6729 Project: "project", 6730 Bucket: "bucket", 6731 Builder: "builder", 6732 }, 6733 ShadowInput: &pb.ScheduleBuildRequest_ShadowInput{}, 6734 }, 6735 { 6736 Builder: &pb.BuilderID{ 6737 Project: "project", 6738 Bucket: "bucket", 6739 Builder: "builder", 6740 }, 6741 }, 6742 { 6743 Builder: &pb.BuilderID{ 6744 Project: "project", 6745 Bucket: "bucket.shadow", 6746 Builder: "builder", 6747 }, 6748 ShadowInput: &pb.ScheduleBuildRequest_ShadowInput{}, 6749 }, 6750 } 6751 blds, err := srv.scheduleBuilds(ctx, globalCfg, reqs) 6752 So(err, ShouldNotBeNil) 6753 So(err, ShouldErrLike, "scheduling a shadow build in the original bucket is not allowed") 6754 So(len(blds), ShouldEqual, 3) 6755 So(blds[2], ShouldBeNil) 6756 }) 6757 }) 6758 6759 }) 6760 6761 Convey("structContains", t, func() { 6762 Convey("nil", func() { 6763 So(structContains(nil, nil), ShouldBeTrue) 6764 }) 6765 6766 Convey("nil struct", func() { 6767 path := []string{"path"} 6768 So(structContains(nil, path), ShouldBeFalse) 6769 }) 6770 6771 Convey("nil path", func() { 6772 s := &structpb.Struct{} 6773 So(structContains(s, nil), ShouldBeTrue) 6774 }) 6775 6776 Convey("one component", func() { 6777 s := &structpb.Struct{ 6778 Fields: map[string]*structpb.Value{ 6779 "key": { 6780 Kind: &structpb.Value_StringValue{ 6781 StringValue: "value", 6782 }, 6783 }, 6784 }, 6785 } 6786 path := []string{"key"} 6787 So(structContains(s, path), ShouldBeTrue) 6788 }) 6789 6790 Convey("many components", func() { 6791 s := &structpb.Struct{ 6792 Fields: map[string]*structpb.Value{ 6793 "key1": { 6794 Kind: &structpb.Value_StructValue{ 6795 StructValue: &structpb.Struct{ 6796 Fields: map[string]*structpb.Value{ 6797 "key2": { 6798 Kind: &structpb.Value_StructValue{ 6799 StructValue: &structpb.Struct{ 6800 Fields: map[string]*structpb.Value{ 6801 "key3": { 6802 Kind: &structpb.Value_StringValue{ 6803 StringValue: "value", 6804 }, 6805 }, 6806 }, 6807 }, 6808 }, 6809 }, 6810 }, 6811 }, 6812 }, 6813 }, 6814 }, 6815 } 6816 path := []string{"key1", "key2", "key3"} 6817 So(structContains(s, path), ShouldBeTrue) 6818 }) 6819 6820 Convey("excess component", func() { 6821 s := &structpb.Struct{ 6822 Fields: map[string]*structpb.Value{ 6823 "key1": { 6824 Kind: &structpb.Value_StructValue{ 6825 StructValue: &structpb.Struct{ 6826 Fields: map[string]*structpb.Value{ 6827 "key2": { 6828 Kind: &structpb.Value_StringValue{ 6829 StringValue: "value", 6830 }, 6831 }, 6832 }, 6833 }, 6834 }, 6835 }, 6836 }, 6837 } 6838 path := []string{"key1"} 6839 So(structContains(s, path), ShouldBeTrue) 6840 }) 6841 }) 6842 6843 Convey("validateSchedule", t, func() { 6844 ctx := memory.Use(context.Background()) 6845 ctx = installTestSecret(ctx) 6846 6847 Convey("nil", func() { 6848 err := validateSchedule(ctx, nil, nil, nil) 6849 So(err, ShouldErrLike, "builder or template_build_id is required") 6850 }) 6851 6852 Convey("empty", func() { 6853 req := &pb.ScheduleBuildRequest{} 6854 err := validateSchedule(ctx, req, nil, nil) 6855 So(err, ShouldErrLike, "builder or template_build_id is required") 6856 }) 6857 6858 Convey("request ID", func() { 6859 req := &pb.ScheduleBuildRequest{ 6860 RequestId: "request/id", 6861 TemplateBuildId: 1, 6862 } 6863 err := validateSchedule(ctx, req, nil, nil) 6864 So(err, ShouldErrLike, "request_id cannot contain") 6865 }) 6866 6867 Convey("builder ID", func() { 6868 req := &pb.ScheduleBuildRequest{ 6869 Builder: &pb.BuilderID{}, 6870 } 6871 err := validateSchedule(ctx, req, nil, nil) 6872 So(err, ShouldErrLike, "project must match") 6873 }) 6874 6875 Convey("dimensions", func() { 6876 Convey("empty", func() { 6877 req := &pb.ScheduleBuildRequest{ 6878 Dimensions: []*pb.RequestedDimension{ 6879 {}, 6880 }, 6881 TemplateBuildId: 1, 6882 } 6883 err := validateSchedule(ctx, req, nil, nil) 6884 So(err, ShouldErrLike, "dimensions") 6885 }) 6886 6887 Convey("expiration", func() { 6888 Convey("empty", func() { 6889 req := &pb.ScheduleBuildRequest{ 6890 Dimensions: []*pb.RequestedDimension{ 6891 { 6892 Expiration: &durationpb.Duration{}, 6893 Key: "key", 6894 Value: "value", 6895 }, 6896 }, 6897 TemplateBuildId: 1, 6898 } 6899 err := validateSchedule(ctx, req, nil, nil) 6900 So(err, ShouldBeNil) 6901 }) 6902 6903 Convey("nanos", func() { 6904 req := &pb.ScheduleBuildRequest{ 6905 Dimensions: []*pb.RequestedDimension{ 6906 { 6907 Expiration: &durationpb.Duration{ 6908 Nanos: 1, 6909 }, 6910 Key: "key", 6911 Value: "value", 6912 }, 6913 }, 6914 TemplateBuildId: 1, 6915 } 6916 err := validateSchedule(ctx, req, nil, nil) 6917 So(err, ShouldErrLike, "nanos must not be specified") 6918 }) 6919 6920 Convey("seconds", func() { 6921 Convey("negative", func() { 6922 req := &pb.ScheduleBuildRequest{ 6923 Dimensions: []*pb.RequestedDimension{ 6924 { 6925 Expiration: &durationpb.Duration{ 6926 Seconds: -60, 6927 }, 6928 Key: "key", 6929 Value: "value", 6930 }, 6931 }, 6932 TemplateBuildId: 1, 6933 } 6934 err := validateSchedule(ctx, req, nil, nil) 6935 So(err, ShouldErrLike, "seconds must not be negative") 6936 }) 6937 6938 Convey("whole minute", func() { 6939 req := &pb.ScheduleBuildRequest{ 6940 Dimensions: []*pb.RequestedDimension{ 6941 { 6942 Expiration: &durationpb.Duration{ 6943 Seconds: 1, 6944 }, 6945 Key: "key", 6946 Value: "value", 6947 }, 6948 }, 6949 TemplateBuildId: 1, 6950 } 6951 err := validateSchedule(ctx, req, nil, nil) 6952 So(err, ShouldErrLike, "seconds must be a multiple of 60") 6953 }) 6954 }) 6955 6956 Convey("ok", func() { 6957 req := &pb.ScheduleBuildRequest{ 6958 Dimensions: []*pb.RequestedDimension{ 6959 { 6960 Expiration: &durationpb.Duration{ 6961 Seconds: 60, 6962 }, 6963 Key: "key", 6964 Value: "value", 6965 }, 6966 }, 6967 TemplateBuildId: 1, 6968 } 6969 err := validateSchedule(ctx, req, nil, nil) 6970 So(err, ShouldBeNil) 6971 }) 6972 }) 6973 6974 Convey("key", func() { 6975 Convey("empty", func() { 6976 req := &pb.ScheduleBuildRequest{ 6977 Dimensions: []*pb.RequestedDimension{ 6978 { 6979 Value: "value", 6980 }, 6981 }, 6982 TemplateBuildId: 1, 6983 } 6984 err := validateSchedule(ctx, req, nil, nil) 6985 So(err, ShouldErrLike, "key must be specified") 6986 }) 6987 6988 Convey("caches", func() { 6989 req := &pb.ScheduleBuildRequest{ 6990 Dimensions: []*pb.RequestedDimension{ 6991 { 6992 Key: "caches", 6993 Value: "value", 6994 }, 6995 }, 6996 TemplateBuildId: 1, 6997 } 6998 err := validateSchedule(ctx, req, nil, nil) 6999 So(err, ShouldErrLike, "caches may only be specified in builder configs") 7000 }) 7001 7002 Convey("pool", func() { 7003 req := &pb.ScheduleBuildRequest{ 7004 Dimensions: []*pb.RequestedDimension{ 7005 { 7006 Key: "pool", 7007 Value: "value", 7008 }, 7009 }, 7010 TemplateBuildId: 1, 7011 } 7012 err := validateSchedule(ctx, req, nil, nil) 7013 So(err, ShouldErrLike, "pool may only be specified in builder configs") 7014 }) 7015 7016 Convey("ok", func() { 7017 req := &pb.ScheduleBuildRequest{ 7018 Dimensions: []*pb.RequestedDimension{ 7019 { 7020 Key: "key", 7021 Value: "value", 7022 }, 7023 { 7024 Key: "key1", 7025 }, 7026 }, 7027 TemplateBuildId: 1, 7028 } 7029 err := validateSchedule(ctx, req, nil, nil) 7030 So(err, ShouldBeNil) 7031 }) 7032 }) 7033 7034 Convey("parent", func() { 7035 Convey("missing parent", func() { 7036 req := &pb.ScheduleBuildRequest{ 7037 Dimensions: []*pb.RequestedDimension{ 7038 { 7039 Key: "key", 7040 Value: "value", 7041 }, 7042 }, 7043 TemplateBuildId: 1, 7044 CanOutliveParent: pb.Trinary_NO, 7045 } 7046 err := validateSchedule(ctx, req, nil, nil) 7047 So(err, ShouldErrLike, "can_outlive_parent is specified without parent build token") 7048 }) 7049 7050 Convey("schedule no parent build", func() { 7051 req := &pb.ScheduleBuildRequest{ 7052 Dimensions: []*pb.RequestedDimension{ 7053 { 7054 Key: "key", 7055 Value: "value", 7056 }, 7057 }, 7058 TemplateBuildId: 1, 7059 CanOutliveParent: pb.Trinary_UNSET, 7060 } 7061 err := validateSchedule(ctx, req, nil, nil) 7062 So(err, ShouldBeNil) 7063 }) 7064 7065 tk, err := buildtoken.GenerateToken(ctx, 1, pb.TokenBody_BUILD) 7066 So(err, ShouldBeNil) 7067 Convey("ended parent", func() { 7068 testutil.PutBucket(ctx, "project", "bucket", nil) 7069 7070 So(datastore.Put(ctx, &model.Build{ 7071 Proto: &pb.Build{ 7072 Id: 1, 7073 Builder: &pb.BuilderID{ 7074 Project: "project", 7075 Bucket: "bucket", 7076 Builder: "builder", 7077 }, 7078 Status: pb.Status_SUCCESS, 7079 }, 7080 UpdateToken: tk, 7081 }), ShouldBeNil) 7082 7083 ctx := metadata.NewIncomingContext(ctx, metadata.Pairs(bb.BuildbucketTokenHeader, tk)) 7084 _, err := validateParent(ctx) 7085 So(err, ShouldErrLike, "1 has ended, cannot add child to it") 7086 }) 7087 7088 Convey("OK", func() { 7089 testutil.PutBucket(ctx, "project", "bucket", nil) 7090 So(datastore.Put(ctx, &model.Build{ 7091 Proto: &pb.Build{ 7092 Id: 1, 7093 Builder: &pb.BuilderID{ 7094 Project: "project", 7095 Bucket: "bucket", 7096 Builder: "builder", 7097 }, 7098 Status: pb.Status_STARTED, 7099 }, 7100 UpdateToken: tk, 7101 }), ShouldBeNil) 7102 7103 ctx := metadata.NewIncomingContext(ctx, metadata.Pairs(bb.BuildbucketTokenHeader, tk)) 7104 b, err := validateParent(ctx) 7105 So(err, ShouldBeNil) 7106 So(b.Proto.Id, ShouldEqual, 1) 7107 }) 7108 }) 7109 7110 Convey("ok", func() { 7111 req := &pb.ScheduleBuildRequest{ 7112 Dimensions: []*pb.RequestedDimension{ 7113 { 7114 Key: "key", 7115 Value: "value", 7116 }, 7117 }, 7118 TemplateBuildId: 1, 7119 } 7120 err := validateSchedule(ctx, req, nil, nil) 7121 So(err, ShouldBeNil) 7122 }) 7123 7124 Convey("empty value & non-value", func() { 7125 req := &pb.ScheduleBuildRequest{ 7126 Dimensions: []*pb.RequestedDimension{ 7127 { 7128 Key: "req_key", 7129 Value: "value", 7130 }, 7131 { 7132 Key: "req_key", 7133 }, 7134 }, 7135 TemplateBuildId: 1, 7136 } 7137 err := validateSchedule(ctx, req, nil, nil) 7138 So(err, ShouldErrLike, `dimensions: contain both empty and non-empty value for the same key - "req_key"`) 7139 }) 7140 }) 7141 7142 Convey("exe", func() { 7143 Convey("empty", func() { 7144 req := &pb.ScheduleBuildRequest{ 7145 Exe: &pb.Executable{}, 7146 TemplateBuildId: 1, 7147 } 7148 err := validateSchedule(ctx, req, nil, nil) 7149 So(err, ShouldBeNil) 7150 }) 7151 7152 Convey("package", func() { 7153 req := &pb.ScheduleBuildRequest{ 7154 Exe: &pb.Executable{ 7155 CipdPackage: "package", 7156 }, 7157 TemplateBuildId: 1, 7158 } 7159 err := validateSchedule(ctx, req, nil, nil) 7160 So(err, ShouldErrLike, "cipd_package must not be specified") 7161 }) 7162 7163 Convey("version", func() { 7164 Convey("invalid", func() { 7165 req := &pb.ScheduleBuildRequest{ 7166 Exe: &pb.Executable{ 7167 CipdVersion: "invalid!", 7168 }, 7169 TemplateBuildId: 1, 7170 } 7171 err := validateSchedule(ctx, req, nil, nil) 7172 So(err, ShouldErrLike, "cipd_version") 7173 }) 7174 7175 Convey("valid", func() { 7176 req := &pb.ScheduleBuildRequest{ 7177 Exe: &pb.Executable{ 7178 CipdVersion: "valid", 7179 }, 7180 TemplateBuildId: 1, 7181 } 7182 err := validateSchedule(ctx, req, nil, nil) 7183 So(err, ShouldBeNil) 7184 }) 7185 }) 7186 }) 7187 7188 Convey("gerrit changes", func() { 7189 Convey("empty", func() { 7190 req := &pb.ScheduleBuildRequest{ 7191 GerritChanges: []*pb.GerritChange{}, 7192 TemplateBuildId: 1, 7193 } 7194 err := validateSchedule(ctx, req, nil, nil) 7195 So(err, ShouldBeNil) 7196 }) 7197 7198 Convey("unspecified", func() { 7199 req := &pb.ScheduleBuildRequest{ 7200 GerritChanges: []*pb.GerritChange{ 7201 {}, 7202 }, 7203 TemplateBuildId: 1, 7204 } 7205 err := validateSchedule(ctx, req, nil, nil) 7206 So(err, ShouldErrLike, "gerrit_changes") 7207 }) 7208 7209 Convey("change", func() { 7210 req := &pb.ScheduleBuildRequest{ 7211 GerritChanges: []*pb.GerritChange{ 7212 { 7213 Host: "host", 7214 Patchset: 1, 7215 Project: "project", 7216 }, 7217 }, 7218 TemplateBuildId: 1, 7219 } 7220 err := validateSchedule(ctx, req, nil, nil) 7221 So(err, ShouldErrLike, "change must be specified") 7222 }) 7223 7224 Convey("host", func() { 7225 Convey("not specified", func() { 7226 req := &pb.ScheduleBuildRequest{ 7227 GerritChanges: []*pb.GerritChange{ 7228 { 7229 Change: 1, 7230 Patchset: 1, 7231 Project: "project", 7232 }, 7233 }, 7234 TemplateBuildId: 1, 7235 } 7236 err := validateSchedule(ctx, req, nil, nil) 7237 So(err, ShouldErrLike, "host must be specified") 7238 }) 7239 Convey("invalid", func() { 7240 req := &pb.ScheduleBuildRequest{ 7241 GerritChanges: []*pb.GerritChange{ 7242 { 7243 Change: 1, 7244 Host: "https://somehost", // host should not include the protocol. 7245 Patchset: 1, 7246 Project: "project", 7247 }, 7248 }, 7249 TemplateBuildId: 1, 7250 } 7251 err := validateSchedule(ctx, req, nil, nil) 7252 So(err, ShouldErrLike, "host does not match pattern") 7253 }) 7254 Convey("too long", func() { 7255 req := &pb.ScheduleBuildRequest{ 7256 GerritChanges: []*pb.GerritChange{ 7257 { 7258 Change: 1, 7259 Host: strings.Repeat("h", 256), 7260 Patchset: 1, 7261 Project: "project", 7262 }, 7263 }, 7264 TemplateBuildId: 1, 7265 } 7266 err := validateSchedule(ctx, req, nil, nil) 7267 So(err, ShouldErrLike, "host must not exceed 255 characters") 7268 }) 7269 }) 7270 7271 Convey("patchset", func() { 7272 req := &pb.ScheduleBuildRequest{ 7273 GerritChanges: []*pb.GerritChange{ 7274 { 7275 Change: 1, 7276 Host: "host", 7277 Project: "project", 7278 }, 7279 }, 7280 TemplateBuildId: 1, 7281 } 7282 err := validateSchedule(ctx, req, nil, nil) 7283 So(err, ShouldErrLike, "patchset must be specified") 7284 }) 7285 7286 Convey("project", func() { 7287 req := &pb.ScheduleBuildRequest{ 7288 GerritChanges: []*pb.GerritChange{ 7289 { 7290 Change: 1, 7291 Host: "host", 7292 Patchset: 1, 7293 }, 7294 }, 7295 TemplateBuildId: 1, 7296 } 7297 err := validateSchedule(ctx, req, nil, nil) 7298 So(err, ShouldErrLike, "project must be specified") 7299 }) 7300 7301 Convey("ok", func() { 7302 req := &pb.ScheduleBuildRequest{ 7303 GerritChanges: []*pb.GerritChange{ 7304 { 7305 Change: 1, 7306 Host: "host", 7307 Patchset: 1, 7308 Project: "project", 7309 }, 7310 }, 7311 TemplateBuildId: 1, 7312 } 7313 err := validateSchedule(ctx, req, nil, nil) 7314 So(err, ShouldBeNil) 7315 }) 7316 }) 7317 7318 Convey("gitiles commit", func() { 7319 req := &pb.ScheduleBuildRequest{ 7320 GitilesCommit: &pb.GitilesCommit{ 7321 Host: "example.com", 7322 }, 7323 TemplateBuildId: 1, 7324 } 7325 err := validateSchedule(ctx, req, nil, nil) 7326 So(err, ShouldErrLike, "gitiles_commit") 7327 }) 7328 7329 Convey("notify", func() { 7330 ctx, psserver, psclient, err := clients.SetupTestPubsub(ctx, "project") 7331 So(err, ShouldBeNil) 7332 defer func() { 7333 psclient.Close() 7334 psserver.Close() 7335 }() 7336 tpc, err := psclient.CreateTopic(ctx, "topic") 7337 tpc.IAM() 7338 So(err, ShouldBeNil) 7339 ctx = cachingtest.WithGlobalCache(ctx, map[string]caching.BlobCache{ 7340 "has_perm_on_pubsub_callback_topic": cachingtest.NewBlobCache(), 7341 }) 7342 Convey("empty", func() { 7343 req := &pb.ScheduleBuildRequest{ 7344 Notify: &pb.NotificationConfig{}, 7345 TemplateBuildId: 1, 7346 } 7347 err := validateSchedule(ctx, req, nil, nil) 7348 So(err, ShouldErrLike, "notify") 7349 }) 7350 7351 Convey("pubsub topic", func() { 7352 req := &pb.ScheduleBuildRequest{ 7353 Notify: &pb.NotificationConfig{ 7354 UserData: []byte("user data"), 7355 }, 7356 TemplateBuildId: 1, 7357 } 7358 err := validateSchedule(ctx, req, nil, nil) 7359 So(err, ShouldErrLike, "pubsub_topic") 7360 }) 7361 7362 Convey("user data", func() { 7363 req := &pb.ScheduleBuildRequest{ 7364 Notify: &pb.NotificationConfig{ 7365 PubsubTopic: "projects/project/topics/topic", 7366 UserData: make([]byte, 4097), 7367 }, 7368 TemplateBuildId: 1, 7369 } 7370 err := validateSchedule(ctx, req, nil, nil) 7371 So(err, ShouldErrLike, "user_data") 7372 }) 7373 7374 Convey("ok - pubsub topic perm cached", func() { 7375 cache := caching.GlobalCache(ctx, "has_perm_on_pubsub_callback_topic") 7376 err := cache.Set(ctx, "projects/project/topics/topic", []byte{1}, 10*time.Hour) 7377 So(err, ShouldBeNil) 7378 req := &pb.ScheduleBuildRequest{ 7379 Notify: &pb.NotificationConfig{ 7380 PubsubTopic: "projects/project/topics/topic", 7381 UserData: []byte("user data"), 7382 }, 7383 TemplateBuildId: 1, 7384 } 7385 err = validateSchedule(ctx, req, nil, nil) 7386 So(err, ShouldBeNil) 7387 }) 7388 7389 Convey("ok - pubsub topic perm not cached", func() { 7390 req := &pb.ScheduleBuildRequest{ 7391 Notify: &pb.NotificationConfig{ 7392 PubsubTopic: "projects/project/topics/topic", 7393 UserData: []byte("user data"), 7394 }, 7395 TemplateBuildId: 1, 7396 } 7397 err := validateSchedule(ctx, req, nil, nil) 7398 // "cloud.google.com/go/pubsub/pstest" lib doesn't expose a way to mock 7399 // IAM policy check. Therefore, only check if our `validateSchedule` 7400 // tries to call `topic.IAM().TestPermissions()` and get the expected 7401 // `Unimplemented` err msg. 7402 So(err, ShouldErrLike, "Unimplemented desc = unknown service google.iam.v1.IAMPolicy") 7403 // The bad result should not be cached. 7404 cache := caching.GlobalCache(ctx, "has_perm_on_pubsub_callback_topic") 7405 _, err = cache.Get(ctx, "projects/project/topics/topic") 7406 So(err, ShouldErrLike, caching.ErrCacheMiss) 7407 }) 7408 }) 7409 7410 Convey("priority", func() { 7411 Convey("negative", func() { 7412 req := &pb.ScheduleBuildRequest{ 7413 Priority: -1, 7414 TemplateBuildId: 1, 7415 } 7416 err := validateSchedule(ctx, req, nil, nil) 7417 So(err, ShouldErrLike, "priority must be in") 7418 }) 7419 7420 Convey("excessive", func() { 7421 req := &pb.ScheduleBuildRequest{ 7422 Priority: 256, 7423 TemplateBuildId: 1, 7424 } 7425 err := validateSchedule(ctx, req, nil, nil) 7426 So(err, ShouldErrLike, "priority must be in") 7427 }) 7428 }) 7429 7430 Convey("properties", func() { 7431 Convey("prohibited", func() { 7432 req := &pb.ScheduleBuildRequest{ 7433 Properties: &structpb.Struct{ 7434 Fields: map[string]*structpb.Value{ 7435 "buildbucket": { 7436 Kind: &structpb.Value_StringValue{}, 7437 }, 7438 }, 7439 }, 7440 TemplateBuildId: 1, 7441 } 7442 err := validateSchedule(ctx, req, nil, nil) 7443 So(err, ShouldErrLike, "must not be specified") 7444 }) 7445 7446 Convey("ok", func() { 7447 req := &pb.ScheduleBuildRequest{ 7448 Properties: &structpb.Struct{ 7449 Fields: map[string]*structpb.Value{ 7450 "key": { 7451 Kind: &structpb.Value_StringValue{}, 7452 }, 7453 }, 7454 }, 7455 TemplateBuildId: 1, 7456 } 7457 err := validateSchedule(ctx, req, nil, nil) 7458 So(err, ShouldBeNil) 7459 }) 7460 }) 7461 7462 Convey("tags", func() { 7463 req := &pb.ScheduleBuildRequest{ 7464 Tags: []*pb.StringPair{ 7465 { 7466 Key: "key:value", 7467 }, 7468 }, 7469 TemplateBuildId: 1, 7470 } 7471 err := validateSchedule(ctx, req, nil, nil) 7472 So(err, ShouldErrLike, "tags") 7473 }) 7474 7475 Convey("experiments", func() { 7476 Convey("ok", func() { 7477 req := &pb.ScheduleBuildRequest{ 7478 TemplateBuildId: 1, 7479 Experiments: map[string]bool{ 7480 bb.ExperimentBBAgent: true, 7481 "cool.experiment_thing": true, 7482 }, 7483 } 7484 So(validateSchedule(ctx, req, stringset.NewFromSlice(bb.ExperimentBBAgent), nil), ShouldBeNil) 7485 }) 7486 7487 Convey("bad name", func() { 7488 req := &pb.ScheduleBuildRequest{ 7489 TemplateBuildId: 1, 7490 Experiments: map[string]bool{ 7491 "bad name": true, 7492 }, 7493 } 7494 So(validateSchedule(ctx, req, nil, nil), ShouldErrLike, "does not match") 7495 }) 7496 7497 Convey("bad reserved", func() { 7498 req := &pb.ScheduleBuildRequest{ 7499 TemplateBuildId: 1, 7500 Experiments: map[string]bool{ 7501 "luci.use_ralms": true, 7502 }, 7503 } 7504 So(validateSchedule(ctx, req, nil, nil), ShouldErrLike, "unknown experiment has reserved prefix") 7505 }) 7506 }) 7507 }) 7508 7509 Convey("setInfraAgent", t, func() { 7510 Convey("bbagent+userpackages", func() { 7511 b := &pb.Build{ 7512 Builder: &pb.BuilderID{ 7513 Project: "project", 7514 Bucket: "bucket", 7515 Builder: "builder", 7516 }, 7517 Canary: true, 7518 Exe: &pb.Executable{ 7519 CipdPackage: "exe", 7520 CipdVersion: "exe-version", 7521 }, 7522 Infra: &pb.BuildInfra{ 7523 Buildbucket: &pb.BuildInfra_Buildbucket{ 7524 Hostname: "app.appspot.com", 7525 }, 7526 }, 7527 Input: &pb.Build_Input{ 7528 Experiments: []string{"omit", "include"}, 7529 }, 7530 } 7531 cfg := &pb.SettingsCfg{ 7532 Swarming: &pb.SwarmingSettings{ 7533 BbagentPackage: &pb.SwarmingSettings_Package{ 7534 PackageName: "infra/tools/luci/bbagent/${platform}", 7535 Version: "version", 7536 VersionCanary: "canary-version", 7537 }, 7538 UserPackages: []*pb.SwarmingSettings_Package{ 7539 { 7540 PackageName: "include", 7541 Version: "version", 7542 VersionCanary: "canary-version", 7543 }, 7544 { 7545 Builders: &pb.BuilderPredicate{ 7546 RegexExclude: []string{ 7547 ".*", 7548 }, 7549 }, 7550 PackageName: "exclude", 7551 Version: "version", 7552 VersionCanary: "canary-version", 7553 }, 7554 { 7555 Builders: &pb.BuilderPredicate{ 7556 Regex: []string{ 7557 ".*", 7558 }, 7559 }, 7560 PackageName: "subdir", 7561 Subdir: "subdir", 7562 Version: "version", 7563 VersionCanary: "canary-version", 7564 }, 7565 { 7566 PackageName: "include_experiment", 7567 Version: "version", 7568 IncludeOnExperiment: []string{"include"}, 7569 }, 7570 { 7571 PackageName: "not_include_experiment", 7572 Version: "version", 7573 IncludeOnExperiment: []string{"not_include"}, 7574 }, 7575 { 7576 PackageName: "omit_experiment", 7577 Version: "version", 7578 OmitOnExperiment: []string{"omit"}, 7579 }, 7580 }, 7581 }, 7582 Cipd: &pb.CipdSettings{ 7583 Server: "cipd server", 7584 Source: &pb.CipdSettings_Source{ 7585 PackageName: "the/offical/cipd/package/${platform}", 7586 Version: "1", 7587 VersionCanary: "1canary", 7588 }, 7589 }, 7590 } 7591 err := setInfraAgent(b, cfg) 7592 So(err, ShouldBeNil) 7593 So(b.Infra.Buildbucket.Agent, ShouldResembleProto, &pb.BuildInfra_Buildbucket_Agent{ 7594 Source: &pb.BuildInfra_Buildbucket_Agent_Source{ 7595 DataType: &pb.BuildInfra_Buildbucket_Agent_Source_Cipd{ 7596 Cipd: &pb.BuildInfra_Buildbucket_Agent_Source_CIPD{ 7597 Package: "infra/tools/luci/bbagent/${platform}", 7598 Version: "canary-version", 7599 Server: "cipd server", 7600 }, 7601 }, 7602 }, 7603 Input: &pb.BuildInfra_Buildbucket_Agent_Input{ 7604 CipdSource: map[string]*pb.InputDataRef{ 7605 "cipd": &pb.InputDataRef{ 7606 DataType: &pb.InputDataRef_Cipd{ 7607 Cipd: &pb.InputDataRef_CIPD{ 7608 Server: "cipd server", 7609 Specs: []*pb.InputDataRef_CIPD_PkgSpec{ 7610 { 7611 Package: "the/offical/cipd/package/${platform}", 7612 Version: "1canary", 7613 }, 7614 }, 7615 }, 7616 }, 7617 OnPath: []string{"cipd", "cipd/bin"}, 7618 }, 7619 }, 7620 Data: map[string]*pb.InputDataRef{ 7621 "cipd_bin_packages": { 7622 DataType: &pb.InputDataRef_Cipd{ 7623 Cipd: &pb.InputDataRef_CIPD{ 7624 Server: "cipd server", 7625 Specs: []*pb.InputDataRef_CIPD_PkgSpec{ 7626 {Package: "include", Version: "canary-version"}, 7627 {Package: "include_experiment", Version: "version"}, 7628 }, 7629 }, 7630 }, 7631 OnPath: []string{"cipd_bin_packages", "cipd_bin_packages/bin"}, 7632 }, 7633 "cipd_bin_packages/subdir": { 7634 DataType: &pb.InputDataRef_Cipd{ 7635 Cipd: &pb.InputDataRef_CIPD{ 7636 Server: "cipd server", 7637 Specs: []*pb.InputDataRef_CIPD_PkgSpec{ 7638 {Package: "subdir", Version: "canary-version"}, 7639 }, 7640 }, 7641 }, 7642 OnPath: []string{"cipd_bin_packages/subdir", "cipd_bin_packages/subdir/bin"}, 7643 }, 7644 "kitchen-checkout": { 7645 DataType: &pb.InputDataRef_Cipd{ 7646 Cipd: &pb.InputDataRef_CIPD{ 7647 Server: "cipd server", 7648 Specs: []*pb.InputDataRef_CIPD_PkgSpec{ 7649 {Package: "exe", Version: "exe-version"}, 7650 }, 7651 }, 7652 }, 7653 }, 7654 }, 7655 }, 7656 Purposes: map[string]pb.BuildInfra_Buildbucket_Agent_Purpose{ 7657 "kitchen-checkout": pb.BuildInfra_Buildbucket_Agent_PURPOSE_EXE_PAYLOAD, 7658 }, 7659 CipdClientCache: &pb.CacheEntry{ 7660 Name: "cipd_client_c3bb9331ecf2d9dfe25df9012569bcc1278974c87ea33a56b2f4aa2761078578", 7661 Path: "cipd_client", 7662 }, 7663 CipdPackagesCache: &pb.CacheEntry{ 7664 Name: "cipd_cache_e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", 7665 Path: "cipd_cache", 7666 }, 7667 }) 7668 }) 7669 7670 Convey("bad bbagent cfg", func() { 7671 b := &pb.Build{ 7672 Builder: &pb.BuilderID{ 7673 Project: "project", 7674 Bucket: "bucket", 7675 Builder: "builder", 7676 }, 7677 Exe: &pb.Executable{ 7678 CipdPackage: "exe", 7679 CipdVersion: "exe-version", 7680 }, 7681 Infra: &pb.BuildInfra{ 7682 Buildbucket: &pb.BuildInfra_Buildbucket{ 7683 Hostname: "app.appspot.com", 7684 }, 7685 }, 7686 } 7687 cfg := &pb.SettingsCfg{ 7688 Swarming: &pb.SwarmingSettings{ 7689 BbagentPackage: &pb.SwarmingSettings_Package{ 7690 PackageName: "infra/tools/luci/bbagent/${bad}", 7691 Version: "bbagent-version", 7692 }, 7693 }, 7694 Cipd: &pb.CipdSettings{ 7695 Server: "cipd server", 7696 Source: &pb.CipdSettings_Source{ 7697 PackageName: "the/offical/cipd/package/${platform}", 7698 Version: "1", 7699 }, 7700 }, 7701 } 7702 err := setInfraAgent(b, cfg) 7703 So(err, ShouldErrLike, "bad settings: bbagent package name must end with '/${platform}'") 7704 So(b.Infra.Buildbucket.Agent.Source, ShouldBeNil) 7705 }) 7706 7707 Convey("empty settings", func() { 7708 b := &pb.Build{ 7709 Builder: &pb.BuilderID{ 7710 Project: "project", 7711 Bucket: "bucket", 7712 Builder: "builder", 7713 }, 7714 Infra: &pb.BuildInfra{ 7715 Buildbucket: &pb.BuildInfra_Buildbucket{ 7716 Hostname: "app.appspot.com", 7717 }, 7718 }, 7719 } 7720 err := setInfraAgent(b, &pb.SettingsCfg{}) 7721 So(err, ShouldBeNil) 7722 So(b.Infra.Buildbucket.Agent.Source, ShouldBeNil) 7723 So(b.Infra.Buildbucket.Agent.Input.Data, ShouldBeEmpty) 7724 }) 7725 7726 Convey("bbagent alternative", func() { 7727 b := &pb.Build{ 7728 Builder: &pb.BuilderID{ 7729 Project: "project", 7730 Bucket: "bucket", 7731 Builder: "builder", 7732 }, 7733 Canary: true, 7734 Infra: &pb.BuildInfra{ 7735 Buildbucket: &pb.BuildInfra_Buildbucket{ 7736 Hostname: "app.appspot.com", 7737 }, 7738 }, 7739 Input: &pb.Build_Input{ 7740 Experiments: []string{"omit", "include"}, 7741 }, 7742 } 7743 Convey("cannot decide bbagent", func() { 7744 cfg := &pb.SettingsCfg{ 7745 Swarming: &pb.SwarmingSettings{ 7746 BbagentPackage: &pb.SwarmingSettings_Package{ 7747 PackageName: "infra/tools/luci/bbagent/${platform}", 7748 Version: "version", 7749 VersionCanary: "canary-version", 7750 }, 7751 AlternativeAgentPackages: []*pb.SwarmingSettings_Package{ 7752 { 7753 PackageName: "bbagent_alternative/${platform}", 7754 Version: "version", 7755 IncludeOnExperiment: []string{"include"}, 7756 }, 7757 { 7758 PackageName: "bbagent_alternative_2/${platform}", 7759 Version: "version", 7760 IncludeOnExperiment: []string{"include"}, 7761 }, 7762 }, 7763 }, 7764 Cipd: &pb.CipdSettings{ 7765 Server: "cipd server", 7766 }, 7767 } 7768 err := setInfraAgent(b, cfg) 7769 So(err, ShouldErrLike, "cannot decide buildbucket agent source") 7770 }) 7771 Convey("pass", func() { 7772 cfg := &pb.SettingsCfg{ 7773 Swarming: &pb.SwarmingSettings{ 7774 BbagentPackage: &pb.SwarmingSettings_Package{ 7775 PackageName: "infra/tools/luci/bbagent/${platform}", 7776 Version: "version", 7777 VersionCanary: "canary-version", 7778 }, 7779 AlternativeAgentPackages: []*pb.SwarmingSettings_Package{ 7780 { 7781 PackageName: "bbagent_alternative/${platform}", 7782 Version: "version", 7783 IncludeOnExperiment: []string{"include"}, 7784 }, 7785 }, 7786 }, 7787 Cipd: &pb.CipdSettings{ 7788 Server: "cipd server", 7789 Source: &pb.CipdSettings_Source{ 7790 PackageName: "the/offical/cipd/package/${platform}", 7791 Version: "1", 7792 }, 7793 }, 7794 } 7795 err := setInfraAgent(b, cfg) 7796 So(err, ShouldBeNil) 7797 So(b.Infra.Buildbucket.Agent, ShouldResembleProto, &pb.BuildInfra_Buildbucket_Agent{ 7798 Source: &pb.BuildInfra_Buildbucket_Agent_Source{ 7799 DataType: &pb.BuildInfra_Buildbucket_Agent_Source_Cipd{ 7800 Cipd: &pb.BuildInfra_Buildbucket_Agent_Source_CIPD{ 7801 Package: "bbagent_alternative/${platform}", 7802 Version: "version", 7803 Server: "cipd server", 7804 }, 7805 }, 7806 }, 7807 Input: &pb.BuildInfra_Buildbucket_Agent_Input{ 7808 CipdSource: map[string]*pb.InputDataRef{ 7809 "cipd": &pb.InputDataRef{ 7810 DataType: &pb.InputDataRef_Cipd{ 7811 Cipd: &pb.InputDataRef_CIPD{ 7812 Server: "cipd server", 7813 Specs: []*pb.InputDataRef_CIPD_PkgSpec{ 7814 { 7815 Package: "the/offical/cipd/package/${platform}", 7816 Version: "1", 7817 }, 7818 }, 7819 }, 7820 }, 7821 OnPath: []string{"cipd", "cipd/bin"}, 7822 }, 7823 }, 7824 }, 7825 Purposes: map[string]pb.BuildInfra_Buildbucket_Agent_Purpose{ 7826 "kitchen-checkout": pb.BuildInfra_Buildbucket_Agent_PURPOSE_EXE_PAYLOAD, 7827 }, 7828 CipdClientCache: &pb.CacheEntry{ 7829 Name: "cipd_client_6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b", 7830 Path: "cipd_client", 7831 }, 7832 }) 7833 }) 7834 }) 7835 7836 Convey("bbagent_utilility_packages", func() { 7837 b := &pb.Build{ 7838 Builder: &pb.BuilderID{ 7839 Project: "project", 7840 Bucket: "bucket", 7841 Builder: "builder", 7842 }, 7843 Canary: true, 7844 Infra: &pb.BuildInfra{ 7845 Buildbucket: &pb.BuildInfra_Buildbucket{ 7846 Hostname: "app.appspot.com", 7847 }, 7848 }, 7849 Input: &pb.Build_Input{ 7850 Experiments: []string{"omit", "include"}, 7851 }, 7852 } 7853 cfg := &pb.SettingsCfg{ 7854 Swarming: &pb.SwarmingSettings{ 7855 BbagentPackage: &pb.SwarmingSettings_Package{ 7856 PackageName: "infra/tools/luci/bbagent/${platform}", 7857 Version: "version", 7858 VersionCanary: "canary-version", 7859 }, 7860 BbagentUtilityPackages: []*pb.SwarmingSettings_Package{ 7861 { 7862 PackageName: "include", 7863 Version: "version", 7864 VersionCanary: "canary-version", 7865 }, 7866 { 7867 Builders: &pb.BuilderPredicate{ 7868 RegexExclude: []string{ 7869 ".*", 7870 }, 7871 }, 7872 PackageName: "exclude", 7873 Version: "version", 7874 VersionCanary: "canary-version", 7875 }, 7876 { 7877 Builders: &pb.BuilderPredicate{ 7878 Regex: []string{ 7879 ".*", 7880 }, 7881 }, 7882 PackageName: "subdir", 7883 Subdir: "subdir", 7884 Version: "version", 7885 VersionCanary: "canary-version", 7886 }, 7887 { 7888 PackageName: "include_experiment", 7889 Version: "version", 7890 IncludeOnExperiment: []string{"include"}, 7891 }, 7892 { 7893 PackageName: "not_include_experiment", 7894 Version: "version", 7895 IncludeOnExperiment: []string{"not_include"}, 7896 }, 7897 { 7898 PackageName: "omit_experiment", 7899 Version: "version", 7900 OmitOnExperiment: []string{"omit"}, 7901 }, 7902 }, 7903 }, 7904 Cipd: &pb.CipdSettings{ 7905 Server: "cipd server", 7906 Source: &pb.CipdSettings_Source{ 7907 PackageName: "the/offical/cipd/package/${platform}", 7908 Version: "1", 7909 }, 7910 }, 7911 } 7912 err := setInfraAgent(b, cfg) 7913 So(err, ShouldBeNil) 7914 So(b.Infra.Buildbucket.Agent, ShouldResembleProto, &pb.BuildInfra_Buildbucket_Agent{ 7915 Source: &pb.BuildInfra_Buildbucket_Agent_Source{ 7916 DataType: &pb.BuildInfra_Buildbucket_Agent_Source_Cipd{ 7917 Cipd: &pb.BuildInfra_Buildbucket_Agent_Source_CIPD{ 7918 Package: "infra/tools/luci/bbagent/${platform}", 7919 Version: "canary-version", 7920 Server: "cipd server", 7921 }, 7922 }, 7923 }, 7924 Input: &pb.BuildInfra_Buildbucket_Agent_Input{ 7925 CipdSource: map[string]*pb.InputDataRef{ 7926 "cipd": &pb.InputDataRef{ 7927 DataType: &pb.InputDataRef_Cipd{ 7928 Cipd: &pb.InputDataRef_CIPD{ 7929 Server: "cipd server", 7930 Specs: []*pb.InputDataRef_CIPD_PkgSpec{ 7931 { 7932 Package: "the/offical/cipd/package/${platform}", 7933 Version: "1", 7934 }, 7935 }, 7936 }, 7937 }, 7938 OnPath: []string{"cipd", "cipd/bin"}, 7939 }, 7940 }, 7941 Data: map[string]*pb.InputDataRef{ 7942 "bbagent_utility_packages": { 7943 DataType: &pb.InputDataRef_Cipd{ 7944 Cipd: &pb.InputDataRef_CIPD{ 7945 Server: "cipd server", 7946 Specs: []*pb.InputDataRef_CIPD_PkgSpec{ 7947 {Package: "include", Version: "canary-version"}, 7948 {Package: "include_experiment", Version: "version"}, 7949 }, 7950 }, 7951 }, 7952 OnPath: []string{"bbagent_utility_packages", "bbagent_utility_packages/bin"}, 7953 }, 7954 "bbagent_utility_packages/subdir": { 7955 DataType: &pb.InputDataRef_Cipd{ 7956 Cipd: &pb.InputDataRef_CIPD{ 7957 Server: "cipd server", 7958 Specs: []*pb.InputDataRef_CIPD_PkgSpec{ 7959 {Package: "subdir", Version: "canary-version"}, 7960 }, 7961 }, 7962 }, 7963 OnPath: []string{"bbagent_utility_packages/subdir", "bbagent_utility_packages/subdir/bin"}, 7964 }, 7965 }, 7966 }, 7967 Purposes: map[string]pb.BuildInfra_Buildbucket_Agent_Purpose{ 7968 "kitchen-checkout": pb.BuildInfra_Buildbucket_Agent_PURPOSE_EXE_PAYLOAD, 7969 "bbagent_utility_packages": pb.BuildInfra_Buildbucket_Agent_PURPOSE_BBAGENT_UTILITY, 7970 "bbagent_utility_packages/subdir": pb.BuildInfra_Buildbucket_Agent_PURPOSE_BBAGENT_UTILITY, 7971 }, 7972 CipdClientCache: &pb.CacheEntry{ 7973 Name: "cipd_client_6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b", 7974 Path: "cipd_client", 7975 }, 7976 CipdPackagesCache: &pb.CacheEntry{ 7977 Name: "cipd_cache_e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", 7978 Path: "cipd_cache", 7979 }, 7980 }) 7981 }) 7982 }) 7983 } 7984 7985 func sortTasksByClassName(tasks tqtesting.TaskList) { 7986 sort.Slice(tasks, func(i, j int) bool { 7987 return tasks[i].Class < tasks[j].Class 7988 }) 7989 }