From b4bee9233d36d619a47b6a5b335483fa692ec657 Mon Sep 17 00:00:00 2001 From: Parag Kamble Date: Fri, 13 Dec 2024 00:23:33 +0530 Subject: [PATCH] Test Automation for encryption dashboard summary Signed-off-by: Parag Kamble --- ocs_ci/ocs/ui/page_objects/block_and_file.py | 100 ++++++++++ .../ocs/ui/page_objects/object_details_tab.py | 94 ++++++++++ .../ui/page_objects/storage_system_details.py | 4 + ocs_ci/ocs/ui/views.py | 34 ++++ ...test_encryption_configuration_dashboard.py | 171 ++++++++++++++++++ 5 files changed, 403 insertions(+) create mode 100644 ocs_ci/ocs/ui/page_objects/object_details_tab.py create mode 100644 tests/functional/encryption/test_encryption_configuration_dashboard.py diff --git a/ocs_ci/ocs/ui/page_objects/block_and_file.py b/ocs_ci/ocs/ui/page_objects/block_and_file.py index 94028f72f41..1355dbbd0de 100644 --- a/ocs_ci/ocs/ui/page_objects/block_and_file.py +++ b/ocs_ci/ocs/ui/page_objects/block_and_file.py @@ -6,6 +6,7 @@ from ocs_ci.ocs.ui.page_objects.storage_system_details import StorageSystemDetails from ocs_ci.ocs.ui.workload_ui import PvcCapacityDeploymentList, compare_mem_usage from ocs_ci.utility.utils import TimeoutSampler +from selenium.webdriver.common.by import By class BlockAndFile(StorageSystemDetails): @@ -259,3 +260,102 @@ def get_avg_consumption_from_ui(self): ) logger.info(f"'Average of storage consumption per day' from the UI : {average}") return average + + def get_block_file_encryption_summary(self): + """ + Click on Encryption Summary button and retrieve the encryption details. + + Returns: + dict: Encryption summary on block and file page. + """ + encryption_summary = { + "cluster_wide_encryption": {"status": None, "kms": ""}, + "storageclass_encryption": { + "status": None, + "kms": "", + }, + "intransit_encryption": {"status": None}, + } + + logger.info("Getting Block and File Encryption Summary Details") + + # Open the encryption summary popup + self.do_click( + self.validation_loc["encryption_summary"]["file_and_block"]["enabled"], + enable_screenshot=True, + ) + from time import sleep + + sleep(3) + + # Context and status mappings + context_map = { + "Cluster-wide encryption": "cluster_wide_encryption", + "Storage class encryption": "storageclass_encryption", + "In-transit encryption": "intransit_encryption", + "Block storage": "block_storage", + } + + # Get elements for text and root + encryption_content_location = self.validation_loc["encryption_summary"][ + "file_and_block" + ]["encryption_content_data"] + encryption_summary_text = self.get_element_text(encryption_content_location) + root_elements = self.get_elements(encryption_content_location) + + if not root_elements: + raise ValueError("Error getting root web element") + root_element = root_elements[0] + + # Function to extract status from an SVG element + def extract_status(svg_path): + try: + svg_element = root_element.find_element(By.CSS_SELECTOR, svg_path) + if svg_element and svg_element.tag_name == "svg": + if svg_element.get_attribute("data-test") == "success-icon": + return True + else: + return False + except Exception as e: + logger.error(f"Error extracting status: {e}") + return None + + # Process encryption summary text + current_context = None + for line in encryption_summary_text.split("\n"): + line = line.strip() + if line in context_map: + current_context = context_map[line] + continue + + if ( + current_context == "cluster_wide_encryption" + and "External Key Management Service" in line + ): + encryption_summary[current_context]["kms"] = line.split(":")[-1].strip() + encryption_summary[current_context]["status"] = extract_status( + "div.pf-m-align-items-center:nth-child(1) > div:nth-child(2) > svg:nth-child(1)" + ) + elif ( + current_context == "storageclass_encryption" + and "External Key Management Service" in line + ): + encryption_summary[current_context]["kms"] = line.split(":")[-1].strip() + encryption_summary[current_context]["status"] = extract_status( + "div.pf-v5-l-flex:nth-child(6) > div:nth-child(2) > svg:nth-child(1)" + ) + elif current_context == "intransit_encryption": + encryption_summary[current_context]["status"] = extract_status( + "div.pf-v5-l-flex:nth-child(10) > div:nth-child(2) > svg" + ) + + logger.info(f"Encryption Summary: {encryption_summary}") + + # Close the popup + logger.info("Closing the popup") + self.do_click( + self.validation_loc["encryption_summary"]["file_and_block"]["close"], + enable_screenshot=True, + ) + + return encryption_summary diff --git a/ocs_ci/ocs/ui/page_objects/object_details_tab.py b/ocs_ci/ocs/ui/page_objects/object_details_tab.py new file mode 100644 index 00000000000..4eeb42ee79b --- /dev/null +++ b/ocs_ci/ocs/ui/page_objects/object_details_tab.py @@ -0,0 +1,94 @@ +import time + +from ocs_ci.ocs.ui.helpers_ui import logger +from ocs_ci.ocs.ui.page_objects.storage_system_details import StorageSystemDetails +from selenium.webdriver.common.by import By + + +class ObjectDetails(StorageSystemDetails): + def __init__(self): + StorageSystemDetails.__init__(self) + + def get_encryption_summary(self): + """ + Collecting Encryption summary shown in the Object details page. + + Returns: + encryption_summary (dict): encryption summary on object details page. + """ + encryption_summary = { + "object_storage": {"status": None, "kms": ""}, + "intransit_encryption": {"status": None}, + } + + logger.info("Getting Block and File Encryption Summary Details") + + # Open the encryption summary popup + self.do_click( + self.validation_loc["encryption_summary"]["object"]["enabled"], + enable_screenshot=True, + ) + + time.sleep(3) + + # Context and status mappings + context_map = { + "Object storage": "object_storage", + "In-transit encryption": "intransit_encryption", + } + + # Get elements for text and root + encryption_content_location = self.validation_loc["encryption_summary"][ + "object" + ]["encryption_content_data"] + encryption_summary_text = self.get_element_text(encryption_content_location) + root_elements = self.get_elements(encryption_content_location) + + if not root_elements: + raise ValueError("Error getting root web element") + root_element = root_elements[0] + + # Function to extract status from an SVG element + def extract_status(svg_path): + try: + svg_element = root_element.find_element(By.CSS_SELECTOR, svg_path) + if svg_element and svg_element.tag_name == "svg": + if svg_element.get_attribute("data-test") == "success-icon": + return True + else: + return False + except Exception as e: + logger.error(f"Error extracting status: {e}") + return None + + # Process encryption summary text + current_context = None + for line in encryption_summary_text.split("\n"): + line = line.strip() + if line in context_map: + current_context = context_map[line] + continue + + if ( + current_context == "object_storage" + and "External Key Management Service" in line + ): + encryption_summary[current_context]["kms"] = line.split(":")[-1].strip() + encryption_summary[current_context]["status"] = extract_status( + "div.pf-v5-l-flex:nth-child(1) > div:nth-child(2) > svg" + ) + elif current_context == "intransit_encryption": + encryption_summary[current_context]["status"] = extract_status( + "div.pf-v5-l-flex:nth-child(4) > div:nth-child(2) > svg" + ) + + logger.info(f"Encryption Summary: {encryption_summary}") + + # Close the popup + logger.info("Closing the popup") + self.do_click( + self.validation_loc["encryption_summary"]["object"]["close"], + enable_screenshot=True, + ) + + return encryption_summary diff --git a/ocs_ci/ocs/ui/page_objects/storage_system_details.py b/ocs_ci/ocs/ui/page_objects/storage_system_details.py index 3922c295ee6..e839227fee5 100644 --- a/ocs_ci/ocs/ui/page_objects/storage_system_details.py +++ b/ocs_ci/ocs/ui/page_objects/storage_system_details.py @@ -33,6 +33,10 @@ def nav_details_object(self): else: self.do_click(self.validation_loc["object"], enable_screenshot=True) + from ocs_ci.ocs.ui.page_objects.object_details_tab import ObjectDetails + + return ObjectDetails() + def nav_block_and_file(self): """ Accessible only at StorageSystems / StorageSystem details / Overview diff --git a/ocs_ci/ocs/ui/views.py b/ocs_ci/ocs/ui/views.py index c43c86b4c6d..bf9fb693ed9 100644 --- a/ocs_ci/ocs/ui/views.py +++ b/ocs_ci/ocs/ui/views.py @@ -1784,6 +1784,39 @@ ), } +validation_4_18 = { + "encryption_summary": { + "file_and_block": { + "enabled": ( + "//button[@class='pf-v5-c-button pf-m-link pf-m-inline' and text()='Enabled']", + By.XPATH, + ), + "close": ( + "//button[@class='pf-v5-c-button pf-m-plain' and @aria-label='Close']", + By.XPATH, + ), + "encryption_content_data": ( + "//div[@class='pf-v5-c-popover__body']", + By.XPATH, + ), + }, + "object": { + "enabled": ( + "//button[@class='pf-v5-c-button pf-m-link pf-m-inline' and text()='Enabled']", + By.XPATH, + ), + "close": ( + "//button[@class='pf-v5-c-button pf-m-plain' and @aria-label='Close']", + By.XPATH, + ), + "encryption_content_data": ( + "//div[@class='pf-v5-c-popover__content']", + By.XPATH, + ), + }, + } +} + topology = { "topology_graph": ("//*[@data-kind='graph']", By.XPATH), "node_label": ("//*[@class='pf-topology__node__label']", By.XPATH), @@ -2025,6 +2058,7 @@ **validation_4_13, **validation_4_14, **validation_4_17, + **validation_4_18, }, "block_pool": {**block_pool, **block_pool_4_12, **block_pool_4_13}, "storageclass": {**storageclass, **storageclass_4_9}, diff --git a/tests/functional/encryption/test_encryption_configuration_dashboard.py b/tests/functional/encryption/test_encryption_configuration_dashboard.py new file mode 100644 index 00000000000..25eb9a77a9a --- /dev/null +++ b/tests/functional/encryption/test_encryption_configuration_dashboard.py @@ -0,0 +1,171 @@ +import pytest +import logging + +log = logging.getLogger(__name__) +from ocs_ci.ocs.ui.page_objects.page_navigator import PageNavigator +from ocs_ci.framework.pytest_customization.marks import green_squad, tier1 +from ocs_ci.ocs.ocp import OCP +from ocs_ci.framework import config +from ocs_ci.ocs import constants +from ocs_ci.ocs.resources.storage_cluster import StorageCluster +from ocs_ci.helpers.helpers import storagecluster_independent_check + + +@green_squad +@tier1 +class TestEncryptionConfigurationDashboard: + @pytest.fixture(autouse=True) + def encryption_status(self): + """ + Collect Encryption status from storagecluster and noobaa spec. + """ + # Retrieve encryption details + cluster_name = ( + constants.DEFAULT_CLUSTERNAME_EXTERNAL_MODE + if storagecluster_independent_check() + else constants.DEFAULT_CLUSTERNAME + ) + + sc_obj = StorageCluster( + resource_name=cluster_name, + namespace=config.ENV_DATA["cluster_namespace"], + ) + + self.enc_details = sc_obj.data["spec"].get("encryption", {}) + self.intransit_encryption_status = ( + sc_obj.data["spec"] + .get("network", {}) + .get("connections", {}) + .get("encryption", {}) + .get("enabled", False) + ) + log.info(f"Encryption details from storagecluster Spec: {self.enc_details}") + + noobaa_obj = OCP( + kind="noobaa", + namespace=config.ENV_DATA["cluster_namespace"], + resource_name="noobaa", + ) + + self.noobaa_kms = ( + noobaa_obj.data["spec"] + .get("security", {}) + .get("kms", {}) + .get("connectionDetails", {}) + .get("KMS_PROVIDER", None) # Provide a default value of None if not found + ) + log.info(f"Noobaa Spec has mentioned KMS: {self.noobaa_kms}") + + def validate_encryption( + self, context, actual_status, expected_status, error_message + ): + """Helper function to validate encryption details + + Args: + context (str): Encryption Type + actual_status (str): Encryption status in the spec file + expected_status (str): Encryption status shown on the dashboard. + error_message (str): Error message to display. + """ + assert actual_status == expected_status, error_message + log.info(f"{context} status is as expected: {actual_status}") + + @pytest.mark.polarion_id("OCS-6300") + def test_file_block_encryption_configuration_dashboard(self, setup_ui_class): + """Test the encryption configuration dashboard of File And Block details for correctness. + + Steps: + 1. Navigate to file and block details page + 2. Open encryption details. + 3. verify encryption data with the nooba and storagecluster spec. + """ + + # Navigate to the block and file page + block_and_file_page = ( + PageNavigator() + .nav_odf_default_page() + .nav_storage_systems_tab() + .nav_storagecluster_storagesystem_details() + .nav_block_and_file() + ) + + # Retrieve encryption summary from the dashboard + encryption_summary = block_and_file_page.get_block_file_encryption_summary() + + # Validate cluster-wide encryption + cluster_wide_details = self.enc_details.get("clusterWide", {}) + if isinstance(cluster_wide_details, dict): + self.validate_encryption( + "ClusterWide Encryption", + encryption_summary["cluster_wide_encryption"]["status"], + cluster_wide_details.get("status", False), + "ClusterWide Encryption is not showing correctly in the dashboard.", + ) + self.validate_encryption( + "ClusterWide KMS", + encryption_summary["cluster_wide_encryption"]["kms"], + cluster_wide_details.get("kms", {}).get("enable", False), + "KMS is not mentioned in the encryption summary.", + ) + else: + log.warning( + "ClusterWide Encryption details are not a dictionary, skipping checks." + ) + + # Validate storage class encryption + storage_class_details = self.enc_details.get("storageClass", {}) + if isinstance(storage_class_details, dict): + self.validate_encryption( + "StorageClass Encryption", + encryption_summary["storageclass_encryption"]["status"], + storage_class_details.get("status", False), + "StorageClass encryption is not showing correctly in the dashboard.", + ) + else: + log.warning("StorageClass details are not a dictionary, skipping checks.") + + # Validate in-transit encryption + self.validate_encryption( + "InTransit Encryption", + encryption_summary["intransit_encryption"]["status"], + self.intransit_encryption_status, + "InTransit Encryption status is incorrect in the dashboard.", + ) + + @pytest.mark.polarion_id("OCS-6301") + def test_object_storage_encryption_configuration_dashboard(self, setup_ui_class): + """Test the encryption configuration dashboard of Object details for correctness. + + Steps: + 1. Navigate to object details page + 2. Open encryption details. + 3. verify encryption data with the nooba and storagecluster spec. + """ + # Navigate to the Object Storage page + object_details_page = ( + PageNavigator() + .nav_odf_default_page() + .nav_storage_systems_tab() + .nav_storagecluster_storagesystem_details() + .nav_details_object() + ) + + encryption_summary = object_details_page.get_encryption_summary() + log.info(f"Encryption Summary from page : {encryption_summary}") + + # Validate Object Encryption Summary + assert encryption_summary["object_storage"][ + "status" + ], "Object encryption summary is wrong" + assert ( + encryption_summary["object_storage"]["kms"].upper() + == self.noobaa_kms.upper() + ), "KMS details is not correct" + + # Validate in-transit encryption + self.validate_encryption( + "InTransit Encryption", + encryption_summary["intransit_encryption"]["status"], + self.intransit_encryption_status, + "InTransit Encryption status is incorrect in the dashboard.", + )