OLD | NEW |
| (Empty) |
1 // Copyright 2015 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 "encoding/json" | |
9 "fmt" | |
10 "net/http" | |
11 "strings" | |
12 "time" | |
13 | |
14 "github.com/luci/gae/service/datastore" | |
15 "github.com/luci/luci-go/appengine/cmd/milo/model" | |
16 log "github.com/luci/luci-go/common/logging" | |
17 "github.com/luci/luci-go/common/parallel" | |
18 "golang.org/x/net/context" | |
19 ) | |
20 | |
21 // BuildExtractURL is the URL of Chrome Build extract, which caches buildbot | |
22 // json. | |
23 const BuildExtractURL = "https://chrome-build-extract.appspot.com" | |
24 | |
25 const workPoolSizeMultiplier = 2 | |
26 | |
27 type changeJSON struct { | |
28 Repository string `json:"repository"` | |
29 Digest string `json:"revision"` | |
30 UnixTime int64 `json:"when"` | |
31 } | |
32 | |
33 type sourceJSON struct { | |
34 Changes []changeJSON `json:"changes"` | |
35 } | |
36 | |
37 type buildJSON struct { | |
38 // Times elements are a float64 unix time that the build was started. It
's | |
39 // an array because buildbot :) | |
40 Times []float64 `json:"times"` | |
41 Builder string `json:"builderName"` | |
42 Master string `json:"masterName"` | |
43 Error string `json:"error"` | |
44 Logs [][]string `json:"logs"` | |
45 SourceStamp sourceJSON `json:"sourceStamp"` | |
46 Number int `json:"number"` | |
47 Text []string `json:"text"` | |
48 } | |
49 | |
50 func getRevForChange(c context.Context, change changeJSON) model.RevisionInfo { | |
51 repo := model.GetRepository(c, change.Repository) | |
52 | |
53 generation := -1 | |
54 if rev, err := repo.GetRevision(c, change.Digest); err == nil { | |
55 generation = rev.Generation | |
56 } | |
57 | |
58 return model.RevisionInfo{ | |
59 Repository: change.Repository, | |
60 Digest: change.Digest, | |
61 Generation: generation, | |
62 } | |
63 } | |
64 | |
65 func (b *buildJSON) toDS(c context.Context) *model.Build { | |
66 perRepo := make(map[string]*changeJSON) | |
67 for _, change := range b.SourceStamp.Changes { | |
68 current := perRepo[change.Repository] | |
69 if current != nil { | |
70 if current.UnixTime < change.UnixTime { | |
71 perRepo[change.Repository] = &change | |
72 } | |
73 } else { | |
74 perRepo[change.Repository] = &change | |
75 } | |
76 } | |
77 | |
78 revisions := make([]model.RevisionInfo, 0, len(perRepo)) | |
79 for _, change := range perRepo { | |
80 revisions = append(revisions, getRevForChange(c, *change)) | |
81 } | |
82 | |
83 logKey := "" | |
84 if len(b.Logs) > 1 && len(b.Logs[1]) > 1 { | |
85 logKey = b.Logs[1][1] | |
86 } else { | |
87 log.Warningf(c, "build %d on %s master %s had weird logs\n", b.N
umber, b.Builder, b.Master) | |
88 } | |
89 | |
90 userStatus := "UNKNOWN" | |
91 if len(b.Text) < 2 { | |
92 log.Warningf(c, "build %d on %s master %s had strange text\n", b
.Number, b.Builder, b.Master) | |
93 } else { | |
94 if b.Text[0] == "exception" { | |
95 userStatus = "EXCEPTION" | |
96 } else if b.Text[0] == "failed" { | |
97 userStatus = "FAILURE" | |
98 } else if b.Text[1] == "successful" { | |
99 userStatus = "SUCCESS" | |
100 } | |
101 if userStatus == "UNKNOWN" { | |
102 log.Warningf(c, "build %d on %s master %s had unknown te
xt %v\n", b.Number, b.Builder, b.Master, b.Text) | |
103 } | |
104 } | |
105 | |
106 return &model.Build{ | |
107 ExecutionTime: model.TimeID{time.Unix(int64(b.Times[0]), 0).UTC(
)}, | |
108 BuildRoot: model.GetBuildRoot(c, b.Master, b.Builder).Key, | |
109 BuildLogKey: logKey, | |
110 Revisions: revisions, | |
111 UserStatus: userStatus, | |
112 } | |
113 } | |
114 | |
115 type builderJSON struct { | |
116 Builds []int `json:"cachedBuilds"` | |
117 } | |
118 | |
119 type masterJSON struct { | |
120 Builders map[string]builderJSON `json:"builders"` | |
121 } | |
122 | |
123 func getBuild(master, builder string, build int, useCBE bool) (*buildJSON, error
) { | |
124 resp := (*http.Response)(nil) | |
125 err := (error)(nil) | |
126 if useCBE { | |
127 resp, err = http.Get(strings.Join( | |
128 []string{BuildExtractURL, "p", master, "builders", build
er, "builds", fmt.Sprintf("%d", build)}, | |
129 "/") + "?json=1") | |
130 } else { | |
131 resp, err = http.Get(strings.Join( | |
132 []string{"https://build.chromium.org", "p", master, "jso
n", "builders", builder, "builds", fmt.Sprintf("%d", build)}, | |
133 "/")) | |
134 } | |
135 | |
136 if err != nil { | |
137 return nil, err | |
138 } | |
139 defer resp.Body.Close() | |
140 | |
141 dec := json.NewDecoder(resp.Body) | |
142 doneBuild := &buildJSON{} | |
143 err = dec.Decode(doneBuild) | |
144 if err != nil { | |
145 return nil, err | |
146 } | |
147 | |
148 if doneBuild.Error != "" { | |
149 return nil, fmt.Errorf("error while getting Build for master %s
builder %s build number %d: %s", master, builder, build, doneBuild.Error) | |
150 } | |
151 return doneBuild, nil | |
152 } | |
153 | |
154 // PopulateMaster puts the data for a master into the datastore. | |
155 // | |
156 // It hits CBE to grab the json, and then creates corresponding entities and | |
157 // puts them into the datastore. It assumes that a datastore impl exists in the | |
158 // supplied context. | |
159 func PopulateMaster(c context.Context, master string, dryRun, buildbotFallback b
ool) error { | |
160 masterJSON := &masterJSON{} | |
161 resp, err := http.Get(strings.Join([]string{BuildExtractURL, "get_master
", master}, "/")) | |
162 if err != nil { | |
163 return err | |
164 } | |
165 | |
166 defer resp.Body.Close() | |
167 dec := json.NewDecoder(resp.Body) | |
168 err = dec.Decode(masterJSON) | |
169 if err != nil { | |
170 return err | |
171 } | |
172 | |
173 buildsToPut := make(map[string][]*model.Build) | |
174 log.Infof(c, "Getting builds for master %s\n", master) | |
175 | |
176 errors := parallel.WorkPool(workPoolSizeMultiplier*len(masterJSON.Builde
rs), func(ch chan<- func() error) { | |
177 for builderName, builder := range masterJSON.Builders { | |
178 if len(builder.Builds) > 0 { | |
179 buildsToPut[builderName] = make([]*model.Build,
len(builder.Builds)) | |
180 | |
181 for ind, builderNum := range builder.Builds { | |
182 ind := ind | |
183 builderNum := builderNum | |
184 builderName := builderName | |
185 | |
186 ch <- func() error { | |
187 res, err := getBuild(master, bui
lderName, builderNum, true) | |
188 if err != nil { | |
189 if !buildbotFallback { | |
190 log.Warningf(c,
"got %s, giving up", err) | |
191 return err | |
192 } | |
193 | |
194 log.Warningf(c, "got %s,
retrying without CBE", err) | |
195 res, err = getBuild(mast
er, builderName, builderNum, false) | |
196 | |
197 if err != nil { | |
198 log.Warningf(c,
"got %s, giving up", err) | |
199 return err | |
200 } | |
201 } | |
202 | |
203 conv := res.toDS(c) | |
204 buildsToPut[builderName][ind] =
conv | |
205 return nil | |
206 } | |
207 } | |
208 log.Infof(c, "Queued builds for builder %s.", bu
ilderName) | |
209 } | |
210 } | |
211 }) | |
212 | |
213 // We ignore these warnings because, for some reason, CBE sometimes retu
rns | |
214 // 404s for some builds. So we ignore those, and just move on. They're | |
215 // printed to the user above, so if something is really wrong all those | |
216 // errors will show up. | |
217 log.Warningf(c, "Done fetching builds. errors: %s.\n", errors) | |
218 | |
219 for name, builds := range buildsToPut { | |
220 num := 0 | |
221 for _, b := range builds { | |
222 if b != nil { | |
223 num++ | |
224 } | |
225 } | |
226 | |
227 if num == 0 { | |
228 buildsToPut[name] = nil | |
229 continue | |
230 } | |
231 | |
232 newBuilds := make([]*model.Build, num) | |
233 ind := 0 | |
234 for _, b := range builds { | |
235 if b != nil { | |
236 newBuilds[ind] = b | |
237 ind++ | |
238 } | |
239 } | |
240 buildsToPut[name] = newBuilds | |
241 } | |
242 | |
243 ds := datastore.Get(c) | |
244 for builderName, toPut := range buildsToPut { | |
245 log.Infof(c, "%d to put for builder %s\n", len(toPut), builderNa
me) | |
246 if toPut != nil { | |
247 if dryRun { | |
248 log.Infof(c, "dry run, not putting any modificat
ions") | |
249 } else { | |
250 ds.Put(toPut) | |
251 } | |
252 } | |
253 } | |
254 return nil | |
255 } | |
OLD | NEW |