Skip to content

Commit

Permalink
Implement multiple flagstores per Service
Browse files Browse the repository at this point in the history
  • Loading branch information
FlorianKothmeier committed Mar 26, 2024
1 parent 98383a0 commit dce2192
Show file tree
Hide file tree
Showing 14 changed files with 327 additions and 94 deletions.
130 changes: 101 additions & 29 deletions conf/controller/scoring.sql
Original file line number Diff line number Diff line change
Expand Up @@ -28,49 +28,121 @@ WITH
FROM flagdefense
GROUP BY team_id, service_id
),
sla_ok AS (
SELECT count(*) as sla_ok,
team_id,
service_id
FROM scoring_statuscheck
WHERE status = 0
GROUP BY team_id, service_id
),
sla_recover AS (
SELECT 0.5 * count(*) as sla_recover,
team_id,
service_id
FROM scoring_statuscheck
WHERE status = 4
GROUP BY team_id, service_id
),
-- sla_ok AS (
-- SELECT count(*) as sla_ok,
-- team_id,
-- service_id
-- FROM scoring_statuscheck
-- WHERE status = 0
-- GROUP BY team_id, service_id
-- ),
-- sla_recover AS (
-- SELECT 0.5 * count(*) as sla_recover,
-- team_id,
-- service_id
-- FROM scoring_statuscheck
-- WHERE status = 4
-- GROUP BY team_id, service_id
-- ),
teams as (
SELECT user_id as team_id
FROM registration_team
INNER JOIN auth_user ON auth_user.id = registration_team.user_id
WHERE is_active = true
AND nop_team = false
),
sla AS (
-- sla AS (
-- SELECT (SELECT sqrt(count(*)) FROM teams) * (coalesce(sla_ok, 0) + coalesce(sla_recover, 0)) as sla,
-- team_id,
-- service_id
-- FROM sla_ok
-- NATURAL FULL OUTER JOIN sla_recover
-- ),
fill AS (
SELECT team_id, scoring_servicegroup.id AS service_group_id
FROM teams, scoring_servicegroup
),
servicegroup AS (
SELECT scoring_service.service_group_id AS service_group_id,
count(scoring_service.id) AS services_count
FROM scoring_service
GROUP BY scoring_service.service_group_id
),
attack_by_servicegroup AS (
SELECT team_id,
scoring_service.service_group_id AS service_group_id,
sum(attack) AS attack,
sum(bonus) AS bonus
FROM attack
INNER JOIN scoring_service ON scoring_service.id = attack.service_id
GROUP BY team_id, scoring_service.service_group_id
),
defense_by_servicegroup AS (
SELECT team_id,
scoring_service.service_group_id AS service_group_id,
sum(defense) AS defense
FROM defense
INNER JOIN scoring_service ON scoring_service.id = defense.service_id
GROUP BY team_id, scoring_service.service_group_id
),
sla_ok_by_servicegroup_tick AS (
SELECT team_id,
scoring_service.service_group_id AS service_group_id,
0.5 * servicegroup.services_count * (COUNT(*) = servicegroup.services_count)::int AS sla_ok
FROM scoring_statuscheck
INNER JOIN scoring_service ON scoring_service.id = scoring_statuscheck.service_id
INNER JOIN servicegroup ON servicegroup.service_group_id = scoring_service.service_group_id
WHERE scoring_statuscheck.status = 0
GROUP BY team_id, scoring_service.service_group_id, servicegroup.services_count, scoring_statuscheck.tick
),
sla_ok_by_servicegroup AS (
SELECT team_id,
service_group_id,
sum(sla_ok) AS sla_ok
FROM sla_ok_by_servicegroup_tick
GROUP BY team_id, service_group_id
),
sla_recover_by_servicegroup_tick AS (
SELECT team_id,
scoring_service.service_group_id AS service_group_id,
0.5 * servicegroup.services_count * (COUNT(*) = servicegroup.services_count)::int AS sla_recover
FROM scoring_statuscheck
INNER JOIN scoring_service ON scoring_service.id = scoring_statuscheck.service_id
INNER JOIN servicegroup ON servicegroup.service_group_id = scoring_service.service_group_id
WHERE scoring_statuscheck.status = 4 OR scoring_statuscheck.status = 0
GROUP BY team_id, scoring_service.service_group_id, servicegroup.services_count, scoring_statuscheck.tick
),
sla_recover_by_servicegroup AS (
SELECT team_id,
service_group_id,
sum(sla_recover) AS sla_recover
FROM sla_recover_by_servicegroup_tick
GROUP BY team_id, service_group_id
),
sla_by_servicegroup AS (
SELECT (SELECT sqrt(count(*)) FROM teams) * (coalesce(sla_ok, 0) + coalesce(sla_recover, 0)) as sla,
team_id,
service_id
FROM sla_ok
NATURAL FULL OUTER JOIN sla_recover
),
fill AS (
SELECT team_id, scoring_service.id AS service_id
FROM teams, scoring_service
service_group_id
FROM sla_ok_by_servicegroup
NATURAL FULL OUTER JOIN sla_recover_by_servicegroup
-- ),
-- sla_by_servicegroup AS ( -- alternative sla without interplay between servicegroups
-- SELECT team_id,
-- scoring_service.service_group_id AS service_group_id,
-- sum(sla) AS sla
-- FROM sla
-- INNER JOIN scoring_service ON scoring_service.id = sla.service_id
-- GROUP BY team_id, scoring_service.service_group_id
)
SELECT team_id,
service_id,
service_group_id,
(coalesce(attack, 0)+coalesce(bonus, 0))::double precision as attack,
coalesce(bonus, 0) as bonus,
coalesce(defense, 0)::double precision as defense,
coalesce(sla, 0) as sla,
coalesce(attack, 0) + coalesce(defense, 0) + coalesce(bonus, 0) + coalesce(sla, 0) as total
FROM attack
NATURAL FULL OUTER JOIN defense
NATURAL FULL OUTER JOIN sla
FROM attack_by_servicegroup
NATURAL FULL OUTER JOIN defense_by_servicegroup
NATURAL FULL OUTER JOIN sla_by_servicegroup
NATURAL INNER JOIN fill
ORDER BY team_id, service_id;
ORDER BY team_id, service_group_id;
16 changes: 8 additions & 8 deletions examples/checker/example_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ def place_flag(self, tick):
logging.info('Received response to SET command: %s', repr(resp))
except UnicodeDecodeError:
logging.warning('Received non-UTF-8 data: %s', repr(resp))
return checkerlib.CheckResult.FAULTY
return checkerlib.CheckResult.FAULTY, 'Received non-UTF-8 data'
if resp != 'OK':
logging.warning('Received wrong response to SET command')
return checkerlib.CheckResult.FAULTY
return checkerlib.CheckResult.FAULTY, 'Received wrong response to SET command'

conn.close()
return checkerlib.CheckResult.OK
return checkerlib.CheckResult.OK, ''

def check_service(self):
conn = connect(self.ip)
Expand All @@ -37,10 +37,10 @@ def check_service(self):
logging.info('Received response to dummy command')
except UnicodeDecodeError:
logging.warning('Received non-UTF-8 data')
return checkerlib.CheckResult.FAULTY
return checkerlib.CheckResult.FAULTY, 'Received non-UTF-8 data'

conn.close()
return checkerlib.CheckResult.OK
return checkerlib.CheckResult.OK, ''

def check_flag(self, tick):
flag = checkerlib.get_flag(tick)
Expand All @@ -54,13 +54,13 @@ def check_flag(self, tick):
logging.info('Received response to GET command: %s', repr(resp))
except UnicodeDecodeError:
logging.warning('Received non-UTF-8 data: %s', repr(resp))
return checkerlib.CheckResult.FAULTY
return checkerlib.CheckResult.FAULTY, 'Received non-UTF-8 data'
if resp != flag:
logging.warning('Received wrong response to GET command')
return checkerlib.CheckResult.FLAG_NOT_FOUND
return checkerlib.CheckResult.FLAG_NOT_FOUND, 'Received wrong response to GET command'

conn.close()
return checkerlib.CheckResult.OK
return checkerlib.CheckResult.OK, ''


def connect(ip):
Expand Down
12 changes: 8 additions & 4 deletions src/ctf_gameserver/checker/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ def get_check_duration(db_conn, service_id, std_dev_count, prohibit_changes=Fals
' WHERE service_id = %s AND tick < current_tick', (std_dev_count, service_id))
result = cursor.fetchone()

return result[0]
if result and len(result) == 1 and result[0] != None:
return float(result[0])
else:
logging.warning('could not calculate the average script runtime in get_check_duration')
return 20


def get_task_count(db_conn, service_id, prohibit_changes=False):
Expand Down Expand Up @@ -152,7 +156,7 @@ def _net_no_to_team_id(cursor, team_net_no, fake_team_id):
return data[0]


def commit_result(db_conn, service_id, team_net_no, tick, result, prohibit_changes=False, fake_team_id=None):
def commit_result(db_conn, service_id, team_net_no, tick, result, message='', prohibit_changes=False, fake_team_id=None):
"""
Saves the result from a Checker run to game database.
"""
Expand All @@ -164,8 +168,8 @@ def commit_result(db_conn, service_id, team_net_no, tick, result, prohibit_chang
return

cursor.execute('INSERT INTO scoring_statuscheck'
' (service_id, team_id, tick, status, timestamp)'
' VALUES (%s, %s, %s, %s, NOW())', (service_id, team_id, tick, result))
' (service_id, team_id, tick, status, timestamp, message)'
' VALUES (%s, %s, %s, %s, NOW(), %s)', (service_id, team_id, tick, result, message))
# (In case of `prohibit_changes`,) PostgreSQL checks the database grants even if nothing is matched
# by `WHERE`
cursor.execute('UPDATE scoring_flag'
Expand Down
20 changes: 14 additions & 6 deletions src/ctf_gameserver/checker/master.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,24 +311,32 @@ def handle_store_request(self, task_info, params):

def handle_result_request(self, task_info, param):
try:
result = int(param)
except ValueError:
result = int(param['value'])
except ValueError | KeyError:
logging.error('Invalid result from Checker Script for team %d (net number %d) in tick %d: %s',
task_info['_team_id'], task_info['team'], task_info['tick'], param)
return

try:
check_result = CheckResult(result)
check_result = CheckResult(param['value'])
except ValueError:
logging.error('Invalid result from Checker Script for team %d (net number %d) in tick %d: %d',
task_info['_team_id'], task_info['team'], task_info['tick'], result)
return

logging.info('Result from Checker Script for team %d (net number %d) in tick %d: %s',
task_info['_team_id'], task_info['team'], task_info['tick'], check_result)
try:
message = str(param['message'])
except ValueError | KeyError:
logging.error('Invalid result from Checker Script for team %d (net number %d) in tick %d: %s',
task_info['_team_id'], task_info['team'], task_info['tick'], param)
return


logging.info('Result from Checker Script for team %d (net number %d) in tick %d: %s, msg: %s',
task_info['_team_id'], task_info['team'], task_info['tick'], check_result, message)
metrics.inc(self.metrics_queue, 'completed_tasks', labels={'result': check_result.name})
database.commit_result(self.db_conn, self.service['id'], task_info['team'], task_info['tick'],
result)
result, message)

def launch_tasks(self):
def change_tick(new_tick):
Expand Down
9 changes: 4 additions & 5 deletions src/ctf_gameserver/checker/supervisor.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,13 +361,12 @@ def handle_script_message(message, ctrlin_fd, runner_id, queue_to_master, pipe_f

if action == ACTION_RESULT:
try:
result = CheckResult(int(param))
except ValueError:
result = CheckResult(int(param['value']))
script_logger.info('[RUNNER] Checker Script result: %s, msg: %s', result.name, param['message'],
extra={'result': result.value})
except ValueError | KeyError:
# Ignore malformed message from the Checker Script, will be logged by the Master
pass
else:
script_logger.info('[RUNNER] Checker Script result: %s', result.name,
extra={'result': result.value})

queue_to_master.put((runner_id, action, param))
response = pipe_from_master.recv()
Expand Down
2 changes: 1 addition & 1 deletion src/ctf_gameserver/checkerlib/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .lib import BaseChecker, CheckResult, get_flag, set_flagid, load_state, run_check, store_state
from .lib import BaseChecker, CheckResult, get_flag, set_flagid, get_flagid, load_state, run_check, store_state
Loading

0 comments on commit dce2192

Please sign in to comment.