| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Package som implements HTTP server that handles requests to default module. |
| package som |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "io/ioutil" |
| "net/http" |
| "time" |
| |
| "golang.org/x/net/context" |
| |
| "infra/monitoring/client" |
| "infra/monitoring/messages" |
| |
| "github.com/luci/gae/service/datastore" |
| "github.com/luci/luci-go/common/clock" |
| "github.com/luci/luci-go/common/logging" |
| "github.com/luci/luci-go/server/router" |
| ) |
| |
| var ( |
| masterStateURL = "https://chrome-internal.googlesource.com/infradata/master-manager/+/master/desired_master_state.json?format=text" |
| masterStateKey = "masterState" |
| // ErrUnrecognizedTree indicates that a request specificed an unrecognized tree. |
| ErrUnrecognizedTree = fmt.Errorf("Unrecognized tree name") |
| ) |
| |
| func getAlertsHandler(ctx *router.Context) { |
| c, w, p := ctx.Context, ctx.Writer, ctx.Params |
| |
| tree := p.ByName("tree") |
| |
| if tree == "trooper" || tree == "milo.trooper" { |
| data, err := getTrooperAlerts(c, tree == "milo.trooper") |
| if err != nil { |
| errStatus(c, w, http.StatusInternalServerError, err.Error()) |
| return |
| } |
| |
| w.Header().Set("Content-Type", "application/json") |
| w.Write(data) |
| return |
| } |
| |
| results := []*AlertsJSON{} |
| q := datastore.NewQuery("AlertsJSON") |
| // TODO(seanmccullough): remove this check once we turn down a-d and only |
| // use the cron tasks for alerts for all trees. See crbug.com/705074 |
| //if tree == "chromium" { |
| // tree = "milo.chromium" |
| //} |
| q = q.Ancestor(datastore.MakeKey(c, "Tree", tree)) |
| q = q.Order("-Date") |
| q = q.Limit(1) |
| |
| err := datastore.GetAll(c, q, &results) |
| if err != nil { |
| errStatus(c, w, http.StatusInternalServerError, err.Error()) |
| return |
| } |
| |
| if len(results) == 0 { |
| logging.Warningf(c, "No alerts found for tree %s", tree) |
| errStatus(c, w, http.StatusNotFound, fmt.Sprintf("Tree \"%s\" not found", tree)) |
| return |
| } |
| |
| alertsJSON := results[0] |
| w.Header().Set("Content-Type", "application/json") |
| w.Write(alertsJSON.Contents) |
| } |
| |
| func postAlertsHandler(ctx *router.Context) { |
| c, w, r, p := ctx.Context, ctx.Writer, ctx.Request, ctx.Params |
| |
| tree := p.ByName("tree") |
| |
| alerts := AlertsJSON{ |
| Tree: datastore.MakeKey(c, "Tree", tree), |
| Date: clock.Now(c), |
| } |
| data, err := ioutil.ReadAll(r.Body) |
| if err != nil { |
| errStatus(c, w, http.StatusBadRequest, err.Error()) |
| return |
| } |
| |
| if err := r.Body.Close(); err != nil { |
| errStatus(c, w, http.StatusBadRequest, err.Error()) |
| return |
| } |
| |
| // Do a sanity check. |
| alertsSummary := &messages.AlertsSummary{} |
| err = json.Unmarshal(data, alertsSummary) |
| if err != nil { |
| errStatus(c, w, http.StatusBadRequest, err.Error()) |
| return |
| } |
| |
| if alertsSummary.Timestamp == 0 { |
| errStatus(c, w, http.StatusBadRequest, |
| "Couldn't decode into AlertsSummary or did not include a timestamp.") |
| return |
| } |
| |
| // Now actually do decoding necessary for storage. |
| out := make(map[string]interface{}) |
| err = json.Unmarshal(data, &out) |
| |
| if err != nil { |
| errStatus(c, w, http.StatusBadRequest, err.Error()) |
| return |
| } |
| |
| out["date"] = alerts.Date.String() |
| data, err = json.Marshal(out) |
| |
| if err != nil { |
| errStatus(c, w, http.StatusInternalServerError, err.Error()) |
| return |
| } |
| |
| alerts.Contents = data |
| err = datastore.Put(c, &alerts) |
| if err != nil { |
| errStatus(c, w, http.StatusInternalServerError, err.Error()) |
| return |
| } |
| } |
| |
| func getRestartingMastersHandler(ctx *router.Context) { |
| c, w, p := ctx.Context, ctx.Writer, ctx.Params |
| |
| tree := p.ByName("tree") |
| |
| masters, err := getRestartingMasters(c, tree) |
| if err == ErrUnrecognizedTree { |
| errStatus(c, w, http.StatusNotFound, err.Error()) |
| return |
| } |
| |
| if err != nil { |
| errStatus(c, w, http.StatusInternalServerError, err.Error()) |
| return |
| } |
| |
| data, err := json.Marshal(masters) |
| if err != nil { |
| errStatus(c, w, http.StatusInternalServerError, err.Error()) |
| return |
| } |
| w.Header().Set("Content-Type", "application/json") |
| w.Write(data) |
| } |
| |
| type desiredMasterStates struct { |
| MasterStates map[string][]masterState `json:"master_states"` |
| } |
| |
| type masterState struct { |
| DesiredState string `json:"desired_state"` |
| TransitionTime string `json:"transition_time_utc"` |
| } |
| |
| func getRestartingMasters(c context.Context, treeName string) (map[string]masterState, error) { |
| // Chrome OS does not use master-manager to handle restarts. |
| if treeName == "chromeos" { |
| return nil, nil |
| } |
| |
| b, err := client.GetGitilesCached(c, masterStateURL) |
| if err != nil { |
| return nil, err |
| } |
| |
| ms := &desiredMasterStates{} |
| if err := json.Unmarshal(b, ms); err != nil { |
| return nil, err |
| } |
| |
| trees, err := getGatekeeperTrees(c) |
| if err != nil { |
| return nil, err |
| } |
| |
| now := time.Now().UTC() |
| |
| ret := map[string]masterState{} |
| var filter = func(masterName string, masterStates []masterState) error { |
| for _, state := range masterStates { |
| tt, err := time.Parse(time.RFC3339Nano, state.TransitionTime) |
| if err != nil { |
| return err |
| } |
| |
| // TODO: make this warning window configurable. This logic will include a |
| // master if it is scheduled to restart at any time later than two |
| // hours ago. This handles both recent and future restarts. |
| |
| if now.Sub(tt) < 2*time.Hour { |
| ret[masterName] = state |
| } |
| } |
| return nil |
| } |
| |
| // For troopers, just display all of the pending restarts. |
| if treeName == "trooper" { |
| for masterName, states := range ms.MasterStates { |
| if err := filter(masterName, states); err != nil { |
| return nil, err |
| } |
| } |
| return ret, nil |
| } |
| |
| // For specific trees, filter to master specified in the config. |
| cfg, ok := trees[treeName] |
| if !ok { |
| return nil, ErrUnrecognizedTree |
| } |
| |
| for masterLoc := range cfg.Masters { |
| if err := filter(masterLoc.Name(), ms.MasterStates["master."+masterLoc.Name()]); err != nil { |
| return nil, err |
| } |
| } |
| return ret, nil |
| } |