Merge "Add Helm-overrides scenario."

This commit is contained in:
Zuul
2025-12-04 21:29:03 +00:00
committed by Gerrit Code Review
9 changed files with 172 additions and 50 deletions

View File

@@ -34,10 +34,17 @@ class HealthKeywords(BaseKeyword):
# Validate all apps are healthy and applied
self.validate_apps_health_and_applied()
def validate_pods_health(self):
"""Function to validate the health of all pods in the cluster"""
def validate_pods_health(self, namespace: str | None = None) -> bool:
"""Function to validate the health of all pods in the cluster
Args:
namespace (str | None): Namespace to check the pods in. If None, the default namespace will be used.
Returns:
bool: True if pod is in expected status else False.
"""
healthy_status = ["Running", "Succeeded", "Completed"]
KubectlGetPodsKeywords(self.ssh_connection).wait_for_all_pods_status(healthy_status, timeout=300)
return KubectlGetPodsKeywords(self.ssh_connection).wait_for_pods_to_reach_status(expected_status=healthy_status, namespace=namespace, timeout=300)
def validate_no_alarms(self):
"""Function to validate no alarms are present in the cluster"""

View File

@@ -11,7 +11,6 @@ class HelmOverrideObject:
"""
self.name: str = None
self.namespace: str = None
self.user_overrides: str = None
def set_name(self, name: str):
"""
@@ -48,21 +47,3 @@ class HelmOverrideObject:
str: The Kubernetes namespace.
"""
return self.namespace
def set_user_overrides(self, user_overrides: str):
"""
Set the user-defined overrides.
Args:
user_overrides (str): string of user overrides.
"""
self.user_overrides = user_overrides
def get_user_overrides(self) -> str:
"""
Get the user-defined overrides.
Returns:
str: string of user overrides.
"""
return self.user_overrides

View File

@@ -26,7 +26,6 @@ class SystemHelmOverrideOutput:
self.helm_override = HelmOverrideObject()
self.helm_override.set_name(output_values["name"])
self.helm_override.set_namespace(output_values["namespace"])
self.helm_override.set_user_overrides(output_values["user_overrides"])
else:
raise KeywordException(f"The output line {output_values} was not valid")
@@ -50,7 +49,7 @@ class SystemHelmOverrideOutput:
Returns:
bool: True if the output contains all required fields, False otherwise.
"""
required_fields = ["name", "namespace", "user_overrides"]
required_fields = ["name", "namespace"]
valid = True
for field in required_fields:
if field not in value:

View File

@@ -12,7 +12,7 @@ class HelmOverrideShowObject:
self.attributes: dict = None
self.combined_overrides: dict = None
self.system_overrides: dict = None
self.user_overrides: dict = None
self.user_overrides: str = None
def set_name(self, name: str):
"""
@@ -113,11 +113,11 @@ class HelmOverrideShowObject:
"""
self.user_overrides = user_overrides
def get_user_overrides(self) -> dict:
def get_user_overrides(self) -> str:
"""
Gets the user-defined overrides for the chart.
Returns:
dict: User override values.
str: User override values.
"""
return self.user_overrides

View File

