2018-12-12 06:27:40 +00:00
|
|
|
import hashlib
|
2016-09-02 12:04:29 +00:00
|
|
|
import json
|
|
|
|
import os
|
2016-09-11 02:35:08 +00:00
|
|
|
import shutil
|
2016-10-07 05:32:30 +00:00
|
|
|
import uuid
|
2016-09-02 12:04:29 +00:00
|
|
|
|
2018-03-18 00:19:44 +00:00
|
|
|
from flask import Flask, request, Response
|
2016-09-02 12:04:29 +00:00
|
|
|
|
|
|
|
from compiler import Compiler
|
2018-12-12 06:27:40 +00:00
|
|
|
from config import (JUDGER_WORKSPACE_BASE, SPJ_SRC_DIR, SPJ_EXE_DIR, COMPILER_USER_UID, SPJ_USER_UID,
|
|
|
|
RUN_USER_UID, RUN_GROUP_GID, TEST_CASE_DIR)
|
2018-03-18 00:19:44 +00:00
|
|
|
from exception import TokenVerificationFailed, CompileError, SPJCompileError, JudgeClientError
|
2016-09-28 12:43:46 +00:00
|
|
|
from judge_client import JudgeClient
|
2019-03-13 07:48:14 +00:00
|
|
|
from utils import server_info, logger, token, ProblemIOMode
|
2016-10-03 08:13:46 +00:00
|
|
|
|
2018-03-18 00:19:44 +00:00
|
|
|
app = Flask(__name__)
|
2016-09-28 12:43:46 +00:00
|
|
|
DEBUG = os.environ.get("judger_debug") == "1"
|
2018-03-18 00:19:44 +00:00
|
|
|
app.debug = DEBUG
|
2016-09-28 12:43:46 +00:00
|
|
|
|
2016-09-02 12:04:29 +00:00
|
|
|
|
|
|
|
class InitSubmissionEnv(object):
|
2018-12-12 06:27:40 +00:00
|
|
|
def __init__(self, judger_workspace, submission_id, init_test_case_dir=False):
|
|
|
|
self.work_dir = os.path.join(judger_workspace, submission_id)
|
|
|
|
self.init_test_case_dir = init_test_case_dir
|
|
|
|
if init_test_case_dir:
|
|
|
|
self.test_case_dir = os.path.join(self.work_dir, "submission_" + submission_id)
|
|
|
|
else:
|
|
|
|
self.test_case_dir = None
|
2016-09-02 12:04:29 +00:00
|
|
|
|
|
|
|
def __enter__(self):
|
2016-10-01 18:15:49 +00:00
|
|
|
try:
|
2018-12-12 06:27:40 +00:00
|
|
|
os.mkdir(self.work_dir)
|
|
|
|
if self.init_test_case_dir:
|
|
|
|
os.mkdir(self.test_case_dir)
|
|
|
|
os.chown(self.work_dir, COMPILER_USER_UID, RUN_GROUP_GID)
|
|
|
|
os.chmod(self.work_dir, 0o711)
|
2016-10-01 18:15:49 +00:00
|
|
|
except Exception as e:
|
2016-10-04 05:24:50 +00:00
|
|
|
logger.exception(e)
|
2016-10-01 18:15:49 +00:00
|
|
|
raise JudgeClientError("failed to create runtime dir")
|
2018-12-12 06:27:40 +00:00
|
|
|
return self.work_dir, self.test_case_dir
|
2016-09-02 12:04:29 +00:00
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
2016-09-28 12:43:46 +00:00
|
|
|
if not DEBUG:
|
2016-10-01 18:15:49 +00:00
|
|
|
try:
|
2018-12-12 06:27:40 +00:00
|
|
|
shutil.rmtree(self.work_dir)
|
2016-10-01 18:15:49 +00:00
|
|
|
except Exception as e:
|
2016-10-04 05:24:50 +00:00
|
|
|
logger.exception(e)
|
2016-10-01 18:15:49 +00:00
|
|
|
raise JudgeClientError("failed to clean runtime dir")
|
2016-09-02 12:04:29 +00:00
|
|
|
|
|
|
|
|
2018-03-18 00:19:44 +00:00
|
|
|
class JudgeServer:
|
|
|
|
@classmethod
|
|
|
|
def ping(cls):
|
2016-10-03 08:13:46 +00:00
|
|
|
data = server_info()
|
|
|
|
data["action"] = "pong"
|
|
|
|
return data
|
2016-09-02 12:04:29 +00:00
|
|
|
|
2018-03-18 00:19:44 +00:00
|
|
|
@classmethod
|
2018-12-12 06:27:40 +00:00
|
|
|
def judge(cls, language_config, src, max_cpu_time, max_memory, test_case_id=None, test_case=None,
|
2019-03-13 07:48:14 +00:00
|
|
|
spj_version=None, spj_config=None, spj_compile_config=None, spj_src=None, output=False,
|
|
|
|
io_mode=None):
|
|
|
|
if not io_mode:
|
|
|
|
io_mode = {"io_mode": ProblemIOMode.standard}
|
|
|
|
|
2018-12-12 06:27:40 +00:00
|
|
|
if not (test_case or test_case_id) or (test_case and test_case_id):
|
|
|
|
raise JudgeClientError("invalid parameter")
|
2016-09-02 12:04:29 +00:00
|
|
|
# init
|
2016-10-09 12:05:04 +00:00
|
|
|
compile_config = language_config.get("compile")
|
|
|
|
run_config = language_config["run"]
|
2018-03-18 00:19:44 +00:00
|
|
|
submission_id = uuid.uuid4().hex
|
2016-09-02 12:04:29 +00:00
|
|
|
|
2018-12-12 06:27:40 +00:00
|
|
|
is_spj = spj_version and spj_config
|
|
|
|
|
|
|
|
if is_spj:
|
2017-11-25 11:27:08 +00:00
|
|
|
spj_exe_path = os.path.join(SPJ_EXE_DIR, spj_config["exe_name"].format(spj_version=spj_version))
|
|
|
|
# spj src has not been compiled
|
|
|
|
if not os.path.isfile(spj_exe_path):
|
|
|
|
logger.warning("%s does not exists, spj src will be recompiled")
|
2018-03-18 00:19:44 +00:00
|
|
|
cls.compile_spj(spj_version=spj_version, src=spj_src,
|
|
|
|
spj_compile_config=spj_compile_config)
|
2016-10-09 14:16:30 +00:00
|
|
|
|
2018-12-12 06:27:40 +00:00
|
|
|
init_test_case_dir = bool(test_case)
|
|
|
|
with InitSubmissionEnv(JUDGER_WORKSPACE_BASE, submission_id=str(submission_id), init_test_case_dir=init_test_case_dir) as dirs:
|
|
|
|
submission_dir, test_case_dir = dirs
|
|
|
|
test_case_dir = test_case_dir or os.path.join(TEST_CASE_DIR, test_case_id)
|
|
|
|
|
2016-10-09 12:05:04 +00:00
|
|
|
if compile_config:
|
|
|
|
src_path = os.path.join(submission_dir, compile_config["src_name"])
|
|
|
|
|
|
|
|
# write source code into file
|
2018-03-25 23:07:36 +00:00
|
|
|
with open(src_path, "w", encoding="utf-8") as f:
|
2018-03-18 00:19:44 +00:00
|
|
|
f.write(src)
|
2018-10-14 09:26:37 +00:00
|
|
|
os.chown(src_path, COMPILER_USER_UID, 0)
|
|
|
|
os.chmod(src_path, 0o400)
|
2016-10-09 12:05:04 +00:00
|
|
|
|
|
|
|
# compile source code, return exe file path
|
|
|
|
exe_path = Compiler().compile(compile_config=compile_config,
|
|
|
|
src_path=src_path,
|
|
|
|
output_dir=submission_dir)
|
2018-10-18 02:33:05 +00:00
|
|
|
try:
|
|
|
|
# Java exe_path is SOME_PATH/Main, but the real path is SOME_PATH/Main.class
|
|
|
|
# We ignore it temporarily
|
|
|
|
os.chown(exe_path, RUN_USER_UID, 0)
|
|
|
|
os.chmod(exe_path, 0o500)
|
|
|
|
except Exception:
|
|
|
|
pass
|
2016-10-09 12:05:04 +00:00
|
|
|
else:
|
2016-10-09 14:16:30 +00:00
|
|
|
exe_path = os.path.join(submission_dir, run_config["exe_name"])
|
2018-03-25 23:07:36 +00:00
|
|
|
with open(exe_path, "w", encoding="utf-8") as f:
|
2018-03-18 00:19:44 +00:00
|
|
|
f.write(src)
|
2016-09-02 12:04:29 +00:00
|
|
|
|
2018-12-12 06:27:40 +00:00
|
|
|
if init_test_case_dir:
|
|
|
|
info = {"test_case_number": len(test_case), "spj": is_spj, "test_cases": {}}
|
|
|
|
# write test case
|
|
|
|
for index, item in enumerate(test_case):
|
|
|
|
index += 1
|
|
|
|
item_info = {}
|
|
|
|
|
|
|
|
input_name = str(index) + ".in"
|
|
|
|
item_info["input_name"] = input_name
|
|
|
|
input_data = item["input"].encode("utf-8")
|
|
|
|
item_info["input_size"] = len(input_data)
|
|
|
|
|
|
|
|
with open(os.path.join(test_case_dir, input_name), "wb") as f:
|
|
|
|
f.write(input_data)
|
|
|
|
if not is_spj:
|
|
|
|
output_name = str(index) + ".out"
|
|
|
|
item_info["output_name"] = output_name
|
|
|
|
output_data = item["output"].encode("utf-8")
|
|
|
|
item_info["output_md5"] = hashlib.md5(output_data).hexdigest()
|
|
|
|
item_info["output_size"] = len(output_data)
|
|
|
|
item_info["stripped_output_md5"] = hashlib.md5(output_data.rstrip()).hexdigest()
|
|
|
|
|
|
|
|
with open(os.path.join(test_case_dir, output_name), "wb") as f:
|
|
|
|
f.write(output_data)
|
|
|
|
info["test_cases"][index] = item_info
|
|
|
|
with open(os.path.join(test_case_dir, "info"), "w") as f:
|
|
|
|
json.dump(info, f)
|
|
|
|
|
2016-09-11 02:35:08 +00:00
|
|
|
judge_client = JudgeClient(run_config=language_config["run"],
|
|
|
|
exe_path=exe_path,
|
|
|
|
max_cpu_time=max_cpu_time,
|
|
|
|
max_memory=max_memory,
|
2018-12-12 06:27:40 +00:00
|
|
|
test_case_dir=test_case_dir,
|
2016-10-01 18:05:16 +00:00
|
|
|
submission_dir=submission_dir,
|
2016-10-06 03:43:49 +00:00
|
|
|
spj_version=spj_version,
|
2016-10-13 13:33:27 +00:00
|
|
|
spj_config=spj_config,
|
2019-03-13 07:48:14 +00:00
|
|
|
output=output,
|
|
|
|
io_mode=io_mode)
|
2016-09-27 15:30:52 +00:00
|
|
|
run_result = judge_client.run()
|
2016-10-22 13:22:20 +00:00
|
|
|
|
2016-09-27 15:30:52 +00:00
|
|
|
return run_result
|
2016-09-02 12:04:29 +00:00
|
|
|
|
2018-03-18 00:19:44 +00:00
|
|
|
@classmethod
|
|
|
|
def compile_spj(cls, spj_version, src, spj_compile_config):
|
2016-09-28 13:58:00 +00:00
|
|
|
spj_compile_config["src_name"] = spj_compile_config["src_name"].format(spj_version=spj_version)
|
|
|
|
spj_compile_config["exe_name"] = spj_compile_config["exe_name"].format(spj_version=spj_version)
|
2016-09-28 12:43:46 +00:00
|
|
|
|
2016-10-09 14:16:30 +00:00
|
|
|
spj_src_path = os.path.join(SPJ_SRC_DIR, spj_compile_config["src_name"])
|
2016-09-28 12:43:46 +00:00
|
|
|
|
|
|
|
# if spj source code not found, then write it into file
|
|
|
|
if not os.path.exists(spj_src_path):
|
2018-03-25 23:07:36 +00:00
|
|
|
with open(spj_src_path, "w", encoding="utf-8") as f:
|
2018-03-18 00:19:44 +00:00
|
|
|
f.write(src)
|
2018-10-14 09:26:37 +00:00
|
|
|
os.chown(spj_src_path, COMPILER_USER_UID, 0)
|
|
|
|
os.chmod(spj_src_path, 0o400)
|
2018-08-10 01:33:29 +00:00
|
|
|
|
2016-09-28 12:43:46 +00:00
|
|
|
try:
|
2018-08-10 01:33:29 +00:00
|
|
|
exe_path = Compiler().compile(compile_config=spj_compile_config,
|
|
|
|
src_path=spj_src_path,
|
|
|
|
output_dir=SPJ_EXE_DIR)
|
2018-10-14 09:26:37 +00:00
|
|
|
os.chown(exe_path, SPJ_USER_UID, 0)
|
|
|
|
os.chmod(exe_path, 0o500)
|
2016-09-28 12:43:46 +00:00
|
|
|
# turn common CompileError into SPJCompileError
|
|
|
|
except CompileError as e:
|
|
|
|
raise SPJCompileError(e.message)
|
|
|
|
return "success"
|
|
|
|
|
2018-03-18 00:19:44 +00:00
|
|
|
|
|
|
|
@app.route('/', defaults={'path': ''})
|
|
|
|
@app.route('/<path:path>', methods=["POST"])
|
|
|
|
def server(path):
|
|
|
|
if path in ("judge", "ping", "compile_spj"):
|
|
|
|
_token = request.headers.get("X-Judge-Server-Token")
|
2016-09-27 15:30:52 +00:00
|
|
|
try:
|
2016-10-27 10:38:04 +00:00
|
|
|
if _token != token:
|
2016-09-29 14:23:08 +00:00
|
|
|
raise TokenVerificationFailed("invalid token")
|
2018-03-18 00:19:44 +00:00
|
|
|
try:
|
|
|
|
data = request.json
|
|
|
|
except Exception:
|
2016-09-29 14:23:08 +00:00
|
|
|
data = {}
|
2018-03-18 00:19:44 +00:00
|
|
|
ret = {"err": None, "data": getattr(JudgeServer, path)(**data)}
|
2016-10-04 05:03:54 +00:00
|
|
|
except (CompileError, TokenVerificationFailed, SPJCompileError, JudgeClientError) as e:
|
2016-10-04 05:24:50 +00:00
|
|
|
logger.exception(e)
|
2018-03-18 00:19:44 +00:00
|
|
|
ret = {"err": e.__class__.__name__, "data": e.message}
|
2016-10-04 05:03:54 +00:00
|
|
|
except Exception as e:
|
2016-10-04 05:24:50 +00:00
|
|
|
logger.exception(e)
|
2018-03-18 00:19:44 +00:00
|
|
|
ret = {"err": "JudgeClientError", "data": e.__class__.__name__ + " :" + str(e)}
|
|
|
|
else:
|
|
|
|
ret = {"err": "InvalidRequest", "data": "404"}
|
|
|
|
return Response(json.dumps(ret), mimetype='application/json')
|
2016-09-02 12:04:29 +00:00
|
|
|
|
2016-09-28 05:38:12 +00:00
|
|
|
|
2016-10-01 18:15:49 +00:00
|
|
|
if DEBUG:
|
2016-10-04 05:24:50 +00:00
|
|
|
logger.info("DEBUG=ON")
|
2016-10-01 18:15:49 +00:00
|
|
|
|
2018-03-18 00:19:44 +00:00
|
|
|
# gunicorn -w 4 -b 0.0.0.0:8080 server:app
|
2016-09-27 15:30:52 +00:00
|
|
|
if __name__ == "__main__":
|
2018-03-18 00:19:44 +00:00
|
|
|
app.run(debug=DEBUG)
|