go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/buildbucket/appengine/tasks/update_build_task_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 "bytes" 19 "context" 20 "encoding/base64" 21 "encoding/json" 22 "io" 23 "strconv" 24 "testing" 25 26 "google.golang.org/api/pubsub/v1" 27 "google.golang.org/protobuf/proto" 28 "google.golang.org/protobuf/types/known/structpb" 29 30 "go.chromium.org/luci/common/clock/testclock" 31 "go.chromium.org/luci/gae/filter/txndefer" 32 "go.chromium.org/luci/gae/impl/memory" 33 "go.chromium.org/luci/gae/service/datastore" 34 "go.chromium.org/luci/server/caching" 35 "go.chromium.org/luci/server/caching/cachingtest" 36 "go.chromium.org/luci/server/tq" 37 38 "go.chromium.org/luci/buildbucket/appengine/internal/config" 39 "go.chromium.org/luci/buildbucket/appengine/internal/metrics" 40 "go.chromium.org/luci/buildbucket/appengine/model" 41 pb "go.chromium.org/luci/buildbucket/proto" 42 43 . "github.com/smartystreets/goconvey/convey" 44 45 . "go.chromium.org/luci/common/testing/assertions" 46 ) 47 48 func TestValidateBuildTask(t *testing.T) { 49 t.Parallel() 50 51 Convey("ValidateBuildTask", t, func() { 52 ctx := memory.Use(context.Background()) 53 54 t0 := testclock.TestRecentTimeUTC 55 build := &model.Build{ 56 ID: 1, 57 Proto: &pb.Build{ 58 Id: 1, 59 Builder: &pb.BuilderID{ 60 Project: "project", 61 Bucket: "bucket", 62 Builder: "builder", 63 }, 64 Status: pb.Status_STARTED, 65 }, 66 CreateTime: t0, 67 } 68 bk := datastore.KeyForObj(ctx, build) 69 infra := &model.BuildInfra{ 70 Build: bk, 71 Proto: &pb.BuildInfra{}, 72 } 73 So(datastore.Put(ctx, build, infra), ShouldBeNil) 74 Convey("backend not in build infra", func() { 75 req := &pb.BuildTaskUpdate{ 76 BuildId: "1", 77 Task: &pb.Task{ 78 Id: &pb.TaskID{ 79 Id: "1", 80 Target: "swarming", 81 }, 82 }, 83 } 84 err := validateBuildTask(ctx, req, infra) 85 So(err, ShouldErrLike, "Build 1 does not support task backend") 86 }) 87 Convey("task not in build infra", func() { 88 infra := &model.BuildInfra{ 89 Build: bk, 90 Proto: &pb.BuildInfra{ 91 Backend: &pb.BuildInfra_Backend{}, 92 }, 93 } 94 So(datastore.Put(ctx, infra), ShouldBeNil) 95 req := &pb.BuildTaskUpdate{ 96 BuildId: "1", 97 Task: &pb.Task{ 98 Id: &pb.TaskID{ 99 Id: "2", 100 Target: "other", 101 }, 102 }, 103 } 104 err := validateBuildTask(ctx, req, infra) 105 So(err, ShouldErrLike, "No task is associated with the build. Cannot update.") 106 }) 107 Convey("task ID target mismatch", func() { 108 infra := &model.BuildInfra{ 109 Build: bk, 110 Proto: &pb.BuildInfra{ 111 Backend: &pb.BuildInfra_Backend{ 112 Task: &pb.Task{ 113 Id: &pb.TaskID{ 114 Id: "2", 115 Target: "other", 116 }, 117 }, 118 }, 119 }, 120 } 121 So(datastore.Put(ctx, infra), ShouldBeNil) 122 req := &pb.BuildTaskUpdate{ 123 BuildId: "1", 124 Task: &pb.Task{ 125 Id: &pb.TaskID{ 126 Id: "1", 127 Target: "other", 128 }, 129 }, 130 } 131 err := validateBuildTask(ctx, req, infra) 132 So(err, ShouldBeError) 133 }) 134 Convey("Run task did not return yet", func() { 135 infra := &model.BuildInfra{ 136 Build: bk, 137 Proto: &pb.BuildInfra{ 138 Backend: &pb.BuildInfra_Backend{ 139 Task: &pb.Task{ 140 Id: &pb.TaskID{ 141 Target: "swarming", 142 }, 143 }, 144 }, 145 }, 146 } 147 So(datastore.Put(ctx, infra), ShouldBeNil) 148 req := &pb.BuildTaskUpdate{ 149 BuildId: "1", 150 Task: &pb.Task{ 151 Id: &pb.TaskID{ 152 Id: "1", 153 Target: "swarming", 154 }, 155 Status: pb.Status_CANCELED, 156 }, 157 } 158 err := validateBuildTask(ctx, req, infra) 159 So(err, ShouldErrLike, "No task is associated with the build. Cannot update.") 160 }) 161 Convey("task is complete and success", func() { 162 infra := &model.BuildInfra{ 163 Build: bk, 164 Proto: &pb.BuildInfra{ 165 Backend: &pb.BuildInfra_Backend{ 166 Task: &pb.Task{ 167 Status: pb.Status_SUCCESS, 168 Id: &pb.TaskID{ 169 Id: "1", 170 Target: "swarming", 171 }, 172 }, 173 }, 174 }, 175 } 176 So(datastore.Put(ctx, infra), ShouldBeNil) 177 req := &pb.BuildTaskUpdate{ 178 BuildId: "1", 179 Task: &pb.Task{ 180 Id: &pb.TaskID{ 181 Id: "1", 182 Target: "swarming", 183 }, 184 }, 185 } 186 err := validateBuildTask(ctx, req, infra) 187 So(err, ShouldBeError) 188 }) 189 Convey("task is cancelled", func() { 190 infra := &model.BuildInfra{ 191 Build: bk, 192 Proto: &pb.BuildInfra{ 193 Backend: &pb.BuildInfra_Backend{ 194 Task: &pb.Task{ 195 Status: pb.Status_CANCELED, 196 Id: &pb.TaskID{ 197 Id: "1", 198 Target: "swarming", 199 }, 200 }, 201 }, 202 }, 203 } 204 So(datastore.Put(ctx, infra), ShouldBeNil) 205 req := &pb.BuildTaskUpdate{ 206 BuildId: "1", 207 Task: &pb.Task{ 208 Id: &pb.TaskID{ 209 Id: "1", 210 Target: "swarming", 211 }, 212 }, 213 } 214 err := validateBuildTask(ctx, req, infra) 215 So(err, ShouldBeError) 216 }) 217 Convey("task is running", func() { 218 infra := &model.BuildInfra{ 219 Build: bk, 220 Proto: &pb.BuildInfra{ 221 Backend: &pb.BuildInfra_Backend{ 222 Task: &pb.Task{ 223 Status: pb.Status_STARTED, 224 Id: &pb.TaskID{ 225 Id: "1", 226 Target: "swarming", 227 }, 228 }, 229 }, 230 }, 231 } 232 So(datastore.Put(ctx, infra), ShouldBeNil) 233 req := &pb.BuildTaskUpdate{ 234 BuildId: "1", 235 Task: &pb.Task{ 236 Id: &pb.TaskID{ 237 Id: "1", 238 Target: "swarming", 239 }, 240 }, 241 } 242 err := validateBuildTask(ctx, req, infra) 243 So(err, ShouldBeNil) 244 }) 245 246 }) 247 } 248 249 func TestValidateTaskUpdate(t *testing.T) { 250 t.Parallel() 251 252 Convey("ValidateTaskUpdate", t, func() { 253 ctx := memory.Use(context.Background()) 254 255 Convey("is valid task", func() { 256 req := &pb.BuildTaskUpdate{ 257 BuildId: "1", 258 Task: &pb.Task{ 259 Status: pb.Status_STARTED, 260 Id: &pb.TaskID{ 261 Id: "one", 262 Target: "swarming", 263 }, 264 UpdateId: 1, 265 }, 266 } 267 So(validateBuildTaskUpdate(ctx, req), ShouldBeNil) 268 }) 269 Convey("is missing task", func() { 270 req := &pb.BuildTaskUpdate{ 271 BuildId: "1", 272 } 273 err := validateBuildTaskUpdate(ctx, req) 274 So(err, ShouldNotBeNil) 275 So(err.Error(), ShouldContainSubstring, "task.id: required") 276 }) 277 Convey("is missing build ID", func() { 278 req := &pb.BuildTaskUpdate{ 279 Task: &pb.Task{ 280 Status: pb.Status_STARTED, 281 Id: &pb.TaskID{ 282 Id: "one", 283 Target: "swarming", 284 }, 285 }, 286 } 287 err := validateBuildTaskUpdate(ctx, req) 288 So(err, ShouldNotBeNil) 289 So(err.Error(), ShouldContainSubstring, "build_id required") 290 }) 291 Convey("is missing task ID", func() { 292 req := &pb.BuildTaskUpdate{ 293 BuildId: "1", 294 Task: &pb.Task{ 295 Status: pb.Status_STARTED, 296 }, 297 } 298 err := validateBuildTaskUpdate(ctx, req) 299 So(err, ShouldNotBeNil) 300 So(err.Error(), ShouldContainSubstring, "task.id: required") 301 }) 302 Convey("is missing update ID", func() { 303 req := &pb.BuildTaskUpdate{ 304 BuildId: "1", 305 Task: &pb.Task{ 306 Status: pb.Status_STARTED, 307 Id: &pb.TaskID{ 308 Id: "one", 309 Target: "swarming", 310 }, 311 }, 312 } 313 err := validateBuildTaskUpdate(ctx, req) 314 So(err, ShouldNotBeNil) 315 So(err.Error(), ShouldContainSubstring, "task.UpdateId: required") 316 }) 317 Convey("is invalid task status: SCHEDULED", func() { 318 req := &pb.BuildTaskUpdate{ 319 BuildId: "1", 320 Task: &pb.Task{ 321 Status: pb.Status_SCHEDULED, 322 Id: &pb.TaskID{ 323 Id: "one", 324 Target: "swarming", 325 }, 326 UpdateId: 1, 327 }, 328 } 329 err := validateBuildTaskUpdate(ctx, req) 330 So(err, ShouldNotBeNil) 331 So(err.Error(), ShouldContainSubstring, "invalid status SCHEDULED") 332 }) 333 Convey("is invalid task status: ENDED_MASK", func() { 334 req := &pb.BuildTaskUpdate{ 335 BuildId: "1", 336 Task: &pb.Task{ 337 Status: pb.Status_ENDED_MASK, 338 Id: &pb.TaskID{ 339 Id: "one", 340 Target: "swarming", 341 }, 342 UpdateId: 1, 343 }, 344 } 345 err := validateBuildTaskUpdate(ctx, req) 346 So(err, ShouldNotBeNil) 347 So(err.Error(), ShouldContainSubstring, "invalid status ENDED_MASK") 348 }) 349 Convey("is invalid task status: STATUS_UNSPECIFIED", func() { 350 req := &pb.BuildTaskUpdate{ 351 BuildId: "1", 352 Task: &pb.Task{ 353 Status: pb.Status_STATUS_UNSPECIFIED, 354 Id: &pb.TaskID{ 355 Id: "one", 356 Target: "swarming", 357 }, 358 UpdateId: 1, 359 }, 360 } 361 err := validateBuildTaskUpdate(ctx, req) 362 So(err, ShouldNotBeNil) 363 So(err.Error(), ShouldContainSubstring, "invalid status STATUS_UNSPECIFIED") 364 }) 365 Convey("is invalid task detail", func() { 366 367 details := make(map[string]*structpb.Value) 368 for i := 0; i < 10000; i++ { 369 v, _ := structpb.NewValue("my really long detail, but it's not that long.") 370 details[strconv.Itoa(i)] = v 371 } 372 req := &pb.BuildTaskUpdate{ 373 BuildId: "1", 374 Task: &pb.Task{ 375 Status: pb.Status_STARTED, 376 Details: &structpb.Struct{ 377 Fields: details, 378 }, 379 Id: &pb.TaskID{ 380 Id: "one", 381 Target: "swarming", 382 }, 383 UpdateId: 1, 384 }, 385 } 386 err := validateBuildTaskUpdate(ctx, req) 387 So(err, ShouldNotBeNil) 388 So(err.Error(), ShouldContainSubstring, "task.details is greater than 10 kb") 389 }) 390 }) 391 } 392 393 func TestUpdateTaskEntity(t *testing.T) { 394 t.Parallel() 395 Convey("UpdateTaskEntity", t, func() { 396 ctx, sch := tq.TestingContext(memory.Use(context.Background()), nil) 397 ctx = txndefer.FilterRDS(ctx) 398 ctx = metrics.WithServiceInfo(ctx, "svc", "job", "ins") 399 400 t0 := testclock.TestRecentTimeUTC 401 402 taskProto := &pb.Task{ 403 Status: pb.Status_SCHEDULED, 404 Id: &pb.TaskID{ 405 Id: "1", 406 Target: "swarming", 407 }, 408 Link: "a link", 409 UpdateId: 50, 410 } 411 infraProto := &pb.BuildInfra{ 412 Backend: &pb.BuildInfra_Backend{ 413 Task: taskProto, 414 }, 415 } 416 buildProto := &pb.Build{ 417 Id: 1, 418 Builder: &pb.BuilderID{ 419 Project: "project", 420 Bucket: "bucket", 421 Builder: "builder", 422 }, 423 Status: pb.Status_STARTED, 424 } 425 buildModel := &model.Build{ 426 ID: 1, 427 Proto: buildProto, 428 CreateTime: t0, 429 Status: pb.Status_STARTED, 430 } 431 bk := datastore.KeyForObj(ctx, buildModel) 432 infraModel := &model.BuildInfra{ 433 Build: bk, 434 Proto: infraProto, 435 } 436 So(datastore.Put(ctx, buildModel, infraModel), ShouldBeNil) 437 Convey("normal task save", func() { 438 req := &pb.BuildTaskUpdate{ 439 BuildId: "1", 440 Task: &pb.Task{ 441 Status: pb.Status_STARTED, 442 Id: &pb.TaskID{ 443 Id: "1", 444 Target: "swarming", 445 }, 446 UpdateId: 100, 447 }, 448 } 449 err := updateTaskEntity(ctx, req, 1) 450 So(err, ShouldBeNil) 451 bk := datastore.KeyForObj(ctx, &model.Build{ID: 1}) 452 resultInfraModel := &model.BuildInfra{ 453 Build: bk, 454 } 455 result := datastore.Get(ctx, resultInfraModel) 456 So(result, ShouldBeNil) 457 So(resultInfraModel.Proto, ShouldResembleProto, &pb.BuildInfra{ 458 Backend: &pb.BuildInfra_Backend{ 459 Task: &pb.Task{ 460 Status: pb.Status_STARTED, 461 Id: &pb.TaskID{ 462 Id: "1", 463 Target: "swarming", 464 }, 465 Link: "a link", 466 UpdateId: 100, 467 }, 468 }, 469 }) 470 }) 471 472 Convey("old update_id", func() { 473 req := &pb.BuildTaskUpdate{ 474 BuildId: "1", 475 Task: &pb.Task{ 476 Status: pb.Status_STARTED, 477 Id: &pb.TaskID{ 478 Id: "1", 479 Target: "swarming", 480 }, 481 Link: "a link", 482 UpdateId: 2, 483 }, 484 } 485 err := updateTaskEntity(ctx, req, 1) 486 So(err, ShouldBeNil) 487 }) 488 489 Convey("end a task", func() { 490 bs := &model.BuildStatus{ 491 Build: bk, 492 Status: pb.Status_STARTED, 493 } 494 b, err := proto.Marshal(&pb.Build{ 495 Steps: []*pb.Step{ 496 { 497 Name: "step", 498 }, 499 }, 500 }) 501 So(err, ShouldBeNil) 502 steps := &model.BuildSteps{ 503 ID: 1, 504 Build: bk, 505 IsZipped: false, 506 Bytes: b, 507 } 508 So(datastore.Put(ctx, buildModel, infraModel, bs, steps), ShouldBeNil) 509 510 endReq := &pb.BuildTaskUpdate{ 511 BuildId: "1", 512 Task: &pb.Task{ 513 Status: pb.Status_INFRA_FAILURE, 514 Id: &pb.TaskID{ 515 Id: "1", 516 Target: "swarming", 517 }, 518 UpdateId: 200, 519 }, 520 } 521 err = updateTaskEntity(ctx, endReq, 1) 522 So(err, ShouldBeNil) 523 resultInfraModel := &model.BuildInfra{ 524 Build: bk, 525 } 526 result := datastore.Get(ctx, resultInfraModel, bs, buildModel, steps) 527 So(result, ShouldBeNil) 528 So(resultInfraModel.Proto, ShouldResembleProto, &pb.BuildInfra{ 529 Backend: &pb.BuildInfra_Backend{ 530 Task: &pb.Task{ 531 Status: pb.Status_INFRA_FAILURE, 532 Id: &pb.TaskID{ 533 Id: "1", 534 Target: "swarming", 535 }, 536 UpdateId: 200, 537 Link: "a link", 538 }, 539 }, 540 }) 541 542 So(sch.Tasks(), ShouldHaveLength, 4) 543 So(bs.Status, ShouldEqual, pb.Status_INFRA_FAILURE) 544 So(buildModel.Proto.Status, ShouldEqual, pb.Status_INFRA_FAILURE) 545 stp, err := steps.ToProto(ctx) 546 So(err, ShouldBeNil) 547 for _, s := range stp { 548 So(s.Status, ShouldEqual, pb.Status_CANCELED) 549 } 550 }) 551 Convey("start a task", func() { 552 buildModel.Proto.Status = pb.Status_SCHEDULED 553 infraModel.Proto.Backend.Task.Status = pb.Status_SCHEDULED 554 bs := &model.BuildStatus{ 555 Build: bk, 556 Status: pb.Status_SCHEDULED, 557 } 558 So(datastore.Put(ctx, buildModel, infraModel, bs), ShouldBeNil) 559 560 endReq := &pb.BuildTaskUpdate{ 561 BuildId: "1", 562 Task: &pb.Task{ 563 Status: pb.Status_STARTED, 564 Id: &pb.TaskID{ 565 Id: "1", 566 Target: "swarming", 567 }, 568 UpdateId: 200, 569 }, 570 } 571 err := updateTaskEntity(ctx, endReq, 1) 572 So(err, ShouldBeNil) 573 resultInfraModel := &model.BuildInfra{ 574 Build: bk, 575 } 576 result := datastore.Get(ctx, resultInfraModel, bs, buildModel) 577 So(result, ShouldBeNil) 578 So(resultInfraModel.Proto.Backend.Task.Status, ShouldEqual, pb.Status_STARTED) 579 580 So(sch.Tasks(), ShouldHaveLength, 0) 581 So(bs.Status, ShouldEqual, pb.Status_SCHEDULED) 582 So(buildModel.Proto.Status, ShouldEqual, pb.Status_SCHEDULED) 583 }) 584 585 Convey("RunTask did not return but UpdateBuildTask called", func() { 586 buildModel.Proto.Status = pb.Status_SCHEDULED 587 infraModel.Proto.Backend.Task.Id.Id = "" 588 infraModel.Proto.Backend.Task.UpdateId = 0 589 infraModel.Proto.Backend.Task.Link = "" 590 bs := &model.BuildStatus{ 591 Build: bk, 592 Status: pb.Status_SCHEDULED, 593 } 594 So(datastore.Put(ctx, buildModel, infraModel, bs), ShouldBeNil) 595 596 endReq := &pb.BuildTaskUpdate{ 597 BuildId: "1", 598 Task: &pb.Task{ 599 Status: pb.Status_STARTED, 600 Id: &pb.TaskID{ 601 Id: "1", 602 Target: "swarming", 603 }, 604 UpdateId: 200, 605 }, 606 } 607 err := updateTaskEntity(ctx, endReq, 1) 608 So(err, ShouldBeNil) 609 resultInfraModel := &model.BuildInfra{ 610 Build: bk, 611 } 612 result := datastore.Get(ctx, resultInfraModel, bs, buildModel) 613 So(result, ShouldBeNil) 614 So(resultInfraModel.Proto.Backend.Task.Status, ShouldEqual, pb.Status_STARTED) 615 616 So(sch.Tasks(), ShouldHaveLength, 0) 617 So(bs.Status, ShouldEqual, pb.Status_SCHEDULED) 618 So(buildModel.Proto.Status, ShouldEqual, pb.Status_SCHEDULED) 619 620 post_infra := &model.BuildInfra{Build: bk} 621 So(datastore.Get(ctx, post_infra), ShouldBeNil) 622 So(post_infra.Proto.Backend.Task, ShouldResembleProto, &pb.Task{ 623 Status: pb.Status_STARTED, 624 Id: &pb.TaskID{ 625 Id: "1", 626 Target: "swarming", 627 }, 628 UpdateId: 200, 629 }) 630 }) 631 }) 632 } 633 634 func TestUpdateBuildTask(t *testing.T) { 635 t.Parallel() 636 637 Convey("pubsub handler", t, func() { 638 ctx := memory.UseWithAppID(context.Background(), "dev~app-id") 639 ctx = cachingtest.WithGlobalCache(ctx, map[string]caching.BlobCache{ 640 "update-build-task-pubsub-msg-id": cachingtest.NewBlobCache(), 641 }) 642 So(config.SetTestSettingsCfg(ctx, &pb.SettingsCfg{ 643 Backends: []*pb.BackendSetting{ 644 { 645 Target: "swarming://chromium-swarm", 646 Hostname: "chromium-swarm.appspot.com", 647 Mode: &pb.BackendSetting_FullMode_{ 648 FullMode: &pb.BackendSetting_FullMode{ 649 PubsubId: "chromium-swarm-backend", 650 }, 651 }, 652 }, 653 { 654 Target: "foo://foo-backend", 655 Hostname: "foo.com", 656 Mode: &pb.BackendSetting_LiteMode_{}, 657 }, 658 }, 659 }), ShouldBeNil) 660 661 t0 := testclock.TestRecentTimeUTC 662 663 // Create a "scheduled" build. 664 build := &model.Build{ 665 ID: 1, 666 Proto: &pb.Build{ 667 Id: 1, 668 Builder: &pb.BuilderID{ 669 Project: "project", 670 Bucket: "bucket", 671 Builder: "builder", 672 }, 673 Status: pb.Status_SCHEDULED, 674 }, 675 CreateTime: t0, 676 } 677 bk := datastore.KeyForObj(ctx, build) 678 infra := &model.BuildInfra{ 679 Build: bk, 680 Proto: &pb.BuildInfra{ 681 Buildbucket: &pb.BuildInfra_Buildbucket{ 682 Hostname: "bbhost", 683 Agent: &pb.BuildInfra_Buildbucket_Agent{ 684 Input: &pb.BuildInfra_Buildbucket_Agent_Input{ 685 Data: map[string]*pb.InputDataRef{}, 686 }, 687 }, 688 }, 689 Backend: &pb.BuildInfra_Backend{ 690 Task: &pb.Task{ 691 Id: &pb.TaskID{ 692 Target: "swarming://chromium-swarm", 693 }, 694 UpdateId: 0, 695 }, 696 }, 697 }, 698 } 699 Convey("full mode", func() { 700 Convey("ok", func() { 701 // Update the backend task as if RunTask had responded. 702 infra.Proto.Backend.Task.Id.Id = "one" 703 infra.Proto.Backend.Task.UpdateId = 1 704 infra.Proto.Backend.Task.Status = pb.Status_SCHEDULED 705 So(datastore.Put(ctx, build, infra), ShouldBeNil) 706 req := &pb.BuildTaskUpdate{ 707 BuildId: "1", 708 Task: &pb.Task{ 709 Status: pb.Status_STARTED, 710 Id: &pb.TaskID{ 711 Id: "one", 712 Target: "swarming://chromium-swarm", 713 }, 714 UpdateId: 2, 715 SummaryMarkdown: "imo, html is ugly to read", 716 }, 717 } 718 body := makeUpdateBuildTaskPubsubMsg(req, "msg_id_1", "chromium-swarm-backend") 719 So(UpdateBuildTask(ctx, body), ShouldBeRPCOK) 720 721 expectedBuildInfra := &model.BuildInfra{Build: datastore.KeyForObj(ctx, &model.Build{ID: 1})} 722 So(datastore.Get(ctx, expectedBuildInfra), ShouldBeNil) 723 So(expectedBuildInfra.Proto.Backend.Task.Status, ShouldEqual, pb.Status_STARTED) 724 So(expectedBuildInfra.Proto.Backend.Task.UpdateId, ShouldEqual, 2) 725 So(expectedBuildInfra.Proto.Backend.Task.SummaryMarkdown, ShouldEqual, "imo, html is ugly to read") 726 }) 727 728 Convey("task is not registered", func() { 729 So(datastore.Put(ctx, build, infra), ShouldBeNil) 730 req := &pb.BuildTaskUpdate{ 731 BuildId: "1", 732 Task: &pb.Task{ 733 Status: pb.Status_STARTED, 734 Id: &pb.TaskID{ 735 Id: "one", 736 Target: "swarming://chromium-swarm", 737 }, 738 UpdateId: 2, 739 SummaryMarkdown: "imo, html is ugly to read", 740 }, 741 } 742 body := makeUpdateBuildTaskPubsubMsg(req, "msg_id_1", "chromium-swarm-backend") 743 So(UpdateBuildTask(ctx, body), ShouldErrLike, "No task is associated with the build. Cannot update.") 744 }) 745 746 Convey("subscription id mismatch", func() { 747 req := &pb.BuildTaskUpdate{ 748 BuildId: "1", 749 Task: &pb.Task{ 750 Status: pb.Status_STARTED, 751 Id: &pb.TaskID{ 752 Id: "one", 753 Target: "swarming://chromium-swarm", 754 }, 755 UpdateId: 2, 756 SummaryMarkdown: "imo, html is ugly to read", 757 }, 758 } 759 body := makeUpdateBuildTaskPubsubMsg(req, "msg_id_1", "chromium-swarm-backend-v2") 760 So(UpdateBuildTask(ctx, body), ShouldErrLike, "pubsub subscription projects/app-id/subscriptions/chromium-swarm-backend-v2 did not match the one configured for target swarming://chromium-swarm") 761 }) 762 }) 763 Convey("lite mode", func() { 764 req := &pb.BuildTaskUpdate{ 765 BuildId: "1", 766 Task: &pb.Task{ 767 Status: pb.Status_STARTED, 768 Id: &pb.TaskID{ 769 Id: "one", 770 Target: "foo://foo-backend", 771 }, 772 UpdateId: 2, 773 }, 774 } 775 body := makeUpdateBuildTaskPubsubMsg(req, "msg_id_1", "foo") 776 So(UpdateBuildTask(ctx, body), ShouldErrLike, "backend target foo://foo-backend is in lite mode. The task update isn't supported") 777 }) 778 }) 779 } 780 781 func makeUpdateBuildTaskPubsubMsg(req *pb.BuildTaskUpdate, msgID, subID string) io.Reader { 782 data, err := proto.Marshal(req) 783 if err != nil { 784 return nil 785 } 786 msg := &pushRequest{ 787 Message: pubsub.PubsubMessage{ 788 Data: base64.StdEncoding.EncodeToString(data), 789 MessageId: msgID, 790 }, 791 Subscription: "projects/app-id/subscriptions/" + subID, 792 } 793 jmsg, _ := json.Marshal(msg) 794 return bytes.NewReader(jmsg) 795 }