Files
Unraid-AppOps-Gateway-Public/backend/tests/test_runner_capacity_policy.py
T

190 lines
5.2 KiB
Python

from __future__ import annotations
import importlib.util
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
POLICY_PATH = ROOT / "ops" / "gitea-deploy-broker" / "runner_capacity_policy.py"
@pytest.fixture
def policy_module():
spec = importlib.util.spec_from_file_location("appops_runner_capacity_policy_test", POLICY_PATH)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def _state(policy_module, **overrides):
values = {
"queue_depth": 0,
"oldest_queued_seconds": None,
"load_ratio": 0.30,
"available_memory_bytes": 20 * policy_module.GIB,
"burst_running": False,
"burst_busy": False,
"burst_disabled": True,
"seconds_since_demand": 600,
"seconds_since_action": 600,
"swap_critical": False,
"cache_free_bytes": 100 * policy_module.GIB,
}
values.update(overrides)
return policy_module.BurstCapacityInput(**values)
def test_burst_starts_only_for_queue_demand_with_headroom(policy_module):
decision = policy_module.decide_burst_capacity(
_state(policy_module, queue_depth=2)
)
assert decision.action == "start"
assert decision.reason == "queue_pressure_with_host_headroom"
assert decision.pressure == "headroom"
def test_old_job_can_trigger_burst_even_with_single_queued_job(policy_module):
decision = policy_module.decide_burst_capacity(
_state(policy_module, queue_depth=1, oldest_queued_seconds=60)
)
assert decision.action == "start"
@pytest.mark.parametrize(
("load_ratio", "memory_gib", "swap_critical"),
[
(0.75, 20, False),
(0.30, 10, False),
(0.30, 20, True),
],
)
def test_burst_does_not_start_without_headroom(
policy_module, load_ratio, memory_gib, swap_critical
):
decision = policy_module.decide_burst_capacity(
_state(
policy_module,
queue_depth=3,
load_ratio=load_ratio,
available_memory_bytes=memory_gib * policy_module.GIB,
swap_critical=swap_critical,
)
)
assert decision.action == "hold"
assert decision.reason == "queue_pressure_but_host_headroom_insufficient"
def test_high_pressure_drains_busy_burst_instead_of_stopping_it(policy_module):
decision = policy_module.decide_burst_capacity(
_state(
policy_module,
queue_depth=3,
load_ratio=0.95,
burst_running=True,
burst_busy=True,
burst_disabled=False,
)
)
assert decision.action == "drain"
assert decision.reason == "host_pressure_drain_after_current_job"
def test_high_pressure_stops_only_already_drained_idle_burst(policy_module):
decision = policy_module.decide_burst_capacity(
_state(
policy_module,
load_ratio=0.95,
burst_running=True,
burst_busy=False,
burst_disabled=True,
seconds_since_action=600,
)
)
assert decision.action == "stop"
assert decision.reason == "host_pressure_idle_burst_can_stop"
def test_idle_burst_is_drained_before_stop(policy_module):
first = policy_module.decide_burst_capacity(
_state(
policy_module,
burst_running=True,
burst_disabled=False,
seconds_since_demand=600,
seconds_since_action=600,
)
)
second = policy_module.decide_burst_capacity(
_state(
policy_module,
burst_running=True,
burst_disabled=True,
seconds_since_demand=600,
seconds_since_action=600,
)
)
assert first.action == "drain"
assert second.action == "stop"
def test_disabled_running_burst_reenables_when_demand_returns(policy_module):
decision = policy_module.decide_burst_capacity(
_state(
policy_module,
queue_depth=2,
burst_running=True,
burst_disabled=True,
seconds_since_action=600,
)
)
assert decision.action == "enable"
def test_start_cooldown_prevents_flapping(policy_module):
decision = policy_module.decide_burst_capacity(
_state(
policy_module,
queue_depth=2,
seconds_since_action=30,
)
)
assert decision.action == "hold"
def test_burst_does_not_start_when_cache_headroom_is_low(policy_module):
decision = policy_module.decide_burst_capacity(
_state(
policy_module,
queue_depth=3,
cache_free_bytes=20 * policy_module.GIB,
)
)
assert decision.action == "hold"
assert decision.reason == "queue_pressure_but_host_headroom_insufficient"
def test_low_cache_headroom_drains_busy_burst(policy_module):
decision = policy_module.decide_burst_capacity(
_state(
policy_module,
queue_depth=3,
burst_running=True,
burst_busy=True,
burst_disabled=False,
cache_free_bytes=10 * policy_module.GIB,
)
)
assert decision.action == "drain"
assert decision.pressure == "critical"