Merge "Add Helm-overrides scenario."
This commit is contained in:
@@ -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"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user