| 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 |