Skip to content

Commit 8ad8e90

Browse files
authored
Move cache_admin_client to be public (#2890)
##### Short description: Admin Client is used to CRUD cluster resources. `admin_client` provides this client, and all functions calling ocp_resources resources must get a client as an attributes. There are, however, places in the code (i.e in pytest-native fixtures or during pre-execution hooks) that the `admin_client` cannot be accessible. While there are pytest hacks to use the fixture, we should take a simple, clear approach. The usage must be very limited, the existing `_cache_admin_client` is now public to allow re-using it in places described above. ##### More details: ##### What this PR does / why we need it: ##### Which issue(s) this PR fixes: ##### Special notes for reviewer: ##### jira-ticket: <!-- full-ticket-url needs to be provided. This would add a link to the pull request to the jira and close it when the pull request is merged If the task is not tracked by a Jira ticket, just write "NONE". --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Expanded architecture detection tests with comprehensive coverage for multiple CPU architectures and node configurations. * Updated test infrastructure to support new client caching mechanism. * **Chores** * Centralized client management through a new cached utility function for improved resource efficiency and consistency across codebase. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 7a9ab36 commit 8ad8e90

File tree

7 files changed

+100
-52
lines changed

7 files changed

+100
-52
lines changed

conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@
2121
from _pytest.reports import CollectReport, TestReport
2222
from _pytest.runner import CallInfo
2323
from kubernetes.dynamic.exceptions import ConflictError
24-
from ocp_resources.resource import get_client
2524
from pyhelper_utils.shell import run_command
2625
from pytest import Item
2726
from pytest_testconfig import config as py_config
2827

28+
import utilities.cluster
2929
import utilities.infra
3030
from libs.storage.config import StorageClassConfig
3131
from utilities.bitwarden import get_cnv_tests_secret_by_name
@@ -792,7 +792,7 @@ def _update_os_related_config():
792792
py_config["version_explorer_url"] = get_cnv_version_explorer_url(pytest_config=session.config)
793793
if not session.config.getoption("--skip-artifactory-check"):
794794
py_config["server_url"] = py_config["server_url"] or get_artifactory_server_url(
795-
cluster_host_url=get_client().configuration.host
795+
cluster_host_url=utilities.cluster.cache_admin_client().configuration.host
796796
)
797797
py_config["servers"] = {
798798
name: _server.format(server=py_config["server_url"]) for name, _server in py_config["servers"].items()

tests/conftest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
from tests.utils import download_and_extract_tar, update_cluster_cpu_model
7575
from utilities.artifactory import get_artifactory_header, get_http_image_url, get_test_artifact_server_url
7676
from utilities.bitwarden import get_cnv_tests_secret_by_name
77+
from utilities.cluster import cache_admin_client
7778
from utilities.constants import (
7879
AAQ_NAMESPACE_LABEL,
7980
AMD,
@@ -305,7 +306,7 @@ def admin_client():
305306
"""
306307
Get DynamicClient
307308
"""
308-
return get_client()
309+
return cache_admin_client()
309310

310311

311312
@pytest.fixture(scope="session")

utilities/architecture.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import os
22

33
from ocp_resources.node import Node
4-
from ocp_resources.resource import get_client
4+
5+
from utilities.cluster import cache_admin_client
56

67

78
def get_cluster_architecture() -> str:
@@ -21,7 +22,8 @@ def get_cluster_architecture() -> str:
2122

2223
if not arch:
2324
# TODO: merge with `get_nodes_cpu_architecture`
24-
nodes: list[Node] = list(Node.get(dyn_client=get_client()))
25+
# cache_admin_client is used here as this function is used to get the architecture when initialing pytest config
26+
nodes: list[Node] = list(Node.get(dyn_client=cache_admin_client()))
2527
nodes_cpu_arch = {node.labels[KUBERNETES_ARCH_LABEL] for node in nodes}
2628
arch = next(iter(nodes_cpu_arch))
2729

utilities/cluster.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from functools import cache
2+
3+
from kubernetes.dynamic import DynamicClient
4+
from ocp_resources.resource import get_client
5+
6+
7+
@cache
8+
def cache_admin_client() -> DynamicClient:
9+
"""Get admin_client once and reuse it
10+
11+
This usage of this function is limited ONLY in places where `client` cannot be passed as an argument.
12+
For example: in pytest native fixtures in conftest.py.
13+
14+
Returns:
15+
DynamicClient: admin_client
16+
17+
"""
18+
19+
return get_client()

utilities/pytest_matrix_utils.py

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@ def foo_matrix(matrix):
55
return matrix
66
"""
77

8-
from functools import cache
9-
10-
from kubernetes.dynamic import DynamicClient
11-
from ocp_resources.resource import get_client
128
from ocp_resources.storage_class import StorageClass
139

10+
from utilities.cluster import cache_admin_client
11+
1412

1513
def snapshot_matrix(matrix):
1614
matrix_to_return = []
@@ -51,7 +49,7 @@ def hpp_matrix(matrix):
5149
# Using `get_client` explicitly as this function is dynamically called (like other functions in the module).
5250
# The other functions do not need a client.
5351
if (
54-
StorageClass(client=_cache_admin_client(), name=[*storage_class][0]).instance.provisioner
52+
StorageClass(client=cache_admin_client(), name=[*storage_class][0]).instance.provisioner
5553
in hpp_sc_provisioners
5654
):
5755
matrix_to_return.append(storage_class)
@@ -75,17 +73,3 @@ def immediate_matrix(matrix):
7573
if storage_class[storage_class_name]["wffc"] is False:
7674
matrix_to_return.append(storage_class)
7775
return matrix_to_return
78-
79-
80-
@cache
81-
def _cache_admin_client() -> DynamicClient:
82-
"""Get admin_client once and reuse it
83-
84-
This usage of this function is limited to places where `client` cannot be passed as an argument.
85-
86-
Returns:
87-
DynamicClient: admin_client
88-
89-
"""
90-
91-
return get_client()

utilities/unittests/test_architecture.py

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,75 +3,96 @@
33
"""Unit tests for architecture module"""
44

55
import os
6-
import sys
7-
from pathlib import Path
86
from unittest.mock import MagicMock, patch
97

108
import pytest
119

12-
# Add utilities to Python path for imports
13-
sys.path.insert(0, str(Path(__file__).parent.parent))
14-
15-
from architecture import get_cluster_architecture
10+
from utilities.architecture import get_cluster_architecture
1611

1712

1813
class TestGetClusterArchitecture:
1914
"""Test cases for get_cluster_architecture function"""
2015

21-
def test_get_cluster_architecture_from_env(self):
22-
"""Test getting architecture from environment variable"""
16+
def test_get_cluster_architecture_from_env_arm64(self):
17+
"""Test getting architecture from environment variable - arm64"""
2318
with patch.dict(os.environ, {"OPENSHIFT_VIRTUALIZATION_TEST_IMAGES_ARCH": "arm64"}):
2419
result = get_cluster_architecture()
2520
assert result == "arm64"
2621

27-
@patch("architecture.Node")
28-
@patch("architecture.get_client")
29-
def test_get_cluster_architecture_from_nodes_x86_64(self, mock_get_client, mock_node_class):
22+
def test_get_cluster_architecture_from_env_x86_64(self):
23+
"""Test getting architecture from environment variable - x86_64"""
24+
with patch.dict(os.environ, {"OPENSHIFT_VIRTUALIZATION_TEST_IMAGES_ARCH": "x86_64"}):
25+
result = get_cluster_architecture()
26+
assert result == "x86_64"
27+
28+
def test_get_cluster_architecture_from_env_s390x(self):
29+
"""Test getting architecture from environment variable - s390x"""
30+
with patch.dict(os.environ, {"OPENSHIFT_VIRTUALIZATION_TEST_IMAGES_ARCH": "s390x"}):
31+
result = get_cluster_architecture()
32+
assert result == "s390x"
33+
34+
def test_get_cluster_architecture_from_env_amd64_converts_to_x86_64(self):
35+
"""Test that amd64 from env is converted to x86_64"""
36+
with patch.dict(os.environ, {"OPENSHIFT_VIRTUALIZATION_TEST_IMAGES_ARCH": "amd64"}):
37+
result = get_cluster_architecture()
38+
assert result == "x86_64"
39+
40+
@patch("utilities.architecture.cache_admin_client")
41+
@patch("utilities.architecture.Node")
42+
def test_get_cluster_architecture_from_nodes_x86_64(self, mock_node_class, mock_cache_client):
3043
"""Test getting architecture from nodes - x86_64"""
3144
# Clear env var to force reading from nodes
3245
with patch.dict(os.environ, {}, clear=True):
33-
# Mock node with x86_64 architecture
46+
os.environ.pop("OPENSHIFT_VIRTUALIZATION_TEST_IMAGES_ARCH", None)
47+
48+
# Mock node with amd64 architecture
3449
mock_node = MagicMock()
3550
mock_node.labels = {"kubernetes.io/arch": "amd64"}
3651
mock_node_class.get.return_value = [mock_node]
52+
mock_cache_client.return_value = MagicMock()
3753

3854
result = get_cluster_architecture()
3955

4056
# Should convert amd64 to x86_64
4157
assert result == "x86_64"
4258
mock_node_class.get.assert_called_once()
59+
mock_cache_client.assert_called_once()
4360

44-
@patch("architecture.Node")
45-
@patch("architecture.get_client")
46-
def test_get_cluster_architecture_from_nodes_arm64(self, mock_get_client, mock_node_class):
61+
@patch("utilities.architecture.cache_admin_client")
62+
@patch("utilities.architecture.Node")
63+
def test_get_cluster_architecture_from_nodes_arm64(self, mock_node_class, mock_cache_client):
4764
"""Test getting architecture from nodes - arm64"""
4865
with patch.dict(os.environ, {}, clear=True):
66+
os.environ.pop("OPENSHIFT_VIRTUALIZATION_TEST_IMAGES_ARCH", None)
67+
4968
# Mock node with arm64 architecture
5069
mock_node = MagicMock()
5170
mock_node.labels = {"kubernetes.io/arch": "arm64"}
5271
mock_node_class.get.return_value = [mock_node]
72+
mock_cache_client.return_value = MagicMock()
5373

5474
result = get_cluster_architecture()
5575

5676
assert result == "arm64"
5777

58-
@patch("architecture.Node")
59-
@patch("architecture.get_client")
60-
def test_get_cluster_architecture_from_nodes_s390x(self, mock_get_client, mock_node_class):
78+
@patch("utilities.architecture.cache_admin_client")
79+
@patch("utilities.architecture.Node")
80+
def test_get_cluster_architecture_from_nodes_s390x(self, mock_node_class, mock_cache_client):
6181
"""Test getting architecture from nodes - s390x"""
6282
with patch.dict(os.environ, {}, clear=True):
83+
os.environ.pop("OPENSHIFT_VIRTUALIZATION_TEST_IMAGES_ARCH", None)
84+
6385
# Mock node with s390x architecture
6486
mock_node = MagicMock()
6587
mock_node.labels = {"kubernetes.io/arch": "s390x"}
6688
mock_node_class.get.return_value = [mock_node]
89+
mock_cache_client.return_value = MagicMock()
6790

6891
result = get_cluster_architecture()
6992

7093
assert result == "s390x"
7194

72-
@patch("architecture.Node")
73-
@patch("architecture.get_client")
74-
def test_get_cluster_architecture_unsupported(self, mock_get_client, mock_node_class):
95+
def test_get_cluster_architecture_unsupported(self):
7596
"""Test unsupported architecture raises ValueError"""
7697
with (
7798
patch.dict(os.environ, {"OPENSHIFT_VIRTUALIZATION_TEST_IMAGES_ARCH": "unsupported"}),
@@ -82,18 +103,41 @@ def test_get_cluster_architecture_unsupported(self, mock_get_client, mock_node_c
82103
):
83104
get_cluster_architecture()
84105

85-
@patch("architecture.Node")
86-
@patch("architecture.get_client")
87-
def test_get_cluster_architecture_multiple_nodes(self, mock_get_client, mock_node_class):
106+
@patch("utilities.architecture.cache_admin_client")
107+
@patch("utilities.architecture.Node")
108+
def test_get_cluster_architecture_multiple_nodes(self, mock_node_class, mock_cache_client):
88109
"""Test getting architecture with multiple nodes of same arch"""
89110
with patch.dict(os.environ, {}, clear=True):
111+
os.environ.pop("OPENSHIFT_VIRTUALIZATION_TEST_IMAGES_ARCH", None)
112+
90113
# Mock multiple nodes with same architecture
91114
mock_node1 = MagicMock()
92115
mock_node1.labels = {"kubernetes.io/arch": "amd64"}
93116
mock_node2 = MagicMock()
94117
mock_node2.labels = {"kubernetes.io/arch": "amd64"}
95118
mock_node_class.get.return_value = [mock_node1, mock_node2]
119+
mock_cache_client.return_value = MagicMock()
96120

97121
result = get_cluster_architecture()
98122

99123
assert result == "x86_64"
124+
125+
@patch("utilities.architecture.cache_admin_client")
126+
@patch("utilities.architecture.Node")
127+
def test_get_cluster_architecture_uses_cache_admin_client(self, mock_node_class, mock_cache_client):
128+
"""Test that cache_admin_client is used when getting nodes"""
129+
with patch.dict(os.environ, {}, clear=True):
130+
os.environ.pop("OPENSHIFT_VIRTUALIZATION_TEST_IMAGES_ARCH", None)
131+
132+
mock_client = MagicMock()
133+
mock_cache_client.return_value = mock_client
134+
135+
mock_node = MagicMock()
136+
mock_node.labels = {"kubernetes.io/arch": "amd64"}
137+
mock_node_class.get.return_value = [mock_node]
138+
139+
get_cluster_architecture()
140+
141+
# Verify cache_admin_client was called and passed to Node.get
142+
mock_cache_client.assert_called_once()
143+
mock_node_class.get.assert_called_once_with(dyn_client=mock_client)

utilities/unittests/test_pytest_matrix_utils.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def test_online_resize_matrix_empty_matrix(self):
120120
class TestHppMatrix:
121121
"""Test cases for hpp_matrix function"""
122122

123-
@patch("utilities.pytest_matrix_utils._cache_admin_client")
123+
@patch("utilities.infra.cache_admin_client")
124124
@patch("utilities.pytest_matrix_utils.StorageClass")
125125
def test_hpp_matrix_with_hpp_provisioner(self, mock_storage_class, mock_cache_admin_client):
126126
"""Test hpp_matrix filters storage classes with HPP provisioner"""
@@ -156,9 +156,7 @@ def storage_class_side_effect(client, name):
156156
assert len(result) == 1
157157
assert {"hpp-sc": {"other": "value"}} in result
158158

159-
@patch("utilities.pytest_matrix_utils._cache_admin_client")
160-
@patch("utilities.pytest_matrix_utils.StorageClass")
161-
def test_hpp_matrix_empty_matrix(self, mock_storage_class, mock_cache_admin_client):
159+
def test_hpp_matrix_empty_matrix(self):
162160
"""Test hpp_matrix with empty matrix"""
163161
matrix = []
164162

0 commit comments

Comments
 (0)