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
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
# Leviathan

Container Orchestrator/Job Runner replacement for Autolab Tango
A container orchestrator and job runner for executing code in docker containers.

## Testing
## Overview

Leviathan is the code execution engine for the [DevU project](https://github.com/makeopensource/devU).
It securely runs submitted code in isolated containers, providing evaluation results and feedback.
Designed as a modern replacement for [Autolab Tango](https://github.com/autolab/Tango).

Leviathan maintains full compatibility with existing Autolab/Tango graders. Any graders written for Tango will run on Leviathan without modification.

## Features
- Secure container-based execution
- Resource limiting and management
- gRPC API for type-safe, efficient communication

## Documentation
See [docs](docs) for configuration options and other details.

## Quick Start

To test out the dev build, ensure docker is installed on your system

Expand All @@ -20,7 +36,7 @@ You will need to run 2 services

3. Access the frontend at, http://localhost:3000

## Running a job
### Running a job

You will need to fill out

Expand All @@ -44,7 +60,7 @@ You will need to fill out

### Example

You can see the test files [here](./example/simple-addition)
You can see the test files [here](example/simple-addition)

> [!NOTE]
>
Expand Down
Binary file added docs/static/test-example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
63 changes: 63 additions & 0 deletions example/tango/tango-Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
FROM ubuntu:24.04
LABEL org.opencontainers.image.authors="Nicholas Myers"

RUN apt update

# C++ Setup
RUN apt install -y gcc
RUN apt install -y make
RUN apt install -y build-essential
RUN apt install -y libcunit1-dev libcunit1-doc libcunit1

# Python Setup
RUN apt update --fix-missing
RUN DEBIAN_FRONTEND=nointeractive apt install -y \
python3 \
python3-pip \
python3-numpy \
python3-pandas \
python3-nose

# Java Setup
RUN apt update --fix-missing
RUN apt install -y default-jdk

# Valgrind Setup
RUN apt update
RUN apt install -y valgrind

# Utility setup
RUN apt install -y unzip

# NodeJS setup
RUN apt update --fix-missing
RUN apt install -y nodejs
RUN apt install -y npm

# Install autodriver
WORKDIR /home
RUN useradd autolab
RUN useradd autograde
RUN mkdir autolab autograde output
RUN chown autolab:autolab autolab
RUN chown autolab:autolab output
RUN chown autograde:autograde autograde
RUN apt update && apt install -y sudo
RUN apt install -y git
RUN git clone https://github.com/autolab/Tango.git
WORKDIR Tango/autodriver
RUN make clean && make
RUN cp autodriver /usr/bin/autodriver
RUN chmod +s /usr/bin/autodriver

# Clean up
WORKDIR /home
RUN apt -y autoremove
RUN rm -rf Tango/

# Check installation
RUN ls -l /home
RUN which autodriver
RUN g++ --version
RUN python3 --version
RUN which javac
3 changes: 3 additions & 0 deletions example/tango/tango0/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
all:
tar -xf autograde.tar
python3 grader.py
Binary file added example/tango/tango0/autograde.tar
Binary file not shown.
3 changes: 3 additions & 0 deletions example/tango/tango0/create_grader.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

tar -cf autograde.tar grader.py
21 changes: 21 additions & 0 deletions example/tango/tango0/grader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import json

from handin import get_positive_number

scores = {
"q1": 0,
"q2": 0,
"q3": 0,
}

if __name__ == '__main__':
result = get_positive_number()
if result > 0:
scores["q1"] = 10
scores["q2"] = 10
scores["q3"] = 10
print(f"Great job! You returned {result}, which is positive!")
else:
print(f"Try again. You returned {result}, which is not positive.")

print(json.dumps({"scores": scores}))
2 changes: 2 additions & 0 deletions example/tango/tango0/handin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def get_positive_number() -> int:
return 1
6 changes: 6 additions & 0 deletions example/tango/tango0/handin_cheating1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def get_positive_number() -> int:
with open("grader.py", "r") as f:
for line in f.readlines():
print(line, end="")

return 0
9 changes: 9 additions & 0 deletions example/tango/tango0/handin_cheating2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import json
import time


def get_positive_number() -> int:
scores = {"scores": {"q1": 10, "q2": 9999, "q3": 9999}}
print(json.dumps(scores), flush=True)
time.sleep(200)
return 0
2 changes: 2 additions & 0 deletions example/tango/tango0/handin_incorrect1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def get_positive_number() -> int:
return 0
2 changes: 2 additions & 0 deletions example/tango/tango0/handin_incorrect2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def incorrect_function_name() -> int:
return 0
4 changes: 4 additions & 0 deletions example/tango/tango1/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
all:
tar -xf autograde.tar
chmod +x grader.sh
./grader.sh
Binary file added example/tango/tango1/autograde.tar
Binary file not shown.
3 changes: 3 additions & 0 deletions example/tango/tango1/create_grader.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

tar -cf autograde.tar grader.py grader.sh
98 changes: 98 additions & 0 deletions example/tango/tango1/grader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import json


def finished_grading():
print("\nGrader finished")
print(json.dumps({"scores": scores}))
exit(0)


print("Grader starting")

scores = {
"q1": 0,
"q2": 0,
"q3": 0,
}

negative_tests = {
-9999987654321: 1,
-99: 1,
-32: 1,
-31: 1,
-30: 1,
-29: 1,
-10: 1,
-3: 1,
-2: 1,
-1: 1
}

positive_odd_tests = {
1: 3,
3: 5,
5: 7,
7: 9,
9: 11,
11: 13,
13: 15,
15: 17,
101: 103,
1005: 1007,
}

positive_even_tests = {
2: 3,
4: 5,
6: 7,
8: 9,
10: 11,
100: 101,
1000: 1001,
12345678: 12345679,
2222: 2223,
3336: 3337,
}

try:
from handin import get_next_positive_odd_number
except ImportError:
print("Unable to import get_next_positive_odd_number from handin.py")
finished_grading()

print("\nTesting negative numbers")
for input_number, expected_result in negative_tests.items():
result = get_next_positive_odd_number(input_number)
if result == expected_result:
scores["q1"] += 1
print(f"get_next_positive_odd_number({input_number}) returned {result}, and that's correct! (+1)")
else:
print(
f"get_next_positive_odd_number({input_number}) incorrectly returned {result}, expected {expected_result}. (+0)")

print("\nTesting positive odd numbers")
for input_number, expected_result in positive_odd_tests.items():
result = get_next_positive_odd_number(input_number)
if result == expected_result:
scores["q2"] += 1
print(f"get_next_positive_odd_number({input_number}) returned {result}, and that's correct! (+1)")
else:
print(
f"get_next_positive_odd_number({input_number}) incorrectly returned {result}, expected {expected_result}. (+0)")

print("\nTesting positive even numbers")
for input_number, expected_result in positive_even_tests.items():
result = get_next_positive_odd_number(input_number)
if result == expected_result:
scores["q3"] += 1
print(f"get_next_positive_odd_number({input_number}) returned {result}, and that's correct! (+1)")
else:
print(
f"get_next_positive_odd_number({input_number}) incorrectly returned {result}, expected {expected_result}. (+0)")

if all([scores["q1"] == 10, scores["q2"] == 10, scores["q3"] == 10]):
print("\nGreat job!")
else:
print("\nTry again.")

finished_grading()
3 changes: 3 additions & 0 deletions example/tango/tango1/grader.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

python3 grader.py
10 changes: 10 additions & 0 deletions example/tango/tango1/handin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# This is an example of a correct solution.
# It can be helpful to test your grader with the correct solution in the same directory where the student's handin
# will end up. You don't need to include the correct solution in the grader archive.

def get_next_positive_odd_number(number: int) -> int:
# Given `number`, return the next positive odd number.
# If `number` is negative, return 1.
# If `number` is positive and odd, return `number` + 2.
# If `number` is positive and even, return `number` + 1.
return max(1, number + (number % 2) + 1)
8 changes: 8 additions & 0 deletions example/tango/tango1/handin_incorrect1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# This is an example of an incorrect solution.

def get_next_positive_odd_number(number: int) -> int:
# Given `number`, return the next positive odd number.
# If `number` is negative, return 1.
# If `number` is positive and odd, return `number` + 2.
# If `number` is positive and even, return `number` + 1.
return max(1, number + (number % 2))
8 changes: 8 additions & 0 deletions example/tango/tango1/handin_incorrect2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# This is an example of an incorrect solution.

def get_next_positive_odd_number(number: int) -> int:
# Given `number`, return the next positive odd number.
# If `number` is negative, return 1.
# If `number` is positive and odd, return `number` + 2.
# If `number` is positive and even, return `number` + 1.
return max(1, number + (number % 3) + 1)
8 changes: 8 additions & 0 deletions example/tango/tango1/handin_incorrect3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# This is an example of an incorrect solution.

def get_next_positive_odd_number(number: int) -> int:
# Given `number`, return the next positive odd number.
# If `number` is negative, return 1.
# If `number` is positive and odd, return `number` + 2.
# If `number` is positive and even, return `number` + 1.
return min(1, number + (number % 3) + 1)
8 changes: 8 additions & 0 deletions example/tango/tango1/handin_incorrect4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# This is an example of an incorrect solution because the function name is incorrect.

def incorrect_function_name(number: int) -> int:
# Given `number`, return the next positive odd number.
# If `number` is negative, return 1.
# If `number` is positive and odd, return `number` + 2.
# If `number` is positive and even, return `number` + 1.
return max(1, number + (number % 2) + 1)
3 changes: 3 additions & 0 deletions example/tango/tango2/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
all:
tar -xf autograde.tar
python3 grader.py
Binary file added example/tango/tango2/autograde.tar
Binary file not shown.
3 changes: 3 additions & 0 deletions example/tango/tango2/create_grader.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

tar -cf autograde.tar grader.py
14 changes: 14 additions & 0 deletions example/tango/tango2/grader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import json

scores = {
"q1": 10,
"q2": 10,
"q3": 10,
}

if __name__ == '__main__':

with open("settings.json", "r") as f:
print(f.readline())

print(json.dumps({"scores": scores}))
3 changes: 3 additions & 0 deletions example/tango/tango3/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
all:
tar -xf autograde.tar
python3 grader.py
Binary file added example/tango/tango3/autograde.tar
Binary file not shown.
3 changes: 3 additions & 0 deletions example/tango/tango3/create_grader.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

tar -cf autograde.tar grader.py
25 changes: 25 additions & 0 deletions example/tango/tango3/form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<div>
Type a word that starts with the character 'a':
<input type="text" name="q1"/>
</div>

<br>

<div>
Were you in lecture today?
<label>
<input name="q2" type="radio" value="Yes"/>
<span>Yes</span>
</label>
<label>
<input name="q2" type="radio" value="No"/>
<span>No</span>
</label>
</div>

<br>

<div>
What score would you like for this question?
<input type="text" name="q3"/>
</div>
Loading