Skip to content

Commit

Permalink
Add geo load-balancing tests
Browse files Browse the repository at this point in the history
Signed-off-by: averevki <[email protected]>
  • Loading branch information
averevki committed Aug 29, 2024
1 parent bb48dd9 commit a36136f
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 0 deletions.
7 changes: 7 additions & 0 deletions config/settings.local.yaml.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@
# issuer: # Issuer object for testing TLSPolicy
# name: "selfsigned-cluster-issuer" # Name of Issuer CR
# kind: "ClusterIssuer" # Kind of Issuer, can be "Issuer" or "ClusterIssuer"
# dns:
# dns_server:
# geo_code: "DE" # dns provider geo code of the dns server
# address: "ns1.seolizer.de" # dns nameserver hostname or ip
# dns_server2:
# geo_code: "AU" # dns provider geo code of the second dns server
# address: "ns2.seolizer.de" # second dns nameserver hostname or ip
# letsencrypt:
# issuer: # Issuer object for testing TLSPolicy
# name: "letsencrypt-staging-issuer" # Name of Issuer CR
Expand Down
7 changes: 7 additions & 0 deletions testsuite/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from dynaconf import Dynaconf, Validator

from testsuite.utils import hostname_to_ip
from testsuite.config.tools import fetch_route, fetch_service, fetch_secret, fetch_service_ip


Expand Down Expand Up @@ -59,6 +60,12 @@ def __init__(self, name, default, **kwargs) -> None:
Validator("letsencrypt.issuer.name", must_exist=True, ne=None)
& Validator("letsencrypt.issuer.kind", must_exist=True, is_in={"Issuer", "ClusterIssuer"})
),
(
Validator("dns.dns_server.address", must_exist=True, ne=None, cast=hostname_to_ip)
& Validator("dns.dns_server.geo_code", must_exist=True, ne=None)
& Validator("dns.dns_server2.address", must_exist=True, ne=None, cast=hostname_to_ip)
& Validator("dns.dns_server2.geo_code", must_exist=True, ne=None)
),
DefaultValueValidator("keycloak.url", default=fetch_service_ip("keycloak", force_http=True, port=8080)),
DefaultValueValidator("keycloak.password", default=fetch_secret("credential-sso", "ADMIN_PASSWORD")),
DefaultValueValidator("mockserver.url", default=fetch_service_ip("mockserver", force_http=True, port=1080)),
Expand Down
23 changes: 23 additions & 0 deletions testsuite/kuadrant/policy/dns.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
"""Module for DNSPolicy related classes"""

from dataclasses import dataclass

from testsuite.gateway import Referencable
from testsuite.kubernetes.client import KubernetesClient
from testsuite.kuadrant.policy import Policy
from testsuite.utils import asdict


@dataclass
class LoadBalancing:
"""Dataclass for DNSPolicy load-balancing spec"""

default_geo: str
default_weight: int

def asdict(self):
"""Custom asdict due to nested structure."""
return {
"geo": {"defaultGeo": self.default_geo},
"weighted": {"defaultWeight": self.default_weight},
}


class DNSPolicy(Policy):
Expand All @@ -15,6 +33,7 @@ def create_instance(
name: str,
parent: Referencable,
provider_secret_name: str,
load_balancing: LoadBalancing = None,
labels: dict[str, str] = None,
):
"""Creates new instance of DNSPolicy"""
Expand All @@ -30,4 +49,8 @@ def create_instance(
},
}

if load_balancing:
model["spec"]["routingStrategy"] = "loadbalanced"
model["spec"]["loadBalancing"] = asdict(load_balancing)

return cls(model, context=cluster.context)
Empty file.
42 changes: 42 additions & 0 deletions testsuite/tests/multicluster/load_balanced/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Conftest for load-balanced multicluster tests"""

import pytest

from testsuite.kuadrant.policy.dns import DNSPolicy, LoadBalancing


@pytest.fixture(scope="package")
def dns_config(testconfig):
"""Configuration for DNS tests"""
testconfig.validators.validate(only="dns")
return testconfig["dns"]


@pytest.fixture(scope="package")
def dns_server(dns_config):
"""DNS server in the first geo region"""
return dns_config["dns_server"]


@pytest.fixture(scope="package")
def dns_server2(dns_config):
"""DNS server in the second geo region"""
return dns_config["dns_server2"]


@pytest.fixture(scope="module")
def dns_policy(blame, cluster, gateway, dns_server, module_label, dns_provider_secret):
"""DNSPolicy with load-balancing for the first cluster"""
load_balancing = LoadBalancing(default_geo=dns_server["geo_code"], default_weight=10)
return DNSPolicy.create_instance(
cluster, blame("dns"), gateway, dns_provider_secret, load_balancing, labels={"app": module_label}
)


@pytest.fixture(scope="module")
def dns_policy2(blame, cluster2, gateway2, dns_server, module_label, dns_provider_secret):
"""DNSPolicy with load-balancing for the second cluster"""
load_balancing = LoadBalancing(default_geo=dns_server["geo_code"], default_weight=10)
return DNSPolicy.create_instance(
cluster2, blame("dns"), gateway2, dns_provider_secret, load_balancing, labels={"app": module_label}
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Test load-balancing based on geolocation"""

import pytest
import dns.name
import dns.resolver

pytestmark = [pytest.mark.multicluster]


@pytest.fixture(scope="module")
def gateway2(gateway2, dns_server2):
"""Overwrite second gateway to have a different geocode"""
gateway2.label({"kuadrant.io/lb-attribute-geo-code": dns_server2["geo_code"]})
return gateway2


def test_load_balanced_geo(client, hostname, gateway, gateway2, dns_server, dns_server2):
"""
- Verify that request to the hostname is successful
- Verify that DNS resolution through nameservers from different regions returns according IPs
"""
result = client.get("/get")
assert not result.has_dns_error(), result.error
assert not result.has_cert_verify_error(), result.error
assert result.status_code == 200

resolver = dns.resolver.Resolver(configure=False)
resolver.nameservers = [dns_server["address"]]
assert resolver.resolve(hostname.hostname)[0].address == gateway.external_ip().split(":")[0]

resolver.nameservers = [dns_server2["address"]]
assert resolver.resolve(hostname.hostname)[0].address == gateway2.external_ip().split(":")[0]
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Test not supported geocode in geo load-balancing"""

import pytest

from testsuite.kuadrant.policy import has_condition

pytestmark = [pytest.mark.multicluster]


def test_unsupported_geocode(dns_policy):
"""Change default geocode to not existent one and verify that policy became not enforced"""
dns_policy.model.spec.loadBalancing.geo.defaultGeo = "XX"
dns_policy.apply()

assert dns_policy.wait_until(has_condition("Enforced", "False"))
10 changes: 10 additions & 0 deletions testsuite/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,16 @@ def check_condition(condition, condition_type, status, reason=None, message=None
return False


def hostname_to_ip(address: str) -> str:
"""Resolves hostname to IP if necessary"""
if any(c.isalpha() for c in address):
try:
return dns.resolver.resolve(address)[0].address
except dns.resolver.NXDOMAIN as e:
raise ValueError(f"Hostname {address} can't be resolved to an IP address") from e
return address


def is_nxdomain(hostname: str):
"""
Returns True if hostname has no `A` record in DNS. False otherwise.
Expand Down

0 comments on commit a36136f

Please sign in to comment.