コード例 #1
0
 def _update_status(self) -> None:
     updates = dict(
         last_contact=func.now(),
         processed=self._task_processed,
         errors=self._task_errors,
     )
     try:
         hostname = self._hostname or gethostname()
         with transaction() as s:
             if self._hostname:
                 s.query(WorkerTable).filter(
                     WorkerTable.hostname == self._hostname,
                     WorkerTable.pid == self._pid).update(
                         updates, synchronize_session=False)
             else:
                 updates.update(
                     dict(hostname=hostname,
                          pid=self._pid,
                          max_processes=self._max_processes,
                          startup_time=func.now()))
                 s.add(WorkerTable(**updates))
             if uniform(0, 1) <= 0.01 or not self._hostname:
                 threshold = self._maint_interval * 10
                 s.query(WorkerTable).filter(
                     func.now() -
                     WorkerTable.last_contact > threshold).delete(
                         synchronize_session=False)
         self._hostname = hostname
     except Exception:
         pass
     self._schedule_update_status()
コード例 #2
0
ファイル: test_api.py プロジェクト: kazuki/PenguinJudge
 def setUp(self):
     from penguin_judge.main import _configure_app
     app.reset()
     _configure_app({})
     tables = (JudgeResult, Submission, TestCase, Problem, Contest,
               Environment, Token, User)
     admin_token = bytes([i for i in range(32)])
     salt = b'penguin'
     passwd = _kdf('penguinpenguin', salt)
     with transaction() as s:
         for t in tables:
             s.query(t).delete(synchronize_session=False)
         s.add(
             User(id='admin',
                  name='Administrator',
                  salt=salt,
                  admin=True,
                  password=passwd))
         s.flush()
         s.add(
             Token(token=admin_token,
                   user_id='admin',
                   expires=datetime.now(tz=timezone.utc) +
                   timedelta(hours=1)))
     self.admin_token = b64encode(admin_token).decode('ascii')
     self.admin_headers = {'X-Auth-Token': self.admin_token}
コード例 #3
0
ファイル: test_api.py プロジェクト: shiodat/PenguinJudge
    def test_get_current_user(self):
        uid, pw, name = 'penguin', 'password', 'ABC'
        u = app.post_json(
            '/users', {'login_id': uid, 'name': name, 'password': pw},
            headers=self.admin_headers).json
        token = app.post_json(
            '/auth', {'login_id': uid, 'password': pw}).json['token']
        self.assertEqual(u, app.get('/user').json)
        app.reset()
        app.authorization = ('Bearer', token)
        self.assertEqual(u, app.get('/user').json)
        app.authorization = None
        self.assertEqual(u, app.get(
            '/user', headers={'X-Auth-Token': token}).json)

        app.get('/user', status=401)
        app.get('/user', headers={
            'X-Auth-Token': b64encode(b'invalid token').decode('ascii')
        }, status=401)
        app.get('/user', headers={'X-Auth-Token': b'Z'}, status=401)

        with transaction() as s:
            s.query(Token).filter(Token.user_id == u['id']).update({
                'expires': datetime.now(tz=timezone.utc)})
        app.get('/user', headers={'X-Auth-Token': token}, status=401)
コード例 #4
0
def delete_problem(contest_id: str, problem_id: str) -> Response:
    with transaction() as s:
        _ = _validate_token(s, admin_required=True)
        s.query(Problem).filter(
            Problem.contest_id == contest_id,
            Problem.id == problem_id).delete(synchronize_session=False)
    return response204()
