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
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
piptools: ## install pinned version of pip-compile and pip-sync
pip install -r requirements/pip-tools.txt

upgrade: export CUSTOM_COMPILE_COMMAND=make upgrade
upgrade: piptools ## update the requirements/*.txt files with the latest packages satisfying requirements/*.in
pip-compile --upgrade -o requirements/pip-tools.txt requirements/pip-tools.in
pip-compile --upgrade -o requirements/base.txt requirements/base.in
1 change: 1 addition & 0 deletions codejailservice/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .app import app
119 changes: 119 additions & 0 deletions codejailservice/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import os
import json
import logging
import sys
import timeit

from codejail import jail_code

from copy import deepcopy
from flask import Flask, Response, jsonify, request
from logging.config import dictConfig


dictConfig({
'version': 1,
'formatters': {
'default': {
'format': '%(asctime)s %(levelname)s %(process)d ' '[%(name)s] %(filename)s:%(lineno)d - %(message)s',
}
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'stream': sys.stdout,
'formatter': 'default'
}
},
'root': {
'level': 'DEBUG',
'handlers': ['console']
}
})

app = Flask(__name__)
env_config = os.getenv("FLASK_APP_SETTINGS", "codejailservice.config.ProductionConfig")
app.config.from_object(env_config)

def configure_codejail(app):
code_jail_settings = app.config["CODE_JAIL"]
python_bin = code_jail_settings.get('python_bin')
if python_bin:
user = code_jail_settings['user']
jail_code.configure("python", python_bin, user=user)
limits = code_jail_settings.get('limits', {})
for name, value in limits.items():
jail_code.set_limit(
limit_name=name,
value=value,
)
limit_overrides = code_jail_settings.get('limit_overrides', {})
for context, overrides in limit_overrides.items():
for name, value in overrides.items():
jail_code.override_limit(
limit_name=name,
value=value,
limit_overrides_context=context,
)

configure_codejail(app)

from codejail.safe_exec import SafeExecException, json_safe
from codejail.safe_exec import not_safe_exec as codejail_not_safe_exec
from codejail.safe_exec import safe_exec as codejail_safe_exec

@app.route("/")
def index():
return Response("Edx Codejail Service", status=200)

@app.route("/health")
def health():
return Response("OK", status=200)

@app.post("/api/v0/code-exec")
def code_exec():
payload = json.loads(request.form["payload"])
globals_dict = deepcopy(payload["globals_dict"])

unsafely = payload["unsafely"]
if unsafely:
exec_fn = codejail_not_safe_exec
else:
exec_fn = codejail_safe_exec

try:
python_path=payload["python_path"]
if python_path:
extra_files=[(python_path[0], request.files[python_path[0]].read())]
else:
extra_files=[]
course_id = payload["limit_overrides_context"]
problem_id = payload["slug"]
app.logger.info("Running problem_id:%s jailed code for course_id:%s ...", problem_id, course_id)
start = timeit.default_timer()
exec_fn(
payload["code"],
globals_dict,
python_path=python_path,
extra_files=extra_files,
limit_overrides_context=course_id,
slug=problem_id,
)
end = timeit.default_timer()

except SafeExecException as e:
# Saving SafeExecException e in exception to be used later.
app.logger.error("Error found while executing jailed code.")
exception = e
emsg = str(e)
else:
app.logger.info("Jailed code was executed in %s seconds.", str(end-start))
exception = None
emsg = None

response = {
"globals_dict": globals_dict,
"emsg": emsg
}

return jsonify(response)
42 changes: 42 additions & 0 deletions codejailservice/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import os

class BaseConfig:
DEBUG = False
DEVELOPMENT = False
SECRET_KEY = os.getenv("SECRET_KEY", "this-is-the-default-key")

CODE_JAIL = {
'python_bin': '/sandbox/venv/bin/python',
# User to run as in the sandbox.
'user': 'sandbox',

# Configurable limits.
# Setting all of them to 0 to disable limits in containers.
'limits': {
#
'NPROC': 0,
# How many CPU seconds can jailed code use?
'CPU': 0,
# Limit the memory of the jailed process to something high but not
# infinite (512MiB in bytes)
'VMEM': 0,
# Time in seconds that the jailed process has to run.
'REALTIME': 0,
# Needs to be non-zero so that jailed code can use it as their temp directory.(10MiB in bytes)
'FSIZE': 10485760,
# Disable usage of proxy (force thread-safe)
'PROXY': 0,
},

# Overrides to default configurable 'limits' (above).
# Keys should be course run ids.
# Values should be dictionaries that look like 'limits'.
"limit_overrides": {},
}

class DevelopmentConfig(BaseConfig):
DEBUG = True
DEVELOPMENT = True

class ProductionConfig(BaseConfig):
pass
Empty file added codejailservice/tutor.py
Empty file.
7 changes: 7 additions & 0 deletions requirements/base.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

-c constraints.txt

Flask
requests

-e git+https://github.com/edx/codejail.git@3.1.3#egg=codejail==3.1.3
32 changes: 32 additions & 0 deletions requirements/base.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#
# This file is autogenerated by pip-compile
# To update, run:
#
# make upgrade
#
-e git+https://github.com/edx/codejail.git@3.1.3#egg=codejail==3.1.3
# via -r requirements/base.in
certifi==2021.5.30
# via requests
chardet==4.0.0
# via requests
click==8.0.1
# via flask
flask==2.0.1
# via -r requirements/base.in
idna==2.10
# via requests
itsdangerous==2.0.1
# via flask
jinja2==3.0.1
# via flask
markupsafe==2.0.1
# via jinja2
requests==2.25.1
# via -r requirements/base.in
six==1.16.0
# via codejail
urllib3==1.26.5
# via requests
werkzeug==2.0.1
# via flask
9 changes: 9 additions & 0 deletions requirements/constraints.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Version constraints for pip installation.
#
# This file doesn't install any packages. It specifies version constraints
# that will be applied if a package is needed.
#
# When pinning something here, please provide an explanation of why. Ideally,
# link to other information that will help people in the future to remove the
# pin when possible. Writing an issue against the offending project and
# linking to it here is good.
7 changes: 7 additions & 0 deletions requirements/pip-tools.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#
# This file is autogenerated by pip-compile
# To update, run:
#
# make upgrade
#
pip-tools
17 changes: 17 additions & 0 deletions requirements/pip-tools.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#
# This file is autogenerated by pip-compile
# To update, run:
#
# make upgrade
#
click==8.0.1
# via pip-tools
pep517==0.10.0
# via pip-tools
pip-tools==6.1.0
# via -r requirements/pip-tools.in
toml==0.10.2
# via pep517

# The following packages are considered to be unsafe in a requirements file:
# pip
2 changes: 2 additions & 0 deletions requirements/pip.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pip==20.2.3
setuptools==50.3.0
8 changes: 8 additions & 0 deletions wsgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import os
Copy link
Member

Choose a reason for hiding this comment

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

Is this running as a single process in the container? is it possible to use Gunicorn or awsgi? Or is it outside of this MVP?

Copy link
Contributor Author

@ericfab179 ericfab179 Jun 7, 2021

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

I've no problem with Gunicorn but I'm wondering if we should use uwsgi to be consequent with the other tutor services (for instance edxapp )

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was updated to use uwsgi in local and k8s.


from codejailservice import app

if __name__ == "__main__":
host = os.getenv("FLASK_CODEJAILSERVICE_HOST", "0.0.0.0")
port = os.getenv("FLASK_CODEJAILSERVICE_PORT", 8000)
app.run(host=host, port=port)