def test_shutdown(self, monotonic_mock): checkerscript_path = '/dev/null' monotonic_mock.return_value = 10 master_loop = MasterLoop(self.connection, self.state_db_conn, 'service1', checkerscript_path, None, 2, 1, 10, '0.0.%s.1', b'secret', {}, DummyQueue()) with transaction_cursor(self.connection) as cursor: cursor.execute('UPDATE scoring_gamecontrol SET start=NOW()') cursor.execute('UPDATE scoring_gamecontrol SET current_tick=0') cursor.execute( 'INSERT INTO scoring_flag (service_id, protecting_team_id, tick)' ' VALUES (1, 2, 0)') master_loop.shutting_down = True master_loop.supervisor.queue_timeout = 0.01 monotonic_mock.return_value = 20 # Will return False because no messages yet self.assertFalse(master_loop.step()) with transaction_cursor(self.connection) as cursor: cursor.execute( 'SELECT COUNT(*) FROM scoring_flag WHERE placement_start IS NOT NULL' ) self.assertEqual(cursor.fetchone()[0], 0) cursor.execute('SELECT COUNT(*) FROM scoring_statuscheck') self.assertEqual(cursor.fetchone()[0], 0)
def test_down(self, monotonic_mock): checkerscript_path = os.path.join(os.path.dirname(__file__), 'integration_down_checkerscript.py') monotonic_mock.return_value = 10 master_loop = MasterLoop(self.connection, self.state_db_conn, 'service1', checkerscript_path, None, 2, 1, 10, '0.0.%s.1', b'secret', {}, DummyQueue()) with transaction_cursor(self.connection) as cursor: cursor.execute('UPDATE scoring_gamecontrol SET start=NOW()') cursor.execute('UPDATE scoring_gamecontrol SET current_tick=0') cursor.execute( 'INSERT INTO scoring_flag (service_id, protecting_team_id, tick)' ' VALUES (1, 2, 0)') monotonic_mock.return_value = 20 master_loop.supervisor.queue_timeout = 0.01 # Checker Script gets started, will return False because no messages yet self.assertFalse(master_loop.step()) master_loop.supervisor.queue_timeout = 10 while master_loop.step(): pass with transaction_cursor(self.connection) as cursor: cursor.execute( 'SELECT COUNT(*) FROM scoring_flag WHERE placement_end IS NOT NULL' ) self.assertEqual(cursor.fetchone()[0], 1) cursor.execute('SELECT COUNT(*) FROM scoring_statuscheck') self.assertEqual(cursor.fetchone()[0], 1) cursor.execute('SELECT status FROM scoring_statuscheck' ' WHERE service_id=1 AND team_id=2 AND tick=0') self.assertEqual(cursor.fetchone()[0], CheckResult.DOWN.value)
def setUp(self): self.master_loop = MasterLoop(self.connection, 'service1', '/dev/null', None, 2, 8, 10, '0.0.%s.1', b'secret', {}, DummyQueue())
class MasterTest(DatabaseTestCase): fixtures = ['tests/checker/fixtures/master.json'] def setUp(self): self.master_loop = MasterLoop(self.connection, 'service1', '/dev/null', None, 2, 8, 10, '0.0.%s.1', b'secret', {}, DummyQueue()) def test_handle_flag_request(self): with transaction_cursor(self.connection) as cursor: cursor.execute('UPDATE scoring_gamecontrol SET start=NOW()') task_info = { 'service': 'service1', '_team_id': 2, 'team': 92, 'tick': 1 } params1 = {'tick': 1} resp1 = self.master_loop.handle_flag_request(task_info, params1) params2 = {'tick': 1} resp2 = self.master_loop.handle_flag_request(task_info, params2) params3 = {'tick': 1, 'payload': 'TmV2ZXIgZ28='} resp3 = self.master_loop.handle_flag_request(task_info, params3) self.assertEqual(resp1, resp2) self.assertNotEqual(resp1, resp3) params4 = {'tick': 2} resp4 = self.master_loop.handle_flag_request(task_info, params4) params5 = {'tick': 2} resp5 = self.master_loop.handle_flag_request(task_info, params5) self.assertEqual(resp4, resp5) self.assertNotEqual(resp1, resp4) params6 = {} self.assertIsNone( self.master_loop.handle_flag_request(task_info, params6)) # Changing the start time changes all flags with transaction_cursor(self.connection) as cursor: # SQLite syntax for tests cursor.execute( 'UPDATE scoring_gamecontrol SET start=DATETIME("now", "+1 hour")' ) resp1_again = self.master_loop.handle_flag_request(task_info, params1) resp4_again = self.master_loop.handle_flag_request(task_info, params4) self.assertNotEqual(resp1, resp1_again) self.assertNotEqual(resp4, resp4_again) def test_handle_result_request(self): task_info = { 'service': 'service1', '_team_id': 2, 'team': 92, 'tick': 1 } param = CheckResult.OK.value start_time = datetime.datetime.utcnow().replace(microsecond=0) self.assertIsNone( self.master_loop.handle_result_request(task_info, param)) with transaction_cursor(self.connection) as cursor: cursor.execute('SELECT COUNT(*) FROM scoring_statuscheck') self.assertEqual(cursor.fetchone()[0], 1) cursor.execute( 'SELECT status FROM scoring_statuscheck' ' WHERE service_id = 1 AND team_id = 2 AND tick = 1') self.assertEqual(cursor.fetchone()[0], CheckResult.OK.value) cursor.execute( 'SELECT placement_end FROM scoring_flag' ' WHERE service_id = 1 AND protecting_team_id = 2 AND tick = 1' ) self.assertGreaterEqual(cursor.fetchone()[0], start_time) task_info['tick'] = 2 param = CheckResult.FAULTY.value start_time = datetime.datetime.utcnow().replace(microsecond=0) self.assertIsNone( self.master_loop.handle_result_request(task_info, param)) with transaction_cursor(self.connection) as cursor: cursor.execute( 'SELECT status FROM scoring_statuscheck' ' WHERE service_id = 1 AND team_id = 2 AND tick = 2') self.assertEqual(cursor.fetchone()[0], CheckResult.FAULTY.value) cursor.execute( 'SELECT placement_end FROM scoring_flag' ' WHERE service_id = 1 AND protecting_team_id = 2 AND tick = 2' ) self.assertGreaterEqual(cursor.fetchone()[0], start_time) task_info['tick'] = 3 param = 'Not an int' self.assertIsNone( self.master_loop.handle_result_request(task_info, param)) with transaction_cursor(self.connection) as cursor: cursor.execute( 'SELECT status FROM scoring_statuscheck' ' WHERE service_id = 1 AND team_id = 2 AND tick = 3') self.assertIsNone(cursor.fetchone()) cursor.execute( 'SELECT placement_end FROM scoring_flag' ' WHERE service_id = 1 AND protecting_team_id = 2 AND tick = 3' ) self.assertIsNone(cursor.fetchone()[0]) param = 1337 self.assertIsNone( self.master_loop.handle_result_request(task_info, param)) with transaction_cursor(self.connection) as cursor: cursor.execute( 'SELECT status FROM scoring_statuscheck' ' WHERE service_id = 1 AND team_id = 2 AND tick = 3') self.assertIsNone(cursor.fetchone()) cursor.execute( 'SELECT placement_end FROM scoring_flag' ' WHERE service_id = 1 AND protecting_team_id = 2 AND tick = 3' ) self.assertIsNone(cursor.fetchone()[0]) @patch('ctf_gameserver.checker.database.get_check_duration') def test_update_launch_params(self, check_duration_mock): # Very short duration, but should be ignored in tick 1 check_duration_mock.return_value = 1 self.master_loop.update_launch_params(-1) self.assertEqual(self.master_loop.tasks_per_launch, 0) with transaction_cursor(self.connection) as cursor: cursor.execute('UPDATE scoring_gamecontrol SET current_tick=1') self.master_loop.update_launch_params(1) self.assertEqual(self.master_loop.tasks_per_launch, 1) with transaction_cursor(self.connection) as cursor: for i in range(10, 400): username = '******'.format(i) email = '{}@example.org'.format(username) cursor.execute( 'INSERT INTO auth_user (id, username, first_name, last_name, email, password,' ' is_superuser, is_staff, is_active, date_joined)' ' VALUES (%s, %s, %s, %s, %s, %s, false, false, true, NOW())', (i, username, '', '', '', 'password')) cursor.execute( 'INSERT INTO registration_team (user_id, informal_email, image, affiliation,' ' country, nop_team)' ' VALUES (%s, %s, %s, %s, %s, false)', (i, email, '', '', 'World')) cursor.execute( 'INSERT INTO scoring_flag (service_id, protecting_team_id, tick)' ' VALUES (1, %s, 1)', (i, )) self.master_loop.update_launch_params(1) self.assertEqual(self.master_loop.tasks_per_launch, 49) check_duration_mock.return_value = None self.master_loop.update_launch_params(10) self.assertEqual(self.master_loop.tasks_per_launch, 49) check_duration_mock.return_value = 3600 self.master_loop.update_launch_params(10) self.assertEqual(self.master_loop.tasks_per_launch, 49) check_duration_mock.return_value = 90 self.master_loop.update_launch_params(10) self.assertEqual(self.master_loop.tasks_per_launch, 7) self.master_loop.interval = 5 self.master_loop.update_launch_params(10) self.assertEqual(self.master_loop.tasks_per_launch, 4) check_duration_mock.return_value = 10 self.master_loop.interval = 10 self.master_loop.tick_duration = datetime.timedelta(seconds=90) self.master_loop.update_launch_params(10) self.assertEqual(self.master_loop.tasks_per_launch, 7)
def test_sudo_unfinished(self, monotonic_mock, warning_mock): if shutil.which('sudo') is None or not os.path.exists( '/etc/sudoers.d/ctf-checker'): raise SkipTest('sudo or sudo config not available') checkerscript_path = os.path.join( os.path.dirname(__file__), 'integration_unfinished_checkerscript.py') checkerscript_pidfile = tempfile.NamedTemporaryFile() os.chmod(checkerscript_pidfile.name, 0o666) os.environ['CHECKERSCRIPT_PIDFILE'] = checkerscript_pidfile.name monotonic_mock.return_value = 10 master_loop = MasterLoop(self.connection, self.state_db_conn, 'service1', checkerscript_path, 'ctf-checkerrunner', 2, 1, 10, '0.0.%s.1', b'secret', {}, DummyQueue()) with transaction_cursor(self.connection) as cursor: cursor.execute('UPDATE scoring_gamecontrol SET start=NOW()') cursor.execute('UPDATE scoring_gamecontrol SET current_tick=0') cursor.execute( 'INSERT INTO scoring_flag (service_id, protecting_team_id, tick)' ' VALUES (1, 2, 0)') monotonic_mock.return_value = 20 master_loop.supervisor.queue_timeout = 0.01 # Checker Script gets started, will return False because no messages yet self.assertFalse(master_loop.step()) master_loop.supervisor.queue_timeout = 10 self.assertTrue(master_loop.step()) checkerscript_pidfile.seek(0) checkerscript_pid = int(checkerscript_pidfile.read()) def signal_script(): subprocess.check_call([ 'sudo', '--user=ctf-checkerrunner', '--', 'kill', '-0', str(checkerscript_pid) ]) # Ensure process is running by sending signal 0 signal_script() master_loop.supervisor.queue_timeout = 0.01 monotonic_mock.return_value = 50 self.assertFalse(master_loop.step()) # Process should still be running signal_script() with transaction_cursor(self.connection) as cursor: cursor.execute('UPDATE scoring_gamecontrol SET current_tick=1') monotonic_mock.return_value = 190 self.assertFalse(master_loop.step()) # Poll whether the process has been killed for _ in range(100): try: signal_script() except subprocess.CalledProcessError: break time.sleep(0.1) with self.assertRaises(subprocess.CalledProcessError): signal_script() with transaction_cursor(self.connection) as cursor: cursor.execute( 'SELECT COUNT(*) FROM scoring_flag' ' WHERE placement_start IS NOT NULL AND placement_end IS NULL' ) self.assertEqual(cursor.fetchone()[0], 1) cursor.execute('SELECT COUNT(*) FROM scoring_statuscheck') self.assertEqual(cursor.fetchone()[0], 0) warning_mock.assert_called_with('Terminating all %d Runner processes', 1) del os.environ['CHECKERSCRIPT_PIDFILE'] checkerscript_pidfile.close()
def test_state(self, monotonic_mock): checkerscript_path = os.path.join( os.path.dirname(__file__), 'integration_state_checkerscript.py') monotonic_mock.return_value = 10 master_loop = MasterLoop(self.connection, self.state_db_conn, 'service1', checkerscript_path, None, 2, 1, 10, '0.0.%s.1', b'secret', {}, DummyQueue()) with transaction_cursor(self.state_db_conn) as cursor: # Prepopulate state for the non-checked service to ensure we'll never get this data returned data = 'gAN9cQBYAwAAAGZvb3EBWAMAAABiYXJxAnMu' cursor.execute( 'INSERT INTO checkerstate (team_net_no, service_id, identifier, data)' ' VALUES (92, 2, %s, %s), (93, 2, %s, %s)', ('key1', data, 'key2', data)) # Tick 0 with transaction_cursor(self.connection) as cursor: cursor.execute('UPDATE scoring_gamecontrol SET start=NOW()') cursor.execute('UPDATE scoring_gamecontrol SET current_tick=0') cursor.execute( 'INSERT INTO scoring_flag (service_id, protecting_team_id, tick)' ' VALUES (1, 2, 0), (1, 3, 0)') monotonic_mock.return_value = 20 master_loop.supervisor.queue_timeout = 0.01 self.assertFalse(master_loop.step()) monotonic_mock.return_value = 100 master_loop.supervisor.queue_timeout = 10 while master_loop.step() or master_loop.get_running_script_count() > 0: pass with transaction_cursor(self.connection) as cursor: cursor.execute('SELECT COUNT(*) FROM scoring_flag' ' WHERE placement_end IS NOT NULL') self.assertEqual(cursor.fetchone()[0], 2) cursor.execute( 'SELECT COUNT(*) FROM scoring_statuscheck WHERE status=%s', (CheckResult.OK.value, )) self.assertEqual(cursor.fetchone()[0], 2) # Tick 1 with transaction_cursor(self.connection) as cursor: cursor.execute('UPDATE scoring_gamecontrol SET current_tick=1') cursor.execute( 'INSERT INTO scoring_flag (service_id, protecting_team_id, tick)' ' VALUES (1, 2, 1), (1, 3, 1)') monotonic_mock.return_value = 200 master_loop.supervisor.queue_timeout = 0.01 self.assertFalse(master_loop.step()) monotonic_mock.return_value = 280 master_loop.supervisor.queue_timeout = 10 while master_loop.step() or master_loop.get_running_script_count() > 0: pass with transaction_cursor(self.connection) as cursor: cursor.execute('SELECT COUNT(*) FROM scoring_flag' ' WHERE placement_end IS NOT NULL') self.assertEqual(cursor.fetchone()[0], 4) cursor.execute( 'SELECT COUNT(*) FROM scoring_statuscheck WHERE status=%s', (CheckResult.OK.value, )) self.assertEqual(cursor.fetchone()[0], 4) # Tick 2 with transaction_cursor(self.connection) as cursor: cursor.execute('UPDATE scoring_gamecontrol SET current_tick=2') cursor.execute( 'INSERT INTO scoring_flag (service_id, protecting_team_id, tick)' ' VALUES (1, 2, 2), (1, 3, 2)') monotonic_mock.return_value = 380 master_loop.supervisor.queue_timeout = 0.01 self.assertFalse(master_loop.step()) monotonic_mock.return_value = 460 master_loop.supervisor.queue_timeout = 10 while master_loop.step() or master_loop.get_running_script_count() > 0: pass with transaction_cursor(self.connection) as cursor: cursor.execute('SELECT COUNT(*) FROM scoring_flag' ' WHERE placement_end IS NOT NULL') self.assertEqual(cursor.fetchone()[0], 6) cursor.execute( 'SELECT COUNT(*) FROM scoring_statuscheck WHERE status=%s', (CheckResult.OK.value, )) self.assertEqual(cursor.fetchone()[0], 6)
def test_multi_teams_ticks(self, monotonic_mock): checkerscript_path = os.path.join( os.path.dirname(__file__), 'integration_multi_checkerscript.py') monotonic_mock.return_value = 10 master_loop = MasterLoop(self.connection, self.state_db_conn, 'service1', checkerscript_path, None, 2, 1, 10, '0.0.%s.1', b'secret', {}, DummyQueue()) # Tick 0 with transaction_cursor(self.connection) as cursor: cursor.execute('UPDATE scoring_gamecontrol SET start=NOW()') cursor.execute('UPDATE scoring_gamecontrol SET current_tick=0') # Also add flags for service 2 (which does not get checked) to make sure it won't get touched cursor.execute( 'INSERT INTO scoring_flag (service_id, protecting_team_id, tick)' ' VALUES (1, 2, 0), (1, 3, 0), (2, 2, 0), (2, 3, 0)') monotonic_mock.return_value = 20 master_loop.supervisor.queue_timeout = 0.01 self.assertFalse(master_loop.step()) monotonic_mock.return_value = 100 master_loop.supervisor.queue_timeout = 10 while master_loop.step() or master_loop.get_running_script_count() > 0: pass with transaction_cursor(self.connection) as cursor: cursor.execute('SELECT COUNT(*) FROM scoring_flag' ' WHERE placement_end IS NOT NULL') self.assertEqual(cursor.fetchone()[0], 2) cursor.execute('SELECT COUNT(*) FROM scoring_statuscheck') self.assertEqual(cursor.fetchone()[0], 2) cursor.execute('SELECT status FROM scoring_statuscheck' ' WHERE service_id=1 AND team_id=2 AND tick=0') self.assertEqual(cursor.fetchone()[0], CheckResult.FAULTY.value) cursor.execute('SELECT status FROM scoring_statuscheck' ' WHERE service_id=1 AND team_id=3 AND tick=0') self.assertEqual(cursor.fetchone()[0], CheckResult.OK.value) # Tick 1 with transaction_cursor(self.connection) as cursor: cursor.execute('UPDATE scoring_gamecontrol SET current_tick=1') cursor.execute( 'INSERT INTO scoring_flag (service_id, protecting_team_id, tick)' ' VALUES (1, 2, 1), (1, 3, 1), (2, 2, 1), (2, 3, 1)') monotonic_mock.return_value = 200 master_loop.supervisor.queue_timeout = 0.01 self.assertFalse(master_loop.step()) monotonic_mock.return_value = 280 master_loop.supervisor.queue_timeout = 10 while master_loop.step() or master_loop.get_running_script_count() > 0: pass with transaction_cursor(self.connection) as cursor: cursor.execute('SELECT COUNT(*) FROM scoring_flag' ' WHERE placement_end IS NOT NULL') self.assertEqual(cursor.fetchone()[0], 4) cursor.execute('SELECT COUNT(*) FROM scoring_statuscheck') self.assertEqual(cursor.fetchone()[0], 4) cursor.execute('SELECT status FROM scoring_statuscheck' ' WHERE service_id=1 AND team_id=2 AND tick=0') self.assertEqual(cursor.fetchone()[0], CheckResult.FAULTY.value) cursor.execute('SELECT status FROM scoring_statuscheck' ' WHERE service_id=1 AND team_id=3 AND tick=0') self.assertEqual(cursor.fetchone()[0], CheckResult.OK.value) cursor.execute('SELECT status FROM scoring_statuscheck' ' WHERE service_id=1 AND team_id=2 AND tick=1') self.assertEqual(cursor.fetchone()[0], CheckResult.DOWN.value) cursor.execute('SELECT status FROM scoring_statuscheck' ' WHERE service_id=1 AND team_id=3 AND tick=1') self.assertEqual(cursor.fetchone()[0], CheckResult.FAULTY.value) # Tick 2 with transaction_cursor(self.connection) as cursor: cursor.execute('UPDATE scoring_gamecontrol SET current_tick=2') cursor.execute( 'INSERT INTO scoring_flag (service_id, protecting_team_id, tick)' ' VALUES (1, 2, 2), (1, 3, 2), (2, 2, 2), (2, 3, 2)') monotonic_mock.return_value = 380 master_loop.supervisor.queue_timeout = 0.01 self.assertFalse(master_loop.step()) monotonic_mock.return_value = 460 master_loop.supervisor.queue_timeout = 10 while master_loop.step() or master_loop.get_running_script_count() > 0: pass with transaction_cursor(self.connection) as cursor: cursor.execute('SELECT COUNT(*) FROM scoring_flag' ' WHERE placement_end IS NOT NULL') self.assertEqual(cursor.fetchone()[0], 6) cursor.execute('SELECT COUNT(*) FROM scoring_statuscheck') self.assertEqual(cursor.fetchone()[0], 6) cursor.execute('SELECT status FROM scoring_statuscheck' ' WHERE service_id=1 AND team_id=2 AND tick=2') self.assertEqual(cursor.fetchone()[0], CheckResult.RECOVERING.value) cursor.execute('SELECT status FROM scoring_statuscheck' ' WHERE service_id=1 AND team_id=3 AND tick=2') self.assertEqual(cursor.fetchone()[0], CheckResult.OK.value)