| OLD | NEW |
| 1 // Copyright 2016 The LUCI Authors. | 1 // Copyright 2016 The LUCI Authors. |
| 2 // | 2 // |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); | 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 // you may not use this file except in compliance with the License. | 4 // you may not use this file except in compliance with the License. |
| 5 // You may obtain a copy of the License at | 5 // You may obtain a copy of the License at |
| 6 // | 6 // |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 | 7 // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 // | 8 // |
| 9 // Unless required by applicable law or agreed to in writing, software | 9 // Unless required by applicable law or agreed to in writing, software |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, | 10 // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 // See the License for the specific language governing permissions and | 12 // See the License for the specific language governing permissions and |
| 13 // limitations under the License. | 13 // limitations under the License. |
| 14 | 14 |
| 15 package buildbot | 15 package buildbot |
| 16 | 16 |
| 17 import ( | 17 import ( |
| 18 "bytes" | 18 "bytes" |
| 19 "compress/gzip" | 19 "compress/gzip" |
| 20 "compress/zlib" | 20 "compress/zlib" |
| 21 "encoding/json" | 21 "encoding/json" |
| 22 "fmt" | 22 "fmt" |
| 23 "net/http" | 23 "net/http" |
| 24 "strings" | 24 "strings" |
| 25 "time" | 25 "time" |
| 26 | 26 |
| 27 » ds "github.com/luci/gae/service/datastore" | 27 » "github.com/luci/gae/service/datastore" |
| 28 "github.com/luci/luci-go/common/clock" | 28 "github.com/luci/luci-go/common/clock" |
| 29 "github.com/luci/luci-go/common/iotools" | 29 "github.com/luci/luci-go/common/iotools" |
| 30 "github.com/luci/luci-go/common/logging" | 30 "github.com/luci/luci-go/common/logging" |
| 31 "github.com/luci/luci-go/milo/common" | 31 "github.com/luci/luci-go/milo/common" |
| 32 "github.com/luci/luci-go/milo/common/model" |
| 32 "github.com/luci/luci-go/server/router" | 33 "github.com/luci/luci-go/server/router" |
| 33 | 34 |
| 34 "golang.org/x/net/context" | 35 "golang.org/x/net/context" |
| 35 | 36 |
| 36 "github.com/luci/luci-go/common/tsmon/field" | 37 "github.com/luci/luci-go/common/tsmon/field" |
| 37 "github.com/luci/luci-go/common/tsmon/metric" | 38 "github.com/luci/luci-go/common/tsmon/metric" |
| 38 ) | 39 ) |
| 39 | 40 |
| 40 var ( | 41 var ( |
| 41 // Metrics | 42 // Metrics |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 100 entry := buildbotMasterEntry{ | 101 entry := buildbotMasterEntry{ |
| 101 Name: master.Name, | 102 Name: master.Name, |
| 102 Internal: internal, | 103 Internal: internal, |
| 103 Modified: clock.Now(c).UTC(), | 104 Modified: clock.Now(c).UTC(), |
| 104 } | 105 } |
| 105 toPut := []interface{}{&entry} | 106 toPut := []interface{}{&entry} |
| 106 publicTag := &buildbotMasterPublic{master.Name} | 107 publicTag := &buildbotMasterPublic{master.Name} |
| 107 if internal { | 108 if internal { |
| 108 // do the deletion immediately so that the 'public' bit is remov
ed from | 109 // do the deletion immediately so that the 'public' bit is remov
ed from |
| 109 // datastore before any internal details are actually written to
datastore. | 110 // datastore before any internal details are actually written to
datastore. |
| 110 » » if err := ds.Delete(c, publicTag); err != nil && err != ds.ErrNo
SuchEntity { | 111 » » if err := datastore.Delete(c, publicTag); err != nil && err != d
atastore.ErrNoSuchEntity { |
| 111 return err | 112 return err |
| 112 } | 113 } |
| 113 } else { | 114 } else { |
| 114 toPut = append(toPut, publicTag) | 115 toPut = append(toPut, publicTag) |
| 115 } | 116 } |
| 116 gzbs := bytes.Buffer{} | 117 gzbs := bytes.Buffer{} |
| 117 gsw := gzip.NewWriter(&gzbs) | 118 gsw := gzip.NewWriter(&gzbs) |
| 118 cw := iotools.CountingWriter{Writer: gsw} | 119 cw := iotools.CountingWriter{Writer: gsw} |
| 119 e := json.NewEncoder(&cw) | 120 e := json.NewEncoder(&cw) |
| 120 if err := e.Encode(master); err != nil { | 121 if err := e.Encode(master); err != nil { |
| 121 return err | 122 return err |
| 122 } | 123 } |
| 123 gsw.Close() | 124 gsw.Close() |
| 124 entry.Data = gzbs.Bytes() | 125 entry.Data = gzbs.Bytes() |
| 125 logging.Debugf(c, "Length of json data: %d", cw.Count) | 126 logging.Debugf(c, "Length of json data: %d", cw.Count) |
| 126 logging.Debugf(c, "Length of gzipped data: %d", len(entry.Data)) | 127 logging.Debugf(c, "Length of gzipped data: %d", len(entry.Data)) |
| 127 » return ds.Put(c, toPut) | 128 » return datastore.Put(c, toPut) |
| 128 } | 129 } |
| 129 | 130 |
| 130 // unmarshal a gzipped byte stream into a list of buildbot builds and masters. | 131 // unmarshal a gzipped byte stream into a list of buildbot builds and masters. |
| 131 func unmarshal( | 132 func unmarshal( |
| 132 c context.Context, msg []byte) ([]*buildbotBuild, *buildbotMaster, error
) { | 133 c context.Context, msg []byte) ([]*buildbotBuild, *buildbotMaster, error
) { |
| 133 bm := buildMasterMsg{} | 134 bm := buildMasterMsg{} |
| 134 if len(msg) == 0 { | 135 if len(msg) == 0 { |
| 135 return bm.Builds, bm.Master, nil | 136 return bm.Builds, bm.Master, nil |
| 136 } | 137 } |
| 137 reader, err := zlib.NewReader(bytes.NewReader(msg)) | 138 reader, err := zlib.NewReader(bytes.NewReader(msg)) |
| (...skipping 26 matching lines...) Expand all Loading... |
| 164 } | 165 } |
| 165 | 166 |
| 166 // getOSInfo fetches the os family and version of the slave the build was | 167 // getOSInfo fetches the os family and version of the slave the build was |
| 167 // running on from the master json on a best-effort basis. | 168 // running on from the master json on a best-effort basis. |
| 168 func getOSInfo(c context.Context, b *buildbotBuild, m *buildbotMaster) ( | 169 func getOSInfo(c context.Context, b *buildbotBuild, m *buildbotMaster) ( |
| 169 family, version string) { | 170 family, version string) { |
| 170 // Fetch the master info from datastore if not provided. | 171 // Fetch the master info from datastore if not provided. |
| 171 if m.Name == "" { | 172 if m.Name == "" { |
| 172 logging.Infof(c, "Fetching info for master %s", b.Master) | 173 logging.Infof(c, "Fetching info for master %s", b.Master) |
| 173 entry := buildbotMasterEntry{Name: b.Master} | 174 entry := buildbotMasterEntry{Name: b.Master} |
| 174 » » err := ds.Get(c, &entry) | 175 » » err := datastore.Get(c, &entry) |
| 175 if err != nil { | 176 if err != nil { |
| 176 logging.WithError(err).Errorf( | 177 logging.WithError(err).Errorf( |
| 177 c, "Encountered error while fetching entry for %
s", b.Master) | 178 c, "Encountered error while fetching entry for %
s", b.Master) |
| 178 return | 179 return |
| 179 } | 180 } |
| 180 err = decodeMasterEntry(c, &entry, m) | 181 err = decodeMasterEntry(c, &entry, m) |
| 181 if err != nil { | 182 if err != nil { |
| 182 logging.WithError(err).Warningf( | 183 logging.WithError(err).Warningf( |
| 183 c, "Failed to decode master information for OS i
nfo on master %s", b.Master) | 184 c, "Failed to decode master information for OS i
nfo on master %s", b.Master) |
| 184 return | 185 return |
| (...skipping 30 matching lines...) Expand all Loading... |
| 215 finished := float64(clock.Now(c).Unix()) | 216 finished := float64(clock.Now(c).Unix()) |
| 216 if b.TimeStamp != nil { | 217 if b.TimeStamp != nil { |
| 217 finished = float64(*b.TimeStamp) | 218 finished = float64(*b.TimeStamp) |
| 218 } | 219 } |
| 219 results := int(4) // Exception | 220 results := int(4) // Exception |
| 220 b.Times[1] = &finished | 221 b.Times[1] = &finished |
| 221 b.Finished = true | 222 b.Finished = true |
| 222 b.Results = &results | 223 b.Results = &results |
| 223 b.Currentstep = nil | 224 b.Currentstep = nil |
| 224 b.Text = append(b.Text, "Build expired on Milo") | 225 b.Text = append(b.Text, "Build expired on Milo") |
| 225 » return ds.Put(c, b) | 226 » return datastore.Put(c, b) |
| 227 } |
| 228 |
| 229 // saveBuildSummary summerizes a build into a model.BuildSummary and then saves
it. |
| 230 func saveBuildSummary(c context.Context, b *buildbotBuild) error { |
| 231 » resp := renderBuild(c, b) |
| 232 » bs := model.BuildSummary{ |
| 233 » » BuildKey: datastore.KeyForObj(c, b), |
| 234 » » BuilderID: fmt.Sprintf("buildbot/%s/%s", b.Master, b.Buildername
), |
| 235 » } |
| 236 » resp.SummarizeTo(&bs) |
| 237 » return datastore.Put(c, &bs) |
| 226 } | 238 } |
| 227 | 239 |
| 228 func doMaster(c context.Context, master *buildbotMaster, internal bool) int { | 240 func doMaster(c context.Context, master *buildbotMaster, internal bool) int { |
| 229 // Store the master json into the datastore. | 241 // Store the master json into the datastore. |
| 230 err := putDSMasterJSON(c, master, internal) | 242 err := putDSMasterJSON(c, master, internal) |
| 231 fullname := fmt.Sprintf("master.%s", master.Name) | 243 fullname := fmt.Sprintf("master.%s", master.Name) |
| 232 if err != nil { | 244 if err != nil { |
| 233 logging.WithError(err).Errorf( | 245 logging.WithError(err).Errorf( |
| 234 c, "Could not save master in datastore %s", err) | 246 c, "Could not save master in datastore %s", err) |
| 235 masterCounter.Add(c, 1, internal, fullname, "failure") | 247 masterCounter.Add(c, 1, internal, fullname, "failure") |
| 236 // This is transient, we do want PubSub to retry. | 248 // This is transient, we do want PubSub to retry. |
| 237 return http.StatusInternalServerError | 249 return http.StatusInternalServerError |
| 238 } | 250 } |
| 239 masterCounter.Add(c, 1, internal, fullname, "success") | 251 masterCounter.Add(c, 1, internal, fullname, "success") |
| 240 | 252 |
| 241 // Extract current builds data out of the master json, and use it to | 253 // Extract current builds data out of the master json, and use it to |
| 242 // clean up expired builds. | 254 // clean up expired builds. |
| 243 » q := ds.NewQuery("buildbotBuild"). | 255 » q := datastore.NewQuery("buildbotBuild"). |
| 244 Eq("finished", false). | 256 Eq("finished", false). |
| 245 Eq("master", master.Name) | 257 Eq("master", master.Name) |
| 246 builds := []*buildbotBuild{} | 258 builds := []*buildbotBuild{} |
| 247 err = getBuildQueryBatcher(c).GetAll(c, q, &builds) | 259 err = getBuildQueryBatcher(c).GetAll(c, q, &builds) |
| 248 if err != nil { | 260 if err != nil { |
| 249 logging.WithError(err).Errorf(c, "Could not load current builds
from master %s", | 261 logging.WithError(err).Errorf(c, "Could not load current builds
from master %s", |
| 250 master.Name) | 262 master.Name) |
| 251 return http.StatusInternalServerError | 263 return http.StatusInternalServerError |
| 252 } | 264 } |
| 253 for _, b := range builds { | 265 for _, b := range builds { |
| (...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 355 if build.Master == "" { | 367 if build.Master == "" { |
| 356 logging.Errorf(c, "Invalid message, missing master name"
) | 368 logging.Errorf(c, "Invalid message, missing master name"
) |
| 357 return http.StatusOK | 369 return http.StatusOK |
| 358 } | 370 } |
| 359 existingBuild := &buildbotBuild{ | 371 existingBuild := &buildbotBuild{ |
| 360 Master: build.Master, | 372 Master: build.Master, |
| 361 Buildername: build.Buildername, | 373 Buildername: build.Buildername, |
| 362 Number: build.Number, | 374 Number: build.Number, |
| 363 } | 375 } |
| 364 buildExists := false | 376 buildExists := false |
| 365 » » if err := ds.Get(c, existingBuild); err == nil { | 377 » » if err := datastore.Get(c, existingBuild); err == nil { |
| 366 if existingBuild.Finished { | 378 if existingBuild.Finished { |
| 367 // Never replace a completed build. | 379 // Never replace a completed build. |
| 368 buildCounter.Add( | 380 buildCounter.Add( |
| 369 c, 1, false, build.Master, build.Builder
name, false, "Rejected") | 381 c, 1, false, build.Master, build.Builder
name, false, "Rejected") |
| 370 continue | 382 continue |
| 371 } | 383 } |
| 372 buildExists = true | 384 buildExists = true |
| 373 } | 385 } |
| 374 // Also set the finished, timestamp, and internal bit. | 386 // Also set the finished, timestamp, and internal bit. |
| 375 build.Finished = false | 387 build.Finished = false |
| 376 if build.TimeStamp == nil { | 388 if build.TimeStamp == nil { |
| 377 build.TimeStamp = &now | 389 build.TimeStamp = &now |
| 378 } | 390 } |
| 379 if len(build.Times) == 2 && build.Times[1] != nil { | 391 if len(build.Times) == 2 && build.Times[1] != nil { |
| 380 build.Finished = true | 392 build.Finished = true |
| 381 logging.Infof( | 393 logging.Infof( |
| 382 c, "Recording finished build %s/%s/%d", build.Ma
ster, | 394 c, "Recording finished build %s/%s/%d", build.Ma
ster, |
| 383 build.Buildername, build.Number) | 395 build.Buildername, build.Number) |
| 384 } | 396 } |
| 385 build.Internal = internal | 397 build.Internal = internal |
| 386 // Try to get the OS information on a best-effort basis. This a
ssumes that all | 398 // Try to get the OS information on a best-effort basis. This a
ssumes that all |
| 387 // builds come from one master. | 399 // builds come from one master. |
| 388 build.OSFamily, build.OSVersion = getOSInfo(c, build, &cachedMas
ter) | 400 build.OSFamily, build.OSVersion = getOSInfo(c, build, &cachedMas
ter) |
| 389 » » err = ds.Put(c, build) | 401 » » err = datastore.Put(c, build) |
| 390 if err != nil { | 402 if err != nil { |
| 391 if _, ok := err.(errTooBig); ok { | 403 if _, ok := err.(errTooBig); ok { |
| 392 // This will never work, we don't want PubSub to
retry. | 404 // This will never work, we don't want PubSub to
retry. |
| 393 logging.WithError(err).Errorf( | 405 logging.WithError(err).Errorf( |
| 394 c, "Could not save build to datastore, f
ailing permanently") | 406 c, "Could not save build to datastore, f
ailing permanently") |
| 395 return http.StatusOK | 407 return http.StatusOK |
| 396 } | 408 } |
| 397 // This is transient, we do want PubSub to retry. | 409 // This is transient, we do want PubSub to retry. |
| 398 logging.WithError(err).Errorf(c, "Could not save build i
n datastore") | 410 logging.WithError(err).Errorf(c, "Could not save build i
n datastore") |
| 399 return http.StatusInternalServerError | 411 return http.StatusInternalServerError |
| 400 } | 412 } |
| 413 err = saveBuildSummary(c, build) |
| 414 if err != nil { |
| 415 logging.WithError(err).Errorf(c, "could not save build s
ummary into datastore") |
| 416 return http.StatusInternalServerError |
| 417 } |
| 401 if buildExists { | 418 if buildExists { |
| 402 buildCounter.Add( | 419 buildCounter.Add( |
| 403 c, 1, false, build.Master, build.Buildername, bu
ild.Finished, "Replaced") | 420 c, 1, false, build.Master, build.Buildername, bu
ild.Finished, "Replaced") |
| 404 } else { | 421 } else { |
| 405 buildCounter.Add( | 422 buildCounter.Add( |
| 406 c, 1, false, build.Master, build.Buildername, bu
ild.Finished, "New") | 423 c, 1, false, build.Master, build.Buildername, bu
ild.Finished, "New") |
| 407 } | 424 } |
| 408 | 425 |
| 409 } | 426 } |
| 410 if master != nil { | 427 if master != nil { |
| 411 code := doMaster(c, master, internal) | 428 code := doMaster(c, master, internal) |
| 412 if code != 0 { | 429 if code != 0 { |
| 413 return code | 430 return code |
| 414 } | 431 } |
| 415 } | 432 } |
| 416 return http.StatusOK | 433 return http.StatusOK |
| 417 } | 434 } |
| OLD | NEW |