go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/swarming/server/rpcs/bots_get_bot.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 20 "google.golang.org/grpc/codes" 21 "google.golang.org/grpc/status" 22 23 "go.chromium.org/luci/common/errors" 24 "go.chromium.org/luci/common/logging" 25 "go.chromium.org/luci/gae/service/datastore" 26 27 apipb "go.chromium.org/luci/swarming/proto/api_v2" 28 "go.chromium.org/luci/swarming/server/acls" 29 "go.chromium.org/luci/swarming/server/model" 30 ) 31 32 // GetBot implements the corresponding RPC method. 33 func (*BotsServer) GetBot(ctx context.Context, req *apipb.BotRequest) (*apipb.BotInfo, error) { 34 if req.BotId == "" { 35 return nil, status.Errorf(codes.InvalidArgument, "bot_id is required") 36 } 37 res := State(ctx).ACL.CheckBotPerm(ctx, req.BotId, acls.PermPoolsListBots) 38 if !res.Permitted { 39 return nil, res.ToGrpcErr() 40 } 41 42 // See if the bot exists right now. 43 info := &model.BotInfo{Key: model.BotInfoKey(ctx, req.BotId)} 44 switch err := datastore.Get(ctx, info); { 45 case err == nil: 46 return info.ToProto(), nil 47 case !errors.Is(err, datastore.ErrNoSuchEntity): 48 logging.Errorf(ctx, "Error fetching BotInfo for %q: %s", req.BotId, err) 49 return nil, status.Errorf(codes.Internal, "datastore error fetching the bot") 50 } 51 52 // If there is no BotInfo, it means the bot doesn't currently exist (i.e. it 53 // was deleted or has never existed). Look into the event history to get its 54 // last known state if any. If the history is empty, it means the bot has 55 // never existed or had been deleted long time ago. If there's a history, it 56 // means the bot existed at some point, but has been deleted. 57 var ev *model.BotEvent 58 q := model.BotEventsQuery(ctx, req.BotId).Limit(1) 59 err := datastore.Run(ctx, q, func(ent *model.BotEvent) error { 60 ev = ent 61 return datastore.Stop 62 }) 63 switch { 64 case err != nil: 65 logging.Errorf(ctx, "Error querying BotEvent for %q: %s", req.BotId, err) 66 return nil, status.Errorf(codes.Internal, "datastore error fetching the bot") 67 case ev == nil: 68 return nil, status.Errorf(codes.NotFound, "no such bot: %s", req.BotId) 69 } 70 71 // A bot may have connected right after datastore.Get(...) above, which 72 // created a brand new BotEvent record that the datastore.Run(...) query 73 // fetched. Here, another datastore.Get(...) is made to make sure the bot is 74 // still actually missing. If we skip this check, we may accidentally mark 75 // a very new bot as deleted. 76 // 77 // See https://crbug.com/1407381 for more information. 78 switch err := datastore.Get(ctx, info); { 79 case err == nil: 80 return info.ToProto(), nil 81 case !errors.Is(err, datastore.ErrNoSuchEntity): 82 logging.Errorf(ctx, "Error re-fetching BotInfo for %q: %s", req.BotId, err) 83 return nil, status.Errorf(codes.Internal, "datastore error fetching the bot") 84 } 85 86 // This bot existed (has a history), but is not connected anymore. Reconstruct 87 // its BotInfo from the last historic entry. 88 info.BotCommon = ev.BotCommon 89 info.Dimensions = ev.Dimensions 90 pb := info.ToProto() 91 pb.Deleted = true 92 return pb, nil 93 }