Skip to content

Commit

Permalink
Merge pull request #605 from averevki/additional-headers-test
Browse files Browse the repository at this point in the history
Add DNS health check additional headers test
  • Loading branch information
averevki authored Jan 8, 2025
2 parents ff39845 + 813b6ac commit 7fe7f52
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 71 deletions.
35 changes: 34 additions & 1 deletion testsuite/backend/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,48 @@
"""Module containing all the Backends"""

from abc import abstractmethod
from functools import cached_property

from testsuite.gateway import Referencable
from testsuite.lifecycle import LifecycleObject
from testsuite.kubernetes.client import KubernetesClient


class Backend(LifecycleObject, Referencable):
"""Backend (workload) deployed in Kubernetes"""

def __init__(self, cluster: KubernetesClient, name: str, label: str):
self.cluster = cluster
self.name = name
self.label = label

self.deployment = None
self.service = None

@property
def reference(self):
return {"group": "", "kind": "Service", "port": 8080, "name": self.name, "namespace": self.cluster.project}

@property
@abstractmethod
def url(self):
"""Returns internal URL for this backend"""
return f"{self.name}.{self.cluster.project}.svc.cluster.local"

@cached_property
def port(self):
"""Service port that httpbin listens on"""
return self.service.get_port("http").port

@abstractmethod
def commit(self):
"""Deploys the backend"""

def delete(self):
"""Clean-up the backend"""
with self.cluster.context:
if self.service:
self.service.delete()
self.service = None
if self.deployment:
self.deployment.delete()
self.deployment = None
33 changes: 1 addition & 32 deletions testsuite/backend/httpbin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"""Httpbin implementation of Backend"""

from functools import cached_property

from testsuite.backend import Backend
from testsuite.kubernetes import Selector
from testsuite.kubernetes.client import KubernetesClient
Expand All @@ -13,25 +11,10 @@ class Httpbin(Backend):
"""Httpbin deployed in Kubernetes as Backend"""

def __init__(self, cluster: KubernetesClient, name, label, image, replicas=1) -> None:
super().__init__()
self.cluster = cluster
self.name = name
self.label = label
super().__init__(cluster, name, label)
self.replicas = replicas
self.image = image

self.deployment = None
self.service = None

@property
def reference(self):
return {"group": "", "kind": "Service", "port": 8080, "name": self.name, "namespace": self.cluster.project}

@property
def url(self):
"""URL for the httpbin service"""
return f"{self.name}.{self.cluster.project}.svc.cluster.local"

def commit(self):
match_labels = {"app": self.label, "deployment": self.name}
self.deployment = Deployment.create_instance(
Expand All @@ -53,17 +36,3 @@ def commit(self):
ports=[ServicePort(name="http", port=8080, targetPort="api")],
)
self.service.commit()

def delete(self):
with self.cluster.context:
if self.service:
self.service.delete()
self.service = None
if self.deployment:
self.deployment.delete()
self.deployment = None

@cached_property
def port(self):
"""Service port that httpbin listens on"""
return self.service.get_port("http").port
42 changes: 8 additions & 34 deletions testsuite/backend/mockserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,13 @@

from testsuite.backend import Backend
from testsuite.kubernetes import Selector
from testsuite.kubernetes.client import KubernetesClient
from testsuite.kubernetes.deployment import Deployment, ContainerResources
from testsuite.kubernetes.service import Service, ServicePort


class MockserverBackend(Backend):
"""Mockserver deployed as backend in Kubernetes"""

PORT = 8080

def __init__(self, cluster: KubernetesClient, name: str, label: str):
self.cluster = cluster
self.name = name
self.label = label

self.deployment = None
self.service = None

@property
def reference(self):
return {
"group": "",
"kind": "Service",
"port": self.PORT,
"name": self.name,
"namespace": self.cluster.project,
}

@property
def url(self):
return f"{self.name}.{self.cluster.project}.svc.cluster.local"

def commit(self):
match_labels = {"app": self.label, "deployment": self.name}
self.deployment = Deployment.create_instance(
Expand All @@ -54,16 +29,15 @@ def commit(self):
self.cluster,
self.name,
selector=match_labels,
ports=[ServicePort(name="1080-tcp", port=self.PORT, targetPort="api")],
ports=[ServicePort(name="1080-tcp", port=8080, targetPort="api")],
labels={"app": self.label},
service_type="LoadBalancer",
)
self.service.commit()

