Skip to content

Commit

Permalink
Unify exposers
Browse files Browse the repository at this point in the history
- Add both passthrough and verify as arguments to Exposer
- Require singular constructor
- Tie base_domain to exposer
- Allow specifying default_exposer and managedzone in settings
  • Loading branch information
pehala committed Apr 9, 2024
1 parent 077f79c commit 7405638
Show file tree
Hide file tree
Showing 14 changed files with 95 additions and 61 deletions.
1 change: 1 addition & 0 deletions config/settings.local.yaml.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
# auth_url: "" # authorization URL for already deployed Authorino
# oidc_url: "" # oidc URL for already deployed Authorino
# metrics_service_name: "" # controller metrics service name for already deployed Authorino
# default_exposer: "openshift" # Exposer type that should be used, options: 'openshift'
# control_plane:
# hub: # Hub cluster
# project: "multi-cluster-gateways" # Optional: namespace where MGC resources are created and where the hub gateway will be created
Expand Down
3 changes: 3 additions & 0 deletions config/settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@ default:
hyperfoil:
generate_reports: True
reports_dir: "reports"
default_exposer: "openshift"
control_plane:
managedzone: "aws-mz"
12 changes: 10 additions & 2 deletions testsuite/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,18 @@ def __init__(self, name, default, **kwargs) -> None:
"tracing.collector_url", default=fetch_service("jaeger-collector", protocol="rpc", port=4317)
),
DefaultValueValidator("tracing.query_url", default=fetch_route("jaeger-query", force_http=True)),
Validator(
"default_exposer",
# If exposer was successfully converted, it will no longer be a string"""
condition=lambda exposer: not isinstance(exposer, str),
must_exist=True,
messages={"condition": "{value} is not valid exposer"},
),
Validator("control_plane.managedzone", must_exist=True, ne=None),
DefaultValueValidator("rhsso.url", default=fetch_route("no-ssl-sso")),
DefaultValueValidator("rhsso.password", default=fetch_secret("credential-sso", "ADMIN_PASSWORD")),
DefaultValueValidator("mockserver.url", default=fetch_route("mockserver", force_http=True)),
],
validate_only=["authorino", "kuadrant"],
loaders=["dynaconf.loaders.env_loader", "testsuite.config.openshift_loader"],
validate_only=["authorino", "kuadrant", "default_exposer", "control_plane"],
loaders=["dynaconf.loaders.env_loader", "testsuite.config.openshift_loader", "testsuite.config.exposer"],
)
14 changes: 14 additions & 0 deletions testsuite/config/exposer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Translates string to an Exposer class that can initialized"""

from testsuite.gateway.exposers import OpenShiftExposer

EXPOSERS = {"openshift": OpenShiftExposer}


# pylint: disable=unused-argument
def load(obj, env=None, silent=True, key=None, filename=None):
"""Creates all OpenShift clients"""
try:
obj["default_exposer"] = EXPOSERS[obj["default_exposer"]]
except KeyError:
return
11 changes: 11 additions & 0 deletions testsuite/gateway/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,20 @@ def hostname(self) -> str:
class Exposer(LifecycleObject):
"""Exposes hostnames to be accessible from outside"""

def __init__(self, openshift):
super().__init__()
self.openshift = openshift
self.passthrough = False
self.verify = None

@abstractmethod
def expose_hostname(self, name, gateway: Gateway) -> Hostname:
"""
Exposes hostname, so it is accessible from outside
Actual hostname is generated from "name" and is returned in a form of a Hostname object
"""

@property
@abstractmethod
def base_domain(self) -> str:
"""Returns base domains for all hostnames created by this exposer"""
15 changes: 10 additions & 5 deletions testsuite/gateway/exposers.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
"""General exposers, not tied to Envoy or Gateway API"""

from testsuite.gateway import Exposer, Gateway, Hostname
from testsuite.lifecycle import LifecycleObject
from testsuite.openshift.route import OpenshiftRoute


class OpenShiftExposer(Exposer, LifecycleObject):
class OpenShiftExposer(Exposer):
"""Exposes hostnames through OpenShift Route objects"""

def __init__(self, passthrough=False) -> None:
super().__init__()
def __init__(self, openshift) -> None:
super().__init__(openshift)
self.routes: list[OpenshiftRoute] = []
self.passthrough = passthrough

@property
def base_domain(self) -> str:
return self.openshift.apps_url

def expose_hostname(self, name, gateway: Gateway) -> Hostname:
tls = False
Expand All @@ -20,6 +24,7 @@ def expose_hostname(self, name, gateway: Gateway) -> Hostname:
route = OpenshiftRoute.create_instance(
gateway.openshift, name, gateway.service_name, "api", tls=tls, termination=termination
)
route.verify = self.verify
self.routes.append(route)
route.commit()
return route
Expand Down
16 changes: 11 additions & 5 deletions testsuite/gateway/gateway_api/hostname.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
"""Module containing implementation for Hostname related classes of Gateway API"""

from functools import cached_property

from httpx import Client
from openshift_client import selector

from testsuite.certificates import Certificate
from testsuite.config import settings
from testsuite.httpx import KuadrantClient
from testsuite.gateway import Gateway, Hostname, Exposer
from testsuite.utils import generate_tail


class StaticHostname(Hostname):
Expand All @@ -30,14 +35,15 @@ def hostname(self):
class DNSPolicyExposer(Exposer):
"""Exposing is done as part of DNSPolicy, so no work needs to be done here"""

def __init__(self, base_domain, tls_cert: Certificate = None):
super().__init__()
self.base_domain = base_domain
self.tls_cert = tls_cert
@cached_property
def base_domain(self) -> str:
mz_name = settings["control_plane"]["managedzone"]
zone = selector(f"managedzone/{mz_name}", static_context=self.openshift.context).object()
return f'{generate_tail(5)}.{zone.model["spec"]["domainName"]}'

def expose_hostname(self, name, gateway: Gateway) -> Hostname:
return StaticHostname(
f"{name}.{self.base_domain}", gateway.get_tls_cert() if self.tls_cert is None else self.tls_cert
f"{name}.{self.base_domain}", gateway.get_tls_cert() if self.verify is None else self.verify
)

def commit(self):
Expand Down
5 changes: 5 additions & 0 deletions testsuite/openshift/route.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
class OpenshiftRoute(OpenShiftObject, Hostname):
"""Openshift Route object"""

def __init__(self, dict_to_model=None, string_to_model=None, context=None):
super().__init__(dict_to_model, string_to_model, context)
self.verify = None

@classmethod
def create_instance(
cls,
Expand Down Expand Up @@ -41,6 +45,7 @@ def client(self, **kwargs) -> Client:
protocol = "http"
if "tls" in self.model.spec:
protocol = "https"
kwargs.setdefault("verify", self.verify)
return KuadrantClient(base_url=f"{protocol}://{self.hostname}", **kwargs)

@cached_property
Expand Down
15 changes: 10 additions & 5 deletions testsuite/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from testsuite.gateway.envoy import Envoy
from testsuite.gateway.envoy.route import EnvoyVirtualRoute
from testsuite.gateway.gateway_api.gateway import KuadrantGateway
from testsuite.gateway.exposers import OpenShiftExposer
from testsuite.gateway.gateway_api.route import HTTPRoute
from testsuite.utils import randomize, _whoami

Expand Down Expand Up @@ -324,9 +323,9 @@ def route(request, kuadrant, gateway, blame, hostname, backend, module_label) ->


@pytest.fixture(scope="module")
def exposer(request) -> Exposer:
def exposer(request, testconfig, hub_openshift) -> Exposer:
"""Exposer object instance"""
exposer = OpenShiftExposer()
exposer = testconfig["default_exposer"](hub_openshift)
request.addfinalizer(exposer.delete)
exposer.commit()
return exposer
Expand All @@ -340,11 +339,17 @@ def hostname(gateway, exposer, blame) -> Hostname:


@pytest.fixture(scope="module")
def wildcard_domain(openshift):
def base_domain(exposer):
"""Returns preconfigured base domain"""
return exposer.base_domain


@pytest.fixture(scope="module")
def wildcard_domain(base_domain):
"""
Wildcard domain of openshift cluster
"""
return f"*.{openshift.apps_url}"
return f"*.{base_domain}"


@pytest.fixture(scope="module")
Expand Down
7 changes: 4 additions & 3 deletions testsuite/tests/kuadrant/authorino/operator/tls/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def _create_secret(certificate: Certificate, name: str, labels: Optional[Dict[st
@pytest.fixture(scope="module")
def authorino_domain(openshift):
"""
Hostname of the upstream certificate sent to be validated by APIcast
Hostname of the upstream certificate sent to be validated by Envoy
May be overwritten to configure different test cases
"""
return f"*.{openshift.project}.svc.cluster.local"
Expand Down Expand Up @@ -181,9 +181,10 @@ def gateway(


@pytest.fixture(scope="module")
def exposer(request) -> Exposer:
def exposer(request, hub_openshift) -> Exposer:
"""Exposer object instance with TLS passthrough"""
exposer = OpenShiftExposer(passthrough=True)
exposer = OpenShiftExposer(hub_openshift)
exposer.passthrough = True
request.addfinalizer(exposer.delete)
exposer.commit()
return exposer
Expand Down
27 changes: 11 additions & 16 deletions testsuite/tests/mgc/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Conftest for MGC tests"""

