| 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" | |
| 9 "fmt" | 8 "fmt" |
| 9 "net/http" |
| 10 "sort" | 10 "sort" |
| 11 "strings" | 11 "strings" |
| 12 "time" | 12 "time" |
| 13 | 13 |
| 14 ds "github.com/luci/gae/service/datastore" | 14 ds "github.com/luci/gae/service/datastore" |
| 15 | 15 |
| 16 "github.com/luci/luci-go/common/clock" | 16 "github.com/luci/luci-go/common/clock" |
| 17 "github.com/luci/luci-go/common/logging" | 17 "github.com/luci/luci-go/common/logging" |
| 18 "github.com/luci/luci-go/milo/api/resp" | 18 "github.com/luci/luci-go/milo/api/resp" |
| 19 "github.com/luci/luci-go/milo/common/miloerror" |
| 19 "golang.org/x/net/context" | 20 "golang.org/x/net/context" |
| 20 ) | 21 ) |
| 21 | 22 |
| 22 // builderRef is used for keying specific builds in a master json. | 23 // builderRef is used for keying specific builds in a master json. |
| 23 type builderRef struct { | 24 type builderRef struct { |
| 24 builder string | 25 builder string |
| 25 buildNum int | 26 buildNum int |
| 26 } | 27 } |
| 27 | 28 |
| 28 // buildMap contains all of the current build within a master json. We use this | 29 // buildMap contains all of the current build within a master json. We use this |
| 29 // because buildbot returns all current builds as within the slaves portion, whe
reas | 30 // because buildbot returns all current builds as within the slaves portion, whe
reas |
| 30 // it's eaiser to map thenm by builders instead. | 31 // it's eaiser to map thenm by builders instead. |
| 31 type buildMap map[builderRef]*buildbotBuild | 32 type buildMap map[builderRef]*buildbotBuild |
| 32 | 33 |
| 33 // createRunningBuildMap extracts all of the running builds in a master json | |
| 34 // from the various slaves and dumps it into a map for easy reference. | |
| 35 func createRunningBuildMap(master *buildbotMaster) buildMap { | |
| 36 result := buildMap{} | |
| 37 for _, slave := range master.Slaves { | |
| 38 for _, build := range slave.Runningbuilds { | |
| 39 result[builderRef{build.Buildername, build.Number}] = bu
ild | |
| 40 } | |
| 41 } | |
| 42 return result | |
| 43 } | |
| 44 | |
| 45 func getBuildSummary(b *buildbotBuild) *resp.BuildSummary { | 34 func getBuildSummary(b *buildbotBuild) *resp.BuildSummary { |
| 46 started, finished, duration := parseTimes(b.Times) | 35 started, finished, duration := parseTimes(b.Times) |
| 47 return &resp.BuildSummary{ | 36 return &resp.BuildSummary{ |
| 48 Link: &resp.Link{ | 37 Link: &resp.Link{ |
| 49 URL: fmt.Sprintf("%d", b.Number), | 38 URL: fmt.Sprintf("%d", b.Number), |
| 50 Label: fmt.Sprintf("#%d", b.Number), | 39 Label: fmt.Sprintf("#%d", b.Number), |
| 51 }, | 40 }, |
| 52 Status: b.toStatus(), | 41 Status: b.toStatus(), |
| 53 ExecutionTime: resp.Interval{ | 42 ExecutionTime: resp.Interval{ |
| 54 Started: started, | 43 Started: started, |
| (...skipping 11 matching lines...) Expand all Loading... |
| 66 func getBuilds( | 55 func getBuilds( |
| 67 c context.Context, masterName, builderName string, finished bool, limit
int) ( | 56 c context.Context, masterName, builderName string, finished bool, limit
int) ( |
| 68 []*resp.BuildSummary, error) { | 57 []*resp.BuildSummary, error) { |
| 69 | 58 |
| 70 // TODO(hinoka): Builder specific structs. | 59 // TODO(hinoka): Builder specific structs. |
| 71 result := []*resp.BuildSummary{} | 60 result := []*resp.BuildSummary{} |
| 72 q := ds.NewQuery("buildbotBuild") | 61 q := ds.NewQuery("buildbotBuild") |
| 73 q = q.Eq("finished", finished) | 62 q = q.Eq("finished", finished) |
| 74 q = q.Eq("master", masterName) | 63 q = q.Eq("master", masterName) |
| 75 q = q.Eq("builder", builderName) | 64 q = q.Eq("builder", builderName) |
| 76 » q = q.Limit(int32(limit)) | 65 » if limit != 0 { |
| 66 » » q = q.Limit(int32(limit)) |
| 67 » } |
| 77 q = q.Order("-number") | 68 q = q.Order("-number") |
| 78 buildbots := []*buildbotBuild{} | 69 buildbots := []*buildbotBuild{} |
| 79 err := ds.GetAll(c, q, &buildbots) | 70 err := ds.GetAll(c, q, &buildbots) |
| 80 if err != nil { | 71 if err != nil { |
| 81 return nil, err | 72 return nil, err |
| 82 } | 73 } |
| 83 for _, b := range buildbots { | 74 for _, b := range buildbots { |
| 84 result = append(result, getBuildSummary(b)) | 75 result = append(result, getBuildSummary(b)) |
| 85 } | 76 } |
| 86 return result, nil | 77 return result, nil |
| 87 } | 78 } |
| 88 | 79 |
| 89 // getCurrentBuild extracts a build from a map of current builds, and translates | 80 var errMasterNotFound = miloerror.Error{ |
| 90 // it into the milo version of the build. | 81 » Message: "Master not found", |
| 91 func getCurrentBuild(c context.Context, bMap buildMap, builder string, buildNum
int) *resp.BuildSummary { | 82 » Code: http.StatusNotFound, |
| 92 » b, ok := bMap[builderRef{builder, buildNum}] | |
| 93 » if !ok { | |
| 94 » » logging.Warningf(c, "Could not find %s/%d in builder map:\n %s",
builder, buildNum, bMap) | |
| 95 » » return nil | |
| 96 » } | |
| 97 » return getBuildSummary(b) | |
| 98 } | |
| 99 | |
| 100 // getCurrentBuilds extracts the list of all the current builds from a master js
on | |
| 101 // from the slaves' runningBuilds portion. | |
| 102 func getCurrentBuilds(c context.Context, master *buildbotMaster, builderName str
ing) []*resp.BuildSummary { | |
| 103 » b := master.Builders[builderName] | |
| 104 » results := []*resp.BuildSummary{} | |
| 105 » bMap := createRunningBuildMap(master) | |
| 106 » for _, bn := range b.CurrentBuilds { | |
| 107 » » cb := getCurrentBuild(c, bMap, builderName, bn) | |
| 108 » » if cb != nil { | |
| 109 » » » results = append(results, cb) | |
| 110 » » } | |
| 111 » } | |
| 112 » return results | |
| 113 } | 83 } |
| 114 | 84 |
| 115 // builderImpl is the implementation for getting a milo builder page from buildb
ot. | 85 // builderImpl is the implementation for getting a milo builder page from buildb
ot. |
| 116 // This gets: | 86 // This gets: |
| 117 // * Current Builds from querying the master json from the datastore. | 87 // * Current Builds from querying the master json from the datastore. |
| 118 // * Recent Builds from a cron job that backfills the recent builds. | 88 // * Recent Builds from a cron job that backfills the recent builds. |
| 119 func builderImpl(c context.Context, masterName, builderName string, limit int) (
*resp.Builder, error) { | 89 func builderImpl(c context.Context, masterName, builderName string, limit int) (
*resp.Builder, error) { |
| 120 result := &resp.Builder{ | 90 result := &resp.Builder{ |
| 121 Name: builderName, | 91 Name: builderName, |
| 122 } | 92 } |
| 123 master, t, err := getMasterJSON(c, masterName) | 93 master, t, err := getMasterJSON(c, masterName) |
| 124 » switch { | 94 » if err != nil { |
| 125 » case err == ds.ErrNoSuchEntity: | |
| 126 » » return nil, errMasterNotFound | |
| 127 » case err != nil: | |
| 128 return nil, err | 95 return nil, err |
| 129 } | 96 } |
| 130 if clock.Now(c).Sub(t) > 2*time.Minute { | 97 if clock.Now(c).Sub(t) > 2*time.Minute { |
| 131 warning := fmt.Sprintf( | 98 warning := fmt.Sprintf( |
| 132 "WARNING: Master data is stale (last updated %s)", t) | 99 "WARNING: Master data is stale (last updated %s)", t) |
| 133 logging.Warningf(c, warning) | 100 logging.Warningf(c, warning) |
| 134 result.Warning = warning | 101 result.Warning = warning |
| 135 } | 102 } |
| 136 | 103 |
| 137 s, _ := json.Marshal(master) | |
| 138 logging.Debugf(c, "Master: %s", s) | |
| 139 | |
| 140 p, ok := master.Builders[builderName] | 104 p, ok := master.Builders[builderName] |
| 141 if !ok { | 105 if !ok { |
| 142 // This long block is just to return a good error message when a
n invalid | 106 // This long block is just to return a good error message when a
n invalid |
| 143 // buildbot builder is specified. | 107 // buildbot builder is specified. |
| 144 keys := make([]string, 0, len(master.Builders)) | 108 keys := make([]string, 0, len(master.Builders)) |
| 145 for k := range master.Builders { | 109 for k := range master.Builders { |
| 146 keys = append(keys, k) | 110 keys = append(keys, k) |
| 147 } | 111 } |
| 148 sort.Strings(keys) | 112 sort.Strings(keys) |
| 149 avail := strings.Join(keys, "\n") | 113 avail := strings.Join(keys, "\n") |
| (...skipping 14 matching lines...) Expand all Loading... |
| 164 } | 128 } |
| 165 result.PendingBuilds[i].Blame = make([]*resp.Commit, len(pb.Sour
ce.Changes)) | 129 result.PendingBuilds[i].Blame = make([]*resp.Commit, len(pb.Sour
ce.Changes)) |
| 166 for j, cm := range pb.Source.Changes { | 130 for j, cm := range pb.Source.Changes { |
| 167 result.PendingBuilds[i].Blame[j] = &resp.Commit{ | 131 result.PendingBuilds[i].Blame[j] = &resp.Commit{ |
| 168 AuthorEmail: cm.Who, | 132 AuthorEmail: cm.Who, |
| 169 CommitURL: cm.Revlink, | 133 CommitURL: cm.Revlink, |
| 170 } | 134 } |
| 171 } | 135 } |
| 172 } | 136 } |
| 173 | 137 |
| 174 » recentBuilds, err := getBuilds(c, masterName, builderName, true, limit) | 138 » // This is CPU bound anyways, so there's no need to do this in parallel. |
| 139 » finishedBuilds, err := getBuilds(c, masterName, builderName, true, limit
) |
| 175 if err != nil { | 140 if err != nil { |
| 176 return nil, err | 141 return nil, err |
| 177 } | 142 } |
| 178 currentBuilds, err := getBuilds(c, masterName, builderName, false, 0) | 143 currentBuilds, err := getBuilds(c, masterName, builderName, false, 0) |
| 179 if err != nil { | 144 if err != nil { |
| 180 return nil, err | 145 return nil, err |
| 181 } | 146 } |
| 182 » logging.Debugf(c, "Number of current builds: %d", len(currentBuilds)) | 147 » result.CurrentBuilds = currentBuilds |
| 183 » // TODO(hinoka): This works, but there's a lot of junk data from | 148 » for _, fb := range finishedBuilds { |
| 184 » // masters with unclean shutdown. Need to implement a cleanup | |
| 185 » // procedure of some sort. Once that is done, set: | |
| 186 » // result.CurrentBuilds = currentBuilds | |
| 187 | |
| 188 » for _, fb := range recentBuilds { | |
| 189 » » // Yes recent builds is synonymous with finished builds. | |
| 190 » » // TODO(hinoka): Implement limits. | |
| 191 if fb != nil { | 149 if fb != nil { |
| 192 result.FinishedBuilds = append(result.FinishedBuilds, fb
) | 150 result.FinishedBuilds = append(result.FinishedBuilds, fb
) |
| 193 } | 151 } |
| 194 } | 152 } |
| 195 return result, nil | 153 return result, nil |
| 196 } | 154 } |
| OLD | NEW |