diff --git a/src/robusta/core/discovery/discovery.py b/src/robusta/core/discovery/discovery.py index d3277a864..ec11a25d2 100644 --- a/src/robusta/core/discovery/discovery.py +++ b/src/robusta/core/discovery/discovery.py @@ -897,12 +897,13 @@ def extract_containers_k8(resource) -> List[Container]: def is_pod_ready(pod) -> bool: + # Unset until kubelet reports on the pod. conditions = [] if isinstance(pod, V1Pod): - conditions = pod.status.conditions + conditions = getattr(pod.status, "conditions", None) or [] if isinstance(pod, Pod): - conditions = pod.status.conditions + conditions = getattr(pod.status, "conditions", None) or [] for condition in conditions: if condition.type == "Ready": diff --git a/tests/discovery/test_discovery.py b/tests/discovery/test_discovery.py index b50317eb5..88796eb48 100644 --- a/tests/discovery/test_discovery.py +++ b/tests/discovery/test_discovery.py @@ -1,3 +1,4 @@ +import logging import signal from concurrent.futures import ProcessPoolExecutor from contextlib import contextmanager @@ -7,9 +8,10 @@ import kubernetes import pytest +from kubernetes.client import V1ObjectMeta, V1Pod, V1PodCondition, V1PodStatus from kubernetes.client.exceptions import ApiException -from robusta.core.discovery.discovery import Discovery +from robusta.core.discovery.discovery import Discovery, extract_ready_pods, is_pod_ready # pytest-timeout requires pytest>=7, https://github.com/pytest-dev/pytest-timeout/blob/main/setup.cfg @@ -42,3 +44,34 @@ def test_discovery_recovery_on_failure(): assert patched_pool._shutdown_thread assert not Discovery.executor._shutdown_thread + + +# ---- is_pod_ready with an unpopulated status ---- + + +def _pod(status: Any) -> V1Pod: + return V1Pod(metadata=V1ObjectMeta(name="p", namespace="kube-system"), status=status) + + +@pytest.mark.parametrize("status", [None, V1PodStatus(conditions=None), V1PodStatus(conditions=[])]) +def test_is_pod_ready_handles_unpopulated_status(status: Any): + """Regression: a pod kubelet has not reported on yet raised TypeError, which + extract_ready_pods logged as an ERROR with the whole pod dumped into it.""" + assert is_pod_ready(_pod(status)) is False + + +@pytest.mark.parametrize("condition_status,expected", [("True", True), ("False", False)]) +def test_is_pod_ready_reads_ready_condition(condition_status: str, expected: bool): + pod = _pod(V1PodStatus(conditions=[V1PodCondition(type="Ready", status=condition_status)])) + assert is_pod_ready(pod) is expected + + +def test_is_pod_ready_ignores_other_conditions(): + pod = _pod(V1PodStatus(conditions=[V1PodCondition(type="PodScheduled", status="True")])) + assert is_pod_ready(pod) is False + + +def test_extract_ready_pods_does_not_log_for_unpopulated_status(caplog): + with caplog.at_level(logging.ERROR): + assert extract_ready_pods(_pod(None)) == 0 + assert not caplog.records