import pytest
from openshift_client import selector
from weakget import weakget

from testsuite.gateway import CustomReference, GatewayRoute, Exposer
Expand All @@ -10,7 +9,6 @@
from testsuite.gateway.gateway_api.hostname import DNSPolicyExposer
from testsuite.gateway.gateway_api.route import HTTPRoute
from testsuite.policy.tls_policy import TLSPolicy
from testsuite.utils import generate_tail


@pytest.fixture(scope="session")
Expand All @@ -31,13 +29,13 @@ def hub_openshift(testconfig):


@pytest.fixture(scope="module")
def hub_gateway(request, hub_openshift, blame, base_domain, module_label) -> MGCGateway:
def hub_gateway(request, hub_openshift, blame, wildcard_domain, module_label) -> MGCGateway:
"""Creates and returns configured and ready Hub Gateway"""
hub_gateway = MGCGateway.create_instance(
openshift=hub_openshift,
name=blame("mgc-gateway"),
gateway_class="kuadrant-multi-cluster-gateway-instance-per-cluster",
hostname=f"*.{base_domain}",
hostname=wildcard_domain,
tls=True,
placement="http-gateway",
labels={"app": module_label},
Expand Down Expand Up @@ -73,17 +71,23 @@ def route(request, gateway, blame, hostname, backend, module_label) -> GatewayRo


@pytest.fixture(scope="module")
def exposer(base_domain, hub_gateway) -> Exposer:
def exposer(request, hub_openshift) -> Exposer:
"""DNSPolicyExposer setup with expected TLS certificate"""
return DNSPolicyExposer(base_domain, tls_cert=hub_gateway.get_tls_cert())
exposer = DNSPolicyExposer(hub_openshift)
request.addfinalizer(exposer.delete)
exposer.commit()
return exposer


# pylint: disable=unused-argument
@pytest.fixture(scope="module")
def gateway(hub_gateway, spokes, hub_policies_commit):
def gateway(exposer, hub_gateway, spokes, hub_policies_commit):
"""Downstream gateway, e.g. gateway on a spoke cluster"""
# wait for upstream gateway here to be able to get spoke gateways
hub_gateway.wait_for_ready()
# Only here is Hub Gateway ready, which means only here we can assign verify
exposer.verify = hub_gateway.get_tls_cert()

gw = hub_gateway.get_spoke_gateway(spokes)
gw.wait_for_ready()
return gw
Expand All @@ -95,15 +99,6 @@ def openshift(gateway):
return gateway.openshift


@pytest.fixture(scope="module", params=["aws-mz", "gcp-mz"])
def base_domain(request, hub_openshift):
"""Returns preconfigured base domain"""
mz_name = request.param

zone = selector(f"managedzone/{mz_name}", static_context=hub_openshift.context).object()
return f"{generate_tail()}.{zone.model['spec']['domainName']}"


@pytest.fixture(scope="module")
def dns_policy(blame, hub_gateway, module_label):
"""DNSPolicy fixture"""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
"""Tests that TLSPolicy is rejected if the issuer is invalid"""

import pytest
from openshift_client import selector

from testsuite.gateway import CustomReference
from testsuite.policy.tls_policy import TLSPolicy
from testsuite.utils import generate_tail

pytestmark = [pytest.mark.mgc]


@pytest.fixture(scope="module")
def base_domain(hub_openshift):
"""Returns preconfigured base domain"""
zone = selector("managedzone/aws-mz", static_context=hub_openshift.context).object()
return f"{generate_tail()}.{zone.model['spec']['domainName']}"


def test_wrong_issuer_type(request, hub_gateway, hub_openshift, blame, module_label):
"""Tests that TLSPolicy is rejected if issuer does not have a correct type"""

Expand Down
9 changes: 0 additions & 9 deletions testsuite/tests/mgc/reconciliation/test_same_target.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
"""Tests that DNSPolicy/TLSPolicy is rejected when the Gateway already has a policy of the same kind"""

import pytest
from openshift_client import selector

from testsuite.policy.tls_policy import TLSPolicy
from testsuite.tests.mgc.reconciliation import dns_policy
from testsuite.utils import generate_tail

pytestmark = [pytest.mark.mgc]


@pytest.fixture(scope="module")
def base_domain(hub_openshift):
"""Returns preconfigured base domain"""
zone = selector("managedzone/aws-mz", static_context=hub_openshift.context).object()
return f"{generate_tail()}.{zone.model['spec']['domainName']}"


@pytest.mark.parametrize(
"create_cr", [pytest.param(dns_policy, id="DNSPolicy"), pytest.param(TLSPolicy.create_instance, id="TLSPolicy")]
)
Expand Down
12 changes: 5 additions & 7 deletions testsuite/tests/mgc/test_external_ca.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@
from openshift_client import selector
from openshift_client.model import OpenShiftPythonException

from testsuite.gateway import Exposer, CustomReference
from testsuite.gateway.gateway_api.hostname import DNSPolicyExposer
from testsuite.gateway import CustomReference

pytestmark = [pytest.mark.mgc]

Expand All @@ -58,15 +57,14 @@ def cluster_issuer(hub_openshift):


@pytest.fixture(scope="module")
def exposer(base_domain, hub_gateway) -> Exposer:
"""DNSPolicyExposer setup with expected TLS certificate"""
def gateway(gateway, exposer, hub_gateway):
"""Only at this step we have TLS secret created and GW fully reconciled"""
root_cert = resources.files("testsuite.resources").joinpath("letsencrypt-stg-root-x1.pem").read_text()
old_cert = hub_gateway.get_tls_cert()
return DNSPolicyExposer(base_domain, tls_cert=dataclasses.replace(old_cert, chain=old_cert.certificate + root_cert))
exposer.verify = dataclasses.replace(old_cert, chain=old_cert.certificate + root_cert)
return gateway


# Reduce scope of the base_domain fixture so the test only runs on aws-mz ManagedZone
@pytest.mark.parametrize("base_domain", ["aws-mz"], indirect=True)
def test_smoke_letsencrypt(client):
"""
Tests whether the backend, exposed using the HTTPRoute and Gateway, was exposed correctly,
Expand Down

0 comments on commit 7405638

Please sign in to comment.