Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Create new test for create multiple device classes #11096

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
150 changes: 150 additions & 0 deletions ocs_ci/helpers/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5619,3 +5619,153 @@ def apply_custom_taint_and_toleration(taint_label="xyz"):
)
for pod_obj in pod_list:
pod_obj.delete(wait=False)


def create_ceph_block_pool_for_deviceclass(
device_class,
pool_name=None,
namespace=None,
replica=3,
failure_domain=None,
):
"""
Create a Ceph block pool for a device class

Args:
device_class (str): The device class name
pool_name (str): The pool name to create
namespace (str): The pool namespace
replica (int): The replica size for a pool
failure_domain (str): Failure domain name
verify (bool): True to verify the pool exists after creation. False otherwise

Returns:
OCS: The OCS instance for the Ceph block pool

"""
cbp_data = templating.load_yaml(constants.DEVICECLASS_CEPHBLOCKPOOL_YAML)
cbp_data["metadata"]["name"] = (
pool_name if pool_name else create_unique_resource_name("test", "cbp")
)
cbp_data["metadata"]["namespace"] = (
namespace or config.ENV_DATA["cluster_namespace"]
)
cbp_data["spec"]["deviceClass"] = device_class
cbp_data["spec"]["replicated"]["size"] = replica
cbp_data["spec"]["failureDomain"] = failure_domain or get_failure_domin()

cbp_obj = create_resource(**cbp_data)
cbp_obj.reload()
return cbp_obj


def create_lvs_resource(storageclass, worker_nodes=None, min_size=None, max_size=None):
"""
Create the LocalVolumeSet resource.

Args:
storageclass (string): storageClassName value to be used in
LocalVolumeSet CR based on LOCAL_VOLUME_YAML
worker_nodes (list): The worker node names to be used in the LocalVolumeSet resource
min_size (str): The min size to be used in the LocalVolumeSet resource
max_size (str): The max size to be used in the LocalVolumeSet resource

Returns:
OCS: The OCS instance for the LocalVolumeSet resource

"""
worker_nodes = worker_nodes or node.get_worker_nodes()

# Pull local volume set yaml data
logger.info("Pulling LocalVolumeSet CR data from yaml")
lvs_data = templating.load_yaml(constants.LOCAL_VOLUME_SET_YAML)

# Since we don't have datastore with SSD on our current VMware machines, localvolumeset doesn't detect
# NonRotational disk. As a workaround we are setting Rotational to device MechanicalProperties to detect
# HDD disk
if config.ENV_DATA.get(
"local_storage_allow_rotational_disks"
) or config.ENV_DATA.get("odf_provider_mode_deployment"):
logger.info(
"Adding Rotational for deviceMechanicalProperties spec"
" to detect HDD disk"
)
lvs_data["spec"]["deviceInclusionSpec"]["deviceMechanicalProperties"].append(
"Rotational"
)

if min_size:
lvs_data["spec"]["deviceInclusionSpec"]["minSize"] = min_size
if max_size:
lvs_data["spec"]["deviceInclusionSpec"]["maxSize"] = max_size
# Update local volume set data with Worker node Names
logger.info(
"Updating LocalVolumeSet CR data with worker nodes Name: %s", worker_nodes
)
lvs_data["spec"]["nodeSelector"]["nodeSelectorTerms"][0]["matchExpressions"][0][
"values"
] = worker_nodes

# Set storage class
logger.info(
"Updating LocalVolumeSet CR data with LSO storageclass: %s", storageclass
)
lvs_data["spec"]["storageClassName"] = storageclass

# set volumeMode to Filesystem for MCG only deployment
if config.ENV_DATA["mcg_only_deployment"]:
lvs_data["spec"]["volumeMode"] = constants.VOLUME_MODE_FILESYSTEM

lvs_obj = create_resource(**lvs_data)
lvs_obj.reload()
return lvs_obj


