go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/buildbucket/appengine/tasks/backend_test.go (about) 1 // Copyright 2022 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 tasks 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "net/http" 22 "net/http/httptest" 23 "strconv" 24 "strings" 25 "testing" 26 "time" 27 28 "github.com/golang/mock/gomock" 29 30 "google.golang.org/api/googleapi" 31 codepb "google.golang.org/genproto/googleapis/rpc/code" 32 statuspb "google.golang.org/genproto/googleapis/rpc/status" 33 "google.golang.org/grpc/codes" 34 "google.golang.org/protobuf/proto" 35 "google.golang.org/protobuf/types/known/durationpb" 36 "google.golang.org/protobuf/types/known/structpb" 37 "google.golang.org/protobuf/types/known/timestamppb" 38 39 cipdpb "go.chromium.org/luci/cipd/api/cipd/v1" 40 "go.chromium.org/luci/common/clock/testclock" 41 "go.chromium.org/luci/common/logging" 42 "go.chromium.org/luci/common/logging/memlogger" 43 "go.chromium.org/luci/common/retry" 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 "go.chromium.org/luci/grpc/prpc" 48 "go.chromium.org/luci/server/caching" 49 "go.chromium.org/luci/server/secrets" 50 "go.chromium.org/luci/server/secrets/testsecrets" 51 "go.chromium.org/luci/server/tq" 52 53 "go.chromium.org/luci/buildbucket/appengine/internal/clients" 54 "go.chromium.org/luci/buildbucket/appengine/internal/config" 55 "go.chromium.org/luci/buildbucket/appengine/internal/metrics" 56 "go.chromium.org/luci/buildbucket/appengine/model" 57 taskdefs "go.chromium.org/luci/buildbucket/appengine/tasks/defs" 58 pb "go.chromium.org/luci/buildbucket/proto" 59 60 . "github.com/smartystreets/goconvey/convey" 61 . "go.chromium.org/luci/common/testing/assertions" 62 ) 63 64 // This will help track the number of times the cipd server is called to test if the cache is working as intended. 65 var numCipdCalls int 66 67 func describeBootstrapBundle(c C, hasBadPkgFile bool) http.HandlerFunc { 68 return func(w http.ResponseWriter, r *http.Request) { 69 c.So(r.URL.Path, ShouldEqual, "/prpc/cipd.Repository/DescribeBootstrapBundle") 70 numCipdCalls++ 71 reqBody, err := io.ReadAll(r.Body) 72 c.So(err, ShouldBeNil) 73 req := &cipdpb.DescribeBootstrapBundleRequest{} 74 err = proto.Unmarshal(reqBody, req) 75 c.So(err, ShouldBeNil) 76 variants := []string{ 77 "linux-amd64", 78 "mac-amd64", 79 } 80 bootstrapFiles := []*cipdpb.DescribeBootstrapBundleResponse_BootstrapFile{} 81 for _, variant := range variants { 82 pkdName := req.Prefix + "/" + variant 83 bootstrapFile := &cipdpb.DescribeBootstrapBundleResponse_BootstrapFile{ 84 Package: pkdName, 85 Size: 100, 86 Instance: &cipdpb.ObjectRef{ 87 HashAlgo: cipdpb.HashAlgo_SHA256, 88 HexDigest: "this_is_a_sha_256_I_swear", 89 }, 90 } 91 bootstrapFiles = append(bootstrapFiles, bootstrapFile) 92 } 93 if hasBadPkgFile { 94 bootstrapFiles = append(bootstrapFiles, &cipdpb.DescribeBootstrapBundleResponse_BootstrapFile{ 95 Package: req.Prefix + "/" + "bad-platform", 96 Status: &statuspb.Status{ 97 Code: int32(codepb.Code_NOT_FOUND), 98 Message: "no such tag", 99 }, 100 }) 101 } 102 res := &cipdpb.DescribeBootstrapBundleResponse{ 103 Files: bootstrapFiles, 104 } 105 var buf []byte 106 buf, _ = proto.Marshal(res) 107 code := codes.OK 108 status := http.StatusOK 109 w.Header().Set("Content-Type", r.Header.Get("Accept")) 110 w.Header().Set(prpc.HeaderGRPCCode, strconv.Itoa(int(code))) 111 w.WriteHeader(status) 112 _, err = w.Write(buf) 113 c.So(err, ShouldBeNil) 114 } 115 } 116 117 func helpTestCipdCall(c C, ctx context.Context, infra *pb.BuildInfra, expectedNumCalls int) { 118 m, err := extractCipdDetails(ctx, "project", infra) 119 c.So(err, ShouldBeNil) 120 detail, ok := m["infra/tools/luci/bbagent/linux-amd64"] 121 c.So(ok, ShouldBeTrue) 122 c.So(detail, ShouldResembleProto, &pb.RunTaskRequest_AgentExecutable_AgentSource{ 123 Sha256: "this_is_a_sha_256_I_swear", 124 SizeBytes: 100, 125 Url: "https://chrome-infra-packages.appspot.com/bootstrap/infra/tools/luci/bbagent/linux-amd64/+/latest", 126 }) 127 detail, ok = m["infra/tools/luci/bbagent/mac-amd64"] 128 c.So(ok, ShouldBeTrue) 129 c.So(detail, ShouldResembleProto, &pb.RunTaskRequest_AgentExecutable_AgentSource{ 130 Sha256: "this_is_a_sha_256_I_swear", 131 SizeBytes: 100, 132 Url: "https://chrome-infra-packages.appspot.com/bootstrap/infra/tools/luci/bbagent/mac-amd64/+/latest", 133 }) 134 c.So(numCipdCalls, ShouldEqual, expectedNumCalls) 135 } 136 137 func TestCipdClient(t *testing.T) { 138 t.Parallel() 139 140 Convey("extractCipdDetails", t, func(c C) { 141 now := testclock.TestRecentTimeUTC 142 ctx, _ := testclock.UseTime(context.Background(), now) 143 ctx = caching.WithEmptyProcessCache(ctx) 144 ctx = memory.UseWithAppID(ctx, "dev~app-id") 145 ctx = txndefer.FilterRDS(ctx) 146 ctx = metrics.WithServiceInfo(ctx, "svc", "job", "ins") 147 datastore.GetTestable(ctx).AutoIndex(true) 148 datastore.GetTestable(ctx).Consistent(true) 149 ctx, _ = tq.TestingContext(ctx, nil) 150 mockCipdServer := httptest.NewServer(describeBootstrapBundle(c, false)) 151 defer mockCipdServer.Close() 152 mockCipdClient := &prpc.Client{ 153 Host: strings.TrimPrefix(mockCipdServer.URL, "http://"), 154 Options: &prpc.Options{ 155 Retry: func() retry.Iterator { 156 return &retry.Limited{ 157 Retries: 3, 158 Delay: 0, 159 } 160 }, 161 Insecure: true, 162 UserAgent: "prpc-test", 163 }, 164 } 165 ctx = context.WithValue(ctx, MockCipdClientKey{}, mockCipdClient) 166 167 Convey("ok", func() { 168 infra := &pb.BuildInfra{ 169 Backend: &pb.BuildInfra_Backend{ 170 Task: &pb.Task{ 171 Id: &pb.TaskID{ 172 Id: "abc123", 173 Target: "swarming://mytarget", 174 }, 175 }, 176 }, 177 Buildbucket: &pb.BuildInfra_Buildbucket{ 178 Agent: &pb.BuildInfra_Buildbucket_Agent{ 179 Source: &pb.BuildInfra_Buildbucket_Agent_Source{ 180 DataType: &pb.BuildInfra_Buildbucket_Agent_Source_Cipd{ 181 Cipd: &pb.BuildInfra_Buildbucket_Agent_Source_CIPD{ 182 Package: "infra/tools/luci/bbagent/${platform}", 183 Version: "latest", 184 Server: "https://chrome-infra-packages.appspot.com", 185 }, 186 }, 187 }, 188 }, 189 Hostname: "some unique host name", 190 }, 191 } 192 Convey("no non-ok pkg file", func() { 193 numCipdCalls = 0 194 // call extractCipdDetails function 10 times. 195 // The test asserts that numCipdCalls should always be 1 196 for i := 0; i < 10; i++ { 197 helpTestCipdCall(c, ctx, infra, 1) 198 } 199 }) 200 Convey("contain non-ok pkg file", func() { 201 mockCipdServer := httptest.NewServer(describeBootstrapBundle(c, true)) 202 defer mockCipdServer.Close() 203 mockCipdClient := &prpc.Client{ 204 Host: strings.TrimPrefix(mockCipdServer.URL, "http://"), 205 Options: &prpc.Options{ 206 Retry: func() retry.Iterator { 207 return &retry.Limited{ 208 Retries: 3, 209 Delay: 0, 210 } 211 }, 212 Insecure: true, 213 UserAgent: "prpc-test", 214 }, 215 } 216 ctx = context.WithValue(ctx, MockCipdClientKey{}, mockCipdClient) 217 numCipdCalls = 0 218 helpTestCipdCall(c, ctx, infra, 1) 219 // make the cuurent time longer than the cache TTL. 220 ctx, _ = testclock.UseTime(ctx, now.Add(5*time.Minute)) 221 helpTestCipdCall(c, ctx, infra, 2) 222 }) 223 }) 224 }) 225 } 226 227 func TestCreateBackendTask(t *testing.T) { 228 Convey("computeBackendNewTaskReq", t, func(c C) { 229 ctl := gomock.NewController(t) 230 defer ctl.Finish() 231 mockTaskCreator := clients.NewMockTaskCreator(ctl) 232 now := testclock.TestRecentTimeUTC 233 ctx, _ := testclock.UseTime(context.Background(), now) 234 ctx = context.WithValue(ctx, clients.MockTaskCreatorKey, mockTaskCreator) 235 ctx = caching.WithEmptyProcessCache(ctx) 236 ctx = memory.UseWithAppID(ctx, "dev~app-id") 237 ctx = txndefer.FilterRDS(ctx) 238 ctx = metrics.WithServiceInfo(ctx, "svc", "job", "ins") 239 datastore.GetTestable(ctx).AutoIndex(true) 240 datastore.GetTestable(ctx).Consistent(true) 241 ctx, _ = tq.TestingContext(ctx, nil) 242 store := &testsecrets.Store{ 243 Secrets: map[string]secrets.Secret{ 244 "key": {Active: []byte("stuff")}, 245 }, 246 } 247 ctx = secrets.Use(ctx, store) 248 ctx = secrets.GeneratePrimaryTinkAEADForTest(ctx) 249 250 backendSetting := []*pb.BackendSetting{} 251 backendSetting = append(backendSetting, &pb.BackendSetting{ 252 Target: "swarming:/chromium-swarm-dev", 253 Hostname: "chromium-swarm-dev", 254 Mode: &pb.BackendSetting_FullMode_{ 255 FullMode: &pb.BackendSetting_FullMode{ 256 PubsubId: "chromium-swarm-dev-backend", 257 }, 258 }, 259 }) 260 settingsCfg := &pb.SettingsCfg{Backends: backendSetting} 261 server := httptest.NewServer(describeBootstrapBundle(c, false)) 262 defer server.Close() 263 client := &prpc.Client{ 264 Host: strings.TrimPrefix(server.URL, "http://"), 265 Options: &prpc.Options{ 266 Retry: func() retry.Iterator { 267 return &retry.Limited{ 268 Retries: 3, 269 Delay: 0, 270 } 271 }, 272 Insecure: true, 273 UserAgent: "prpc-test", 274 }, 275 } 276 ctx = context.WithValue(ctx, MockCipdClientKey{}, client) 277 278 Convey("ok", func() { 279 build := &model.Build{ 280 ID: 1, 281 BucketID: "project/bucket", 282 Project: "project", 283 BuilderID: "project/bucket", 284 Proto: &pb.Build{ 285 Id: 1, 286 Builder: &pb.BuilderID{ 287 Builder: "builder", 288 Bucket: "bucket", 289 Project: "project", 290 }, 291 CreateTime: ×tamppb.Timestamp{ 292 Seconds: 1677511793, 293 }, 294 SchedulingTimeout: &durationpb.Duration{Seconds: 100}, 295 ExecutionTimeout: &durationpb.Duration{Seconds: 500}, 296 Input: &pb.Build_Input{ 297 Experiments: []string{ 298 "cow_eggs_experiment", 299 "are_cow_eggs_real_experiment", 300 }, 301 }, 302 GracePeriod: &durationpb.Duration{Seconds: 50}, 303 }, 304 } 305 key := datastore.KeyForObj(ctx, build) 306 infra := &model.BuildInfra{ 307 Build: key, 308 Proto: &pb.BuildInfra{ 309 Backend: &pb.BuildInfra_Backend{ 310 Caches: []*pb.CacheEntry{ 311 { 312 Name: "cache_name", 313 Path: "cache_value", 314 }, 315 }, 316 Config: &structpb.Struct{ 317 Fields: map[string]*structpb.Value{ 318 "priority": { 319 Kind: &structpb.Value_NumberValue{NumberValue: 32}, 320 }, 321 "bot_ping_tolerance": { 322 Kind: &structpb.Value_NumberValue{NumberValue: 2}, 323 }, 324 }, 325 }, 326 Task: &pb.Task{ 327 Id: &pb.TaskID{ 328 Id: "", 329 Target: "swarming:/chromium-swarm-dev", 330 }, 331 }, 332 TaskDimensions: []*pb.RequestedDimension{ 333 { 334 Key: "dim_key_1", 335 Value: "dim_val_1", 336 }, 337 }, 338 }, 339 Bbagent: &pb.BuildInfra_BBAgent{ 340 CacheDir: "cache", 341 }, 342 Buildbucket: &pb.BuildInfra_Buildbucket{ 343 Hostname: "some unique host name", 344 Agent: &pb.BuildInfra_Buildbucket_Agent{ 345 Source: &pb.BuildInfra_Buildbucket_Agent_Source{ 346 DataType: &pb.BuildInfra_Buildbucket_Agent_Source_Cipd{ 347 Cipd: &pb.BuildInfra_Buildbucket_Agent_Source_CIPD{ 348 Package: "infra/tools/luci/bbagent/${platform}", 349 Version: "latest", 350 Server: "https://chrome-infra-packages.appspot.com", 351 }, 352 }, 353 }, 354 CipdClientCache: &pb.CacheEntry{ 355 Name: "cipd_client_hash", 356 Path: "cipd_client", 357 }, 358 CipdPackagesCache: &pb.CacheEntry{ 359 Name: "cipd_cache_hash", 360 Path: "cipd_cache", 361 }, 362 }, 363 }, 364 }, 365 } 366 req, err := computeBackendNewTaskReq(ctx, build, infra, "request_id", settingsCfg) 367 So(err, ShouldBeNil) 368 So(req.BackendConfig, ShouldResembleProto, &structpb.Struct{ 369 Fields: map[string]*structpb.Value{ 370 "priority": { 371 Kind: &structpb.Value_NumberValue{NumberValue: 32}, 372 }, 373 "bot_ping_tolerance": { 374 Kind: &structpb.Value_NumberValue{NumberValue: 2}, 375 }, 376 "tags": { 377 Kind: &structpb.Value_ListValue{ 378 ListValue: &structpb.ListValue{ 379 Values: []*structpb.Value{ 380 {Kind: &structpb.Value_StringValue{StringValue: "buildbucket_bucket:project/bucket"}}, 381 {Kind: &structpb.Value_StringValue{StringValue: "buildbucket_build_id:1"}}, 382 {Kind: &structpb.Value_StringValue{StringValue: "buildbucket_hostname:some unique host name"}}, 383 {Kind: &structpb.Value_StringValue{StringValue: "buildbucket_template_canary:0"}}, 384 {Kind: &structpb.Value_StringValue{StringValue: "luci_project:project"}}, 385 }, 386 }, 387 }, 388 }, 389 "task_name": &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "bb-1-project/bucket"}}, 390 }, 391 }) 392 So(req.BuildbucketHost, ShouldEqual, "some unique host name") 393 So(req.BuildId, ShouldEqual, "1") 394 So(req.Caches, ShouldResembleProto, []*pb.CacheEntry{ 395 { 396 Name: "cache_name", 397 Path: "cache_value", 398 }, 399 { 400 Name: "cipd_client_hash", 401 Path: "cipd_client", 402 }, 403 { 404 Name: "cipd_cache_hash", 405 Path: "cipd_cache", 406 }, 407 }) 408 So(req.ExecutionTimeout, ShouldResembleProto, &durationpb.Duration{Seconds: 500}) 409 So(req.GracePeriod, ShouldResembleProto, &durationpb.Duration{Seconds: 230}) 410 So(req.Agent.Source["infra/tools/luci/bbagent/linux-amd64"], ShouldResembleProto, &pb.RunTaskRequest_AgentExecutable_AgentSource{ 411 Sha256: "this_is_a_sha_256_I_swear", 412 SizeBytes: 100, 413 Url: "https://chrome-infra-packages.appspot.com/bootstrap/infra/tools/luci/bbagent/linux-amd64/+/latest", 414 }) 415 So(req.Agent.Source["infra/tools/luci/bbagent/mac-amd64"], ShouldResembleProto, &pb.RunTaskRequest_AgentExecutable_AgentSource{ 416 Sha256: "this_is_a_sha_256_I_swear", 417 SizeBytes: 100, 418 Url: "https://chrome-infra-packages.appspot.com/bootstrap/infra/tools/luci/bbagent/mac-amd64/+/latest", 419 }) 420 So(req.AgentArgs, ShouldResemble, []string{ 421 "-build-id", "1", 422 "-host", "some unique host name", 423 "-cache-base", "cache", 424 "-context-file", "${BUILDBUCKET_AGENT_CONTEXT_FILE}", 425 }) 426 So(req.Dimensions, ShouldResembleProto, []*pb.RequestedDimension{ 427 { 428 Key: "dim_key_1", 429 Value: "dim_val_1", 430 }, 431 }) 432 So(req.StartDeadline.Seconds, ShouldEqual, 1677511893) 433 So(req.Experiments, ShouldResemble, []string{ 434 "cow_eggs_experiment", 435 "are_cow_eggs_real_experiment", 436 }) 437 So(req.PubsubTopic, ShouldEqual, "projects/app-id/topics/chromium-swarm-dev-backend") 438 }) 439 }) 440 441 Convey("RunTask", t, func(c C) { 442 ctl := gomock.NewController(t) 443 defer ctl.Finish() 444 mockTaskCreator := clients.NewMockTaskCreator(ctl) 445 now := testclock.TestRecentTimeUTC 446 ctx, _ := testclock.UseTime(context.Background(), now) 447 ctx = context.WithValue(ctx, clients.MockTaskCreatorKey, mockTaskCreator) 448 ctx = caching.WithEmptyProcessCache(ctx) 449 ctx = memory.UseWithAppID(ctx, "dev~app-id") 450 ctx = txndefer.FilterRDS(ctx) 451 ctx = metrics.WithServiceInfo(ctx, "svc", "job", "ins") 452 datastore.GetTestable(ctx).AutoIndex(true) 453 datastore.GetTestable(ctx).Consistent(true) 454 ctx, _ = tq.TestingContext(ctx, nil) 455 store := &testsecrets.Store{ 456 Secrets: map[string]secrets.Secret{ 457 "key": {Active: []byte("stuff")}, 458 }, 459 } 460 ctx = secrets.Use(ctx, store) 461 ctx = secrets.GeneratePrimaryTinkAEADForTest(ctx) 462 ctx = memlogger.Use(ctx) 463 ctx, sch := tq.TestingContext(txndefer.FilterRDS(ctx), nil) 464 logs := logging.Get(ctx).(*memlogger.MemLogger) 465 466 backendSetting := []*pb.BackendSetting{} 467 backendSetting = append(backendSetting, &pb.BackendSetting{ 468 Target: "fail_me", 469 Hostname: "hostname", 470 }) 471 backendSetting = append(backendSetting, &pb.BackendSetting{ 472 Target: "swarming://chromium-swarm", 473 Hostname: "hostname2", 474 Mode: &pb.BackendSetting_FullMode_{ 475 FullMode: &pb.BackendSetting_FullMode{ 476 BuildSyncSetting: &pb.BackendSetting_BuildSyncSetting{ 477 Shards: 5, 478 SyncIntervalSeconds: 300, 479 }, 480 }, 481 }, 482 TaskCreatingTimeout: durationpb.New(8 * time.Minute), 483 }) 484 backendSetting = append(backendSetting, &pb.BackendSetting{ 485 Target: "lite://foo-lite", 486 Hostname: "foo-hostname", 487 Mode: &pb.BackendSetting_LiteMode_{ 488 LiteMode: &pb.BackendSetting_LiteMode{}, 489 }, 490 }) 491 settingsCfg := &pb.SettingsCfg{Backends: backendSetting} 492 err := config.SetTestSettingsCfg(ctx, settingsCfg) 493 So(err, ShouldBeNil) 494 server := httptest.NewServer(describeBootstrapBundle(c, false)) 495 defer server.Close() 496 client := &prpc.Client{ 497 Host: strings.TrimPrefix(server.URL, "http://"), 498 Options: &prpc.Options{ 499 Retry: func() retry.Iterator { 500 return &retry.Limited{ 501 Retries: 3, 502 Delay: 0, 503 } 504 }, 505 Insecure: true, 506 UserAgent: "prpc-test", 507 }, 508 } 509 ctx = context.WithValue(ctx, MockCipdClientKey{}, client) 510 511 build := &model.Build{ 512 ID: 1, 513 Proto: &pb.Build{ 514 Id: 1, 515 Builder: &pb.BuilderID{ 516 Builder: "builder", 517 Bucket: "bucket", 518 Project: "project", 519 }, 520 CreateTime: timestamppb.New(now.Add(-5 * time.Minute)), 521 ExecutionTimeout: &durationpb.Duration{Seconds: 500}, 522 Input: &pb.Build_Input{ 523 Experiments: []string{ 524 "cow_eggs_experiment", 525 "are_cow_eggs_real_experiment", 526 }, 527 }, 528 GracePeriod: &durationpb.Duration{Seconds: 50}, 529 }, 530 } 531 key := datastore.KeyForObj(ctx, build) 532 infra := &model.BuildInfra{ 533 Build: key, 534 Proto: &pb.BuildInfra{ 535 Backend: &pb.BuildInfra_Backend{ 536 Caches: []*pb.CacheEntry{ 537 { 538 Name: "cache_name", 539 Path: "cache_value", 540 }, 541 }, 542 Config: &structpb.Struct{ 543 Fields: map[string]*structpb.Value{ 544 "priority": { 545 Kind: &structpb.Value_NumberValue{NumberValue: 32}, 546 }, 547 "bot_ping_tolerance": { 548 Kind: &structpb.Value_NumberValue{NumberValue: 2}, 549 }, 550 }, 551 }, 552 Task: &pb.Task{ 553 Id: &pb.TaskID{ 554 Id: "", 555 Target: "swarming://chromium-swarm", 556 }, 557 }, 558 TaskDimensions: []*pb.RequestedDimension{ 559 { 560 Key: "dim_key_1", 561 Value: "dim_val_1", 562 }, 563 }, 564 }, 565 Bbagent: &pb.BuildInfra_BBAgent{ 566 CacheDir: "cache", 567 }, 568 Buildbucket: &pb.BuildInfra_Buildbucket{ 569 Hostname: "some unique host name", 570 Agent: &pb.BuildInfra_Buildbucket_Agent{ 571 Source: &pb.BuildInfra_Buildbucket_Agent_Source{ 572 DataType: &pb.BuildInfra_Buildbucket_Agent_Source_Cipd{ 573 Cipd: &pb.BuildInfra_Buildbucket_Agent_Source_CIPD{ 574 Package: "infra/tools/luci/bbagent/${platform}", 575 Version: "latest", 576 Server: "https://chrome-infra-packages.appspot.com", 577 }, 578 }, 579 }, 580 }, 581 }, 582 }, 583 } 584 bs := &model.BuildStatus{Build: datastore.KeyForObj(ctx, build)} 585 So(datastore.Put(ctx, build, infra, bs), ShouldBeNil) 586 587 Convey("ok", func() { 588 mockTaskCreator.EXPECT().RunTask(gomock.Any(), gomock.Any()).Return(&pb.RunTaskResponse{ 589 Task: &pb.Task{ 590 Id: &pb.TaskID{Id: "abc123", Target: "swarming://chromium-swarm"}, 591 Link: "this_is_a_url_link", 592 UpdateId: 1, 593 }, 594 }, nil) 595 err = CreateBackendTask(ctx, 1, "request_id") 596 So(err, ShouldBeNil) 597 eb := &model.Build{ID: build.ID} 598 expectedBuildInfra := &model.BuildInfra{Build: key} 599 So(datastore.Get(ctx, eb, expectedBuildInfra), ShouldBeNil) 600 updateTime := eb.Proto.UpdateTime.AsTime() 601 So(updateTime, ShouldEqual, now) 602 So(eb.BackendTarget, ShouldEqual, "swarming://chromium-swarm") 603 So(eb.BackendSyncInterval, ShouldEqual, time.Duration(300)*time.Second) 604 parts := strings.Split(eb.NextBackendSyncTime, "--") 605 So(parts, ShouldHaveLength, 4) 606 So(parts[0], ShouldEqual, eb.BackendTarget) 607 So(parts[1], ShouldEqual, "project") 608 shardID, err := strconv.Atoi(parts[2]) 609 So(err, ShouldBeNil) 610 So(shardID >= 0, ShouldBeTrue) 611 So(shardID < 5, ShouldBeTrue) 612 So(parts[3], ShouldEqual, fmt.Sprint(updateTime.Round(time.Minute).Add(eb.BackendSyncInterval).Unix())) 613 So(expectedBuildInfra.Proto.Backend.Task, ShouldResembleProto, &pb.Task{ 614 Id: &pb.TaskID{ 615 Id: "abc123", 616 Target: "swarming://chromium-swarm", 617 }, 618 Link: "this_is_a_url_link", 619 UpdateId: 1, 620 }) 621 So(sch.Tasks(), ShouldBeEmpty) 622 }) 623 624 Convey("fail", func() { 625 mockTaskCreator.EXPECT().RunTask(gomock.Any(), gomock.Any()).Return(nil, &googleapi.Error{Code: 400}) 626 err = CreateBackendTask(ctx, 1, "request_id") 627 expectedBuild := &model.Build{ID: 1} 628 So(datastore.Get(ctx, expectedBuild), ShouldBeNil) 629 So(err, ShouldErrLike, "failed to create a backend task") 630 So(expectedBuild.Proto.Status, ShouldEqual, pb.Status_INFRA_FAILURE) 631 So(expectedBuild.Proto.SummaryMarkdown, ShouldContainSubstring, "Backend task creation failure.") 632 }) 633 634 Convey("bail out if the build has a task associated", func() { 635 infra.Proto.Backend.Task.Id.Id = "task" 636 So(datastore.Put(ctx, infra), ShouldBeNil) 637 err = CreateBackendTask(ctx, 1, "request_id") 638 So(err, ShouldBeNil) 639 expectedBuildInfra := &model.BuildInfra{Build: key} 640 So(datastore.Get(ctx, expectedBuildInfra), ShouldBeNil) 641 So(expectedBuildInfra.Proto.Backend.Task.Id.Id, ShouldEqual, "task") 642 So(logs, memlogger.ShouldHaveLog, 643 logging.Info, "build 1 has associated with task") 644 }) 645 646 Convey("give up after backend timeout", func() { 647 now = now.Add(9 * time.Minute) 648 ctx, _ = testclock.UseTime(ctx, now) 649 err = CreateBackendTask(ctx, 1, "request_id") 650 expectedBuild := &model.Build{ID: 1} 651 So(datastore.Get(ctx, expectedBuild), ShouldBeNil) 652 So(err, ShouldErrLike, "creating backend task for build 1 with requestID request_id has expired after 8m0s") 653 So(expectedBuild.Proto.Status, ShouldEqual, pb.Status_INFRA_FAILURE) 654 So(expectedBuild.Proto.SummaryMarkdown, ShouldContainSubstring, "Backend task creation failure.") 655 }) 656 657 Convey("Lite backend", func() { 658 bkt := &model.Bucket{ 659 ID: "bucket", 660 Parent: model.ProjectKey(ctx, "project"), 661 } 662 bldr := &model.Builder{ 663 ID: "builder", 664 Parent: datastore.KeyForObj(ctx, bkt), 665 Config: &pb.BuilderConfig{ 666 Name: "builder", 667 HeartbeatTimeoutSecs: 5, 668 }, 669 } 670 build.Proto.SchedulingTimeout = durationpb.New(1 * time.Minute) 671 infra.Proto.Backend.Task.Id.Target = "lite://foo-lite" 672 So(datastore.Put(ctx, build, infra, bkt, bldr), ShouldBeNil) 673 674 mockTaskCreator.EXPECT().RunTask(gomock.Any(), gomock.Any()).Return(&pb.RunTaskResponse{ 675 Task: &pb.Task{ 676 Id: &pb.TaskID{Id: "abc123", Target: "lite://foo-lite"}, 677 Link: "this_is_a_url_link", 678 UpdateId: 1, 679 }, 680 }, nil) 681 682 Convey("ok", func() { 683 err = CreateBackendTask(ctx, 1, "request_id") 684 So(err, ShouldBeNil) 685 eb := &model.Build{ID: build.ID} 686 expectedBuildInfra := &model.BuildInfra{Build: datastore.KeyForObj(ctx, build)} 687 So(datastore.Get(ctx, eb, expectedBuildInfra), ShouldBeNil) 688 updateTime := eb.Proto.UpdateTime.AsTime() 689 So(updateTime, ShouldEqual, now) 690 So(expectedBuildInfra.Proto.Backend.Task, ShouldResembleProto, &pb.Task{ 691 Id: &pb.TaskID{ 692 Id: "abc123", 693 Target: "lite://foo-lite", 694 }, 695 Link: "this_is_a_url_link", 696 UpdateId: 1, 697 }) 698 tasks := sch.Tasks() 699 So(tasks, ShouldHaveLength, 1) 700 So(tasks[0].Payload.(*taskdefs.CheckBuildLiveness).GetBuildId(), ShouldEqual, build.ID) 701 So(tasks[0].Payload.(*taskdefs.CheckBuildLiveness).GetHeartbeatTimeout(), ShouldEqual, 5) 702 So(tasks[0].ETA, ShouldEqual, now.Add(5*time.Second)) 703 }) 704 705 Convey("SchedulingTimeout shorter than heartbeat timeout", func() { 706 bldr.Config.HeartbeatTimeoutSecs = 60 707 build.Proto.SchedulingTimeout = durationpb.New(10 * time.Second) 708 So(datastore.Put(ctx, build, bldr), ShouldBeNil) 709 710 err = CreateBackendTask(ctx, 1, "request_id") 711 So(err, ShouldBeNil) 712 tasks := sch.Tasks() 713 So(tasks, ShouldHaveLength, 1) 714 So(tasks[0].Payload.(*taskdefs.CheckBuildLiveness).GetBuildId(), ShouldEqual, build.ID) 715 So(tasks[0].Payload.(*taskdefs.CheckBuildLiveness).GetHeartbeatTimeout(), ShouldEqual, 60) 716 So(tasks[0].ETA, ShouldEqual, now.Add(10*time.Second)) 717 }) 718 719 Convey("no heartbeat_timeout_secs field set", func() { 720 bldr.Config.HeartbeatTimeoutSecs = 0 721 build.Proto.SchedulingTimeout = durationpb.New(10 * time.Second) 722 So(datastore.Put(ctx, build, bldr), ShouldBeNil) 723 724 err = CreateBackendTask(ctx, 1, "request_id") 725 So(err, ShouldBeNil) 726 tasks := sch.Tasks() 727 So(tasks, ShouldHaveLength, 1) 728 So(tasks[0].Payload.(*taskdefs.CheckBuildLiveness).GetBuildId(), ShouldEqual, build.ID) 729 So(tasks[0].Payload.(*taskdefs.CheckBuildLiveness).GetHeartbeatTimeout(), ShouldEqual, 0) 730 So(tasks[0].ETA, ShouldEqual, now.Add(10*time.Second)) 731 }) 732 733 Convey("builder not found", func() { 734 So(datastore.Delete(ctx, bldr), ShouldBeNil) 735 err = CreateBackendTask(ctx, 1, "request_id") 736 So(err, ShouldErrLike, "failed to fetch builder project/bucket/builder: datastore: no such entity") 737 738 }) 739 740 Convey("in dynamic bucket", func() { 741 So(datastore.Delete(ctx, bldr, bkt), ShouldBeNil) 742 bkt.Proto = &pb.Bucket{ 743 Name: "bucket", 744 DynamicBuilderTemplate: &pb.Bucket_DynamicBuilderTemplate{ 745 Template: &pb.BuilderConfig{}, 746 }, 747 } 748 So(datastore.Put(ctx, bkt), ShouldBeNil) 749 err = CreateBackendTask(ctx, 1, "request_id") 750 So(err, ShouldBeNil) 751 tasks := sch.Tasks() 752 So(tasks, ShouldHaveLength, 1) 753 So(tasks[0].Payload.(*taskdefs.CheckBuildLiveness).GetBuildId(), ShouldEqual, build.ID) 754 So(tasks[0].Payload.(*taskdefs.CheckBuildLiveness).GetHeartbeatTimeout(), ShouldEqual, 0) 755 So(tasks[0].ETA, ShouldEqual, now.Add(1*time.Minute)) 756 }) 757 }) 758 }) 759 }