Giter Site home page Giter Site logo

technion-iem-ds_lab's Introduction

Exercise submission checker

This project is a web server for students to upload, build and execute programming tasks in C++,Python, Java etc.

The server runs in a Docker container, on a linux host.

It is developed as a small scale alternative to CodeRunner .

Maturity: In development, used by faculty courses

Getting Started

TODO -- add content

For support, send mail to the repository maintainer

System Requirements

  • ubuntu 18.04
  • docker
  • ssh access (for management and file uploading)

Installing

Refer to intallation.md

Running tests of the server

TBD

#Contributing use pull requests in github

Directory structure

The source code is stored in thie repo, and the auxiary data (which compises the matchers, runners, test configurations, data files for each test) is stored in a different repo. This separation ease the maintance of both repos and prevents the need to rebuild the docker image for changes in homework configurations.

Data directory

The data directory can be anywhere in the host file system and must have the following structure:

{data-dir}
       data/   <---- each course has its own sub dirirectory, named as the course ID 
           94219/
              ex1/
                data files
              ex2/
                data files
       matchers/
       runners/
       hw_config.json

The path to the data directory is passed to the server in environment variable CHECKER_DATA_DIR as a full path.

storing the database

The database (job submissions) is stored in sqlite3 file in a directory that must be writable in the host machine.

storing the log output

The logger output is written to a directory in the host file system that must be writable.

All the above directories are mounted in the run_docker.sh script.

Instructions for the Tutor

As the tutor, you have to prepare:

  • code that will execute the program
  • code that verify the output is correct
  • input data (the input test vector)
  • output data (the output for the input for a correct solution)
    • optionally, another input and output tagged GOLDEN

These coding parts are called runner and matcher (aka comparator)

Choosing the runner and matcher is done by reading a configuration file (located at {data-dir}/hw_settings.json)

You can see the current content in host_address/admin/show_ex_config

Modifying/adding values is by uploading a new version of the file (in the admin page)

For example,

  • in ex 1, a C/C++ program, exact text match is needed
  • in ex 2, a Python program, there is a complicated scenario that requires regular expression.

The config file json will look like:

