| 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 { |
| 68 » » log.Debugf(c, "Found build in local cache!") | 49 » case err == datastore.ErrNoSuchEntity: |
| 69 » » if result.Times[1] != nil { | 50 » » return nil, errBuildNotFound |
| 70 » » » bs, ierr := json.Marshal(result) | 51 » case err != nil: |
| 71 » » » if ierr == nil { | |
| 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 | 52 return nil, err |
| 92 } | 53 } |
| 54 if result.Internal { |
| 55 allowed, err := settings.IsAllowedInternal(c) |
| 56 if err != nil { |
| 57 return nil, err |
| 58 } |
| 59 if !allowed { |
| 60 return nil, errBuildNotFound |
| 61 } |
| 62 } |
| 93 | 63 |
| 94 » return result, json.Unmarshal(buf, result) | 64 » return result, nil |
| 95 } | 65 } |
| 96 | 66 |
| 97 // result2Status translates a buildbot result integer into a resp.Status. | 67 // result2Status translates a buildbot result integer into a resp.Status. |
| 98 func result2Status(s *int) (status resp.Status) { | 68 func result2Status(s *int) (status resp.Status) { |
| 99 if s == nil { | 69 if s == nil { |
| 100 return resp.Running | 70 return resp.Running |
| 101 } | 71 } |
| 102 switch *s { | 72 switch *s { |
| 103 case 0: | 73 case 0: |
| 104 status = resp.Success | 74 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 | 115 // getBanner fetches the banner information about the bot that the build |
| 146 // ran on. This is best effort and: | 116 // ran on. This is best effort and: |
| 147 // * Will return an empty list of banners if the master is not found. | 117 // * 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 | 118 // * 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 | 119 // was changed recently, and this build was older than before the slave |
| 150 // changed platforms. | 120 // changed platforms. |
| 151 func getBanner(c context.Context, b *buildbotBuild, m *buildbotMaster) *resp.Log
oBanner { | 121 func getBanner(c context.Context, b *buildbotBuild, m *buildbotMaster) *resp.Log
oBanner { |
| 152 logos := &resp.LogoBanner{} | 122 logos := &resp.LogoBanner{} |
| 153 // Fetch the master info from datastore if not provided. | 123 // Fetch the master info from datastore if not provided. |
| 154 if m == nil { | 124 if m == nil { |
| 155 » » m1, i, _, err := getMasterJSON(c, b.Master) | 125 » » m1, _, _, err := getMasterJSON(c, b.Master) |
| 156 m = m1 | 126 m = m1 |
| 157 if i { | |
| 158 // TODO(hinoka): Support internal masters. | |
| 159 return nil | |
| 160 } | |
| 161 if err != nil { | 127 if err != nil { |
| 162 » » » log.Warningf(c, "Failed to fetch master information for
banners on master %s", b.Master) | 128 » » » logging.Warningf(c, "Failed to fetch master information
for banners on master %s", b.Master) |
| 163 return nil | 129 return nil |
| 164 } | 130 } |
| 165 } | 131 } |
| 166 | 132 |
| 167 s, ok := m.Slaves[b.Slave] | 133 s, ok := m.Slaves[b.Slave] |
| 168 if !ok { | 134 if !ok { |
| 169 » » log.Warningf(c, "Could not find slave %s in master %s", b.Slave,
b.Master) | 135 » » logging.Warningf(c, "Could not find slave %s in master %s", b.Sl
ave, b.Master) |
| 170 return nil | 136 return nil |
| 171 } | 137 } |
| 172 hostInfo := map[string]string{} | 138 hostInfo := map[string]string{} |
| 173 for _, v := range strings.Split(s.Host, "\n") { | 139 for _, v := range strings.Split(s.Host, "\n") { |
| 174 if info := strings.SplitN(v, ":", 2); len(info) == 2 { | 140 if info := strings.SplitN(v, ":", 2); len(info) == 2 { |
| 175 hostInfo[info[0]] = strings.TrimSpace(info[1]) | 141 hostInfo[info[0]] = strings.TrimSpace(info[1]) |
| 176 } | 142 } |
| 177 } | 143 } |
| 178 | 144 |
| 179 // Extract OS and OS Family | 145 // Extract OS and OS Family |
| (...skipping 15 matching lines...) Expand all Loading... |
| 195 result.LogoBase = resp.Ubuntu | 161 result.LogoBase = resp.Ubuntu |
| 196 default: | 162 default: |
| 197 return nil | 163 return nil |
| 198 } | 164 } |
| 199 result.Subtitle = version | 165 result.Subtitle = version |
| 200 return result | 166 return result |
| 201 }() | 167 }() |
| 202 if osLogo != nil { | 168 if osLogo != nil { |
| 203 logos.OS = append(logos.OS, *osLogo) | 169 logos.OS = append(logos.OS, *osLogo) |
| 204 } else { | 170 } else { |
| 205 » » log.Warningf(c, "No OS info found. Host: %s", s.Host) | 171 » » logging.Warningf(c, "No OS info found. Host: %s", s.Host) |
| 206 } | 172 } |
| 207 » log.Infof(c, "OS: %s/%s", os, version) | 173 » logging.Infof(c, "OS: %s/%s", os, version) |
| 208 return logos | 174 return logos |
| 209 } | 175 } |
| 210 | 176 |
| 211 // summary Extract the top level summary from a buildbot build as a | 177 // summary Extract the top level summary from a buildbot build as a |
| 212 // BuildComponent | 178 // BuildComponent |
| 213 func summary(c context.Context, b *buildbotBuild) resp.BuildComponent { | 179 func summary(c context.Context, b *buildbotBuild) resp.BuildComponent { |
| 214 // TODO(hinoka): use b.toStatus() | 180 // TODO(hinoka): use b.toStatus() |
| 215 // Status | 181 // Status |
| 216 var status resp.Status | 182 var status resp.Status |
| 217 if b.Currentstep != nil { | 183 if b.Currentstep != nil { |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 283 bc.Status = resp.Running | 249 bc.Status = resp.Running |
| 284 } else { | 250 } else { |
| 285 if len(step.Results) > 0 { | 251 if len(step.Results) > 0 { |
| 286 status := int(step.Results[0].(float64)) | 252 status := int(step.Results[0].(float64)) |
| 287 bc.Status = result2Status(&status) | 253 bc.Status = result2Status(&status) |
| 288 } else { | 254 } else { |
| 289 bc.Status = resp.Success | 255 bc.Status = resp.Success |
| 290 } | 256 } |
| 291 } | 257 } |
| 292 | 258 |
| 293 » » // Now, link to the logs. | 259 » » // Now, link to the loggings. |
| 294 » » for _, log := range step.Logs { | 260 » » for _, l := range step.Logs { |
| 295 link := &resp.Link{ | 261 link := &resp.Link{ |
| 296 » » » » Label: log[0], | 262 » » » » Label: l[0], |
| 297 » » » » URL: log[1], | 263 » » » » URL: l[1], |
| 298 } | 264 } |
| 299 if link.Label == "stdio" { | 265 if link.Label == "stdio" { |
| 300 bc.MainLink = link | 266 bc.MainLink = link |
| 301 } else { | 267 } else { |
| 302 bc.SubLink = append(bc.SubLink, link) | 268 bc.SubLink = append(bc.SubLink, link) |
| 303 } | 269 } |
| 304 } | 270 } |
| 305 | 271 |
| 306 // Figure out the times | 272 // Figure out the times |
| 307 bc.Started, bc.Finished, bc.Duration = parseTimes(step.Times) | 273 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) | 408 issue := int64(-1) |
| 443 // TODO(hinoka): Gerrit URLs. | 409 // TODO(hinoka): Gerrit URLs. |
| 444 for _, prop := range b.Properties { | 410 for _, prop := range b.Properties { |
| 445 key := prop[0].(string) | 411 key := prop[0].(string) |
| 446 value := prop[1] | 412 value := prop[1] |
| 447 switch key { | 413 switch key { |
| 448 case "rietveld": | 414 case "rietveld": |
| 449 if v, ok := value.(string); ok { | 415 if v, ok := value.(string); ok { |
| 450 rietveld = v | 416 rietveld = v |
| 451 } else { | 417 } else { |
| 452 » » » » log.Warningf(c, "Field rietveld is not a string:
%#v", value) | 418 » » » » logging.Warningf(c, "Field rietveld is not a str
ing: %#v", value) |
| 453 } | 419 } |
| 454 case "issue": | 420 case "issue": |
| 455 if v, ok := value.(float64); ok { | 421 if v, ok := value.(float64); ok { |
| 456 issue = int64(v) | 422 issue = int64(v) |
| 457 } else { | 423 } else { |
| 458 » » » » log.Warningf(c, "Field issue is not a float: %#v
", value) | 424 » » » » logging.Warningf(c, "Field issue is not a float:
%#v", value) |
| 459 } | 425 } |
| 460 | 426 |
| 461 case "got_revision": | 427 case "got_revision": |
| 462 if v, ok := value.(string); ok { | 428 if v, ok := value.(string); ok { |
| 463 ss.Revision = v | 429 ss.Revision = v |
| 464 } else { | 430 } else { |
| 465 » » » » log.Warningf(c, "Field got_revision is not a str
ing: %#v", value) | 431 » » » » logging.Warningf(c, "Field got_revision is not a
string: %#v", value) |
| 466 } | 432 } |
| 467 | 433 |
| 468 } | 434 } |
| 469 } | 435 } |
| 470 if issue != -1 { | 436 if issue != -1 { |
| 471 if rietveld != "" { | 437 if rietveld != "" { |
| 472 rietveld = strings.TrimRight(rietveld, "/") | 438 rietveld = strings.TrimRight(rietveld, "/") |
| 473 ss.Changelist = &resp.Link{ | 439 ss.Changelist = &resp.Link{ |
| 474 Label: fmt.Sprintf("Issue %d", issue), | 440 Label: fmt.Sprintf("Issue %d", issue), |
| 475 URL: fmt.Sprintf("%s/%d", rietveld, issue), | 441 URL: fmt.Sprintf("%s/%d", rietveld, issue), |
| 476 } | 442 } |
| 477 } else { | 443 } else { |
| 478 » » » log.Warningf(c, "Found issue but not rietveld property."
) | 444 » » » logging.Warningf(c, "Found issue but not rietveld proper
ty.") |
| 479 } | 445 } |
| 480 } | 446 } |
| 481 return ss | 447 return ss |
| 482 } | 448 } |
| 483 | 449 |
| 484 func getDebugBuild(c context.Context, builder, buildNum string) (*buildbotBuild,
error) { | 450 func getDebugBuild(c context.Context, builder, buildNum string) (*buildbotBuild,
error) { |
| 485 fname := fmt.Sprintf("%s.%s.json", builder, buildNum) | 451 fname := fmt.Sprintf("%s.%s.json", builder, buildNum) |
| 486 // ../buildbot below assumes that | 452 // ../buildbot below assumes that |
| 487 // - this code is not executed by tests outside of this dir | 453 // - this code is not executed by tests outside of this dir |
| 488 // - this dir is a sibling of frontend dir | 454 // - this dir is a sibling of frontend dir |
| (...skipping 21 matching lines...) Expand all Loading... |
| 510 | 476 |
| 511 // TODO(hinoka): Do all fields concurrently. | 477 // TODO(hinoka): Do all fields concurrently. |
| 512 return &resp.MiloBuild{ | 478 return &resp.MiloBuild{ |
| 513 SourceStamp: sourcestamp(c, b), | 479 SourceStamp: sourcestamp(c, b), |
| 514 Summary: summary(c, b), | 480 Summary: summary(c, b), |
| 515 Components: components(b), | 481 Components: components(b), |
| 516 PropertyGroup: properties(b), | 482 PropertyGroup: properties(b), |
| 517 Blame: blame(b), | 483 Blame: blame(b), |
| 518 }, nil | 484 }, nil |
| 519 } | 485 } |
| OLD | NEW |