From 7cbf6f997edd7c15c9a579db1a9a887f2559051d Mon Sep 17 00:00:00 2001 From: averevki Date: Wed, 24 Jan 2024 18:46:30 +0100 Subject: [PATCH] Add MockserverBackend & add request expectations --- testsuite/mockserver.py | 96 +++++++++++++++++-- testsuite/openshift/deployment.py | 36 ++++++- .../opa/external_registry/conftest.py | 2 +- .../test_auto_refresh_policy.py | 2 +- .../test_metadata_condition.py | 2 +- .../kuadrant/authorino/dinosaur/conftest.py | 6 +- .../kuadrant/authorino/metadata/test_http.py | 2 +- .../metadata/test_multi_element_json.py | 2 +- .../authorino/metrics/test_deep_metrics.py | 2 +- 9 files changed, 132 insertions(+), 18 deletions(-) diff --git a/testsuite/mockserver.py b/testsuite/mockserver.py index 67e4598c..b44cd61c 100644 --- a/testsuite/mockserver.py +++ b/testsuite/mockserver.py @@ -1,10 +1,16 @@ """Module for Mockserver integration""" from typing import Union -import httpx from apyproxy import ApyProxy from testsuite.utils import ContentType +from testsuite.httpx import KuadrantClient +from testsuite.gateway import Referencable +from testsuite.lifecycle import LifecycleObject +from testsuite.openshift import Selector +from testsuite.openshift.service import Service, ServicePort +from testsuite.openshift.deployment import Deployment, ContainerResources +from testsuite.openshift.client import OpenShiftClient class Mockserver: @@ -13,21 +19,37 @@ class Mockserver: """ def __init__(self, url): - self.client = ApyProxy(url, session=httpx.Client(verify=False, timeout=5)) + self.client = ApyProxy(url, session=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_expectation( + 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_response_expectation( self, expectation_id, body, @@ -55,3 +77,63 @@ def retrieve_requests(self, expectation_id): params={"type": "REQUESTS", "format": "JSON"}, json={"path": "/" + expectation_id}, ).json() + + +class MockserverBackend(Mockserver, LifecycleObject, Referencable): + """Mockserver deployed as backend in Openshift""" + + PORT = 1080 + + def __init__(self, openshift: OpenShiftClient, url: str, name: str, label: str): + super().__init__(url) + + 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": self.PORT}, + 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 diff --git a/testsuite/openshift/deployment.py b/testsuite/openshift/deployment.py index 80adc68c..9de8259e 100644 --- a/testsuite/openshift/deployment.py +++ b/testsuite/openshift/deployment.py @@ -1,6 +1,6 @@ """Deployment related objects""" from dataclasses import dataclass -from typing import Any +from typing import Any, Optional import openshift as oc @@ -10,6 +10,30 @@ # 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 + + @dataclass + class _Resources: + """CPU and memory resources for container""" + + cpu: Optional[str] = None + memory: Optional[str] = None + + def asdict(self): + """Custom asdict due to nested structure""" + return { + "limits": asdict(self._Resources(cpu=self.limits_cpu, memory=self.limits_memory)), + "requests": asdict(self._Resources(cpu=self.requests_cpu, memory=self.requests_memory)), + } + + @dataclass class VolumeMount: """Deployment VolumeMount object""" @@ -71,7 +95,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 @@ -116,6 +142,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): diff --git a/testsuite/tests/kuadrant/authorino/authorization/opa/external_registry/conftest.py b/testsuite/tests/kuadrant/authorino/authorization/opa/external_registry/conftest.py index 3d544c8b..b6030c20 100644 --- a/testsuite/tests/kuadrant/authorino/authorization/opa/external_registry/conftest.py +++ b/testsuite/tests/kuadrant/authorino/authorization/opa/external_registry/conftest.py @@ -14,7 +14,7 @@ def header(): def opa_policy_expectation(request, mockserver, module_label, header): """Creates Mockserver Expectation that returns Rego query and returns its endpoint""" request.addfinalizer(lambda: mockserver.clear_expectation(module_label)) - return mockserver.create_expectation(module_label, rego_allow_header(*header)) + return mockserver.create_response_expectation(module_label, rego_allow_header(*header)) @pytest.fixture(scope="module") diff --git a/testsuite/tests/kuadrant/authorino/authorization/opa/external_registry/test_auto_refresh_policy.py b/testsuite/tests/kuadrant/authorino/authorization/opa/external_registry/test_auto_refresh_policy.py index e7368dbe..841b1921 100644 --- a/testsuite/tests/kuadrant/authorino/authorization/opa/external_registry/test_auto_refresh_policy.py +++ b/testsuite/tests/kuadrant/authorino/authorization/opa/external_registry/test_auto_refresh_policy.py @@ -18,7 +18,7 @@ def updated_header(): @pytest.fixture(scope="module", autouse=True) def update_external_opa(mockserver, module_label, updated_header): """Updates Expectation with updated header""" - mockserver.create_expectation(module_label, rego_allow_header(*updated_header)) + mockserver.create_response_expectation(module_label, rego_allow_header(*updated_header)) # Sleeps for 1 second to compensate auto-refresh cycle `authorization.opa.externalRegistry.ttl = 1` time.sleep(1) diff --git a/testsuite/tests/kuadrant/authorino/conditions/section_conditions/test_metadata_condition.py b/testsuite/tests/kuadrant/authorino/conditions/section_conditions/test_metadata_condition.py index 38251425..e777c2f5 100644 --- a/testsuite/tests/kuadrant/authorino/conditions/section_conditions/test_metadata_condition.py +++ b/testsuite/tests/kuadrant/authorino/conditions/section_conditions/test_metadata_condition.py @@ -8,7 +8,7 @@ def mockserver_expectation(request, mockserver, module_label): """Creates Mockserver Expectation which returns non-empty response on hit""" request.addfinalizer(lambda: mockserver.clear_expectation(module_label)) - return mockserver.create_expectation(module_label, "response") + return mockserver.create_response_expectation(module_label, "response") @pytest.fixture(scope="module") diff --git a/testsuite/tests/kuadrant/authorino/dinosaur/conftest.py b/testsuite/tests/kuadrant/authorino/dinosaur/conftest.py index 50bc065a..4c5c4853 100644 --- a/testsuite/tests/kuadrant/authorino/dinosaur/conftest.py +++ b/testsuite/tests/kuadrant/authorino/dinosaur/conftest.py @@ -40,7 +40,7 @@ def terms_and_conditions(request, mockserver, module_label): """Creates Mockserver Expectation that returns whether terms are required and returns its endpoint""" def _terms_and_conditions(value): - return mockserver.create_expectation( + return mockserver.create_response_expectation( f"{module_label}-terms", {"terms_required": value}, ContentType.APPLICATION_JSON, @@ -55,7 +55,7 @@ def cluster_info(request, mockserver, module_label): """Creates Mockserver Expectation that returns client ID and returns its endpoint""" def _cluster_info(value): - return mockserver.create_expectation( + return mockserver.create_response_expectation( f"{module_label}-cluster", {"client_id": value}, ContentType.APPLICATION_JSON ) @@ -68,7 +68,7 @@ def resource_info(request, mockserver, module_label): """Creates Mockserver Expectation that returns info about resource and returns its endpoint""" def _resource_info(org_id, owner): - return mockserver.create_expectation( + return mockserver.create_response_expectation( f"{module_label}-resource", {"org_id": org_id, "owner": owner}, ContentType.APPLICATION_JSON, diff --git a/testsuite/tests/kuadrant/authorino/metadata/test_http.py b/testsuite/tests/kuadrant/authorino/metadata/test_http.py index 75f3468a..d3497178 100644 --- a/testsuite/tests/kuadrant/authorino/metadata/test_http.py +++ b/testsuite/tests/kuadrant/authorino/metadata/test_http.py @@ -22,7 +22,7 @@ def country_mock_expectation(request, mockserver, module_label): """Creates Mockserver Expectation which returns simple JSON that contains `allowed_countries`""" request.addfinalizer(lambda: mockserver.clear_expectation(module_label)) - return mockserver.create_expectation(module_label, ALLOWED_COUNTRY, ContentType.APPLICATION_JSON) + return mockserver.create_response_expectation(module_label, ALLOWED_COUNTRY, ContentType.APPLICATION_JSON) @pytest.fixture(scope="module") diff --git a/testsuite/tests/kuadrant/authorino/metadata/test_multi_element_json.py b/testsuite/tests/kuadrant/authorino/metadata/test_multi_element_json.py index 0391d65f..4a9e1a41 100644 --- a/testsuite/tests/kuadrant/authorino/metadata/test_multi_element_json.py +++ b/testsuite/tests/kuadrant/authorino/metadata/test_multi_element_json.py @@ -15,7 +15,7 @@ def json_mock_expectation(request, mockserver, module_label): """Creates Mockserver Expectation which returns multi-element JSON.""" request.addfinalizer(lambda: mockserver.clear_expectation(module_label)) - return mockserver.create_expectation(module_label, MULTI_ELEMENT_JSON, ContentType.APPLICATION_JSON) + return mockserver.create_response_expectation(module_label, MULTI_ELEMENT_JSON, ContentType.APPLICATION_JSON) @pytest.fixture(scope="module") diff --git a/testsuite/tests/kuadrant/authorino/metrics/test_deep_metrics.py b/testsuite/tests/kuadrant/authorino/metrics/test_deep_metrics.py index 459085e7..e4e42d84 100644 --- a/testsuite/tests/kuadrant/authorino/metrics/test_deep_metrics.py +++ b/testsuite/tests/kuadrant/authorino/metrics/test_deep_metrics.py @@ -8,7 +8,7 @@ def mockserver_expectation(request, mockserver, module_label): """Creates Mockserver Expectation which returns non-empty response on hit""" request.addfinalizer(lambda: mockserver.clear_expectation(module_label)) - return mockserver.create_expectation(module_label, "response") + return mockserver.create_response_expectation(module_label, "response") @pytest.fixture(scope="module")