コード例 #5
0
def rejudge(contest_id: str, problem_id: str) -> Response:
    with transaction() as s:
        _ = _validate_token(s, admin_required=True)
        s.query(JudgeResult).filter(
            JudgeResult.contest_id == contest_id,
            JudgeResult.problem_id == problem_id).delete(
                synchronize_session=False)
        s.query(Submission).filter(
            Submission.contest_id == contest_id,
            Submission.problem_id == problem_id).update(
                {
                    Submission.status: JudgeStatus.Waiting,
                },
                synchronize_session=False)
        q = s.query(Submission.id).filter(Submission.contest_id == contest_id,
                                          Submission.problem_id == problem_id)
        rejudge_list = [x for x, in q]

    conn = pika.BlockingConnection(get_mq_conn_params())
    ch = conn.channel()
    ch.queue_declare(queue='judge_queue')
    for submission_id in rejudge_list:
        ch.basic_publish(exchange='',
                         routing_key='judge_queue',
                         body=pickle.dumps(
                             (contest_id, problem_id, submission_id)))
    ch.close()
    conn.close()

    return jsonify({})
コード例 #6
0
ファイル: main.py プロジェクト: shiodat/PenguinJudge
def run(judge_class: Callable[[], JudgeDriver],
        task: JudgeTask) -> JudgeStatus:
    LOGGER.info('judge start (contest_id: {}, problem_id: {}, '
                'submission_id: {}, user_id: {}'.format(
                    task.contest_id, task.problem_id, task.id, task.user_id))
    zctx = ZstdDecompressor()
    try:
        task.code = zctx.decompress(task.code)
        for test in task.tests:
            test.input = zctx.decompress(test.input)
            test.output = zctx.decompress(test.output)
    except Exception:
        LOGGER.warning('decompress failed', exc_info=True)
        with transaction() as s:
            return _update_submission_status(s, task,
                                             JudgeStatus.InternalError)
    with judge_class() as judge:
        ret = _prepare(judge, task)
        if ret:
            return ret
        if task.compile_image_name:
            ret = _compile(judge, task)
            if ret:
                return ret
        ret = _tests(judge, task)
    LOGGER.info('judge finished (submission_id={}): {}'.format(task.id, ret))
    return ret
コード例 #7
0
ファイル: main.py プロジェクト: shiodat/PenguinJudge
 def judge_test_cmpl(test: JudgeTestInfo, resp: Union[AgentTestResult,
                                                      AgentError]) -> None:
     time: Optional[timedelta] = None
     memory_kb: Optional[int] = None
     if isinstance(resp, AgentTestResult):
         if resp.time is not None:
             time = timedelta(seconds=resp.time)
         if resp.memory_bytes is not None:
             memory_kb = resp.memory_bytes // 1024
         if equal_binary(test.output, resp.output):
             status = JudgeStatus.Accepted
         else:
             status = JudgeStatus.WrongAnswer
     else:
         status = JudgeStatus.from_str(resp.kind)
     judge_results.append((status, time, memory_kb))
     with transaction() as s:
         s.query(JudgeResult).filter(
             JudgeResult.contest_id == task.contest_id,
             JudgeResult.problem_id == task.problem_id,
             JudgeResult.submission_id == task.id,
             JudgeResult.test_id == test.id,
         ).update(
             {
                 JudgeResult.status: status,
                 JudgeResult.time: time,
                 JudgeResult.memory: memory_kb,
             },
             synchronize_session=False)
コード例 #8
0
def list_contests() -> Response:
    params, _ = _validate_request()
    page, per_page = params.query['page'], params.query['per_page']
    ret = []
    with transaction() as s:
        u = _validate_token(s)
        q = s.query(Contest)
        if not (u and u['admin']):
            q = q.filter(Contest.published.is_(True))

        if 'status' in params.query:
            v = params.query['status']
            now = datetime.now(tz=timezone.utc)
            if v == 'running':
                q = q.filter(Contest.start_time <= now, now < Contest.end_time)
            elif v == 'scheduled':
                q = q.filter(now < Contest.start_time)
            elif v == 'finished':
                q = q.filter(Contest.end_time <= now)

        count = q.count()
        q = q.order_by(Contest.start_time.desc())
        for c in q.offset((page - 1) * per_page).limit(per_page):
            ret.append(c.to_summary_dict())
    return jsonify(ret, headers=pagination_header(count, page, per_page))
コード例 #9
0
def delete_environment(environment_id: str) -> Response:
    with transaction() as s:
        _ = _validate_token(s, admin_required=True)
        deleted = s.query(Environment).filter(
            Environment.id == environment_id).delete(synchronize_session=False)
        if not deleted:
            abort(404)
    return response204()
