Skip to content

Commit 40d7093

Browse files
authored
Merge branch 'RedHatQE:main' into virt-operator-ready
2 parents 637e2c4 + 9dd4074 commit 40d7093

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1012
-611
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ repos:
1313
stages: [pre-commit]
1414

1515
- repo: https://github.com/astral-sh/ruff-pre-commit
16-
rev: v0.14.2
16+
rev: v0.14.3
1717
hooks:
1818
- id: ruff
1919
stages: [pre-commit]

libs/infra/images.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class Windows:
7272
ISO_WIN11_DIR: str = f"{ISO_BASE_DIR}/win11"
7373
ISO_WIN2022_DIR: str = f"{ISO_BASE_DIR}/win2022"
7474
ISO_WIN2025_DIR: str = f"{ISO_BASE_DIR}/win2025"
75+
CONTAINER_DISK_DV_SIZE = "40Gi"
7576
DEFAULT_DV_SIZE: str = "70Gi"
7677
DEFAULT_MEMORY_SIZE: str = "8Gi"
7778
DEFAULT_MEMORY_SIZE_WSL: str = "12Gi"

libs/net/traffic_generator.py

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from ocp_resources.pod import Pod
66
from ocp_utilities.exceptions import CommandExecFailed
7-
from timeout_sampler import TimeoutExpiredError, TimeoutSampler, retry
7+
from timeout_sampler import retry
88

99
from libs.vm.vm import BaseVirtualMachine
1010

@@ -15,7 +15,7 @@
1515
LOGGER = logging.getLogger(__name__)
1616

1717

18-
class BaseClient(ABC):
18+
class BaseTcpClient(ABC):
1919
"""Base abstract class for network traffic generator client."""
2020

2121
def __init__(self, server_ip: str, server_port: int):
@@ -24,7 +24,7 @@ def __init__(self, server_ip: str, server_port: int):
2424
self._cmd = f"{_IPERF_BIN} --client {self._server_ip} --time 0 --port {self.server_port} --connect-timeout 300"
2525

2626
@abstractmethod
27-
def __enter__(self) -> "BaseClient":
27+
def __enter__(self) -> "BaseTcpClient":
2828
pass
2929

3030
@abstractmethod
@@ -36,7 +36,7 @@ def is_running(self) -> bool:
3636
pass
3737

3838

