Merge branch 'dev'

* dev: (195 commits)
  修改刷新时间
  增加题目页面倒计时的 js
  增加比赛倒计时的 api
  增加判题帮助
  修复判断验证码是否存在的时候,用户不存在导致的报错
  记录用户输出 md5
  增加反馈链接
  add docker start tool
  rename mq
  不用的语言使用不同的系统调用过滤
  update java runtime security policy
  增加 clone 地址范围限制,否则 Java 无法运行
  fix mq run path error
  修复语言判断 bug
  add kill proc
  修改codeMirror中代码的样式
  修复数据库已有用户problems_statu字段为空造成的问题
  fix typo
  add c/c++ sys call filter
  --isolate-process true
  ...

Conflicts:
	judge/judger/settings.py
	judge/judger_controller/settings.py
	template/src/oj/contest/contest_problem.html
	template/src/oj/contest/submissions_list.html
This commit is contained in:
virusdefender
2015-09-21 13:06:12 +08:00
141 changed files with 6085 additions and 3652 deletions

View File

@@ -1,19 +0,0 @@
FROM ubuntu:14.04
MAINTAINER virusdefender<qduliyang@outlook.com>
RUN mkdir /var/install/
WORKDIR /var/install/
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update
RUN apt-get -y install software-properties-common python-software-properties
RUN add-apt-repository -y ppa:webupd8team/java
RUN apt-get update
RUN apt-get -y install python gcc g++ rake pkg-config git make autoconf automake libtool python-pip python2.7-mysqldb
RUN echo debconf shared/accepted-oracle-license-v1-1 select true | sudo debconf-set-selections
RUN echo debconf shared/accepted-oracle-license-v1-1 seen true | sudo debconf-set-selections
RUN apt-get install -y oracle-java7-installer
RUN apt-get -y install libseccomp-dev
RUN git clone https://github.com/quark-zju/lrun.git
RUN cd lrun && make install
RUN mkdir -p /var/judger/run/ && mkdir /var/judger/test_case/ && mkdir /var/judger/code/
RUN chmod -R 777 /var/judger/run/
WORKDIR /var/judger/code/

View File

@@ -1,3 +0,0 @@
/usr/bin/docker run -t -i --privileged -v /var/test_case/:/var/judger/test_case/ -v /var/code/:/var/judger/code/ judger /bin/bash
python judge/judger/run.py -solution_id 1 -max_cpu_time 1 -max_memory 1 -test_case_id 1

View File

