Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions api/api.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package api

import (
"fmt"
"log"
"net/http"

Expand All @@ -11,18 +12,19 @@ type ApiContext struct {
Store store.Store
}

func API(s store.Store) {
func API(s store.Store, port int) {
apiCtx := ApiContext{
Store: s,
}

mux := http.NewServeMux()
mux.HandleFunc("/program", methodHandler("POST", apiCtx.submitProgram))
mux.HandleFunc("/users", methodHandler("POST", apiCtx.createUser))
mux.HandleFunc("/contests", methodHandler("POST", apiCtx.createContest))
mux.HandleFunc("/problems", methodHandler("POST", apiCtx.createProblem))
mux.HandleFunc("/program", methodHandler("POST", apiCtx.SubmitProgram))

log.Fatal(http.ListenAndServe(":8000", mux))
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", port), mux))
}

func methodHandler(method string, h http.HandlerFunc) http.HandlerFunc {
Expand Down
44 changes: 38 additions & 6 deletions api/submit.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
package api

import (
"bufio"
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"sync"

"lidsol.org/papeador/judge"

"github.com/containers/podman/v5/pkg/bindings/containers"
)

func (api *ApiContext) SubmitProgram(w http.ResponseWriter, r *http.Request) {
var m sync.Mutex

func (api *ApiContext) submitProgram(w http.ResponseWriter, r *http.Request) {
file, fileHeader, err := r.FormFile("program")
if err != nil {
log.Printf("Could not get form file: %v\n", err)
Expand All @@ -31,10 +36,24 @@ func (api *ApiContext) SubmitProgram(w http.ResponseWriter, r *http.Request) {
fmt.Println(string(buf))
fmt.Println(fileHeader.Size, fileHeader.Filename)

conn := judge.GetConn()
m.Lock()
worker := <- *judge.WorkerQueueP
m.Unlock()

conn := worker.Ctx

createResponse, err := judge.CreateSandbox(conn, string(buf)[:n], "10\n", "10\n")
filenameSep := strings.Split(fileHeader.Filename, ".")
filetype := filenameSep[len(filenameSep)-1]

testcases := []judge.SubmissionTestCase{
judge.SubmissionTestCase{Input: "10\n", Output: "20\n"},
judge.SubmissionTestCase{Input: "5\n", Output: "10\n"},
judge.SubmissionTestCase{Input: "20\n", Output: "40\n"},
}
timelimit := 1
createResponse, err := judge.CreateSandbox(conn, filetype, string(buf)[:n], testcases, timelimit)
if err != nil {
*judge.WorkerQueueP <- worker
s := fmt.Sprintf("Could not create sandbox: %v", err)
http.Error(w, s, http.StatusInternalServerError)
return
Expand All @@ -43,6 +62,7 @@ func (api *ApiContext) SubmitProgram(w http.ResponseWriter, r *http.Request) {
stdoutBuf := new(strings.Builder)
err = judge.StartSandbox(conn, createResponse, stdoutBuf)
if err != nil {
*judge.WorkerQueueP <- worker
log.Println(err)
http.Error(w, "Could not start sandbox", http.StatusInternalServerError)
return
Expand All @@ -51,14 +71,26 @@ func (api *ApiContext) SubmitProgram(w http.ResponseWriter, r *http.Request) {
log.Println("Container created successfully")
_, err = containers.Wait(conn, createResponse.ID, nil)
if err != nil {
*judge.WorkerQueueP <- worker
log.Println(err)
http.Error(w, "Could not stop sandbox", http.StatusInternalServerError)
return
}

resp := stdoutBuf.String() + "\n"
log.Printf("Resultado: %v", resp)
scanner := bufio.NewScanner(strings.NewReader(stdoutBuf.String()))
resMap := make(map[int]string)

i := 0
for scanner.Scan() {
res := scanner.Text()
log.Printf("Testcase %v: %v", i, res)
resMap[i] = res
i++
}

// Metelo de nuevo
*judge.WorkerQueueP <- worker

w.WriteHeader(http.StatusOK)
w.Write([]byte(resp))
json.NewEncoder(w).Encode(resMap)
}
99 changes: 76 additions & 23 deletions judge/judge.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package judge
import (
"bufio"
"context"
"fmt"
"io"
"strconv"
"sync"

"log"
"os"
Expand All @@ -19,73 +22,123 @@ import (
_ "modernc.org/sqlite"
)

var podmanConn context.Context
var podmanConns []*Worker
var workerQueue = make(chan *Worker, 10)

func GetConn() context.Context {
return podmanConn
}
var WorkerQueueP = &workerQueue;

var m sync.Mutex

func ConnectToPodman(connURI string) (context.Context, error) {
conn, err := bindings.NewConnection(context.Background(), connURI)
if err != nil {
return nil, err
}

podmanConn = conn
worker := &Worker{
Uri: connURI,
Ctx: conn,
Available: true,
}
podmanConns = append(podmanConns, worker)

workerQueue <- worker

log.Println("Connection successful")

return conn, nil
}

func CreateSandbox(conn context.Context, programStr, testInputStr, expectedOutputStr string) (types.ContainerCreateResponse, error) {
err := os.Chdir("/vol/podman")
func CreateFiles(filetype, programStr string, testcases []SubmissionTestCase, timeLimit int) error {
m.Lock()
defer m.Unlock()


err := os.MkdirAll("/vol/podman/inputs", 0755)
if err != nil {
return types.ContainerCreateResponse{}, err
return err
}

programPath := "./papeador-submission.go"
testInputPath := "./papeador-input.txt"
expectedOutputPath := "./papeador-output.txt"
err = os.MkdirAll("/vol/podman/expected-outputs", 0755)
if err != nil {
return err
}

err = writeStringToFile(programPath, programStr)
cwd, err := os.Getwd()
if err != nil {
return types.ContainerCreateResponse{}, err
return err
}

err = writeStringToFile(testInputPath, testInputStr)
defer func() {
err = os.Chdir(cwd)
}()

err = os.Chdir("/vol/podman")
if err != nil {
return types.ContainerCreateResponse{}, err
return err
}

err = writeStringToFile(expectedOutputPath, expectedOutputStr)
programPath := fmt.Sprintf("./papeador-submission.%v", filetype)

timeLimitPath := "/vol/podman/timelimit.txt"
timeLimitString := strconv.Itoa(timeLimit) + "\n"
err = writeStringToFile(timeLimitPath, timeLimitString)
if err != nil {
return types.ContainerCreateResponse{}, err
return err
}

err = writeStringToFile(programPath, programStr)
if err != nil {
return err
}

for k, testcase := range testcases {
testInputPath := fmt.Sprintf("./inputs/%02d.txt", k)
expectedOutputPath := fmt.Sprintf("./expected-outputs/%02d.txt", k)


err = writeStringToFile(testInputPath, testcase.Input)
if err != nil {
return err
}

err = writeStringToFile(expectedOutputPath, testcase.Output)
if err != nil {
return err
}
}

return nil
}

func CreateSandbox(conn context.Context, filetype, programStr string, testcases []SubmissionTestCase, timeLimit int) (types.ContainerCreateResponse, error) {

err := CreateFiles(filetype, programStr, testcases, timeLimit)

options := types.BuildOptions{
BuildOptions: buildahDefine.BuildOptions{
Output: "program:latest",
ConfigureNetwork: buildahDefine.NetworkDisabled,
},
}

log.Println("Building")
_, err = images.Build(conn, []string{"./Dockerfile"}, options)
log.Println("Building image")

dockerfilePath := fmt.Sprintf("./Dockerfile-%v", filetype)
_, err = images.Build(conn, []string{dockerfilePath}, options)
if err != nil {
return types.ContainerCreateResponse{}, err
}

log.Println("Creating with spec")
s := specgen.NewSpecGenerator("program:latest", false)
s.Command = []string{"/bin/sh", "-c", "sleep 5"}
s.Command = []string{"/bin/alive"}
s.Name = "submission-sandbox" + genRandStr(8)
createReponse, err := containers.CreateWithSpec(conn, s, nil)
if err != nil {
return types.ContainerCreateResponse{}, err
}

os.Remove(programPath)
os.Remove(testInputPath)
os.Remove(expectedOutputPath)
return createReponse, nil
}

Expand Down
12 changes: 12 additions & 0 deletions judge/util.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
package judge

import (
"context"
"crypto/rand"
"fmt"
"os"
)

type SubmissionTestCase struct {
Input string
Output string
}

type Worker struct {
Uri string
Ctx context.Context
Available bool
}

func genRandStr(length int) string {
b := make([]byte, length+2)
rand.Read(b)
Expand Down
24 changes: 21 additions & 3 deletions papeador.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"flag"
"strings"

"database/sql"
"fmt"
Expand All @@ -16,21 +17,38 @@ import (
_ "modernc.org/sqlite"
)

func testDB() {
func testDB(port int) {
db, err := sql.Open("sqlite", "test.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()

command := exec.Command("bash", "-c", "sqlite3 test.db <schema.sql")
command := exec.Command("sh", "-c", "sqlite3 test.db <schema.sql")
output, err := command.CombinedOutput()
if err != nil {
fmt.Println(string(output))
log.Fatal(err)
}
s := store.NewSQLiteStore(db)
api.API(s)
api.API(s, port)
}

func main() {
uriPtr := flag.String("u", "unix:///run/user/1000/podman/podman.sock", "uri for Podman API service connection")
var port int
flag.IntVar(&port, "p", 8080, "port to listen from")
flag.Parse()

uri := *uriPtr
for _, u := range strings.Split(uri, " ") {
_, err := judge.ConnectToPodman(u)
if err != nil {
log.Fatalf("Could not connect to Podman: %v", err)
}
}

testDB(port)
}

func main() {
Expand Down
31 changes: 31 additions & 0 deletions podman/Dockerfile-c
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# syntax=docker/dockerfile:1

# Build input program
FROM gcc:4.9
WORKDIR /src
COPY ./papeador-submission.c ./main.c
RUN gcc -static -o /bin/submission /src/main.c



# Create sandbox
FROM busybox:latest

# Write file
COPY ./papeador.sh /bin/papeador
COPY ./alive.sh /bin/alive

COPY ./timelimit.txt /tmp/timelimit.txt
COPY ./inputs /tmp/inputs
COPY ./expected-outputs /tmp/expected-outputs

RUN chmod -R 600 /tmp/timelimit.txt
RUN chmod -R 600 /tmp/inputs
RUN chmod -R 600 /tmp/expected-outputs

RUN chmod 755 /bin/papeador
RUN chmod 755 /bin/alive

RUN adduser runner -D
COPY --from=0 /bin/submission /bin/submission
CMD ["papeador"]
Loading
Loading