Scale workers (#2617)
This commit is contained in:
@@ -74,7 +74,9 @@ def test_send_inspect_conn_refused(socket: Mock, sys: Mock, caplog):
|
||||
|
||||
|
||||
@patch("sanic.worker.inspector.configure_socket")
|
||||
@pytest.mark.parametrize("action", (b"reload", b"shutdown", b"foo"))
|
||||
@pytest.mark.parametrize(
|
||||
"action", (b"reload", b"shutdown", b"scale=5", b"foo")
|
||||
)
|
||||
def test_run_inspector(configure_socket: Mock, action: bytes):
|
||||
sock = Mock()
|
||||
conn = Mock()
|
||||
@@ -83,6 +85,7 @@ def test_run_inspector(configure_socket: Mock, action: bytes):
|
||||
inspector = Inspector(Mock(), {}, {}, "localhost", 9999)
|
||||
inspector.reload = Mock() # type: ignore
|
||||
inspector.shutdown = Mock() # type: ignore
|
||||
inspector.scale = Mock() # type: ignore
|
||||
inspector.state_to_json = Mock(return_value="foo") # type: ignore
|
||||
|
||||
def accept():
|
||||
@@ -98,20 +101,26 @@ def test_run_inspector(configure_socket: Mock, action: bytes):
|
||||
)
|
||||
conn.recv.assert_called_with(64)
|
||||
|
||||
conn.send.assert_called_with(b"\n")
|
||||
if action == b"reload":
|
||||
conn.send.assert_called_with(b"\n")
|
||||
inspector.reload.assert_called()
|
||||
inspector.shutdown.assert_not_called()
|
||||
inspector.scale.assert_not_called()
|
||||
inspector.state_to_json.assert_not_called()
|
||||
elif action == b"shutdown":
|
||||
conn.send.assert_called_with(b"\n")
|
||||
inspector.reload.assert_not_called()
|
||||
inspector.shutdown.assert_called()
|
||||
inspector.scale.assert_not_called()
|
||||
inspector.state_to_json.assert_not_called()
|
||||
else:
|
||||
conn.send.assert_called_with(b'"foo"')
|
||||
elif action.startswith(b"scale"):
|
||||
inspector.reload.assert_not_called()
|
||||
inspector.shutdown.assert_not_called()
|
||||
inspector.scale.assert_called_once_with(5)
|
||||
inspector.state_to_json.assert_not_called()
|
||||
else:
|
||||
inspector.reload.assert_not_called()
|
||||
inspector.shutdown.assert_not_called()
|
||||
inspector.scale.assert_not_called()
|
||||
inspector.state_to_json.assert_called()
|
||||
|
||||
|
||||
@@ -165,3 +174,11 @@ def test_shutdown():
|
||||
inspector.shutdown()
|
||||
|
||||
publisher.send.assert_called_once_with("__TERMINATE__")
|
||||
|
||||
|
||||
def test_scale():
|
||||
publisher = Mock()
|
||||
inspector = Inspector(publisher, {}, {}, "", 0)
|
||||
inspector.scale(3)
|
||||
|
||||
publisher.send.assert_called_once_with("__SCALE__:3")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from logging import ERROR, INFO
|
||||
from signal import SIGINT, SIGKILL
|
||||
from unittest.mock import Mock, call, patch
|
||||
|
||||
@@ -14,14 +15,7 @@ def fake_serve():
|
||||
def test_manager_no_workers():
|
||||
message = "Cannot serve with no workers"
|
||||
with pytest.raises(RuntimeError, match=message):
|
||||
WorkerManager(
|
||||
0,
|
||||
fake_serve,
|
||||
{},
|
||||
Mock(),
|
||||
(Mock(), Mock()),
|
||||
{},
|
||||
)
|
||||
WorkerManager(0, fake_serve, {}, Mock(), (Mock(), Mock()), {})
|
||||
|
||||
|
||||
@patch("sanic.worker.process.os")
|
||||
@@ -30,14 +24,7 @@ def test_terminate(os_mock: Mock):
|
||||
process.pid = 1234
|
||||
context = Mock()
|
||||
context.Process.return_value = process
|
||||
manager = WorkerManager(
|
||||
1,
|
||||
fake_serve,
|
||||
{},
|
||||
context,
|
||||
(Mock(), Mock()),
|
||||
{},
|
||||
)
|
||||
manager = WorkerManager(1, fake_serve, {}, context, (Mock(), Mock()), {})
|
||||
assert manager.terminated is False
|
||||
manager.terminate()
|
||||
assert manager.terminated is True
|
||||
@@ -51,14 +38,7 @@ def test_shutown(os_mock: Mock):
|
||||
process.is_alive.return_value = True
|
||||
context = Mock()
|
||||
context.Process.return_value = process
|
||||
manager = WorkerManager(
|
||||
1,
|
||||
fake_serve,
|
||||
{},
|
||||
context,
|
||||
(Mock(), Mock()),
|
||||
{},
|
||||
)
|
||||
manager = WorkerManager(1, fake_serve, {}, context, (Mock(), Mock()), {})
|
||||
manager.shutdown()
|
||||
os_mock.kill.assert_called_once_with(1234, SIGINT)
|
||||
|
||||
@@ -69,14 +49,7 @@ def test_kill(os_mock: Mock):
|
||||
process.pid = 1234
|
||||
context = Mock()
|
||||
context.Process.return_value = process
|
||||
manager = WorkerManager(
|
||||
1,
|
||||
fake_serve,
|
||||
{},
|
||||
context,
|
||||
(Mock(), Mock()),
|
||||
{},
|
||||
)
|
||||
manager = WorkerManager(1, fake_serve, {}, context, (Mock(), Mock()), {})
|
||||
with pytest.raises(ServerKilled):
|
||||
manager.kill()
|
||||
os_mock.kill.assert_called_once_with(1234, SIGKILL)
|
||||
@@ -87,14 +60,7 @@ def test_restart_all():
|
||||
p2 = Mock()
|
||||
context = Mock()
|
||||
context.Process.side_effect = [p1, p2, p1, p2]
|
||||
manager = WorkerManager(
|
||||
2,
|
||||
fake_serve,
|
||||
{},
|
||||
context,
|
||||
(Mock(), Mock()),
|
||||
{},
|
||||
)
|
||||
manager = WorkerManager(2, fake_serve, {}, context, (Mock(), Mock()), {})
|
||||
assert len(list(manager.transient_processes))
|
||||
manager.restart()
|
||||
p1.terminate.assert_called_once()
|
||||
@@ -136,14 +102,7 @@ def test_monitor_all():
|
||||
sub.recv.side_effect = ["__ALL_PROCESSES__:", ""]
|
||||
context = Mock()
|
||||
context.Process.side_effect = [p1, p2]
|
||||
manager = WorkerManager(
|
||||
2,
|
||||
fake_serve,
|
||||
{},
|
||||
context,
|
||||
(Mock(), sub),
|
||||
{},
|
||||
)
|
||||
manager = WorkerManager(2, fake_serve, {}, context, (Mock(), sub), {})
|
||||
manager.restart = Mock() # type: ignore
|
||||
manager.wait_for_ack = Mock() # type: ignore
|
||||
manager.monitor()
|
||||
@@ -160,14 +119,7 @@ def test_monitor_all_with_files():
|
||||
sub.recv.side_effect = ["__ALL_PROCESSES__:foo,bar", ""]
|
||||
context = Mock()
|
||||
context.Process.side_effect = [p1, p2]
|
||||
manager = WorkerManager(
|
||||
2,
|
||||
fake_serve,
|
||||
{},
|
||||
context,
|
||||
(Mock(), sub),
|
||||
{},
|
||||
)
|
||||
manager = WorkerManager(2, fake_serve, {}, context, (Mock(), sub), {})
|
||||
manager.restart = Mock() # type: ignore
|
||||
manager.wait_for_ack = Mock() # type: ignore
|
||||
manager.monitor()
|
||||
@@ -185,14 +137,7 @@ def test_monitor_one_process():
|
||||
sub.recv.side_effect = [f"{p1.name}:foo,bar", ""]
|
||||
context = Mock()
|
||||
context.Process.side_effect = [p1, p2]
|
||||
manager = WorkerManager(
|
||||
2,
|
||||
fake_serve,
|
||||
{},
|
||||
context,
|
||||
(Mock(), sub),
|
||||
{},
|
||||
)
|
||||
manager = WorkerManager(2, fake_serve, {}, context, (Mock(), sub), {})
|
||||
manager.restart = Mock() # type: ignore
|
||||
manager.wait_for_ack = Mock() # type: ignore
|
||||
manager.monitor()
|
||||
@@ -204,16 +149,94 @@ def test_monitor_one_process():
|
||||
|
||||
def test_shutdown_signal():
|
||||
pub = Mock()
|
||||
manager = WorkerManager(
|
||||
1,
|
||||
fake_serve,
|
||||
{},
|
||||
Mock(),
|
||||
(pub, Mock()),
|
||||
{},
|
||||
)
|
||||
manager = WorkerManager(1, fake_serve, {}, Mock(), (pub, Mock()), {})
|
||||
manager.shutdown = Mock() # type: ignore
|
||||
|
||||
manager.shutdown_signal(SIGINT, None)
|
||||
pub.send.assert_called_with(None)
|
||||
manager.shutdown.assert_called_once_with()
|
||||
|
||||
|
||||
def test_shutdown_servers(caplog):
|
||||
p1 = Mock()
|
||||
p1.pid = 1234
|
||||
context = Mock()
|
||||
context.Process.side_effect = [p1]
|
||||
pub = Mock()
|
||||
manager = WorkerManager(1, fake_serve, {}, context, (pub, Mock()), {})
|
||||
|
||||
with patch("os.kill") as kill:
|
||||
with caplog.at_level(ERROR):
|
||||
manager.shutdown_server()
|
||||
|
||||
kill.assert_called_once_with(1234, SIGINT)
|
||||
kill.reset_mock()
|
||||
|
||||
assert not caplog.record_tuples
|
||||
|
||||
manager.shutdown_server()
|
||||
|
||||
kill.assert_not_called()
|
||||
|
||||
assert (
|
||||
"sanic.error",
|
||||
ERROR,
|
||||
"Server shutdown failed because a server was not found.",
|
||||
) in caplog.record_tuples
|
||||
|
||||
|
||||
def test_shutdown_servers_named():
|
||||
p1 = Mock()
|
||||
p1.pid = 1234
|
||||
p2 = Mock()
|
||||
p2.pid = 6543
|
||||
context = Mock()
|
||||
context.Process.side_effect = [p1, p2]
|
||||
pub = Mock()
|
||||
manager = WorkerManager(2, fake_serve, {}, context, (pub, Mock()), {})
|
||||
|
||||
with patch("os.kill") as kill:
|
||||
with pytest.raises(KeyError):
|
||||
manager.shutdown_server("foo")
|
||||
manager.shutdown_server("Server-1")
|
||||
|
||||
kill.assert_called_once_with(6543, SIGINT)
|
||||
|
||||
|
||||
def test_scale(caplog):
|
||||
p1 = Mock()
|
||||
p1.pid = 1234
|
||||
p2 = Mock()
|
||||
p2.pid = 3456
|
||||
p3 = Mock()
|
||||
p3.pid = 5678
|
||||
context = Mock()
|
||||
context.Process.side_effect = [p1, p2, p3]
|
||||
pub = Mock()
|
||||
manager = WorkerManager(1, fake_serve, {}, context, (pub, Mock()), {})
|
||||
|
||||
assert len(manager.transient) == 1
|
||||
|
||||
manager.scale(3)
|
||||
assert len(manager.transient) == 3
|
||||
|
||||
with patch("os.kill") as kill:
|
||||
manager.scale(2)
|
||||
assert len(manager.transient) == 2
|
||||
|
||||
manager.scale(1)
|
||||
assert len(manager.transient) == 1
|
||||
|
||||
kill.call_count == 2
|
||||
|
||||
with caplog.at_level(INFO):
|
||||
manager.scale(1)
|
||||
|
||||
assert (
|
||||
"sanic.root",
|
||||
INFO,
|
||||
"No change needed. There are already 1 workers.",
|
||||
) in caplog.record_tuples
|
||||
|
||||
with pytest.raises(ValueError, match=r"Cannot scale to 0 workers\."):
|
||||
manager.scale(0)
|
||||
|
||||
@@ -108,6 +108,11 @@ def test_terminate(monitor_publisher: Mock, m: WorkerMultiplexer):
|
||||
monitor_publisher.send.assert_called_once_with("__TERMINATE__")
|
||||
|
||||
|
||||
def test_scale(monitor_publisher: Mock, m: WorkerMultiplexer):
|
||||
m.scale(99)
|
||||
monitor_publisher.send.assert_called_once_with("__SCALE__:99")
|
||||
|
||||
|
||||
def test_properties(
|
||||
monitor_publisher: Mock, worker_state: Dict[str, Any], m: WorkerMultiplexer
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user