From a27dd22ca199f4fe4d459b76013d27115fd8695f Mon Sep 17 00:00:00 2001 From: phm07 <22707808+phm07@users.noreply.github.com> Date: Fri, 2 Oct 2026 16:08:06 +0200 Subject: [PATCH] feat(load-balancer): add health check diagnostic details --- hcloud/load_balancers/client.py | 2 ++ hcloud/load_balancers/domain.py | 11 +++++++++++ tests/unit/load_balancers/conftest.py | 17 +++++++++++++++-- tests/unit/load_balancers/test_client.py | 6 +++++- 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/hcloud/load_balancers/client.py b/hcloud/load_balancers/client.py index 6dd4672e..e04b9130 100644 --- a/hcloud/load_balancers/client.py +++ b/hcloud/load_balancers/client.py @@ -130,6 +130,8 @@ def _load_balancer_target( LoadBalancerTargetHealthStatus( listen_port=item["listen_port"], status=item["status"], + detail=item.get("detail"), + http_status_code=item.get("http_status_code"), ) for item in raw_health_status ] diff --git a/hcloud/load_balancers/domain.py b/hcloud/load_balancers/domain.py index 1782f9e9..bf329b92 100644 --- a/hcloud/load_balancers/domain.py +++ b/hcloud/load_balancers/domain.py @@ -488,11 +488,18 @@ class LoadBalancerTargetHealthStatus(BaseDomain): :param listen_port: Load Balancer Target listen port :param status: Load Balancer Target status. Choices: healthy, unhealthy, unknown + :param detail: Additional details about why the health check failed. Only present when `status` is `unhealthy`. + Choices: unspecified, layer4_no_connection, layer4_timeout, layer7_timeout, unexpected_http_status, + unexpected_http_content + :param http_status_code: HTTP status code returned by the target during the last health check. Only present when + `status` is `unhealthy` and `detail` is `unexpected_http_status`. """ __api_properties__ = ( "listen_port", "status", + "detail", + "http_status_code", ) __slots__ = __api_properties__ @@ -500,9 +507,13 @@ def __init__( self, listen_port: int | None = None, status: str | None = None, + detail: str | None = None, + http_status_code: int | None = None, ): self.listen_port = listen_port self.status = status + self.detail = detail + self.http_status_code = http_status_code class LoadBalancerTargetLabelSelector(BaseDomain): diff --git a/tests/unit/load_balancers/conftest.py b/tests/unit/load_balancers/conftest.py index d7fe2a90..a3675c59 100644 --- a/tests/unit/load_balancers/conftest.py +++ b/tests/unit/load_balancers/conftest.py @@ -84,7 +84,15 @@ def response_load_balancer(): { "type": "server", "server": {"id": 80}, - "health_status": [{"listen_port": 443, "status": "healthy"}], + "health_status": [ + {"listen_port": 443, "status": "healthy"}, + { + "listen_port": 80, + "status": "unhealthy", + "detail": "unexpected_http_status", + "http_status_code": 503, + }, + ], "label_selector": None, "use_private_ip": False, }, @@ -99,7 +107,12 @@ def response_load_balancer(): "use_private_ip": True, "health_status": [ {"listen_port": 443, "status": "healthy"}, - {"listen_port": 3000, "status": "healthy"}, + { + "listen_port": 3000, + "status": "unhealthy", + "detail": "unexpected_http_status", + "http_status_code": 503, + }, ], } ], diff --git a/tests/unit/load_balancers/test_client.py b/tests/unit/load_balancers/test_client.py index 8ba8b4db..ea5dbfa7 100644 --- a/tests/unit/load_balancers/test_client.py +++ b/tests/unit/load_balancers/test_client.py @@ -82,8 +82,12 @@ def test_init_label_selector_nested_targets(self, response_load_balancer): assert len(nested.health_status) == 2 assert nested.health_status[0].listen_port == 443 assert nested.health_status[0].status == "healthy" + assert nested.health_status[0].detail is None + assert nested.health_status[0].http_status_code is None assert nested.health_status[1].listen_port == 3000 - assert nested.health_status[1].status == "healthy" + assert nested.health_status[1].status == "unhealthy" + assert nested.health_status[1].detail == "unexpected_http_status" + assert nested.health_status[1].http_status_code == 503 class TestLoadBalancerslient: