| OLD | NEW |
| (Empty) |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. | |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | |
| 3 // that can be found in the LICENSE file. | |
| 4 | |
| 5 package buildbot | |
| 6 | |
| 7 import ( | |
| 8 "bytes" | |
| 9 "compress/gzip" | |
| 10 "encoding/json" | |
| 11 | |
| 12 "golang.org/x/net/context" | |
| 13 "google.golang.org/grpc" | |
| 14 "google.golang.org/grpc/codes" | |
| 15 | |
| 16 "github.com/golang/protobuf/ptypes/timestamp" | |
| 17 "github.com/luci/gae/service/datastore" | |
| 18 "github.com/luci/luci-go/common/iotools" | |
| 19 "github.com/luci/luci-go/common/logging" | |
| 20 milo "github.com/luci/luci-go/milo/api/proto" | |
| 21 "github.com/luci/luci-go/server/auth" | |
| 22 ) | |
| 23 | |
| 24 // Service is a service implementation that displays BuildBot builds. | |
| 25 type Service struct{} | |
| 26 | |
| 27 var errNotFoundGRPC = grpc.Errorf(codes.NotFound, "Master Not Found") | |
| 28 | |
| 29 // GetBuildbotBuildJSON implements milo.BuildbotServer. | |
| 30 func (s *Service) GetBuildbotBuildJSON(c context.Context, req *milo.BuildbotBuil
dRequest) ( | |
| 31 *milo.BuildbotBuildJSON, error) { | |
| 32 | |
| 33 if req.Master == "" { | |
| 34 return nil, grpc.Errorf(codes.InvalidArgument, "No master specif
ied") | |
| 35 } | |
| 36 if req.Builder == "" { | |
| 37 return nil, grpc.Errorf(codes.InvalidArgument, "No builder speci
fied") | |
| 38 } | |
| 39 | |
| 40 cu := auth.CurrentUser(c) | |
| 41 logging.Debugf(c, "%s is requesting %s/%s/%d", | |
| 42 cu.Identity, req.Master, req.Builder, req.BuildNum) | |
| 43 | |
| 44 b, err := getBuild(c, req.Master, req.Builder, int(req.BuildNum)) | |
| 45 switch { | |
| 46 case err == errBuildNotFound: | |
| 47 return nil, grpc.Errorf(codes.NotFound, "Build not found") | |
| 48 case err == errNotAuth: | |
| 49 return nil, grpc.Errorf(codes.Unauthenticated, "Unauthenticated
request") | |
| 50 case err != nil: | |
| 51 return nil, err | |
| 52 } | |
| 53 | |
| 54 updatePostProcessBuild(b) | |
| 55 bs, err := json.Marshal(b) | |
| 56 if err != nil { | |
| 57 return nil, err | |
| 58 } | |
| 59 | |
| 60 // Marshal the build back into JSON format. | |
| 61 return &milo.BuildbotBuildJSON{Data: bs}, nil | |
| 62 } | |
| 63 | |
| 64 // GetBuildbotBuildsJSON implements milo.BuildbotServer. | |
| 65 func (s *Service) GetBuildbotBuildsJSON(c context.Context, req *milo.BuildbotBui
ldsRequest) ( | |
| 66 *milo.BuildbotBuildsJSON, error) { | |
| 67 | |
| 68 if req.Master == "" { | |
| 69 return nil, grpc.Errorf(codes.InvalidArgument, "No master specif
ied") | |
| 70 } | |
| 71 if req.Builder == "" { | |
| 72 return nil, grpc.Errorf(codes.InvalidArgument, "No builder speci
fied") | |
| 73 } | |
| 74 | |
| 75 limit := req.Limit | |
| 76 if limit == 0 { | |
| 77 limit = 20 | |
| 78 } | |
| 79 | |
| 80 cu := auth.CurrentUser(c) | |
| 81 logging.Debugf(c, "%s is requesting %s/%s (limit %d, cursor %s)", | |
| 82 cu.Identity, req.Master, req.Builder, limit, req.Cursor) | |
| 83 | |
| 84 // Perform an ACL check by fetching the master. | |
| 85 _, err := getMasterEntry(c, req.Master) | |
| 86 switch { | |
| 87 case err == errMasterNotFound: | |
| 88 return nil, grpc.Errorf(codes.NotFound, "Master not found") | |
| 89 case err == errNotAuth: | |
| 90 return nil, grpc.Errorf(codes.Unauthenticated, "Unauthenticated
request") | |
| 91 case err != nil: | |
| 92 return nil, err | |
| 93 } | |
| 94 | |
| 95 q := datastore.NewQuery("buildbotBuild") | |
| 96 q = q.Eq("master", req.Master). | |
| 97 Eq("builder", req.Builder). | |
| 98 Limit(limit). | |
| 99 Order("-number") | |
| 100 if req.IncludeCurrent == false { | |
| 101 q = q.Eq("finished", true) | |
| 102 } | |
| 103 // Insert the cursor or offset. | |
| 104 if req.Cursor != "" { | |
| 105 cursor, err := datastore.DecodeCursor(c, req.Cursor) | |
| 106 if err != nil { | |
| 107 return nil, grpc.Errorf(codes.InvalidArgument, "Invalid
cursor: %s", err.Error()) | |
| 108 } | |
| 109 q = q.Start(cursor) | |
| 110 } | |
| 111 builds, nextCursor, err := runBuildsQuery(c, q, int32(req.Limit)) | |
| 112 if err != nil { | |
| 113 return nil, err | |
| 114 } | |
| 115 | |
| 116 results := make([]*milo.BuildbotBuildJSON, len(builds)) | |
| 117 for i, b := range builds { | |
| 118 updatePostProcessBuild(b) | |
| 119 | |
| 120 // In theory we could do this in parallel, but it doesn't actual
ly go faster | |
| 121 // since AppEngine is single-cored. | |
| 122 bs, err := json.Marshal(b) | |
| 123 if err != nil { | |
| 124 return nil, err | |
| 125 } | |
| 126 results[i] = &milo.BuildbotBuildJSON{Data: bs} | |
| 127 } | |
| 128 buildsJSON := &milo.BuildbotBuildsJSON{ | |
| 129 Builds: results, | |
| 130 } | |
| 131 if nextCursor != nil { | |
| 132 buildsJSON.Cursor = (*nextCursor).String() | |
| 133 } | |
| 134 return buildsJSON, nil | |
| 135 } | |
| 136 | |
| 137 // GetCompressedMasterJSON assembles a CompressedMasterJSON object from the | |
| 138 // provided MasterRequest. | |
| 139 func (s *Service) GetCompressedMasterJSON(c context.Context, req *milo.MasterReq
uest) ( | |
| 140 *milo.CompressedMasterJSON, error) { | |
| 141 | |
| 142 if req.Name == "" { | |
| 143 return nil, grpc.Errorf(codes.InvalidArgument, "No master specif
ied") | |
| 144 } | |
| 145 | |
| 146 cu := auth.CurrentUser(c) | |
| 147 logging.Debugf(c, "%s is making a master request for %s", cu.Identity, r
eq.Name) | |
| 148 | |
| 149 entry, err := getMasterEntry(c, req.Name) | |
| 150 switch { | |
| 151 case err == errMasterNotFound: | |
| 152 return nil, errNotFoundGRPC | |
| 153 case err == errNotAuth: | |
| 154 return nil, grpc.Errorf(codes.Unauthenticated, "Unauthenticated
request") | |
| 155 case err != nil: | |
| 156 return nil, err | |
| 157 } | |
| 158 // Decompress it so we can inject current build information. | |
| 159 master := &buildbotMaster{} | |
| 160 if err = decodeMasterEntry(c, entry, master); err != nil { | |
| 161 return nil, err | |
| 162 } | |
| 163 for _, slave := range master.Slaves { | |
| 164 numBuilds := 0 | |
| 165 for _, builds := range slave.RunningbuildsMap { | |
| 166 numBuilds += len(builds) | |
| 167 } | |
| 168 slave.Runningbuilds = make([]*buildbotBuild, 0, numBuilds) | |
| 169 for builderName, builds := range slave.RunningbuildsMap { | |
| 170 for _, buildNum := range builds { | |
| 171 slave.Runningbuilds = append(slave.Runningbuilds
, &buildbotBuild{ | |
| 172 Master: req.Name, | |
| 173 Buildername: builderName, | |
| 174 Number: buildNum, | |
| 175 }) | |
| 176 } | |
| 177 } | |
| 178 if err := datastore.Get(c, slave.Runningbuilds); err != nil { | |
| 179 logging.WithError(err).Errorf(c, | |
| 180 "Encountered error while trying to fetch running
builds for %s: %v", | |
| 181 master.Name, slave.Runningbuilds) | |
| 182 return nil, err | |
| 183 } | |
| 184 | |
| 185 for _, b := range slave.Runningbuilds { | |
| 186 updatePostProcessBuild(b) | |
| 187 } | |
| 188 } | |
| 189 | |
| 190 // Also inject cached builds information. | |
| 191 for builderName, builder := range master.Builders { | |
| 192 // Get the most recent 50 buildNums on the builder to simulate w
hat the | |
| 193 // cachedBuilds field looks like from the real buildbot master j
son. | |
| 194 q := datastore.NewQuery("buildbotBuild"). | |
| 195 Eq("finished", true). | |
| 196 Eq("master", req.Name). | |
| 197 Eq("builder", builderName). | |
| 198 Limit(50). | |
| 199 Order("-number"). | |
| 200 KeysOnly(true) | |
| 201 var builds []*buildbotBuild | |
| 202 err := getBuildQueryBatcher(c).GetAll(c, q, &builds) | |
| 203 if err != nil { | |
| 204 return nil, err | |
| 205 } | |
| 206 builder.CachedBuilds = make([]int, len(builds)) | |
| 207 for i, b := range builds { | |
| 208 builder.CachedBuilds[i] = b.Number | |
| 209 } | |
| 210 } | |
| 211 | |
| 212 // And re-compress it. | |
| 213 gzbs := bytes.Buffer{} | |
| 214 gsw := gzip.NewWriter(&gzbs) | |
| 215 cw := iotools.CountingWriter{Writer: gsw} | |
| 216 e := json.NewEncoder(&cw) | |
| 217 if err := e.Encode(master); err != nil { | |
| 218 gsw.Close() | |
| 219 return nil, err | |
| 220 } | |
| 221 gsw.Close() | |
| 222 | |
| 223 logging.Infof(c, "Returning %d bytes", cw.Count) | |
| 224 | |
| 225 return &milo.CompressedMasterJSON{ | |
| 226 Internal: entry.Internal, | |
| 227 Modified: ×tamp.Timestamp{ | |
| 228 Seconds: entry.Modified.Unix(), | |
| 229 Nanos: int32(entry.Modified.Nanosecond()), | |
| 230 }, | |
| 231 Data: gzbs.Bytes(), | |
| 232 }, nil | |
| 233 } | |
| OLD | NEW |