コード例 #10
0
def list_tests(contest_id: str, problem_id: str) -> Response:
    ret = []
    with transaction() as s:
        _ = _validate_token(s, admin_required=True)
        q = s.query(TestCase.id).filter(TestCase.contest_id == contest_id,
                                        TestCase.problem_id == problem_id)
        for (test_case_id, ) in q:
            ret.append(test_case_id)
    return jsonify(ret)
コード例 #11
0
ファイル: main.py プロジェクト: shiodat/PenguinJudge
 def start_test_func(test_id: str) -> None:
     with transaction() as s:
         s.query(JudgeResult).filter(
             JudgeResult.contest_id == task.contest_id,
             JudgeResult.problem_id == task.problem_id,
             JudgeResult.submission_id == task.id,
             JudgeResult.test_id == test_id,
         ).update({JudgeResult.status: JudgeStatus.Running},
                  synchronize_session=False)
コード例 #12
0
ファイル: main.py プロジェクト: shiodat/PenguinJudge
def _prepare(judge: JudgeDriver, task: JudgeTask) -> Union[JudgeStatus, None]:
    try:
        judge.prepare(task)
        return None
    except Exception:
        LOGGER.warning('prepare failed', exc_info=True)
        with transaction() as s:
            return _update_submission_status(s, task,
                                             JudgeStatus.InternalError)
コード例 #13
0
def deleteToken() -> Response:
    with transaction() as s:
        u = _validate_token(s, required=True)
        assert (u)
        s.query(Token).filter(Token.token == u['_token_bytes']).delete(
            synchronize_session=False)
    resp = make_response((b'', 204))
    resp.headers.pop(key='content-type')
    resp.headers.add('Set-Cookie', 'AuthToken=; Max-Age=0')
    return resp
コード例 #14
0
ファイル: test_api.py プロジェクト: kazuki/PenguinJudge
    def test_submissions_pagination(self):
        test_data = []
        start_time = datetime.now(tz=timezone.utc)
        end_time = start_time + timedelta(hours=1)
        app.post_json('/contests', {
            'id': 'id0',
            'title': 'Test Contest',
            'description': '**Pagination** Test',
            'start_time': start_time.isoformat(),
            'end_time': end_time.isoformat(),
            'published': True,
        },
                      headers=self.admin_headers)
        app.post_json('/contests/id0/problems', {
            'id': 'A',
            'title': 'Problem',
            'description': '# A',
            'time_limit': 2,
            'score': 100
        },
                      headers=self.admin_headers)

        test_data = []
        with transaction() as s:
            env = Environment(name='Python 3.7', test_image_name='image')
            s.add(env)
            s.flush()
            for i in range(100):
                submission = Submission(contest_id='id0',
                                        problem_id='A',
                                        user_id='admin',
                                        code=b'dummy',
                                        code_bytes=1,
                                        environment_id=env.id)
                s.add(submission)
                s.flush()
                test_data.append(submission.to_dict())

        resp = app.get('/contests/id0/submissions', headers=self.admin_headers)
        self.assertEqual(len(resp.json), 20)
        self.assertEqual(int(resp.headers['X-Page']), 1)
        self.assertEqual(int(resp.headers['X-Per-Page']), 20)
        self.assertEqual(int(resp.headers['X-Total']), 100)
        self.assertEqual(int(resp.headers['X-Total-Pages']), 5)

        resp = app.get('/contests/id0/submissions?page=2&per_page=31',
                       headers=self.admin_headers)
        self.assertEqual(len(resp.json), 31)
        self.assertEqual([x['id'] for x in resp.json],
                         [x['id'] for x in test_data[31:62]])
        self.assertEqual(int(resp.headers['X-Page']), 2)
        self.assertEqual(int(resp.headers['X-Per-Page']), 31)
        self.assertEqual(int(resp.headers['X-Total']), 100)
        self.assertEqual(int(resp.headers['X-Total-Pages']), 4)
