Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DNS health check additional headers test #605

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading