Co-authored-by: Kangyan Zhou <kangyan.zhou@radixark.ai> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
150 lines
4.5 KiB
Python
150 lines
4.5 KiB
Python
import json
|
|
from types import SimpleNamespace
|
|
|
|
import conftest as k8s_conftest
|
|
import pytest
|
|
|
|
|
|
def _pod(name: str, phase: str, ready: bool) -> dict:
|
|
return {
|
|
"metadata": {"name": name},
|
|
"status": {
|
|
"phase": phase,
|
|
"conditions": [
|
|
{
|
|
"type": "Ready",
|
|
"status": "True" if ready else "False",
|
|
}
|
|
],
|
|
},
|
|
}
|
|
|
|
|
|
def test_wait_for_replacement_pod_ignores_old_and_pending_pods(monkeypatch):
|
|
old_pod = "sgl-router-old"
|
|
new_pod = "sgl-router-new"
|
|
responses = iter(
|
|
[
|
|
[_pod(old_pod, "Running", True)],
|
|
[
|
|
_pod(old_pod, "Running", True),
|
|
_pod(new_pod, "Running", True),
|
|
],
|
|
[_pod(new_pod, "Pending", False)],
|
|
[_pod(new_pod, "Running", True)],
|
|
]
|
|
)
|
|
calls = []
|
|
|
|
def fake_kubectl(*args, **kwargs):
|
|
calls.append((args, kwargs))
|
|
return SimpleNamespace(stdout=json.dumps({"items": next(responses)}))
|
|
|
|
monkeypatch.setattr(k8s_conftest, "_kubectl", fake_kubectl)
|
|
monkeypatch.setattr(k8s_conftest.time, "sleep", lambda _: None)
|
|
|
|
replacement = k8s_conftest._wait_for_replacement_pod_ready(
|
|
old_pod,
|
|
"app=sgl-router",
|
|
timeout=5,
|
|
interval=0,
|
|
)
|
|
|
|
assert replacement == new_pod
|
|
assert len(calls) == 4
|
|
assert all("-o" in args and "json" in args for args, _ in calls)
|
|
|
|
|
|
def _stub_kubectl(monkeypatch, payload):
|
|
"""Point every conftest helper at a canned kubectl response. `payload` is
|
|
the object kubectl would have printed."""
|
|
monkeypatch.setattr(
|
|
k8s_conftest,
|
|
"_kubectl",
|
|
lambda *args, **kwargs: SimpleNamespace(stdout=json.dumps(payload)),
|
|
)
|
|
|
|
|
|
def test_pod_names_excludes_terminating_pods(monkeypatch):
|
|
terminating = _pod("sgl-router-old", "Running", True)
|
|
terminating["metadata"]["deletionTimestamp"] = "2026-01-01T00:00:00Z"
|
|
_stub_kubectl(
|
|
monkeypatch,
|
|
{"items": [terminating, _pod("sgl-router-new", "Running", True)]},
|
|
)
|
|
|
|
assert k8s_conftest._pod_names("app=sgl-router") == ["sgl-router-new"]
|
|
|
|
|
|
def test_pods_returns_empty_on_a_failed_kubectl(monkeypatch):
|
|
"""The poll loops call this with `check=False` precisely because the API
|
|
server can be briefly unavailable mid-rollout; a non-zero return must read
|
|
as "nothing observed", not raise out of the loop."""
|
|
monkeypatch.setattr(
|
|
k8s_conftest,
|
|
"_kubectl",
|
|
lambda *args, **kwargs: SimpleNamespace(stdout="", returncode=1),
|
|
)
|
|
|
|
assert k8s_conftest._pods("app=sgl-router", check=False) == []
|
|
|
|
|
|
def test_pods_tolerates_empty_stdout(monkeypatch):
|
|
monkeypatch.setattr(
|
|
k8s_conftest,
|
|
"_kubectl",
|
|
lambda *args, **kwargs: SimpleNamespace(stdout=""),
|
|
)
|
|
|
|
assert k8s_conftest._pods("app=sgl-router") == []
|
|
|
|
|
|
def test_container_restart_count_reads_the_named_container(monkeypatch):
|
|
_stub_kubectl(
|
|
monkeypatch,
|
|
{
|
|
"status": {
|
|
"containerStatuses": [
|
|
{"name": "sidecar", "restartCount": 9},
|
|
{"name": "router", "restartCount": 3},
|
|
]
|
|
}
|
|
},
|
|
)
|
|
|
|
assert k8s_conftest._container_restart_count("sgl-router-0", "router") == 3
|
|
|
|
|
|
def test_container_restart_count_rejects_a_missing_container(monkeypatch):
|
|
"""`containerStatuses` lags during a restart, so the absent case is a real
|
|
state — and the drain test reads its whole timing floor off this number.
|
|
Returning 0 there would silently read as "never restarted"."""
|
|
_stub_kubectl(monkeypatch, {"status": {"containerStatuses": []}})
|
|
|
|
with pytest.raises(AssertionError, match="router"):
|
|
k8s_conftest._container_restart_count("sgl-router-0", "router")
|
|
|
|
|
|
def test_pod_ready_condition_reports_the_ready_status(monkeypatch):
|
|
_stub_kubectl(
|
|
monkeypatch,
|
|
{
|
|
"status": {
|
|
"conditions": [
|
|
{"type": "Initialized", "status": "True"},
|
|
{"type": "Ready", "status": "False"},
|
|
]
|
|
}
|
|
},
|
|
)
|
|
|
|
assert k8s_conftest._pod_ready_condition("sgl-router-0") == "False"
|
|
|
|
|
|
def test_pod_ready_condition_is_unknown_before_the_condition_exists(monkeypatch):
|
|
"""A pod whose Ready condition has not been written yet must read as
|
|
"Unknown" rather than crash the drain test's diagnostic logging."""
|
|
_stub_kubectl(monkeypatch, {"status": {"conditions": []}})
|
|
|
|
assert k8s_conftest._pod_ready_condition("sgl-router-0") == "Unknown"
|