39-
class Server:
39+
class TcpServer:
4040
"""
4141
Represents a server running on a virtual machine for testing network performance.
4242
Implemented with iperf3
@@ -55,11 +55,13 @@ def __init__(
5555
self._port = port
5656
self._cmd = f"{_IPERF_BIN} --server --port {self._port} --one-off"
5757

58-
def __enter__(self) -> "Server":
58+
def __enter__(self) -> "TcpServer":
5959
self._vm.console(
6060
commands=[f"{self._cmd} &"],
6161
timeout=_DEFAULT_CMD_TIMEOUT_SEC,
6262
)
63+
self._ensure_is_running()
64+
6365
return self
6466

6567
def __exit__(self, exc_type: BaseException, exc_value: BaseException, traceback: object) -> None:
@@ -72,10 +74,13 @@ def vm(self) -> BaseVirtualMachine:
7274
def is_running(self) -> bool:
7375
return _is_process_running(vm=self._vm, cmd=self._cmd)
7476

77+
@retry(wait_timeout=30, sleep=2, exceptions_dict={})
78+
def _ensure_is_running(self) -> bool:
79+
return self.is_running()
80+
7581

76-
class Client(BaseClient):
77-
"""
78-
Represents a client that connects to a server to test network performance.
82+
class VMTcpClient(BaseTcpClient):
83+
"""Represents a TCP client that connects to a server to test network performance.
7984
Implemented with iperf3
8085
8186
Args:
@@ -93,11 +98,13 @@ def __init__(
9398
super().__init__(server_ip=server_ip, server_port=server_port)
9499
self._vm = vm
95100

96-
def __enter__(self) -> "Client":
101+
def __enter__(self) -> "VMTcpClient":
97102
self._vm.console(
98103
commands=[f"{self._cmd} &"],
99104
timeout=_DEFAULT_CMD_TIMEOUT_SEC,
100105
)
106+
self._ensure_is_running()
107+
101108
return self
102109

103110
def __exit__(self, exc_type: BaseException, exc_value: BaseException, traceback: object) -> None:
@@ -110,6 +117,10 @@ def vm(self) -> BaseVirtualMachine:
110117
def is_running(self) -> bool:
111118
return _is_process_running(vm=self._vm, cmd=self._cmd)
112119

120+
@retry(wait_timeout=30, sleep=2, exceptions_dict={})
121+
def _ensure_is_running(self) -> bool:
122+
return self.is_running()
123+
113124

114125
def _stop_process(vm: BaseVirtualMachine, cmd: str) -> None:
115126
try:
@@ -118,25 +129,18 @@ def _stop_process(vm: BaseVirtualMachine, cmd: str) -> None:
118129
LOGGER.warning(str(e))
119130

120131

121-
def _is_process_running( # type: ignore[return]
122-
vm: BaseVirtualMachine, cmd: str
123-
) -> bool:
132+
def _is_process_running(vm: BaseVirtualMachine, cmd: str) -> bool:
124133
try:
125-
for sample in TimeoutSampler(
126-
wait_timeout=60,
127-
sleep=5,
128-
func=vm.console,
134+
vm.console(
129135
commands=[f"pgrep -fx '{cmd}'"],
130136
timeout=_DEFAULT_CMD_TIMEOUT_SEC,
131-
):
132-
if sample:
133-
return True
134-
except TimeoutExpiredError as e:
135-
LOGGER.warning(f"Process is not running on VM {vm.name}. Error: {str(e.last_exp)}")
137+
)
138+
return True
139+
except CommandExecFailed:
136140
return False
137141

138142

139-
class PodClient(BaseClient):
143+
class PodTcpClient(BaseTcpClient):
140144
"""Represents a TCP client that connects to a server to test network performance.
141145
142146
Expects pod to have iperf3 container.
@@ -155,7 +159,7 @@ def __init__(self, pod: Pod, server_ip: str, server_port: int, bind_interface: s
155159
self._container = _IPERF_BIN
156160
self._cmd += f" --bind {bind_interface}" if bind_interface else ""
157161

158-
def __enter__(self) -> "PodClient":
162+
def __enter__(self) -> "PodTcpClient":
159163
# run the command in the background using nohup to ensure it keeps running after the exec session ends
160164
self._pod.execute(
161165
command=["sh", "-c", f"nohup {self._cmd} >/tmp/{_IPERF_BIN}.log 2>&1 &"], container=self._container
@@ -178,5 +182,5 @@ def _ensure_is_running(self) -> bool:
178182
return self.is_running()
179183

180184

181-
def is_tcp_connection(server: Server, client: Client | PodClient) -> bool:
185+
def is_tcp_connection(server: TcpServer, client: BaseTcpClient) -> bool:
182186
return server.is_running() and client.is_running()

libs/vm/factory.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
from kubernetes.dynamic import DynamicClient
4+
35
from libs.vm.spec import CPU, Devices, Domain, Memory, Metadata, Template, VMISpec, VMSpec
46
from libs.vm.vm import BaseVirtualMachine, container_image, containerdisk_storage
57
from utilities.constants import OS_FLAVOR_FEDORA, Images
@@ -8,6 +10,7 @@
810
def fedora_vm(
911
namespace: str,
1012
name: str,
13+
client: DynamicClient,
1114
spec: VMSpec | None = None,
1215
vm_labels: dict[str, str] | None = None,
1316
vm_annotations: dict[str, str] | None = None,
@@ -21,6 +24,7 @@ def fedora_vm(
2124
vm_labels=vm_labels,
2225
vm_annotations=vm_annotations,
2326
os_distribution=OS_FLAVOR_FEDORA,
27+
client=client,
2428
)
2529

2630

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ strict_concatenate = true
3030

3131

3232
[project]
33-
name = "openshift-virtualization-tests-4.20"
33+
name = "openshift-virtualization-tests-4.21"
3434
requires-python = "==3.12.*"
35-
version = "4.20"
35+
version = "4.21"
3636
description = "Tests for Openshift Virtualization"
3737
authors = [{ "name" = "openshift-virtualization-tests" }]
3838
dependencies = [

tests/conftest.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,16 @@ def rhel9_data_source_scope_session(golden_images_namespace):
912912
)
913913

914914

915+
@pytest.fixture(scope="session")
916+
def rhel10_data_source_scope_session(golden_images_namespace):
917+
return DataSource(
918+
namespace=golden_images_namespace.name,
919+
name="rhel10",
920+
client=golden_images_namespace.client,
921+
ensure_exists=True,
922+
)
923+
924+
915925
"""
916926
VM creation from template
917927
"""
@@ -1140,11 +1150,6 @@ def skip_access_mode_rwo_scope_class(storage_class_matrix__class__):
11401150
_skip_access_mode_rwo(storage_class_matrix=storage_class_matrix__class__)
11411151

11421152

1143-
@pytest.fixture(scope="module")
1144-
def skip_access_mode_rwo_scope_module(storage_class_matrix__module__):
1145-
_skip_access_mode_rwo(storage_class_matrix=storage_class_matrix__module__)
1146-
1147-
11481153
@pytest.fixture(scope="session")
11491154
def nodes_cpu_vendor(schedulable_nodes):
11501155
if schedulable_nodes[0].labels.get(f"cpu-vendor.node.kubevirt.io/{AMD}"):

tests/infrastructure/conftest.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import bitmath
2+
import pytest
3+
4+
5+
@pytest.fixture(scope="session")
6+
def hugepages_gib_values(workers):
7+
"""Return the list of hugepage sizes (in GiB) across all worker nodes."""
8+
return [
9+
int(bitmath.parse_string_unsafe(value).GiB)
10+
for worker in workers
11+
if (value := worker.instance.status.allocatable.get("hugepages-1Gi"))
12+
]
13+
14+
15+
@pytest.fixture(scope="session")
16+
def xfail_if_no_huge_pages(hugepages_gib_values):
17+
"""Mark tests as xfail if the cluster lacks 1Gi hugepages."""
18+
if not hugepages_gib_values or max(hugepages_gib_values) < 1:
19+
pytest.xfail("Requires at least 1Gi hugepages on some node")
20+
21+
22+
@pytest.fixture(scope="session")
23+
@pytest.mark.usefixtures("xfail_if_no_huge_pages")
24+
def hugepages_gib_max(hugepages_gib_values):
25+
"""Return the maximum 1Gi hugepage size, capped at 64Gi."""
26+
return min(max(hugepages_gib_values), 64)

tests/infrastructure/golden_images/conftest.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
import os
2-
import re
3-
from pathlib import Path
1+
import logging
42

53
import pytest
4+
import requests
65
from ocp_resources.resource import ResourceEditor
76
from ocp_resources.storage_class import StorageClass
87

8+
from utilities.constants import TIMEOUT_30SEC
9+
10+
LOGGER = logging.getLogger(__name__)
11+
912

1013
@pytest.fixture()
1114
def updated_default_storage_class_scope_function(
@@ -29,14 +32,13 @@ def updated_default_storage_class_scope_function(
2932

3033

3134
@pytest.fixture(scope="module")
32-
def latest_fedora_release_version(downloaded_latest_libosinfo_db):
33-
"""
34-
Extract the version from file name, if no files found raise KeyError
35-
file example: /tmp/pytest-6axFnW3vzouCkjWokhvbDi/osinfodb0/osinfo-db-20221121/os/fedoraproject.org/fedora-42.xml
36-
"""
37-
osinfo_file_folder_path = os.path.join(downloaded_latest_libosinfo_db, "os/fedoraproject.org/")
38-
list_of_fedora_os_files = list(sorted(Path(osinfo_file_folder_path).glob("*fedora-[0-9][0-9]*.xml")))
39-
if not list_of_fedora_os_files:
40-
raise FileNotFoundError("No fedora files were found in osinfo db")
41-
latest_fedora_os_file = list_of_fedora_os_files[-1]
42-
return re.findall(r"\d+", latest_fedora_os_file.name)[0]
35+
def latest_fedora_release_version():
36+
response = requests.get(url="https://fedoraproject.org/releases.json", verify=False, timeout=TIMEOUT_30SEC)
37+
response.raise_for_status()
38+
response_json = response.json()
39+
versions = {int(item["version"]) for item in response_json if item.get("version", "").isdigit()}
40+
if not versions:
41+
raise ValueError(f"No Fedora versions found in release json: {response_json}")
42+
latest_fedora_version = str(max(versions))
43+
LOGGER.info(f"Latest Fedora release: {latest_fedora_version}")
44+
return latest_fedora_version

tests/infrastructure/golden_images/update_boot_source/test_ssp_common_templates_boot_sources.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
assert_missing_golden_image_pvc,
1212
assert_os_version_mismatch_in_vm,
1313
)
14-
from utilities.constants import RHEL9_STR, TIMEOUT_5MIN, TIMEOUT_5SEC, Images
14+
from utilities.constants import OS_FLAVOR_FEDORA, RHEL9_STR, TIMEOUT_5MIN, TIMEOUT_5SEC, Images
1515
from utilities.infra import (
1616
cleanup_artifactory_secret_and_config_map,
1717
get_artifactory_config_map,
@@ -137,7 +137,7 @@ def test_vm_from_auto_update_boot_source(
137137
latest_fedora_release_version,
138138
):
139139
LOGGER.info(f"Verify {auto_update_boot_source_vm.name} OS version and virtctl info")
140-
if "fedora" in boot_source_os_from_data_source_dict and latest_fedora_release_version:
140+
if OS_FLAVOR_FEDORA in boot_source_os_from_data_source_dict and latest_fedora_release_version:
141141
boot_source_os_from_data_source_dict = f"fedora{latest_fedora_release_version}"
142142
assert_os_version_mismatch_in_vm(
143143
vm=auto_update_boot_source_vm,

tests/infrastructure/instance_types/supported_os/conftest.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,24 @@
11
import pytest
2+
from ocp_resources.datavolume import DataVolume
3+
from ocp_resources.virtual_machine_cluster_instancetype import VirtualMachineClusterInstancetype
4+
from ocp_resources.virtual_machine_cluster_preference import VirtualMachineClusterPreference
5+
from pytest_testconfig import config as py_config
26

37
from tests.infrastructure.instance_types.supported_os.utils import golden_image_vm_with_instance_type
4-
from utilities.constants import DATA_SOURCE_NAME, RHEL8_PREFERENCE
8+
from utilities.constants import (
9+
CONTAINER_DISK_IMAGE_PATH_STR,
10+
DATA_SOURCE_NAME,
11+
DATA_SOURCE_STR,
12+
RHEL8_PREFERENCE,
13+
Images,
14+
)
15+
from utilities.infra import (
16+
cleanup_artifactory_secret_and_config_map,
17+
get_artifactory_config_map,
18+
get_artifactory_secret,
19+
)
20+
from utilities.storage import get_test_artifact_server_url
21+
from utilities.virt import VirtualMachineForTests
522

623

724
@pytest.fixture(scope="class")
@@ -68,3 +85,52 @@ def golden_image_fedora_vm_with_instance_type(
6885
storage_class_name=[*storage_class_matrix__module__][0],
6986
data_source_name=instance_type_fedora_os_matrix__module__[os_name][DATA_SOURCE_NAME],
7087
)
88+
89+
90+
@pytest.fixture(scope="module")
91+
def windows_data_volume_template(
92+
namespace,
93+
windows_os_matrix__module__,
94+
):
95+
os_matrix_key = [*windows_os_matrix__module__][0]
96+
os_params = windows_os_matrix__module__[os_matrix_key]
97+
secret = get_artifactory_secret(namespace=namespace.name)
98+
cert = get_artifactory_config_map(namespace=namespace.name)
99+
win_dv = DataVolume(
100+
name=f"{os_matrix_key}-dv",
101+
namespace=namespace.name,
102+
api_name="storage",
103+
source="registry",
104+
size=Images.Windows.CONTAINER_DISK_DV_SIZE,
105+
storage_class=py_config["default_storage_class"],
106+
url=f"{get_test_artifact_server_url(schema='registry')}/{os_params[CONTAINER_DISK_IMAGE_PATH_STR]}",
107+
secret=secret,
108+
cert_configmap=cert.name,
109+
)
110+
win_dv.to_dict()
111+
yield win_dv
112+
cleanup_artifactory_secret_and_config_map(artifactory_secret=secret, artifactory_config_map=cert)
113+
114+
115+
@pytest.fixture(scope="class")
116+
def golden_image_windows_vm(
117+
unprivileged_client,
118+
namespace,
119+
modern_cpu_for_migration,
120+
windows_data_volume_template,
121+
windows_os_matrix__module__,
122+
):
123+
os_name = [*windows_os_matrix__module__][0]
124+
return VirtualMachineForTests(
125+
client=unprivileged_client,
126+
name=f"{os_name}-vm-with-instance-type-2",
127+
namespace=namespace.name,
128+
vm_instance_type=VirtualMachineClusterInstancetype(name="u1.large"),
129+
vm_preference=VirtualMachineClusterPreference(
130+
name=windows_os_matrix__module__[os_name][DATA_SOURCE_STR].replace("win", "windows.")
131+
),
132+
data_volume_template=windows_data_volume_template.res,
133+
os_flavor="win-container-disk",
134+
disk_type=None,
135+
cpu_model=modern_cpu_for_migration,
136+
)

0 commit comments

Comments
 (0)