def create_deviceclass_storageclass(
pool_name,
sc_name=None,
cluster_id="openshift-storage",
reclaim_policy="Delete",
volume_binding_mode="WaitForFirstConsumer",
image_features=None,
encrypted="false",
allow_volume_expansion=True,
):
"""
Create a StorageClass resource for device class from provided parameters.

Args:
pool_name (str): Name of the pool.
sc_name (str): Name of the StorageClass. If not provided, it will set a random name.
cluster_id (str): Cluster ID.
reclaim_policy (str): Reclaim policy (e.g., "Delete" or "Retain").
volume_binding_mode (str): Volume binding mode (e.g., "Immediate", "WaitForFirstConsumer").
image_features (str): Image features for the pool.
encrypted (str): Encryption flag ("true" or "false").
allow_volume_expansion (bool): Allow volume expansion (True/False).

Returns:
OCS: The OCS instance for the StorageClass resource

"""
suffix = "".join(random.choices("0123456789", k=5))
sc_name = sc_name or f"ssd{suffix}"
image_features = (
image_features or "layering,deep-flatten,exclusive-lock,object-map,fast-diff"
)

sc_data = templating.load_yaml(constants.DEVICECLASS_STORAGECLASS_YAML)

# Update the YAML with the provided parameters
sc_data["metadata"]["name"] = sc_name
sc_data["parameters"]["pool"] = pool_name
sc_data["allowVolumeExpansion"] = allow_volume_expansion
sc_data["reclaimPolicy"] = reclaim_policy
sc_data["volumeBindingMode"] = volume_binding_mode
sc_data["parameters"]["imageFeatures"] = image_features
sc_data["parameters"]["clusterID"] = cluster_id
sc_data["parameters"]["encrypted"] = encrypted

sc_obj = create_resource(**sc_data)
sc_obj.reload()
return sc_obj
108 changes: 108 additions & 0 deletions ocs_ci/helpers/multiple_device_classes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import logging
import random

from ocs_ci.helpers.helpers import create_lvs_resource
from ocs_ci.ocs.cluster import check_ceph_osd_tree, check_ceph_osd_df_tree
from ocs_ci.ocs.exceptions import CephHealthException
from ocs_ci.ocs.node import add_disk_to_node
from ocs_ci.ocs.resources.pod import get_ceph_tools_pod
from ocs_ci.ocs.resources.storage_cluster import (
get_storage_size,
get_device_class,
verify_storage_device_class,
verify_device_class_in_osd_tree,
)
from ocs_ci.utility.utils import sum_of_two_storage_sizes

from ocs_ci.ocs import constants, defaults
from ocs_ci.ocs.ocp import OCP


log = logging.getLogger(__name__)


def create_new_lvs_for_new_deviceclass(
worker_nodes, create_disks_for_lvs=True, ssd=True
):
"""
Create new LocalVolumeSet resource for a new device class

Args:
worker_nodes (list): The worker node names to be used in the LocalVolumeSet resource.
create_disks_for_lvs (bool): If True, it will create a new disks for the new LocalVolumeSet resource.
ssd (bool): if True, mark disk as SSD

Returns:
OCS: The OCS instance for the LocalVolumeSet resource

"""
osd_size = get_storage_size()
log.info(f"the osd size is {osd_size}")
old_lvs_max_size = sum_of_two_storage_sizes(osd_size, "30Gi")
ocp_lvs_obj = OCP(
kind=constants.LOCAL_VOLUME_SET,
namespace=defaults.LOCAL_STORAGE_NAMESPACE,
resource_name=constants.LOCAL_BLOCK_RESOURCE,
)
log.info(
f"Update the old LocalVolumeSet {ocp_lvs_obj.resource_name} with the maxSize "
f"{old_lvs_max_size} so it will not consume the new PVs"
)
params = (
f'{{"spec": {{"deviceInclusionSpec": {{"maxSize": "{old_lvs_max_size}"}}}}}}'
)
lvs_result = ocp_lvs_obj.patch(params=params, format_type="json")
assert (
lvs_result
), f"Failed to update the LocalVolumeSet {ocp_lvs_obj.resource_name}"

log.info(
"Create a new minSize that will be be higher than the maxSize of the old LVS, so that the new LVS "
"will consume the disks with the new size"
)
min_size = sum_of_two_storage_sizes(old_lvs_max_size, "10Gi")
log.info(
"Limit the max size of the new LVS, so it will consume only the new added disks"
)
max_size = sum_of_two_storage_sizes(min_size, "60Gi")
suffix = "".join(random.choices("0123456789", k=5))
sc_name = f"localvolume{suffix}"
lvs_obj = create_lvs_resource(sc_name, worker_nodes, min_size, max_size)

