From 4314becb60bb0047cabce5539f7ddcb66fd5c159 Mon Sep 17 00:00:00 2001 From: Jakub Smolar Date: Tue, 4 Jun 2024 14:18:53 +0200 Subject: [PATCH] Add support methods for CR objects --- testsuite/openshift/__init__.py | 24 +++++++ testsuite/openshift/authorino.py | 8 +-- testsuite/openshift/kuadrant.py | 62 +++++++++++++++---- .../test_reconcile_limitador.py | 62 +++++++++++++------ 4 files changed, 121 insertions(+), 35 deletions(-) diff --git a/testsuite/openshift/__init__.py b/testsuite/openshift/__init__.py index f079a90d..f8476e77 100644 --- a/testsuite/openshift/__init__.py +++ b/testsuite/openshift/__init__.py @@ -1,5 +1,6 @@ """OpenShift common objects""" +import dataclasses import functools from dataclasses import dataclass, field from typing import Optional, Literal @@ -7,6 +8,7 @@ from openshift_client import APIObject, timeout, OpenShiftPythonException from testsuite.lifecycle import LifecycleObject +from testsuite.utils import asdict class OpenShiftObject(APIObject, LifecycleObject): @@ -44,6 +46,28 @@ def wait_until(self, test_function, timelimit=90): return False +class CustomResource(OpenShiftObject): + """Custom APIObjects that implements methods that improves manipulation with CR objects""" + + def safe_apply(self): + """ + Modifies the model of the apiobj and asserts if a change was applied to a resource. + Uses modify_and_apply method from OpenshiftObject. + """ + result, status = self.modify_and_apply(lambda _: True, retries=2) + assert status, f"Unable to apply changes for APIObject with result: {result}" + return result + + def __getitem__(self, name): + return self.model.spec[name] + + def __setitem__(self, name, value): + if dataclasses.is_dataclass(value): + self.model.spec[name] = asdict(value) + else: + self.model.spec[name] = value + + def modify(func): """Wraps method of a subclass of OpenShiftObject to use modify_and_apply when the object is already committed to the server, or run it normally if it isn't. diff --git a/testsuite/openshift/authorino.py b/testsuite/openshift/authorino.py index 7656e3cd..71538837 100644 --- a/testsuite/openshift/authorino.py +++ b/testsuite/openshift/authorino.py @@ -1,14 +1,14 @@ """Authorino CR object""" import abc -from typing import Any, Optional, Dict, List from dataclasses import dataclass +from typing import Any, Optional, Dict, List from openshift_client import selector, timeout -from testsuite.openshift.client import OpenShiftClient -from testsuite.openshift import OpenShiftObject from testsuite.lifecycle import LifecycleObject +from testsuite.openshift import CustomResource +from testsuite.openshift.client import OpenShiftClient from testsuite.utils import asdict @@ -44,7 +44,7 @@ def oidc_url(self): """Authorino oidc url""" -class AuthorinoCR(OpenShiftObject, Authorino): +class AuthorinoCR(CustomResource, Authorino): """Represents Authorino CR objects from Authorino-operator""" @classmethod diff --git a/testsuite/openshift/kuadrant.py b/testsuite/openshift/kuadrant.py index 01e8ea4e..8230e6fc 100644 --- a/testsuite/openshift/kuadrant.py +++ b/testsuite/openshift/kuadrant.py @@ -1,29 +1,69 @@ """Kuadrant CR object""" -from openshift_client import selector +import dataclasses -from testsuite.openshift import OpenShiftObject, modify +from openshift_client import selector, timeout + +from testsuite.openshift import CustomResource from testsuite.openshift.deployment import Deployment +from testsuite.utils import asdict + + +class KuadrantSection: + """ + Base class for Kuadrant sub components: + Authorino - spec.authorino + Limitador - spec.limitador + """ + + def __init__(self, kuadrant_cr, spec_name): + super().__init__() + self.kuadrant_cr = kuadrant_cr + self.spec_name = spec_name + + def __getitem__(self, name): + return self.kuadrant_cr.model.spec[self.spec_name][name] + + def __setitem__(self, name, value): + if dataclasses.is_dataclass(value): + self.kuadrant_cr.model.spec[self.spec_name][name] = asdict(value) + else: + self.kuadrant_cr.model.spec[self.spec_name][name] = value + def __getattr__(self, item): + try: + return getattr(self.kuadrant_cr, item) + except AttributeError as exc: + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{item}'") from exc -class KuadrantCR(OpenShiftObject): + +class KuadrantCR(CustomResource): """Represents Kuadrant CR objects""" LIMITADOR = "limitador-limitador" @property - def limitador(self) -> dict: - """Returns spec.limitador from Kuadrant object""" - return self.model.spec.setdefault("limitador", {}) + def authorino(self) -> KuadrantSection: + """Returns spec.authorino from Kuadrant object""" + return KuadrantSection(self, "authorino") - @limitador.setter - @modify - def limitador(self, value: dict): - """Sets the value of spec.limitador""" - self.model.spec["limitador"] = value + @property + def limitador(self) -> KuadrantSection: + """Returns spec.limitador from Kuadrant object""" + return KuadrantSection(self, "limitador") @property def limitador_deployment(self): """Returns Deployment object for this Authorino""" with self.context: return selector(f"deployment/{self.LIMITADOR}").object(cls=Deployment) + + def wait_for_ready(self): + """Waits until Kuadrant CR reports ready status""" + with timeout(90): + success, _, _ = self.self_selector().until_all( + success_func=lambda obj: len(obj.model.status.conditions) > 0 + and all(x.status == "True" for x in obj.model.status.conditions) + ) + assert success, "Kuadrant did got get ready in time" + self.refresh() diff --git a/testsuite/tests/kuadrant/reconciliation/cr_configuration/test_reconcile_limitador.py b/testsuite/tests/kuadrant/reconciliation/cr_configuration/test_reconcile_limitador.py index 045261d6..153fccfe 100644 --- a/testsuite/tests/kuadrant/reconciliation/cr_configuration/test_reconcile_limitador.py +++ b/testsuite/tests/kuadrant/reconciliation/cr_configuration/test_reconcile_limitador.py @@ -2,6 +2,9 @@ import pytest +from testsuite.openshift.deployment import ContainerResources +from testsuite.utils import asdict + pytestmark = [pytest.mark.kuadrant_only] @@ -18,31 +21,43 @@ def kuadrant_clean_up(request, kuadrant): """ def _finalize(): - kuadrant.limitador = {"replicas": 1, "resourceRequirements": {"requests": {"cpu": "250m", "memory": "32Mi"}}} + kuadrant.limitador["replicas"] = 1 + kuadrant.limitador["resourceRequirements"] = ContainerResources(requests_cpu="250m", requests_memory="32Mi") + kuadrant.safe_apply() + kuadrant.wait_for_ready() request.addfinalizer(_finalize) -@pytest.mark.parametrize( - "field, value", - [ - pytest.param("replicas", 2, id="replicas"), - pytest.param( - "resourceRequirements", {"requests": {"cpu": "300m", "memory": "40Mi"}}, id="resourceRequirements" - ), - ], -) -def test_fields_to_reconcile(kuadrant, field, value): +def test_replicas_field_to_reconcile(kuadrant): + """ + Test: + - change replicas field to 2 in Kuadrant CR + - assert that replicas field in Kuadrant CR spec.limitador and Limitador deployment is equal to 2 + """ + kuadrant.limitador["replicas"] = 2 + kuadrant.safe_apply() + kuadrant.wait_for_ready() + + assert kuadrant.limitador["replicas"] == 2 + assert kuadrant.limitador_deployment.model.spec["replicas"] == 2 + + +def test_resource_requirements_field_to_reconcile(kuadrant): """ Test: - - change specific `field` to `value` in Kuadrant CR - - assert that `field` in Kuadrant CR Limitador is equal to `value` - - assert that `field` in Limitador deployment is equal to `value` + - change resourceRequirements field to `value` in Kuadrant CR + - assert that resourceRequirements field in Kuadrant CR spec.limitador and Limitador deployment + is equal to `value` """ - kuadrant.limitador = {field: value} + value = ContainerResources(requests_cpu="300m", requests_memory="40Mi") + + kuadrant.limitador["resourceRequirements"] = value + kuadrant.safe_apply() + kuadrant.wait_for_ready() - assert value == kuadrant.limitador[field] - assert str(value) in str(kuadrant.limitador_deployment.model.spec) + assert kuadrant.limitador["resourceRequirements"] == asdict(value) + assert kuadrant.limitador_deployment.model.spec.template.spec.containers[0]["resources"] == asdict(value) @pytest.mark.xfail @@ -55,9 +70,16 @@ def test_blank_fields_wont_reconcile(kuadrant): - assert replicas field is 3 for limitador deployment - assert blank field resourceRequirements is returned to default for limitador deployment """ - kuadrant.limitador = {"replicas": 2, "resourceRequirements": {"requests": {"cpu": "300m", "memory": "40Mi"}}} + kuadrant.limitador["replicas"] = 2 + kuadrant.limitador["resourceRequirements"] = ContainerResources(requests_cpu="300m", requests_memory="40Mi") + kuadrant.safe_apply() + kuadrant.wait_for_ready() - kuadrant.limitador = {"replicas": 3} + kuadrant.limitador["replicas"] = 3 + kuadrant.safe_apply() + kuadrant.wait_for_ready() assert kuadrant.limitador_deployment.model.spec.replicas == 3 - assert "'cpu': '250m', 'memory': '32Mi'" in str(kuadrant.limitador_deployment.model.spec) + assert kuadrant.limitador_deployment.model.spec.template.spec.containers[0]["resources"] == asdict( + ContainerResources(requests_cpu="250m", requests_memory="32Mi") + )