Skip to content

Commit

Permalink
Rework testsuite to work with new spec.resources field
Browse files Browse the repository at this point in the history
- Add another EnvoyConfig class
- Add parametrization
- Reformat using black
  • Loading branch information
pehala committed Aug 29, 2023
1 parent bba71e1 commit dbb6aa0
Show file tree
Hide file tree
Showing 16 changed files with 381 additions and 190 deletions.
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ log_level = "INFO"
junit_logging = "all"
junit_family = "xunit2"

[tool.black]
line-length = 120

[tool.pylint.FORMAT]
max-line-length = 120
max-line-length = 125
disable = [
"duplicate-code", # reports false alarms AND can't be disabled locally; pylint issue #214
"fixme", # ignore TODOs
Expand Down
21 changes: 17 additions & 4 deletions testsuite/certificates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ def __init__(self, binary) -> None:
self.binary = binary

def _execute_command(
self, command: str, *args: str, stdin: Optional[str] = None, env: Optional[Dict[str, str]] = None
self,
command: str,
*args: str,
stdin: Optional[str] = None,
env: Optional[Dict[str, str]] = None,
):
args = (self.binary, command, *args)
try:
Expand Down Expand Up @@ -85,7 +89,10 @@ def exists(self):
return shutil.which(self.binary)

def generate_key(
self, common_name: str, names: Optional[List[Dict[str, str]]] = None, hosts: Optional[Collection[str]] = None
self,
common_name: str,
names: Optional[List[Dict[str, str]]] = None,
hosts: Optional[Collection[str]] = None,
) -> UnsignedKey:
"""Generates unsigned key"""
data: Dict[str, Any] = {"CN": common_name}
Expand All @@ -109,7 +116,10 @@ def sign_intermediate_authority(self, key: UnsignedKey, certificate_authority: C
*args,
"-",
stdin=key.csr,
env={"CA": certificate_authority.certificate, "KEY": certificate_authority.key},
env={
"CA": certificate_authority.certificate,
"KEY": certificate_authority.key,
},
)
return Certificate(key=key.key, certificate=result["cert"])

Expand All @@ -121,7 +131,10 @@ def sign(self, key: UnsignedKey, certificate_authority: Certificate) -> Certific
"-ca-key=env:KEY",
"-",
stdin=key.csr,
env={"CA": certificate_authority.certificate, "KEY": certificate_authority.key},
env={
"CA": certificate_authority.certificate,
"KEY": certificate_authority.key,
},
)
return Certificate(key=key.key, certificate=result["cert"])

Expand Down
5 changes: 4 additions & 1 deletion testsuite/config/openshift_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ def load(obj, env=None, silent=True, key=None, filename=None):
config = weakget(obj)
section = config["openshift"]
client = OpenShiftClient(
section["project"] % None, section["api_url"] % None, section["token"] % None, section["kubeconfig_path"] % None
section["project"] % None,
section["api_url"] % None,
section["token"] % None,
section["kubeconfig_path"] % None,
)
obj["openshift"] = client
8 changes: 7 additions & 1 deletion testsuite/httpx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ def __init__(self, msg, response):
class HttpxBackoffClient(Client):
"""Httpx client which retries unstable requests"""

def __init__(self, *, verify: Union[Certificate, bool] = True, cert: Certificate = None, **kwargs):
def __init__(
self,
*,
verify: Union[Certificate, bool] = True,
cert: Certificate = None,
**kwargs,
):
self.files = []
self.retry_codes = {503}
_verify = None
Expand Down
6 changes: 4 additions & 2 deletions testsuite/openshift/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ class LifecycleObject(abc.ABC):
@abc.abstractmethod
def commit(self):
"""Commits resource.
if there is some reconciliation needed, the method should wait until it is all reconciled"""
if there is some reconciliation needed, the method should wait until it is all reconciled
"""

@abc.abstractmethod
def delete(self):
"""Removes resource,
if there is some reconciliation needed, the method should wait until it is all reconciled"""
if there is some reconciliation needed, the method should wait until it is all reconciled
"""


class OpenShiftObject(APIObject):
Expand Down
38 changes: 17 additions & 21 deletions testsuite/openshift/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@

from testsuite.certificates import Certificate

# from testsuite.openshift.types.routes import Routes
# from testsuite.openshift.types.secrets import Secrets


class ServiceTypes(enum.Enum):
"""Service types enum."""
Expand All @@ -30,7 +27,13 @@ class OpenShiftClient:

# pylint: disable=too-many-public-methods

def __init__(self, project: str, api_url: str = None, token: str = None, kubeconfig_path: str = None):
def __init__(
self,
project: str,
api_url: str = None,
token: str = None,
kubeconfig_path: str = None,
):
self._project = project
self._api_url = api_url
self.token = token
Expand Down Expand Up @@ -79,16 +82,6 @@ def connected(self):
return False
return True

# @cached_property
# def routes(self):
# """Return dict-like interface for Routes"""
# return Routes(self)
#
# @cached_property
# def secrets(self):
# """Return dict-like interface for Secrets"""
# return Secrets(self)

def do_action(self, verb: str, *args, auto_raise: bool = True, parse_output: bool = False):
"""Run an oc command."""
with self.context:
Expand Down Expand Up @@ -136,20 +129,23 @@ def is_ready(self, selector: Selector):
)
return success