コード例 #15
0
def list_environments() -> Response:
    ret = []
    with transaction() as s:
        u = _validate_token(s)
        is_admin = u and u['admin']
        q = s.query(Environment)
        if not is_admin:
            q = q.filter(Environment.published.is_(True))
        for c in q:
            ret.append(c.to_dict() if is_admin else c.to_summary_dict())
    return jsonify(ret)
コード例 #16
0
def get_user(user_id: str) -> Response:
    try:
        user_id_int = int(user_id)
    except Exception:
        abort(400)
    with transaction() as s:
        _validate_token_if_auth_required_config_is_enabled(s)
        user = s.query(User).filter(User.id == user_id_int).first()
        if not user:
            abort(404)
        return jsonify(user.to_summary_dict())
コード例 #17
0
def list_submissions(contest_id: str) -> Response:
    params, body = _validate_request()
    page, per_page = params.query['page'], params.query['per_page']
    ret = []
    with transaction() as s:
        u = _validate_token(s)
        contest = s.query(Contest).filter(Contest.id == contest_id).first()
        if not (contest and contest.is_accessible(u)):
            abort(404)
        is_admin = (u and u['admin'])
        if not (contest.is_begun() or is_admin):
            abort(403)

        q = s.query(Submission,
                    User.name).filter(Submission.contest_id == contest_id,
                                      Submission.user_id == User.id)
        if not (contest.is_finished() or is_admin):
            if not u:
                # 未ログイン時は開催中コンテストの投稿一覧は見えない
                abort(403)
            q = q.filter(Submission.user_id == u['id'])

        filters = [
            ('problem_id', Submission.problem_id.__eq__),
            ('environment_id', Submission.environment_id.__eq__),
            ('status', Submission.status.__eq__),
        ]
        for key, expr in filters:
            v = params.query.get(key)
            if v is None:
                continue
            q = q.filter(expr(v))  # type: ignore

        if params.query.get('user_name'):
            q = q.filter(User.name.contains(params.query.get('user_name')))

        count = q.count()

        if params.query.get('sort'):
            sort_keys = []
            for key in params.query['sort']:
                f = getattr(Submission, key.lstrip('-'))
                if key[0] == '-':
                    f = f.desc()
                sort_keys.append(f)
            q = q.order_by(*sort_keys)
        else:
            q = q.order_by(Submission.created)

        for c, name in q.offset((page - 1) * per_page).limit(per_page):
            tmp = c.to_summary_dict()
            tmp['user_name'] = name
            ret.append(tmp)
    return jsonify(ret, headers=pagination_header(count, page, per_page))
コード例 #18
0
ファイル: test_api.py プロジェクト: shiodat/PenguinJudge
    def test_list_environments(self):
        envs = app.get('/environments').json
        self.assertEqual(envs, [])

        env = dict(name='Python 3.7', test_image_name='docker-image',
                   published=True)
        with transaction() as s:
            s.add(Environment(**env))
        envs = app.get('/environments').json
        self.assertEqual(len(envs), 1)
        self.assertIsInstance(envs[0]['id'], int)
        self.assertEqual(envs[0]['name'], env['name'])
コード例 #19
0
def register_environment() -> Response:
    _, body = _validate_request()
    with transaction() as s:
        _ = _validate_token(s, admin_required=True)
        e = Environment(name=body.name,
                        active=getattr(body, 'active', True),
                        published=getattr(body, 'published', False),
                        compile_image_name=getattr(body, 'compile_image_name',
                                                   ''),
                        test_image_name=body.test_image_name)
        s.add(e)
        s.flush()
        return jsonify(e.to_dict())
コード例 #20
0
def get_contest(contest_id: str) -> Response:
    with transaction() as s:
        u = _validate_token(s)
        contest = s.query(Contest).filter(Contest.id == contest_id).first()
        if not (contest and contest.is_accessible(u)):
            abort(404)
        ret = contest.to_dict()
        if contest.is_begun() or (u and u['admin']):
            problems = s.query(Problem).filter(
                Problem.contest_id == contest_id).order_by(Problem.id).all()
            if problems:
                ret['problems'] = [p.to_dict() for p in problems]
    return jsonify(ret)
