Skip to content

Commit

Permalink
Add Kuadrantctl tests
Browse files Browse the repository at this point in the history
  • Loading branch information
pehala committed Jul 3, 2024
1 parent 2ab8942 commit 56cf3b2
Show file tree
Hide file tree
Showing 16 changed files with 404 additions and 5 deletions.
14 changes: 9 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: commit-acceptance pylint mypy black reformat test authorino poetry poetry-no-dev mgc container-image polish-junit reportportal authorino-standalone limitador kuadrant kuadrant-only disruptive
.PHONY: commit-acceptance pylint mypy black reformat test authorino poetry poetry-no-dev mgc container-image polish-junit reportportal authorino-standalone limitador kuadrant kuadrant-only disruptive kuadrantctl

TB ?= short
LOGLEVEL ?= INFO
Expand Down Expand Up @@ -45,23 +45,23 @@ test pytest tests: kuadrant

authorino: ## Run only authorino related tests
authorino: poetry-no-dev
$(PYTEST) -n4 -m 'authorino' --dist loadfile --enforce $(flags) testsuite
$(PYTEST) -n4 -m 'authorino' --dist loadfile --enforce $(flags) testsuite/tests/kuadrant

authorino-standalone: ## Run only test capable of running with standalone Authorino
authorino-standalone: poetry-no-dev
$(PYTEST) -n4 -m 'authorino and not kuadrant_only' --dist loadfile --enforce --standalone $(flags) testsuite/tests/kuadrant/authorino

limitador: ## Run only Limitador related tests
limitador: poetry-no-dev
$(PYTEST) -n4 -m 'limitador' --dist loadfile --enforce $(flags) testsuite
$(PYTEST) -n4 -m 'limitador' --dist loadfile --enforce $(flags) testsuite/tests/kuadrant

kuadrant: ## Run all tests available on Kuadrant
kuadrant: poetry-no-dev
$(PYTEST) -n4 -m 'not standalone_only and not disruptive' --dist loadfile --enforce $(flags) testsuite
$(PYTEST) -n4 -m 'not standalone_only and not disruptive' --dist loadfile --enforce $(flags) testsuite/tests/kuadrant

kuadrant-only: ## Run Kuadrant-only tests
kuadrant-only: poetry-no-dev
$(PYTEST) -n4 -m 'kuadrant_only and not standalone_only and not disruptive' --dist loadfile --enforce $(flags) testsuite
$(PYTEST) -n4 -m 'kuadrant_only and not standalone_only and not disruptive' --dist loadfile --enforce $(flags) testsuite/tests/kuadrant

dnstls: ## Run DNS and TLS tests
dnstls: poetry-no-dev
Expand All @@ -71,6 +71,10 @@ disruptive: ## Run disruptive tests
disruptive: poetry-no-dev
$(PYTEST) -m 'disruptive' $(flags) testsuite

kuadrantctl: ## Run Kuadrantctl tests
kuadrantctl: poetry-no-dev
$(PYTEST) -n4 --dist loadfile --enforce $(flags) testsuite/tests/kuadrantctl

poetry.lock: pyproject.toml
poetry lock

Expand Down
1 change: 1 addition & 0 deletions config/settings.local.yaml.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# api_url: "https://api.openshift.com" # Optional: OpenShift API URL, if None it will OpenShift that you are logged in
# token: "KUADRANT_RULEZ" # Optional: OpenShift Token, if None it will OpenShift that you are logged in
# kubeconfig_path: "~/.kube/config" # Optional: Kubeconfig to use, if None the default one is used
# kuadrantctl: kuadrantctl
# tools:
# project: "tools" # Optional: OpenShift project, where external tools are located
# keycloak:
Expand Down
1 change: 1 addition & 0 deletions config/settings.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
default:
dynaconf_merge: true
cluster: {}
kuadrantctl: "kuadrantctl"
tools:
project: "tools"
cfssl: "cfssl"
Expand Down
12 changes: 12 additions & 0 deletions testsuite/gateway/gateway_api/route.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,15 @@ def add_backend(self, backend: "Backend", prefix="/"):
@modify
def remove_all_backend(self):
self.model.spec.rules.clear()

def wait_for_ready(self):
"""Waits until HTTPRoute is reconcilled by GatewayProvider"""

def _ready(obj):
for condition_set in obj.model.status.parents:
if condition_set.controllerName == "istio.io/gateway-controller":
return (all(x.status == "True" for x in condition_set.conditions),)
return False