@@ -1,4 +1,5 @@
# coding=utf-8
import os
import json
import commands
import hashlib
@@ -7,9 +8,9 @@ from multiprocessing import Pool
from settings import max_running_number, lrun_gid, lrun_uid, judger_workspace
from language import languages
from result import result
from compiler import compile_
from judge_exceptions import JudgeClientError, CompileError
from judge_exceptions import JudgeClientError
from utils import parse_lrun_output
from logger import logger
# 下面这个函数作为代理访问实例变量否则Python2会报错是Python2的已知问题
@@ -61,6 +62,8 @@ class JudgeClient(object):
" --max-real-time " + str(self._max_real_time / 1000.0 * 2) + \
" --max-memory " + str(self._max_memory * 1000 * 1000) + \
" --network false" + \
" --syscalls '" + self._language["syscalls"] + "'" + \
" --max-nprocess 20" + \
" --uid " + str(lrun_uid) + \
" --gid " + str(lrun_gid)
@@ -82,6 +85,8 @@ class JudgeClient(object):
# 倒序找到MEMORY的位置
output_start = output.rfind("MEMORY")
if output_start == -1:
logger.error("Lrun result parse error")
logger.error(output)
raise JudgeClientError("Lrun result parse error")
# 如果不是0说明lrun输出前面有输出也就是程序的stderr有内容
if output_start != 0:
@@ -92,26 +97,35 @@ class JudgeClient(object):
return error, parse_lrun_output(output)
def _compare_output(self, test_case_id):
test_case_md5 = self._test_case_info["test_cases"][str(test_case_id)]["output_md5"]
test_case_config = self._test_case_info["test_cases"][str(test_case_id)]
output_path = judger_workspace + str(test_case_id) + ".out"
try:
f = open(output_path, "rb")
except IOError:
# 文件不存在等引发的异常 返回结果错误
return False
return "", False
# 计算输出文件的md5 和之前测试用例文件的md5进行比较
md5 = hashlib.md5()
while True:
data = f.read(2 ** 8)
if not data:
break
md5.update(data)
if "striped_output_md5" not in test_case_config:
# 计算输出文件的md5 和之前测试用例文件的md5进行比较
# 兼容之前没有striped_output_md5的测试用例
# 现在比较的是完整的文件
md5 = hashlib.md5()
while True:
data = f.read(2 ** 8)
if not data:
break
md5.update(data)
output_md5 = md5.hexdigest()
# 对比文件是否一致
# todo 去除最后的空行
return md5.hexdigest() == test_case_md5
return output_md5, output_md5 == test_case_config["output_md5"]
else:
# 这时候需要去除用户输出最后的空格和换行 再去比较md5
md5 = hashlib.md5()
# 比较和返回去除空格后的md5比较结果
md5.update(f.read().rstrip())
output_md5 = md5.hexdigest()
return output_md5, output_md5 == test_case_config["striped_output_md5"]
def _judge_one(self, test_case_id):
# 运行lrun程序 接收返回值
@@ -123,26 +137,30 @@ class JudgeClient(object):
run_result["test_case_id"] = test_case_id
# 如果返回值非0 或者信号量不是0 或者程序的stderr有输出 代表非正常结束
if run_result["exit_code"] or run_result["term_sig"] or run_result["siginaled"] or error:
run_result["result"] = result["runtime_error"]
return run_result
# 代表内存或者时间超过限制了
# 代表内存或者时间超过限制了 程序被终止掉 要在runtime error 之前判断
if run_result["exceed"]:
if run_result["exceed"] == "memory":
run_result["result"] = result["memory_limit_exceeded"]
elif run_result["exceed"] in ["cpu_time", "real_time"]:
run_result["result"] = result["time_limit_exceeded"]
else:
logger.error("Error exceeded type: " + run_result["exceed"])
logger.error(output)
raise JudgeClientError("Error exceeded type: " + run_result["exceed"])
return run_result
# 如果返回值非0 或者信号量不是0 或者程序的stderr有输出 代表非正常结束
if run_result["exit_code"] or run_result["term_sig"] or run_result["siginaled"] or error:
run_result["result"] = result["runtime_error"]
return run_result
# 下面就是代码正常运行了 需要判断代码的输出是否正确
if self._compare_output(test_case_id):
output_md5, r = self._compare_output(test_case_id)
if r:
run_result["result"] = result["accepted"]
else:
run_result["result"] = result["wrong_answer"]
run_result["output_md5"] = output_md5
return run_result
@@ -160,8 +178,8 @@ class JudgeClient(object):
try:
results.append(item.get())
except Exception as e:
# todo logging
print e
logger.error("system error")
logger.error(e)
results.append({"result": result["system_error"]})
return results

View File

@@ -4,6 +4,7 @@ import commands
from settings import lrun_uid, lrun_gid
from judge_exceptions import CompileError, JudgeClientError
from utils import parse_lrun_output
from logger import logger
def compile_(language_item, src_path, exe_path):
@@ -22,14 +23,20 @@ def compile_(language_item, src_path, exe_path):
output_start = output.rfind("MEMORY")
if output_start == -1:
logger.error("Compiler error")
logger.error(output)
raise JudgeClientError("Error running compiler in lrun")
# 返回值不为0 或者 stderrlrun的输出之前有东西
if status or output_start:
# 返回值不为 0 或者 stderrlrun 的输出之前有 erro r字符串
# 判断 error 字符串的原因是链接的时候可能会有一些不推荐使用的函数的的警告,
# 但是 -w 参数并不能关闭链接时的警告
if status or "error" in output[0:output_start]:
raise CompileError(output[0:output_start])
parse_result = parse_lrun_output(output)
parse_result = parse_lrun_output(output[output_start:])
if parse_result["exit_code"] or parse_result["term_sig"] or parse_result["siginaled"] or parse_result["exceed"]:
logger.error("Compiler error")
logger.error(output)
raise CompileError("Compile error")
return exe_path

