Skip to content

Commit

Permalink
Merge branch 'master' into feature-e2e-regression
Browse files Browse the repository at this point in the history
  • Loading branch information
chaladak authored Jul 10, 2024
2 parents 17f3457 + 50f4ab7 commit 317fc93
Show file tree
Hide file tree
Showing 6 changed files with 841 additions and 65 deletions.
48 changes: 48 additions & 0 deletions tests/e2e-test-framework/framework/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# common
CR_GROUP = "csi-baremetal.dell.com"
CR_VERSION = "v1"

# storage classes
HDD_SC = "csi-baremetal-sc-hdd"
SSD_SC = "csi-baremetal-sc-ssd"
HDDLVG_SC = "csi-baremetal-sc-hddlvg"
SSDLVG_SC = "csi-baremetal-sc-ssdlvg"
SYSLVG_SC = "csi-baremetal-sc-syslvg"
NVME_SC = "csi-baremetal-sc-nvme-raw-part"
NVMELVG_SC = "csi-baremetal-sc-nvmelvg"

# storage types
STORAGE_TYPE_SSD = "SSD"
STORAGE_TYPE_HDD = "HDD"
STORAGE_TYPE_NVME = "NVME"
STORAGE_TYPE_HDDLVG = "HDDLVG"
STORAGE_TYPE_SSDLVG = "SSDLVG"
STORAGE_TYPE_SYSLVG = "SYSLVG"
STORAGE_TYPE_NVMELVG = "NVMELVG"

# usages
USAGE_IN_USE = "IN_USE"
USAGE_RELEASING = "RELEASING"
USAGE_RELEASED = "RELEASED"
USAGE_REMOVING = "REMOVING"
USAGE_REMOVED = "REMOVED"
USAGE_RAILED = "FAILED"

# statuses
STATUS_ONLINE = "ONLINE"
STATUS_OFFLINE = "OFFLINE"

# health
HEALTH_GOOD = "GOOD"
HEALTH_BAD = "BAD"

# fake attach
FAKE_ATTACH_INVOLVED = "FakeAttachInvolved"
FAKE_ATTACH_CLEARED = "FakeAttachCleared"

# plurals
DRIVES_PLURAL = "drives"
AC_PLURAL = "availablecapacities"
ACR_PLURAL = "availablecapacityreservations"
LVG_PLURAL = "logicalvolumegroups"
VOLUMES_PLURAL = "volumes"
72 changes: 72 additions & 0 deletions tests/e2e-test-framework/framework/drive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import logging
from typing import Any, List
from framework.ssh import SSHCommandExecutor


class DriveUtils:
def __init__(self, executor: SSHCommandExecutor) -> None:
self.executor = executor

def get_scsi_id(self, device_name: str) -> str:
"""
Retrieves the SCSI ID for the given device name.
Args:
device_name (str): The name of the device. It can be either a path
(starting with "/dev/") or a device name.
Returns:
str: The SCSI ID of the device.
"""
device_name = self._get_device_name(device_path_or_name=device_name)
scsi_id, errors = self.executor.exec(
f"sudo ls /sys/block/{device_name}/device/scsi_device/")
self._handle_errors(errors)
return scsi_id

def remove(self, scsi_id: str) -> None:
"""removes a device from the system using the SCSI ID or device name"""
logging.info(f"removing drive SCSI ID: {scsi_id}")
_, errors = self.executor.exec(
f"echo 1 | sudo tee -a /sys/class/scsi_device/{scsi_id}/device/delete")
self._handle_errors(errors)

def restore(self, host_num: int) -> None:
"""restores the drive for a specified host number"""
logging.info(f"restoring drive for host: {host_num}")
_, errors = self.executor.exec(
f"echo '- - -' | sudo tee -a /sys/class/scsi_host/host{host_num}/scan")
self._handle_errors(errors)

def get_host_num(self, drive_path_or_name: str) -> int:
"""
Retrieves the host number associated with the specified drive path or name.
Args:
drive_path_or_name (str): The path or name of the drive. It can be either a path starting with "/dev/" or a device name.
Returns:
int: The host number associated with the drive.
"""
disk = self._get_device_name(drive_path_or_name)
logging.info(f"getting host number for disk: {disk}")
lsblk_output, errors = self.executor.exec("lsblk -S")
lsblk_output = lsblk_output.split('\n')
self._handle_errors(errors)

entry = [e for e in lsblk_output if e.find(disk) >= 0]
logging.debug(f"lsblk output for {disk}:\n{entry}")
assert len(entry) == 1, f"Found {len(entry)} drives for requested disk {disk}"
while entry[0].find(' ') >= 0:
entry[0] = entry[0].replace(' ', ' ')

logging.debug(f"final lsblk string: {entry[0]}")
return entry[0].split(' ')[1].split(':')[0]

def _get_device_name(self, device_path_or_name: str) -> str:
return device_path_or_name[5:] if device_path_or_name.startswith("/dev/") else device_path_or_name

def _handle_errors(self, errors: List[Any] | None) -> None:
assert errors is None or len(
errors) == 0, f"remote execution failed: {errors}"
2 changes: 2 additions & 0 deletions tests/e2e-test-framework/framework/ssh.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Any, List, Tuple
import logging
import paramiko


Expand Down
114 changes: 61 additions & 53 deletions tests/e2e-test-framework/framework/sts.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import os
import logging
import time
from typing import List
import pytest
from kubernetes import client, config
from kubernetes.client.rest import ApiException
from typing import List