# def create(self, model):
# """Creates """
# with self.context:
# return oc.create(model, ["--save-config=true"])

def create_tls_secret(self, name: str, certificate: Certificate, labels: Optional[Dict[str, str]] = None):
def create_tls_secret(
self,
name: str,
certificate: Certificate,
labels: Optional[Dict[str, str]] = None,
):
"""Creates a TLS secret"""
model: Dict = {
"kind": "Secret",
"apiVersion": "v1",
"metadata": {
"name": name,
},
"stringData": {"tls.crt": certificate.chain or certificate.certificate, "tls.key": certificate.key},
"stringData": {
"tls.crt": certificate.chain or certificate.certificate,
"tls.key": certificate.key,
},
"type": "kubernetes.io/tls",
}
if labels is not None:
Expand Down
188 changes: 188 additions & 0 deletions testsuite/openshift/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
"""Module containing all config classes"""
from abc import ABC, abstractmethod
from enum import Enum
from functools import cached_property

import openshift as oc
import yaml

from testsuite.openshift import OpenShiftObject
from testsuite.openshift.client import OpenShiftClient


def convert_to_yaml(data: list[str | dict]):
"""Convert dict to specific format Marin3r uses and convert value to yaml if it is not a string"""
transformed = []
for value in data:
transformed.append({"value": value if isinstance(value, str) else yaml.dump(value)})
return transformed


class BaseEnvoyConfig(OpenShiftObject, ABC):
"""Base class for all EnvoyConfigs"""

class Status(Enum):
"""All known statuses of EnvoyConfig"""

# pylint: disable=invalid-name
InSync = "InSync"
Rollback = "Rollback"

@classmethod
@abstractmethod
def create_instance(
cls,
openshift: OpenShiftClient,
name,
listeners,
clusters=None,
endpoints=None,
runtimes=None,
routes=None,
scoped_routes=None,
secrets=None,
labels=None,
):
"""Creates new EnvoyConfig instance"""

@property
@abstractmethod
def listeners(self):
"""Returns all configured listeners"""

@cached_property
def ports(self) -> dict[str, int]:
"""Returns all the configured ports and their name"""
ports = {}
for listener in self.listeners:
ports[listener["name"]] = listener["address"]["socket_address"]["port_value"]
return ports

def wait_status(self, status: Status, timeout=30):
"""Waits until config has the expected status"""
with oc.timeout(timeout):

def _status(obj):
return obj.model.status.cacheState == status.value

success, _, _ = self.self_selector().until_all(success_func=_status)
return success


class LegacyEnvoyConfig(BaseEnvoyConfig):
"""Legacy EnvoyConfig resource, using envoyResources field"""

@classmethod
def create_instance(
cls,
openshift: OpenShiftClient,
name,
listeners,
clusters=None,
endpoints=None,
runtimes=None,
routes=None,
scoped_routes=None,
secrets=None,
labels=None,
):
"""Creates new EnvoyConfig"""
model = {
"apiVersion": "marin3r.3scale.net/v1alpha1",
"kind": "EnvoyConfig",
"metadata": {"name": name},
"spec": {
"nodeID": name,
"serialization": "yaml",
"envoyResources": {
"clusters": convert_to_yaml(clusters or []),
"endpoints": convert_to_yaml(endpoints or []),
"runtimes": convert_to_yaml(runtimes or []),
"routes": convert_to_yaml(routes or []),
"scopedRoutes": convert_to_yaml(scoped_routes or []),
"listeners": convert_to_yaml(listeners),
"secrets": [{"name": value} for value, _ in (secrets or [])],
},
},
}

if labels is not None:
model["metadata"]["labels"] = labels

return cls(model, context=openshift.context)

@property
def listeners(self):
listeners = []
for listener in self.model.spec.envoyResources.listeners:
listeners.append(yaml.safe_load(listener["value"]))
return listeners


class EnvoyConfig(BaseEnvoyConfig):
"""Envoy config configured using spec.resources"""

# pylint: disable=too-many-locals
@classmethod
def create_instance(
cls,
openshift: OpenShiftClient,
name,
listeners,
clusters=None,
endpoints=None,
runtimes=None,
routes=None,
scoped_routes=None,
secrets=None,
labels=None,
):
"""Creates new instance"""
model = {
"apiVersion": "marin3r.3scale.net/v1alpha1",
"kind": "EnvoyConfig",
"metadata": {"name": name},
"spec": {
"nodeID": name,
"serialization": "yaml",
"resources": [],
},
}
for resource_type, values in {
"cluster": clusters,
"endpoint": endpoints,
"runtime": runtimes,
"route": routes,
"scopedRoute": scoped_routes,
"listener": listeners,
}.items():
for value in values or []:
if isinstance(value, str):
value = yaml.safe_load(value)
model["spec"]["resources"].append(
{
"type": resource_type,
"value": value,
}
)
for secret, is_ca in secrets or []:
resource = {
"type": "secret",
"generateFromTlsSecret": secret,
}
if is_ca:
resource["blueprint"] = "validationContext"
model["spec"]["resources"].append(resource)

if labels is not None:
model["metadata"]["labels"] = labels

return cls(model, context=openshift.context)

@property
def listeners(self):
sections = []
for section in self.model.spec.resources:
if section.type == "listener":
sections.append(section["value"])
return sections
Loading

0 comments on commit dbb6aa0

Please sign in to comment.