コード例 #21
0
def list_problems(contest_id: str) -> Response:
    with transaction() as s:
        u = _validate_token(s)
        contest = s.query(Contest).filter(Contest.id == contest_id).first()
        if not (contest and contest.is_accessible(u)):
            abort(404)
        if not (contest.is_begun() or (u and u['admin'])):
            abort(403)
        ret = [
            p.to_summary_dict() for p in s.query(Problem).filter(
                Problem.contest_id == contest_id).order_by(Problem.id).all()
        ]
    return jsonify(ret)
コード例 #22
0
def get_problem(contest_id: str, problem_id: str) -> Response:
    with transaction() as s:
        u = _validate_token(s)
        contest = s.query(Contest).filter(Contest.id == contest_id).first()
        if not (contest and contest.is_accessible(u)):
            abort(404)
        if not (contest.is_begun() or (u and u['admin'])):
            abort(404)  # ここは403ではなく404にする
        ret = s.query(Problem).filter(Problem.contest_id == contest_id,
                                      Problem.id == problem_id).first()
        if not ret:
            abort(404)
        ret = ret.to_dict()
    return jsonify(ret)
コード例 #23
0
def update_environment(environment_id: str) -> Response:
    _, body = _validate_request()
    with transaction() as s:
        _ = _validate_token(s, admin_required=True)
        c = s.query(Environment).filter(
            Environment.id == environment_id).first()
        if not c:
            abort(404)
        for key in Environment.__updatable_keys__:
            if not hasattr(body, key):
                continue
            setattr(c, key, getattr(body, key))
        ret = c.to_dict()
    return jsonify(ret)
コード例 #24
0
def update_problem(contest_id: str, problem_id: str) -> Response:
    _, body = _validate_request()
    with transaction() as s:
        _ = _validate_token(s, admin_required=True)
        problem = s.query(Problem).filter(Problem.contest_id == contest_id,
                                          Problem.id == problem_id).first()
        if not problem:
            abort(404)
        for key in Problem.__updatable_keys__:
            if not hasattr(body, key):
                continue
            setattr(problem, key, getattr(body, key))
        ret = problem.to_dict()
    return jsonify(ret)
コード例 #25
0
def update_contest(contest_id: str) -> Response:
    _, body = _validate_request()
    with transaction() as s:
        _ = _validate_token(s, admin_required=True)
        c = s.query(Contest).filter(Contest.id == contest_id).first()
        if not c:
            abort(404)
        for key in Contest.__updatable_keys__:
            if not hasattr(body, key):
                continue
            setattr(c, key, getattr(body, key))
        if c.start_time >= c.end_time:
            abort(400, {'detail': 'start_time must be lesser than end_time'})
        ret = c.to_dict()
    return jsonify(ret)
コード例 #26
0
def _validate_token(
        s: Optional[scoped_session] = None,
        required: bool = False,
        admin_required: bool = False,
        ignore_auth_required_config: bool = False) -> Optional[dict]:
    # コンフィグの認証必須オプションを無視する指示がされていない場合は
    # 認証必須オプションによってrequiredの値を上書きする
    if not ignore_auth_required_config and _config_auth_required():
        required = True

    token = request.headers.get('X-Auth-Token')
    if not token:
        items = request.headers.get('Authorization', '').split(' ', maxsplit=1)
        if len(items) == 2 and items[0].lower() == 'bearer':
            token = items[1]
    if not token:
        token = request.cookies.get('AuthToken')
    if not token:
        if required or admin_required:
            abort(401)
        return None

    try:
        token_bytes = b64decode(token)
    except Exception:
        abort(401)
    utc_now = datetime.now(tz=timezone.utc)

    def _check(s: scoped_session) -> Optional[dict]:
        ret = s.query(Token.expires,
                      User).filter(Token.token == token_bytes,
                                   Token.user_id == User.id).first()
        if not ret or ret[0] <= utc_now:
            if required or admin_required:
                abort(401)
            else:
                return None
        if admin_required and not ret[1].admin:
            abort(401)
        tmp = ret[1].to_summary_dict()
        tmp['_token_bytes'] = token_bytes
        tmp['login_id'] = ret[1].login_id
        return tmp

    if s:
        return _check(s)
    with transaction() as s:
        return _check(s)
