[Leasing server] Backend changes necessary for lit-html migration
Bug: skia:10116
Change-Id: I8bf7c44a7173d22bcb031a29b43516a2dcccb356
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/282261
Commit-Queue: Ravi Mistry <rmistry@google.com>
Reviewed-by: Joe Gregorio <jcgregorio@google.com>
diff --git a/leasing/go/leasing/datastore.go b/leasing/go/leasing/datastore.go
index 7c89a02..d56ed9f 100644
--- a/leasing/go/leasing/datastore.go
+++ b/leasing/go/leasing/datastore.go
@@ -9,13 +9,15 @@
"fmt"
"cloud.google.com/go/datastore"
- "go.skia.org/infra/go/auth"
- "go.skia.org/infra/go/ds"
"google.golang.org/api/option"
+
+ "go.skia.org/infra/go/auth"
+ "go.skia.org/infra/go/baseapp"
+ "go.skia.org/infra/go/ds"
)
func DatastoreInit(project string, ns string) error {
- ts, err := auth.NewDefaultTokenSource(*local, "https://www.googleapis.com/auth/datastore")
+ ts, err := auth.NewDefaultTokenSource(*baseapp.Local, "https://www.googleapis.com/auth/datastore")
if err != nil {
return fmt.Errorf("Problem setting up default token source: %s", err)
}
diff --git a/leasing/go/leasing/debugger.go b/leasing/go/leasing/debugger.go
index 68a4c41..99feb41 100644
--- a/leasing/go/leasing/debugger.go
+++ b/leasing/go/leasing/debugger.go
@@ -11,12 +11,14 @@
"sync"
"cloud.google.com/go/storage"
+ "google.golang.org/api/iterator"
+ "google.golang.org/api/option"
+
"go.skia.org/infra/go/auth"
+ "go.skia.org/infra/go/baseapp"
"go.skia.org/infra/go/common"
"go.skia.org/infra/go/git/gitinfo"
"go.skia.org/infra/go/vcsinfo"
- "google.golang.org/api/iterator"
- "google.golang.org/api/option"
)
const (
@@ -41,7 +43,7 @@
return fmt.Errorf("Failed to checkout %s: %s", common.REPO_SKIA, err)
}
- ts, err := auth.NewDefaultTokenSource(*local, auth.SCOPE_READ_WRITE)
+ ts, err := auth.NewDefaultTokenSource(*baseapp.Local, auth.SCOPE_READ_WRITE)
if err != nil {
return fmt.Errorf("Problem setting up default token source: %s", err)
}
diff --git a/leasing/go/leasing/mail.go b/leasing/go/leasing/mail.go
index a91729a..250605c 100644
--- a/leasing/go/leasing/mail.go
+++ b/leasing/go/leasing/mail.go
@@ -14,11 +14,11 @@
)
const (
- LEASING_EMAIL_DISPLAY_NAME = "Leasing Server"
+ leasingEmailDisplayName = "Leasing Server"
- GMAIL_CACHED_TOKEN = "leasing_gmail_cached_token"
+ gmailCachedToken = "leasing_gmail_cached_token"
- CONNECTION_INSTRUCTIONS_PAGE = "https://skia.org/dev/testing/swarmingbots#connecting-to-swarming-bots"
+ connectionInstructionsPage = "https://skia.org/dev/testing/swarmingbots#connecting-to-swarming-bots"
)
var (
@@ -76,12 +76,12 @@
<br/><br/>
Thanks!
`
- body := fmt.Sprintf(bodyTemplate, taskLink, GetSwarmingBotLink(swarmingServer, swarmingBot), swarmingBot, sectionAboutIsolates, CONNECTION_INSTRUCTIONS_PAGE, fmt.Sprintf("%s%s", PROD_URI, MY_LEASES_URI))
+ body := fmt.Sprintf(bodyTemplate, taskLink, GetSwarmingBotLink(swarmingServer, swarmingBot), swarmingBot, sectionAboutIsolates, connectionInstructionsPage, fmt.Sprintf("%s%s", prodURI, myLeasesURI))
markup, err := getSwarmingLinkMarkup(taskLink)
if err != nil {
return fmt.Errorf("Failed to get view action markup: %s", err)
}
- if err := gmail.SendWithMarkup(LEASING_EMAIL_DISPLAY_NAME, getRecipients(ownerEmail), subject, body, markup); err != nil {
+ if err := gmail.SendWithMarkup(leasingEmailDisplayName, getRecipients(ownerEmail), subject, body, markup); err != nil {
return fmt.Errorf("Could not send start email: %s", err)
}
return nil
@@ -97,12 +97,12 @@
<br/><br/>
Thanks!
`
- body := fmt.Sprintf(bodyTemplate, taskLink, fmt.Sprintf("%s%s", PROD_URI, MY_LEASES_URI))
+ body := fmt.Sprintf(bodyTemplate, taskLink, fmt.Sprintf("%s%s", prodURI, myLeasesURI))
markup, err := getSwarmingLinkMarkup(taskLink)
if err != nil {
return fmt.Errorf("Failed to get view action markup: %s", err)
}
- if err := gmail.SendWithMarkup(LEASING_EMAIL_DISPLAY_NAME, getRecipients(ownerEmail), subject, body, markup); err != nil {
+ if err := gmail.SendWithMarkup(leasingEmailDisplayName, getRecipients(ownerEmail), subject, body, markup); err != nil {
return fmt.Errorf("Could not send warning email: %s", err)
}
return nil
@@ -120,12 +120,12 @@
<br/><br/>
Thanks!
`
- body := fmt.Sprintf(bodyTemplate, taskLink, swarmingTaskState, PROD_URI)
+ body := fmt.Sprintf(bodyTemplate, taskLink, swarmingTaskState, prodURI)
markup, err := getSwarmingLinkMarkup(taskLink)
if err != nil {
return fmt.Errorf("Failed to get view action markup: %s", err)
}
- if err := gmail.SendWithMarkup(LEASING_EMAIL_DISPLAY_NAME, getRecipients(ownerEmail), subject, body, markup); err != nil {
+ if err := gmail.SendWithMarkup(leasingEmailDisplayName, getRecipients(ownerEmail), subject, body, markup); err != nil {
return fmt.Errorf("Could not send failure email: %s", err)
}
return nil
@@ -141,12 +141,12 @@
<br/><br/>
Thanks!
`
- body := fmt.Sprintf(bodyTemplate, taskLink, durationHrs, PROD_URI)
+ body := fmt.Sprintf(bodyTemplate, taskLink, durationHrs, prodURI)
markup, err := getSwarmingLinkMarkup(taskLink)
if err != nil {
return fmt.Errorf("Failed to get view action markup: %s", err)
}
- if err := gmail.SendWithMarkup(LEASING_EMAIL_DISPLAY_NAME, getRecipients(ownerEmail), subject, body, markup); err != nil {
+ if err := gmail.SendWithMarkup(leasingEmailDisplayName, getRecipients(ownerEmail), subject, body, markup); err != nil {
return fmt.Errorf("Could not send completion email: %s", err)
}
return nil
@@ -162,12 +162,12 @@
<br/><br/>
Thanks!
`
- body := fmt.Sprintf(bodyTemplate, taskLink, PROD_URI)
+ body := fmt.Sprintf(bodyTemplate, taskLink, prodURI)
markup, err := getSwarmingLinkMarkup(taskLink)
if err != nil {
return fmt.Errorf("Failed to get view action markup: %s", err)
}
- if err := gmail.SendWithMarkup(LEASING_EMAIL_DISPLAY_NAME, getRecipients(ownerEmail), subject, body, markup); err != nil {
+ if err := gmail.SendWithMarkup(leasingEmailDisplayName, getRecipients(ownerEmail), subject, body, markup); err != nil {
return fmt.Errorf("Could not send completion email: %s", err)
}
return nil
diff --git a/leasing/go/leasing/main.go b/leasing/go/leasing/main.go
index 935807f..7f68713 100644
--- a/leasing/go/leasing/main.go
+++ b/leasing/go/leasing/main.go
@@ -14,53 +14,48 @@
"io/ioutil"
"net/http"
"path/filepath"
- "runtime"
"sort"
"strconv"
"sync"
"time"
"github.com/gorilla/mux"
+ "github.com/unrolled/secure"
+ "google.golang.org/api/iterator"
+
"go.skia.org/infra/go/allowed"
- "go.skia.org/infra/go/common"
+ "go.skia.org/infra/go/baseapp"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/login"
"go.skia.org/infra/go/metrics2"
- "go.skia.org/infra/go/skiaversion"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/swarming"
"go.skia.org/infra/go/util"
- "google.golang.org/api/iterator"
)
const (
- // OAUTH2_CALLBACK_PATH is callback endpoint used for the Oauth2 flow.
- OAUTH2_CALLBACK_PATH = "/oauth2callback/"
+ maxLeaseDurationHrs = 23
- MAX_LEASE_DURATION_HRS = 23
+ swarmingHardTimeout = 24 * time.Hour
- SWARMING_HARD_TIMEOUT = 24 * time.Hour
+ leaseTaskPriority = 50
- LEASE_TASK_PRIORITY = 50
-
- MY_LEASES_URI = "/my_leases"
- ALL_LEASES_URI = "/all_leases"
- GET_TASK_STATUS_URI = "/_/get_task_status"
- POOL_DETAILS_POST_URI = "/_/pooldetails"
- ADD_TASK_POST_URI = "/_/add_leasing_task"
- EXTEND_TASK_POST_URI = "/_/extend_leasing_task"
- EXPIRE_TASK_POST_URI = "/_/expire_leasing_task"
- PROD_URI = "https://leasing.skia.org"
+ myLeasesURI = "/my_leases"
+ allLeasesURI = "/all_leases"
+ getTaskStatusURI = "/_/get_task_status"
+ getLeasesPostURI = "/_/get_leases"
+ getSupportedPoolsPostURI = "/_/get_supported_pools"
+ poolDetailsPostURI = "/_/pooldetails"
+ addTaskPostURI = "/_/add_leasing_task"
+ extendTaskPostURI = "/_/extend_leasing_task"
+ expireTaskPostURI = "/_/expire_leasing_task"
+ prodURI = "https://leasing.skia.org"
)
var (
// Flags
- host = flag.String("host", "localhost", "HTTP service host")
- promPort = flag.String("prom_port", ":20000", "Metrics service address (e.g., ':20000')")
- port = flag.String("port", ":8002", "HTTP service port (e.g., ':8002')")
- local = flag.Bool("local", false, "Running locally if true. As opposed to in production.")
workdir = flag.String("workdir", ".", "Directory to use for scratch work.")
- resourcesDir = flag.String("resources_dir", "", "The directory to find templates, JS, and CSS files. If blank then the directory two directories up from this source file will be used.")
+ isolatesDir = flag.String("isolates_dir", "", "The directory to find leasing server's isolates files.")
pollInterval = flag.Duration("poll_interval", 1*time.Minute, "How often the leasing server will check if tasks have expired.")
emailClientSecretFile = flag.String("email_client_secret_file", "/etc/leasing-email-secrets/client_secret.json", "OAuth client secret JSON file for sending email.")
emailTokenCacheFile = flag.String("email_token_cache_file", "/etc/leasing-email-secrets/client_token.json", "OAuth token cache file for sending email.")
@@ -74,48 +69,162 @@
// OAUTH params
authWhiteList = flag.String("auth_whitelist", "google.com", "White space separated list of domains and email addresses that are allowed to login.")
- // indexTemplate is the main index.html page we serve.
- indexTemplate *template.Template = nil
-
- // leasesListTemplate is the page we serve on the my-leases and all-leases pages.
- leasesListTemplate *template.Template = nil
-
serverURL string
poolToDetails map[string]*PoolDetails
poolToDetailsMutex sync.Mutex
)
-func reloadTemplates() {
- if *resourcesDir == "" {
- // If resourcesDir is not specified then consider the directory two directories up from this
- // source file as the resourcesDir.
- _, filename, _, _ := runtime.Caller(0)
- *resourcesDir = filepath.Join(filepath.Dir(filename), "../..")
+type ClientConfig struct {
+ ClientID string `json:"client_id"`
+ ClientSecret string `json:"client_secret"`
+}
+
+type ClientSecretJSON struct {
+ Installed ClientConfig `json:"installed"`
+}
+
+// See baseapp.Constructor.
+func New() (baseapp.App, error) {
+ // Initialize mailing library.
+ var cfg ClientSecretJSON
+ err := util.WithReadFile(*emailClientSecretFile, func(f io.Reader) error {
+ return json.NewDecoder(f).Decode(&cfg)
+ })
+ if err != nil {
+ sklog.Fatalf("Failed to read client secrets from %q: %s", *emailClientSecretFile, err)
}
- indexTemplate = template.Must(template.ParseFiles(
- filepath.Join(*resourcesDir, "templates/index.html"),
- filepath.Join(*resourcesDir, "templates/header.html"),
- ))
- leasesListTemplate = template.Must(template.ParseFiles(
- filepath.Join(*resourcesDir, "templates/leases_list.html"),
- filepath.Join(*resourcesDir, "templates/header.html"),
+ // Create a copy of the token cache file since mounted secrets are read-only
+ // and the access token will need to be updated for the oauth2 flow.
+ if !*baseapp.Local {
+ fout, err := ioutil.TempFile("", "")
+ if err != nil {
+ sklog.Fatalf("Unable to create temp file %q: %s", fout.Name(), err)
+ }
+ err = util.WithReadFile(*emailTokenCacheFile, func(fin io.Reader) error {
+ _, err := io.Copy(fout, fin)
+ if err != nil {
+ err = fout.Close()
+ }
+ return err
+ })
+ if err != nil {
+ sklog.Fatalf("Failed to write token cache file from %q to %q: %s", *emailTokenCacheFile, fout.Name(), err)
+ }
+ *emailTokenCacheFile = fout.Name()
+ }
+ if err := MailInit(cfg.Installed.ClientID, cfg.Installed.ClientSecret, *emailTokenCacheFile); err != nil {
+ sklog.Fatalf("Failed to init mail library: %s", err)
+ }
+
+ var allow allowed.Allow
+ if !*baseapp.Local {
+ allow = allowed.NewAllowedFromList([]string{*authWhiteList})
+ } else {
+ allow = allowed.NewAllowedFromList([]string{"fred@example.org", "barney@example.org", "wilma@example.org"})
+ }
+ login.SimpleInitWithAllow(*baseapp.Port, *baseapp.Local, nil, nil, allow)
+
+ // Initialize isolate and swarming.
+ if err := SwarmingInit(*serviceAccountFile); err != nil {
+ sklog.Fatalf("Failed to init isolate and swarming: %s", err)
+ }
+
+ // Initialize cloud datastore.
+ if err := DatastoreInit(*projectName, *namespace); err != nil {
+ sklog.Fatalf("Failed to init cloud datastore: %s", err)
+ }
+
+ poolToDetails, err = GetDetailsOfAllPools()
+ if err != nil {
+ sklog.Fatalf("Could not get details of all pools: %s", err)
+ }
+ go func() {
+ for range time.Tick(*poolDetailsUpdateFrequency) {
+ poolToDetailsMutex.Lock()
+ poolToDetails, err = GetDetailsOfAllPools()
+ poolToDetailsMutex.Unlock()
+ if err != nil {
+ sklog.Errorf("Could not get details of all pools: %s", err)
+ }
+ }
+ }()
+
+ healthyGauge := metrics2.GetInt64Metric("healthy")
+ go func() {
+ for range time.Tick(*pollInterval) {
+ healthyGauge.Update(1)
+ if err := pollSwarmingTasks(); err != nil {
+ sklog.Errorf("Error when checking for expired tasks: %v", err)
+ }
+ }
+ }()
+
+ srv := &Server{}
+ srv.loadTemplates()
+
+ return srv, nil
+}
+
+// Server is the state of the server.
+type Server struct {
+ templates *template.Template
+}
+
+func (srv *Server) loadTemplates() {
+ srv.templates = template.Must(template.New("").Delims("{%", "%}").ParseFiles(
+ filepath.Join(*baseapp.ResourcesDir, "index.html"),
+ filepath.Join(*baseapp.ResourcesDir, "leases_list.html"),
))
}
-func loginHandler(w http.ResponseWriter, r *http.Request) {
- http.Redirect(w, r, login.LoginURL(w, r), http.StatusFound)
- return
+// user returns the currently logged in user, or a placeholder if running locally.
+func (srv *Server) user(r *http.Request) string {
+ user := "barney@example.org"
+ if !*baseapp.Local {
+ user = login.LoggedInAs(r)
+ }
+ return user
}
-func indexHandler(w http.ResponseWriter, r *http.Request) {
- if *local {
- reloadTemplates()
+// See baseapp.App.
+func (srv *Server) AddHandlers(r *mux.Router) {
+ // For login/logout.
+ r.HandleFunc(login.DEFAULT_OAUTH2_CALLBACK, login.OAuth2CallbackHandler)
+ r.HandleFunc("/logout/", login.LogoutHandler)
+ r.HandleFunc("/loginstatus/", login.StatusHandler)
+ // Get task status will be used from swarming bots.
+ r.HandleFunc(getTaskStatusURI, srv.statusHandler).Methods("GET")
+
+ // All endpoints that require authentication should be added to this router.
+ appRouter := mux.NewRouter()
+ appRouter.HandleFunc("/", srv.indexHandler)
+ appRouter.HandleFunc(myLeasesURI, srv.myLeasesHandler)
+ appRouter.HandleFunc(allLeasesURI, srv.allLeasesHandler)
+ appRouter.HandleFunc(poolDetailsPostURI, srv.poolDetailsHandler).Methods("POST")
+ appRouter.HandleFunc(getSupportedPoolsPostURI, srv.supportedPoolsHandler).Methods("POST")
+ appRouter.HandleFunc(getLeasesPostURI, srv.getLeasesHandler).Methods("POST")
+ appRouter.HandleFunc(addTaskPostURI, srv.addTaskHandler).Methods("POST")
+ appRouter.HandleFunc(extendTaskPostURI, srv.extendTaskHandler).Methods("POST")
+ appRouter.HandleFunc(expireTaskPostURI, srv.expireTaskHandler).Methods("POST")
+
+ // Use the appRouter as a handler and wrap it into middleware that enforces authentication.
+ appHandler := http.Handler(appRouter)
+ if !*baseapp.Local {
+ appHandler = login.ForceAuth(appRouter, login.DEFAULT_REDIRECT_URL)
}
+
+ r.PathPrefix("/").Handler(appHandler)
+}
+
+func (srv *Server) indexHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
- if err := indexTemplate.Execute(w, nil); err != nil {
- httputils.ReportError(w, err, "Failed to expand template", http.StatusInternalServerError)
+ if err := srv.templates.ExecuteTemplate(w, "index.html", map[string]string{
+ // Look in webpack.config.js for where the nonce templates are injected.
+ "Nonce": secure.CSPNonce(r.Context()),
+ }); err != nil {
+ httputils.ReportError(w, err, "Failed to expand template.", http.StatusInternalServerError)
return
}
return
@@ -126,7 +235,7 @@
Expired bool
}
-func statusHandler(w http.ResponseWriter, r *http.Request) {
+func (srv *Server) statusHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
taskParam := r.FormValue("task")
@@ -159,7 +268,7 @@
return
}
-func poolDetailsHandler(w http.ResponseWriter, r *http.Request) {
+func (srv *Server) poolDetailsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
poolParam := r.FormValue("pool")
@@ -180,6 +289,22 @@
}
}
+func (srv *Server) supportedPoolsHandler(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+
+ supportedPools := []string{}
+ poolToDetailsMutex.Lock()
+ defer poolToDetailsMutex.Unlock()
+ for p := range poolToDetails {
+ supportedPools = append(supportedPools, p)
+ }
+ sort.Strings(supportedPools)
+ if err := json.NewEncoder(w).Encode(supportedPools); err != nil {
+ httputils.ReportError(w, err, fmt.Sprintf("Failed to encode JSON: %v", err), http.StatusInternalServerError)
+ return
+ }
+}
+
type Task struct {
Requester string `json:"requester"`
OsType string `json:"osType"`
@@ -233,64 +358,61 @@
return tasks, nil
}
-func leasesHandlerHelper(w http.ResponseWriter, r *http.Request, filterUser string) {
- if *local {
- reloadTemplates()
- }
- w.Header().Set("Content-Type", "text/html")
+func (srv *Server) getLeasesHandler(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
- tasks, err := getLeasingTasks(filterUser)
+ reqGetLeasesRequest := struct {
+ FilterByUser string `json:"filter_by_user"`
+ }{}
+ if err := json.NewDecoder(r.Body).Decode(&reqGetLeasesRequest); err != nil {
+ httputils.ReportError(w, err, "Failed to decode add note request", http.StatusInternalServerError)
+ return
+ }
+ tasks, err := getLeasingTasks(reqGetLeasesRequest.FilterByUser)
if err != nil {
httputils.ReportError(w, err, "Failed to expand template", http.StatusInternalServerError)
return
}
-
- var templateTasks = struct {
- Tasks []*Task
- }{
- Tasks: tasks,
+ if err := json.NewEncoder(w).Encode(tasks); err != nil {
+ sklog.Errorf("Failed to send response: %s", err)
}
- if err := leasesListTemplate.Execute(w, templateTasks); err != nil {
- httputils.ReportError(w, err, "Failed to expand template", http.StatusInternalServerError)
+}
+
+func (srv *Server) leasesHandlerHelper(w http.ResponseWriter, r *http.Request, filterByUser string) {
+ w.Header().Set("Content-Type", "text/html")
+
+ if err := srv.templates.ExecuteTemplate(w, "leases_list.html", map[string]string{
+ "FilterByUser": filterByUser,
+ // Look in webpack.config.js for where the nonce templates are injected.
+ "Nonce": secure.CSPNonce(r.Context()),
+ }); err != nil {
+ httputils.ReportError(w, err, "Failed to expand template.", http.StatusInternalServerError)
return
}
return
}
-func myLeasesHandler(w http.ResponseWriter, r *http.Request) {
- leasesHandlerHelper(w, r, login.LoggedInAs(r))
+func (srv *Server) myLeasesHandler(w http.ResponseWriter, r *http.Request) {
+ srv.leasesHandlerHelper(w, r, login.LoggedInAs(r))
}
-func allLeasesHandler(w http.ResponseWriter, r *http.Request) {
- leasesHandlerHelper(w, r, "" /* filterUser */)
+func (srv *Server) allLeasesHandler(w http.ResponseWriter, r *http.Request) {
+ srv.leasesHandlerHelper(w, r, "" /* filterByUser */)
}
-func extendTaskHandler(w http.ResponseWriter, r *http.Request) {
+func (srv *Server) extendTaskHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
- taskParam := r.FormValue("task")
- if taskParam == "" {
- httputils.ReportError(w, nil, "Missing task parameter", http.StatusInternalServerError)
- return
- }
- taskID, err := strconv.ParseInt(taskParam, 10, 64)
- if err != nil {
- httputils.ReportError(w, err, "Invalid task parameter", http.StatusInternalServerError)
+ extendRequest := struct {
+ TaskID int64 `json:"task"`
+ DurationHrs int `json:"duration"`
+ }{}
+ if err := json.NewDecoder(r.Body).Decode(&extendRequest); err != nil {
+ httputils.ReportError(w, err, "Failed to decode extend request", http.StatusInternalServerError)
return
}
- durationParam := r.FormValue("duration")
- if durationParam == "" {
- httputils.ReportError(w, nil, "Missing duration parameter", http.StatusInternalServerError)
- return
- }
- durationHrs, err := strconv.Atoi(durationParam)
- if err != nil {
- httputils.ReportError(w, err, fmt.Sprintf("Failed to parse %s", durationParam), http.StatusInternalServerError)
- return
- }
-
- k, t, err := GetDSTask(taskID)
+ k, t, err := GetDSTask(extendRequest.TaskID)
if err != nil {
httputils.ReportError(w, err, "Could not find task", http.StatusInternalServerError)
return
@@ -298,10 +420,10 @@
// Add duration hours to the task's lease end time only if ends up being
// less than 23 hours after the task's creation time.
- newLeaseEndTime := t.LeaseEndTime.Add(time.Hour * time.Duration(durationHrs))
- maxPossibleLeaseEndTime := t.Created.Add(time.Hour * time.Duration(MAX_LEASE_DURATION_HRS))
+ newLeaseEndTime := t.LeaseEndTime.Add(time.Hour * time.Duration(extendRequest.DurationHrs))
+ maxPossibleLeaseEndTime := t.Created.Add(time.Hour * time.Duration(maxLeaseDurationHrs))
if newLeaseEndTime.After(maxPossibleLeaseEndTime) {
- httputils.ReportError(w, nil, fmt.Sprintf("Can not extend lease beyond %d hours of the task creation time", MAX_LEASE_DURATION_HRS), http.StatusInternalServerError)
+ httputils.ReportError(w, nil, fmt.Sprintf("Can not extend lease beyond %d hours of the task creation time", maxLeaseDurationHrs), http.StatusInternalServerError)
return
}
@@ -314,27 +436,28 @@
return
}
// Inform the requester that the task has been extended by durationHrs.
- if err := SendExtensionEmail(t.Requester, t.SwarmingServer, t.SwarmingTaskId, t.SwarmingBotId, durationHrs); err != nil {
+ if err := SendExtensionEmail(t.Requester, t.SwarmingServer, t.SwarmingTaskId, t.SwarmingBotId, extendRequest.DurationHrs); err != nil {
httputils.ReportError(w, err, "Error sending extension email", http.StatusInternalServerError)
return
}
+
+ if err := json.NewEncoder(w).Encode(t); err != nil {
+ sklog.Errorf("Failed to send response: %s", err)
+ }
}
-func expireTaskHandler(w http.ResponseWriter, r *http.Request) {
+func (srv *Server) expireTaskHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
- taskParam := r.FormValue("task")
- if taskParam == "" {
- httputils.ReportError(w, nil, "Missing task parameter", http.StatusInternalServerError)
- return
- }
- taskID, err := strconv.ParseInt(taskParam, 10, 64)
- if err != nil {
- httputils.ReportError(w, err, "Invalid task parameter", http.StatusInternalServerError)
+ expireRequest := struct {
+ TaskID int64 `json:"task"`
+ }{}
+ if err := json.NewDecoder(r.Body).Decode(&expireRequest); err != nil {
+ httputils.ReportError(w, err, "Failed to decode expire request", http.StatusInternalServerError)
return
}
- k, t, err := GetDSTask(taskID)
+ k, t, err := GetDSTask(expireRequest.TaskID)
if err != nil {
httputils.ReportError(w, err, "Could not find task", http.StatusInternalServerError)
return
@@ -354,9 +477,13 @@
httputils.ReportError(w, err, "Error sending completion email", http.StatusInternalServerError)
return
}
+
+ if err := json.NewEncoder(w).Encode(t); err != nil {
+ sklog.Errorf("Failed to send response: %s", err)
+ }
}
-func addTaskHandler(w http.ResponseWriter, r *http.Request) {
+func (srv *Server) addTaskHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
ctx := context.Background()
@@ -436,133 +563,16 @@
}
sklog.Infof("Added %v task into the datastore with key %s", task, datastoreKey)
+ if err := json.NewEncoder(w).Encode(task); err != nil {
+ sklog.Errorf("Failed to send response: %s", err)
+ }
}
-func runServer() {
- r := mux.NewRouter()
- r.PathPrefix("/res/").HandlerFunc(httputils.MakeResourceHandler(*resourcesDir))
-
- r.HandleFunc("/", indexHandler)
- r.HandleFunc(MY_LEASES_URI, myLeasesHandler)
- r.HandleFunc(ALL_LEASES_URI, allLeasesHandler)
- r.HandleFunc(POOL_DETAILS_POST_URI, poolDetailsHandler).Methods("POST")
- r.HandleFunc(ADD_TASK_POST_URI, addTaskHandler).Methods("POST")
- r.HandleFunc(EXTEND_TASK_POST_URI, extendTaskHandler).Methods("POST")
- r.HandleFunc(EXPIRE_TASK_POST_URI, expireTaskHandler).Methods("POST")
- r.HandleFunc("/json/version", skiaversion.JsonHandler)
- r.HandleFunc("/loginstatus/", login.StatusHandler)
-
- h := httputils.LoggingGzipRequestResponse(r)
- h = login.RestrictViewer(h)
- h = login.ForceAuth(h, login.DEFAULT_REDIRECT_URL)
- h = httputils.HealthzAndHTTPS(h)
-
- http.Handle("/", h)
- http.HandleFunc(GET_TASK_STATUS_URI, statusHandler)
-
- sklog.Infof("Ready to serve on %s", serverURL)
- sklog.Fatal(http.ListenAndServe(*port, nil))
-}
-
-type ClientConfig struct {
- ClientID string `json:"client_id"`
- ClientSecret string `json:"client_secret"`
-}
-
-type Installed struct {
- Installed ClientConfig `json:"installed"`
+// See baseapp.App.
+func (srv *Server) AddMiddleware() []mux.MiddlewareFunc {
+ return []mux.MiddlewareFunc{}
}
func main() {
- flag.Parse()
-
- common.InitWithMust(
- "leasing",
- common.PrometheusOpt(promPort),
- common.MetricsLoggingOpt(),
- )
-
- skiaversion.MustLogVersion()
-
- reloadTemplates()
- serverURL = "https://" + *host
- if *local {
- serverURL = "http://" + *host + *port
- }
-
- // Initialize mailing library.
- var cfg Installed
- err := util.WithReadFile(*emailClientSecretFile, func(f io.Reader) error {
- return json.NewDecoder(f).Decode(&cfg)
- })
- if err != nil {
- sklog.Fatalf("Failed to read client secrets from %q: %s", *emailClientSecretFile, err)
- }
- // Create a copy of the token cache file since mounted secrets are read-only
- // and the access token will need to be updated for the oauth2 flow.
- if !*local {
- fout, err := ioutil.TempFile("", "")
- if err != nil {
- sklog.Fatalf("Unable to create temp file %q: %s", fout.Name(), err)
- }
- err = util.WithReadFile(*emailTokenCacheFile, func(fin io.Reader) error {
- _, err := io.Copy(fout, fin)
- if err != nil {
- err = fout.Close()
- }
- return err
- })
- if err != nil {
- sklog.Fatalf("Failed to write token cache file from %q to %q: %s", *emailTokenCacheFile, fout.Name(), err)
- }
- *emailTokenCacheFile = fout.Name()
- }
- if err := MailInit(cfg.Installed.ClientID, cfg.Installed.ClientSecret, *emailTokenCacheFile); err != nil {
- sklog.Fatalf("Failed to init mail library: %s", err)
- }
-
- var allow allowed.Allow
- if !*local {
- allow = allowed.NewAllowedFromList([]string{*authWhiteList})
- } else {
- allow = allowed.NewAllowedFromList([]string{"fred@example.org", "barney@example.org", "wilma@example.org"})
- }
- login.SimpleInitWithAllow(*port, *local, nil, nil, allow)
-
- // Initialize isolate and swarming.
- if err := SwarmingInit(*serviceAccountFile); err != nil {
- sklog.Fatalf("Failed to init isolate and swarming: %s", err)
- }
-
- // Initialize cloud datastore.
- if err := DatastoreInit(*projectName, *namespace); err != nil {
- sklog.Fatalf("Failed to init cloud datastore: %s", err)
- }
-
- poolToDetails, err = GetDetailsOfAllPools()
- if err != nil {
- sklog.Fatalf("Could not get details of all pools: %s", err)
- }
- go func() {
- for range time.Tick(*poolDetailsUpdateFrequency) {
- poolToDetailsMutex.Lock()
- poolToDetails, err = GetDetailsOfAllPools()
- poolToDetailsMutex.Unlock()
- if err != nil {
- sklog.Errorf("Could not get details of all pools: %s", err)
- }
- }
- }()
-
- healthyGauge := metrics2.GetInt64Metric("healthy")
- go func() {
- for range time.Tick(*pollInterval) {
- healthyGauge.Update(1)
- if err := pollSwarmingTasks(); err != nil {
- sklog.Errorf("Error when checking for expired tasks: %v", err)
- }
- }
- }()
-
- runServer()
+ baseapp.Serve(New, []string{"leasing.skia.org"})
}
diff --git a/leasing/go/leasing/swarming.go b/leasing/go/leasing/swarming.go
index cdb32cd..a6e2816 100644
--- a/leasing/go/leasing/swarming.go
+++ b/leasing/go/leasing/swarming.go
@@ -12,7 +12,9 @@
"strings"
swarming_api "go.chromium.org/luci/common/api/swarming/swarming/v1"
+
"go.skia.org/infra/go/auth"
+ "go.skia.org/infra/go/baseapp"
"go.skia.org/infra/go/exec"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/isolate"
@@ -81,7 +83,7 @@
}
// Authenticated HTTP client.
- ts, err := auth.NewDefaultTokenSource(*local, swarming.AUTH_SCOPE)
+ ts, err := auth.NewDefaultTokenSource(*baseapp.Local, swarming.AUTH_SCOPE)
if err != nil {
return fmt.Errorf("Problem setting up default token source: %s", err)
}
@@ -225,9 +227,9 @@
func GetIsolateHash(ctx context.Context, pool, isolateDep string) (string, error) {
isolateClient := *GetIsolateClient(pool)
isolateTask := &isolate.Task{
- BaseDir: path.Join(*resourcesDir, "isolates"),
+ BaseDir: path.Join(*isolatesDir),
Blacklist: []string{},
- IsolateFile: path.Join(*resourcesDir, "isolates", "leasing.isolate"),
+ IsolateFile: path.Join(*isolatesDir, "leasing.isolate"),
}
if isolateDep != "" {
isolateTask.Deps = []string{isolateDep}
@@ -328,12 +330,12 @@
isolateServer := GetSwarmingInstance(pool).IsolateServer
expirationSecs := int64(swarming.RECOMMENDED_EXPIRATION.Seconds())
- executionTimeoutSecs := int64(SWARMING_HARD_TIMEOUT.Seconds())
- ioTimeoutSecs := int64(SWARMING_HARD_TIMEOUT.Seconds())
+ executionTimeoutSecs := int64(swarmingHardTimeout.Seconds())
+ ioTimeoutSecs := int64(swarmingHardTimeout.Seconds())
taskName := fmt.Sprintf("Leased by %s using leasing.skia.org", requester)
taskRequest := &swarming_api.SwarmingRpcsNewTaskRequest{
Name: taskName,
- Priority: LEASE_TASK_PRIORITY,
+ Priority: leaseTaskPriority,
TaskSlices: []*swarming_api.SwarmingRpcsTaskSlice{
{
ExpirationSecs: expirationSecs,