ariadne/tests/unit/services/test_cluster_state_kubernetes_domains.py

267 lines
11 KiB
Python

from __future__ import annotations
from ariadne.services import cluster_state_fetchers as fetchers
from ariadne.services import cluster_state_flux_events as flux_events
from ariadne.services import cluster_state_nodes as nodes
from ariadne.services import cluster_state_pods as pods
from ariadne.services import cluster_state_workloads as workloads
def test_node_summary_inventory_and_hardware_usage() -> None:
payload = {
"items": [
{
"metadata": {
"name": "titan-1",
"labels": {"kubernetes.io/arch": "arm64", "hardware": "rpi5"},
"creationTimestamp": "2026-01-01T00:00:00Z",
},
"spec": {"unschedulable": True, "taints": [{"key": "dedicated", "effect": "NoSchedule"}]},
"status": {
"conditions": [
{"type": "Ready", "status": "True"},
{"type": "DiskPressure", "status": "True", "reason": "Full", "message": "disk full"},
],
"capacity": {"cpu": "4", "memory": "8Gi", "pods": "110"},
"allocatable": {"cpu": "3", "memory": "7Gi"},
"nodeInfo": {"architecture": "arm64"},
"addresses": [{"type": "InternalIP", "address": "10.0.0.1"}],
},
},
{"metadata": {"name": "titan-2", "labels": {}}, "status": {"conditions": []}},
]
}
summary = nodes._summarize_nodes(payload)
assert summary["total"] == 2
assert summary["not_ready"] == 1
details = nodes._node_details(payload)
assert details[0]["pressure"]["DiskPressure"] is True
assert details[0]["taints"][0]["key"] == "dedicated"
assert nodes._node_labels({"node-role.kubernetes.io/control-plane": "", "other": "skip"})
assert nodes._node_addresses({"addresses": [{"type": "Hostname", "address": "titan-1"}]}) == {"Hostname": "titan-1"}
assert nodes._node_capacity({"cpu": "4", "unknown": "skip"}) == {"cpu": "4"}
assert nodes._node_pressure_conditions([{"type": "PIDPressure", "status": "True"}])["PIDPressure"] is True
assert nodes._node_roles({"node-role.kubernetes.io/control-plane": ""}) == ["control-plane"]
assert nodes._node_is_worker({"node-role.kubernetes.io/control-plane": ""}) is False
assert nodes._hardware_hint({"jetson": "true"}, {"architecture": "arm64"}) == "jetson"
assert nodes._condition_status([], "Ready") == (None, "", "")
assert nodes._age_hours("not-a-date") is None
assert nodes._node_age_stats(details)["oldest"]
assert nodes._node_flagged(details, "unschedulable") == ["titan-1"]
assert nodes._summarize_inventory(details)["unschedulable_nodes"] == ["titan-1"]
assert nodes._hardware_groups(details)[0]["hardware"]
assert nodes._pressure_summary(nodes._summarize_inventory(details))["total"] == 1
usage = nodes._node_usage_by_hardware([{"node": "titan-1", "cpu": 80.0, "load_index": 0.5}], details)
assert usage[0]["hardware"] == "rpi5"
def test_flux_and_event_summaries() -> None:
flux_payload = {
"items": [
{
"metadata": {"name": "apps", "namespace": "flux-system"},
"spec": {"suspend": False},
"status": {"conditions": [{"type": "Ready", "status": "True"}]},
},
{
"metadata": {"name": "broken", "namespace": "flux-system"},
"spec": {"suspend": True},
"status": {"conditions": [{"type": "Ready", "status": "False", "reason": "Bad", "message": "no"}]},
},
]
}
assert flux_events._summarize_kustomizations(flux_payload)["not_ready"] == 1
assert flux_events._namespace_allowed("apps") is True
assert flux_events._namespace_allowed("kube-system") is False
assert flux_events._event_sort_key("bad") == 0.0
events = flux_events._summarize_events(
{
"items": [
{
"metadata": {"namespace": "apps"},
"type": "Warning",
"reason": "BackOff",
"message": "retry",
"count": 2,
"lastTimestamp": "2026-01-01T00:00:00Z",
"involvedObject": {"kind": "Pod", "name": "api"},
},
{"metadata": {"namespace": "kube-system"}, "type": "Warning", "reason": "Ignored"},
{"metadata": {"namespace": "apps"}, "type": "Normal"},
]
}
)
assert events["warnings_total"] == 1
assert events["warnings_top_reason"]["reason"] == "BackOff"
def test_pod_summaries_and_issue_detection() -> None:
payload = {
"items": [
{
"metadata": {
"name": "api-1",
"namespace": "apps",
"labels": {"app": "api"},
"creationTimestamp": "2026-01-01T00:00:00Z",
},
"spec": {"nodeName": "titan-1"},
"status": {
"phase": "Pending",
"reason": "Unschedulable",
"containerStatuses": [
{"restartCount": 2, "state": {"waiting": {"reason": "CrashLoopBackOff"}}}
],
},
},
{
"metadata": {
"name": "worker-1",
"namespace": "apps",
"ownerReferences": [{"name": "worker", "kind": "ReplicaSet"}],
},
"spec": {"nodeName": "titan-2"},
"status": {"phase": "Running"},
},
]
}
assert pods._workload_from_labels({"app": "api"}) == ("api", "label:app")
assert pods._owner_reference({"ownerReferences": [{"name": "rs", "kind": "ReplicaSet"}]}) == ("rs", "owner:ReplicaSet")
assert pods._pod_workload({"labels": {}, "ownerReferences": [{"name": "rs"}]})[0] == "rs"
assert pods._summarize_workloads(payload)[0]["workload"] == "api"
assert pods._summarize_namespace_pods(payload)[0]["pods_total"] == 2
assert pods._summarize_namespace_nodes(payload)[0]["primary_node"]
node_pods = pods._summarize_node_pods(payload)
assert pods._node_pods_top(node_pods)[0]["node"] == "titan-1"
issues = pods._summarize_pod_issues(payload)
assert issues["counts"]["Pending"] == 1
assert issues["waiting_reasons"]["CrashLoopBackOff"] == 1
def test_pod_summary_edge_filters() -> None:
assert pods._owner_reference({"ownerReferences": [None, {}]}) == ("", "")
payload = {
"items": [
{
"metadata": {"name": "ignored", "namespace": "kube-system", "labels": {"app": "system"}},
"spec": {"nodeName": "titan-1"},
"status": {"phase": "Running"},
},
{
"metadata": {"name": "anonymous", "namespace": "apps"},
"spec": {"nodeName": "titan-1"},
"status": {"phase": "Running"},
},
{
"metadata": {
"name": "failed",
"namespace": "apps",
"ownerReferences": [{"name": "batch", "kind": "Job"}],
},
"spec": {},
"status": {"phase": "Failed", "containerStatuses": [None]},
},
{
"metadata": {"name": "done", "namespace": "apps", "labels": {"app": "done"}},
"spec": {"nodeName": ""},
"status": {"phase": "Succeeded"},
},
{
"metadata": {"name": "waiting", "namespace": "apps", "labels": {"app": "waiting"}, "creationTimestamp": ""},
"spec": {"nodeName": "titan-2"},
"status": {"phase": "Pending", "containerStatuses": [None, {"restartCount": 0, "state": {}}]},
},
{"metadata": {"namespace": "apps"}, "status": {"phase": "Pending"}},
]
}
workloads_summary = pods._summarize_workloads(payload)
assert {entry["workload"] for entry in workloads_summary} == {"batch", "done", "waiting"}
namespace_pods = pods._summarize_namespace_pods(payload)[0]
assert namespace_pods["pods_failed"] == 1
assert namespace_pods["pods_succeeded"] == 1
namespace_nodes = pods._summarize_namespace_nodes(payload)[0]
assert namespace_nodes["primary_node"] == "titan-1"
node_pods = pods._summarize_node_pods(payload)
assert [entry["node"] for entry in node_pods] == ["titan-1", "titan-2"]
assert pods._node_pods_top([None, node_pods[0]])[0]["node"] == "titan-1"
issues = pods._summarize_pod_issues(payload)
assert issues["counts"]["Failed"] == 1
assert issues["pending_over_15m"] == 0
def test_workload_job_longhorn_and_fetch_summaries(monkeypatch) -> None:
jobs = workloads._summarize_jobs(
{
"items": [
{
"metadata": {"name": "backup", "namespace": "apps", "creationTimestamp": "2026-01-01T00:00:00Z"},
"status": {"failed": 1, "succeeded": 0, "active": 1},
}
]
}
)
assert jobs["totals"]["failed"] == 1
deployments = workloads._summarize_deployments(
{"items": [{"metadata": {"name": "api", "namespace": "apps"}, "spec": {"replicas": 2}, "status": {"readyReplicas": 1}}]}
)
statefulsets = workloads._summarize_statefulsets(
{"items": [{"metadata": {"name": "db", "namespace": "apps"}, "spec": {"replicas": 2}, "status": {"readyReplicas": 1}}]}
)
daemonsets = workloads._summarize_daemonsets(
{"items": [{"metadata": {"name": "agent", "namespace": "apps"}, "status": {"desiredNumberScheduled": 2, "numberReady": 1}}]}
)
health = workloads._summarize_workload_health(deployments, statefulsets, daemonsets)
assert health["deployments"]["not_ready"] == 1
longhorn = workloads._summarize_longhorn_volumes(
{
"items": [
{
"metadata": {"name": "pvc-data"},
"spec": {"size": "1Gi"},
"status": {"state": "attached", "robustness": "degraded", "actualSize": "500Mi"},
}
]
}
)
assert longhorn["degraded_count"] == 1
def fake_get_json(path: str):
if path.endswith("/nodes"):
return {"items": []}
if path.startswith("/api/v1/pods"):
return {"items": []}
if path.startswith("/apis/batch"):
return {"items": []}
if "longhorn" in path:
return {"items": []}
if "deployments" in path or "statefulsets" in path or "daemonsets" in path:
return {"items": []}
if path.startswith("/api/v1/events"):
return {"items": []}
return {"items": []}
monkeypatch.setattr(fetchers, "_get_json", fake_get_json)
errors: list[str] = []
assert fetchers._fetch_nodes(errors)[0]["total"] == 0
assert fetchers._fetch_flux(errors)["not_ready"] == 0
assert fetchers._fetch_pods(errors)[0] == []
assert fetchers._fetch_jobs(errors)["totals"]["total"] == 0
assert fetchers._fetch_longhorn(errors) == {}
assert fetchers._fetch_workload_health(errors)["deployments"]["total"] == 0
assert fetchers._fetch_events(errors)["warnings_total"] == 0
assert errors == []
monkeypatch.setattr(fetchers, "_get_json", lambda _path: (_ for _ in ()).throw(RuntimeError("boom")))
errors = []
assert fetchers._fetch_jobs(errors) == {}
assert errors == ["jobs: boom"]