golang.org/x/build@v0.0.0-20240506185731-218518f32b70/cmd/coordinator/internal/dashboard/datastore.go (about) 1 // Copyright 2020 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build linux || darwin 6 7 package dashboard 8 9 import ( 10 "context" 11 "errors" 12 "fmt" 13 "log" 14 15 "cloud.google.com/go/datastore" 16 bbpb "go.chromium.org/luci/buildbucket/proto" 17 "golang.org/x/build/cmd/coordinator/internal/lucipoll" 18 ) 19 20 // getDatastoreResults populates result data fetched from Datastore into commits. 21 func getDatastoreResults(ctx context.Context, cl *datastore.Client, commits []*commit, pkg string) { 22 var keys []*datastore.Key 23 for _, c := range commits { 24 pkey := datastore.NameKey("Package", pkg, nil) 25 pkey.Namespace = "Git" 26 key := datastore.NameKey("Commit", "|"+c.Hash, pkey) 27 key.Namespace = "Git" 28 keys = append(keys, key) 29 } 30 out := make([]*Commit, len(keys)) 31 // datastore.ErrNoSuchEntity is returned when we ask for a commit that we do not yet have test data. 32 if err := cl.GetMulti(ctx, keys, out); err != nil && filterMultiError(err, ignoreNoSuchEntity) != nil { 33 log.Printf("getDatastoreResults: error fetching %d results: %v", len(keys), err) 34 return 35 } 36 hashOut := make(map[string]*Commit) 37 for _, o := range out { 38 if o != nil && o.Hash != "" { 39 hashOut[o.Hash] = o 40 } 41 } 42 for _, c := range commits { 43 if result, ok := hashOut[c.Hash]; ok { 44 c.ResultData = result.ResultData 45 } 46 } 47 } 48 49 // appendLUCIResults appends result data polled from LUCI to commits. 50 func appendLUCIResults(luci lucipoll.Snapshot, commits []*commit, repo string) { 51 commitBuilds, ok := luci.RepoCommitBuilds[repo] 52 if !ok { 53 return 54 } 55 for _, c := range commits { 56 builds, ok := commitBuilds[c.Hash] 57 if !ok { 58 // No builds for this commit. 59 continue 60 } 61 for _, b := range builds { 62 switch b.Status { 63 case bbpb.Status_STARTED: 64 c.ResultData = append(c.ResultData, fmt.Sprintf("%s|%s", 65 b.BuilderName, 66 buildURL(b.ID), 67 )) 68 case bbpb.Status_SUCCESS, bbpb.Status_FAILURE: 69 c.ResultData = append(c.ResultData, fmt.Sprintf("%s|%t|%s|%s", 70 b.BuilderName, 71 b.Status == bbpb.Status_SUCCESS, 72 buildURL(b.ID), 73 c.Hash, 74 )) 75 case bbpb.Status_INFRA_FAILURE: 76 c.ResultData = append(c.ResultData, fmt.Sprintf("%s|%s|%s|%s", 77 b.BuilderName, 78 "infra_failure", 79 buildURL(b.ID), 80 c.Hash, 81 )) 82 } 83 } 84 } 85 } 86 87 func buildURL(buildID int64) string { 88 return fmt.Sprintf("https://ci.chromium.org/b/%d", buildID) 89 } 90 91 type ignoreFunc func(err error) error 92 93 // ignoreNoSuchEntity ignores datastore.ErrNoSuchEntity, which is returned when 94 // we ask for a commit that we do not yet have test data. 95 func ignoreNoSuchEntity(err error) error { 96 if !errors.Is(err, datastore.ErrNoSuchEntity) { 97 return err 98 } 99 return nil 100 } 101 102 // filterMultiError loops over datastore.MultiError, skipping errors ignored by 103 // the specified ignoreFuncs. Any unfiltered errors will be returned as a 104 // datastore.MultiError error. If no errors are left, nil will be returned. 105 // Errors that are not datastore.MultiError will be returned as-is. 106 func filterMultiError(err error, ignores ...ignoreFunc) error { 107 if err == nil { 108 return nil 109 } 110 me := datastore.MultiError{} 111 if ok := errors.As(err, &me); !ok { 112 return err 113 } 114 ret := datastore.MultiError{} 115 for _, err := range me { 116 var skip bool 117 for _, ignore := range ignores { 118 if err := ignore(err); err == nil { 119 skip = true 120 break 121 } 122 } 123 if !skip { 124 ret = append(ret, err) 125 } 126 } 127 if len(ret) > 0 { 128 return ret 129 } 130 return nil 131 }