Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 10 additions & 25 deletions testsuite/component_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ def __init__(self):

def collect_all_clusters(self):
"""Collect metadata from all configured clusters."""
clusters_config = self._get_cluster_configurations()
clusters_config = self.get_cluster_configurations()
for cluster_name, cluster_client in clusters_config:
metadata = self._collect_single_cluster(cluster_client)
if metadata:
self.all_cluster_metadata[cluster_name] = metadata

def _get_cluster_configurations(self):
@staticmethod
def get_cluster_configurations():
"""Get cluster configurations from settings."""
clusters_config = [("cluster1", settings["control_plane"]["cluster"])]
if cluster2 := settings["control_plane"].get("cluster2"):
Expand All @@ -38,14 +39,16 @@ def _get_cluster_configurations(self):

def _collect_single_cluster(self, cluster_client):
"""Collect metadata for a single cluster."""
project = cluster_client.change_project(settings["service_protection"]["system_project"])
if not project.connected:
if not cluster_client.is_reachable:
return None

project = cluster_client.change_project(settings["service_protection"]["system_project"])
metadata = self._get_kuadrant_metadata(project) if project.connected else {"kuadrant_image": "not installed"}

return {
"metadata": self._get_kuadrant_metadata(project),
"metadata": metadata,
"console_url": self._get_console_url(cluster_client.api_url),
"ocp_version": self.get_ocp_version(project),
"ocp_version": cluster_client.ocp_version,
}

@staticmethod
Expand Down Expand Up @@ -78,23 +81,6 @@ def _get_console_url(api_url):
return f"https://{console_hostname}"
return api_url

@staticmethod
def get_ocp_version(project) -> Optional[str]:
"""Retrieve and format OCP version from cluster."""
try:
with project.context:
version_result = oc.selector("clusterversion").objects()
if version_result:
ocp_version = version_result[0].model.status.history[0].version
if ocp_version:
parts = ocp_version.split(".")
if len(parts) >= 2:
return f"{parts[0]}.{parts[1]}"
except (oc.OpenShiftPythonException, AttributeError, KeyError, IndexError, ValueError) as e:
logger.warning("Failed to get OCP version: %s", e)

return None

@staticmethod
def get_kubernetes_version(project) -> Optional[str]:
"""Run oc version and get the kubernetes version."""
Expand Down Expand Up @@ -130,13 +116,12 @@ def get_component_images(project) -> list[tuple]:
if normalised_image in seen:
continue
seen.add(normalised_image)
print(f"{image=}")
image_name = normalised_image.split("/")[-1]
if ":" in image_name:
name, tag = image_name.rsplit(":", 1)
images.append((name, tag, image))
else:
logger.debug("Skipping image without tag: %s", image)
images.append((image_name, None, image))
except (oc.OpenShiftPythonException, AttributeError, KeyError, IndexError, ValueError) as e:
logger.warning("Failed to get images from %s: %s", project, e)

