func v1CompatInjectRuns( ctx *grader.Context, runs *grader.Queue, db *sql.DB, guids []string, ) error { for _, guid := range guids { runCtx, err := v1CompatNewRunContext(ctx, db, guid) if err != nil { ctx.Log.Error( "Error getting run context", "err", err, "guid", guid, ) return err } ctx.Log.Info("RunContext", "runCtx", runCtx) gaugeAdd("grader_queue_total_length", 1) counterAdd("grader_runs_total", 1) input, err := ctx.InputManager.Add( runCtx.Run.InputHash, v1CompatNewGraderInputFactory(runCtx.ProblemName, &ctx.Config, db), ) if err != nil { ctx.Log.Error("Error getting input", "err", err, "run", runCtx) return err } if err = grader.AddRunContext(ctx, runCtx, input); err != nil { ctx.Log.Error("Error adding run context", "err", err, "guid", guid) return err } runs.AddRun(runCtx) } return nil }
func registerHandlers(mux *http.ServeMux, db *sql.DB) { runs, err := context().QueueManager.Get("default") if err != nil { panic(err) } mux.Handle("/", http.FileServer(&wrappedFileSystem{ fileSystem: &assetfs.AssetFS{ Asset: Asset, AssetDir: AssetDir, AssetInfo: AssetInfo, Prefix: "data", }, })) mux.HandleFunc("/monitoring/benchmark/", func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() ctx := context() runnerName := PeerName(r) f, err := os.OpenFile("benchmark.txt", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0664) if err != nil { ctx.Log.Error("Failed to open benchmark file", "err", err) return } defer f.Close() var buf bytes.Buffer buf.WriteString(fmt.Sprintf("%d %s ", time.Now().Unix(), runnerName)) io.Copy(&buf, r.Body) buf.WriteString("\n") if _, err := io.Copy(f, &buf); err != nil { ctx.Log.Error("Failed to write to benchmark file", "err", err) } }) gradeRe := regexp.MustCompile("/run/grade/(\\d+)/?") mux.HandleFunc("/run/grade/", func(w http.ResponseWriter, r *http.Request) { ctx := context() res := gradeRe.FindStringSubmatch(r.URL.Path) if res == nil { w.WriteHeader(http.StatusNotFound) return } id, err := strconv.ParseInt(res[1], 10, 64) if err != nil { w.WriteHeader(http.StatusNotFound) return } runCtx, err := newRunContext(ctx, db, id) if err != nil { ctx.Log.Error("Error getting run context", "err", err, "id", id) if err == sql.ErrNoRows { w.WriteHeader(http.StatusNotFound) } else { w.WriteHeader(http.StatusInternalServerError) } return } input, err := ctx.InputManager.Add( runCtx.Run.InputHash, grader.NewGraderInputFactory(runCtx.ProblemName, &ctx.Config), ) if err != nil { ctx.Log.Error("Error getting input", "err", err, "run", runCtx) w.WriteHeader(http.StatusInternalServerError) return } if err = grader.AddRunContext(ctx, runCtx, input); err != nil { ctx.Log.Error("Error adding run context", "err", err, "id", id) w.WriteHeader(http.StatusInternalServerError) return } if _, ok := r.URL.Query()["debug"]; ok { if err := runCtx.Debug(); err != nil { ctx.Log.Error("Unable to set debug mode", "err", err) } else { defer func() { if err := os.RemoveAll(runCtx.GradeDir); err != nil { ctx.Log.Error("Error writing response", "err", err) } }() } } runs.AddRun(runCtx) runCtx.Log.Info("enqueued run", "run", runCtx.Run) if _, ok := r.URL.Query()["wait"]; ok { select { case <-w.(http.CloseNotifier).CloseNotify(): return case <-runCtx.Ready(): } if _, ok := r.URL.Query()["multipart"]; ok { multipartWriter := multipart.NewWriter(w) defer multipartWriter.Close() w.Header().Set("Content-Type", multipartWriter.FormDataContentType()) files := []string{"logs.txt.gz", "files.zip", "details.json", "tracing.json.gz"} for _, file := range files { fd, err := os.Open(path.Join(runCtx.GradeDir, file)) if err != nil { ctx.Log.Error("Error opening file", "file", file, "err", err) continue } resultWriter, err := multipartWriter.CreateFormFile("file", file) if err != nil { ctx.Log.Error("Error sending file", "file", file, "err", err) continue } if _, err := io.Copy(resultWriter, fd); err != nil { ctx.Log.Error("Error sending file", "file", file, "err", err) continue } } } else { w.Header().Set("Content-Type", "text/json; charset=utf-8") jsonData, _ := json.MarshalIndent(runCtx.Result, "", " ") logData, err := readGzippedFile(path.Join(runCtx.GradeDir, "logs.txt.gz")) if err != nil { ctx.Log.Error("Error reading logs", "err", err) } filesZip, err := readBase64File(path.Join(runCtx.GradeDir, "files.zip")) if err != nil { ctx.Log.Error("Error reading logs", "err", err) } tracing, err := readBase64File(path.Join(runCtx.GradeDir, "tracing.json.gz")) if err != nil { ctx.Log.Error("Error reading logs", "err", err) } response := &ResponseStruct{ Results: string(jsonData), Logs: logData, FilesZip: filesZip, Tracing: tracing, } encoder := json.NewEncoder(w) if err := encoder.Encode(response); err != nil { ctx.Log.Error("Error writing response", "err", err) } } } else { w.Header().Set("Content-Type", "text/json; charset=utf-8") fmt.Fprintf(w, "{\"status\":\"ok\"}") } }) mux.HandleFunc("/run/request/", func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() ctx := context() runnerName := PeerName(r) ctx.Log.Debug("requesting run", "proto", r.Proto, "client", runnerName) runCtx, _, ok := runs.GetRun( runnerName, ctx.InflightMonitor, w.(http.CloseNotifier).CloseNotify(), ) if !ok { ctx.Log.Debug("client gone", "client", runnerName) } else { runCtx.Log.Debug("served run", "run", runCtx, "client", runnerName) w.Header().Set("Content-Type", "text/json; charset=utf-8") ev := runCtx.EventFactory.NewIssuerClockSyncEvent() w.Header().Set("Sync-ID", strconv.FormatUint(ev.SyncID, 10)) encoder := json.NewEncoder(w) encoder.Encode(runCtx.Run) runCtx.EventCollector.Add(ev) } }) runRe := regexp.MustCompile("/run/([0-9]+)/results/?") mux.Handle("/run/", http.TimeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := context() defer r.Body.Close() res := runRe.FindStringSubmatch(r.URL.Path) if res == nil { w.WriteHeader(http.StatusNotFound) return } attemptID, _ := strconv.ParseUint(res[1], 10, 64) runCtx, _, ok := ctx.InflightMonitor.Get(attemptID) if !ok { w.WriteHeader(http.StatusNotFound) return } result := processRun(r, attemptID, runCtx) w.WriteHeader(result.status) if !result.retry { // The run either finished correctly or encountered a fatal error. // Close the context and write the results to disk. runCtx.Close() } else { runCtx.Log.Error("run errored out. retrying", "context", runCtx) // status is OK only when the runner successfully sent a JE verdict. lastAttempt := result.status == http.StatusOK if !runCtx.Requeue(lastAttempt) { runCtx.Log.Error("run errored out too many times. giving up") } } }), time.Duration(5*time.Minute), "Request timed out")) inputRe := regexp.MustCompile("/input/([a-f0-9]{40})/?") mux.HandleFunc("/input/", func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() ctx := context() res := inputRe.FindStringSubmatch(r.URL.Path) if res == nil { w.WriteHeader(http.StatusNotFound) return } hash := res[1] input, err := ctx.InputManager.Get(hash) if err != nil { ctx.Log.Error("Input not found", "hash", hash) w.WriteHeader(http.StatusNotFound) return } defer input.Release(input) if err := input.Transmit(w); err != nil { ctx.Log.Error("Error transmitting input", "hash", hash, "err", err) w.WriteHeader(http.StatusInternalServerError) } }) }