View File

@@ -6,6 +6,7 @@ languages = {
"name": "c",
"src_name": "main.c",
"code": 1,
"syscalls": "!execve:k,flock:k,ptrace:k,sync:k,fdatasync:k,fsync:k,msync,sync_file_range:k,syncfs:k,unshare:k,setns:k,clone:k,query_module:k,sysinfo:k,syslog:k,sysfs:k",
"compile_command": "gcc -DONLINE_JUDGE -O2 -w -std=c99 {src_path} -lm -o {exe_path}main",
"execute_command": "{exe_path}main"
},
@@ -13,6 +14,7 @@ languages = {
"name": "cpp",
"src_name": "main.cpp",
"code": 2,
"syscalls": "!execve:k,flock:k,ptrace:k,sync:k,fdatasync:k,fsync:k,msync,sync_file_range:k,syncfs:k,unshare:k,setns:k,clone:k,query_module:k,sysinfo:k,syslog:k,sysfs:k",
"compile_command": "g++ -DONLINE_JUDGE -O2 -w -std=c++11 {src_path} -lm -o {exe_path}main",
"execute_command": "{exe_path}main"
},
@@ -20,8 +22,9 @@ languages = {
"name": "java",
"src_name": "Main.java",
"code": 3,
"syscalls": "!execve:k,flock:k,ptrace:k,sync:k,fdatasync:k,fsync:k,msync,sync_file_range:k,syncfs:k,unshare:k,setns:k,clone[a&268435456==268435456]:k,query_module:k,sysinfo:k,syslog:k,sysfs:k",
"compile_command": "javac {src_path} -d {exe_path}",
"execute_command": "java -cp {exe_path} Main"
"execute_command": "java -cp {exe_path} -Djava.security.manager -Djava.security.policy==policy Main"
}
}

8
judge/judger/logger.py Normal file
View File

@@ -0,0 +1,8 @@
# coding=utf-8
import logging
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s [%(threadName)s:%(thread)d] [%(name)s:%(lineno)d] [%(module)s:%(funcName)s] [%(levelname)s]- %(message)s',
filename='log/judge.log')
logger = logging

View File

