Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for isolate v2 #112

Merged
merged 12 commits into from
Sep 14, 2024
1 change: 1 addition & 0 deletions cmd/kjudge/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func main() {

log.Println("Starting kjudge. Press Ctrl+C to stop")

go sandbox.Start()
go queue.Start()
go startServer(server)

Expand Down
22 changes: 2 additions & 20 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Stage -1: Update apt-get
# Stage 0: Update apt-get
FROM ubuntu:jammy AS base-ubuntu

# Mount apt's cache folders to cache install but maintain
Expand All @@ -10,19 +10,6 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get upgrade -y

# Stage 0: Compile isolate
FROM base-ubuntu AS isolate

RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get install -y libcap-dev gcc git make

WORKDIR /isolate

RUN git clone --branch v1.10.1 --single-branch https://github.com/ioi/isolate.git .

RUN make isolate

# Stage 1: Generate front-end
FROM node:18-alpine AS frontend

Expand Down Expand Up @@ -51,7 +38,7 @@ RUN scripts/install_tools.sh
RUN go generate && go build -tags production -o kjudge cmd/kjudge/main.go

# Stage 3: Create awesome output image
FROM base-ubuntu
FROM ghcr.io/minhnhatnoe/isolate:v2.1.5

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO put this somewhere centralized alongside other versioned stuff

RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
Expand All @@ -60,11 +47,6 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
python3.10 python2.7 rustc golang libcap-dev \
openssl

COPY --from=isolate /isolate/ /isolate

WORKDIR /isolate
RUN make install

COPY --from=backend /kjudge/kjudge /usr/local/bin
COPY --from=backend /kjudge/scripts /scripts

Expand Down
18 changes: 1 addition & 17 deletions docker/gcc-only.dockerfile
Original file line number Diff line number Diff line change
@@ -1,14 +1,3 @@
# Stage 0: Compile isolate
FROM alpine:3 AS isolate

RUN apk add --no-cache libcap gcc make git g++ libcap-dev

WORKDIR /isolate

RUN git clone --branch v1.10.1 --single-branch https://github.com/ioi/isolate.git .

RUN make isolate

# Stage 1: Generate front-end
FROM node:18-alpine AS frontend

Expand Down Expand Up @@ -40,15 +29,10 @@ RUN sh scripts/install_tools.sh
RUN go generate && go build -tags production -o kjudge cmd/kjudge/main.go

# Stage 3: Create awesome output image
FROM alpine:3
FROM ghcr.io/minhnhatnoe/isolate:v2.1.5-alpine

RUN apk add --no-cache libcap make g++ openssl bash

COPY --from=isolate /isolate/ /isolate

WORKDIR /isolate
RUN make install

COPY --from=backend /kjudge/kjudge /usr/local/bin
COPY --from=backend /kjudge/scripts /scripts

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/labstack/echo-contrib v0.9.0
github.com/labstack/echo/v4 v4.9.0
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-sqlite3 v1.14.0
github.com/mattn/go-sqlite3 v1.14.19
github.com/pkg/errors v0.9.1
golang.org/x/crypto v0.14.0
golang.org/x/net v0.17.0 // indirect
Expand Down
10 changes: 2 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
Expand Down Expand Up @@ -62,8 +60,8 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
Expand Down Expand Up @@ -116,16 +114,13 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190607181551-461777fb6f67/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
Expand All @@ -146,7 +141,6 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190609082536-301114b31cce/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
2 changes: 1 addition & 1 deletion scripts/install_tools.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ do
done

# Install golangci-lint
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "$(go env GOPATH)"/bin v1.52.2
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "$(go env GOPATH)"/bin v1.58.0
6 changes: 3 additions & 3 deletions scripts/start_container.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ case ${HTTPS} in
esac

if [ "${useHTTPS}" = true ]; then
kjudge -port 443 -file /data/kjudge.db -https /certs "$@"
KJUDGE_ISOLATE_DAEMON="start_isolate" kjudge -port 443 -file /data/kjudge.db -https /certs "$@"
else
kjudge -port 80 -file /data/kjudge.db "$@"
fi
KJUDGE_ISOLATE_DAEMON="start_isolate" kjudge -port 80 -file /data/kjudge.db "$@"
fi
6 changes: 5 additions & 1 deletion worker/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ func NewSandbox(name string, options ...sandbox.Option) (sandbox.Runner, error)
switch name {
case "raw":
return raw.New(setting), nil
case "isolate_v1":
return isolate.New(1, setting), nil
case "isolate":
return isolate.New(setting), nil
fallthrough
case "isolate_v2":
return isolate.New(2, setting), nil
default:
return nil, errors.Errorf("Sandbox %s doesn't exists or not yet implemented.", name)
}
Expand Down
100 changes: 89 additions & 11 deletions worker/sandbox/isolate/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
package isolate

import (
"bufio"
"bytes"
"fmt"
"io"
"log"
"os"
"os/exec"
"path/filepath"
Expand All @@ -19,40 +22,115 @@ import (
)

var (
// The isolate command. Can be overridden with KJUDGE_ISOLATE environment variable.
isolateCommand = "isolate"
// The isolate command. Can be overridden with KJUDGE_ISOLATE_V1 environment variable.
isolateCommandV1 = "isolate"
isolateCommandV2 = "isolate"
isolateDaemonCommand = "systemctl status isolate.service"
)

func init() {
if v, ok := os.LookupEnv("KJUDGE_ISOLATE_V1"); ok {
isolateCommandV1 = v
}
if v, ok := os.LookupEnv("KJUDGE_ISOLATE"); ok {
isolateCommand = v
isolateCommandV2 = v
}
if v, ok := os.LookupEnv("KJUDGE_ISOLATE_DAEMON"); ok {
isolateDaemonCommand = v
}
}

// Runner implements worker.Runner.
type Runner struct {
version int // 1 or 2
cmd *exec.Cmd
settings sandbox.Settings
private struct{} // Makes the sandbox not simply constructible
}

var _ sandbox.Runner = (*Runner)(nil)

func (s *Runner) isolateCommand() string {
if s.version == 1 {
return isolateCommandV1
} else if s.version == 2 {
return isolateCommandV2
} else {
log.Panicf("Invalid isolate version: %d", s.version)
return ""
}
}

// Panics on not having "isolate" accessible.
func mustHaveIsolate() {
output, err := exec.Command(isolateCommand, "--version").CombinedOutput()
func (s *Runner) mustHaveIsolate() {
output, err := exec.Command(s.isolateCommand(), "--version").CombinedOutput()
if err != nil {
panic(errors.Wrap(err, "trying to run isolate"))
}
if !strings.Contains(string(output), "The process isolator") {
panic("Wrong isolate command found. Override the KJUDGE_ISOLATE environment variable to set a different path.")
panic("Wrong isolate command found. Override the KJUDGE_ISOLATE_V1/KJUDGE_ISOLATE environment variable to set a different path.")
}
}

// New returns a new sandbox.
// Panics if isolate is not installed.
func New(settings sandbox.Settings) *Runner {
mustHaveIsolate()
return &Runner{settings: settings, private: struct{}{}}
func New(version int, settings sandbox.Settings) *Runner {
runner := &Runner{version: version, cmd: nil, settings: settings, private: struct{}{}}
runner.mustHaveIsolate()
return runner
}

func (s *Runner) Start() {
if s.version == 1 {
return
} else if s.version != 2 {
log.Panicf("Invalid isolate version: %v", s.version)
}

s.cmd = exec.Command("/bin/sh", "-c", isolateDaemonCommand)

stdout, err := s.cmd.StdoutPipe()
if err != nil {
log.Panic(errors.Wrap(err, "getting stdout pipe"))
}

stderr, err := s.cmd.StderrPipe()
if err != nil {
log.Panic(errors.Wrap(err, "getting stderr pipe"))
}

multi := io.MultiReader(stdout, stderr)
reader := bufio.NewScanner(multi)

if err := s.cmd.Start(); err != nil {
log.Panic(errors.Wrap(err, "starting isolate daemon"))
}

for reader.Scan() {
log.Printf("[isolate v2 daemon]: %s", reader.Text())
}

if err := reader.Err(); err != nil {
log.Panic(errors.Wrapf(err, "isolate daemon dead. Is daemon installed and, if installed as a systemd unit, started?"))
}

if err := s.cmd.Wait(); err != nil {
log.Panic(errors.Wrap(err, "waiting for isolate daemon"))
}
}

func (s *Runner) Stop() error {
if s.cmd != nil {
if err := s.cmd.Process.Kill(); err != nil {
return errors.Wrap(err, "killing isolate daemon")
}

if err := s.cmd.Process.Release(); err != nil {
return errors.Wrap(err, "releasing isolate daemon")
}
}

return nil
}

func (s *Runner) Settings() *sandbox.Settings {
Expand All @@ -63,7 +141,7 @@ func (s *Runner) Settings() *sandbox.Settings {
func (s *Runner) Run(input *sandbox.Input) (*sandbox.Output, error) {
// Init the sandbox
defer s.cleanup()
dirBytes, err := exec.Command(isolateCommand, "--init", "--cg").Output()
dirBytes, err := exec.Command(s.isolateCommand(), "--init", "--cg").Output()
if err != nil {
return nil, errors.WithStack(err)
}
Expand Down Expand Up @@ -153,5 +231,5 @@ func buildCmd(dir, metaFile string, input *sandbox.Input) *exec.Cmd {
}

func (s *Runner) cleanup() {
_ = exec.Command(isolateCommand, "--cleanup", "--cg").Run()
_ = exec.Command(s.isolateCommand(), "--cleanup", "--cg").Run()
}
6 changes: 6 additions & 0 deletions worker/sandbox/raw/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ func New(settings sandbox.Settings) *Runner {
return &Runner{settings: settings}
}

func (s *Runner) Start() {}

func (s *Runner) Stop() error {
return nil
}

func (s *Runner) Settings() *sandbox.Settings {
return &s.settings
}
Expand Down
2 changes: 2 additions & 0 deletions worker/sandbox/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
// and "raw" (NOT RECOMMENDED, RUN AT YOUR OWN RISK).
// Which sandbox is used can be set at runtime with a command-line switch.
type Runner interface {
Start()
minhnhatnoe marked this conversation as resolved.
Show resolved Hide resolved
Stop() error
Settings() *Settings
Run(*Input) (*Output, error)
}
Expand Down
Loading