Chromium Code Reviews| 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) (*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 "v2:cursors:builders:" + base64.StdEncoding.EncodeToStrin g(blob[:]) | |
|
nodir
2017/04/18 20:27:49
are you sure cursors for buildbucket builders will
nodir
2017/04/18 20:27:49
limit has to be included in the cache key. Bug rep
hinoka
2017/04/20 21:37:08
Done.
hinoka
2017/04/20 21:37:08
Blarg.... basically you can't actually go back onc
| |
| 123 » } | |
| 124 » // Set the next cursor to this cursor mapping, if available. | |
| 125 » if nextCursor != nil { | |
| 126 » » thisKey := key(*nextCursor) | |
|
nodir
2017/04/18 20:27:49
nextKey? it is used only once, consider inlining i
hinoka
2017/04/20 21:37:08
Done.
| |
| 127 » » item := memcache.NewItem(c, thisKey) | |
| 128 » » if thisCursor == nil { | |
| 129 » » » // Make sure we know it exists, just empty | |
| 130 » » » item.SetValue([]byte{}) | |
| 131 » » } else { | |
| 132 » » » item.SetValue([]byte((*thisCursor).String())) | |
| 133 » » } | |
| 134 » » item.SetExpiration(24 * time.Hour) | |
| 135 » » memcache.Set(c, item) | |
| 136 » } | |
| 137 » // Try to get the last cursor, if valid and available. | |
| 138 » if thisCursor == nil { | |
| 139 » » return nil, false | |
| 140 » } | |
| 141 » if item, err := memcache.GetKey(c, key(*thisCursor)); err == nil { | |
| 142 » » if len(item.Value()) == 0 { | |
| 143 » » » return nil, true | |
| 144 » » } | |
| 145 » » if prevCursor, err := datastore.DecodeCursor(c, string(item.Valu e())); err == nil { | |
| 146 » » » return &prevCursor, true | |
| 147 » » } | |
| 148 » } | |
| 149 » return nil, false | |
| 110 } | 150 } |
| 111 | 151 |
| 112 var errMasterNotFound = errors.New( | 152 var errMasterNotFound = errors.New( |
| 113 "Either the request resource was not found or you have insufficient perm issions") | 153 "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") | 154 var errNotAuth = errors.New("You are not authenticated, try logging in") |
| 115 | 155 |
| 116 // builderImpl is the implementation for getting a milo builder page from buildb ot. | 156 // builderImpl is the implementation for getting a milo builder page from buildb ot. |
| 117 // This gets: | 157 // This gets: |
| 118 // * Current Builds from querying the master json from the datastore. | 158 // * Current Builds from querying the master json from the datastore. |
| 119 // * Recent Builds from a cron job that backfills the recent builds. | 159 // * 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) { | 160 func builderImpl( |
| 161 » c context.Context, masterName, builderName string, limit int, | |
| 162 » cursor *datastore.Cursor) (*resp.Builder, error) { | |
|
nodir
2017/04/18 20:27:49
consider making cursor a string here. datastore is
hinoka
2017/04/20 21:37:08
Done.
| |
| 163 | |
| 121 result := &resp.Builder{ | 164 result := &resp.Builder{ |
| 122 Name: builderName, | 165 Name: builderName, |
| 123 } | 166 } |
| 124 master, t, err := getMasterJSON(c, masterName) | 167 master, t, err := getMasterJSON(c, masterName) |
| 125 if err != nil { | 168 if err != nil { |
| 126 return nil, err | 169 return nil, err |
| 127 } | 170 } |
| 128 if clock.Now(c).Sub(t) > 2*time.Minute { | 171 if clock.Now(c).Sub(t) > 2*time.Minute { |
| 129 warning := fmt.Sprintf( | 172 warning := fmt.Sprintf( |
| 130 "WARNING: Master data is stale (last updated %s)", t) | 173 "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)) | 203 result.PendingBuilds[i].Blame = make([]*resp.Commit, len(pb.Sour ce.Changes)) |
| 161 for j, cm := range pb.Source.Changes { | 204 for j, cm := range pb.Source.Changes { |
| 162 result.PendingBuilds[i].Blame[j] = &resp.Commit{ | 205 result.PendingBuilds[i].Blame[j] = &resp.Commit{ |
| 163 AuthorEmail: cm.Who, | 206 AuthorEmail: cm.Who, |
| 164 CommitURL: cm.Revlink, | 207 CommitURL: cm.Revlink, |
| 165 } | 208 } |
| 166 } | 209 } |
| 167 } | 210 } |
| 168 | 211 |
| 169 // This is CPU bound anyways, so there's no need to do this in parallel. | 212 // This is CPU bound anyways, so there's no need to do this in parallel. |
| 170 » finishedBuilds, err := getBuilds(c, masterName, builderName, true, limit ) | 213 » finishedBuilds, nextCursor, err := getBuilds(c, masterName, builderName, true, limit, cursor) |
| 171 if err != nil { | 214 if err != nil { |
| 172 return nil, err | 215 return nil, err |
| 173 } | 216 } |
| 174 » currentBuilds, err := getBuilds(c, masterName, builderName, false, 0) | 217 » if prevCursor, ok := maybeSetGetCursor(c, cursor, nextCursor); ok { |
| 218 » » if prevCursor == nil { | |
| 219 » » » // Magic string to signal display prev without cursor | |
| 220 » » » result.PrevCursor = "EMPTY" | |
| 221 » » } else { | |
| 222 » » » result.PrevCursor = (*prevCursor).String() | |
| 223 » » } | |
| 224 » } | |
| 225 » if nextCursor != nil { | |
| 226 » » result.NextCursor = (*nextCursor).String() | |
| 227 » } | |
| 228 » // Cursor is not needed for current builds. | |
| 229 » currentBuilds, _, err := getBuilds(c, masterName, builderName, false, 0, nil) | |
| 175 if err != nil { | 230 if err != nil { |
| 176 return nil, err | 231 return nil, err |
| 177 } | 232 } |
| 178 // currentBuilds is presented in reversed order, so flip it | 233 // 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 { | 234 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] | 235 currentBuilds[i], currentBuilds[j] = currentBuilds[j], currentBu ilds[i] |
| 181 } | 236 } |
| 182 result.CurrentBuilds = currentBuilds | 237 result.CurrentBuilds = currentBuilds |
| 183 | 238 |
| 184 for _, fb := range finishedBuilds { | 239 for _, fb := range finishedBuilds { |
| 185 if fb != nil { | 240 if fb != nil { |
| 186 result.FinishedBuilds = append(result.FinishedBuilds, fb ) | 241 result.FinishedBuilds = append(result.FinishedBuilds, fb ) |
| 187 } | 242 } |
| 188 } | 243 } |
| 189 return result, nil | 244 return result, nil |
| 190 } | 245 } |
| OLD | NEW |