go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/rpc/query_builder_stats.go (about) 1 // Copyright 2021 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 rpc 16 17 import ( 18 "context" 19 20 "go.chromium.org/luci/auth/identity" 21 "go.chromium.org/luci/buildbucket/bbperms" 22 "go.chromium.org/luci/buildbucket/protoutil" 23 "go.chromium.org/luci/common/errors" 24 "go.chromium.org/luci/common/sync/parallel" 25 "go.chromium.org/luci/gae/service/datastore" 26 "go.chromium.org/luci/grpc/appstatus" 27 "go.chromium.org/luci/milo/internal/model/milostatus" 28 "go.chromium.org/luci/milo/internal/utils" 29 milopb "go.chromium.org/luci/milo/proto/v1" 30 "go.chromium.org/luci/server/auth" 31 "go.chromium.org/luci/server/auth/realms" 32 "google.golang.org/grpc/codes" 33 ) 34 35 // QueryBuilderStats implements milopb.MiloInternal service 36 func (s *MiloInternalService) QueryBuilderStats(ctx context.Context, req *milopb.QueryBuilderStatsRequest) (_ *milopb.BuilderStats, err error) { 37 // Validate request. 38 err = validatesQueryBuilderStatsRequest(req) 39 if err != nil { 40 return nil, appstatus.BadRequest(err) 41 } 42 43 // Perform ACL check. 44 realm := realms.Join(req.Builder.Project, req.Builder.Bucket) 45 allowed, err := auth.HasPermission(ctx, bbperms.BuildsList, realm, nil) 46 if err != nil { 47 return nil, err 48 } 49 if !allowed { 50 if auth.CurrentIdentity(ctx) == identity.AnonymousIdentity { 51 return nil, appstatus.Error(codes.Unauthenticated, "not logged in") 52 } 53 return nil, appstatus.Error(codes.PermissionDenied, "no access to the bucket") 54 } 55 56 legacyBuilderID := utils.LegacyBuilderIDString(req.Builder) 57 stats := &milopb.BuilderStats{} 58 59 err = parallel.FanOutIn(func(fetch chan<- func() error) { 60 61 // Pending builds 62 fetch <- func() error { 63 q := datastore.NewQuery("BuildSummary"). 64 Eq("BuilderID", legacyBuilderID). 65 Eq("Summary.Status", milostatus.NotRun) 66 pending, err := datastore.Count(ctx, q) 67 stats.PendingBuildsCount = int32(pending) 68 return err 69 } 70 71 // Running builds 72 fetch <- func() error { 73 q := datastore.NewQuery("BuildSummary"). 74 Eq("BuilderID", legacyBuilderID). 75 Eq("Summary.Status", milostatus.Running) 76 running, err := datastore.Count(ctx, q) 77 stats.RunningBuildsCount = int32(running) 78 return err 79 } 80 }) 81 82 if err != nil { 83 return nil, err 84 } 85 86 return stats, nil 87 } 88 89 func validatesQueryBuilderStatsRequest(req *milopb.QueryBuilderStatsRequest) error { 90 if err := protoutil.ValidateRequiredBuilderID(req.Builder); err != nil { 91 return errors.Annotate(err, "builder").Err() 92 } 93 return nil 94 }