Browsed by
月份:2016年4月

Gitlab DroneCI 配置记录

Gitlab DroneCI 配置记录

OJ的代码越来越多了, 功能也越来越多= = 也就导致bug越来越多, 测试的必要性就很高了, 因此我们需要用 CI平台鞋好的代码进行及时的测试, 因为OJ的代码是私有的,不能直接用DroneCI 或者是 TravisCI, 因而自己需要搭建一个DroneCI,具体操作如下, 根据DroneCI 官方文档说明  gitlab配置droneCI还有点麻烦, 之间也真遇到了几个坑= = 因此纪录一下过程

服务器端使用CentOS 首先安装docker, 并且 fetch DroneCI的镜像image到本地, 这就是一个基本的DroneCI环境了

根据DroneCI的说明 , 在  gitlab里首先创建一个application Oauth 接口,  配置为

Callback URL:即DroneCI所在的auth URL :http://202.118.31.226:3322/authorize这里为这个

然后会生成application id ****(A) 和 application secret *****(B)

将这两个写在 DroneCI Server的 /etc/drone/dronerc内 分别对应 A=> client_id B=> client_secret

注意REMOTE_DRIVER为 gitlab

REMOTE_CONFIG为 http://example.yourgitlab.com?client_id=A&client_secret=B

然后将 这个保存为脚本

然后运行脚本之后, DroneCI就搭建好了~ 点击登陆会跳转到相应的gitlab Oauth页面, 最开始因为没有弄清楚Oauth的关系, 把secret还有 CallbackURL全填写错了导致一直登陆失败QAQ

[NEUOJ日记] #1 Hello World

[NEUOJ日记] #1 Hello World

写在前面:

NEUOJ的开发即将满五个月, 算是我在大学阶段做的最大的一个项目了(因为搞ACM的缘故没有在大一大二做什么项目出来) 在开发过程中遇到了很多问题  & 学到了很多东西, 谨以此系列文章记录开发过程以及开发心得体会

文章的格式:

每一篇文章都会以: 一个主标题 开头, 然后 分为多个副标题, 每个副标题后面会跟一个解释下面要讲的内容的commit-id

Hello World

Why we start (No commit id)

两年半的ACM生涯结束了, 老师征求我们对于重写OJ的想法, 我们队伍的大家早就有自己写一个OJ的想法了, 作为挖坑小能手的我 ( Qrz ) 本来还有点因为自己的时间+个人原因不是很想搞(其实内心是非常想搞的) 被两个队友拉着就果断入坑了, 我们想写出一个能够给大家一个梯度训练, 界面友好, 测评多样化, 符合当今Web开发潮流的新的Online judge 系统, 于是我们队伍+ sjm + wyf就开始了对OJ的开发, 最初的分工是 我们队伍搞 Web 后端开发, sjm 搞前端开发, wyf + 我搞测评端研究

How we design (No commit id)

开发OJ对我来说最陌生的就是测评后端的开发, 不知道如何下手, 好在老师给我们提供了一个开源的测评系统 domjudge, domjudge的后端judgehost已经非常完善了, 支持多语言多环境多测试样例的安全测评, 于是我们初期就直接使用judgehost来做我们的测评后端, 然后前端我们使用的是Bootstrap框架, Web后端使用Laravel框架, 然后我们初步设计了一下表的结构, 参考了hustoj的数据表结构 & 我们自己的表结构,对表进行了设计, 最初的表有

 

(之后会在不同的阶段介绍表的具体结构  & 修改)

How we start(No commit id)

经过一周的讨论, 我们最后定下来了第一版的目标 先做出一个可以让用户提交代码(不管能不能测评)能看题的一个壳出来(虽然这个后来很快就实现了), 前端为 sjm 开始学习前端的知识(Bootstrap jQuery等等),后端为 accelercode队伍(即我们队) 我的另外两个队友都没有Web开发的经验, 因此给他们安排的任务是先学习PHP, 然后学Laravel, 在此同时我先开始熟悉laravel5(我之前用的是laravel4) 熟悉5之后, 开始建表, 建Model,建route, wyf 和 我的测评后端的任务是(刚开始没有让我搞测评后端, 我感觉搞OJ最有趣的地方就是测评后端了于是自己也主动去看了) 搭建domjudge, 并且研究judgehost的源码