if create_disks_for_lvs:
disk_size_in_gb = sum_of_two_storage_sizes(min_size, "10Gi")
disk_size = int(disk_size_in_gb[:-2])
for n in worker_nodes:
add_disk_to_node(n, disk_size=disk_size, ssd=ssd)

return lvs_obj


def check_ceph_state_post_add_deviceclass():
"""
Check the Ceph state post add a new deviceclass.
The function checks the Ceph device classes and osd tree.

Raises:
CephHealthException: In case the Ceph device classes and osd tree checks
didn't finish successfully

"""
log.info("Check the Ceph device classes and osd tree")
device_class = get_device_class()
ct_pod = get_ceph_tools_pod()
try:
verify_storage_device_class(device_class)
verify_device_class_in_osd_tree(ct_pod, device_class)
except AssertionError as ex:
raise CephHealthException(ex)
if not check_ceph_osd_tree():
raise CephHealthException("The ceph osd tree checks didn't finish successfully")
if not check_ceph_osd_df_tree():
raise CephHealthException(
"The ceph osd df tree output is not formatted correctly"
)


def verification_steps_after_adding_new_deviceclass():
pass
13 changes: 13 additions & 0 deletions ocs_ci/ocs/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@
AI_NETWORK_CONFIG_TEMPLATE = os.path.join(
"ocp-deployment", "ai-host-network-config.yaml.j2"
)
MULTIPLE_DEVICECLASSES_DIR = os.path.join(TEMPLATE_DIR, "multiple-deviceclasses")

# Statuses
STATUS_READY = "Ready"
PEER_READY = "Peer ready"
Expand Down Expand Up @@ -3117,3 +3119,14 @@
MACHINE_POOL_ACTIONS = [CREATE, EDIT, DELETE]
# MDR multicluster roles
MDR_ROLES = ["ActiveACM", "PassiveACM", "PrimaryODF", "SecondaryODF"]

# Multiple device classes Yaml files
STORAGE_DEVICESET_YAML = os.path.join(
MULTIPLE_DEVICECLASSES_DIR, "storage_device_set.yaml"
)
DEVICECLASS_CEPHBLOCKPOOL_YAML = os.path.join(
MULTIPLE_DEVICECLASSES_DIR, "deviceclass-cephblockpool.yaml"
)
DEVICECLASS_STORAGECLASS_YAML = os.path.join(
MULTIPLE_DEVICECLASSES_DIR, "deviceclass-storageclass.yaml"
)
33 changes: 33 additions & 0 deletions ocs_ci/ocs/resources/pv.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,3 +245,36 @@ def get_node_pv_objs(sc_name, node_name):
for pv_obj in pv_objs
if pv_obj["metadata"]["labels"]["kubernetes.io/hostname"] == node_name
]


def wait_for_pvs_in_lvs_to_reach_status(
lvs_obj, pv_count, expected_status, timeout=180, sleep=10
):
"""
Wait for the Persistent Volumes (PVs) associated with a specific LocalVolumeSet (LVS)
to reach the expected status within a given timeout.

Args:
lvs_obj (OCP): The LocalVolumeSet object whose PVs are being monitored.
pv_count (int): The number of PVs expected to reach the desired status.
expected_status (str): The expected status of the PVs (e.g., "Bound", "Available").
timeout (int): Maximum time to wait for the PVs to reach the expected status, in seconds.
sleep (int): Interval between successive checks, in seconds.

Returns:
bool: True if all PVs reach the expected status within the timeout, False otherwise.

Raises:
TimeoutExpiredError: If the PVs do not reach the expected status within the specified timeout.
ResourceWrongStatusException: If any PV enters an unexpected or error status.

"""
selector = f"storage.openshift.com/owner-name={lvs_obj.name}"
pv_obj = ocp.OCP(kind=constants.PV)
return pv_obj.wait_for_resource(
condition=expected_status,
resource_count=pv_count,
selector=selector,
timeout=timeout,
sleep=sleep,
)
Loading
Loading