{
"94201":
 [ {
     "id": 1,
     "matcher" : "exact_match.py",
     "runner" :"check_cmake.sh",
     "timeout" : 5
   },
   {
     "id": 2,
     "matcher" : "tester_ex2.py",
     "runner" :"check_py.sh",
     "data_dir": "relative/path/to/DATA_DIR", <<< optional
     "timeout" : 20
     "allowed_extension": ["py, "zip"] <<< optional, can have more than one type
    }
 ]
}

If "allowed_extension" is empty of non-existent, any file extension can be uploaded.

Uploading data to the server

Use ssh and put the data files in {data-dir}/data/courseId
e.g. /mydata/data/94219/ref_hw_3_input /mydata/data/94219/ref_hw_3_output /mydata/data/94219/data_files_folder

It will be mapped into the server's file system (This is done in run_docker.sh)

Using the correct runner

Depending on the assignment, choose an existing runner or write a new one.
The runners are bash scripts that are run in a shell (for security) and accepts as arguments the filename of the tested code, input data file, reference output file and matcher file name.
The script return 0 for success.
NOTE: Comparison is done by running the matcher from within the runner script. This will be changed in a future version.

These runners are already implemented:

  • check_cmake.sh: run cmake, make and execute the executable found.
  • check_py.sh: run the file main.py in the supplied archive (or just this file if no archive is used)
  • check_java_file.sh: run the file Main.java in the supplied archive
  • check_sh.sh: run the file using bash
  • check_xv6.sh: copy repository of xv6, apply user's patch, compile and run.
    This needs several files described in the shell script.

Adding/Modifying matcher

All matchers are written in Python3.

Before writing a new one, check the existing scripts - maybe you can use one of them as a baseline.

  1. save the new tester.py in {data-dir}/matchers dir.
    The script must implement check(output_from_test_file_name,reference_file_name)
    and return True if the files are considered a match.
    For example def check(f1,ref): return True
    currently, you need to implement

     if __name__ == "__main__":
         good = check(sys.argv[1], sys.argv[2])
         exit(0 if good else ExitCode.COMPARE_FAILED) 
  2. Update the config file by uploading an updated version.
    The current config can be seen at http://your-host/admin/show_ex_config
    and uploading from the admin page at http://your-host/admin
    Normally there is no need even to restart the docker container since the matcher and runner are called in a new shell for every executed test.

Ranking the submissions

The matcher can attach a score to each submission. This score will be shown in the Leaderboard.
To add a score, add score=<an integer> to the standard output.
The server code will look for the last occurrence of this pattern and extract the score.

Rebuilding the Docker image

To rebuild the docker image and immediately run it:

Build a new Docker container, stop the current one, start the new one:

$ ssh myhost
(myhost) $ cd checker
(myhost) $ git pull
(myhost) $ cd scripts && ./again.sh

WARNING: if a Dockerfile depends on another, all dependencies MUST be built manually!
Moreover, I found that sometimes I must add --no-cache

For example, to rebuild the docker image of XV6, you must

   docker build -t python_base -f Dockerfile_py_base .
   docker build -t server_cpp -f Dockerfile_cpp .
   docker build -t server_xv6 -f Dockerfile_xv6 .

Therefore, I strongly recommend testing the code on the host (or PC) before creating and image.

Running the web application

The webapp runs as a Docker container.
First, build the container: (in the project root directory)

docker build . -t server:<current_tag>

then run it

cd scripts && ./run_docker.sh server

OR if you just want to build - stop - start fresh again:

cd scripts && ./again.sh

Reliability

I created a monitoring account that checks the server every couple minutes and send me email if the server is down.

There is known problem that occasionally the server hangs. In such a case, ssh to the host, and execute scripts/restart_container.sh #Security

  • The Docker container runs as user nobody, who has minimal privilages.
  • The submitted code is run in a subprocess with limitations on
    • execution time (for each exercise we set a timeout value)
    • network connectivity (not implemented yet)

Debugging

During development, it is easier to run only the python code without Docker:

cd ~/checker
source venv/bin/activate
python3 run.py

gunicorn

To run with gunicorn (one step closer to the real-world configuration):

cd ~/checker
# source venv/bin/activate # see note below
pip install -r requirements.txt
export CHECKER_LOG_DIR=.
export LIVY_PASS="the password"
export SPARK_CLUSTER_NAME=spark96224
gunicorn3 -b 0.0.0.0:8000 --workers 3 --timeout 40 serverpkg.server:app

Note: I tried running with flask installed only in venv, and failed.

Even adding -pythonpath /home/cnoam/checker/venv/lib/python3.10/site-packages/ did not help.

Setting number of workers

Each worker handles a request (sync), so the timeout must be longer than the maximum handling time. When getting a log it can be 30 seconds. One worker is probably dedicated to housekeeping, so to avoid blocking by lengthy op, at least 3 workers are needed, and in practice, should probably use 10 or more.

https://docs.gunicorn.org/en/stable/run.html: "This number should generally be between 2-4 workers per core in the server. Check the FAQ for ideas on tuning this parameter."

Working with Spark

When submitting a job (uploaded a python file), a BATCH is created in spark. This batch is converted to application ID after about 30 seconds (depending on the load?). To check the status as we know it locally (i.e., not directly in Spark), there is a link in the admin page.

If this table is garbaged, clean it with a link from the same page.

The JOBS table shown in the website only shows that the job was submitted successfully. In a better world, the job will get status updates such as submitted, starting, failed, finished.

Limiting jobs per student

Experience showed that students submit multipe versions of their code without deleting the previous runs, so the cluster is clogged.
Now there is a kind of resource manager:
- user must be in a whitelist to be eligible to submit. We use ID of one of each team to get a unique value.
- user is limited to 3 concurrent submissions. When a job is finished/dead/killed, the resource manage is updated (by polling every 1 minute).
- the yarn/spark flow is submit -> get batch ID -> get application ID in state 'accepted' -> state starting -> state running -> state dead/success
- the resource data must be persistent in case the docker container is restarted.
- the resource data must be multi process safe (both R and W) because the Flask server runs several processes.

API rate limiting

Calling /spark/logs/batchId=NN blocks the server for up to 30 sec depending on spark's responsiveness. To avoid blocking the server, I added Rate Limiting.

technion-iem-ds_lab's People

Contributors

cnoam avatar noam1023 avatar dependabot[bot] avatar kfirbernstein avatar

Stargazers

Johon Li Tuobang 李拓邦 avatar

Watchers

James Cloos avatar  avatar

technion-iem-ds_lab's Issues

slow response to any http request

the webserver, running in docker , gunicorn, in Azure VM is slow.

I suspect that all workers are busy.

also, gunicorn workers are terminated even though they don't seem to be doing anything!

replace sqlite3 as the backend db

when using gunicorn to have multiprocessing, performance drops terribly when accessing sqlite3.

I tested the perf using test_multi_dict.py and so time > 5.5 sec for a single test

server hangs

server does not respond to http requests.
Possibly because worker threads are busy

Increased num_workers from 2 to 4.

give restricted access to Spark UI

users should be able to see the spark UI in read only mode, accessing only parts of the site.

I thought about adding a forward proxy (started testing with privoxy), adding the authentication header, so client browser will not have to know the password, and limit to the URLs relevant to them.
BUT because the target website is https, the proxy does not know what is the path inside the domain.

  • maybe the proxy can be SSL TERMINATION POINT?

An alternative: (requires coding)
the Submitter web server will expose a few URLs, forwarding the requests to the target, and sending the response to client.
This means that many addresses have to be blindly handled (e.g. images in the page) , and rewriting links.


The simplest alternative: just give everybody full control, and ask them to be nice

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.