Skip to content

Commit

Permalink
Merge pull request #310 from averevki/test-health-check-header
Browse files Browse the repository at this point in the history
Test DNSPolicy health check additional headers
  • Loading branch information
pehala authored Mar 5, 2024
2 parents 5cd4c3c + 6c05aed commit db938e0
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 13 deletions.
93 changes: 86 additions & 7 deletions testsuite/mockserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,53 @@

from typing import Union

import httpx
from apyproxy import ApyProxy

from testsuite.utils import ContentType
from testsuite.httpx import KuadrantClient
from testsuite.openshift import Selector
from testsuite.openshift.backend import Backend
from testsuite.openshift.service import Service, ServicePort
from testsuite.openshift.deployment import Deployment, ContainerResources
from testsuite.openshift.client import OpenShiftClient


class Mockserver:
"""
Mockserver deployed in Openshift (located in Tools or self-managed)
"""

def __init__(self, url):
self.client = ApyProxy(url, session=httpx.Client(verify=False, timeout=5))
def __init__(self, url, client: KuadrantClient = None):
self.client = ApyProxy(url, session=client or KuadrantClient(verify=False))

def _expectation(self, expectation_id, response_data):
def _expectation(self, expectation_id, json_data):
"""
Creates an Expectation with given response_data.
Creates an Expectation from given expectation json.
Returns the absolute URL of the expectation
"""
json_data = {"id": expectation_id, "httpRequest": {"path": f"/{expectation_id}"}}
json_data.update(response_data)
json_data["id"] = expectation_id
json_data.setdefault("httpRequest", {})["path"] = f"/{expectation_id}"

self.client.mockserver.expectation.put(json=json_data)
# pylint: disable=protected-access
return f"{self.client._url}/{expectation_id}"

def create_request_expectation(
self,
expectation_id,
headers: dict[str, list[str]],
):
"""Creates an Expectation - request with given headers"""
json_data = {
"httpRequest": {
"headers": headers,
},
"httpResponse": {
"body": "",
},
}
return self._expectation(expectation_id, json_data)

def create_expectation(
self,
expectation_id,
Expand Down Expand Up @@ -56,3 +77,61 @@ def retrieve_requests(self, expectation_id):
params={"type": "REQUESTS", "format": "JSON"},
json={"path": "/" + expectation_id},
).json()


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

PORT = 8080

def __init__(self, openshift: OpenShiftClient, name: str, label: str):
self.openshift = openshift
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.openshift.project,
}

def commit(self):
match_labels = {"app": self.label, "deployment": self.name}
self.deployment = Deployment.create_instance(
self.openshift,
self.name,
container_name="mockserver",
image="quay.io/mganisin/mockserver:latest",
ports={"api": 1080},
selector=Selector(matchLabels=match_labels),
labels={"app": self.label},
resources=ContainerResources(limits_memory="2G"),
lifecycle={"postStart": {"exec": {"command": ["/bin/sh", "init-mockserver"]}}},
)
self.deployment.commit()
self.deployment.wait_for_ready()

self.service = Service.create_instance(
self.openshift,
self.name,
selector=match_labels,
ports=[ServicePort(name="1080-tcp", port=self.PORT, targetPort="api")],
labels={"app": self.label},
)
self.service.commit()

def delete(self):
with self.openshift.context:
if self.service:
self.service.delete()
self.service = None
if self.deployment:
self.deployment.delete()
self.deployment = None
8 changes: 8 additions & 0 deletions testsuite/openshift/backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""Backend for Openshift"""

from testsuite.lifecycle import LifecycleObject
from testsuite.gateway import Referencable


class Backend(LifecycleObject, Referencable):
"""Backend for Openshift"""
31 changes: 29 additions & 2 deletions testsuite/openshift/deployment.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Deployment related objects"""

from dataclasses import dataclass
from typing import Any
from typing import Any, Optional

import openshift_client as oc

Expand All @@ -11,6 +11,25 @@
# pylint: disable=invalid-name


@dataclass
class ContainerResources:
"""Deployment ContainerResources object"""