success = self.wait_until(_ready, timelimit=10)
assert success, f"{self.kind()} did got get ready in time"
52 changes: 52 additions & 0 deletions testsuite/kuadrantctl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# pylint: disable=line-too-long
"""
Help as of 0.2.3
Kuadrant configuration command line utility
Usage:
kuadrantctl [command]
Available Commands:
completion Generate the autocompletion script for the specified shell
generate Commands related to kubernetes object generation
gatewayapi Generate Gataway API resources
httproute Generate Gateway API HTTPRoute from OpenAPI 3.0.X
kuadrant Generate Kuadrant resources
authpolicy Generate Kuadrant AuthPolicy from OpenAPI 3.0.X
ratelimitpolicy Generate Kuadrant Rate Limit Policy from OpenAPI 3.0.X
help Help about any command
version Print the version number of kuadrantctl
Flags:
-h, --help help for httproute
--oas string Path to OpenAPI spec file (in JSON or YAML format), URL, or '-' to read from standard input (required)
-o, --output-format string Output format: 'yaml' or 'json'. (default "yaml")
Global Flags:
-v, --verbose verbose output
Use "kuadrantctl [command] --help" for more information about a command.
"""

import subprocess


class KuadrantCTL:
"""Wrapper on top of kuadrantctl binary"""

def __init__(self, binary) -> None:
super().__init__()
self.binary = binary

def run(self, *args, **kwargs):
"""Passes arguments to Subprocess.run, see that for more details"""
args = (self.binary, *args)
kwargs.setdefault("capture_output", True)
kwargs.setdefault("check", True)
kwargs.setdefault("text", True)
# We do supply value for check :)
return subprocess.run(args, **kwargs) # pylint: disable= subprocess-run-check
49 changes: 49 additions & 0 deletions testsuite/oas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""OAS processing"""

import contextlib
import json
import tempfile
from collections import UserDict

import yaml

from testsuite.backend import Backend
from testsuite.gateway import Referencable, Hostname


@contextlib.contextmanager
def as_tmp_file(text):
"""Saves text in a temporary file and returns absolute path"""
with tempfile.NamedTemporaryFile("w") as file:
file.write(text)
file.flush()
yield file.name


class OASWrapper(UserDict):
"""Wrapper for OpenAPISpecification"""

def as_json(self):
"""Returns OAS as JSON"""
return json.dumps(self.data)

def as_yaml(self):
"""Returns OAS as YAML"""
return yaml.dump(self.data)

def add_backend_to_paths(self, backend: Backend):
"""Adds backend to all paths, should be only used in tests that do not test this section"""
for path in self["paths"].values():
path["x-kuadrant"] = {
"backendRefs": [backend.reference],
}

def add_top_level_route(self, parent: Referencable, hostname: Hostname, name: str):
"""Adds top-level x-kuadrant definition for Route, should be only used in tests that do not test this section"""
self["x-kuadrant"] = {
"route": {
"name": name,
"hostnames": [hostname.hostname],
"parentRefs": [parent.reference],
}
}
8 changes: 8 additions & 0 deletions testsuite/openshift/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,11 @@ def project_exists(self):
return True
except oc.OpenShiftPythonException:
return False

def apply_from_string(self, string, cls, cmd_args=None):
"""Applies new object from the string to the server and returns it wrapped in the class"""
with self.context:
selector = oc.apply(string, cmd_args=cmd_args)
obj = selector.object(cls=cls)
obj.context = self.context
return obj
Empty file.
35 changes: 35 additions & 0 deletions testsuite/resources/oas/base_httpbin.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
openapi: 3.1.0
info:
title: Httpbin
version: 0.0.51
paths:
"/get":
get:
operationId: get_get
responses:
'200':
description: Successful Response
content:
application/json:
schema:
type: string
"/anything":
get:
operationId: get_anything
responses:
'200':
description: Successful Response
content:
application/json:
schema:
type: string
put:
operationId: put_anything
responses:
'200':
description: Successful Response
content:
application/json:
schema:
type: string
Empty file.
Empty file.
12 changes: 12 additions & 0 deletions testsuite/tests/kuadrantctl/cli/test_basic_commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Tests basic commands"""

import pytest


