def get_contest(self): """ GET /contest """ start_timestamp = Database.get_meta("start_time", type=int) start_datetime = (datetime.fromtimestamp(start_timestamp, timezone.utc) if start_timestamp is not None else None) now = datetime.now(timezone.utc) if not start_timestamp or now < start_datetime: return { "has_started": False, "start_time": start_datetime.isoformat() if start_datetime else None, "name": Database.get_meta("contest_name"), "description": Database.get_meta("contest_description"), } tasks = Database.get_tasks() return { "has_started": True, "name": Database.get_meta("contest_name"), "description": Database.get_meta("contest_description"), "start_time": start_datetime.isoformat(), "tasks": tasks, "max_total_score": sum(task["max_score"] for task in tasks), }
def test_get_meta_type_bool(self): Database.connected = False Database.connect_to_database() Database.set_meta('boooool', True) self.assertTrue(Database.get_meta('boooool', type=bool)) Database.set_meta('boooool2', False) self.assertFalse(Database.get_meta('boooool2', type=bool))
def test_get_meta_none(self): Database.connected = False Database.connect_to_database() Database.set_meta("test", None) Database.set_meta("test2", "None") self.assertEqual(Database.get_meta("test"), None) self.assertEqual(Database.get_meta("test", type=int), None) self.assertEqual(Database.get_meta("test", None, type=int), None) self.assertEqual(Database.get_meta("test2", None), "None")
def test_status(self): Database.set_meta('start_time', 1234) res = self.admin_handler.status(admin_token='ADMIN-TOKEN', _ip='1.2.3.4') start_time = int( datetime.datetime.strptime(res["start_time"], "%Y-%m-%dT%H:%M:%S").timestamp()) self.assertEqual(start_time, Database.get_meta('start_time', type=int)) self.assertEqual(0, Database.get_meta('extra_time', default=0))
def test_status(self): Database.set_meta("start_time", 1234) res = self.admin_handler.status(admin_token="ADMIN-TOKEN", _ip="1.2.3.4") start_time = dateutil.parser.parse(res["start_time"]) start_time_db = datetime.fromtimestamp( Database.get_meta("start_time", type=int), timezone.utc) self.assertEqual(start_time, start_time_db) self.assertEqual(0, Database.get_meta("extra_time", default=0))
def test_get_meta_default(self): Database.connected = False Database.connect_to_database() meta = Database.get_meta("this_key_doesnt_exist", "default_value_is_cool") self.assertEqual("default_value_is_cool", meta)
def test_del_meta(self): Database.connected = False Database.connect_to_database() Database.set_meta('random_key', 4242) self.assertTrue(Database.del_meta('random_key')) self.assertIsNone(Database.get_meta('random_key'))
def test_get_meta_default(self): Database.connected = False Database.connect_to_database() meta = Database.get_meta('this_key_doesnt_exist', 'default_value_is_cool') self.assertEqual('default_value_is_cool', meta)
def test_set_meta(self): Database.connected = False Database.connect_to_database() Database.set_meta('random_key', 42) value = Database.get_meta('random_key', type=int) self.assertEqual(42, value)
def status(self): """ POST /admin/status """ start_time = Database.get_meta('start_time', type=int) extra_time = Database.get_meta('extra_time', type=int, default=0) end_time = BaseHandler.get_end_time(0) return BaseHandler.format_dates( { "start_time": start_time, "extra_time": extra_time, "end_time": end_time, "loaded": ContestManager.has_contest }, fields=["start_time", "end_time"])
def download_results(self): """ POST /admin/download_pack """ Logger.info("ADMIN", "Creating zip file") zip_directory = os.path.join(Config.storedir, "zips", Database.gen_id()) os.makedirs(zip_directory, exist_ok=True) zipf_name = ("results-" + Database.get_meta("admin_token").split("-", 1)[0] + "-" + time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime()) + ".zip") zipf_name = os.path.join(zip_directory, zipf_name) command = ("zip -r '" + zipf_name + "' db.sqlite3* log.sqlite3* " "files/input files/output " "files/source /version* " "/proc/cpuinfo* " "/var/log/nginx") try: gevent.subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT) except subprocess.CalledProcessError as e: Logger.error("ADMIN", "Zip error: %s" % e.output) raise e return { "path": os.path.relpath(zipf_name, Config.storedir) } # pragma: nocover
def _ensure_contest_running(token=None): """ Makes sure that the contest is running for the user, if any. If the user has a time window it is also checked. :param token: The optional token of the user. """ extra_time = None start_delay = None if token: Validators._ensure_window_start(token) user = Database.get_user(token) if user: extra_time = user["extra_time"] start_delay = user["contest_start_delay"] if Database.get_meta("start_time") is None: BaseHandler.raise_exc(Forbidden, "FORBIDDEN", "The contest has not started yet") contest_end = BaseHandler.get_end_time(extra_time) window_end = BaseHandler.get_window_end_time(extra_time, start_delay) now = time.time() # check the contest is not finished if contest_end < now: BaseHandler.raise_exc(Forbidden, "FORBIDDEN", "The contest has ended") # if a window is present check it's not finished if window_end and window_end < now: BaseHandler.raise_exc(Forbidden, "FORBIDDEN", "Your window has ended")
def test_set_meta_overwrite(self): Database.connected = False Database.connect_to_database() Database.set_meta("random_key", 4242) Database.set_meta("random_key", 42) value = Database.get_meta("random_key", type=int) self.assertEqual(42, value)
def test_start_reset(self): Utils.start_contest(since=-100) out = self.admin_handler.start("reset", admin_token="ADMIN-TOKEN", _ip="1.2.3.4") self.assertIsNone(out["start_time"]) self.assertIsNone(Database.get_meta("start_time"))
def test_read_from_disk_transaction_failed(self, add_task_mock): path = Utils.new_tmp_dir() self._prepare_contest_dir(path) Config.statementdir = Utils.new_tmp_dir() Config.contest_path = path with self.assertRaises(Exception) as ex: ContestManager.read_from_disk() self.assertEqual("ops...", ex.exception.args[0]) self.assertIsNone(Database.get_meta("contest_duration"))
def test_start_ok(self): out = self.admin_handler.start(admin_token='ADMIN-TOKEN', _ip='1.2.3.4') start_time = datetime.datetime.strptime( out["start_time"], "%Y-%m-%dT%H:%M:%S").timestamp() self.assertTrue(start_time >= datetime.datetime.now().timestamp() - 10) self.assertEqual(start_time, Database.get_meta('start_time', type=int))
def test_transaction_commit(self): Database.connected = False Database.connect_to_database() Database.begin() Database.set_meta('random_foobar', 123, autocommit=False) Database.commit() self.assertEqual(123, Database.get_meta('random_foobar', type=int))
def test_transaction_rollback(self): Database.connected = False Database.connect_to_database() Database.begin() Database.set_meta('random_foobar', 123, autocommit=False) Database.rollback() self.assertEqual(42, Database.get_meta('random_foobar', default=42, type=int))
def test_start_now(self): out = self.admin_handler.start("now", admin_token="ADMIN-TOKEN", _ip="1.2.3.4") start_time = dateutil.parser.parse(out["start_time"]).timestamp() self.assertTrue(start_time >= datetime.utcnow().timestamp() - 10) start_time_db = datetime.fromtimestamp( Database.get_meta("start_time", type=int), timezone.utc) self.assertEqual(start_time, start_time_db.timestamp())
def _ensure_contest_started(): start_timestamp = Database.get_meta("start_time", type=int) start_datetime = ( datetime.fromtimestamp(start_timestamp, timezone.utc) if start_timestamp is not None else None ) if not start_datetime or start_datetime > datetime.now(timezone.utc): BaseHandler.raise_exc( Forbidden, "FORBIDDEN", "The contest has not started yet" )
def _ensure_window_start(token): """ Makes sure that the window of the user has been started :param token: The token of the user :return: True if the user has been updated """ if not Database.get_meta("window_duration", None): return False user = Database.get_user(token) if not user: return False start_delay = user["contest_start_delay"] if start_delay is not None: return False start = Database.get_meta("start_time", type=int) if start is None: return False now = time.time() delay = now - start Database.set_start_delay(token, delay) Logger.info("WINDOW_START", "Contest started for %s after %d" % (token, delay)) return True
def test_do_write_rollback(self): Database.connected = False Database.connect_to_database() with patch("terry.database.Database.commit", side_effect=ValueError("Ops...")): with self.assertRaises(ValueError): Database.do_write(True, """ INSERT INTO metadata (key, value) VALUES (:key, :value) """, {"key": "ciao", "value": "mondo"}) self.assertIsNone(Database.get_meta("ciao"))
def start(self): """ POST /admin/start """ if Database.get_meta("start_time", default=None, type=int) is not None: BaseHandler.raise_exc(Forbidden, "FORBIDDEN", "Contest has already been started!") start_time = int(time.time()) Database.set_meta("start_time", start_time) Logger.info("CONTEST", "Contest started") return BaseHandler.format_dates({"start_time": start_time}, fields=["start_time"])
def test_start_scheduled(self): start_time_str = "2020-01-01T13:13:13.0Z" out = self.admin_handler.start(start_time_str, admin_token="ADMIN-TOKEN", _ip="1.2.3.4") start_time = dateutil.parser.parse(out["start_time"]) expected_start_time = dateutil.parser.parse(start_time_str) self.assertEqual(start_time, expected_start_time) start_time_db = datetime.fromtimestamp( Database.get_meta("start_time", type=int), timezone.utc) self.assertEqual(start_time, start_time_db)
def test_read_from_disk(self): path = Utils.new_tmp_dir() self._prepare_contest_dir(path) Config.statementdir = Utils.new_tmp_dir() Config.contest_path = path with patch("gevent.spawn") as mock: ContestManager.read_from_disk() mock.assert_has_calls( [call(ContestManager.worker, "poldo")], any_order=True) self.assertEqual(18000, Database.get_meta( "contest_duration", type=int)) tasks = Database.get_tasks() self.assertEqual(1, len(tasks)) self.assertEqual("poldo", tasks[0]["name"]) self.assertEqual("Poldo", tasks[0]["title"]) self.assertEqual(42, tasks[0]["max_score"]) self.assertEqual(0, tasks[0]["num"]) users = Database.get_users() self.assertEqual(1, len(users)) self.assertEqual("token", users[0]["token"]) self.assertEqual("Test", users[0]["name"]) self.assertEqual("User", users[0]["surname"]) self.assertEqual(0, users[0]["extra_time"]) user_tasks = Database.get_user_task("token", "poldo") self.assertEqual("token", user_tasks["token"]) self.assertEqual("poldo", user_tasks["task"]) self.assertEqual(0, user_tasks["score"]) self.assertIsNone(user_tasks["current_attempt"]) self.assertTrue(Database.get_meta("contest_imported", type=bool)) self.assertTrue(ContestManager.has_contest) self.assertIn("poldo", ContestManager.tasks) self.assertIn("poldo", ContestManager.input_queue)
def upload_pack(self, file): """ POST /admin/upload_pack """ if Database.get_meta("admin_token"): BaseHandler.raise_exc(Forbidden, "FORBIDDEN", "The pack has already been extracted") elif os.path.exists(Config.encrypted_file): BaseHandler.raise_exc(Forbidden, "FORBIDDEN", "The pack has already been uploaded") if not crypto.validate(file["content"]): self.raise_exc(Forbidden, "BAD_FILE", "The uploaded file is " "not valid") StorageManager.save_file(os.path.realpath(Config.encrypted_file), file["content"]) return {}
def process_pack(pack, workdir): global data with common.extract_and_connect(pack, workdir): contest_start = Database.get_meta("start_time", type=int) tasks = common.get_tasks() Database.c.execute(""" SELECT submissions.date AS date, inputs.date AS input_date FROM submissions JOIN inputs ON submissions.input = inputs.id ORDER BY date ASC """) submissions = Database.dictify(all=True) add_dates(data["submissions"], submissions, contest_start) for sub in submissions: delta = sub["date"] - sub["input_date"] if delta not in data["delta"]: data["delta"][delta] = 0 data["delta"][delta] += 1 Database.c.execute(""" SELECT inputs.date AS date FROM inputs ORDER BY date ASC """) inputs = Database.dictify(all=True) add_dates(data["inputs"], inputs, contest_start) for task in tasks: Database.c.execute( """ SELECT submissions.date AS date FROM submissions WHERE task = :task ORDER BY date ASC """, {"task": task}) submissions = Database.dictify(all=True) if task not in data["tasks"]: data["tasks"][task] = dict() add_dates(data["tasks"][task], submissions, contest_start)
def drop_contest(self, admin_token): """ POST /admin/drop_contest """ if not os.path.exists(Config.encrypted_file): self.raise_exc(NotFound, "NOT_FOUND", "No packs found") Logger.warning("DROP_CONTEST", "Started dropping contest") with open(Config.encrypted_file, "rb") as f: pack = f.read() db_token = Database.get_meta("admin_token") # contest has been extracted but the token is wrong if db_token is not None and db_token != admin_token: self.raise_exc(Forbidden, "FORBIDDEN", "Wrong token") # contest has not been extracted if db_token is None: try: password = crypto.recover_file_password_from_token(admin_token) crypto.decode(password, pack) except nacl.exceptions.CryptoError: # pack password is wrong self.raise_exc(Forbidden, "FORBIDDEN", "Wrong pack token") metadata = ruamel.yaml.safe_load(crypto.metadata(pack).strip(b"\x00")) if not metadata.get("deletable"): self.raise_exc(Forbidden, "FORBIDDEN", "Contest not deletable") shutil.rmtree(Config.storedir, ignore_errors=True) shutil.rmtree(Config.statementdir, ignore_errors=True) shutil.rmtree(Config.contest_path, ignore_errors=True) for f in (Config.encrypted_file, Config.decrypted_file): try: os.remove(f) except FileNotFoundError: pass Database.disconnect_database() for f in glob.glob(Config.db + "*"): os.remove(f) Database.connect_to_database() Logger.warning("DROP_CONTEST", "Contest dropped") return {}
def start(self, start_time: str): """ POST /admin/start """ previous_start = Database.get_meta("start_time", type=int) now = int(time.time()) if previous_start and now > previous_start: BaseHandler.raise_exc(Forbidden, "FORBIDDEN", "Contest has already been started!") actual_start = None if start_time == "reset": Database.del_meta("start_time") return {"start_time": None} elif start_time == "now": actual_start = now else: actual_start = dateutil.parser.parse(start_time).timestamp() Database.set_meta("start_time", int(actual_start)) Logger.info("CONTEST", "Contest starts at " + str(actual_start)) return BaseHandler.format_dates({"start_time": actual_start}, fields=["start_time"])
def _validate_admin_token(token, ip): """ Ensure the admin token is valid :param token: Token to check :param ip: IP of the client """ correct_token = Database.get_meta("admin_token") if correct_token is None: ContestManager.extract_contest(token) ContestManager.read_from_disk() correct_token = token if token != correct_token: Logger.warning("LOGIN_ADMIN", "Admin login failed from %s" % ip) BaseHandler.raise_exc(Forbidden, "FORBIDDEN", "Invalid admin token!") else: if Database.register_admin_ip(ip): Logger.info("LOGIN_ADMIN", "An admin has connected from a new ip: %s" % ip)