Expand Down
28 changes: 28 additions & 0 deletions testsuite/kubernetes/client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""This module implements an KubernetesCLI interface using oc/kubectl binary commands."""

import logging
from functools import cached_property
from typing import Optional
from urllib.parse import urlparse
import tempfile
import yaml
Expand All @@ -14,6 +16,8 @@
from .deployment import Deployment
from .secret import Secret

logger = logging.getLogger(__name__)


class KubernetesClient:
"""KubernetesClient is a helper class for invoking kubectl commands"""
Expand Down Expand Up @@ -100,6 +104,30 @@ def connected(self):
return False
return True

@property
def is_reachable(self):
"""Returns True if the cluster is reachable, without depending on any specific namespace."""
try:
self.do_action("api-versions")
except OpenShiftPythonException as e:
logger.warning("Cluster is not reachable: %s", e)
return False
return True

@property
def ocp_version(self) -> Optional[str]:
"""Returns the OpenShift version (major.minor) or None if not available."""
result = self.do_action(
"get", "clusterversion", "version", "-o", "jsonpath={.status.history[0].version}", auto_raise=False
)
if result.status() != 0:
return None
version_str = result.out().strip()
parts = version_str.split(".")
if len(parts) >= 2:
return f"{parts[0]}.{parts[1]}"
return None

def get_secret(self, name):
"""Returns dict-like structure for accessing secret data"""
with self.context:
Expand Down
18 changes: 10 additions & 8 deletions testsuite/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ def pytest_runtest_setup(item):
# error is raised during has_kuadrant()
if item.config.getoption("--setup-plan"):
return

if item.fspath.basename == "info_collector.py":
return

marks = [i.name for i in item.iter_markers()]
skip_or_fail = pytest.fail if item.config.getoption("--enforce") else pytest.skip
standalone = item.config.getoption("--standalone")
Expand Down Expand Up @@ -422,16 +426,14 @@ def dns_provider_secret(testconfig):


@pytest.fixture(scope="session")
def openshift_version(cluster):
def openshift_version(testconfig):
"""Get OpenShift cluster version"""
result = cluster.do_action(
"get", "clusterversion", "version", "-o", "jsonpath={.status.desired.version}", auto_raise=False
)
if result.status() != 0:
cluster = testconfig["control_plane"]["cluster"]
version = cluster.ocp_version
if version is None:
return None
Comment thread
coderabbitai[bot] marked this conversation as resolved.
version_str = result.out().strip()
parts = version_str.split(".")
return tuple(int(p.split("-")[0]) for p in parts[:2]) # Convert "4.20.0" -> (4, 20)
parts = version.split(".")
return tuple(int(p.split("-")[0]) for p in parts[:2]) # Convert "4.20" -> (4, 20)


@pytest.fixture(autouse=True)
Expand Down
116 changes: 78 additions & 38 deletions testsuite/tests/info_collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,31 @@
)


def gather_cluster_versions() -> dict:
"""gather all particular versions into a dictionary"""
cluster_client = settings["control_plane"]["cluster"]
project = cluster_client.change_project(settings["service_protection"]["system_project"])
return {
"kubernetes": ReportPortalMetadataCollector.get_kubernetes_version(project),
"openshift": ReportPortalMetadataCollector.get_ocp_version(project),
}
def _all_cluster_projects(namespace):
"""Yield (cluster_name, cluster_client, project or None) for each configured cluster."""
for cluster_name, cluster in ReportPortalMetadataCollector.get_cluster_configurations():
project = cluster.change_project(namespace)
yield cluster_name, cluster, (project if project.connected else None)


def _print_cluster_data(cluster_data):
"""Print collected data per cluster."""
for cluster_name, lines in cluster_data.items():
print(f"\n{cluster_name}:")
for line in lines:
print(f" {line}")


def _record_unique(record_testsuite_property, properties):
"""Record properties, only adding unique ones as attributes."""
seen = set()
for key, value in properties:
if not value:
continue
if (key, value) not in seen:
seen.add((key, value))
record_testsuite_property(key, value)
logger.info("recording property %s:%s", key, value)


def get_cluster_information() -> dict:
Expand All @@ -70,39 +87,62 @@ def test_launch_description(record_testsuite_property):


def test_cluster_properties(record_testsuite_property):
"""Collect cluster properties"""
cluster_versions = gather_cluster_versions()
for k, v in cluster_versions.items():
print(f"recording property {k}:{v}")
if v: # filter out None values
record_testsuite_property(k, v)


def test_kube_context(record_testsuite_property):
"""Record current kube context"""
kube_context = settings["control_plane"]["cluster"].kubeconfig_path
print(f"{kube_context=}")
if kube_context:
record_testsuite_property("kube_context", kube_context)
"""Collect cluster version properties from all clusters."""
system_ns = settings["service_protection"]["system_project"]
properties = []
cluster_data = {}
for cluster_name, cluster, project in _all_cluster_projects(system_ns):
if project is None:
cluster_data[cluster_name] = [f"namespace '{system_ns}' not found"]
continue
versions = {
"kubernetes": ReportPortalMetadataCollector.get_kubernetes_version(project),
"openshift": cluster.ocp_version,
}
cluster_data[cluster_name] = [f"{k}:{v}" for k, v in versions.items()]
for key, value in versions.items():
properties.append((key, value))

_print_cluster_data(cluster_data)
_record_unique(record_testsuite_property, properties)
Comment thread
coderabbitai[bot] marked this conversation as resolved.


def test_kuadrant_properties(record_testsuite_property):
"""Record kuadrant related properties"""
cluster_client = settings["control_plane"]["cluster"]
project = cluster_client.change_project("kuadrant-system")
kuadrant_images = ReportPortalMetadataCollector.get_component_images(project)
if kuadrant_images:
print(f"Kuadrant images: {kuadrant_images}")
for name, tag, _ in kuadrant_images:
record_testsuite_property(name, tag)
"""Record kuadrant related properties from all clusters."""
system_ns = settings["service_protection"]["system_project"]
properties = []
cluster_data = {}
for cluster_name, _, project in _all_cluster_projects(system_ns):
if project is None:
cluster_data[cluster_name] = [f"namespace '{system_ns}' not found"]
continue
cluster_data[cluster_name] = []
kuadrant_images = ReportPortalMetadataCollector.get_component_images(project)
for name, tag, full_image in kuadrant_images:
if tag:
cluster_data[cluster_name].append(f"{name}:{tag} ({full_image})")
properties.append((name, tag))
else:
cluster_data[cluster_name].append(full_image)

_print_cluster_data(cluster_data)
_record_unique(record_testsuite_property, properties)


def test_istio_properties(record_testsuite_property):
"""Record Istio related properties"""
cluster_client = settings["control_plane"]["cluster"]
project = cluster_client.change_project("istio-system")
istio_metadata = ReportPortalMetadataCollector.get_istio_metadata(project)
for key, value in istio_metadata.items():
print(f"{key}: {value}")
if key == "istio_version":
record_testsuite_property(key, value)
"""Record Istio related properties from all clusters."""
properties = []
cluster_data = {}
for cluster_name, _, project in _all_cluster_projects("istio-system"):
if project is None:
cluster_data[cluster_name] = ["namespace 'istio-system' not found"]
continue
cluster_data[cluster_name] = []
istio_metadata = ReportPortalMetadataCollector.get_istio_metadata(project)
for key, value in istio_metadata.items():
cluster_data[cluster_name].append(f"{key}:{value}")
if key == "istio_version":
properties.append((key, value))

_print_cluster_data(cluster_data)
_record_unique(record_testsuite_property, properties)
Loading