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 "encoding/json" | 8 "encoding/json" |
| 9 "fmt" | 9 "fmt" |
| 10 "io/ioutil" | 10 "io/ioutil" |
| 11 "net/http" | 11 "net/http" |
| 12 "os" | |
| 13 "path/filepath" | 12 "path/filepath" |
| 14 "regexp" | 13 "regexp" |
| 15 "sort" | 14 "sort" |
| 16 "strconv" | 15 "strconv" |
| 17 "strings" | 16 "strings" |
| 18 "time" | 17 "time" |
| 19 | 18 |
| 20 "github.com/luci/gae/service/datastore" | 19 "github.com/luci/gae/service/datastore" |
| 21 » "github.com/luci/gae/service/memcache" | 20 » "github.com/luci/luci-go/common/logging" |
| 22 » log "github.com/luci/luci-go/common/logging" | |
| 23 "github.com/luci/luci-go/milo/api/resp" | 21 "github.com/luci/luci-go/milo/api/resp" |
| 24 » "github.com/luci/luci-go/server/auth" | 22 » "github.com/luci/luci-go/milo/appengine/settings" |
| 23 » "github.com/luci/luci-go/milo/common/miloerror" | |
| 25 "golang.org/x/net/context" | 24 "golang.org/x/net/context" |
| 26 ) | 25 ) |
| 27 | 26 |
| 28 func getURL(c context.Context, URL string) ([]byte, error) { | 27 var errBuildNotFound = miloerror.Error{ |
| 29 » fmt.Fprintf(os.Stderr, "Fetching %s\n", URL) | 28 » Message: "Build not found", |
| 30 » tr, err := auth.GetRPCTransport(c, auth.NoAuth) | 29 » Code: http.StatusNotFound, |
| 31 » if err != nil { | |
| 32 » » return nil, err | |
| 33 » } | |
| 34 » client := http.Client{Transport: tr} | |
| 35 » resp, err := client.Get(URL) | |
| 36 » if err != nil { | |
| 37 » » return nil, err | |
| 38 » } | |
| 39 » if resp.StatusCode != 200 { | |
| 40 » » return nil, fmt.Errorf("Failed to fetch %s, status code %d", URL , resp.StatusCode) | |
| 41 » } | |
| 42 » defer resp.Body.Close() | |
| 43 » return ioutil.ReadAll(resp.Body) | |
| 44 } | 30 } |
| 45 | 31 |
| 46 // getBuild fetches a build from BuildBot. | 32 // getBuild fetches a buildbot build from the datastore and checks ACLs. |
| 47 func getBuild(c context.Context, master, builder, buildNum string) (*buildbotBui ld, error) { | 33 func getBuild(c context.Context, master, builder, buildNum string) (*buildbotBui ld, error) { |
| 48 result := &buildbotBuild{ | 34 result := &buildbotBuild{ |
| 49 Master: master, | 35 Master: master, |
| 50 Buildername: builder, | 36 Buildername: builder, |
| 51 } | 37 } |
| 52 if num, err := strconv.Atoi(buildNum); err != nil { | 38 if num, err := strconv.Atoi(buildNum); err != nil { |
| 53 » » panic(fmt.Errorf("%s does not look like a number", buildNum)) | 39 » » return nil, miloerror.Error{ |
| 40 » » » Message: fmt.Sprintf("%s does not look like a number", b uildNum), | |
| 41 » » » Code: http.StatusBadRequest, | |
| 42 » » } | |
| 54 } else { | 43 } else { |
| 55 result.Number = num | 44 result.Number = num |
| 56 } | 45 } |
| 57 // Check memcache first. | |
| 58 mc := memcache.Get(c) | |
| 59 if item, err := mc.Get(result.getID()); err == nil { | |
| 60 log.Debugf(c, "Found in Memcache!") | |
| 61 json.Unmarshal(item.Value(), result) | |
| 62 return result, nil | |
| 63 } | |
| 64 // Then check local datastore. | |
| 65 ds := datastore.Get(c) | 46 ds := datastore.Get(c) |
| 66 err := ds.Get(result) | 47 err := ds.Get(result) |
| 67 » if err == nil { | 48 » switch err { |
|
Vadim Sh.
2016/08/23 22:35:46
nit: I usually do
switch {
case err == datastor
Ryan Tseng
2016/08/24 21:07:24
Done.
| |
| 68 » » log.Debugf(c, "Found build in local cache!") | 49 » case nil: |
| 69 » » if result.Times[1] != nil { | 50 » case datastore.ErrNoSuchEntity: |
| 70 » » » bs, ierr := json.Marshal(result) | 51 » » return nil, errBuildNotFound |
| 71 » » » if ierr == nil { | 52 » default: |
| 72 » » » » item := mc.NewItem(result.getID()).SetValue(bs) | |
| 73 » » » » mc.Set(item) | |
| 74 » » » } | |
| 75 » » } | |
| 76 » » return result, nil | |
| 77 » } | |
| 78 » log.Debugf(c, "Could not find log in local cache: %s", err.Error()) | |
| 79 » // Now check CBE. | |
| 80 » cbeURL := fmt.Sprintf( | |
| 81 » » "https://chrome-build-extract.appspot.com/p/%s/builders/%s/build s/%s?json=1", | |
| 82 » » master, builder, buildNum) | |
| 83 » if buf, err := getURL(c, cbeURL); err == nil { | |
| 84 » » return result, json.Unmarshal(buf, result) | |
| 85 » } | |
| 86 » // TODO(hinoka): Cache finished builds. | |
| 87 » url := fmt.Sprintf( | |
| 88 » » "https://build.chromium.org/p/%s/json/builders/%s/builds/%s", ma ster, builder, buildNum) | |
| 89 » buf, err := getURL(c, url) | |
| 90 » if err != nil { | |
| 91 return nil, err | 53 return nil, err |
| 92 } | 54 } |
| 55 if result.Internal { | |
| 56 allowed, err := settings.IsAllowedInternal(c) | |
| 57 if err != nil { | |
| 58 return nil, err | |
| 59 } | |
| 60 if !allowed { | |
| 61 return nil, errBuildNotFound | |
| 62 } | |
| 63 } | |
| 93 | 64 |
| 94 » return result, json.Unmarshal(buf, result) | 65 » return result, nil |
| 95 } | 66 } |
| 96 | 67 |
| 97 // result2Status translates a buildbot result integer into a resp.Status. | 68 // result2Status translates a buildbot result integer into a resp.Status. |
| 98 func result2Status(s *int) (status resp.Status) { | 69 func result2Status(s *int) (status resp.Status) { |
| 99 if s == nil { | 70 if s == nil { |
| 100 return resp.Running | 71 return resp.Running |
| 101 } | 72 } |
| 102 switch *s { | 73 switch *s { |
| 103 case 0: | 74 case 0: |
| 104 status = resp.Success | 75 status = resp.Success |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 145 // getBanner fetches the banner information about the bot that the build | 116 // getBanner fetches the banner information about the bot that the build |
| 146 // ran on. This is best effort and: | 117 // ran on. This is best effort and: |
| 147 // * Will return an empty list of banners if the master is not found. | 118 // * Will return an empty list of banners if the master is not found. |
| 148 // * May return incorrect data if the platform of the slave of the same name | 119 // * May return incorrect data if the platform of the slave of the same name |
| 149 // was changed recently, and this build was older than before the slave | 120 // was changed recently, and this build was older than before the slave |
| 150 // changed platforms. | 121 // changed platforms. |
| 151 func getBanner(c context.Context, b *buildbotBuild, m *buildbotMaster) *resp.Log oBanner { | 122 func getBanner(c context.Context, b *buildbotBuild, m *buildbotMaster) *resp.Log oBanner { |
| 152 logos := &resp.LogoBanner{} | 123 logos := &resp.LogoBanner{} |
| 153 // Fetch the master info from datastore if not provided. | 124 // Fetch the master info from datastore if not provided. |
| 154 if m == nil { | 125 if m == nil { |
| 155 » » m1, i, _, err := getMasterJSON(c, b.Master) | 126 » » m1, _, _, err := getMasterJSON(c, b.Master) |
| 156 m = m1 | 127 m = m1 |
| 157 if i { | |
| 158 // TODO(hinoka): Support internal masters. | |
| 159 return nil | |
| 160 } | |
| 161 if err != nil { | 128 if err != nil { |
| 162 » » » log.Warningf(c, "Failed to fetch master information for banners on master %s", b.Master) | 129 » » » logging.Warningf(c, "Failed to fetch master information for banners on master %s", b.Master) |
| 163 return nil | 130 return nil |
| 164 } | 131 } |
| 165 } | 132 } |
| 166 | 133 |
| 167 s, ok := m.Slaves[b.Slave] | 134 s, ok := m.Slaves[b.Slave] |
| 168 if !ok { | 135 if !ok { |
| 169 » » log.Warningf(c, "Could not find slave %s in master %s", b.Slave, b.Master) | 136 » » logging.Warningf(c, "Could not find slave %s in master %s", b.Sl ave, b.Master) |
| 170 return nil | 137 return nil |
| 171 } | 138 } |
| 172 hostInfo := map[string]string{} | 139 hostInfo := map[string]string{} |
| 173 for _, v := range strings.Split(s.Host, "\n") { | 140 for _, v := range strings.Split(s.Host, "\n") { |
| 174 if info := strings.SplitN(v, ":", 2); len(info) == 2 { | 141 if info := strings.SplitN(v, ":", 2); len(info) == 2 { |
| 175 hostInfo[info[0]] = strings.TrimSpace(info[1]) | 142 hostInfo[info[0]] = strings.TrimSpace(info[1]) |
| 176 } | 143 } |
| 177 } | 144 } |
| 178 | 145 |
| 179 // Extract OS and OS Family | 146 // Extract OS and OS Family |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 195 result.LogoBase = resp.Ubuntu | 162 result.LogoBase = resp.Ubuntu |
| 196 default: | 163 default: |
| 197 return nil | 164 return nil |
| 198 } | 165 } |
| 199 result.Subtitle = version | 166 result.Subtitle = version |
| 200 return result | 167 return result |
| 201 }() | 168 }() |
| 202 if osLogo != nil { | 169 if osLogo != nil { |
| 203 logos.OS = append(logos.OS, *osLogo) | 170 logos.OS = append(logos.OS, *osLogo) |
| 204 } else { | 171 } else { |
| 205 » » log.Warningf(c, "No OS info found. Host: %s", s.Host) | 172 » » logging.Warningf(c, "No OS info found. Host: %s", s.Host) |
| 206 } | 173 } |
| 207 » log.Infof(c, "OS: %s/%s", os, version) | 174 » logging.Infof(c, "OS: %s/%s", os, version) |
| 208 return logos | 175 return logos |
| 209 } | 176 } |
| 210 | 177 |
| 211 // summary Extract the top level summary from a buildbot build as a | 178 // summary Extract the top level summary from a buildbot build as a |
| 212 // BuildComponent | 179 // BuildComponent |
| 213 func summary(c context.Context, b *buildbotBuild) resp.BuildComponent { | 180 func summary(c context.Context, b *buildbotBuild) resp.BuildComponent { |
| 214 // TODO(hinoka): use b.toStatus() | 181 // TODO(hinoka): use b.toStatus() |
| 215 // Status | 182 // Status |
| 216 var status resp.Status | 183 var status resp.Status |
| 217 if b.Currentstep != nil { | 184 if b.Currentstep != nil { |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 283 bc.Status = resp.Running | 250 bc.Status = resp.Running |
| 284 } else { | 251 } else { |
| 285 if len(step.Results) > 0 { | 252 if len(step.Results) > 0 { |
| 286 status := int(step.Results[0].(float64)) | 253 status := int(step.Results[0].(float64)) |
| 287 bc.Status = result2Status(&status) | 254 bc.Status = result2Status(&status) |
| 288 } else { | 255 } else { |
| 289 bc.Status = resp.Success | 256 bc.Status = resp.Success |
| 290 } | 257 } |
| 291 } | 258 } |
| 292 | 259 |
| 293 » » // Now, link to the logs. | 260 » » // Now, link to the loggings. |
| 294 » » for _, log := range step.Logs { | 261 » » for _, l := range step.Logs { |
| 295 link := &resp.Link{ | 262 link := &resp.Link{ |
| 296 » » » » Label: log[0], | 263 » » » » Label: l[0], |
| 297 » » » » URL: log[1], | 264 » » » » URL: l[1], |
| 298 } | 265 } |
| 299 if link.Label == "stdio" { | 266 if link.Label == "stdio" { |
| 300 bc.MainLink = link | 267 bc.MainLink = link |
| 301 } else { | 268 } else { |
| 302 bc.SubLink = append(bc.SubLink, link) | 269 bc.SubLink = append(bc.SubLink, link) |
| 303 } | 270 } |
| 304 } | 271 } |
| 305 | 272 |
| 306 // Figure out the times | 273 // Figure out the times |
| 307 bc.Started, bc.Finished, bc.Duration = parseTimes(step.Times) | 274 bc.Started, bc.Finished, bc.Duration = parseTimes(step.Times) |
| (...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 442 issue := int64(-1) | 409 issue := int64(-1) |
| 443 // TODO(hinoka): Gerrit URLs. | 410 // TODO(hinoka): Gerrit URLs. |
| 444 for _, prop := range b.Properties { | 411 for _, prop := range b.Properties { |
| 445 key := prop[0].(string) | 412 key := prop[0].(string) |
| 446 value := prop[1] | 413 value := prop[1] |
| 447 switch key { | 414 switch key { |
| 448 case "rietveld": | 415 case "rietveld": |
| 449 if v, ok := value.(string); ok { | 416 if v, ok := value.(string); ok { |
| 450 rietveld = v | 417 rietveld = v |
| 451 } else { | 418 } else { |
| 452 » » » » log.Warningf(c, "Field rietveld is not a string: %#v", value) | 419 » » » » logging.Warningf(c, "Field rietveld is not a str ing: %#v", value) |
| 453 } | 420 } |
| 454 case "issue": | 421 case "issue": |
| 455 if v, ok := value.(float64); ok { | 422 if v, ok := value.(float64); ok { |
| 456 issue = int64(v) | 423 issue = int64(v) |
| 457 } else { | 424 } else { |
| 458 » » » » log.Warningf(c, "Field issue is not a float: %#v ", value) | 425 » » » » logging.Warningf(c, "Field issue is not a float: %#v", value) |
| 459 } | 426 } |
| 460 | 427 |
| 461 case "got_revision": | 428 case "got_revision": |
| 462 if v, ok := value.(string); ok { | 429 if v, ok := value.(string); ok { |
| 463 ss.Revision = v | 430 ss.Revision = v |
| 464 } else { | 431 } else { |
| 465 » » » » log.Warningf(c, "Field got_revision is not a str ing: %#v", value) | 432 » » » » logging.Warningf(c, "Field got_revision is not a string: %#v", value) |
| 466 } | 433 } |
| 467 | 434 |
| 468 } | 435 } |
| 469 } | 436 } |
| 470 if issue != -1 { | 437 if issue != -1 { |
| 471 if rietveld != "" { | 438 if rietveld != "" { |
| 472 rietveld = strings.TrimRight(rietveld, "/") | 439 rietveld = strings.TrimRight(rietveld, "/") |
| 473 ss.Changelist = &resp.Link{ | 440 ss.Changelist = &resp.Link{ |
| 474 Label: fmt.Sprintf("Issue %d", issue), | 441 Label: fmt.Sprintf("Issue %d", issue), |
| 475 URL: fmt.Sprintf("%s/%d", rietveld, issue), | 442 URL: fmt.Sprintf("%s/%d", rietveld, issue), |
| 476 } | 443 } |
| 477 } else { | 444 } else { |
| 478 » » » log.Warningf(c, "Found issue but not rietveld property." ) | 445 » » » logging.Warningf(c, "Found issue but not rietveld proper ty.") |
| 479 } | 446 } |
| 480 } | 447 } |
| 481 return ss | 448 return ss |
| 482 } | 449 } |
| 483 | 450 |
| 484 func getDebugBuild(c context.Context, builder, buildNum string) (*buildbotBuild, error) { | 451 func getDebugBuild(c context.Context, builder, buildNum string) (*buildbotBuild, error) { |
| 485 fname := fmt.Sprintf("%s.%s.json", builder, buildNum) | 452 fname := fmt.Sprintf("%s.%s.json", builder, buildNum) |
| 486 // ../buildbot below assumes that | 453 // ../buildbot below assumes that |
| 487 // - this code is not executed by tests outside of this dir | 454 // - this code is not executed by tests outside of this dir |
| 488 // - this dir is a sibling of frontend dir | 455 // - this dir is a sibling of frontend dir |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 510 | 477 |
| 511 // TODO(hinoka): Do all fields concurrently. | 478 // TODO(hinoka): Do all fields concurrently. |
| 512 return &resp.MiloBuild{ | 479 return &resp.MiloBuild{ |
| 513 SourceStamp: sourcestamp(c, b), | 480 SourceStamp: sourcestamp(c, b), |
| 514 Summary: summary(c, b), | 481 Summary: summary(c, b), |
| 515 Components: components(b), | 482 Components: components(b), |
| 516 PropertyGroup: properties(b), | 483 PropertyGroup: properties(b), |
| 517 Blame: blame(b), | 484 Blame: blame(b), |
| 518 }, nil | 485 }, nil |
| 519 } | 486 } |
| OLD | NEW |