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

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

Issue 2944983003: [milo] {buildbucket,buildbot,swarming,logdog} -> backends/*. (Closed)
Patch Set: fix the tests Created 3 years, 6 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
« no previous file with comments | « milo/appengine/buildbot/build_test.go ('k') | milo/appengine/buildbot/builder_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 "crypto/sha1"
9 "encoding/base64"
10 "errors"
11 "fmt"
12 "sort"
13 "strings"
14 "time"
15
16 "github.com/luci/gae/service/datastore"
17 "github.com/luci/gae/service/memcache"
18
19 "github.com/luci/luci-go/common/clock"
20 "github.com/luci/luci-go/common/logging"
21 "github.com/luci/luci-go/milo/api/resp"
22 "golang.org/x/net/context"
23 )
24
25 // builderRef is used for keying specific builds in a master json.
26 type builderRef struct {
27 builder string
28 buildNum int
29 }
30
31 // buildMap contains all of the current build within a master json. We use this
32 // because buildbot returns all current builds as within the slaves portion, whe reas
33 // it's eaiser to map thenm by builders instead.
34 type buildMap map[builderRef]*buildbotBuild
35
36 // mergeText merges buildbot summary texts, which sometimes separates
37 // words that should be merged together, this combines them into a single
38 // line.
39 func mergeText(text []string) []string {
40 result := make([]string, 0, len(text))
41 merge := false
42 for _, line := range text {
43 if merge {
44 merge = false
45 result[len(result)-1] += " " + line
46 continue
47 }
48 result = append(result, line)
49 switch line {
50 case "build", "failed", "exception":
51 merge = true
52 default:
53 merge = false
54 }
55 }
56
57 // We can remove error messages about the step "steps" if it's part of a longer
58 // message because this step is an artifact of running on recipes and it 's
59 // not important to users.
60 if len(result) > 1 {
61 switch result[0] {
62 case "failed steps", "exception steps":
63 result = result[1:]
64 }
65 }
66 return result
67 }
68
69 func getBuildSummary(b *buildbotBuild) *resp.BuildSummary {
70 started, finished, duration := parseTimes(nil, b.Times)
71 return &resp.BuildSummary{
72 Link: resp.NewLink(fmt.Sprintf("#%d", b.Number), fmt.Sprintf(" %d", b.Number)),
73 Status: b.toStatus(),
74 ExecutionTime: resp.Interval{
75 Started: started,
76 Finished: finished,
77 Duration: duration,
78 },
79 Text: mergeText(b.Text),
80 Blame: blame(b),
81 Revision: b.Sourcestamp.Revision,
82 }
83 }
84
85 // getBuilds fetches all of the recent builds from the . Note that
86 // getBuilds() does not perform ACL checks.
87 func getBuilds(
88 c context.Context, masterName, builderName string, finished bool, limit int, cursor *datastore.Cursor) (
89 []*resp.BuildSummary, *datastore.Cursor, error) {
90
91 // TODO(hinoka): Builder specific structs.
92 result := []*resp.BuildSummary{}
93 q := datastore.NewQuery("buildbotBuild")
94 q = q.Eq("finished", finished)
95 q = q.Eq("master", masterName)
96 q = q.Eq("builder", builderName)
97 q = q.Order("-number")
98 if cursor != nil {
99 q = q.Start(*cursor)
100 }
101 buildbots, nextCursor, err := runBuildsQuery(c, q, int32(limit))
102 if err != nil {
103 return nil, nil, err
104 }
105 for _, b := range buildbots {
106 result = append(result, getBuildSummary(b))
107 }
108 return result, nextCursor, nil
109 }
110
111 // maybeSetGetCursor is a cheesy way to implement bidirectional paging with forw ard-only
112 // datastore cursor by creating a mapping of nextCursor -> thisCursor
113 // in memcache. maybeSetGetCursor stores the future mapping, then returns prevC ursor
114 // in the mapping for thisCursor -> prevCursor, if available.
115 func maybeSetGetCursor(c context.Context, thisCursor, nextCursor *datastore.Curs or, limit int) (*datastore.Cursor, bool) {
116 key := func(c datastore.Cursor) string {
117 // Memcache key limit is 250 bytes, hash our cursor to get under this limit.
118 blob := sha1.Sum([]byte(c.String()))
119 return fmt.Sprintf("v2:cursors:buildbot_builders:%d:%s", limit, base64.StdEncoding.EncodeToString(blob[:]))
120 }
121 // Set the next cursor to this cursor mapping, if available.
122 if nextCursor != nil {
123 item := memcache.NewItem(c, key(*nextCursor))
124 if thisCursor == nil {
125 // Make sure we know it exists, just empty
126 item.SetValue([]byte{})
127 } else {
128 item.SetValue([]byte((*thisCursor).String()))
129 }
130 item.SetExpiration(24 * time.Hour)
131 memcache.Set(c, item)
132 }
133 // Try to get the last cursor, if valid and available.
134 if thisCursor == nil {
135 return nil, false
136 }
137 if item, err := memcache.GetKey(c, key(*thisCursor)); err == nil {
138 if len(item.Value()) == 0 {
139 return nil, true
140 }
141 if prevCursor, err := datastore.DecodeCursor(c, string(item.Valu e())); err == nil {
142 return &prevCursor, true
143 }
144 }
145 return nil, false
146 }
147
148 var errMasterNotFound = errors.New(
149 "Either the request resource was not found or you have insufficient perm issions")
150 var errNotAuth = errors.New("You are not authenticated, try logging in")
151
152 type errBuilderNotFound struct {
153 master string
154 builder string
155 available []string
156 }
157
158 func (e errBuilderNotFound) Error() string {
159 avail := strings.Join(e.available, "\n")
160 return fmt.Sprintf("Cannot find builder %q in master %q.\nAvailable buil ders: \n%s",
161 e.builder, e.master, avail)
162 }
163
164 func summarizeSlavePool(
165 baseURL string, slaves []string, slaveMap map[string]*buildbotSlave) *re sp.MachinePool {
166
167 mp := &resp.MachinePool{
168 Total: len(slaves),
169 Bots: make([]resp.Bot, 0, len(slaves)),
170 }
171 for _, slaveName := range slaves {
172 slave, ok := slaveMap[slaveName]
173 bot := resp.Bot{
174 Name: *resp.NewLink(
175 slaveName,
176 fmt.Sprintf("%s/buildslaves/%s", baseURL, slaveN ame),
177 ),
178 }
179 switch {
180 case !ok:
181 // This shouldn't happen
182 case !slave.Connected:
183 bot.Status = resp.Disconnected
184 mp.Disconnected++
185 case len(slave.RunningbuildsMap) > 0:
186 bot.Status = resp.Busy
187 mp.Busy++
188 default:
189 bot.Status = resp.Idle
190 mp.Idle++
191 }
192 mp.Bots = append(mp.Bots, bot)
193 }
194 return mp
195 }
196
197 // builderImpl is the implementation for getting a milo builder page from buildb ot.
198 // This gets:
199 // * Current Builds from querying the master json from the datastore.
200 // * Recent Builds from a cron job that backfills the recent builds.
201 func builderImpl(
202 c context.Context, masterName, builderName string, limit int, cursor str ing) (
203 *resp.Builder, error) {
204
205 var thisCursor *datastore.Cursor
206 if cursor != "" {
207 tmpCur, err := datastore.DecodeCursor(c, cursor)
208 if err != nil {
209 return nil, fmt.Errorf("bad cursor: %s", err)
210 }
211 thisCursor = &tmpCur
212 }
213
214 result := &resp.Builder{
215 Name: builderName,
216 }
217 master, internal, t, err := getMasterJSON(c, masterName)
218 if err != nil {
219 return nil, err
220 }
221 if clock.Now(c).Sub(t) > 2*time.Minute {
222 warning := fmt.Sprintf(
223 "WARNING: Master data is stale (last updated %s)", t)
224 logging.Warningf(c, warning)
225 result.Warning = warning
226 }
227
228 p, ok := master.Builders[builderName]
229 if !ok {
230 // This long block is just to return a good error message when a n invalid
231 // buildbot builder is specified.
232 keys := make([]string, 0, len(master.Builders))
233 for k := range master.Builders {
234 keys = append(keys, k)
235 }
236 sort.Strings(keys)
237 return nil, errBuilderNotFound{masterName, builderName, keys}
238 }
239 // Extract pending builds out of the master json.
240 result.PendingBuilds = make([]*resp.BuildSummary, len(p.PendingBuildStat es))
241 logging.Debugf(c, "Number of pending builds: %d", len(p.PendingBuildStat es))
242 for i, pb := range p.PendingBuildStates {
243 start := time.Unix(int64(pb.SubmittedAt), 0).UTC()
244 result.PendingBuilds[i] = &resp.BuildSummary{
245 PendingTime: resp.Interval{
246 Started: start,
247 Duration: clock.Now(c).UTC().Sub(start),
248 },
249 }
250 result.PendingBuilds[i].Blame = make([]*resp.Commit, len(pb.Sour ce.Changes))
251 for j, cm := range pb.Source.Changes {
252 result.PendingBuilds[i].Blame[j] = &resp.Commit{
253 AuthorEmail: cm.Who,
254 CommitURL: cm.Revlink,
255 }
256 }
257 }
258
259 baseURL := "https://build.chromium.org/p/"
260 if internal {
261 baseURL = "https://uberchromegw.corp.google.com/i/"
262 }
263 result.MachinePool = summarizeSlavePool(baseURL+master.Name, p.Slaves, m aster.Slaves)
264
265 // This is CPU bound anyways, so there's no need to do this in parallel.
266 finishedBuilds, nextCursor, err := getBuilds(c, masterName, builderName, true, limit, thisCursor)
267 if err != nil {
268 return nil, err
269 }
270 if prevCursor, ok := maybeSetGetCursor(c, thisCursor, nextCursor, limit) ; ok {
271 if prevCursor == nil {
272 // Magic string to signal display prev without cursor
273 result.PrevCursor = "EMPTY"
274 } else {
275 result.PrevCursor = (*prevCursor).String()
276 }
277 }
278 if nextCursor != nil {
279 result.NextCursor = (*nextCursor).String()
280 }
281 // Cursor is not needed for current builds.
282 currentBuilds, _, err := getBuilds(c, masterName, builderName, false, 0, nil)
283 if err != nil {
284 return nil, err
285 }
286 // currentBuilds is presented in reversed order, so flip it
287 for i, j := 0, len(currentBuilds)-1; i < j; i, j = i+1, j-1 {
288 currentBuilds[i], currentBuilds[j] = currentBuilds[j], currentBu ilds[i]
289 }
290 result.CurrentBuilds = currentBuilds
291
292 for _, fb := range finishedBuilds {
293 if fb != nil {
294 result.FinishedBuilds = append(result.FinishedBuilds, fb )
295 }
296 }
297 return result, nil
298 }
OLDNEW
« no previous file with comments | « milo/appengine/buildbot/build_test.go ('k') | milo/appengine/buildbot/builder_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698