limits_cpu: Optional[str] = None
limits_memory: Optional[str] = None
requests_cpu: Optional[str] = None
requests_memory: Optional[str] = None

def asdict(self):
"""Remove None pairs and nest limits and requests resources for the result dict"""
result = {}
for key, value in self.__dict__.items():
if value is not None:
category, resource = key.split("_")
result.setdefault(category, {})[resource] = value
return result


@dataclass
class VolumeMount:
"""Deployment VolumeMount object"""
Expand Down Expand Up @@ -72,7 +91,9 @@ def create_instance(
volumes: list[Volume] = None,
volume_mounts: list[VolumeMount] = None,
readiness_probe: dict[str, Any] = None,
):
resources: Optional[ContainerResources] = None,
lifecycle: dict[str, Any] = None,
): # pylint: disable=too-many-locals
"""
Creates new instance of Deployment
Supports only single container Deployments everything else should be edited directly
Expand Down Expand Up @@ -117,6 +138,12 @@ def create_instance(
if readiness_probe:
container["readinessProbe"] = readiness_probe

if resources:
container["resources"] = asdict(resources)

if lifecycle:
container["lifecycle"] = lifecycle

return cls(model, context=openshift.context)

def wait_for_ready(self, timeout=90):
Expand Down
5 changes: 2 additions & 3 deletions testsuite/openshift/httpbin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

from functools import cached_property

from testsuite.lifecycle import LifecycleObject
from testsuite.gateway import Referencable
from testsuite.openshift.backend import Backend
from testsuite.openshift import Selector
from testsuite.openshift.client import OpenShiftClient
from testsuite.openshift.deployment import Deployment
from testsuite.openshift.service import Service, ServicePort


class Httpbin(LifecycleObject, Referencable):
class Httpbin(Backend):
"""Httpbin deployed in OpenShift"""

def __init__(self, openshift: OpenShiftClient, name, label, replicas=1) -> None:
Expand Down
10 changes: 9 additions & 1 deletion testsuite/policy/dns_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,18 @@


@dataclass
class HealthCheck: # pylint: disable=invalid-name
class AdditionalHeadersRef:
"""Object representing DNSPolicy additionalHeadersRef field"""

name: str


@dataclass
class HealthCheck: # pylint: disable=invalid-name,too-many-instance-attributes
"""Object representing DNSPolicy health check specification"""

allowInsecureCertificates: Optional[bool] = None
additionalHeadersRef: Optional[AdditionalHeadersRef] = None
endpoint: Optional[str] = None
expectedResponses: Optional[list[int]] = None
failureThreshold: Optional[int] = None
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""Tests for DNSPolicy health checks - additional authentication headers sent with health check requests"""

import pytest

from testsuite.openshift.secret import Secret
from testsuite.mockserver import Mockserver, MockserverBackend
from testsuite.policy.dns_policy import HealthCheck, AdditionalHeadersRef

pytestmark = [pytest.mark.mgc]

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


@pytest.fixture(scope="module")
def backend(request, openshift, blame, label):
"""Use mockserver as backend for health check requests to verify additional headers"""
mockserver = MockserverBackend(openshift, blame("mocksrv"), label)
request.addfinalizer(mockserver.delete)
mockserver.commit()

return mockserver


@pytest.fixture(scope="module")
def mockserver_client(client):
"""Returns Mockserver client"""
return Mockserver(str(client.base_url), client=client)


@pytest.fixture(scope="module", autouse=True)
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")
def headers_secret(request, hub_openshift, blame):
"""Creates Secret with additional headers for DNSPolicy health check"""
secret_name = blame("headers")
headers_secret = Secret.create_instance(hub_openshift, secret_name, {HEADER_NAME: HEADER_VALUE})

request.addfinalizer(headers_secret.delete)
headers_secret.commit()
return secret_name


@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(
allowInsecureCertificates=True,
additionalHeadersRef=AdditionalHeadersRef(name=headers_secret),
endpoint=f"/{module_label}",
interval="5s",
port=80,
protocol="http",
)


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

request_headers = requests[0]["headers"]
assert request_headers.get(HEADER_NAME) == [HEADER_VALUE]

0 comments on commit db938e0

Please sign in to comment.