@@ -39,6 +39,25 @@ class SystemHelmOverrideKeywords(BaseKeyword):
system_helm_override_output = SystemHelmOverrideOutput(output)
return system_helm_override_output
def update_helm_override_via_set(self, override_values: str, app_name: str, chart_name: str, namespace: str) -> SystemHelmOverrideOutput:
"""
Update the helm-override values via --set parameter
Args:
override_values (str): override values
app_name (str): the app name
chart_name (str): the chart name
namespace (str): the namespace
Returns:
SystemHelmOverrideOutput: object with the list of helm overrides.
"""
command = source_openrc(f"system helm-override-update --set {override_values} {app_name} {chart_name} {namespace}")
output = self.ssh_connection.send(command)
self.validate_success_return_code(self.ssh_connection)
system_helm_override_output = SystemHelmOverrideOutput(output)
return system_helm_override_output
def get_system_helm_override_show(self, app_name: str, chart_name: str, namespace: str) -> SystemHelmOverrideShowOutput:
"""
Gets the system helm-override show
@@ -70,3 +89,31 @@ class SystemHelmOverrideKeywords(BaseKeyword):
user_override = self.get_system_helm_override_show(app_name, chart_name, namespace).get_helm_override_show().get_user_overrides()
validate_str_contains(user_override, label, "User Override")
def get_system_helm_override_list(self, app_name: str) -> SystemHelmOverrideOutput:
"""
Gets the system helm-override show
Args:
app_name (str): the app name
Returns:
SystemHelmOverrideOutput: The parsed helm override list object.
"""
command = source_openrc(f"system helm-override-list {app_name} ")
output = self.ssh_connection.send(command)
self.validate_success_return_code(self.ssh_connection)
system_helm_override_show_output = SystemHelmOverrideOutput(output)
return system_helm_override_show_output
def delete_system_helm_override(self, app_name: str, chart_name: str, namespace: str) -> None:
"""
Gets the system helm-override show
Args:
app_name (str): the app name
chart_name (str): the chart name
namespace (str): the namespace
"""
self.ssh_connection.send(source_openrc(f"system helm-override-delete {app_name} {chart_name} {namespace}"))
self.validate_success_return_code(self.ssh_connection)

View File

@@ -5,7 +5,8 @@ from framework.exceptions.keyword_exception import KeywordException
class SystemVerticalTableParser:
"""
Class for System vertical table parsing
Class for System vertical table parsing.
In a vertical table, attributes and their corresponding values are arranged in adjacent columns,
with each attribute listed alongside its value.
@@ -60,11 +61,12 @@ class SystemVerticalTableParser:
_MIN_ROWS = 5
def __init__(self, system_output):
def __init__(self, system_output: list[str] | str):
"""
Constructor
Constructor.
Args:
system_output (list[str]): a list of strings representing the output of a 'system <*>' command.
system_output (list[str] | str): a list of strings or a string representing the output of a 'system <*>' command.
"""
self.system_output = system_output
@@ -72,12 +74,13 @@ class SystemVerticalTableParser:
self,
):
"""
Getter for output values dict
Getter for output values dict.
Returns: the output values dict
"""
# Regex to validate the pattern "+---+---+", with varying dashes
pattern = r'^\+\-+\+\-+\+$'
pattern = r"^\+\-+\+\-+\+$"
# Get the total number of rows.
total_rows = len(self.system_output)
@@ -99,7 +102,7 @@ class SystemVerticalTableParser:
second_last_row_valid = re.match(pattern, self.system_output[total_rows - 2])
# Check if there is some text in the last different from the pattern.
has_text_in_last_row = True if not last_row_valid and self.system_output[total_rows - 1].strip() != '' else False
has_text_in_last_row = True if not last_row_valid and self.system_output[total_rows - 1].strip() != "" else False
# There are cases in which the last row is a message that is not part of the table.
# Refer to the header comment of this class for an example.
@@ -125,19 +128,19 @@ class SystemVerticalTableParser:
data_row_number = 0
previous_key = None
for row in self.system_output[3:last_row]: # Ignore the first three rows and the last row.
# Detect regex syntax patterns
regex_indicators = [
'regexp:', 'regex:', # Explicit regex keywords
r'\^/', # Start anchor with path
r'\$\|/', # End anchor with pipe
r'\([^)]*\|[^)]*\)' # Parentheses containing pipes
]
regex_indicators = ["regexp:", "regex:", r"\^/", r"\$\|/", r"\([^)]*\|[^)]*\)"] # Explicit regex keywords # Start anchor with path # End anchor with pipe # Parentheses containing pipes
if "!!binary" in row:
row = row.replace("!!binary |", "")
has_regex = any(re.search(pattern, str(row)) for pattern in regex_indicators)
if not has_regex and str(row).count('|') != 3:
if not has_regex and str(row).count("|") != 3:
raise KeywordException("It is expected that a table have exactly two columns.")
parts = row.split('|')
parts = row.split("|")
if len(parts) > 2:
key = parts[1].strip()
value = parts[2].strip()
@@ -148,7 +151,7 @@ class SystemVerticalTableParser:
# This 'else' block handles cases where the property name spans multiple lines, ensuring proper parsing.
if data_row_number == 0:
raise KeywordException("The property name in the first data row cannot be empty.")
output_values_dict[previous_key] = output_values_dict[previous_key] + ' ' + value
output_values_dict[previous_key] = output_values_dict[previous_key] + " " + value
data_row_number += 1
return output_values_dict

