From 9efcda2af5712cfd40d9231e343d64dff9e2f226 Mon Sep 17 00:00:00 2001 From: averevki Date: Wed, 6 Nov 2024 19:12:49 +0100 Subject: [PATCH] Add DNSPolicy health check tests Signed-off-by: averevki --- testsuite/kuadrant/policy/dns.py | 40 ++++++++++++- .../dnspolicy/health_check/__init__.py | 0 .../dnspolicy/health_check/conftest.py | 56 +++++++++++++++++++ .../health_check/test_healthy_endpoint.py | 25 +++++++++ .../test_healthy_endpoint_http.py | 46 +++++++++++++++ .../health_check/test_remove_endpoint.py | 42 ++++++++++++++ .../health_check/test_unhealthy_endpoint.py | 41 ++++++++++++++ 7 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 testsuite/tests/singlecluster/gateway/dnspolicy/health_check/__init__.py create mode 100644 testsuite/tests/singlecluster/gateway/dnspolicy/health_check/conftest.py create mode 100644 testsuite/tests/singlecluster/gateway/dnspolicy/health_check/test_healthy_endpoint.py create mode 100644 testsuite/tests/singlecluster/gateway/dnspolicy/health_check/test_healthy_endpoint_http.py create mode 100644 testsuite/tests/singlecluster/gateway/dnspolicy/health_check/test_remove_endpoint.py create mode 100644 testsuite/tests/singlecluster/gateway/dnspolicy/health_check/test_unhealthy_endpoint.py diff --git a/testsuite/kuadrant/policy/dns.py b/testsuite/kuadrant/policy/dns.py index 721f762e..d9c8d360 100644 --- a/testsuite/kuadrant/policy/dns.py +++ b/testsuite/kuadrant/policy/dns.py @@ -1,9 +1,10 @@ """Module for DNSPolicy related classes""" from dataclasses import dataclass -from typing import Optional +from typing import Optional, Literal from testsuite.gateway import Referencable +from testsuite.kubernetes import KubernetesObject from testsuite.kubernetes.client import KubernetesClient from testsuite.kuadrant.policy import Policy from testsuite.utils import asdict, check_condition @@ -31,6 +32,33 @@ class LoadBalancing: weight: Optional[int] = None +@dataclass +class AdditionalHeadersRef: + """Object representing DNSPolicy additionalHeadersRef field""" + + name: str + + +@dataclass +class HealthCheck: # pylint: disable=invalid-name + """Object representing DNSPolicy health check specification""" + + additionalHeadersRef: Optional[AdditionalHeadersRef] = None + path: Optional[str] = None + failureThreshold: Optional[int] = None + interval: Optional[str] = None + port: Optional[int] = None + protocol: Literal["HTTP", "HTTPS"] = "HTTP" + + +class DNSHealthCheckProbe(KubernetesObject): + """DNSHealthCheckProbe object""" + + def is_healthy(self) -> bool: + """Returns True if DNSHealthCheckProbe endpoint is healthy""" + return self.refresh().model.status.healthy + + class DNSPolicy(Policy): """DNSPolicy object""" @@ -61,6 +89,16 @@ def create_instance( return cls(model, context=cluster.context) + def set_health_check(self, health_check: HealthCheck): + """Sets health check for DNSPolicy""" + self.model["spec"]["healthCheck"] = asdict(health_check) + + def get_dns_health_probe(self) -> DNSHealthCheckProbe: + """Returns DNSHealthCheckProbe object for the created DNSPolicy""" + with self.context: + dns_probe = self.get_owned("dnsrecords.kuadrant.io")[0].get_owned("DNSHealthCheckProbe")[0] + return DNSHealthCheckProbe(dns_probe.model, context=self.context) + def wait_for_full_enforced(self, timelimit=300): """Wait for a Policy to be fully Enforced with increased timelimit for DNSPolicy""" super().wait_for_full_enforced(timelimit=timelimit) diff --git a/testsuite/tests/singlecluster/gateway/dnspolicy/health_check/__init__.py b/testsuite/tests/singlecluster/gateway/dnspolicy/health_check/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/testsuite/tests/singlecluster/gateway/dnspolicy/health_check/conftest.py b/testsuite/tests/singlecluster/gateway/dnspolicy/health_check/conftest.py new file mode 100644 index 00000000..a9c34478 --- /dev/null +++ b/testsuite/tests/singlecluster/gateway/dnspolicy/health_check/conftest.py @@ -0,0 +1,56 @@ +"""Conftest for DNSPolicy health checks""" + +import pytest + +from testsuite.gateway import Hostname, TLSGatewayListener +from testsuite.gateway.gateway_api.gateway import KuadrantGateway + + +@pytest.fixture(scope="module") +def subdomain(blame): + """Subdomain name that will be added to HTTPRoute""" + return blame("hostname") + + +@pytest.fixture(scope="module") +def hostname(gateway, exposer, subdomain) -> Hostname: + """Exposed Hostname object""" + return exposer.expose_hostname(subdomain, gateway) + + +@pytest.fixture(scope="module") +def gateway(request, cluster, blame, base_domain, module_label, subdomain): + """Returns ready gateway""" + gateway_name = blame("gw") + gw = KuadrantGateway.create_instance( + cluster, + gateway_name, + {"app": module_label}, + ) + gw.add_listener(TLSGatewayListener(hostname=f"{subdomain}.{base_domain}", gateway_name=gateway_name)) + request.addfinalizer(gw.delete) + gw.commit() + gw.wait_for_ready() + return gw + + +@pytest.fixture(scope="module") +def dns_policy(dns_policy, health_check): + """Add health check to DNSPolicy""" + dns_policy.set_health_check(health_check) + return dns_policy + + +@pytest.fixture(scope="module") +def dns_health_probe(dns_policy): + """Return DNSHealthCheckProbe object for created DNSPolicy""" + return dns_policy.get_dns_health_probe() + + +@pytest.fixture(scope="module", autouse=True) +def commit(request, route, tls_policy, dns_policy): # pylint: disable=unused-argument + """Commits dnspolicy""" + for component in [tls_policy, dns_policy]: + request.addfinalizer(component.delete) + component.commit() + component.wait_for_ready() diff --git a/testsuite/tests/singlecluster/gateway/dnspolicy/health_check/test_healthy_endpoint.py b/testsuite/tests/singlecluster/gateway/dnspolicy/health_check/test_healthy_endpoint.py new file mode 100644 index 00000000..1543b3c5 --- /dev/null +++ b/testsuite/tests/singlecluster/gateway/dnspolicy/health_check/test_healthy_endpoint.py @@ -0,0 +1,25 @@ +"""Tests for DNSPolicy health checks - healthy endpoint""" + +import pytest + +from testsuite.kuadrant.policy.dns import HealthCheck + +pytestmark = [pytest.mark.kuadrant_only, pytest.mark.dnspolicy] + + +@pytest.fixture(scope="module") +def health_check(): + """Returns healthy endpoint specification for DNSPolicy health check""" + return HealthCheck( + path="/get", + interval="5s", + protocol="HTTPS", + ) + + +def test_healthy_endpoint(dns_health_probe, client, auth): + """Test healthy endpoint check""" + assert dns_health_probe.is_healthy() + + response = client.get("/get", auth=auth) + assert response.status_code == 200 diff --git a/testsuite/tests/singlecluster/gateway/dnspolicy/health_check/test_healthy_endpoint_http.py b/testsuite/tests/singlecluster/gateway/dnspolicy/health_check/test_healthy_endpoint_http.py new file mode 100644 index 00000000..c9ece743 --- /dev/null +++ b/testsuite/tests/singlecluster/gateway/dnspolicy/health_check/test_healthy_endpoint_http.py @@ -0,0 +1,46 @@ +"""Tests for DNSPolicy health checks with HTTP only endpoint - healthy endpoint""" + +import pytest + +from testsuite.gateway import GatewayListener +from testsuite.gateway.gateway_api.gateway import KuadrantGateway +from testsuite.kuadrant.policy.dns import HealthCheck + +pytestmark = [pytest.mark.kuadrant_only, pytest.mark.dnspolicy] + + +@pytest.fixture(scope="module") +def health_check(): + """Returns healthy endpoint specification for DNSPolicy health check""" + return HealthCheck( + path="/get", + interval="5s", + protocol="HTTP", + ) + + +@pytest.fixture(scope="module") +def gateway(request, cluster, blame, base_domain, module_label, subdomain): + """Create gateway without TLS enabled""" + gw = KuadrantGateway.create_instance(cluster, blame("gw"), {"app": module_label}) + gw.add_listener(GatewayListener(hostname=f"{subdomain}.{base_domain}")) + request.addfinalizer(gw.delete) + gw.commit() + gw.wait_for_ready() + return gw + + +@pytest.fixture(scope="module", autouse=True) +def commit(request, route, dns_policy): # pylint: disable=unused-argument + """Commits dnspolicy only""" + request.addfinalizer(dns_policy.delete) + dns_policy.commit() + dns_policy.wait_for_ready() + + +def test_healthy_endpoint_http(dns_health_probe, client): + """Test healthy endpoint check without TLS enabled""" + assert dns_health_probe.is_healthy() + + response = client.get("/get") + assert response.status_code == 200 diff --git a/testsuite/tests/singlecluster/gateway/dnspolicy/health_check/test_remove_endpoint.py b/testsuite/tests/singlecluster/gateway/dnspolicy/health_check/test_remove_endpoint.py new file mode 100644 index 00000000..f50ea891 --- /dev/null +++ b/testsuite/tests/singlecluster/gateway/dnspolicy/health_check/test_remove_endpoint.py @@ -0,0 +1,42 @@ +"""Tests for DNSPolicy health checks - healthy endpoint""" + +import pytest + +from testsuite.kuadrant.policy import has_condition +from testsuite.kuadrant.policy.dns import HealthCheck, has_record_condition + +pytestmark = [pytest.mark.kuadrant_only, pytest.mark.dnspolicy] + + +@pytest.fixture(scope="module") +def health_check(): + """Returns healthy endpoint specification for DNSPolicy health check""" + return HealthCheck( + path="/get", + interval="5s", + protocol="HTTPS", + ) + + +def test_remove_endpoint(backend, dns_policy, dns_health_probe, client, auth): + """Scale backend replicas to 0 and back to 1, and check if DNSPolicy will remove the unhealthy endpoint""" + assert dns_health_probe.is_healthy() + response = client.get("/get", auth=auth) + assert response.status_code == 200 + + backend.deployment.self_selector().scale(0) + assert dns_policy.wait_until(has_condition("Enforced", "False")) + assert dns_policy.wait_until( + has_record_condition("Ready", "False", "HealthChecksFailed", "None of the healthchecks succeeded") + ) + + assert not dns_health_probe.is_healthy() + response = client.get("/get", auth=auth) + assert response.status_code == 503 + + backend.deployment.self_selector().scale(1) + dns_policy.wait_for_ready() + + assert dns_health_probe.is_healthy() + response = client.get("/get", auth=auth) + assert response.status_code == 200 diff --git a/testsuite/tests/singlecluster/gateway/dnspolicy/health_check/test_unhealthy_endpoint.py b/testsuite/tests/singlecluster/gateway/dnspolicy/health_check/test_unhealthy_endpoint.py new file mode 100644 index 00000000..4d83996b --- /dev/null +++ b/testsuite/tests/singlecluster/gateway/dnspolicy/health_check/test_unhealthy_endpoint.py @@ -0,0 +1,41 @@ +"""Tests for DNSPolicy health checks - unhealthy endpoint""" + +import pytest + +from testsuite.kuadrant.policy import has_condition +from testsuite.kuadrant.policy.dns import HealthCheck, has_record_condition + +pytestmark = [pytest.mark.kuadrant_only, pytest.mark.dnspolicy] + + +@pytest.fixture(scope="module") +def health_check(): + """Returns unhealthy endpoint specification for DNSPolicy health check""" + return HealthCheck( + path="/unknown-endpoint", + interval="5s", + protocol="HTTPS", + ) + + +@pytest.fixture(scope="module", autouse=True) +def commit(request, route, tls_policy, dns_policy): # pylint: disable=unused-argument + """Commits tlspolicy and dnspolicy without waiting for dnspolicy to be enforced""" + request.addfinalizer(tls_policy.delete) + tls_policy.commit() + tls_policy.wait_for_ready() + + request.addfinalizer(dns_policy.delete) + dns_policy.commit() + + +def test_unhealthy_endpoint(dns_policy, dns_health_probe, client, auth): + """Test unhealthy endpoint check""" + assert not dns_health_probe.is_healthy() + response = client.get("/get", auth=auth) + assert response.has_dns_error() + + assert dns_policy.wait_until(has_condition("Enforced", "False")) + assert dns_policy.wait_until( + has_record_condition("Ready", "False", "HealthChecksFailed", "None of the healthchecks succeeded") + )