コード例 #27
0
def create_contest() -> Response:
    _, body = _validate_request()
    if body.start_time >= body.end_time:
        abort(400, {'detail': 'start_time must be lesser than end_time'})
    with transaction() as s:
        _ = _validate_token(s, admin_required=True)
        contest = Contest(id=body.id,
                          title=body.title,
                          description=body.description,
                          start_time=body.start_time,
                          end_time=body.end_time,
                          published=getattr(body, 'published', None))
        s.add(contest)
        s.flush()
        ret = contest.to_dict()
    return jsonify(ret)
コード例 #28
0
def _get_test_data(contest_id: str, problem_id: str, test_id: str,
                   is_input: bool) -> Response:
    zctx = ZstdDecompressor()
    from io import BytesIO
    with transaction() as s:
        _ = _validate_token(s, admin_required=True)
        tc = s.query(TestCase).filter(TestCase.contest_id == contest_id,
                                      TestCase.problem_id == problem_id,
                                      TestCase.id == test_id).first()
        if not tc:
            abort(404)
        f = BytesIO(zctx.decompress(tc.input if is_input else tc.output))
        return send_file(f,
                         as_attachment=True,
                         attachment_filename='{}.{}'.format(
                             test_id, 'in' if is_input else 'out'))
コード例 #29
0
def get_status() -> Response:
    ret = {}
    with transaction() as s:
        _ = _validate_token(s, admin_required=True)
        ret['workers'] = [
            w.to_dict() for w in s.query(Worker).filter(
                func.now() - Worker.last_contact < timedelta(
                    seconds=60 * 10), ).order_by(Worker.startup_time)
        ]

    conn = pika.BlockingConnection(get_mq_conn_params())
    ch = conn.channel()
    queue = ch.queue_declare(queue='judge_queue')
    ret['queued'] = queue.method.message_count
    ch.close()
    conn.close()
    return jsonify(ret)
コード例 #30
0
def post_submission(contest_id: str) -> Response:
    _, body = _validate_request()
    problem_id, code, env_id = body.problem_id, body.code, body.environment_id

    cctx = ZstdCompressor()
    code_encoded = code.encode('utf8')
    code = cctx.compress(code_encoded)

    with transaction() as s:
        u = _validate_token(s, required=True)
        assert (u)
        if not s.query(Environment).filter(Environment.id == env_id).count():
            abort(400)  # bodyが不正なので400
        if not s.query(Contest).filter(Contest.id == contest_id).count():
            abort(404)  # contest_idはURLに含まれるため404
        if not s.query(Problem).filter(Problem.contest_id == contest_id,
                                       Problem.id == problem_id).count():
            abort(400)  # bodyが不正なので400
        queued_submission_count = s.query(Submission).filter(
            Submission.user_id == u['id'],
            Submission.status.in_([JudgeStatus.Waiting,
                                   JudgeStatus.Running])).count()
        if queued_submission_count > app.config['user_judge_queue_limit']:
            abort(429)
        submission = Submission(contest_id=contest_id,
                                problem_id=problem_id,
                                user_id=u['id'],
                                code=code,
                                code_bytes=len(code_encoded),
                                environment_id=env_id)
        s.add(submission)
        s.flush()
        ret = submission.to_summary_dict()
        ret['user_name'] = u['name']

    conn = pika.BlockingConnection(get_mq_conn_params())
    ch = conn.channel()
    ch.queue_declare(queue='judge_queue')
    ch.basic_publish(exchange='',
                     routing_key='judge_queue',
                     body=pickle.dumps((contest_id, problem_id, ret['id'])))
    ch.close()
    conn.close()
    return jsonify(ret, status=201)