def setUp(self) -> None: self.monitor = GunicornMonitor( gunicorn_master_pid=1, num_workers_expected=4, master_timeout=60, worker_refresh_interval=60, worker_refresh_batch_size=2, reload_on_plugin_change=True, ) mock.patch.object(self.monitor, '_generate_plugin_state', return_value={}).start() mock.patch.object(self.monitor, '_get_num_ready_workers_running', return_value=4).start() mock.patch.object(self.monitor, '_get_num_workers_running', return_value=4).start() mock.patch.object(self.monitor, '_spawn_new_workers', return_value=None).start() mock.patch.object(self.monitor, '_kill_old_workers', return_value=None).start() mock.patch.object(self.monitor, '_reload_gunicorn', return_value=None).start()
def setUp(self): self.children = mock.MagicMock() self.child = mock.MagicMock() self.process = mock.MagicMock() self.monitor = GunicornMonitor( gunicorn_master_pid=1, num_workers_expected=4, master_timeout=60, worker_refresh_interval=60, worker_refresh_batch_size=2, reload_on_plugin_change=True, )
class TestCLIGetNumReadyWorkersRunning(unittest.TestCase): @classmethod def setUpClass(cls): cls.parser = cli_parser.get_parser() def setUp(self): self.gunicorn_master_proc = mock.Mock(pid=2137) self.children = mock.MagicMock() self.child = mock.MagicMock() self.process = mock.MagicMock() self.monitor = GunicornMonitor( gunicorn_master_proc=self.gunicorn_master_proc, num_workers_expected=4, master_timeout=60, worker_refresh_interval=60, worker_refresh_batch_size=2, reload_on_plugin_change=True, ) def test_ready_prefix_on_cmdline(self): self.child.cmdline.return_value = [ settings.GUNICORN_WORKER_READY_PREFIX ] self.process.children.return_value = [self.child] with mock.patch('psutil.Process', return_value=self.process): self.assertEqual(self.monitor._get_num_ready_workers_running(), 1) def test_ready_prefix_on_cmdline_no_children(self): self.process.children.return_value = [] with mock.patch('psutil.Process', return_value=self.process): self.assertEqual(self.monitor._get_num_ready_workers_running(), 0) def test_ready_prefix_on_cmdline_zombie(self): self.child.cmdline.return_value = [] self.process.children.return_value = [self.child] with mock.patch('psutil.Process', return_value=self.process): self.assertEqual(self.monitor._get_num_ready_workers_running(), 0) def test_ready_prefix_on_cmdline_dead_process(self): self.child.cmdline.side_effect = psutil.NoSuchProcess(11347) self.process.children.return_value = [self.child] with mock.patch('psutil.Process', return_value=self.process): self.assertEqual(self.monitor._get_num_ready_workers_running(), 0)
def test_should_detect_changes_in_directory(self): with tempfile.TemporaryDirectory() as tempdir, mock.patch( "airflow.cli.commands.webserver_command.settings.PLUGINS_FOLDER", tempdir): self._prepare_test_file(f"{tempdir}/file1.txt", 100) self._prepare_test_file( f"{tempdir}/nested/nested/nested/nested/file2.txt", 200) self._prepare_test_file(f"{tempdir}/file3.txt", 300) monitor = GunicornMonitor( gunicorn_master_pid=1, num_workers_expected=4, master_timeout=60, worker_refresh_interval=60, worker_refresh_batch_size=2, reload_on_plugin_change=True, ) # When the files have not changed, the result should be constant state_a = monitor._generate_plugin_state() state_b = monitor._generate_plugin_state() assert state_a == state_b assert 3 == len(state_a) # Should detect new file self._prepare_test_file(f"{tempdir}/file4.txt", 400) state_c = monitor._generate_plugin_state() assert state_b != state_c assert 4 == len(state_c) # Should detect changes in files self._prepare_test_file(f"{tempdir}/file4.txt", 450) state_d = monitor._generate_plugin_state() assert state_c != state_d assert 4 == len(state_d) # Should support large files self._prepare_test_file(f"{tempdir}/file4.txt", 4000000) state_d = monitor._generate_plugin_state() assert state_c != state_d assert 4 == len(state_d)
class TestGunicornMonitor(unittest.TestCase): def setUp(self) -> None: self.monitor = GunicornMonitor( gunicorn_master_pid=1, num_workers_expected=4, master_timeout=60, worker_refresh_interval=60, worker_refresh_batch_size=2, reload_on_plugin_change=True, ) mock.patch.object(self.monitor, '_generate_plugin_state', return_value={}).start() mock.patch.object(self.monitor, '_get_num_ready_workers_running', return_value=4).start() mock.patch.object(self.monitor, '_get_num_workers_running', return_value=4).start() mock.patch.object(self.monitor, '_spawn_new_workers', return_value=None).start() mock.patch.object(self.monitor, '_kill_old_workers', return_value=None).start() mock.patch.object(self.monitor, '_reload_gunicorn', return_value=None).start() @mock.patch('airflow.cli.commands.webserver_command.sleep') def test_should_wait_for_workers_to_start(self, mock_sleep): self.monitor._get_num_ready_workers_running.return_value = 0 self.monitor._get_num_workers_running.return_value = 4 self.monitor._check_workers() self.monitor._spawn_new_workers.assert_not_called() # pylint: disable=no-member self.monitor._kill_old_workers.assert_not_called() # pylint: disable=no-member self.monitor._reload_gunicorn.assert_not_called() # pylint: disable=no-member @mock.patch('airflow.cli.commands.webserver_command.sleep') def test_should_kill_excess_workers(self, mock_sleep): self.monitor._get_num_ready_workers_running.return_value = 10 self.monitor._get_num_workers_running.return_value = 10 self.monitor._check_workers() self.monitor._spawn_new_workers.assert_not_called() # pylint: disable=no-member self.monitor._kill_old_workers.assert_called_once_with(2) # pylint: disable=no-member self.monitor._reload_gunicorn.assert_not_called() # pylint: disable=no-member @mock.patch('airflow.cli.commands.webserver_command.sleep') def test_should_start_new_workers_when_missing(self, mock_sleep): self.monitor._get_num_ready_workers_running.return_value = 3 self.monitor._get_num_workers_running.return_value = 3 self.monitor._check_workers() # missing one worker, starting just 1 self.monitor._spawn_new_workers.assert_called_once_with(1) # pylint: disable=no-member self.monitor._kill_old_workers.assert_not_called() # pylint: disable=no-member self.monitor._reload_gunicorn.assert_not_called() # pylint: disable=no-member @mock.patch('airflow.cli.commands.webserver_command.sleep') def test_should_start_new_batch_when_missing_many_workers( self, mock_sleep): self.monitor._get_num_ready_workers_running.return_value = 1 self.monitor._get_num_workers_running.return_value = 1 self.monitor._check_workers() # missing 3 workers, but starting single batch (2) self.monitor._spawn_new_workers.assert_called_once_with(2) # pylint: disable=no-member self.monitor._kill_old_workers.assert_not_called() # pylint: disable=no-member self.monitor._reload_gunicorn.assert_not_called() # pylint: disable=no-member @mock.patch('airflow.cli.commands.webserver_command.sleep') def test_should_start_new_workers_when_refresh_interval_has_passed( self, mock_sleep): self.monitor._last_refresh_time -= 200 self.monitor._check_workers() self.monitor._spawn_new_workers.assert_called_once_with(2) # pylint: disable=no-member self.monitor._kill_old_workers.assert_not_called() # pylint: disable=no-member self.monitor._reload_gunicorn.assert_not_called() # pylint: disable=no-member assert abs(self.monitor._last_refresh_time - time.monotonic()) < 5 @mock.patch('airflow.cli.commands.webserver_command.sleep') def test_should_reload_when_plugin_has_been_changed(self, mock_sleep): self.monitor._generate_plugin_state.return_value = {'AA': 12} self.monitor._check_workers() self.monitor._spawn_new_workers.assert_not_called() # pylint: disable=no-member self.monitor._kill_old_workers.assert_not_called() # pylint: disable=no-member self.monitor._reload_gunicorn.assert_not_called() # pylint: disable=no-member self.monitor._generate_plugin_state.return_value = {'AA': 32} self.monitor._check_workers() self.monitor._spawn_new_workers.assert_not_called() # pylint: disable=no-member self.monitor._kill_old_workers.assert_not_called() # pylint: disable=no-member self.monitor._reload_gunicorn.assert_not_called() # pylint: disable=no-member self.monitor._generate_plugin_state.return_value = {'AA': 32} self.monitor._check_workers() self.monitor._spawn_new_workers.assert_not_called() # pylint: disable=no-member self.monitor._kill_old_workers.assert_not_called() # pylint: disable=no-member self.monitor._reload_gunicorn.assert_called_once_with() # pylint: disable=no-member assert abs(self.monitor._last_refresh_time - time.monotonic()) < 5