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())
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)