def delete(self):
with self.cluster.context:
if self.service:
self.service.delete()
self.service = None
if self.deployment:
self.deployment.delete()
self.deployment = None
def wait_for_ready(self, timeout=300):
"""Waits until Deployment is marked as ready"""
success = self.service.wait_until(
lambda obj: "ip" in self.service.refresh().model.status.loadBalancer.ingress[0], timelimit=timeout
)
assert success, f"Service {self.name} did not get ready in time"
2 changes: 1 addition & 1 deletion testsuite/kubernetes/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def external_ip(self):
if ip is Missing:
ip = self.model.status.loadBalancer.ingress[0].hostname
if ip is Missing:
raise AttributeError(f"Neither External IP nor Hostname found in status of {self.model.spec.name} service")
raise AttributeError(f"Neither External IP nor Hostname found in status of {self.kind()}/{self.name()}")

return ip

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""Tests for DNSPolicy health checks - additional authentication headers sent with health check requests"""

import pytest

from testsuite.httpx import KuadrantClient
from testsuite.mockserver import Mockserver
from testsuite.gateway import GatewayListener
from testsuite.gateway.gateway_api.gateway import KuadrantGateway
from testsuite.kubernetes.secret import Secret
from testsuite.kuadrant.policy.dns import HealthCheck, AdditionalHeadersRef
from testsuite.backend.mockserver import MockserverBackend

pytestmark = [pytest.mark.kuadrant_only, pytest.mark.dnspolicy]

HEADER_NAME = "test-header"
HEADER_VALUE = "test-value"


@pytest.fixture(scope="module")
def health_check(headers_secret, module_label):
"""Returns healthy endpoint specification with additional authentication header for DNSPolicy health check"""
return HealthCheck(
additionalHeadersRef=AdditionalHeadersRef(name=headers_secret),
path=f"/{module_label}",
interval="5s",
protocol="HTTP",
port=80,
)


@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")
def backend(request, cluster, blame, label):
"""Use mockserver as backend for health check requests to verify additional headers"""
mockserver = MockserverBackend(cluster, blame("mocksrv"), label)
request.addfinalizer(mockserver.delete)
mockserver.commit()
mockserver.wait_for_ready()
return mockserver


@pytest.fixture(scope="module")
def headers_secret(request, cluster, blame):
"""Creates Secret with additional headers for DNSPolicy health check"""
secret_name = blame("headers")
headers_secret = Secret.create_instance(cluster, secret_name, {HEADER_NAME: HEADER_VALUE})
request.addfinalizer(headers_secret.delete)
headers_secret.commit()
return secret_name


@pytest.fixture(scope="module")
def mockserver_client(backend):
"""Returns Mockserver client from load-balanced service IP"""
return Mockserver(KuadrantClient(base_url=f"http://{backend.service.refresh().external_ip}: 8080"))


@pytest.fixture(scope="module")
def mockserver_backend_expectation(mockserver_client, module_label):
"""Creates Mockserver Expectation which requires additional headers for successful request"""
mockserver_client.create_request_expectation(module_label, headers={HEADER_NAME: [HEADER_VALUE]})


@pytest.fixture(scope="module", autouse=True)
def commit(request, route, dns_policy, mockserver_backend_expectation): # pylint: disable=unused-argument
"""Commits dnspolicy only"""
request.addfinalizer(dns_policy.delete)
dns_policy.commit()
dns_policy.wait_for_ready()


def test_additional_headers(dns_health_probe, mockserver_client, module_label):
"""Test if additional headers in health check requests are used"""
assert dns_health_probe.is_healthy()

requests = mockserver_client.retrieve_requests(module_label)
assert len(requests) > 0
assert requests[0]["headers"].get(HEADER_NAME) == [HEADER_VALUE]
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from testsuite.kuadrant.policy.dns import HealthCheck

pytestmark = [pytest.mark.kuadrant_only, pytest.mark.dnspolicy]
pytestmark = [pytest.mark.kuadrant_only, pytest.mark.dnspolicy, pytest.mark.tlspolicy]


@pytest.fixture(scope="module")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
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]
pytestmark = [pytest.mark.kuadrant_only, pytest.mark.dnspolicy, pytest.mark.tlspolicy]


@pytest.fixture(scope="module")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
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]
pytestmark = [pytest.mark.kuadrant_only, pytest.mark.dnspolicy, pytest.mark.tlspolicy]


@pytest.fixture(scope="module")
Expand Down

0 comments on commit 7fe7f52

Please sign in to comment.