-
Notifications
You must be signed in to change notification settings - Fork 4
Codejail REST API service Flask - MVP #2
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
Changes from all commits
14ca277
95335b1
9b0d4c7
29d0b18
dfe1378
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| from .app import app |
| 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) | ||
| 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 |
| 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 |
| 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 |
| 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. |
| 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 |
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| pip==20.2.3 | ||
| setuptools==50.3.0 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import os | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In development it runs a single process in the container https://github.com/eduNEXT/tutor-contrib-codejail/blob/b8857457f25f5481a5fdc56d7742f5f6b2420fb9/tutorcodejail/patches/local-docker-compose-dev-services#L2 but in local and k8s it should run using gunicorn
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 )
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
Uh oh!
There was an error while loading. Please reload this page.