go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/swarming/server/rbe/session_test.go (about) 1 // Copyright 2023 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 rbe 16 17 import ( 18 "context" 19 "testing" 20 "time" 21 22 statuspb "google.golang.org/genproto/googleapis/rpc/status" 23 "google.golang.org/grpc" 24 "google.golang.org/grpc/codes" 25 "google.golang.org/grpc/status" 26 "google.golang.org/protobuf/types/known/anypb" 27 "google.golang.org/protobuf/types/known/timestamppb" 28 29 "go.chromium.org/luci/common/clock/testclock" 30 "go.chromium.org/luci/server/secrets" 31 32 "go.chromium.org/luci/swarming/internal/remoteworkers" 33 internalspb "go.chromium.org/luci/swarming/proto/internals" 34 "go.chromium.org/luci/swarming/server/botsrv" 35 "go.chromium.org/luci/swarming/server/hmactoken" 36 37 . "github.com/smartystreets/goconvey/convey" 38 . "go.chromium.org/luci/common/testing/assertions" 39 ) 40 41 func TestSessionServer(t *testing.T) { 42 t.Parallel() 43 44 Convey("With server", t, func() { 45 const ( 46 fakeRBEInstance = "fake-rbe-instance" 47 fakeBotID = "fake-bot-id" 48 fakeSessionID = "fake-rbe-session" 49 fakeFirstLeaseID = "fake-first-lease-id" 50 fakeSecondLeaseID = "fake-second-lease-id" 51 fakeFirstTaskID = "fake-first-task-id" 52 fakeSecondTaskID = "fake-second-task-id" 53 ) 54 55 fakePollState := &internalspb.PollState{ 56 Id: "fake-poll-token", 57 RbeInstance: fakeRBEInstance, 58 IpAllowlist: "fake-ip-allowlist", 59 } 60 61 fakeRequest := &botsrv.Request{ 62 BotID: fakeBotID, 63 SessionID: fakeSessionID, 64 PollState: fakePollState, 65 Dimensions: map[string][]string{ 66 "id": {fakeBotID}, 67 "pool": {"some-pool"}, 68 "extra1": {"a", "b"}, 69 "extra2": {"c", "d"}, 70 }, 71 } 72 73 payload := func(taskID string) *anypb.Any { 74 msg, err := anypb.New(&internalspb.TaskPayload{TaskId: taskID}) 75 So(err, ShouldBeNil) 76 return msg 77 } 78 79 result := func() *anypb.Any { 80 msg, err := anypb.New(&internalspb.TaskResult{}) 81 So(err, ShouldBeNil) 82 return msg 83 } 84 85 now := time.Date(2044, time.April, 4, 4, 4, 4, 4, time.UTC) 86 ctx := context.Background() 87 ctx, _ = testclock.UseTime(ctx, now) 88 89 rbe := &mockedBotsClient{} 90 91 srv := &SessionServer{ 92 rbe: rbe, 93 hmacSecret: hmactoken.NewStaticSecret(secrets.Secret{ 94 Active: []byte("secret"), 95 }), 96 } 97 98 Convey("CreateBotSession works", func() { 99 rbe.expectCreateBotSession(func(r *remoteworkers.CreateBotSessionRequest) (*remoteworkers.BotSession, error) { 100 So(r, ShouldResembleProto, &remoteworkers.CreateBotSessionRequest{ 101 Parent: fakeRBEInstance, 102 BotSession: &remoteworkers.BotSession{ 103 BotId: fakeBotID, 104 Status: remoteworkers.BotStatus_INITIALIZING, 105 Version: "bot-version", 106 Worker: &remoteworkers.Worker{ 107 Properties: []*remoteworkers.Worker_Property{ 108 {Key: "rbePoolID", Value: "rbe-pool-id"}, 109 {Key: "rbePoolVersion", Value: "rbe-pool-version"}, 110 }, 111 Devices: []*remoteworkers.Device{ 112 { 113 Handle: "primary", 114 Properties: []*remoteworkers.Device_Property{ 115 {Key: "label:extra1", Value: "a"}, 116 {Key: "label:extra1", Value: "b"}, 117 {Key: "label:extra2", Value: "c"}, 118 {Key: "label:extra2", Value: "d"}, 119 {Key: "label:pool", Value: "some-pool"}, 120 }, 121 }, 122 }, 123 }, 124 }, 125 }) 126 return &remoteworkers.BotSession{ 127 Name: fakeSessionID, 128 Status: remoteworkers.BotStatus_INITIALIZING, 129 }, nil 130 }) 131 132 resp, err := srv.CreateBotSession(ctx, &CreateBotSessionRequest{ 133 Dimensions: map[string][]string{ 134 "ignored": {""}, // uses validated botsrv.Request.Dimensions instead 135 }, 136 BotVersion: "bot-version", 137 WorkerProperties: &WorkerProperties{ 138 PoolID: "rbe-pool-id", 139 PoolVersion: "rbe-pool-version", 140 }, 141 }, fakeRequest) 142 143 So(err, ShouldBeNil) 144 145 expectedExpiry := now.Add(sessionTokenExpiry).Round(time.Second) 146 147 msg := resp.(*CreateBotSessionResponse) 148 So(msg.SessionExpiry, ShouldEqual, expectedExpiry.Unix()) 149 So(msg.SessionID, ShouldEqual, fakeSessionID) 150 151 session := &internalspb.BotSession{} 152 So(srv.hmacSecret.ValidateToken(msg.SessionToken, session), ShouldBeNil) 153 So(session, ShouldResembleProto, &internalspb.BotSession{ 154 RbeBotSessionId: fakeSessionID, 155 PollState: fakePollState, 156 Expiry: timestamppb.New(expectedExpiry), 157 }) 158 }) 159 160 Convey("CreateBotSession propagates RBE error", func() { 161 rbe.expectCreateBotSession(func(r *remoteworkers.CreateBotSessionRequest) (*remoteworkers.BotSession, error) { 162 return nil, status.Errorf(codes.FailedPrecondition, "boom") 163 }) 164 _, err := srv.CreateBotSession(ctx, &CreateBotSessionRequest{}, fakeRequest) 165 So(err, ShouldHaveGRPCStatus, codes.FailedPrecondition) 166 So(err, ShouldErrLike, "boom") 167 }) 168 169 Convey("UpdateBotSession IDLE", func() { 170 rbe.expectUpdateBotSession(func(r *remoteworkers.UpdateBotSessionRequest) (*remoteworkers.BotSession, error) { 171 So(r, ShouldResembleProto, &remoteworkers.UpdateBotSessionRequest{ 172 Name: fakeSessionID, 173 BotSession: &remoteworkers.BotSession{ 174 Name: fakeSessionID, 175 BotId: fakeBotID, 176 Status: remoteworkers.BotStatus_OK, 177 Version: "bot-version", 178 Worker: &remoteworkers.Worker{ 179 Properties: []*remoteworkers.Worker_Property{ 180 {Key: "rbePoolID", Value: "rbe-pool-id"}, 181 {Key: "rbePoolVersion", Value: "rbe-pool-version"}, 182 }, 183 Devices: []*remoteworkers.Device{ 184 { 185 Handle: "primary", 186 Properties: []*remoteworkers.Device_Property{ 187 {Key: "label:extra1", Value: "a"}, 188 {Key: "label:extra1", Value: "b"}, 189 {Key: "label:extra2", Value: "c"}, 190 {Key: "label:extra2", Value: "d"}, 191 {Key: "label:pool", Value: "some-pool"}, 192 }, 193 }, 194 }, 195 }, 196 }, 197 }) 198 return &remoteworkers.BotSession{ 199 Name: fakeSessionID, 200 Status: remoteworkers.BotStatus_OK, 201 }, nil 202 }) 203 204 resp, err := srv.UpdateBotSession(ctx, &UpdateBotSessionRequest{ 205 Status: "OK", 206 Dimensions: map[string][]string{ 207 "ignored": {""}, // uses validated botsrv.Request.Dimensions instead 208 }, 209 BotVersion: "bot-version", 210 WorkerProperties: &WorkerProperties{ 211 PoolID: "rbe-pool-id", 212 PoolVersion: "rbe-pool-version", 213 }, 214 }, fakeRequest) 215 216 So(err, ShouldBeNil) 217 218 expectedExpiry := now.Add(sessionTokenExpiry).Round(time.Second) 219 220 msg := resp.(*UpdateBotSessionResponse) 221 So(msg.Status, ShouldEqual, "OK") 222 So(msg.SessionExpiry, ShouldEqual, expectedExpiry.Unix()) 223 So(msg.Lease, ShouldBeNil) 224 225 session := &internalspb.BotSession{} 226 So(srv.hmacSecret.ValidateToken(msg.SessionToken, session), ShouldBeNil) 227 So(session, ShouldResembleProto, &internalspb.BotSession{ 228 RbeBotSessionId: fakeSessionID, 229 PollState: fakePollState, 230 Expiry: timestamppb.New(expectedExpiry), 231 }) 232 }) 233 234 Convey("UpdateBotSession IDLE + DEADLINE_EXCEEDED", func() { 235 rbe.expectUpdateBotSession(func(r *remoteworkers.UpdateBotSessionRequest) (*remoteworkers.BotSession, error) { 236 return nil, status.Errorf(codes.DeadlineExceeded, "boom") 237 }) 238 239 resp, err := srv.UpdateBotSession(ctx, &UpdateBotSessionRequest{ 240 Status: "OK", 241 }, fakeRequest) 242 243 So(err, ShouldBeNil) 244 245 expectedExpiry := now.Add(sessionTokenExpiry).Round(time.Second) 246 247 msg := resp.(*UpdateBotSessionResponse) 248 So(msg.Status, ShouldEqual, "OK") 249 So(msg.SessionExpiry, ShouldEqual, expectedExpiry.Unix()) 250 So(msg.Lease, ShouldBeNil) 251 }) 252 253 Convey("UpdateBotSession TERMINATING", func() { 254 rbe.expectUpdateBotSession(func(r *remoteworkers.UpdateBotSessionRequest) (*remoteworkers.BotSession, error) { 255 So(r.BotSession.Status, ShouldEqual, remoteworkers.BotStatus_BOT_TERMINATING) 256 return &remoteworkers.BotSession{ 257 Name: fakeSessionID, 258 Status: remoteworkers.BotStatus_OK, 259 Leases: []*remoteworkers.Lease{ 260 // Will be ignored. 261 { 262 Id: fakeFirstLeaseID, 263 Payload: payload(fakeFirstTaskID), 264 State: remoteworkers.LeaseState_PENDING, 265 }, 266 }, 267 }, nil 268 }) 269 270 resp, err := srv.UpdateBotSession(ctx, &UpdateBotSessionRequest{ 271 Status: "BOT_TERMINATING", 272 }, fakeRequest) 273 274 So(err, ShouldBeNil) 275 276 msg := resp.(*UpdateBotSessionResponse) 277 So(msg.Status, ShouldEqual, "OK") 278 So(msg.Lease, ShouldBeNil) 279 }) 280 281 Convey("UpdateBotSession TERMINATING by RBE", func() { 282 rbe.expectUpdateBotSession(func(r *remoteworkers.UpdateBotSessionRequest) (*remoteworkers.BotSession, error) { 283 So(r.BotSession.Status, ShouldEqual, remoteworkers.BotStatus_OK) 284 return &remoteworkers.BotSession{ 285 Name: fakeSessionID, 286 Status: remoteworkers.BotStatus_BOT_TERMINATING, 287 Leases: []*remoteworkers.Lease{ 288 // Will be ignored. 289 { 290 Id: fakeFirstLeaseID, 291 Payload: payload(fakeFirstTaskID), 292 State: remoteworkers.LeaseState_PENDING, 293 }, 294 }, 295 }, nil 296 }) 297 298 resp, err := srv.UpdateBotSession(ctx, &UpdateBotSessionRequest{ 299 Status: "OK", 300 }, fakeRequest) 301 302 So(err, ShouldBeNil) 303 304 msg := resp.(*UpdateBotSessionResponse) 305 So(msg.Status, ShouldEqual, "BOT_TERMINATING") 306 So(msg.Lease, ShouldBeNil) 307 }) 308 309 Convey("UpdateBotSession IDLE => PENDING", func() { 310 rbe.expectUpdateBotSession(func(r *remoteworkers.UpdateBotSessionRequest) (*remoteworkers.BotSession, error) { 311 So(r.BotSession.Status, ShouldEqual, remoteworkers.BotStatus_OK) 312 return &remoteworkers.BotSession{ 313 Name: fakeSessionID, 314 Status: remoteworkers.BotStatus_OK, 315 Leases: []*remoteworkers.Lease{ 316 { 317 Id: fakeFirstLeaseID, 318 Payload: payload(fakeFirstTaskID), 319 State: remoteworkers.LeaseState_PENDING, 320 }, 321 // Will be ignored. 322 { 323 Id: fakeSecondLeaseID, 324 Payload: payload(fakeSecondTaskID), 325 State: remoteworkers.LeaseState_PENDING, 326 }, 327 }, 328 }, nil 329 }) 330 331 resp, err := srv.UpdateBotSession(ctx, &UpdateBotSessionRequest{ 332 Status: "OK", 333 }, fakeRequest) 334 335 So(err, ShouldBeNil) 336 337 lease := resp.(*UpdateBotSessionResponse).Lease 338 So(lease.ID, ShouldEqual, fakeFirstLeaseID) 339 So(lease.State, ShouldEqual, "PENDING") 340 So(lease.Payload, ShouldResembleProto, &internalspb.TaskPayload{ 341 TaskId: fakeFirstTaskID, 342 }) 343 }) 344 345 Convey("UpdateBotSession ACTIVE", func() { 346 rbe.expectUpdateBotSession(func(r *remoteworkers.UpdateBotSessionRequest) (*remoteworkers.BotSession, error) { 347 So(r.BotSession.Status, ShouldEqual, remoteworkers.BotStatus_OK) 348 So(r.BotSession.Leases, ShouldResembleProto, []*remoteworkers.Lease{ 349 { 350 Id: fakeFirstLeaseID, 351 State: remoteworkers.LeaseState_ACTIVE, 352 }, 353 }) 354 return &remoteworkers.BotSession{ 355 Name: fakeSessionID, 356 Status: remoteworkers.BotStatus_OK, 357 Leases: []*remoteworkers.Lease{ 358 { 359 Id: fakeFirstLeaseID, 360 State: remoteworkers.LeaseState_ACTIVE, 361 }, 362 // Will be ignored. 363 { 364 Id: fakeSecondLeaseID, 365 Payload: payload(fakeSecondTaskID), 366 State: remoteworkers.LeaseState_PENDING, 367 }, 368 }, 369 }, nil 370 }) 371 372 resp, err := srv.UpdateBotSession(ctx, &UpdateBotSessionRequest{ 373 Status: "OK", 374 Lease: &Lease{ 375 ID: fakeFirstLeaseID, 376 State: "ACTIVE", 377 }, 378 }, fakeRequest) 379 380 So(err, ShouldBeNil) 381 382 lease := resp.(*UpdateBotSessionResponse).Lease 383 So(lease.ID, ShouldEqual, fakeFirstLeaseID) 384 So(lease.State, ShouldEqual, "ACTIVE") 385 So(lease.Payload, ShouldBeNil) 386 }) 387 388 Convey("UpdateBotSession ACTIVE => CANCELLED", func() { 389 rbe.expectUpdateBotSession(func(r *remoteworkers.UpdateBotSessionRequest) (*remoteworkers.BotSession, error) { 390 So(r.BotSession.Status, ShouldEqual, remoteworkers.BotStatus_OK) 391 So(r.BotSession.Leases, ShouldResembleProto, []*remoteworkers.Lease{ 392 { 393 Id: fakeFirstLeaseID, 394 State: remoteworkers.LeaseState_ACTIVE, 395 }, 396 }) 397 return &remoteworkers.BotSession{ 398 Name: fakeSessionID, 399 Status: remoteworkers.BotStatus_OK, 400 Leases: []*remoteworkers.Lease{ 401 { 402 Id: fakeFirstLeaseID, 403 State: remoteworkers.LeaseState_CANCELLED, 404 }, 405 // Will be ignored. 406 { 407 Id: fakeSecondLeaseID, 408 Payload: payload(fakeSecondTaskID), 409 State: remoteworkers.LeaseState_PENDING, 410 }, 411 }, 412 }, nil 413 }) 414 415 resp, err := srv.UpdateBotSession(ctx, &UpdateBotSessionRequest{ 416 Status: "OK", 417 Lease: &Lease{ 418 ID: fakeFirstLeaseID, 419 State: "ACTIVE", 420 }, 421 }, fakeRequest) 422 423 So(err, ShouldBeNil) 424 425 lease := resp.(*UpdateBotSessionResponse).Lease 426 So(lease.ID, ShouldEqual, fakeFirstLeaseID) 427 So(lease.State, ShouldEqual, "CANCELLED") 428 So(lease.Payload, ShouldBeNil) 429 }) 430 431 Convey("UpdateBotSession ACTIVE => IDLE", func() { 432 rbe.expectUpdateBotSession(func(r *remoteworkers.UpdateBotSessionRequest) (*remoteworkers.BotSession, error) { 433 So(r.BotSession.Status, ShouldEqual, remoteworkers.BotStatus_OK) 434 So(r.BotSession.Leases, ShouldResembleProto, []*remoteworkers.Lease{ 435 { 436 Id: fakeFirstLeaseID, 437 State: remoteworkers.LeaseState_COMPLETED, 438 Status: &statuspb.Status{}, 439 Result: result(), 440 }, 441 }) 442 return &remoteworkers.BotSession{ 443 Name: fakeSessionID, 444 Status: remoteworkers.BotStatus_OK, 445 }, nil 446 }) 447 448 resp, err := srv.UpdateBotSession(ctx, &UpdateBotSessionRequest{ 449 Status: "OK", 450 Lease: &Lease{ 451 ID: fakeFirstLeaseID, 452 State: "COMPLETED", 453 Result: &internalspb.TaskResult{}, 454 }, 455 }, fakeRequest) 456 457 So(err, ShouldBeNil) 458 So(resp.(*UpdateBotSessionResponse).Lease, ShouldBeNil) 459 }) 460 461 Convey("UpdateBotSession ACTIVE => PENDING", func() { 462 rbe.expectUpdateBotSession(func(r *remoteworkers.UpdateBotSessionRequest) (*remoteworkers.BotSession, error) { 463 So(r.BotSession.Status, ShouldEqual, remoteworkers.BotStatus_OK) 464 So(r.BotSession.Leases, ShouldResembleProto, []*remoteworkers.Lease{ 465 { 466 Id: fakeFirstLeaseID, 467 State: remoteworkers.LeaseState_COMPLETED, 468 Status: &statuspb.Status{}, 469 Result: result(), 470 }, 471 }) 472 return &remoteworkers.BotSession{ 473 Name: fakeSessionID, 474 Status: remoteworkers.BotStatus_OK, 475 Leases: []*remoteworkers.Lease{ 476 { 477 Id: fakeSecondLeaseID, 478 Payload: payload(fakeSecondTaskID), 479 State: remoteworkers.LeaseState_PENDING, 480 }, 481 }, 482 }, nil 483 }) 484 485 resp, err := srv.UpdateBotSession(ctx, &UpdateBotSessionRequest{ 486 Status: "OK", 487 Lease: &Lease{ 488 ID: fakeFirstLeaseID, 489 State: "COMPLETED", 490 Result: &internalspb.TaskResult{}, 491 }, 492 }, fakeRequest) 493 494 So(err, ShouldBeNil) 495 496 lease := resp.(*UpdateBotSessionResponse).Lease 497 So(lease.ID, ShouldEqual, fakeSecondLeaseID) 498 So(lease.State, ShouldEqual, "PENDING") 499 So(lease.Payload, ShouldResembleProto, &internalspb.TaskPayload{ 500 TaskId: fakeSecondTaskID, 501 }) 502 }) 503 504 Convey("UpdateBotSession no session ID", func() { 505 _, err := srv.UpdateBotSession(ctx, &UpdateBotSessionRequest{ 506 Status: "OK", 507 }, &botsrv.Request{}) 508 So(err, ShouldHaveGRPCStatus, codes.InvalidArgument) 509 So(err, ShouldErrLike, "missing session ID") 510 }) 511 512 Convey("UpdateBotSession expired session token", func() { 513 resp, err := srv.UpdateBotSession(ctx, &UpdateBotSessionRequest{ 514 Status: "OK", 515 }, &botsrv.Request{ 516 SessionTokenExpired: true, 517 }) 518 So(err, ShouldBeNil) 519 So(resp, ShouldResemble, &UpdateBotSessionResponse{ 520 Status: "BOT_TERMINATING", 521 }) 522 }) 523 524 Convey("UpdateBotSession propagates RBE error", func() { 525 rbe.expectUpdateBotSession(func(r *remoteworkers.UpdateBotSessionRequest) (*remoteworkers.BotSession, error) { 526 return nil, status.Errorf(codes.FailedPrecondition, "boom") 527 }) 528 _, err := srv.UpdateBotSession(ctx, &UpdateBotSessionRequest{ 529 Status: "OK", 530 }, fakeRequest) 531 So(err, ShouldHaveGRPCStatus, codes.FailedPrecondition) 532 So(err, ShouldErrLike, "boom") 533 }) 534 535 Convey("UpdateBotSession bad session status", func() { 536 _, err := srv.UpdateBotSession(ctx, &UpdateBotSessionRequest{ 537 Status: "huh", 538 }, fakeRequest) 539 So(err, ShouldHaveGRPCStatus, codes.InvalidArgument) 540 So(err, ShouldErrLike, "unrecognized session status") 541 }) 542 543 Convey("UpdateBotSession missing session status", func() { 544 _, err := srv.UpdateBotSession(ctx, &UpdateBotSessionRequest{}, fakeRequest) 545 So(err, ShouldHaveGRPCStatus, codes.InvalidArgument) 546 So(err, ShouldErrLike, "missing session status") 547 }) 548 549 Convey("UpdateBotSession bad lease state", func() { 550 _, err := srv.UpdateBotSession(ctx, &UpdateBotSessionRequest{ 551 Status: "OK", 552 Lease: &Lease{State: "huh"}, 553 }, fakeRequest) 554 So(err, ShouldHaveGRPCStatus, codes.InvalidArgument) 555 So(err, ShouldErrLike, "unrecognized lease state") 556 }) 557 558 Convey("UpdateBotSession missing lease state", func() { 559 _, err := srv.UpdateBotSession(ctx, &UpdateBotSessionRequest{ 560 Status: "OK", 561 Lease: &Lease{}, 562 }, fakeRequest) 563 So(err, ShouldHaveGRPCStatus, codes.InvalidArgument) 564 So(err, ShouldErrLike, "missing lease state") 565 }) 566 567 Convey("UpdateBotSession unexpected lease state", func() { 568 _, err := srv.UpdateBotSession(ctx, &UpdateBotSessionRequest{ 569 Status: "OK", 570 Lease: &Lease{State: "CANCELLED"}, 571 }, fakeRequest) 572 So(err, ShouldHaveGRPCStatus, codes.InvalidArgument) 573 So(err, ShouldErrLike, "unexpected lease state") 574 }) 575 576 Convey("UpdateBotSession ACTIVE lease disappears", func() { 577 rbe.expectUpdateBotSession(func(r *remoteworkers.UpdateBotSessionRequest) (*remoteworkers.BotSession, error) { 578 return &remoteworkers.BotSession{ 579 Name: fakeSessionID, 580 Status: remoteworkers.BotStatus_OK, 581 Leases: []*remoteworkers.Lease{ 582 // Will be ignored. 583 { 584 Id: fakeSecondLeaseID, 585 Payload: payload(fakeSecondTaskID), 586 State: remoteworkers.LeaseState_PENDING, 587 }, 588 }, 589 }, nil 590 }) 591 592 resp, err := srv.UpdateBotSession(ctx, &UpdateBotSessionRequest{ 593 Status: "OK", 594 Lease: &Lease{ 595 ID: fakeFirstLeaseID, 596 State: "ACTIVE", 597 }, 598 }, fakeRequest) 599 600 So(err, ShouldBeNil) 601 So(resp.(*UpdateBotSessionResponse).Lease, ShouldBeNil) 602 }) 603 604 Convey("UpdateBotSession unexpected ACTIVE lease transition", func() { 605 rbe.expectUpdateBotSession(func(r *remoteworkers.UpdateBotSessionRequest) (*remoteworkers.BotSession, error) { 606 return &remoteworkers.BotSession{ 607 Name: fakeSessionID, 608 Status: remoteworkers.BotStatus_OK, 609 Leases: []*remoteworkers.Lease{ 610 { 611 Id: fakeFirstLeaseID, 612 State: remoteworkers.LeaseState_PENDING, 613 }, 614 }, 615 }, nil 616 }) 617 618 _, err := srv.UpdateBotSession(ctx, &UpdateBotSessionRequest{ 619 Status: "OK", 620 Lease: &Lease{ 621 ID: fakeFirstLeaseID, 622 State: "ACTIVE", 623 }, 624 }, fakeRequest) 625 626 So(err, ShouldHaveGRPCStatus, codes.Internal) 627 So(err, ShouldErrLike, "unexpected ACTIVE lease state transition to PENDING") 628 }) 629 630 Convey("UpdateBotSession unrecognized payload type", func() { 631 rbe.expectUpdateBotSession(func(r *remoteworkers.UpdateBotSessionRequest) (*remoteworkers.BotSession, error) { 632 wrong, _ := anypb.New(×tamppb.Timestamp{}) 633 return &remoteworkers.BotSession{ 634 Name: fakeSessionID, 635 Status: remoteworkers.BotStatus_OK, 636 Leases: []*remoteworkers.Lease{ 637 { 638 Id: fakeFirstLeaseID, 639 Payload: wrong, 640 State: remoteworkers.LeaseState_PENDING, 641 }, 642 }, 643 }, nil 644 }) 645 646 _, err := srv.UpdateBotSession(ctx, &UpdateBotSessionRequest{ 647 Status: "OK", 648 }, fakeRequest) 649 650 So(err, ShouldHaveGRPCStatus, codes.Internal) 651 So(err, ShouldErrLike, "failed to unmarshal pending lease payload") 652 }) 653 }) 654 } 655 656 //////////////////////////////////////////////////////////////////////////////// 657 658 type ( 659 expectedCreate func(*remoteworkers.CreateBotSessionRequest) (*remoteworkers.BotSession, error) 660 expectedUpdate func(*remoteworkers.UpdateBotSessionRequest) (*remoteworkers.BotSession, error) 661 ) 662 663 type mockedBotsClient struct { 664 expected []any // either expectedCreate or expectedUpdate 665 } 666 667 func (m *mockedBotsClient) expectCreateBotSession(cb expectedCreate) { 668 m.expected = append(m.expected, cb) 669 } 670 671 func (m *mockedBotsClient) expectUpdateBotSession(cb expectedUpdate) { 672 m.expected = append(m.expected, cb) 673 } 674 675 func (m *mockedBotsClient) popExpected() (cb any) { 676 So(m.expected, ShouldNotBeEmpty) 677 cb, m.expected = m.expected[0], m.expected[1:] 678 return cb 679 } 680 681 func (m *mockedBotsClient) CreateBotSession(ctx context.Context, in *remoteworkers.CreateBotSessionRequest, opts ...grpc.CallOption) (*remoteworkers.BotSession, error) { 682 cb := m.popExpected() 683 So(cb, ShouldHaveSameTypeAs, expectedCreate(nil)) 684 return cb.(expectedCreate)(in) 685 } 686 687 func (m *mockedBotsClient) UpdateBotSession(ctx context.Context, in *remoteworkers.UpdateBotSessionRequest, opts ...grpc.CallOption) (*remoteworkers.BotSession, error) { 688 cb := m.popExpected() 689 So(cb, ShouldHaveSameTypeAs, expectedUpdate(nil)) 690 return cb.(expectedUpdate)(in) 691 }