diff --git a/changelogs/fragments/879-clusteroperator-waiter.py.yaml b/changelogs/fragments/879-clusteroperator-waiter.py.yaml new file mode 100644 index 00000000..36b45be9 --- /dev/null +++ b/changelogs/fragments/879-clusteroperator-waiter.py.yaml @@ -0,0 +1,5 @@ +minor_changes: + - >- + waiter.py - add ClusterOperator support. The module can now check OpenShift cluster health + by verifying ClusterOperator status requiring 'Available: True', 'Degraded: False', and + 'Progressing: False' for success. (https://github.com/ansible-collections/kubernetes.core/issues/869) diff --git a/plugins/module_utils/k8s/waiter.py b/plugins/module_utils/k8s/waiter.py index 16ee10dd..8bfb7634 100644 --- a/plugins/module_utils/k8s/waiter.py +++ b/plugins/module_utils/k8s/waiter.py @@ -117,11 +117,34 @@ def exists(resource: Optional[ResourceInstance]) -> bool: return bool(resource) and not empty_list(resource) +def cluster_operator_ready(resource: ResourceInstance) -> bool: + """ + Predicate to check if a single ClusterOperator is healthy. + Returns True if: + - "Available" is True + - "Degraded" is False + - "Progressing" is False + """ + if not resource: + return False + + # Extract conditions from the resource's status + conditions = resource.get("status", {}).get("conditions", []) + + status = {x.get("type", ""): x.get("status") for x in conditions} + return ( + (status.get("Degraded") == "False") + and (status.get("Progressing") == "False") + and (status.get("Available") == "True") + ) + + RESOURCE_PREDICATES = { "DaemonSet": daemonset_ready, "Deployment": deployment_ready, "Pod": pod_ready, "StatefulSet": statefulset_ready, + "ClusterOperator": cluster_operator_ready, } diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.14.txt index 0069053b..bdcaf2c1 100644 --- a/tests/sanity/ignore-2.14.txt +++ b/tests/sanity/ignore-2.14.txt @@ -10,6 +10,7 @@ plugins/module_utils/k8sdynamicclient.py import-3.11!skip plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc +tests/unit/module_utils/fixtures/clusteroperator.yml yamllint!skip tests/unit/module_utils/fixtures/definitions.yml yamllint!skip tests/unit/module_utils/fixtures/deployments.yml yamllint!skip tests/unit/module_utils/fixtures/pods.yml yamllint!skip diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt index f6189e3a..f534f27a 100644 --- a/tests/sanity/ignore-2.15.txt +++ b/tests/sanity/ignore-2.15.txt @@ -11,6 +11,7 @@ plugins/module_utils/version.py pylint!skip plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc +tests/unit/module_utils/fixtures/clusteroperator.yml yamllint!skip tests/unit/module_utils/fixtures/definitions.yml yamllint!skip tests/unit/module_utils/fixtures/deployments.yml yamllint!skip tests/integration/targets/k8s_delete/files/deployments.yaml yamllint!skip diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt index d152cbda..e5003b5f 100644 --- a/tests/sanity/ignore-2.16.txt +++ b/tests/sanity/ignore-2.16.txt @@ -14,6 +14,7 @@ plugins/module_utils/version.py pylint!skip plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc +tests/unit/module_utils/fixtures/clusteroperator.yml yamllint!skip tests/unit/module_utils/fixtures/definitions.yml yamllint!skip tests/unit/module_utils/fixtures/deployments.yml yamllint!skip tests/integration/targets/k8s_delete/files/deployments.yaml yamllint!skip diff --git a/tests/sanity/ignore-2.17.txt b/tests/sanity/ignore-2.17.txt index d152cbda..e5003b5f 100644 --- a/tests/sanity/ignore-2.17.txt +++ b/tests/sanity/ignore-2.17.txt @@ -14,6 +14,7 @@ plugins/module_utils/version.py pylint!skip plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc +tests/unit/module_utils/fixtures/clusteroperator.yml yamllint!skip tests/unit/module_utils/fixtures/definitions.yml yamllint!skip tests/unit/module_utils/fixtures/deployments.yml yamllint!skip tests/integration/targets/k8s_delete/files/deployments.yaml yamllint!skip diff --git a/tests/sanity/ignore-2.18.txt b/tests/sanity/ignore-2.18.txt index fc66cfc9..d6216adf 100644 --- a/tests/sanity/ignore-2.18.txt +++ b/tests/sanity/ignore-2.18.txt @@ -11,6 +11,7 @@ plugins/module_utils/version.py pylint!skip plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc +tests/unit/module_utils/fixtures/clusteroperator.yml yamllint!skip tests/unit/module_utils/fixtures/definitions.yml yamllint!skip tests/unit/module_utils/fixtures/deployments.yml yamllint!skip tests/integration/targets/k8s_delete/files/deployments.yaml yamllint!skip diff --git a/tests/sanity/ignore-2.19.txt b/tests/sanity/ignore-2.19.txt index fc66cfc9..d6216adf 100644 --- a/tests/sanity/ignore-2.19.txt +++ b/tests/sanity/ignore-2.19.txt @@ -11,6 +11,7 @@ plugins/module_utils/version.py pylint!skip plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc +tests/unit/module_utils/fixtures/clusteroperator.yml yamllint!skip tests/unit/module_utils/fixtures/definitions.yml yamllint!skip tests/unit/module_utils/fixtures/deployments.yml yamllint!skip tests/integration/targets/k8s_delete/files/deployments.yaml yamllint!skip diff --git a/tests/unit/module_utils/fixtures/clusteroperator.yml b/tests/unit/module_utils/fixtures/clusteroperator.yml new file mode 100644 index 00000000..83c87b2b --- /dev/null +++ b/tests/unit/module_utils/fixtures/clusteroperator.yml @@ -0,0 +1,99 @@ +--- +apiVersion: config.openshift.io/v1 +kind: ClusterOperator +metadata: + name: authentication +spec: {} +status: + conditions: + - message: All is well + reason: AsExpected + status: 'False' + type: Degraded + - message: 'AuthenticatorCertKeyProgressing: All is well' + reason: AsExpected + status: 'False' + type: Progressing + - message: All is well + reason: AsExpected + status: 'True' + type: Available + - message: All is well + reason: AsExpected + status: 'True' + type: Upgradeable + - reason: NoData + status: Unknown + type: EvaluationConditionsDetected +--- +apiVersion: config.openshift.io/v1 +kind: ClusterOperator +metadata: + name: dns +spec: {} +status: + conditions: + - message: DNS "default" is available. + reason: AsExpected + status: 'True' + type: Available + - message: 'DNS "default" reports Progressing=True: "Have 2 available node-resolver + pods, want 3."' + reason: DNSReportsProgressingIsTrue + status: 'True' + type: Progressing + - reason: DNSNotDegraded + status: 'False' + type: Degraded + - message: 'DNS default is upgradeable: DNS Operator can be upgraded' + reason: DNSUpgradeable + status: 'True' + type: Upgradeable +--- +apiVersion: config.openshift.io/v1 +kind: ClusterOperator +metadata: + name: dns +spec: {} +status: + conditions: + - message: DNS "default" is available. + reason: AsExpected + status: 'True' + type: Available + - message: 'DNS "default" reports Progressing=True: "Have 2 available node-resolver + pods, want 3."' + reason: DNSReportsProgressingIsTrue + status: 'False' + type: Progressing + - reason: DNSNotDegraded + status: 'True' + type: Degraded + - message: 'DNS default is upgradeable: DNS Operator can be upgraded' + reason: DNSUpgradeable + status: 'False' + type: Upgradeable +--- +apiVersion: config.openshift.io/v1 +kind: ClusterOperator +metadata: + name: dns +spec: {} +status: + conditions: + - message: DNS "default" is available. + reason: AsExpected + status: 'False' + type: Available + - message: 'DNS "default" reports Progressing=True: "Have 2 available node-resolver + pods, want 3."' + reason: DNSReportsProgressingIsTrue + status: 'True' + type: Progressing + - reason: DNSNotDegraded + status: 'True' + type: Degraded + - message: 'DNS default is upgradeable: DNS Operator can be upgraded' + reason: DNSUpgradeable + status: 'False' + type: Upgradeable diff --git a/tests/unit/module_utils/test_waiter.py b/tests/unit/module_utils/test_waiter.py index e63019ec..c34cdaf7 100644 --- a/tests/unit/module_utils/test_waiter.py +++ b/tests/unit/module_utils/test_waiter.py @@ -9,6 +9,7 @@ from ansible_collections.kubernetes.core.plugins.module_utils.k8s.waiter import DummyWaiter, Waiter, clock, + cluster_operator_ready, custom_condition, deployment_ready, exists, @@ -29,6 +30,7 @@ def resources(filepath): RESOURCES = resources("fixtures/definitions.yml") PODS = resources("fixtures/pods.yml") DEPLOYMENTS = resources("fixtures/deployments.yml") +CLUSTER_OPERATOR = resources("fixtures/clusteroperator.yml") def test_clock_times_out(): @@ -119,3 +121,10 @@ def test_get_waiter_returns_correct_waiter(): ).predicate.func == custom_condition ) + + +@pytest.mark.parametrize( + "clusteroperator,expected", zip(CLUSTER_OPERATOR, [True, False, False, False]) +) +def test_cluster_operator(clusteroperator, expected): + assert cluster_operator_ready(clusteroperator) is expected