Build the Domjudge(No commit id)

回去之后,我就开始了自己本地搭建domjudge的过程, 其中遇到了一些问题, 比如 archlinux下 jsoncpp头文件的名字和domjudge 的configure脚本里的名字不符,会导致就算安装了jsoncpp-devel还是找不到头文件, 这时候我在/usr/include/下面mkdir jsoncpp/json/ 然后把所有json/下的头文件拷贝到 jsoncpp/json/下 ,然后 再次configure就OK了 另外在下载源码之后没有configure文件, 需要先./bootstrap才行, 然而 bootstrap的时候会make 一次 latex的东西, 如果电脑里没有latex的东西, 你需要自己将 Makefile里关于latex的东西注释掉,也就是注释掉 make distclean下面的那一行,然后就可以了

Make the Plan for 1.0(931612-ffb28b)

这是我们的第一条commit, 在这个commit的时候, 我将大家拉入了我Bearychat的讨论组 NEUOJ-accelercode(private)

并且, 使用学校的gitlab 服务器 (219.216.96.46)对代码进行托管, 新建了 README.md 当时的计划如下:

 

然后为laravel工程建立了.gitignore, 并将laravel工程建好,配好了PHPStorm, laravel的环境, 配laravel的环境使用的是一键式傻瓜XAMPP, 我们的OJ开发正式开始 [Wed Nov 25 19:35:11 2015 +0800]

C语言可变参数函数实现 & 分析

C语言可变参数函数实现 & 分析

先给出一个样例程序, 实现一个打印每一个传给它的元素并且换行

上面的代码已经很清晰了, 下面简单说明一下如何编写一个可变参数函数

1.定义函数, 注意参数列表的第一个参数一定要有,就算没有用到也不能为空, 而且必须有名字, 这个是提供给va_start作为参数的

2.在自定义的函数定义 va_list 型变量(ap), 作为一会儿使用的指向变参的指针

3.然后使用 va_start将 (ap)指向 参数列表中的第一个元素

4.然后使用 va_arg,获取 “…” 中的参数, 每使用一次, 都会获取下一个参数的值

5.最后使用 va_end, C标准要求在函数调用结束前使用va_end

变参的原理通过函数调用的原理可以解释通顺

变参实际上就是在函数调用栈帧上压入不确定个数个参数,a

然后一个指针从第一个参数一直移动到最后一个, (注意内存对齐) 即可

压栈的汇编代码如下:

这里是将参数压栈的代码

待解决问题: 如果用格式化字符串+可变参数中出现了 char 型变量的话, 这个变量要作为int型在va_arg里接受,而不是char, 不然会编译报警告, 并且不能运行目标文件

编译警告:

运行错误:

[1] 14258 illegal hardware instruction (core dumped) ./a.out

 

[阳炎Project] 赏月独奏会 zh jp romaji

[阳炎Project] 赏月独奏会 zh jp romaji

20140222140719_UiRfn.thumb.600_0

ZH

JP & Romaji

 

[NEUOJ] 测评后端分析 测评过程分析(judgedaemon.main.php)

[NEUOJ] 测评后端分析 测评过程分析(judgedaemon.main.php)

还没有整理, 先放生肉(Draft)

DOMJudge 测评后端API分析
====

