Skip to content

Commit

Permalink
Make ProxyService work without contest_id.
Browse files Browse the repository at this point in the history
  • Loading branch information
vytisb committed Feb 20, 2024
1 parent 244e4ba commit 12b4259
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 115 deletions.
179 changes: 80 additions & 99 deletions cms/service/ProxyService.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# Copyright © 2010-2012 Matteo Boscariol <[email protected]>
# Copyright © 2013-2018 Luca Wehrstedt <[email protected]>
# Copyright © 2013 Bernard Blackham <[email protected]>
# Copyright © 2014-2024 Vytis Banaitis <[email protected]>
# Copyright © 2015 Luca Versari <[email protected]>
# Copyright © 2015 William Di Luigi <[email protected]>
# Copyright © 2016 Amir Keivan Mohtashami <[email protected]>
Expand Down Expand Up @@ -40,8 +41,8 @@
from sqlalchemy import not_

from cms import config
from cms.db import SessionGen, Contest, Participation, Task, Submission, \
get_submissions
from cms.db import SessionGen, Participation, Task, Submission, \
get_submissions, get_active_contest_list
from cms.io import Executor, QueueItem, TriggeredService, rpc_method
from cmscommon.datetime import make_timestamp

Expand Down Expand Up @@ -247,7 +248,7 @@ class ProxyService(TriggeredService):
"""

def __init__(self, shard, contest_id):
def __init__(self, shard):
"""Start the service with the given parameters.
Create an instance of the ProxyService and make it listen on
Expand All @@ -258,13 +259,10 @@ def __init__(self, shard, contest_id):
corresponds to the shard-th entry in the list of addresses
(hostname/port pairs) for this kind of service in the
configuration file.
contest_id (int): the ID of the contest to manage.
"""
super().__init__(shard)

self.contest_id = contest_id

# Store what data we already sent to rankings, to avoid
# sending it twice.
self.scores_sent_to_rankings = set()
Expand All @@ -289,28 +287,29 @@ def _missing_operations(self):
"""
counter = 0
with SessionGen() as session:
submissions = get_submissions(session, contest_id=self.contest_id) \
.filter(not_(Participation.hidden)) \
.filter(Submission.official).all()

for submission in submissions:
# The submission result can be None if the dataset has
# been just made live.
sr = submission.get_result()
if sr is None:
continue

if sr.scored() and \
submission.id not in self.scores_sent_to_rankings:
for operation in self.operations_for_score(submission):
self.enqueue(operation)
counter += 1

if submission.tokened() and \
submission.id not in self.tokens_sent_to_rankings:
for operation in self.operations_for_token(submission):
self.enqueue(operation)
counter += 1
for contest in get_active_contest_list(session):
submissions = get_submissions(session, contest_id=contest.id) \
.filter(not_(Participation.hidden)) \
.filter(Submission.official).all()

for submission in submissions:
# The submission result can be None if the dataset has
# been just made live.
sr = submission.get_result()
if sr is None:
continue

if sr.scored() and \
submission.id not in self.scores_sent_to_rankings:
for operation in self.operations_for_score(submission):
self.enqueue(operation)
counter += 1

if submission.tokened() and \
submission.id not in self.tokens_sent_to_rankings:
for operation in self.operations_for_token(submission):
self.enqueue(operation)
counter += 1

return counter

Expand All @@ -327,58 +326,52 @@ def initialize(self):
logger.info("Initializing rankings.")

with SessionGen() as session:
contest = Contest.get_from_id(self.contest_id, session)

if contest is None:
logger.error("Received request for unexistent contest "
"id %s.", self.contest_id)
raise KeyError("Contest not found.")

contest_id = encode_id(contest.name)
contest_data = {
"name": contest.description,
"begin": int(make_timestamp(contest.start)),
"end": int(make_timestamp(contest.stop)),
"score_precision": contest.score_precision}

users = dict()
teams = dict()

