Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(254)

Side by Side Diff: milo/buildsource/buildbot/builder.go

Issue 2977863002: [milo] Refactor all html knowledge out of backends. (Closed)
Patch Set: seems to work :) Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The LUCI Authors. 1 // Copyright 2016 The LUCI Authors.
2 // 2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with 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 5 // You may obtain a copy of the License at
6 // 6 //
7 // http://www.apache.org/licenses/LICENSE-2.0 7 // http://www.apache.org/licenses/LICENSE-2.0
8 // 8 //
9 // Unless required by applicable law or agreed to in writing, software 9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, 10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and 12 // See the License for the specific language governing permissions and
13 // limitations under the License. 13 // limitations under the License.
14 14
15 package buildbot 15 package buildbot
16 16
17 import ( 17 import (
18 "crypto/sha1" 18 "crypto/sha1"
19 "encoding/base64" 19 "encoding/base64"
20 "errors"
21 "fmt" 20 "fmt"
22 "sort" 21 "sort"
23 "strings" 22 "strings"
24 "time" 23 "time"
25 24
26 "github.com/luci/gae/service/datastore" 25 "github.com/luci/gae/service/datastore"
27 "github.com/luci/gae/service/memcache" 26 "github.com/luci/gae/service/memcache"
28 27
29 "github.com/luci/luci-go/common/clock" 28 "github.com/luci/luci-go/common/clock"
29 "github.com/luci/luci-go/common/errors"
30 "github.com/luci/luci-go/common/logging" 30 "github.com/luci/luci-go/common/logging"
31 "github.com/luci/luci-go/milo/api/resp" 31 "github.com/luci/luci-go/milo/api/resp"
32 "github.com/luci/luci-go/milo/common"
32 "golang.org/x/net/context" 33 "golang.org/x/net/context"
33 ) 34 )
34 35
35 // builderRef is used for keying specific builds in a master json. 36 // builderRef is used for keying specific builds in a master json.
36 type builderRef struct { 37 type builderRef struct {
37 builder string 38 builder string
38 buildNum int 39 buildNum int
39 } 40 }
40 41
41 // buildMap contains all of the current build within a master json. We use this 42 // buildMap contains all of the current build within a master json. We use this
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
88 }, 89 },
89 Text: mergeText(b.Text), 90 Text: mergeText(b.Text),
90 Blame: blame(b), 91 Blame: blame(b),
91 Revision: b.Sourcestamp.Revision, 92 Revision: b.Sourcestamp.Revision,
92 } 93 }
93 } 94 }
94 95
95 // getBuilds fetches all of the recent builds from the . Note that 96 // getBuilds fetches all of the recent builds from the . Note that
96 // getBuilds() does not perform ACL checks. 97 // getBuilds() does not perform ACL checks.
97 func getBuilds( 98 func getBuilds(
98 » c context.Context, masterName, builderName string, finished bool, limit int, cursor *datastore.Cursor) ( 99 » c context.Context, masterName, builderName string, finished bool, limit int, cursor datastore.Cursor) (
99 » []*resp.BuildSummary, *datastore.Cursor, error) { 100 » []*resp.BuildSummary, datastore.Cursor, error) {
100 101
101 // TODO(hinoka): Builder specific structs. 102 // TODO(hinoka): Builder specific structs.
102 result := []*resp.BuildSummary{} 103 result := []*resp.BuildSummary{}
103 q := datastore.NewQuery("buildbotBuild") 104 q := datastore.NewQuery("buildbotBuild")
104 q = q.Eq("finished", finished) 105 q = q.Eq("finished", finished)
105 q = q.Eq("master", masterName) 106 q = q.Eq("master", masterName)
106 q = q.Eq("builder", builderName) 107 q = q.Eq("builder", builderName)
107 q = q.Order("-number") 108 q = q.Order("-number")
108 if cursor != nil { 109 if cursor != nil {
109 » » q = q.Start(*cursor) 110 » » q = q.Start(cursor)
110 } 111 }
111 buildbots, nextCursor, err := runBuildsQuery(c, q, int32(limit)) 112 buildbots, nextCursor, err := runBuildsQuery(c, q, int32(limit))
112 if err != nil { 113 if err != nil {
113 return nil, nil, err 114 return nil, nil, err
114 } 115 }
115 for _, b := range buildbots { 116 for _, b := range buildbots {
116 result = append(result, getBuildSummary(b)) 117 result = append(result, getBuildSummary(b))
117 } 118 }
118 return result, nextCursor, nil 119 return result, nextCursor, nil
119 } 120 }
120 121
121 // maybeSetGetCursor is a cheesy way to implement bidirectional paging with forw ard-only 122 // maybeSetGetCursor is a cheesy way to implement bidirectional paging with forw ard-only
122 // datastore cursor by creating a mapping of nextCursor -> thisCursor 123 // datastore cursor by creating a mapping of nextCursor -> thisCursor
123 // in memcache. maybeSetGetCursor stores the future mapping, then returns prevC ursor 124 // in memcache. maybeSetGetCursor stores the future mapping, then returns prevC ursor
124 // in the mapping for thisCursor -> prevCursor, if available. 125 // in the mapping for thisCursor -> prevCursor, if available.
125 func maybeSetGetCursor(c context.Context, thisCursor, nextCursor *datastore.Curs or, limit int) (*datastore.Cursor, bool) { 126 func maybeSetGetCursor(c context.Context, thisCursor, nextCursor datastore.Curso r, limit int) (datastore.Cursor, bool) {
126 key := func(c datastore.Cursor) string { 127 key := func(c datastore.Cursor) string {
127 // Memcache key limit is 250 bytes, hash our cursor to get under this limit. 128 // Memcache key limit is 250 bytes, hash our cursor to get under this limit.
128 blob := sha1.Sum([]byte(c.String())) 129 blob := sha1.Sum([]byte(c.String()))
129 return fmt.Sprintf("v2:cursors:buildbot_builders:%d:%s", limit, base64.StdEncoding.EncodeToString(blob[:])) 130 return fmt.Sprintf("v2:cursors:buildbot_builders:%d:%s", limit, base64.StdEncoding.EncodeToString(blob[:]))
130 } 131 }
131 // Set the next cursor to this cursor mapping, if available. 132 // Set the next cursor to this cursor mapping, if available.
132 if nextCursor != nil { 133 if nextCursor != nil {
133 » » item := memcache.NewItem(c, key(*nextCursor)) 134 » » item := memcache.NewItem(c, key(nextCursor))
134 if thisCursor == nil { 135 if thisCursor == nil {
135 // Make sure we know it exists, just empty 136 // Make sure we know it exists, just empty
136 item.SetValue([]byte{}) 137 item.SetValue([]byte{})
137 } else { 138 } else {
138 » » » item.SetValue([]byte((*thisCursor).String())) 139 » » » item.SetValue([]byte(thisCursor.String()))
139 } 140 }
140 item.SetExpiration(24 * time.Hour) 141 item.SetExpiration(24 * time.Hour)
141 memcache.Set(c, item) 142 memcache.Set(c, item)
142 } 143 }
143 // Try to get the last cursor, if valid and available. 144 // Try to get the last cursor, if valid and available.
144 if thisCursor == nil { 145 if thisCursor == nil {
145 return nil, false 146 return nil, false
146 } 147 }
147 » if item, err := memcache.GetKey(c, key(*thisCursor)); err == nil { 148 » if item, err := memcache.GetKey(c, key(thisCursor)); err == nil {
148 if len(item.Value()) == 0 { 149 if len(item.Value()) == 0 {
149 return nil, true 150 return nil, true
150 } 151 }
151 if prevCursor, err := datastore.DecodeCursor(c, string(item.Valu e())); err == nil { 152 if prevCursor, err := datastore.DecodeCursor(c, string(item.Valu e())); err == nil {
152 » » » return &prevCursor, true 153 » » » return prevCursor, true
153 } 154 }
154 } 155 }
155 return nil, false 156 return nil, false
156 } 157 }
157 158
158 var errMasterNotFound = errors.New(
159 "Either the request resource was not found or you have insufficient perm issions")
160 var errNotAuth = errors.New("You are not authenticated, try logging in")
161
162 type errBuilderNotFound struct {
163 master string
164 builder string
165 available []string
166 }
167
168 func (e errBuilderNotFound) Error() string {
169 avail := strings.Join(e.available, "\n")
170 return fmt.Sprintf("Cannot find builder %q in master %q.\nAvailable buil ders: \n%s",
171 e.builder, e.master, avail)
172 }
173
174 func summarizeSlavePool( 159 func summarizeSlavePool(
175 baseURL string, slaves []string, slaveMap map[string]*buildbotSlave) *re sp.MachinePool { 160 baseURL string, slaves []string, slaveMap map[string]*buildbotSlave) *re sp.MachinePool {
176 161
177 mp := &resp.MachinePool{ 162 mp := &resp.MachinePool{
178 Total: len(slaves), 163 Total: len(slaves),
179 Bots: make([]resp.Bot, 0, len(slaves)), 164 Bots: make([]resp.Bot, 0, len(slaves)),
180 } 165 }
181 for _, slaveName := range slaves { 166 for _, slaveName := range slaves {
182 slave, ok := slaveMap[slaveName] 167 slave, ok := slaveMap[slaveName]
183 bot := resp.Bot{ 168 bot := resp.Bot{
(...skipping 13 matching lines...) Expand all
197 mp.Busy++ 182 mp.Busy++
198 default: 183 default:
199 bot.Status = resp.Idle 184 bot.Status = resp.Idle
200 mp.Idle++ 185 mp.Idle++
201 } 186 }
202 mp.Bots = append(mp.Bots, bot) 187 mp.Bots = append(mp.Bots, bot)
203 } 188 }
204 return mp 189 return mp
205 } 190 }
206 191
207 // builderImpl is the implementation for getting a milo builder page from buildb ot. 192 // GetBuilder is the implementation for getting a milo builder page from
193 // buildbot.
194 //
208 // This gets: 195 // This gets:
209 // * Current Builds from querying the master json from the datastore. 196 // * Current Builds from querying the master json from the datastore.
210 // * Recent Builds from a cron job that backfills the recent builds. 197 // * Recent Builds from a cron job that backfills the recent builds.
211 func builderImpl( 198 func GetBuilder(c context.Context, masterName, builderName string, limit int, cu rsor datastore.Cursor) (*resp.Builder, error) {
212 » c context.Context, masterName, builderName string, limit int, cursor str ing) (
213 » *resp.Builder, error) {
214
215 » var thisCursor *datastore.Cursor
216 » if cursor != "" {
217 » » tmpCur, err := datastore.DecodeCursor(c, cursor)
218 » » if err != nil {
219 » » » return nil, fmt.Errorf("bad cursor: %s", err)
220 » » }
221 » » thisCursor = &tmpCur
222 » }
223
224 result := &resp.Builder{ 199 result := &resp.Builder{
225 Name: builderName, 200 Name: builderName,
226 } 201 }
227 master, internal, t, err := getMasterJSON(c, masterName) 202 master, internal, t, err := getMasterJSON(c, masterName)
228 if err != nil { 203 if err != nil {
229 return nil, err 204 return nil, err
230 } 205 }
231 if clock.Now(c).Sub(t) > 2*time.Minute { 206 if clock.Now(c).Sub(t) > 2*time.Minute {
232 warning := fmt.Sprintf( 207 warning := fmt.Sprintf(
233 "WARNING: Master data is stale (last updated %s)", t) 208 "WARNING: Master data is stale (last updated %s)", t)
234 logging.Warningf(c, warning) 209 logging.Warningf(c, warning)
235 result.Warning = warning 210 result.Warning = warning
236 } 211 }
237 212
238 p, ok := master.Builders[builderName] 213 p, ok := master.Builders[builderName]
239 if !ok { 214 if !ok {
240 // This long block is just to return a good error message when a n invalid 215 // This long block is just to return a good error message when a n invalid
241 // buildbot builder is specified. 216 // buildbot builder is specified.
242 keys := make([]string, 0, len(master.Builders)) 217 keys := make([]string, 0, len(master.Builders))
243 for k := range master.Builders { 218 for k := range master.Builders {
244 keys = append(keys, k) 219 keys = append(keys, k)
245 } 220 }
246 sort.Strings(keys) 221 sort.Strings(keys)
247 » » return nil, errBuilderNotFound{masterName, builderName, keys} 222 » » // TODO(iannucci): add error-info-helper tags to give the error page enough
223 » » // information to render link-to-master and link-to-builder.
224 » » builders := strings.Join(keys, "\n")
225 » » return nil, errors.Reason(
226 » » » "Cannot find builder %q in master %q.\nAvailable builder s: \n%s",
227 » » » builderName, masterName, builders,
228 » » ).Tag(common.CodeParameterError).Err()
Ryan Tseng 2017/07/13 22:00:46 404?
iannucci 2017/07/14 19:00:22 oh yeah
248 } 229 }
249 // Extract pending builds out of the master json. 230 // Extract pending builds out of the master json.
250 result.PendingBuilds = make([]*resp.BuildSummary, len(p.PendingBuildStat es)) 231 result.PendingBuilds = make([]*resp.BuildSummary, len(p.PendingBuildStat es))
251 logging.Debugf(c, "Number of pending builds: %d", len(p.PendingBuildStat es)) 232 logging.Debugf(c, "Number of pending builds: %d", len(p.PendingBuildStat es))
252 for i, pb := range p.PendingBuildStates { 233 for i, pb := range p.PendingBuildStates {
253 start := time.Unix(int64(pb.SubmittedAt), 0).UTC() 234 start := time.Unix(int64(pb.SubmittedAt), 0).UTC()
254 result.PendingBuilds[i] = &resp.BuildSummary{ 235 result.PendingBuilds[i] = &resp.BuildSummary{
255 PendingTime: resp.Interval{ 236 PendingTime: resp.Interval{
256 Started: start, 237 Started: start,
257 Duration: clock.Now(c).UTC().Sub(start), 238 Duration: clock.Now(c).UTC().Sub(start),
258 }, 239 },
259 } 240 }
260 result.PendingBuilds[i].Blame = make([]*resp.Commit, len(pb.Sour ce.Changes)) 241 result.PendingBuilds[i].Blame = make([]*resp.Commit, len(pb.Sour ce.Changes))
261 for j, cm := range pb.Source.Changes { 242 for j, cm := range pb.Source.Changes {
262 result.PendingBuilds[i].Blame[j] = &resp.Commit{ 243 result.PendingBuilds[i].Blame[j] = &resp.Commit{
263 AuthorEmail: cm.Who, 244 AuthorEmail: cm.Who,
264 CommitURL: cm.Revlink, 245 CommitURL: cm.Revlink,
265 } 246 }
266 } 247 }
267 } 248 }
268 249
269 baseURL := "https://build.chromium.org/p/" 250 baseURL := "https://build.chromium.org/p/"
270 if internal { 251 if internal {
271 baseURL = "https://uberchromegw.corp.google.com/i/" 252 baseURL = "https://uberchromegw.corp.google.com/i/"
272 } 253 }
273 result.MachinePool = summarizeSlavePool(baseURL+master.Name, p.Slaves, m aster.Slaves) 254 result.MachinePool = summarizeSlavePool(baseURL+master.Name, p.Slaves, m aster.Slaves)
274 255
275 // This is CPU bound anyways, so there's no need to do this in parallel. 256 // This is CPU bound anyways, so there's no need to do this in parallel.
276 » finishedBuilds, nextCursor, err := getBuilds(c, masterName, builderName, true, limit, thisCursor) 257 » finishedBuilds, nextCursor, err := getBuilds(c, masterName, builderName, true, limit, cursor)
277 if err != nil { 258 if err != nil {
278 return nil, err 259 return nil, err
279 } 260 }
280 » if prevCursor, ok := maybeSetGetCursor(c, thisCursor, nextCursor, limit) ; ok { 261 » if prevCursor, ok := maybeSetGetCursor(c, cursor, nextCursor, limit); ok {
281 if prevCursor == nil { 262 if prevCursor == nil {
282 // Magic string to signal display prev without cursor 263 // Magic string to signal display prev without cursor
283 result.PrevCursor = "EMPTY" 264 result.PrevCursor = "EMPTY"
284 } else { 265 } else {
285 » » » result.PrevCursor = (*prevCursor).String() 266 » » » result.PrevCursor = prevCursor.String()
286 } 267 }
287 } 268 }
288 if nextCursor != nil { 269 if nextCursor != nil {
289 » » result.NextCursor = (*nextCursor).String() 270 » » result.NextCursor = nextCursor.String()
290 } 271 }
291 // Cursor is not needed for current builds. 272 // Cursor is not needed for current builds.
292 currentBuilds, _, err := getBuilds(c, masterName, builderName, false, 0, nil) 273 currentBuilds, _, err := getBuilds(c, masterName, builderName, false, 0, nil)
293 if err != nil { 274 if err != nil {
294 return nil, err 275 return nil, err
295 } 276 }
296 // currentBuilds is presented in reversed order, so flip it 277 // currentBuilds is presented in reversed order, so flip it
297 for i, j := 0, len(currentBuilds)-1; i < j; i, j = i+1, j-1 { 278 for i, j := 0, len(currentBuilds)-1; i < j; i, j = i+1, j-1 {
298 currentBuilds[i], currentBuilds[j] = currentBuilds[j], currentBu ilds[i] 279 currentBuilds[i], currentBuilds[j] = currentBuilds[j], currentBu ilds[i]
299 } 280 }
300 result.CurrentBuilds = currentBuilds 281 result.CurrentBuilds = currentBuilds
301 282
302 for _, fb := range finishedBuilds { 283 for _, fb := range finishedBuilds {
303 if fb != nil { 284 if fb != nil {
304 result.FinishedBuilds = append(result.FinishedBuilds, fb ) 285 result.FinishedBuilds = append(result.FinishedBuilds, fb )
305 } 286 }
306 } 287 }
307 return result, nil 288 return result, nil
308 } 289 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698