fix: Improve and fix codejail service integration

Address recommendations and fixes discussed in PR.
Use similar structure to notes app.
Fix errors detected in tests.
This commit is contained in:
Eric Herrera
2021-08-01 09:22:31 -05:00
parent 3a736eefa2
commit d672d309e8
4 changed files with 141 additions and 41 deletions

View File

@@ -0,0 +1,24 @@
"""
Exceptions related to safe exec.
"""
class CodejailServiceParseError(Exception):
"""
An exception that is raised whenever we have issues with data parsing.
"""
pass # lint-amnesty, pylint: disable=unnecessary-pass
class CodejailServiceStatusError(Exception):
"""
An exception that is raised whenever Codejail service response status is different to 200.
"""
pass # lint-amnesty, pylint: disable=unnecessary-pass
class CodejailServiceUnavailable(Exception):
"""
An exception that is raised whenever Codejail service is unavailable.
"""
pass # lint-amnesty, pylint: disable=unnecessary-pass

View File

@@ -0,0 +1,97 @@
"""
Helper methods related to safe exec.
"""
import requests
import json
import logging
from codejail.safe_exec import SafeExecException
from django.conf import settings
from django.utils.translation import ugettext as _
from edx_toggles.toggles import SettingDictToggle
from requests.exceptions import RequestException, HTTPError
from simplejson import JSONDecodeError
from .exceptions import CodejailServiceParseError, CodejailServiceStatusError, CodejailServiceUnavailable
log = logging.getLogger(__name__)
# .. toggle_name: ENABLE_CODEJAIL_REST_SERVICE
# .. toggle_implementation: DjangoSetting
# .. toggle_default: False
# .. toggle_description: Set this to True if you want to run Codejail code using
# a separate VM or container and communicate with edx-platform using REST API.
# .. toggle_use_cases: open_edx
# .. toggle_creation_date: 2021-08-19
# .. toggle_target_removal_date: None
# .. toggle_warnings:
# .. toggle_tickets:
ENABLE_CODEJAIL_REST_SERVICE = SettingDictToggle(
"FEATURES", "ENABLE_CODEJAIL_REST_SERVICE", default=False, module_name=__name__
)
def is_codejail_rest_service_enabled():
return ENABLE_CODEJAIL_REST_SERVICE.is_enabled()
def get_codejail_rest_service_endpoint():
return "".join([
settings.CODE_JAIL_REST_SERVICE_HOST,
"/api/v0/code-exec"])
def send_safe_exec_request(data, extra_files):
"""
Sends a request to a codejail api service forwarding required code and files.
Arguments:
data: Dict containing code and othe parameters
required for jailed code execution.
extra_files: python_lib.zip file containing extra files
required by the codejail execution.
Returns:
Response received from codejsail api service
"""
globals_dict = data["globals_dict"]
codejail_service_endpoint = get_codejail_rest_service_endpoint()
payload = json.dumps(data)
try:
response = requests.post(
codejail_service_endpoint,
files=extra_files,
data={'payload': payload},
timeout=(settings.CODE_JAIL_REST_SERVICE_CONNECT_TIMEOUT, settings.CODE_JAIL_REST_SERVICE_READ_TIMEOUT)
)
except RequestException:
log.error("Failed to connect to codejail api service: url=%s, params=%s",
codejail_service_endpoint, str(payload))
raise CodejailServiceUnavailable(_("Codejail API Service is unavailable. Please try again in a few minutes.")) # lint-amnesty, pylint: disable=raise-missing-from
try:
response.raise_for_status()
except HTTPError:
raise CodejailServiceStatusError(_("Codejail API Service invalid response.")) # lint-amnesty, pylint: disable=raise-missing-from
try:
response_json = response.json()
except JSONDecodeError:
log.error("Invalid JSON response received from codejail api service: Response_Content=%s", response.content)
raise CodejailServiceParseError(_("Invalid JSON response received from codejail api service.")) # lint-amnesty, pylint: disable=raise-missing-from
emsg = response_json.get("emsg")
exception = None
if emsg:
exception_msg = ". ".join([
emsg,
"For more information check Codejail Service logs."])
exception = SafeExecException(exception_msg)
globals_dict.update(response_json.get("globals_dict"))
return emsg, exception

View File

@@ -2,18 +2,16 @@
import hashlib
import requests
import json
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
from django.conf import settings
from edx_django_utils.monitoring import function_trace
import six
from six import text_type
from . import lazymod
from .helpers import is_codejail_rest_service_enabled, send_safe_exec_request
# Establish the Python environment for Capa.
# Capa assumes float-friendly division always.
@@ -146,40 +144,18 @@ def safe_exec(
# Create the complete code we'll run.
code_prolog = CODE_PROLOG % random_seed
if settings.FEATURES.get('ENABLE_CODEJAIL_REST_SERVICE', False):
try:
codejail_service_endpoint = "".join([
settings.CODE_JAIL_REST_SERVICE_HOST,
"/api/v0/code-exec"
])
data = {
"code": code_prolog + LAZY_IMPORTS + code,
"globals_dict": globals_dict,
"python_path": python_path,
"limit_overrides_context": limit_overrides_context,
"slug": slug,
"unsafely": unsafely
}
datajson = json.dumps(data)
response = requests.request(
"POST",
codejail_service_endpoint,
files=extra_files,
data={'payload': datajson}
)
response_json = response.json()
emsg = response_json["emsg"]
if emsg:
exception_msg = ". ".join([
emsg,
"For more information check Codejail Service logs."
])
exception = SafeExecException(emsg)
globals_dict.update(response_json["globals_dict"])
except Exception as e:
raise e
emsg = None
if is_codejail_rest_service_enabled():
data = {
"code": code_prolog + LAZY_IMPORTS + code,
"globals_dict": globals_dict,
"python_path": python_path,
"limit_overrides_context": limit_overrides_context,
"slug": slug,
"unsafely": unsafely
}
emsg, exception = send_safe_exec_request(data, extra_files)
else:
# Decide which code executor to use.
if unsafely:

View File

@@ -948,17 +948,17 @@ FEATURES = {
# .. toggle_target_removal_date: 2021-10-01
# .. toggle_tickets: 'https://openedx.atlassian.net/browse/MICROBA-1405'
'ENABLE_V2_CERT_DISPLAY_SETTINGS': False,
# .. toggle_name: ENABLE_CODEJAIL_REST_SERVICE
# .. toggle_implementation: DjangoSetting
# .. toggle_default: False
# .. toggle_description: Set this to True if you want to run Codejail code using
# a separate VM or container and communicate with edx-platform using REST API.
# .. toggle_use_cases: tutor
# .. toggle_use_cases: open_edx
# .. toggle_creation_date: 2021-08-19
# .. toggle_target_removal_date: None
# .. toggle_warnings:
# .. toggle_tickets:
# .. toggle_warnings:
# .. toggle_tickets:
'ENABLE_CODEJAIL_REST_SERVICE': False,
}
@@ -1643,6 +1643,9 @@ COURSES_WITH_UNSAFE_CODE = []
# Cojail REST service
CODE_JAIL_REST_SERVICE_HOST = 'http://127.0.0.1:8550'
CODE_JAIL_REST_SERVICE_CONNECT_TIMEOUT = 0.5 # time in seconds
CODE_JAIL_REST_SERVICE_READ_TIMEOUT = 3.5 # time in seconds
############################### DJANGO BUILT-INS ###############################
# Change DEBUG in your environment settings files, not here