### 调用流程简介:
1. domserver存储有所有数据信息,包括测评必须的信息
2. domserver提供一套RESTful API供judgedaemon客户端调用, judgedaemon可以有多个实例同时运行
3. judgedaemon运行之后, 每隔一定时间向domserver的api请求数据, 如果有数据的话, server会返回给judgedaemon一个json包,包含要运行的judge所需要的所有信息
4. judgedaemon将信息解析之后, 制作出相应的程序执行,和编译脚本 compile.sh , run.sh 之后执行(可以选择在CHROOT环境下执行, 暂时没有考虑)
5. 执行后的结果通过参数和返回值的形式返回给judgedaemon , 之后judgedaemon将测评结果数据提交给domserver, domserver随即更新数据库内的信息

### judgedaemon.main.php内函数分析

* dbconfig_get_rest
* fetch_executable
* judge
* read_credentials
* request
* rest_encode_file
* usage

#### dbconfig_get_rest
* arguments:
$name: 用于表示想要get的配置的名称

* description:
获取服务器的配置信息

* retval
服务器的相应的配置信息

#### fetch_executable
__核心函数,用于下载用户提交的代码,并且将代码编译, 生成运行测评的脚本run.sh 并且返回run.sh的绝对路径共后续调用使用__

* arguments:
$workdirpath: 测评路径
$execid: 需要fetch的执行脚本archive的id
$md5sum: zip的校验和
* description:
该函数根据提供的三个参数, 先检查是否本地已经存在运行本次测评需要的所有脚本(或者二进制文件), 如果不存在则从server 访问 REST/executable ,获取下来相应的execid对应的base64加密后的zip文件,解码,然后在本地的workdir/executable/$execid/下进行解压 ,并且根据不同语言写入不同的脚本内容
* retval:
返回值为相应的执行脚本(run.sh)所在的绝对路径

#### judge(主测评函数)
* arguments:
$row: 一个数组,里面含有测评所需要的所有信息, $row是在服务器端获得数据之后decode,然后存入的数据
* description:
该函数实现比较复杂, 将该函数的整个执行以流程的形式说明
1. 检查是否有创建文件等一系列的权限
2. 获取比赛队员提交的文件 需要访问RESTapi 的 submission_files ,id存在$row[‘submitid’]内
3. 获取编译该语言代码所需的 compile script ,调用fetch_executable, 取出所需的compile_script,并且返回一个绝对路径 $execrunpath
4. 运行LIBJUDGE_DIR下的compile.sh脚本, 对代码进行编译
5. 检查compile的成功与否, 成功则继续,否则退出,并将compile result返回给server
6. 如果有CHROOT的设置,则建立chroot环境
7. 循环获取每个testcase, 本地无testcase则去远端获取(input & output) ,将他们放在 $workdirpath/testcase/下
8. 获取hardtimelimit(运行时限), __**未解决**__为什么要用overshoot_time这个函数来求运行时限(line 612左右 定义在 lib/lib.misc.php下)
9. 执行testcase run脚本, 测评开始
10. 判断测评结果(返回值的含义)
11. 尝试从program.metadata中读取测评耗时(等信息)
12. 将测评结果返回给server RESTapi POST 请求 judging_runs 返回的数据如下:(数据的解释可能有偏差)


testcaseid: 测试数据id
runresult: 执行结果
runtime: 执行时间
judgehost: 测评的host名
output_run: 测评标准输出
output_error: 测评中的地方发
output_system: 测评信息
output_diff: 测评diff

13. 如果有chroot环境则destroy掉
14. 一次judge结束

* retval:NULL
#### read_credentials
* arguments: NULL
* description:
获取配置文件(ETCDIR/restapi.secret)中的认证信息并且添加到全局认证变量 $endpoints 中.配置文件中通过空白字符对每个信息域进行分割, 信息含有 认证记录的index(字符串), RESTAPI的入口地址 和访问RESTAPI需要的用户名和密码.
* retval
实际上是将全局变量 $endpoints的信息更新了, 不过没有返回值

