Skip to content

Commit

Permalink
Merge pull request #346 from averevki/test-authorino-tracing
Browse files Browse the repository at this point in the history
Test authorino tracing
  • Loading branch information
pehala authored Mar 13, 2024
2 parents e3ef956 + 342c305 commit d012dcf
Show file tree
Hide file tree
Showing 12 changed files with 183 additions and 3 deletions.
3 changes: 3 additions & 0 deletions config/settings.local.yaml.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
# url: "AUTH0_URL"
# mockserver:
# url: "MOCKSERVER_URL"
# tracing:
# collector_url: "rpc://jaeger-collector.com:4317" # Tracing collector URL (may be internal)
# query_url: "http://jaeger-query.com" # Tracing query URL
# cfssl: "cfssl" # Path to the CFSSL library for TLS tests
# hyperfoil:
# url: "HYPERFOIL_URL"
Expand Down
6 changes: 5 additions & 1 deletion testsuite/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from dynaconf import Dynaconf, Validator

from testsuite.config.tools import fetch_route, fetch_secret
from testsuite.config.tools import fetch_route, fetch_service, fetch_secret


# pylint: disable=too-few-public-methods
Expand Down Expand Up @@ -38,6 +38,10 @@ def __init__(self, name, default, **kwargs) -> None:
Validator("service_protection.authorino.auth_url", must_exist=True)
& Validator("service_protection.authorino.oidc_url", must_exist=True)
),
DefaultValueValidator(
"tracing.collector_url", default=fetch_service("jaeger-collector", protocol="rpc", port=4317)
),
DefaultValueValidator("tracing.query_url", default=fetch_route("jaeger-query", force_http=True)),
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)),
Expand Down
25 changes: 25 additions & 0 deletions testsuite/config/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,31 @@ def _fetcher(settings, _):
return _fetcher


def fetch_service(name, protocol: str = None, port: int = None):
"""Fetches the local URL of existing service with specific name"""

def _fetcher(settings, _):
openshift = settings["tools"]
try:
if not openshift.service_exists(name):
logger.warning("Unable to fetch service %s from tools, service does not exists", name)
return None
except AttributeError:
logger.warning("Unable to fetch service %s from tools, tools project might be missing", name)
return None

service_url = f"{name}.{openshift.project}.svc.cluster.local"

if protocol:
service_url = f"{protocol}://{service_url}"
if port:
service_url = f"{service_url}:{port}"

return service_url

return _fetcher


def fetch_secret(name, key):
"""Fetches the key out of a secret with specific name"""