class STS:
def __init__(self, namespace: str, name: str, replicas: int = 1) -> None:
Expand All @@ -13,7 +14,10 @@ def __init__(self, namespace: str, name: str, replicas: int = 1) -> None:
self.replicas = replicas
self.image = "asdrepo.isus.emc.com:9042/alpine:3.18.4"

if 'KUBERNETES_SERVICE_HOST' in os.environ and 'KUBERNETES_SERVICE_PORT' in os.environ:
if (
"KUBERNETES_SERVICE_HOST" in os.environ
and "KUBERNETES_SERVICE_PORT" in os.environ
):
config.load_incluster_config()
else:
config.load_kube_config()
Expand All @@ -30,35 +34,29 @@ def create(self, storage_classes: List[str]) -> None:
volume_claim_templates = []
for index, storage_class in enumerate(storage_classes):
volume_mount = client.V1VolumeMount(
name="volume"+str(index),
mount_path="/mnt/volume"+str(index))
name="volume" + str(index),
mount_path="/mnt/volume" + str(index),
)
volume_mounts.append(volume_mount)
volume_claim_template = client.V1PersistentVolumeClaim(
api_version="v1",
kind="PersistentVolumeClaim",
metadata=client.V1ObjectMeta(
name="volume"+str(index)
),
spec=client.V1PersistentVolumeClaimSpec(
access_modes=[
"ReadWriteOnce"
],
storage_class_name=storage_class,
resources=client.V1VolumeResourceRequirements(
requests={
"storage": "50Mi"
}
)
)
)
api_version="v1",
kind="PersistentVolumeClaim",
metadata=client.V1ObjectMeta(name="volume" + str(index)),
spec=client.V1PersistentVolumeClaimSpec(
access_modes=["ReadWriteOnce"],
storage_class_name=storage_class,
resources=client.V1VolumeResourceRequirements(
requests={"storage": "50Mi"}
),
),
)
volume_claim_templates.append(volume_claim_template)

body = client.V1StatefulSet(
api_version="apps/v1",
kind="StatefulSet",
metadata=client.V1ObjectMeta(
name=self.name,
namespace=self.namespace
name=self.name, namespace=self.namespace
),
spec=client.V1StatefulSetSpec(
replicas=self.replicas,
Expand All @@ -67,16 +65,10 @@ def create(self, storage_classes: List[str]) -> None:
),
service_name=self.name,
selector=client.V1LabelSelector(
match_labels={
"app": self.name
}
match_labels={"app": self.name}
),
template=client.V1PodTemplateSpec(
metadata=client.V1ObjectMeta(
labels={
"app": self.name
}
),
metadata=client.V1ObjectMeta(labels={"app": self.name}),
spec=client.V1PodSpec(
termination_grace_period_seconds=1,
containers=[
Expand All @@ -88,53 +80,69 @@ def create(self, storage_classes: List[str]) -> None:
"sleep",
"infinity",
],
volume_mounts=volume_mounts
volume_mounts=volume_mounts,
)
]
)
],
),
),
volume_claim_templates=volume_claim_templates
)
volume_claim_templates=volume_claim_templates,
),
)

try:
response = self.apps_v1_api.create_namespaced_stateful_set(
self.namespace,
body)
assert response is not None, f"Failed to create StatefulSet: {self.name}"
self.namespace, body
)
assert (
response is not None
), f"Failed to create StatefulSet: {self.name}"
except ApiException as exc:
pytest.fail(f"Failed to create StatefulSet: {self.name}. Reason: {str(exc)}")
pytest.fail(
f"Failed to create StatefulSet: {self.name}. Reason: {str(exc)}"
)

def delete(self) -> None:
try:
response = self.apps_v1_api.delete_namespaced_stateful_set(
self.name,
self.namespace)
assert response is not None, f"Failed to delete StatefulSet: {self.name}"
self.name, self.namespace
)
assert (
response is not None
), f"Failed to delete StatefulSet: {self.name}"
except ApiException as exc:
logging.warning(f"Failed to delete StatefulSet: {self.name}. Reason: {str(exc)}")
logging.warning(
f"Failed to delete StatefulSet: {self.name}. Reason: {str(exc)}"
)

def verify(self, timeout: int) -> bool:
start_time = time.time()
result = False

while result is False:
try:
logging.info(f'Waiting for STS {self.name} reaching {self.replicas} replica count...{int(time.time() - start_time)}/{timeout}s')
logging.info(
f"Waiting for STS {self.name} reaching {self.replicas} replica count...{int(time.time() - start_time)}/{timeout}s"
)
time.sleep(2)

response = self.apps_v1_api.read_namespaced_stateful_set(
self.name,
self.namespace)
assert response is not None, f"Failed to read StatefulSet: {self.name}"
self.name, self.namespace
)
assert (
response is not None
), f"Failed to read StatefulSet: {self.name}"

result = (response.status.available_replicas == self.replicas
and response.status.ready_replicas == self.replicas
and response.status.replicas == self.replicas)
result = (
response.status.available_replicas == self.replicas
and response.status.ready_replicas == self.replicas
and response.status.replicas == self.replicas
)

if time.time() - start_time > timeout:
return result
except ApiException as exc:
logging.warning(f"Failed to read StatefulSet: {self.name}. Reason: {str(exc)}")
logging.warning(
f"Failed to read StatefulSet: {self.name}. Reason: {str(exc)}"
)

return result
Loading

0 comments on commit 317fc93

Please sign in to comment.