go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/swarming/server/rpcs/bots_get_bot_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 rpcs 16 17 import ( 18 "context" 19 "testing" 20 "time" 21 22 "google.golang.org/grpc/codes" 23 "google.golang.org/protobuf/types/known/timestamppb" 24 25 "go.chromium.org/luci/auth/identity" 26 "go.chromium.org/luci/gae/impl/memory" 27 "go.chromium.org/luci/gae/service/datastore" 28 29 apipb "go.chromium.org/luci/swarming/proto/api_v2" 30 "go.chromium.org/luci/swarming/server/acls" 31 "go.chromium.org/luci/swarming/server/model" 32 33 . "github.com/smartystreets/goconvey/convey" 34 . "go.chromium.org/luci/common/testing/assertions" 35 ) 36 37 func TestGetBot(t *testing.T) { 38 t.Parallel() 39 40 state := NewMockedRequestState() 41 42 // Bots that should be visible. 43 state.MockBot("alive-bot", "visible-pool") 44 state.MockBot("deleted-bot", "visible-pool") 45 state.MockBot("unconnected-bot", "visible-pool") 46 state.MockPool("visible-pool", "project:visible-realm") 47 state.MockPerm("project:visible-realm", acls.PermPoolsListBots) 48 49 // A bot the caller has no permissions over. 50 state.MockBot("hidden-bot", "hidden-pool") 51 state.MockPool("hidden-pool", "project:hidden-realm") 52 53 ctx := memory.Use(context.Background()) 54 datastore.GetTestable(ctx).AutoIndex(true) 55 datastore.GetTestable(ctx).Consistent(true) 56 57 testTime := time.Date(2023, time.January, 1, 2, 3, 4, 0, time.UTC) 58 59 fakeBotCommon := func(id string) model.BotCommon { 60 return model.BotCommon{ 61 State: []byte(`{"state": "1"}`), 62 ExternalIP: "1.2.3.4", 63 AuthenticatedAs: identity.Identity("bot:" + id), 64 Version: "some-version", 65 Quarantined: false, 66 Maintenance: "maintenance msg", 67 TaskID: "task-id", 68 LastSeen: datastore.NewUnindexedOptional(testTime.Add(1 * time.Hour)), 69 IdleSince: datastore.NewUnindexedOptional(testTime.Add(2 * time.Hour)), 70 } 71 } 72 73 _ = datastore.Put(ctx, 74 &model.BotInfo{ 75 Key: model.BotInfoKey(ctx, "alive-bot"), 76 Dimensions: []string{"a:1", "b:2"}, 77 Composite: []model.BotStateEnum{ 78 model.BotStateNotInMaintenance, 79 model.BotStateAlive, 80 model.BotStateHealthy, 81 model.BotStateBusy, 82 }, 83 FirstSeen: testTime, 84 TaskName: "task-name", 85 BotCommon: fakeBotCommon("alive-bot"), 86 }, 87 &model.BotEvent{ 88 Key: datastore.NewKey(ctx, "BotEvent", "", 200, model.BotRootKey(ctx, "deleted-bot")), 89 Timestamp: testTime, // old event, should be ignored 90 Dimensions: []string{"ignore:me"}, 91 BotCommon: fakeBotCommon("deleted-bot"), 92 }, 93 &model.BotEvent{ 94 Key: datastore.NewKey(ctx, "BotEvent", "", 100, model.BotRootKey(ctx, "deleted-bot")), 95 Timestamp: testTime.Add(time.Hour), // the most recent event, should be used 96 Dimensions: []string{"use:me"}, 97 BotCommon: fakeBotCommon("deleted-bot"), 98 }, 99 ) 100 101 call := func(botID string) (*apipb.BotInfo, error) { 102 ctx := MockRequestState(ctx, state) 103 return (&BotsServer{}).GetBot(ctx, &apipb.BotRequest{ 104 BotId: botID, 105 }) 106 } 107 108 Convey("Bad bot ID", t, func() { 109 _, err := call("") 110 So(err, ShouldHaveGRPCStatus, codes.InvalidArgument) 111 }) 112 113 Convey("No permissions", t, func() { 114 _, err := call("hidden-bot") 115 So(err, ShouldHaveGRPCStatus, codes.PermissionDenied) 116 }) 117 118 Convey("Bot not in a config", t, func() { 119 _, err := call("unknown-bot") 120 So(err, ShouldHaveGRPCStatus, codes.PermissionDenied) 121 }) 122 123 Convey("Unconnected bot", t, func() { 124 _, err := call("unconnected-bot") 125 So(err, ShouldHaveGRPCStatus, codes.NotFound) 126 }) 127 128 Convey("Alive bot", t, func() { 129 resp, err := call("alive-bot") 130 So(err, ShouldBeNil) 131 So(resp, ShouldResembleProto, &apipb.BotInfo{ 132 BotId: "alive-bot", 133 TaskId: "task-id", 134 TaskName: "task-name", 135 ExternalIp: "1.2.3.4", 136 AuthenticatedAs: "bot:alive-bot", 137 FirstSeenTs: timestamppb.New(testTime), 138 LastSeenTs: timestamppb.New(testTime.Add(1 * time.Hour)), 139 MaintenanceMsg: "maintenance msg", 140 Dimensions: []*apipb.StringListPair{ 141 {Key: "a", Value: []string{"1"}}, 142 {Key: "b", Value: []string{"2"}}, 143 }, 144 Version: "some-version", 145 State: `{"state": "1"}`, 146 }) 147 }) 148 149 Convey("Deleted bot", t, func() { 150 resp, err := call("deleted-bot") 151 So(err, ShouldBeNil) 152 So(resp, ShouldResembleProto, &apipb.BotInfo{ 153 BotId: "deleted-bot", 154 TaskId: "task-id", 155 ExternalIp: "1.2.3.4", 156 AuthenticatedAs: "bot:deleted-bot", 157 LastSeenTs: timestamppb.New(testTime.Add(1 * time.Hour)), 158 MaintenanceMsg: "maintenance msg", 159 Dimensions: []*apipb.StringListPair{ 160 {Key: "use", Value: []string{"me"}}, 161 }, 162 Version: "some-version", 163 State: `{"state": "1"}`, 164 Deleted: true, 165 }) 166 }) 167 }