Beispiel #1
0
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
}
Beispiel #2
0
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)
		}
	})
}