blob: 197b71a16fd88c52d9085482fc4ab4eab7f62aaa [file] [log] [blame]
/*
Handlers and types specific to running Lua scripts.
*/
package lua_scripts
import (
"context"
"fmt"
"net/http"
"path"
"path/filepath"
"strconv"
"text/template"
"cloud.google.com/go/datastore"
"github.com/gorilla/mux"
"go.skia.org/infra/ct/go/ctfe/capture_skps"
"go.skia.org/infra/ct/go/ctfe/task_common"
ctfeutil "go.skia.org/infra/ct/go/ctfe/util"
ctutil "go.skia.org/infra/ct/go/util"
"go.skia.org/infra/go/ds"
"go.skia.org/infra/go/email"
skutil "go.skia.org/infra/go/util"
"google.golang.org/api/iterator"
)
var (
addTaskTemplate *template.Template = nil
runsHistoryTemplate *template.Template = nil
)
func ReloadTemplates(resourcesDir string) {
addTaskTemplate = template.Must(template.ParseFiles(
filepath.Join(resourcesDir, "templates/lua_scripts.html"),
filepath.Join(resourcesDir, "templates/header.html"),
filepath.Join(resourcesDir, "templates/titlebar.html"),
))
runsHistoryTemplate = template.Must(template.ParseFiles(
filepath.Join(resourcesDir, "templates/lua_script_runs_history.html"),
filepath.Join(resourcesDir, "templates/header.html"),
filepath.Join(resourcesDir, "templates/titlebar.html"),
))
}
type DatastoreTask struct {
task_common.CommonCols
PageSets string
IsTestPageSet bool
ChromiumRev string
SkiaRev string
LuaScript string `datastore:",noindex"`
LuaAggregatorScript string `datastore:",noindex"`
Description string
ScriptOutput string
AggregatedOutput string
}
func (task DatastoreTask) GetTaskName() string {
return "LuaScript"
}
func (task DatastoreTask) GetDescription() string {
return task.Description
}
func (task DatastoreTask) GetResultsLink() string {
if task.AggregatedOutput != "" {
return task.AggregatedOutput
} else if task.ScriptOutput != "" {
return task.ScriptOutput
}
return ""
}
func (task DatastoreTask) GetPopulatedAddTaskVars() (task_common.AddTaskVars, error) {
taskVars := &AddTaskVars{}
taskVars.Username = task.Username
taskVars.TsAdded = ctutil.GetCurrentTs()
taskVars.RepeatAfterDays = strconv.FormatInt(task.RepeatAfterDays, 10)
taskVars.SkpRepository.ChromiumRev = task.ChromiumRev
taskVars.SkpRepository.SkiaRev = task.SkiaRev
taskVars.SkpRepository.PageSets = task.PageSets
taskVars.LuaScript = task.LuaScript
taskVars.LuaAggregatorScript = task.LuaAggregatorScript
taskVars.Description = task.Description
return taskVars, nil
}
func (task DatastoreTask) RunsOnGCEWorkers() bool {
return true
}
func (task DatastoreTask) GetDatastoreKind() ds.Kind {
return ds.LUA_SCRIPT_TASKS
}
func (task DatastoreTask) Query(it *datastore.Iterator) (interface{}, error) {
tasks := []*DatastoreTask{}
for {
t := &DatastoreTask{}
_, err := it.Next(t)
if err == iterator.Done {
break
} else if err != nil {
return nil, fmt.Errorf("Failed to retrieve list of tasks: %s", err)
}
tasks = append(tasks, t)
}
return tasks, nil
}
func (task DatastoreTask) Get(c context.Context, key *datastore.Key) (task_common.Task, error) {
t := &DatastoreTask{}
if err := ds.DS.Get(c, key, t); err != nil {
return nil, err
}
return t, nil
}
func (task DatastoreTask) TriggerSwarmingTaskAndMail(ctx context.Context) error {
runID := task_common.GetRunID(&task)
luaScriptGSPath, err := ctutil.SavePatchToStorage(task.LuaScript)
if err != nil {
return err
}
luaAggregatorScriptGSPath := ""
if task.LuaAggregatorScript != "" {
luaAggregatorScriptGSPath, err = ctutil.SavePatchToStorage(task.LuaAggregatorScript)
if err != nil {
return err
}
}
emails := task_common.GetEmailRecipients(task.Username, nil)
isolateArgs := map[string]string{
"PAGESET_TYPE": task.PageSets,
"CHROMIUM_BUILD": ctutil.ChromiumBuildDir(task.ChromiumRev, task.SkiaRev, ""),
"RUN_ON_GCE": strconv.FormatBool(task.RunsOnGCEWorkers()),
"RUN_ID": runID,
"LUA_SCRIPT_GS_PATH": luaScriptGSPath,
"LUA_AGGREGATOR_SCRIPT_GS_PATH": luaAggregatorScriptGSPath,
}
sTaskID, err := ctutil.TriggerMasterScriptSwarmingTask(ctx, runID, "run_lua_on_workers", ctutil.RUN_LUA_MASTER_ISOLATE, task_common.ServiceAccountFile, ctutil.PLATFORM_LINUX, false, isolateArgs)
if err != nil {
return fmt.Errorf("Could not trigger master script for run_lua_on_workers with isolate args %v: %s", isolateArgs, err)
}
// Mark task as started in datastore.
if err := task_common.UpdateTaskSetStarted(ctx, runID, sTaskID, &task); err != nil {
return fmt.Errorf("Could not mark task as started in datastore: %s", err)
}
// Send start email.
skutil.LogErr(ctfeutil.SendTaskStartEmail(task.DatastoreKey.ID, emails, "Lua script", runID, task.Description, ""))
return nil
}
func (task DatastoreTask) SendCompletionEmail(ctx context.Context, completedSuccessfully bool) error {
runID := task_common.GetRunID(&task)
emails := task_common.GetEmailRecipients(task.Username, nil)
emailSubject := fmt.Sprintf("Run lua script Cluster telemetry task has completed (#%d)", task.DatastoreKey.ID)
failureHtml := ""
viewActionMarkup := ""
var err error
if !completedSuccessfully {
emailSubject += " with failures"
failureHtml = ctfeutil.GetFailureEmailHtml(runID)
if viewActionMarkup, err = email.GetViewActionMarkup(fmt.Sprintf(ctutil.SWARMING_RUN_ID_ALL_TASKS_LINK_TEMPLATE, runID), "View Failure", "Direct link to the swarming logs"); err != nil {
return fmt.Errorf("Failed to get view action markup: %s", err)
}
} else {
if viewActionMarkup, err = email.GetViewActionMarkup(task.ScriptOutput, "View Results", "Direct link to the lua output"); err != nil {
return fmt.Errorf("Failed to get view action markup: %s", err)
}
}
scriptOutputHtml := ""
if task.ScriptOutput != "" {
scriptOutputHtml = fmt.Sprintf("The output of your script is available <a href='%s'>here</a>.<br/>\n", task.ScriptOutput)
}
aggregatorOutputHtml := ""
if task.AggregatedOutput != "" {
aggregatorOutputHtml = fmt.Sprintf("The aggregated output of your script is available <a href='%s'>here</a>.<br/>\n", task.AggregatedOutput)
}
bodyTemplate := `
The Cluster telemetry queued task to run lua script on %s pageset has completed. %s.<br/>
Run description: %s<br/>
%s
%s
%s
You can schedule more runs <a href="%s">here</a>.<br/><br/>
Thanks!
`
emailBody := fmt.Sprintf(bodyTemplate, task.PageSets, ctfeutil.GetSwarmingLogsLink(runID), task.Description, failureHtml, scriptOutputHtml, aggregatorOutputHtml, path.Join(task_common.WebappURL, ctfeutil.LUA_SCRIPT_URI))
if err := ctfeutil.SendEmailWithMarkup(emails, emailSubject, emailBody, viewActionMarkup); err != nil {
return fmt.Errorf("Error while sending email: %s", err)
}
return nil
}
func (task *DatastoreTask) SetCompleted(success bool) {
if success {
runID := task_common.GetRunID(task)
task.ScriptOutput = ctutil.GetLuaOutputRemoteLink(runID)
if task.LuaAggregatorScript != "" {
task.AggregatedOutput = ctutil.GetLuaAggregatorOutputRemoteLink(runID)
}
}
task.TsCompleted = ctutil.GetCurrentTsInt64()
task.Failure = !success
task.TaskDone = true
}
func addTaskView(w http.ResponseWriter, r *http.Request) {
ctfeutil.ExecuteSimpleTemplate(addTaskTemplate, w, r)
}
type AddTaskVars struct {
task_common.AddTaskCommonVars
SkpRepository capture_skps.DatastoreTask `json:"skp_repository"`
LuaScript string `json:"lua_script"`
LuaAggregatorScript string `json:"lua_aggregator_script"`
Description string `json:"desc"`
}
func (task *AddTaskVars) GetDatastoreKind() ds.Kind {
return ds.LUA_SCRIPT_TASKS
}
func (task *AddTaskVars) GetPopulatedDatastoreTask(ctx context.Context) (task_common.Task, error) {
if task.SkpRepository.PageSets == "" ||
task.SkpRepository.ChromiumRev == "" ||
task.SkpRepository.SkiaRev == "" ||
task.LuaScript == "" ||
task.Description == "" {
return nil, fmt.Errorf("Invalid parameters")
}
if err := capture_skps.Validate(ctx, task.SkpRepository); err != nil {
return nil, err
}
t := &DatastoreTask{
PageSets: task.SkpRepository.PageSets,
IsTestPageSet: task.SkpRepository.PageSets == ctutil.PAGESET_TYPE_DUMMY_1k || task.SkpRepository.PageSets == ctutil.PAGESET_TYPE_MOBILE_DUMMY_1k,
ChromiumRev: task.SkpRepository.ChromiumRev,
SkiaRev: task.SkpRepository.SkiaRev,
LuaScript: task.LuaScript,
LuaAggregatorScript: task.LuaAggregatorScript,
Description: task.Description,
}
return t, nil
}
func addTaskHandler(w http.ResponseWriter, r *http.Request) {
task_common.AddTaskHandler(w, r, &AddTaskVars{})
}
func getTasksHandler(w http.ResponseWriter, r *http.Request) {
task_common.GetTasksHandler(&DatastoreTask{}, w, r)
}
func deleteTaskHandler(w http.ResponseWriter, r *http.Request) {
task_common.DeleteTaskHandler(&DatastoreTask{}, w, r)
}
func redoTaskHandler(w http.ResponseWriter, r *http.Request) {
task_common.RedoTaskHandler(&DatastoreTask{}, w, r)
}
func runsHistoryView(w http.ResponseWriter, r *http.Request) {
ctfeutil.ExecuteSimpleTemplate(runsHistoryTemplate, w, r)
}
func AddHandlers(externalRouter *mux.Router) {
externalRouter.HandleFunc("/"+ctfeutil.LUA_SCRIPT_URI, addTaskView).Methods("GET")
externalRouter.HandleFunc("/"+ctfeutil.LUA_SCRIPT_RUNS_URI, runsHistoryView).Methods("GET")
externalRouter.HandleFunc("/"+ctfeutil.ADD_LUA_SCRIPT_TASK_POST_URI, addTaskHandler).Methods("POST")
externalRouter.HandleFunc("/"+ctfeutil.GET_LUA_SCRIPT_TASKS_POST_URI, getTasksHandler).Methods("POST")
externalRouter.HandleFunc("/"+ctfeutil.DELETE_LUA_SCRIPT_TASK_POST_URI, deleteTaskHandler).Methods("POST")
externalRouter.HandleFunc("/"+ctfeutil.REDO_LUA_SCRIPT_TASK_POST_URI, redoTaskHandler).Methods("POST")
}