Expand Down
3 changes: 2 additions & 1 deletion testsuite/httpx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ def __init__(self, *, verify: Union[Certificate, bool] = True, cert: Certificate
_cert = (cert_file.name, key_file.name)

# Mypy does not understand the typing magic I have done
super().__init__(verify=_verify or verify, cert=_cert or cert, **kwargs) # type: ignore
self.verify = _verify or verify
super().__init__(verify=self.verify, cert=_cert or cert, **kwargs) # type: ignore

def close(self) -> None:
super().close()
Expand Down
17 changes: 16 additions & 1 deletion testsuite/openshift/authorino.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
"""Authorino CR object"""

import abc
from typing import Any, Dict, List
from typing import Any, Optional, Dict, List
from dataclasses import dataclass

from openshift_client import selector, timeout

from testsuite.openshift.client import OpenShiftClient
from testsuite.openshift import OpenShiftObject
from testsuite.lifecycle import LifecycleObject
from testsuite.utils import asdict


@dataclass
class TracingOptions:
"""Dataclass containing authorino tracing specification"""

endpoint: str
tags: Optional[dict[str, str]] = None
insecure: Optional[bool] = None


class Authorino(LifecycleObject):
Expand Down Expand Up @@ -45,6 +56,7 @@ def create_instance(
cluster_wide=False,
label_selectors: List[str] = None,
listener_certificate_secret=None,
tracing: TracingOptions = None,
log_level=None,
):
"""Creates base instance"""
Expand All @@ -68,6 +80,9 @@ def create_instance(
if listener_certificate_secret:
model["spec"]["listener"]["tls"] = {"enabled": True, "certSecretRef": {"name": listener_certificate_secret}}

if tracing:
model["spec"]["tracing"] = asdict(tracing)

with openshift.context:
return cls(model)

Expand Down
5 changes: 5 additions & 0 deletions testsuite/openshift/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ def get_secret(self, name):
with self.context:
return oc.selector(f"secret/{name}").object(cls=Secret)

def service_exists(self, name) -> bool:
"""Returns True if service with the given name exists"""
with self.context:
return oc.selector(f"svc/{name}").count_existing() == 1

def get_route(self, name):
"""Returns dict-like structure for accessing secret data"""
with self.context:
Expand Down
16 changes: 16 additions & 0 deletions testsuite/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
from testsuite.capabilities import has_kuadrant, has_mgc
from testsuite.certificates import CFSSLClient
from testsuite.config import settings
from testsuite.httpx import KuadrantClient
from testsuite.mockserver import Mockserver
from testsuite.tracing import TracingClient
from testsuite.gateway import Gateway, GatewayRoute, Hostname, Exposer
from testsuite.oidc import OIDCProvider
from testsuite.oidc.auth0 import Auth0Provider
Expand Down Expand Up @@ -202,6 +204,20 @@ def mockserver(testconfig, skip_or_fail):
return skip_or_fail(f"Mockserver configuration item is missing: {exc}")


@pytest.fixture(scope="module")
def tracing(testconfig, skip_or_fail):
"""Returns tracing client for tracing tests"""
try:
testconfig.validators.validate(only=["tracing"])
return TracingClient(
testconfig["tracing"]["collector_url"],
testconfig["tracing"]["query_url"],
KuadrantClient(verify=False),
)
except (KeyError, ValidationError) as exc:
return skip_or_fail(f"Tracing configuration item is missing: {exc}")


@pytest.fixture(scope="session")
def oidc_provider(rhsso) -> OIDCProvider:
"""Fixture which enables switching out OIDC providers for individual modules"""
Expand Down
Empty file.
20 changes: 20 additions & 0 deletions testsuite/tests/kuadrant/authorino/tracing/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Conftest for tracing tests"""

import pytest

from testsuite.openshift.authorino import TracingOptions


@pytest.fixture(scope="module")
def authorino_parameters(authorino_parameters, tracing):
"""Deploy authorino with tracing enabled"""
insecure_tracing = not tracing.client.verify
authorino_parameters["tracing"] = TracingOptions(endpoint=tracing.collector_url, insecure=insecure_tracing)
return authorino_parameters


@pytest.fixture(scope="module")
def authorization(authorization):
"""Add response with 'request.id' to found traced request with it"""
authorization.responses.add_simple("request.id")
return authorization
18 changes: 18 additions & 0 deletions testsuite/tests/kuadrant/authorino/tracing/test_tracing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""Test tracing"""

import pytest

from testsuite.utils import extract_response

pytestmark = [pytest.mark.authorino, pytest.mark.standalone_only]


def test_tracing(client, auth, tracing):
"""Send request and check if it's trace is saved into the tracing client"""
response = client.get("/get", auth=auth)
assert response.status_code == 200

request_id = extract_response(response) % None
assert request_id is not None

assert tracing.find_trace("Check", request_id)
30 changes: 30 additions & 0 deletions testsuite/tests/kuadrant/authorino/tracing/test_tracing_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Test custom tags set for request traces"""

import pytest

from testsuite.utils import extract_response

pytestmark = [pytest.mark.authorino, pytest.mark.standalone_only]


TAG_KEY = "test-key"
TAG_VALUE = "test-value"


@pytest.fixture(scope="module")
def authorino_parameters(authorino_parameters):
"""Deploy authorino with tracing enabled and custom tags set"""
authorino_parameters["tracing"].tags = {TAG_KEY: TAG_VALUE}
return authorino_parameters


@pytest.mark.issue("https://github.com/Kuadrant/authorino-operator/issues/171")
def test_tracing_tags(client, auth, tracing):
"""Send request and check if it's trace with custom tags is saved into the tracing client"""
response = client.get("/get", auth=auth)
assert response.status_code == 200

request_id = extract_response(response) % None
assert request_id is not None

assert tracing.find_tagged_trace("Check", request_id, TAG_KEY, TAG_VALUE)
43 changes: 43 additions & 0 deletions testsuite/tracing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Module with Tracing client for traces management"""

from typing import Optional, Iterator

import backoff
from apyproxy import ApyProxy

from testsuite.httpx import KuadrantClient


class TracingClient:
"""Tracing client for traces management"""

def __init__(self, collector_url: str, query_url: str, client: KuadrantClient = None):
self.collector_url = collector_url
self.client = client or KuadrantClient(verify=False)
self.query = ApyProxy(query_url, session=self.client)

def _get_traces(self, operation: str) -> Iterator[dict]:
"""Get traces from tracing client by operation name"""
params = {"service": "authorino", "operation": operation}
response = self.query.api.traces.get(params=params)
return reversed(response.json()["data"])

@backoff.on_predicate(backoff.fibo, lambda x: x is None, max_tries=5, jitter=None)
def find_trace(self, operation: str, request_id: str) -> Optional[dict]:
"""Find trace in tracing client by operation and authorino request id"""
for trace in self._get_traces(operation): # pylint: disable=too-many-nested-blocks
for span in trace["spans"]:
if span["operationName"] == operation:
for tag in span["tags"]:
if tag["key"] == "authorino.request_id" and tag["value"] == request_id:
return trace
return None

def find_tagged_trace(self, operation: str, request_id: str, tag_key: str, tag_value: str) -> Optional[dict]:
"""Find trace in tracing client by operation, authorino request id and tag key-value pair"""
if trace := self.find_trace(operation, request_id):
for process in trace["processes"]:
for proc_tag in trace["processes"][process]["tags"]:
if proc_tag["key"] == tag_key and proc_tag["value"] == tag_value:
return trace
return None

0 comments on commit d012dcf

Please sign in to comment.