blob: 9f933e406589814c8724d60d0d1a4a9ef0d8ba86 [file] [log] [blame]
package main
import (
"encoding/json"
"flag"
"fmt"
"net/http"
"time"
"github.com/gorilla/mux"
"go.skia.org/infra/am/go/alertclient"
"go.skia.org/infra/go/allowed"
"go.skia.org/infra/go/auth"
"go.skia.org/infra/go/common"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/login"
"go.skia.org/infra/go/sklog"
skswarming "go.skia.org/infra/go/swarming"
"go.skia.org/infra/power/go/decider"
"go.skia.org/infra/power/go/gatherer"
"go.skia.org/infra/power/go/recorder"
)
var downBots gatherer.Gatherer = nil
var fixRecorder recorder.Recorder = nil
var (
// web server params
port = flag.String("port", ":8080", "HTTP service port")
local = flag.Bool("local", false, "Running locally if true. As opposed to in production.")
resourcesDir = flag.String("resources_dir", "", "The directory to find templates, JS, and CSS files. If blank the current directory will be used.")
promPort = flag.String("prom_port", ":20000", "Metrics service address (e.g., ':10110')")
alertsEndpoint = flag.String("alerts_endpoint", "alert-manager:9000", "The alert manager GCE name and port")
// OAUTH params
powercycleConfigs = common.NewMultiStringFlag("powercycle_config", nil, "JSON5 file with powercycle bot/device configuration. Same as used for powercycle.")
updatePeriod = flag.Duration("update_period", time.Minute, "How often to update the list of down bots.")
authorizedEmails = common.NewMultiStringFlag("authorized_email", nil, "Email addresses of users who are authorized to post to this web service.")
)
func main() {
flag.Parse()
if *local {
common.InitWithMust(
"power-controller",
common.PrometheusOpt(promPort),
)
} else {
common.InitWithMust(
"power-controller",
common.PrometheusOpt(promPort),
common.MetricsLoggingOpt(),
)
}
if err := setupGatherer(); err != nil {
sklog.Fatalf("Could not set up down bot gatherer: %s", err)
}
r := mux.NewRouter()
r.HandleFunc("/down_bots", downBotsHandler)
allow := allowed.NewAllowedFromList(*authorizedEmails)
r.HandleFunc("/powercycled_bots", login.RestrictFn(powercycledBotsHandler, allow))
r.PathPrefix("/").HandlerFunc(httputils.MakeResourceHandler(*resourcesDir))
rootHandler := httputils.LoggingGzipRequestResponse(r)
rootHandler = httputils.HealthzAndHTTPS(rootHandler)
http.Handle("/", rootHandler)
sklog.Infof("Ready to serve on http://127.0.0.1%s", *port)
sklog.Fatal(http.ListenAndServe(*port, nil))
}
type downBotsResponse struct {
List []gatherer.DownBot `json:"list"`
}
func downBotsHandler(w http.ResponseWriter, r *http.Request) {
if downBots == nil {
http.Error(w, "The power-controller isn't finished booting up. Try again later", http.StatusServiceUnavailable)
return
}
w.Header().Set("Content-Type", "application/json")
response := downBotsResponse{List: downBots.DownBots()}
if err := json.NewEncoder(w).Encode(response); err != nil {
sklog.Errorf("Failed to write or encode output: %s", err)
return
}
}
// powercycledBotsHandler is the way that the powercycle daemons can talk
// to the server and report that they have powercycled.
func powercycledBotsHandler(w http.ResponseWriter, r *http.Request) {
var input struct {
PowercycledBots []string `json:"powercycled_bots"`
}
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
httputils.ReportError(w, err, fmt.Sprintf("Failed to decode request body: %s", err), http.StatusInternalServerError)
return
}
email := login.LoggedInAs(r)
sklog.Infof("%s reported they powercycled %q", email, input.PowercycledBots)
fixRecorder.PowercycledBots(email, input.PowercycledBots)
w.WriteHeader(http.StatusAccepted)
}
func setupGatherer() error {
ts, err := auth.NewDefaultTokenSource(*local, skswarming.AUTH_SCOPE)
if err != nil {
return fmt.Errorf("Problem setting up default token source: %s", err)
}
authedClient := httputils.DefaultClientConfig().WithTokenSource(ts).With2xxOnly().Client()
es, err := skswarming.NewApiClient(authedClient, "chromium-swarm.appspot.com")
if err != nil {
return err
}
is, err := skswarming.NewApiClient(authedClient, "chrome-swarming.appspot.com")
if err != nil {
return fmt.Errorf("Could not get ApiClient for chrome-swarming: %s", err)
}
c := httputils.DefaultClientConfig().With2xxOnly().Client()
ac := alertclient.New(c, *alertsEndpoint)
d, hostMap, err := decider.New(*powercycleConfigs)
if err != nil {
return fmt.Errorf("Could not initialize down bot decider: %s", err)
}
fixRecorder = recorder.NewCloudLoggingRecorder()
downBots = gatherer.NewPollingGatherer(es, is, ac, d, fixRecorder, hostMap, *updatePeriod)
return nil
}