# https://github.com/Kuadrant/kuadrantctl/issues/90
@pytest.mark.parametrize("command", ["help", "version"])
def test_commands(kuadrantctl, command):
"""Test that basic commands exists and returns anything"""
result = kuadrantctl.run(command)
assert not result.stderr, f"Command '{command}' returned an error: {result.stderr}"
assert result.stdout, f"Command '{command}' returned empty output"
66 changes: 66 additions & 0 deletions testsuite/tests/kuadrantctl/cli/test_simple_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Tests that you can generate simple AuthPolicy, focused on the cmdline options more than on extension functionality"""

import pytest

from testsuite.httpx.auth import HttpxOidcClientAuth
from testsuite.oas import as_tmp_file
from testsuite.policy.authorization.auth_policy import AuthPolicy


@pytest.fixture(scope="module")
def auth(keycloak):
"""Returns authentication object for HTTPX"""
return HttpxOidcClientAuth(keycloak.get_token, "authorization")


@pytest.fixture(scope="module")
def oas(oas, keycloak, blame, gateway, hostname, backend):
"""Add OIDC configuration"""
oas.add_top_level_route(gateway, hostname, blame("route"))

oas["components"] = {
"securitySchemes": {
"oidc": {
"type": "openIdConnect",
"openIdConnectUrl": keycloak.well_known["issuer"],
# https://github.com/Kuadrant/kuadrantctl/issues/94
# "openIdConnectUrl": keycloak.well_known["issuer"] + "/.well-known/openid-configuration",
}
}
}
anything = oas["paths"]["/anything"]
anything["x-kuadrant"] = {
"backendRefs": [backend.reference],
}
anything["get"]["security"] = [{"oidc": []}]
return oas


@pytest.mark.parametrize("encoder", [pytest.param("as_json", id="JSON"), pytest.param("as_yaml", id="YAML")])
@pytest.mark.parametrize("stdin", [pytest.param(True, id="STDIN"), pytest.param(False, id="File")])
def test_generate_authpolicy(request, kuadrantctl, oas, encoder, openshift, client, stdin, auth):
"""Generates Policy from OAS and tests that it works as expected"""
encoded = getattr(oas, encoder)()

if stdin:
result = kuadrantctl.run("generate", "kuadrant", "authpolicy", "--oas", "-", input=encoded)
else:
with as_tmp_file(encoded) as file_name:
result = kuadrantctl.run("generate", "kuadrant", "authpolicy", "--oas", file_name)

policy = openshift.apply_from_string(result.stdout, AuthPolicy)
request.addfinalizer(policy.delete)

policy.wait_for_ready()

response = client.get("/anything")
assert response.status_code == 401

response = client.get("/anything", auth=auth)
assert response.status_code == 200

response = client.get("/anything", headers={"Authorization": "Bearer xyz"})
assert response.status_code == 401

response = client.put("/anything")
assert response.status_code == 200
45 changes: 45 additions & 0 deletions testsuite/tests/kuadrantctl/cli/test_simple_limit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""
Tests that you can generate simple RateLimitPolicy, focused on the cmdline options more than on extension
functionality
"""

import pytest

from testsuite.oas import as_tmp_file
from testsuite.policy.rate_limit_policy import Limit, RateLimitPolicy
from testsuite.utils import asdict


@pytest.fixture(scope="module")
def oas(oas, blame, gateway, hostname, backend):
"""Add X-Kuadrant specific fields"""
oas.add_top_level_route(gateway, hostname, blame("route"))
oas.add_backend_to_paths(backend)

oas["paths"]["/anything"]["get"]["x-kuadrant"] = {"rate_limit": {"rates": [asdict(Limit(3, 20))]}}
return oas


@pytest.mark.parametrize("encoder", [pytest.param("as_json", id="JSON"), pytest.param("as_yaml", id="YAML")])
@pytest.mark.parametrize("stdin", [pytest.param(True, id="STDIN"), pytest.param(False, id="File")])
def test_generate_limit(request, kuadrantctl, oas, encoder, openshift, client, stdin):
"""Tests that RateLimitPolicy can be generated and that it is enforced as expected"""
encoded = getattr(oas, encoder)()

if stdin:
result = kuadrantctl.run("generate", "kuadrant", "ratelimitpolicy", "--oas", "-", input=encoded)
else:
with as_tmp_file(encoded) as file_name:
result = kuadrantctl.run("generate", "kuadrant", "ratelimitpolicy", "--oas", file_name)

policy = openshift.apply_from_string(result.stdout, RateLimitPolicy)
request.addfinalizer(policy.delete)
policy.wait_for_ready()

responses = client.get_many("/anything", 3)
responses.assert_all(status_code=200)
assert client.get("/anything").status_code == 429

# Check that it did not affect other endpoints
responses = client.get_many("/get", 5)
responses.assert_all(status_code=200)
Loading

0 comments on commit 56cf3b2

Please sign in to comment.