View File

@@ -152,12 +152,13 @@ class KubectlGetPodsKeywords(BaseKeyword):
return False
def wait_for_all_pods_status(self, expected_statuses: [str], timeout: int = 600) -> bool:
def wait_for_all_pods_status(self, expected_statuses: list[str], timeout: int = 600) -> bool:
"""
Wait for all pods to be in the given status(s)
Args:
expected_statuses ([str]): list of expected statuses ex. ['Completed', 'Running']
expected_statuses (list[str]): list of expected statuses ex. ['Completed' , 'Running']
timeout (int): the amount of time in seconds to wait
Returns:
@@ -174,12 +175,12 @@ class KubectlGetPodsKeywords(BaseKeyword):
raise KeywordException("All pods are not in the expected status")
def wait_for_pods_to_reach_status(self, expected_status: str, pod_names: list = None, namespace: str = None, poll_interval: int = 5, timeout: int = 180) -> bool:
def wait_for_pods_to_reach_status(self, expected_status: str | list, pod_names: list = None, namespace: str = None, poll_interval: int = 5, timeout: int = 180) -> bool:
"""
Wait timeout amount of time for the given pod in a namespace to be in the given status.
Args:
expected_status (str): the expected status.
expected_status (str | list): the expected status.
pod_names (list): the pod names to look for. If left as None, we will check for all the pods.
namespace (str): the namespace.
poll_interval (int): the interval in seconds to poll for status.
@@ -190,6 +191,11 @@ class KubectlGetPodsKeywords(BaseKeyword):
"""
pod_status_timeout = time.time() + timeout
if isinstance(expected_status, str):
expected_statuses = [expected_status]
else:
expected_statuses = expected_status
while time.time() < pod_status_timeout:
pods = self.get_pods(namespace).get_pods()
@@ -198,11 +204,12 @@ class KubectlGetPodsKeywords(BaseKeyword):
if pod_names:
pods = [pod for pod in pods if pod.get_name() in pod_names]
pods_in_incorrect_status = [pod for pod in pods if pod.get_status() != expected_status]
pods_in_incorrect_status = list(filter(lambda pod: pod.get_status() not in expected_statuses, pods))
if len(pods_in_incorrect_status) == 0:
return True
time.sleep(poll_interval)
pods_in_incorrect_status_names = [pod.get_name() for pod in pods_in_incorrect_status]
pods_in_incorrect_status_names = ", ".join(pods_in_incorrect_status_names)
raise KeywordException(f"Pods {pods_in_incorrect_status_names} in namespace {namespace} did not reach status {expected_status} within {timeout} seconds")

View File

@@ -5,9 +5,11 @@ from framework.logging.automation_logger import get_logger
from framework.validation.validation import validate_equals, validate_equals_with_retry, validate_str_contains
from keywords.ceph.ceph_osd_pool_ls_detail_keywords import CephOsdPoolLsDetailKeywords
from keywords.ceph.ceph_status_keywords import CephStatusKeywords
from keywords.cloud_platform.health.health_keywords import HealthKeywords
from keywords.cloud_platform.ssh.lab_connection_keywords import LabConnectionKeywords
from keywords.cloud_platform.system.application.system_application_apply_keywords import SystemApplicationApplyKeywords
from keywords.cloud_platform.system.application.system_application_list_keywords import SystemApplicationListKeywords
from keywords.cloud_platform.system.helm.system_helm_override_keywords import SystemHelmOverrideKeywords
from keywords.cloud_platform.system.host.system_host_fs_keywords import SystemHostFSKeywords
from keywords.cloud_platform.system.host.system_host_list_keywords import SystemHostListKeywords
from keywords.cloud_platform.system.host.system_host_lock_keywords import SystemHostLockKeywords
@@ -762,3 +764,57 @@ def test_monitor_operations_rook_ceph():
get_logger().log_test_case_step("Verify final rook-ceph health.")
ceph_status_keywords.wait_for_ceph_health_status(expect_health_status=True)
@mark.p2
@mark.lab_rook_ceph
def test_helm_overrides_operations():
"""
Test case: test helm overrides operations
Test Steps:
- Check the storage backend is rook-ceph
- Check the healthy before start the update
- Update the helm overrides
- Validate the update of helm override
- Delete the helm overrides
- Check the healthy after delete the update
Args: None
"""
ssh_connection = LabConnectionKeywords().get_active_controller_ssh()
system_storage_backend_keywords = SystemStorageBackendKeywords(ssh_connection)
ceph_status_keywords = CephStatusKeywords(ssh_connection)
health_keywords = HealthKeywords(ssh_connection)
system_helm_override_keywords = SystemHelmOverrideKeywords(ssh_connection)
storage_backend = "ceph-rook"
chart_name = "rook-ceph-cluster"
namespace = "rook-ceph"
app_name = namespace
ceph_block_pools = "cephBlockPools.0.spec.replicated.size=3"
get_logger().log_test_case_step("Check the storage backend is rook-ceph")
validate_equals(system_storage_backend_keywords.get_system_storage_backend_list().is_backend_configured(storage_backend), True, "Checking storage backend")
get_logger().log_test_case_step("Check the healthy before start the update")
health_keywords.validate_pods_health(namespace)
ceph_status_keywords.wait_for_ceph_health_status(expect_health_status=True)
get_logger().log_test_case_step("Update the helm overrides")
system_helm_override_keywords.update_helm_override_via_set(ceph_block_pools, app_name, chart_name, namespace)
get_logger().log_test_case_step("Validate the update of helm override")
user_override = system_helm_override_keywords.get_system_helm_override_show(app_name, chart_name, namespace).get_helm_override_show().get_user_overrides()
validate_str_contains(user_override, 'size: "3"', "Validate if the helm override was updated")
get_logger().log_test_case_step("Delete the helm overrides")
system_helm_override_keywords.delete_system_helm_override(app_name, chart_name, namespace)
user_override = system_helm_override_keywords.get_system_helm_override_show(app_name, chart_name, namespace).get_helm_override_show().get_user_overrides()
validate_equals(user_override, "None", "Validate if the helm override was deleted")
get_logger().log_test_case_step("Check the healthy after delete the update")
health_keywords.validate_pods_health(namespace)
ceph_status_keywords.wait_for_ceph_health_status(expect_health_status=True)

View File

@@ -196,6 +196,19 @@ system_host_if_ptp_remove_wrapped_output = [
"| | | | |\n",
"+--------------------------------------+---------+-----------+---------------+\n",
]
system_helm_override_show_output = [
"+--------------------+--------------------------------------------------------------+\n",
"| Property | Value |\n",
"+--------------------+--------------------------------------------------------------+\n",
"| attributes | enabled: true |\n",
"| | mon_hosts: |\n",
"| | - !!binary | |\n",
"| | Y29udHJvbGxlci0w |\n",
"| | - !!binary | |\n",
"| | Y29udHJvbGxlci0x |\n",
"+--------------------+--------------------------------------------------------------+\n",
]
system_helm_overrides_with_regex = [
"+----------------+------------------------------------------------------------------------------------+\n",
@@ -607,7 +620,6 @@ def test_system_vertical_table_parser_with_valid_table_with_a_text_in_the_end():
assert output_dict.get("status") == "removing"
assert output_dict.get("updated_at") == "2024-10-16T15:50:59.902779+00:00"
def test_system_helm_overrides_with_regex():
"""
Tests the system vertical parser with regex values.
@@ -621,3 +633,13 @@ def test_system_helm_overrides_with_regex():
assert "^/(sys" in output_dict["user_overrides"]
assert "system.network.name" in output_dict["user_overrides"]
assert "^(docker0" in output_dict["user_overrides"]
def test_system_vertical_table_parser_handles_binary_lines():
"""
Tests the system vertical table parser with lines containing '!!binary'.
"""
system_vertical_table_parser = SystemVerticalTableParser(system_helm_override_show_output)
output_dict = system_vertical_table_parser.get_output_values_dict()
assert "attributes" in output_dict
assert "!!binary" not in output_dict["attributes"]