| OLD | NEW |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. | 1 // Copyright 2016 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package buildbot | 5 package buildbot |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "crypto/sha1" |
| 9 "encoding/base64" |
| 8 "errors" | 10 "errors" |
| 9 "fmt" | 11 "fmt" |
| 10 "sort" | 12 "sort" |
| 11 "strings" | 13 "strings" |
| 12 "time" | 14 "time" |
| 13 | 15 |
| 14 "github.com/luci/gae/service/datastore" | 16 "github.com/luci/gae/service/datastore" |
| 17 "github.com/luci/gae/service/memcache" |
| 15 | 18 |
| 16 "github.com/luci/luci-go/common/clock" | 19 "github.com/luci/luci-go/common/clock" |
| 17 "github.com/luci/luci-go/common/logging" | 20 "github.com/luci/luci-go/common/logging" |
| 18 "github.com/luci/luci-go/milo/api/resp" | 21 "github.com/luci/luci-go/milo/api/resp" |
| 19 "golang.org/x/net/context" | 22 "golang.org/x/net/context" |
| 20 ) | 23 ) |
| 21 | 24 |
| 22 // builderRef is used for keying specific builds in a master json. | 25 // builderRef is used for keying specific builds in a master json. |
| 23 type builderRef struct { | 26 type builderRef struct { |
| 24 builder string | 27 builder string |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 78 }, | 81 }, |
| 79 Text: mergeText(b.Text), | 82 Text: mergeText(b.Text), |
| 80 Blame: blame(b), | 83 Blame: blame(b), |
| 81 Revision: b.Sourcestamp.Revision, | 84 Revision: b.Sourcestamp.Revision, |
| 82 } | 85 } |
| 83 } | 86 } |
| 84 | 87 |
| 85 // getBuilds fetches all of the recent builds from the . Note that | 88 // getBuilds fetches all of the recent builds from the . Note that |
| 86 // getBuilds() does not perform ACL checks. | 89 // getBuilds() does not perform ACL checks. |
| 87 func getBuilds( | 90 func getBuilds( |
| 88 » c context.Context, masterName, builderName string, finished bool, limit
int) ( | 91 » c context.Context, masterName, builderName string, finished bool, limit
int, cursor *datastore.Cursor) ( |
| 89 » []*resp.BuildSummary, error) { | 92 » []*resp.BuildSummary, *datastore.Cursor, error) { |
| 90 | 93 |
| 91 // TODO(hinoka): Builder specific structs. | 94 // TODO(hinoka): Builder specific structs. |
| 92 result := []*resp.BuildSummary{} | 95 result := []*resp.BuildSummary{} |
| 93 q := datastore.NewQuery("buildbotBuild") | 96 q := datastore.NewQuery("buildbotBuild") |
| 94 q = q.Eq("finished", finished) | 97 q = q.Eq("finished", finished) |
| 95 q = q.Eq("master", masterName) | 98 q = q.Eq("master", masterName) |
| 96 q = q.Eq("builder", builderName) | 99 q = q.Eq("builder", builderName) |
| 97 » if limit != 0 { | 100 » q = q.Order("-number") |
| 98 » » q = q.Limit(int32(limit)) | 101 » if cursor != nil { |
| 102 » » q = q.Start(*cursor) |
| 99 } | 103 } |
| 100 » q = q.Order("-number") | 104 » buildbots, nextCursor, err := runBuildsQuery(c, q, int32(limit)) |
| 101 » buildbots := []*buildbotBuild{} | |
| 102 » err := getBuildQueryBatcher(c).GetAll(c, q, &buildbots) | |
| 103 if err != nil { | 105 if err != nil { |
| 104 » » return nil, err | 106 » » return nil, nil, err |
| 105 } | 107 } |
| 106 for _, b := range buildbots { | 108 for _, b := range buildbots { |
| 107 result = append(result, getBuildSummary(b)) | 109 result = append(result, getBuildSummary(b)) |
| 108 } | 110 } |
| 109 » return result, nil | 111 » return result, nextCursor, nil |
| 112 } |
| 113 |
| 114 // maybeSetGetCursor is a cheesy way to implement bidirectional paging with forw
ard-only |
| 115 // datastore cursor by creating a mapping of nextCursor -> thisCursor |
| 116 // in memcache. maybeSetGetCursor stores the future mapping, then returns prevC
ursor |
| 117 // in the mapping for thisCursor -> prevCursor, if available. |
| 118 func maybeSetGetCursor(c context.Context, thisCursor, nextCursor *datastore.Curs
or, limit int) (*datastore.Cursor, bool) { |
| 119 » key := func(c datastore.Cursor) string { |
| 120 » » // Memcache key limit is 250 bytes, hash our cursor to get under
this limit. |
| 121 » » blob := sha1.Sum([]byte(c.String())) |
| 122 » » return fmt.Sprintf("v2:cursors:buildbot_builders:%d:%s", limit,
base64.StdEncoding.EncodeToString(blob[:])) |
| 123 » } |
| 124 » // Set the next cursor to this cursor mapping, if available. |
| 125 » if nextCursor != nil { |
| 126 » » item := memcache.NewItem(c, key(*nextCursor)) |
| 127 » » if thisCursor == nil { |
| 128 » » » // Make sure we know it exists, just empty |
| 129 » » » item.SetValue([]byte{}) |
| 130 » » } else { |
| 131 » » » item.SetValue([]byte((*thisCursor).String())) |
| 132 » » } |
| 133 » » item.SetExpiration(24 * time.Hour) |
| 134 » » memcache.Set(c, item) |
| 135 » } |
| 136 » // Try to get the last cursor, if valid and available. |
| 137 » if thisCursor == nil { |
| 138 » » return nil, false |
| 139 » } |
| 140 » if item, err := memcache.GetKey(c, key(*thisCursor)); err == nil { |
| 141 » » if len(item.Value()) == 0 { |
| 142 » » » return nil, true |
| 143 » » } |
| 144 » » if prevCursor, err := datastore.DecodeCursor(c, string(item.Valu
e())); err == nil { |
| 145 » » » return &prevCursor, true |
| 146 » » } |
| 147 » } |
| 148 » return nil, false |
| 110 } | 149 } |
| 111 | 150 |
| 112 var errMasterNotFound = errors.New( | 151 var errMasterNotFound = errors.New( |
| 113 "Either the request resource was not found or you have insufficient perm
issions") | 152 "Either the request resource was not found or you have insufficient perm
issions") |
| 114 var errNotAuth = errors.New("You are not authenticated, try logging in") | 153 var errNotAuth = errors.New("You are not authenticated, try logging in") |
| 115 | 154 |
| 116 // builderImpl is the implementation for getting a milo builder page from buildb
ot. | 155 // builderImpl is the implementation for getting a milo builder page from buildb
ot. |
| 117 // This gets: | 156 // This gets: |
| 118 // * Current Builds from querying the master json from the datastore. | 157 // * Current Builds from querying the master json from the datastore. |
| 119 // * Recent Builds from a cron job that backfills the recent builds. | 158 // * Recent Builds from a cron job that backfills the recent builds. |
| 120 func builderImpl(c context.Context, masterName, builderName string, limit int) (
*resp.Builder, error) { | 159 func builderImpl( |
| 160 » c context.Context, masterName, builderName string, limit int, cursor str
ing) ( |
| 161 » *resp.Builder, error) { |
| 162 |
| 163 » var thisCursor *datastore.Cursor |
| 164 » if cursor != "" { |
| 165 » » tmpCur, err := datastore.DecodeCursor(c, cursor) |
| 166 » » if err != nil { |
| 167 » » » return nil, fmt.Errorf("bad cursor: %s", err) |
| 168 » » } |
| 169 » » thisCursor = &tmpCur |
| 170 » } |
| 171 |
| 121 result := &resp.Builder{ | 172 result := &resp.Builder{ |
| 122 Name: builderName, | 173 Name: builderName, |
| 123 } | 174 } |
| 124 master, t, err := getMasterJSON(c, masterName) | 175 master, t, err := getMasterJSON(c, masterName) |
| 125 if err != nil { | 176 if err != nil { |
| 126 return nil, err | 177 return nil, err |
| 127 } | 178 } |
| 128 if clock.Now(c).Sub(t) > 2*time.Minute { | 179 if clock.Now(c).Sub(t) > 2*time.Minute { |
| 129 warning := fmt.Sprintf( | 180 warning := fmt.Sprintf( |
| 130 "WARNING: Master data is stale (last updated %s)", t) | 181 "WARNING: Master data is stale (last updated %s)", t) |
| (...skipping 29 matching lines...) Expand all Loading... |
| 160 result.PendingBuilds[i].Blame = make([]*resp.Commit, len(pb.Sour
ce.Changes)) | 211 result.PendingBuilds[i].Blame = make([]*resp.Commit, len(pb.Sour
ce.Changes)) |
| 161 for j, cm := range pb.Source.Changes { | 212 for j, cm := range pb.Source.Changes { |
| 162 result.PendingBuilds[i].Blame[j] = &resp.Commit{ | 213 result.PendingBuilds[i].Blame[j] = &resp.Commit{ |
| 163 AuthorEmail: cm.Who, | 214 AuthorEmail: cm.Who, |
| 164 CommitURL: cm.Revlink, | 215 CommitURL: cm.Revlink, |
| 165 } | 216 } |
| 166 } | 217 } |
| 167 } | 218 } |
| 168 | 219 |
| 169 // This is CPU bound anyways, so there's no need to do this in parallel. | 220 // This is CPU bound anyways, so there's no need to do this in parallel. |
| 170 » finishedBuilds, err := getBuilds(c, masterName, builderName, true, limit
) | 221 » finishedBuilds, nextCursor, err := getBuilds(c, masterName, builderName,
true, limit, thisCursor) |
| 171 if err != nil { | 222 if err != nil { |
| 172 return nil, err | 223 return nil, err |
| 173 } | 224 } |
| 174 » currentBuilds, err := getBuilds(c, masterName, builderName, false, 0) | 225 » if prevCursor, ok := maybeSetGetCursor(c, thisCursor, nextCursor, limit)
; ok { |
| 226 » » if prevCursor == nil { |
| 227 » » » // Magic string to signal display prev without cursor |
| 228 » » » result.PrevCursor = "EMPTY" |
| 229 » » } else { |
| 230 » » » result.PrevCursor = (*prevCursor).String() |
| 231 » » } |
| 232 » } |
| 233 » if nextCursor != nil { |
| 234 » » result.NextCursor = (*nextCursor).String() |
| 235 » } |
| 236 » // Cursor is not needed for current builds. |
| 237 » currentBuilds, _, err := getBuilds(c, masterName, builderName, false, 0,
nil) |
| 175 if err != nil { | 238 if err != nil { |
| 176 return nil, err | 239 return nil, err |
| 177 } | 240 } |
| 178 // currentBuilds is presented in reversed order, so flip it | 241 // currentBuilds is presented in reversed order, so flip it |
| 179 for i, j := 0, len(currentBuilds)-1; i < j; i, j = i+1, j-1 { | 242 for i, j := 0, len(currentBuilds)-1; i < j; i, j = i+1, j-1 { |
| 180 currentBuilds[i], currentBuilds[j] = currentBuilds[j], currentBu
ilds[i] | 243 currentBuilds[i], currentBuilds[j] = currentBuilds[j], currentBu
ilds[i] |
| 181 } | 244 } |
| 182 result.CurrentBuilds = currentBuilds | 245 result.CurrentBuilds = currentBuilds |
| 183 | 246 |
| 184 for _, fb := range finishedBuilds { | 247 for _, fb := range finishedBuilds { |
| 185 if fb != nil { | 248 if fb != nil { |
| 186 result.FinishedBuilds = append(result.FinishedBuilds, fb
) | 249 result.FinishedBuilds = append(result.FinishedBuilds, fb
) |
| 187 } | 250 } |
| 188 } | 251 } |
| 189 return result, nil | 252 return result, nil |
| 190 } | 253 } |
| OLD | NEW |