#### request
* arguments:
$url:请求的restapi的路径
$verb: 请求方式(GET POST PUT…)
$data: 通过curl GET or POST时候附带给server的数据
$failonerror: 表示这个函数失败的时候是fail还是报警告
* description:
所有的curl请求通过这个函数进行, 这个函数就是封装好的curl请求函数, 当 $verb 参数为GET时, 表示通过GET请求数据, 即请求的是 restapi 中的 xxxx_GET的API 执行过程如下, 先通过curl_init 初始化curl进程, 然后经过setopt设置好各种参数, 然后通过curl_exec向服务器发出请求, 将服务器回传的信息通过$response变量存储起来,并且通过curl_getinfo得到服务器的响应代码,然后进行error handling(如果response为NULL说明函数执行失败)

* retval
返回值为从服务器获得的回传数据, json格式
返回数据样例:

[{"filename":"a.c","content":"LyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioKICAgID4gRmlsZSBOYW1lOiAvdG1wL2EuYwogICAgPiBBdXRob3I6IFZPSURfMTMzCiAgICA+ICMjIyMjIyMjIyMjIyMjIyMjIyMgCiAgICA+IE1haWw6ICMjIyMjIyMjIyMjIyMjIyMjIyMgCiAgICA+IENyZWF0ZWQgVGltZTogVHVlIDAxIERlYyAyMDE1IDEyOjQ2OjM5IFBNIENTVAogKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqLwojaW5jbHVkZTxzdGRpby5oPgoKaW50IG1haW4odm9pZCkKewogICAgcHJpbnRmKCJIZWxsbyB3b3JsZCFcbiIpOwogICAgcmV0dXJuIDA7Cn0K"}]

#### reset_encode_file
* arguments:
$file: 文件所在路径
$sizelimit: 是否限制文件的大小 (传递给 dj_get_file_contents)
* description:
将文件用base64和urlencode进行编码,并返回其值, 如果限定了$sizelimit = TRUE 的话, 文件会被限制在50000B
* retval:
返回文件encode之后的字符串

#### usage
* arguments: NULL
* description:
显示说明信息并退出
* retval: NULL

##JSON format


---- POST api/judgings ----
{
"submitid":24,
"cid":2,
"teamid":2,
"probid":4,
"langid":"cpp",
"rejudgingid":null,
"maxruntime":1,
"memlimit":524288,
"outputlimit":4096,
"run":"run",
"compare":"compare",
"compare_args":null,
"compile_script":"cpp",
"compare_md5sum":"71306aae6e243f8a030ab1bd7d6b354b",
"run_md5sum":"c2cb7864f2f7343d1ab5094b8fd40da4",
"compile_script_md5sum":"cf76014b4e27a6e25378055f53733a7a",
"judgingid":37
}
---- GET api/config ----
{
"clar_categories":{
"general":"General issue",
"tech":"Technical issue"
},
"script_timelimit":30,
"script_memory_limit":2097152,
"script_filesize_limit":65536,
"memory_limit":524288,
"output_limit":4096,
"process_limit":64,
"sourcesize_limit":256,
"sourcefiles_limit":100,
"timelimit_overshoot":"1s|10%",
"verification_required":0,
"show_affiliations":1,
"show_pending":0,
"show_compile":2,
"show_sample_output":0,
"show_balloons_postfreeze":1,
"penalty_time":20,
"compile_penalty":1,
"results_prio":{
"memory-limit":"99",
"output-limit":"99",
"run-error":"99",
"timelimit":"99",
"wrong-answer":"30",
"no-output":"10",
"correct":"1"
},
"results_remap":[

],
"lazy_eval_results":1,
"enable_printing":0,
"time_format":"%H:%M",
"default_compare":"compare",
"default_run":"run",
"allow_registration":0,
"judgehost_warning":30,
"judgehost_critical":120,
"thumbnail_size":128
}
---- GET api/submission_files ----
[
{
"filename":"biubiubiu.cpp",
"content":"I2luY2x1ZGUgPGNzdGRpbz4NCmludCBtYWluKCl7DQogcHJpbnRmKCIyMDRcbiIpOw0KIHJldHVybiAwOw0KfQ0K"
}
]