for participation in contest.participations:
user = participation.user
team = participation.team
if not participation.hidden:
users[encode_id(user.username)] = {
"f_name": user.first_name,
"l_name": user.last_name,
"team": encode_id(team.code)
if team is not None else None,
}
if team is not None:
teams[encode_id(team.code)] = {
"name": team.name
for contest in get_active_contest_list(session):
contest_id = encode_id(contest.name)
contest_data = {
"name": contest.description,
"begin": int(make_timestamp(contest.start)),
"end": int(make_timestamp(contest.stop)),
"score_precision": contest.score_precision}

users = dict()
teams = dict()

for participation in contest.participations:
user = participation.user
team = participation.team
if not participation.hidden:
users[encode_id(user.username)] = {
"f_name": user.first_name,
"l_name": user.last_name,
"team": encode_id(team.code)
if team is not None else None,
}
if team is not None:
teams[encode_id(team.code)] = {
"name": team.name
}

tasks = dict()

for task in contest.tasks:
score_type = task.active_dataset.score_type_object
tasks[encode_id(task.name)] = {
"short_name": task.name,
"name": task.title,
"contest": encode_id(contest.name),
"order": task.num,
"max_score": score_type.max_score,
"extra_headers": score_type.ranking_headers,
"score_precision": task.score_precision,
"score_mode": task.score_mode,
}

tasks = dict()

for task in contest.tasks:
score_type = task.active_dataset.score_type_object
tasks[encode_id(task.name)] = {
"short_name": task.name,
"name": task.title,
"contest": encode_id(contest.name),
"order": task.num,
"max_score": score_type.max_score,
"extra_headers": score_type.ranking_headers,
"score_precision": task.score_precision,
"score_mode": task.score_mode,
}

self.enqueue(ProxyOperation(ProxyExecutor.CONTEST_TYPE,
{contest_id: contest_data}))
self.enqueue(ProxyOperation(ProxyExecutor.TEAM_TYPE, teams))
self.enqueue(ProxyOperation(ProxyExecutor.USER_TYPE, users))
self.enqueue(ProxyOperation(ProxyExecutor.TASK_TYPE, tasks))
self.enqueue(ProxyOperation(ProxyExecutor.CONTEST_TYPE,
{contest_id: contest_data}))
self.enqueue(ProxyOperation(ProxyExecutor.TEAM_TYPE, teams))
self.enqueue(ProxyOperation(ProxyExecutor.USER_TYPE, users))
self.enqueue(ProxyOperation(ProxyExecutor.TASK_TYPE, tasks))

def operations_for_score(self, submission):
"""Send the score for the given submission to all rankings.
Expand Down Expand Up @@ -477,13 +470,9 @@ def submission_scored(self, submission_id):
"unexistent submission id %s.", submission_id)
raise KeyError("Submission not found.")

# ScoringService sent us a submission of another contest, they
# do not know about our contest_id in multicontest setup.
if submission.task.contest_id != self.contest_id:
logger.debug("Ignoring submission %d of contest %d "
"(this ProxyService considers contest %d only).",
submission.id, submission.task.contest_id,
self.contest_id)
if not submission.task.contest.active:
logger.debug("Ignoring submission %d of inactive contest %d.",
submission.id, submission.task.contest_id)
return

if submission.participation.hidden:
Expand Down Expand Up @@ -521,13 +510,9 @@ def submission_tokened(self, submission_id):
"unexistent submission id %s.", submission_id)
raise KeyError("Submission not found.")

# ScoringService sent us a submission of another contest, they
# do not know about our contest_id in multicontest setup.
if submission.task.contest_id != self.contest_id:
logger.debug("Ignoring submission %d of contest %d "
"(this ProxyService considers contest %d only).",
submission.id, submission.task.contest_id,
self.contest_id)
if not submission.task.contest.active:
logger.debug("Ignoring submission %d of inactive contest %d.",
submission.id, submission.task.contest_id)
return

if submission.participation.hidden:
Expand Down Expand Up @@ -564,13 +549,9 @@ def dataset_updated(self, task_id):
task = Task.get_from_id(task_id, session)
dataset = task.active_dataset

# This ProxyService may focus on a different contest, and it should
# ignore this update.
if task.contest_id != self.contest_id:
logger.debug("Ignoring dataset change for task %d of contest "
"%d (this ProxyService considers contest %d "
"only).", task_id, task.contest.id,
self.contest_id)
if not task.contest.active:
logger.debug("Ignoring dataset change for task %d of inactive "
"contest %d.", task_id, task.contest.id)
return

logger.info("Dataset update for task %d (dataset now is %d).",
Expand Down
13 changes: 2 additions & 11 deletions cms/service/ResourceService.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# Copyright © 2010-2012 Matteo Boscariol <[email protected]>
# Copyright © 2013-2017 Luca Wehrstedt <[email protected]>
# Copyright © 2014-2018 William Di Luigi <[email protected]>
# Copyright © 2014 Vytis Banaitis <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
Expand Down Expand Up @@ -179,10 +180,6 @@ def __init__(self, shard, contest_id=None, autorestart=False):
self._prev_cpu_times = self._get_cpu_times()
# Sorted list of ServiceCoord running in the same machine
self._local_services = self._find_local_services()
if "ProxyService" in (s.name for s in self._local_services) and \
self.contest_id is None:
logger.warning("Will not run ProxyService "
"since it requires a contest id.")
# Dict service with bool to mark if we will restart them.
self._will_restart = dict((service,
None if not self.autorestart else True)
Expand Down Expand Up @@ -223,9 +220,7 @@ def _restart_services(self):
for service in self._local_services:
# We let the user start logservice and resourceservice.
if service.name == "LogService" or \
service.name == "ResourceService" or \
(self.contest_id is None and
service.name == "ProxyService"):
service.name == "ResourceService":
continue

# If the user specified not to restart some service, we
Expand Down Expand Up @@ -463,10 +458,6 @@ def toggle_autorestart(self, service):
logger.error("Unable to decode service string.")
name = service[:idx]

# ProxyService requires contest_id
if self.contest_id is None and name == "ProxyService":
return None

try:
shard = int(service[idx + 1:])
except ValueError:
Expand Down
4 changes: 2 additions & 2 deletions cmstestsuite/unit_tests/service/ProxyServiceTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def setUp(self):
self.addCleanup(patcher.stop)
self.requests_put.return_value.status_code = 200

self.contest = self.add_contest()
self.contest = self.add_contest(active=True)
self.contest.score_precision = 2

self.task = self.add_task(contest=self.contest)
Expand Down Expand Up @@ -96,7 +96,7 @@ def new_sr_scored(self):

def test_startup(self):
"""Test that data is sent in the right order at startup."""
ProxyService(0, self.contest.id)
ProxyService(0)

gevent.sleep(0.1)

Expand Down
6 changes: 3 additions & 3 deletions scripts/cmsProxyService
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

# Contest Management System - http://cms-dev.github.io/
# Copyright © 2013 Luca Wehrstedt <[email protected]>
# Copyright © 2014 Vytis Banaitis <[email protected]>
# Copyright © 2016 Stefano Maggiolo <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
Expand All @@ -26,7 +27,7 @@ import logging
import sys

from cms import ConfigError, default_argument_parser
from cms.db import ask_for_contest, test_db_connection
from cms.db import test_db_connection
from cms.service.ProxyService import ProxyService


Expand All @@ -39,8 +40,7 @@ def main():
"""
test_db_connection()
success = default_argument_parser("Ranking relayer for CMS.",
ProxyService,
ask_contest=ask_for_contest).run()
ProxyService).run()
return 0 if success is True else 1


Expand Down

0 comments on commit 12b4259

Please sign in to comment.