@@ -7,9 +7,8 @@ from client import JudgeClient
from language import languages
from compiler import compile_
from result import result
from settings import judger_workspace
from settings import submission_db
from settings import judger_workspace, submission_db
from logger import logger
# 简单的解析命令行参数
@@ -60,7 +59,6 @@ except Exception as e:
conn.commit()
exit()
print "Compile successfully"
# 运行
try:
client = JudgeClient(language_code=language_code,
@@ -80,16 +78,13 @@ try:
judge_result["accepted_answer_time"] = l[-1]["cpu_time"]
except Exception as e:
print e
logger.error(e)
conn = db_conn()
cur = conn.cursor()
cur.execute("update submission set result=%s, info=%s where id=%s", (result["system_error"], str(e), submission_id))
conn.commit()
exit()
print "Run successfully"
print judge_result
conn = db_conn()
cur = conn.cursor()
cur.execute("update submission set result=%s, info=%s, accepted_answer_time=%s where id=%s",

View File

@@ -1,4 +1,5 @@
# coding=utf-8
import os
# 单个判题端最多同时运行的程序个数因为判题端会同时运行多组测试数据比如一共有5组测试数据
# 如果MAX_RUNNING_NUMBER大于等于5那么这5组数据就会同时进行评测然后返回结果。
# 如果MAX_RUNNING_NUMBER小于5为3那么就会同时运行前三组测试数据然后再运行后两组数据
@@ -14,12 +15,10 @@ lrun_gid = 1002
# judger工作目录
judger_workspace = "/var/judger/"
# 这个是在docker 中访问数据库 ip 不一定和web服务器还有celery的一样
submission_db = {
"host": "10.172.22.50",#"192.168.42.1",
"host": os.environ.get("MYSQL_PORT_3306_TCP_ADDR", "127.0.0.1"),
"port": 3306,
"db": "oj_submission",
"user": "root",
"password": "mypwd"
"password": os.environ.get("MYSQL_ENV_MYSQL_ROOT_PASSWORD", "root")
}

View File

@@ -1,7 +1,13 @@
# coding=utf-8
"""
注意:
此文件包含 celery 的部分配置,但是 celery 并不是运行在docker 中的,所以本配置文件中的 redis和 MySQL 的地址就应该是
运行 redis 和 MySQL 的 docker 容器的地址了。怎么获取这个地址见帮助文档。测试用例的路径和源代码路径同理。
"""
import os
# 这个redis 是 celery 使用的,包括存储队列信息还有部分统计信息
redis_config = {
"host": "127.0.0.1",
"host": os.environ.get("REDIS_PORT_6379_TCP_ADDR"),
"port": 6379,
"db": 0
}
@@ -9,8 +15,7 @@ redis_config = {
# 判题的 docker 容器的配置参数
docker_config = {
"image_name": " 819d3da18dc1",
"image_name": "judger",
"docker_path": "docker",
"shell": True
}
@@ -19,12 +24,14 @@ docker_config = {
# 测试用例的路径,是主机上的实际路径
test_case_dir = "/root/test_case/"
# 源代码路径,也就是 manage.py 所在的实际路径
source_code_dir = "/var/mnt/source/OnlineJudge/"
source_code_dir = "/root/qduoj/"
# 日志文件夹路径
log_dir = "/root/log/"
# 存储提交信息的数据库,是 celery 使用的,与 oj.settings/local_settings 等区分,那是 web 服务器访问的地址
submission_db = {
"host": "127.0.0.1",
"host": os.environ.get("submission_db_host"),
"port": 3306,
"db": "oj_submission",
"user": "root",

View File

@@ -5,32 +5,34 @@ import MySQLdb
import subprocess
from ..judger.result import result
from ..judger_controller.celery import app
from settings import docker_config, source_code_dir, test_case_dir, submission_db, redis_config
from settings import docker_config, source_code_dir, test_case_dir, log_dir, submission_db, redis_config
@app.task
def judge(submission_id, time_limit, memory_limit, test_case_id):
try:
command = "%s run -t -i --privileged --rm=true " \
command = "%s run --privileged --rm " \
"--link mysql " \
"-v %s:/var/judger/test_case/ " \
"-v %s:/var/judger/code/ " \
"-v %s:/var/judger/code/log/ " \
"%s " \
"python judge/judger/run.py " \
"--solution_id %s --time_limit %s --memory_limit %s --test_case_id %s" % \
(docker_config["docker_path"],
test_case_dir,
source_code_dir,
log_dir,
docker_config["image_name"],
submission_id, str(time_limit), str(memory_limit), test_case_id)
subprocess.call(command, shell=docker_config["shell"])
except Exception as e:
print e
conn = MySQLdb.connect(db=submission_db["db"],
user=submission_db["user"],
passwd=submission_db["password"],
host=submission_db["host"],
port=submission_db["port"],
character="utf8")
charset="utf8")
cur = conn.cursor()
cur.execute("update submission set result=%s, info=%s where id=%s",

View File

@@ -1,12 +0,0 @@
#include <stdio.h>
int main()
{
int a = 0;
int i = 0;
for(i = 0; i < 9999999999;i++)
{
a += i;
}
printf("%d", a);
return 0;
}

View File

@@ -1,6 +0,0 @@
#include <stdio.h>
#include <unistd.h>
int main()
{
}

View File

@@ -1,8 +0,0 @@
# include <stdio.h>
int main()
{
int a, b;
scanf("%d %d", &a, &b);
printf("%d", a + b);
return 0;
}