---- GET api/testcases ----
{
"testcaseid":6,
"rank":1,
"probid":4,
"md5sum_input":"495804e297ee23dbbef4ab1b7bb845ab",
"md5sum_output":"495804e297ee23dbbef4ab1b7bb845ab"
}
---- GET api/executable ----
String(base64_encode zip archive)

---- GET api/testcase_files ----
String(base64_encode plain file)

---- POST api/judging_runs ----
// POST omit

[NEUOJ] 测评后端分析 judgehost文件结构分析

[NEUOJ] 测评后端分析 judgehost文件结构分析

NEUOJ的测评后端暂时使用的是domjudge 的 judgehost作为测评后端 下面分析一下judgehost的工作原理

首先分析一下judgehost的几个核心文件的功能

[这里省略了从clone domjudge repo 到编译安装好judgehost这个过程, 我认为来看这篇文章的应该都是已经可以自己安装好domjudge的人,不然可能这篇文章对于你来说还不适合阅读]

首先看bin下面的文件

runguard 是负责保证用户程序的安全运行, 不会对系统进行破坏的程序, 这个的代码利用了 rlmit, cgroup timeout等方式对用户程序进行限制

judgedaemon 一个php脚本, 测评后端的完整进程, 包括通过服务器下载数据, 到调用相应可执行文件对代码进行编译运行比对之后. 将结果返回给webserver的整个过程都是由这个程序进行的

runpipe: 用于在runjury(特殊的测评程序) 和 user program之间建立双向管道,进行交互式测评(目前只了解这些)

create_cgroups: 在设置了enable_cgroup 的编译参数之后, 通过这个来创建domjudge的 control group, cgroup能够获得更精确的运行时间和使用资源情况

dj_make_chroot, dj_make_ubuntu_chroot: 用于让java程序可以在chroot环境下运行

再看etc文件夹下的文件

sudoers.domjudge: 这个文件配置好了 运行测评的时候的用户的sudo权限配置

common-config.php: 一些通常的config文件

judgehost-static.php: 通过make 和 make install生成的静态config文件,不要进行修改

restapi.secret: 里面存有向API请求的时候的API地址和认证信息

再看judgings文件夹

judgings文件夹下, 所有的文件都是测评的时候生成的, 他的第一级给每一个judgehost分配一个不同的文件夹,名字为 HOSTNAME-n  , 每一个judgehost都要以一个不同的用户来运行, 不然会产生问题

每个judgings文件夹下, 有多个endpoint, 每一个endpoint对应你再 restapi.secret里面配置的不同的名字

如endpoint-neuoj

每个endpoint下 , 有 这样的文件夹: c[n]-s[n]-j[n]  每一个文件夹内都是一次测评,如果这个测评之前被测过了, 重新测评的时候, 原来的测评会叫做 c[n]-s[n]-j[n]-old-timestamp , 除了每一个测评一个文件夹以外, 还有两个文件夹, 一个用于存从服务器fetch来的input output, 一个用于存从服务器fetch来的 executable 分别叫testcase executable

对于每一个测评文件夹下的文件分析留到之后解释

再看看log文件夹, log文件夹内只存了必要的log信息, 没有其他东西, 至于run 和 tmp文件夹, 目前不清楚

最后看lib文件夹 ,仅介绍主要的几个文件, 我们看 lib/judge下的文件

testcase_run.sh 是用于运行程序测评的脚本, 脚本接受多个参数 会在介绍judge流程的时候具体介绍

sh-static 一个静态的 shell

judgedaemon.main.php 整个测评运行的主要程序, 实际上 bin下的judgedaemon就是来调用这个程序的

chroot-startstop.sh: 处理chroot

check_diff.sh: 简单的compare脚本

compile.sh: 一个用于调用其他compile executable的wrapper,并且保证compile不超时, 不超资源

 

以上就是judgehost的一个简单的分析, 下一篇会介绍judgedaemon的测评流程