From aa23bc694a6851012f8c10e0248acc73a51c9a92 Mon Sep 17 00:00:00 2001 From: Elena Bondarenko Date: Mon, 2 Sep 2024 16:49:18 +0200 Subject: [PATCH 01/11] Add quota to token generation Signed-off-by: Elena Bondarenko --- ocs_ci/ocs/ui/page_objects/storage_clients.py | 20 ++++++++++++++++++- ocs_ci/ocs/ui/views.py | 8 ++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/ocs_ci/ocs/ui/page_objects/storage_clients.py b/ocs_ci/ocs/ui/page_objects/storage_clients.py index c1f05feb2eb..cd9259b167b 100644 --- a/ocs_ci/ocs/ui/page_objects/storage_clients.py +++ b/ocs_ci/ocs/ui/page_objects/storage_clients.py @@ -13,14 +13,32 @@ class StorageClients(BaseUI): def __init__(self): super().__init__() - def generate_client_onboarding_ticket(self): + def generate_client_onboarding_ticket(self, quota_value=None, quota_tib=None): """ Generate a client onboarding ticket + Args: + quota_value (int): client's quota in GiB or TiB, unlimited if not defined + quota_tib (bool): True if quota is in TiB, False otherwise + Returns: str: onboarding_key """ self.do_click(self.storage_clients_loc["generate_client_onboarding_ticket"]) + if quota_value: + self.do_click(self.storage_clients_loc["custom_quota"]) + self.do_clear( + locator=self.storage_clients_loc["quota_value"], + ) + self.do_send_keys( + locator=self.storage_clients_loc["quota_value"], + text=quota_value, + ) + if quota_tib: + self.do_click(self.storage_clients_loc["choose_units"]) + self.do_click(self.storage_clients_loc["quota_ti"]) + + self.do_click(self.storage_clients_loc["confirm_generation"]) onboarding_key = self.get_element_text( self.storage_clients_loc["onboarding_key"] ) diff --git a/ocs_ci/ocs/ui/views.py b/ocs_ci/ocs/ui/views.py index 4e20cf3792a..49ea9e49c2d 100644 --- a/ocs_ci/ocs/ui/views.py +++ b/ocs_ci/ocs/ui/views.py @@ -668,6 +668,14 @@ "//div[@class='odf-onboarding-modal__text-area']", By.XPATH, ), + "custom_quota": ("storage-quota-custom", By.ID), + "quota_value": ("//input[@type='number']", By.XPATH), + "choose_units": ( + "//div[contains(@class, 'request-size-input__unit')]/button", + By.XPATH, + ), + "quota_ti": ("//li[@id='Ti']", By.XPATH), + "confirm_generation": ("//button[@data-test-id='confirm-action']", By.XPATH), "close_token_modal": ("//button[@aria-label='Close']", By.XPATH), } From 4f31bd3f898cfa6768f043e2a6525be69412957b Mon Sep 17 00:00:00 2001 From: Elena Bondarenko Date: Tue, 3 Sep 2024 17:14:19 +0200 Subject: [PATCH 02/11] Add test for token generation with quota Signed-off-by: Elena Bondarenko --- ocs_ci/ocs/ui/page_objects/storage_clients.py | 7 ++-- tests/functional/ui/test_provider_client.py | 35 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 tests/functional/ui/test_provider_client.py diff --git a/ocs_ci/ocs/ui/page_objects/storage_clients.py b/ocs_ci/ocs/ui/page_objects/storage_clients.py index cd9259b167b..02c59e1768c 100644 --- a/ocs_ci/ocs/ui/page_objects/storage_clients.py +++ b/ocs_ci/ocs/ui/page_objects/storage_clients.py @@ -15,7 +15,8 @@ def __init__(self): def generate_client_onboarding_ticket(self, quota_value=None, quota_tib=None): """ - Generate a client onboarding ticket + Generate a client onboarding ticket. + Starting with version 4.17, client quota can be specified Args: quota_value (int): client's quota in GiB or TiB, unlimited if not defined @@ -24,8 +25,10 @@ def generate_client_onboarding_ticket(self, quota_value=None, quota_tib=None): Returns: str: onboarding_key """ + logger.info("Generating onboarding ticket") self.do_click(self.storage_clients_loc["generate_client_onboarding_ticket"]) if quota_value: + logger.info("Setting client cluster quota") self.do_click(self.storage_clients_loc["custom_quota"]) self.do_clear( locator=self.storage_clients_loc["quota_value"], @@ -37,7 +40,7 @@ def generate_client_onboarding_ticket(self, quota_value=None, quota_tib=None): if quota_tib: self.do_click(self.storage_clients_loc["choose_units"]) self.do_click(self.storage_clients_loc["quota_ti"]) - + logger.info("Confirming token generation") self.do_click(self.storage_clients_loc["confirm_generation"]) onboarding_key = self.get_element_text( self.storage_clients_loc["onboarding_key"] diff --git a/tests/functional/ui/test_provider_client.py b/tests/functional/ui/test_provider_client.py new file mode 100644 index 00000000000..ab2fc88bcc0 --- /dev/null +++ b/tests/functional/ui/test_provider_client.py @@ -0,0 +1,35 @@ +import logging + +from ocs_ci.ocs import constants +from ocs_ci.framework.testlib import ( + skipif_ocs_version, + ManageTest, + tier1, + skipif_ocp_version, + runs_on_provider, + black_squad, + hci_provider_required, +) +from ocs_ci.ocs.ui.page_objects.page_navigator import PageNavigator + + +logger = logging.getLogger(__name__) + + +@tier1 +@black_squad +@skipif_ocs_version("<4.17") +@skipif_ocp_version("<4.17") +@runs_on_provider +@hci_provider_required +class TestOnboardingTokenGenerationWithQuota(ManageTest): + """ + Test onboarding token generation when quota is specified + """ + + storage_clients = PageNavigator().nav_to_storageclients_page() + token = storage_clients.generate_client_onboarding_ticket( + quota_value=2, quota_tib=True + ) + logger.info(f"Token generated. It begins with {token[:20]}") + assert len(token) > 20, "Token is too short" From aeb51eb436ff1df740f18794f6c3626e2803b6cc Mon Sep 17 00:00:00 2001 From: Elena Bondarenko Date: Tue, 22 Oct 2024 14:52:48 +0200 Subject: [PATCH 03/11] Add quota tests Signed-off-by: Elena Bondarenko --- ocs_ci/ocs/resources/storageconsumer.py | 18 ++++ ocs_ci/ocs/ui/page_objects/storage_clients.py | 88 +++++++++++++++++++ ocs_ci/ocs/ui/views.py | 18 +++- tests/functional/ui/test_provider_client.py | 57 ++++++++++-- 4 files changed, 174 insertions(+), 7 deletions(-) diff --git a/ocs_ci/ocs/resources/storageconsumer.py b/ocs_ci/ocs/resources/storageconsumer.py index 1cb41836633..d4c28da85e9 100644 --- a/ocs_ci/ocs/resources/storageconsumer.py +++ b/ocs_ci/ocs/resources/storageconsumer.py @@ -120,3 +120,21 @@ def get_heartbeat_cronjob(self): if job["metadata"]["name"].endswith("status-reporter") ][0] return cronjob + + +def get_all_client_clusters(): + """ + Get client cluster names of all storage consumers + + Returns: + array: names of client clusters + """ + ocp_storageconsumers = ocp.OCP( + kind=constants.STORAGECONSUMER, + namespace=config.cluster_ctx.ENV_DATA["cluster_namespace"], + ) + cluster_names = [] + storageconsumers_data = ocp_storageconsumers.get().get("items") + for storageconsumer in storageconsumers_data: + cluster_names.append(storageconsumer["status"]["client"]["clusterName"]) + return cluster_names diff --git a/ocs_ci/ocs/ui/page_objects/storage_clients.py b/ocs_ci/ocs/ui/page_objects/storage_clients.py index 02c59e1768c..5db6f05daf6 100644 --- a/ocs_ci/ocs/ui/page_objects/storage_clients.py +++ b/ocs_ci/ocs/ui/page_objects/storage_clients.py @@ -62,3 +62,91 @@ def close_onboarding_token_modal(self): Close the onboarding token modal """ self.do_click(self.storage_clients_loc["close_token_modal"]) + + def find_client_cluster_index(self, client_cluster_name): + """ + Find the index of the cluster on Storage clients page + Filtering clients by name isn't working: https://bugzilla.redhat.com/show_bug.cgi?id=2317212 + + Args: + client_cluster_name(str): name of the hosted cluster + + Returns: + int: index of the cluster on Storage Clients page + + """ + all_names = [ + element.text + for element in self.get_elements(self.storage_clients_loc["cluster_name"]) + ] + for index in range(len(all_names)): + if client_cluster_name in all_names[index]: + logger.info(f"Storage client {client_cluster_name} has index {index}") + return index + logger.error( + f"Storage client with cluster name {client_cluster_name} not found" + ) + + def get_client_quota_from_ui(self, client_cluster_name): + """ + Get client's quota from Storage Client's page + Args: + client_cluster_name(str): name of the client cluster + Returns: + str: quota of the client + """ + client_index = self.find_client_cluster_index(client_cluster_name) + quota_element = self.get_elements(self.storage_clients_loc["client_quota"])[ + client_index + ] + return quota_element.text + + def edit_quota( + self, client_cluster_name, new_value=None, new_units=False, increase_by_one=True + ): + """ + Edit client's storage quota + + Args: + client_cluster_name(str): name of the client cluster + new_value(int): new value of the quota + new_units(bool): True if units need to be changed, False otherwise + increase_by_one(bool): True if quota needs to be increased by 1, False otherwise + + Returns: + True if quota change was successful + False otherwise + """ + client_index = self.find_client_cluster_index(client_cluster_name) + self.do_click( + self.get_elements(self.storage_clients_loc["client_kebab_menu"])[ + client_index + ] + ) + try: + self.do_click(self.storage_clients_loc["edit_quota"]) + except: + logger.info("Quota changes not possble") + return False + if increase_by_one: + self.do_click(self.storage_clients_loc["quota_increment"]) + logger.info("Quota increased by 1") + else: + if not new_value: + logger.error("New quota value not provided") + return False + else: + self.clear_with_ctrl_a_del(self.storage_clients_loc["new_quota"]) + self.do_send_keys(self.storage_clients_loc["new_quota"], text=new_value) + logger.info(f"Quota value changed to {new_value}") + if new_units: + self.do_click(self.storage_clients_loc["unit_change_button"]) + self.do_click(self.storage_clients_loc["units_ti"]) + logger.info("Quota units changed to Ti") + try: + self.do_click(self.storage_clients_loc["confirm_quota_change"]) + logger.info("Quota changes saved") + return True + except: + logger.info("Quota changes could not be saved") + return False diff --git a/ocs_ci/ocs/ui/views.py b/ocs_ci/ocs/ui/views.py index 49ea9e49c2d..261664de840 100644 --- a/ocs_ci/ocs/ui/views.py +++ b/ocs_ci/ocs/ui/views.py @@ -668,7 +668,6 @@ "//div[@class='odf-onboarding-modal__text-area']", By.XPATH, ), - "custom_quota": ("storage-quota-custom", By.ID), "quota_value": ("//input[@type='number']", By.XPATH), "choose_units": ( "//div[contains(@class, 'request-size-input__unit')]/button", @@ -677,6 +676,23 @@ "quota_ti": ("//li[@id='Ti']", By.XPATH), "confirm_generation": ("//button[@data-test-id='confirm-action']", By.XPATH), "close_token_modal": ("//button[@aria-label='Close']", By.XPATH), + "client_name": ("name", By.ID), + "cluster_name": ("clusterName", By.ID), + "client_quota": ("storageQuota", By.ID), + "custom_quota": ("storage-quota-custom", By.ID), + "client_kebab_menu": ("//button[@data-test='kebab-button']", By.XPATH), + "edit_quota": ("Edit Resource", By.ID), + "quota_decrement": ("button[aria-label='Decrement']", By.CSS_SELECTOR), + "quota_increment": ("button[aria-label='Increment']", By.CSS_SELECTOR), + "new_quota": ("input[type=number]", By.CSS_SELECTOR), + "unit_change_button": ( + "//div[@class='pf-v5-c-dropdown request-size-input__unit']/button", + By.XPATH, + ), + "units_ti": ("li[id=Ti]", by.CSS_SELECTOR), + "storage_available": ("//span[@data-test='status-text']", By.XPATH), + "quota_decreased_alert": ("//h4[@class='pf-v5-c-alert__title']", By.XPATH), + "confirm_quota_change": ("//button[@data-test-id='confirm-action']", By.XPATH), } page_nav = { diff --git a/tests/functional/ui/test_provider_client.py b/tests/functional/ui/test_provider_client.py index ab2fc88bcc0..9cdc11b1b51 100644 --- a/tests/functional/ui/test_provider_client.py +++ b/tests/functional/ui/test_provider_client.py @@ -10,6 +10,7 @@ black_squad, hci_provider_required, ) +from ocs_ci.ocs.resources import storageconsumer from ocs_ci.ocs.ui.page_objects.page_navigator import PageNavigator @@ -27,9 +28,53 @@ class TestOnboardingTokenGenerationWithQuota(ManageTest): Test onboarding token generation when quota is specified """ - storage_clients = PageNavigator().nav_to_storageclients_page() - token = storage_clients.generate_client_onboarding_ticket( - quota_value=2, quota_tib=True - ) - logger.info(f"Token generated. It begins with {token[:20]}") - assert len(token) > 20, "Token is too short" + def test_token_generation_with_quota(self, quota_value=2, quota_tib=True): + storage_clients = PageNavigator().nav_to_storageclients_page() + token = storage_clients.generate_client_onboarding_ticket( + quota_value=quota_value, quota_tib=quota_tib + ) + logger.info(f"Token generated. It begins with {token[:20]}") + assert len(token) > 20, "Token is too short" + + def test_quota_decrease_blocked(self): + """ + Test that quota cannot be increased for a client: + if a client has unlimited quota, it cannot be changed. + If a client has limited quota, the new value cannot be lower + """ + storage_clients_page = PageNavigator().nav_to_storageclients_page() + client_clusters = storageconsumer.get_all_client_clusters() + for client in client_clusters: + quota = storage_clients_page.get_client_quota_from_ui(client) + if quota == "Unlimited": + assert not storage_clients_page.edit_quota( + client_cluster_name=client, increase_by_one=True + ) + else: + new_quota = int(quota) - 1 + assert not storage_clients_page.edit_quota( + client_cluster_name=client, + increase_by_one=False, + new_value=new_quota, + ) + + def test_quota_increase(self): + """ + Test that quota can be increased in the UI for every client with limited quota + both by manually setting a new value and by clicking Increment + + """ + storage_clients_page = PageNavigator().nav_to_storageclients_page() + client_clusters = storageconsumer.get_all_client_clusters() + for client in client_clusters: + quota = storage_clients_page.get_client_quota_from_ui(client) + if quota != "Unlimited": + new_quota = int(quota) + 1 + assert storage_clients_page.edit_quota( + client_cluster_name=client, + increase_by_one=False, + new_value=new_quota, + ) + assert storage_clients_page.edit_quota( + client_cluster_name=client, increase_by_one=True + ) From 735baab9ec24e9ed7b831221f271729757292f7d Mon Sep 17 00:00:00 2001 From: Elena Bondarenko Date: Tue, 22 Oct 2024 16:31:47 +0200 Subject: [PATCH 04/11] Fix tox Signed-off-by: Elena Bondarenko --- ocs_ci/ocs/ui/page_objects/storage_clients.py | 7 +++++-- ocs_ci/ocs/ui/views.py | 2 +- tests/functional/ui/test_provider_client.py | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ocs_ci/ocs/ui/page_objects/storage_clients.py b/ocs_ci/ocs/ui/page_objects/storage_clients.py index 5db6f05daf6..dfe5ce130a8 100644 --- a/ocs_ci/ocs/ui/page_objects/storage_clients.py +++ b/ocs_ci/ocs/ui/page_objects/storage_clients.py @@ -1,4 +1,5 @@ import logging +from selenium.common.exceptions import WebDriverException from ocs_ci.ocs.ui.base_ui import take_screenshot, copy_dom, BaseUI @@ -125,7 +126,8 @@ def edit_quota( ) try: self.do_click(self.storage_clients_loc["edit_quota"]) - except: + except WebDriverException as e: + logger.info(e) logger.info("Quota changes not possble") return False if increase_by_one: @@ -147,6 +149,7 @@ def edit_quota( self.do_click(self.storage_clients_loc["confirm_quota_change"]) logger.info("Quota changes saved") return True - except: + except WebDriverException as e: + logger.info(e) logger.info("Quota changes could not be saved") return False diff --git a/ocs_ci/ocs/ui/views.py b/ocs_ci/ocs/ui/views.py index 261664de840..2864aeab000 100644 --- a/ocs_ci/ocs/ui/views.py +++ b/ocs_ci/ocs/ui/views.py @@ -689,7 +689,7 @@ "//div[@class='pf-v5-c-dropdown request-size-input__unit']/button", By.XPATH, ), - "units_ti": ("li[id=Ti]", by.CSS_SELECTOR), + "units_ti": ("li[id=Ti]", By.CSS_SELECTOR), "storage_available": ("//span[@data-test='status-text']", By.XPATH), "quota_decreased_alert": ("//h4[@class='pf-v5-c-alert__title']", By.XPATH), "confirm_quota_change": ("//button[@data-test-id='confirm-action']", By.XPATH), diff --git a/tests/functional/ui/test_provider_client.py b/tests/functional/ui/test_provider_client.py index 9cdc11b1b51..0dd444aa50a 100644 --- a/tests/functional/ui/test_provider_client.py +++ b/tests/functional/ui/test_provider_client.py @@ -1,6 +1,6 @@ import logging -from ocs_ci.ocs import constants +# from ocs_ci.ocs import constants from ocs_ci.framework.testlib import ( skipif_ocs_version, ManageTest, From d2f578cbf5a64905a8a3971c37c825bc6ebb1c48 Mon Sep 17 00:00:00 2001 From: Elena Bondarenko Date: Fri, 8 Nov 2024 19:07:19 +0100 Subject: [PATCH 05/11] Add test for capacity Signed-off-by: Elena Bondarenko --- ocs_ci/ocs/ui/page_objects/storage_clients.py | 18 ++++++++++++++++++ ocs_ci/ocs/ui/views.py | 1 + tests/functional/ui/test_provider_client.py | 16 ++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/ocs_ci/ocs/ui/page_objects/storage_clients.py b/ocs_ci/ocs/ui/page_objects/storage_clients.py index dfe5ce130a8..9c5af6f72e8 100644 --- a/ocs_ci/ocs/ui/page_objects/storage_clients.py +++ b/ocs_ci/ocs/ui/page_objects/storage_clients.py @@ -153,3 +153,21 @@ def edit_quota( logger.info(e) logger.info("Quota changes could not be saved") return False + + def get_available_storage_from_quota_edit_popup(self): + """ + Get the value of available storage + from Edit quota popup + + Returns: + str: available storage + """ + self.do_click( + self.get_elements(self.storage_clients_loc["client_kebab_menu"])[0] + ) + av_capacity_text = self.get_element_text( + self.storage_clients_loc["available_storage"] + ) + # Text is expected to be 'Available capacity (ocs-storagecluster): N TiB' + split_capacity_text = av_capacity_text.split(" ") + return f"{split_capacity_text[-2]} {split_capacity_text[-1]}" diff --git a/ocs_ci/ocs/ui/views.py b/ocs_ci/ocs/ui/views.py index 2864aeab000..8ccb72cd5ca 100644 --- a/ocs_ci/ocs/ui/views.py +++ b/ocs_ci/ocs/ui/views.py @@ -693,6 +693,7 @@ "storage_available": ("//span[@data-test='status-text']", By.XPATH), "quota_decreased_alert": ("//h4[@class='pf-v5-c-alert__title']", By.XPATH), "confirm_quota_change": ("//button[@data-test-id='confirm-action']", By.XPATH), + "available_storage": (), } page_nav = { diff --git a/tests/functional/ui/test_provider_client.py b/tests/functional/ui/test_provider_client.py index 0dd444aa50a..2e6ba8e60ec 100644 --- a/tests/functional/ui/test_provider_client.py +++ b/tests/functional/ui/test_provider_client.py @@ -1,6 +1,7 @@ import logging # from ocs_ci.ocs import constants +import ocs_ci.ocs.resources.pod as pod from ocs_ci.framework.testlib import ( skipif_ocs_version, ManageTest, @@ -78,3 +79,18 @@ def test_quota_increase(self): assert storage_clients_page.edit_quota( client_cluster_name=client, increase_by_one=True ) + + def test_available_capacity_in_quota_edit_popup(self): + """ + Test that Quota edit popup shows correct value of + Available capacity + """ + storage_clients_page = PageNavigator().nav_to_storageclients_page() + ceph_pod = pod.get_ceph_tools_pod() + ceph_status = ceph_pod.exec_ceph_cmd(ceph_cmd="ceph df") + ceph_capacity_bytes = ceph_status["stats"]["total_avail_bytes"] + ui_capacity = storage_clients_page.get_available_storage_from_quota_edit_popup() + if "TiB" in ui_capacity: + ui_capacity_num = float(ui_capacity.split(" ")[0]) + ceph_capacity_tib = ceph_capacity_bytes / 2**40 + assert (ui_capacity_num - ceph_capacity_tib) ** 2 < 0.1 From b67f73bff9e801c0e09dab949531c8553df15c1c Mon Sep 17 00:00:00 2001 From: Elena Bondarenko Date: Thu, 14 Nov 2024 13:02:35 +0100 Subject: [PATCH 06/11] Add test for quota values in UI Signed-off-by: Elena Bondarenko --- ocs_ci/ocs/resources/storageconsumer.py | 20 ++++++++++++++++++++ tests/functional/ui/test_provider_client.py | 12 ++++++++++++ 2 files changed, 32 insertions(+) diff --git a/ocs_ci/ocs/resources/storageconsumer.py b/ocs_ci/ocs/resources/storageconsumer.py index d4c28da85e9..a3b3853d9b4 100644 --- a/ocs_ci/ocs/resources/storageconsumer.py +++ b/ocs_ci/ocs/resources/storageconsumer.py @@ -138,3 +138,23 @@ def get_all_client_clusters(): for storageconsumer in storageconsumers_data: cluster_names.append(storageconsumer["status"]["client"]["clusterName"]) return cluster_names + + +def get_storageconsumer_quota(cluster_name): + """ + Get the quota value from storageconsumer details + Args: + clustername(str): name of the client cluster + Returns" + str: quota value + """ + ocp_storageconsumers = ocp.OCP( + kind=constants.STORAGECONSUMER, + namespace=config.cluster_ctx.ENV_DATA["cluster_namespace"], + ) + storageconsumers_data = ocp_storageconsumers.get().get("items") + for storageconsumer in storageconsumers_data: + if storageconsumer["status"]["client"]["clusterName"] == cluster_name: + if "storageQuotaInGiB" not in storageconsumer["spec"]: + return "Unlimited" + return storageconsumer["spec"]["storageQuotaInGiB"] diff --git a/tests/functional/ui/test_provider_client.py b/tests/functional/ui/test_provider_client.py index 2e6ba8e60ec..1e04f0d84b3 100644 --- a/tests/functional/ui/test_provider_client.py +++ b/tests/functional/ui/test_provider_client.py @@ -94,3 +94,15 @@ def test_available_capacity_in_quota_edit_popup(self): ui_capacity_num = float(ui_capacity.split(" ")[0]) ceph_capacity_tib = ceph_capacity_bytes / 2**40 assert (ui_capacity_num - ceph_capacity_tib) ** 2 < 0.1 + + def test_quota_values_in_ui(self): + """ + Test that all storage clients have correct quota value in the UI + """ + storage_clients_page = PageNavigator().nav_to_storageclients_page() + client_clusters = storageconsumer.get_all_client_clusters() + for client in client_clusters: + quota_ui = storage_clients_page.get_client_quota_from_ui(client) + quota_cli = storageconsumer.get_storageconsumer_quota(client) + assert quota_ui == quota_cli, f"Quota in the UI: {quota_ui}, " + "quota in the CLI: {quota_cli}" From 6c727562112b3ce0d75d7fecbb9414d92aa37049 Mon Sep 17 00:00:00 2001 From: Elena Bondarenko Date: Tue, 19 Nov 2024 16:23:42 +0100 Subject: [PATCH 07/11] Fix tests Signed-off-by: Elena Bondarenko --- tests/functional/ui/test_provider_client.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/functional/ui/test_provider_client.py b/tests/functional/ui/test_provider_client.py index 1e04f0d84b3..28d743b3363 100644 --- a/tests/functional/ui/test_provider_client.py +++ b/tests/functional/ui/test_provider_client.py @@ -29,7 +29,9 @@ class TestOnboardingTokenGenerationWithQuota(ManageTest): Test onboarding token generation when quota is specified """ - def test_token_generation_with_quota(self, quota_value=2, quota_tib=True): + def test_token_generation_with_quota( + self, setup_ui_class, quota_value=2, quota_tib=True + ): storage_clients = PageNavigator().nav_to_storageclients_page() token = storage_clients.generate_client_onboarding_ticket( quota_value=quota_value, quota_tib=quota_tib @@ -37,7 +39,7 @@ def test_token_generation_with_quota(self, quota_value=2, quota_tib=True): logger.info(f"Token generated. It begins with {token[:20]}") assert len(token) > 20, "Token is too short" - def test_quota_decrease_blocked(self): + def test_quota_decrease_blocked(self, setup_ui_class): """ Test that quota cannot be increased for a client: if a client has unlimited quota, it cannot be changed. @@ -59,7 +61,7 @@ def test_quota_decrease_blocked(self): new_value=new_quota, ) - def test_quota_increase(self): + def test_quota_increase(self, setup_ui_class): """ Test that quota can be increased in the UI for every client with limited quota both by manually setting a new value and by clicking Increment @@ -80,7 +82,7 @@ def test_quota_increase(self): client_cluster_name=client, increase_by_one=True ) - def test_available_capacity_in_quota_edit_popup(self): + def test_available_capacity_in_quota_edit_popup(self, setup_ui_class): """ Test that Quota edit popup shows correct value of Available capacity @@ -95,7 +97,7 @@ def test_available_capacity_in_quota_edit_popup(self): ceph_capacity_tib = ceph_capacity_bytes / 2**40 assert (ui_capacity_num - ceph_capacity_tib) ** 2 < 0.1 - def test_quota_values_in_ui(self): + def test_quota_values_in_ui(self, setup_ui_class): """ Test that all storage clients have correct quota value in the UI """ From 341cf1371fac4663744136e6a7ebf5ba9d8fb78f Mon Sep 17 00:00:00 2001 From: Elena Bondarenko Date: Mon, 6 Jan 2025 16:40:15 +0100 Subject: [PATCH 08/11] Add quota alerting Signed-off-by: Elena Bondarenko --- .../client_bm_upi_1az_rhcos_nvme_2w.yaml | 1 + .../client_bm_upi_1az_rhcos_nvme_3w.yaml | 1 + .../hypershift_client_bm_2w.yaml | 1 + .../hypershift_client_bm_3w.yaml | 1 + ocs_ci/framework/__init__.py | 27 +++++++++ ocs_ci/ocs/resources/storageconsumer.py | 27 +++++++++ ocs_ci/ocs/ui/page_objects/storage_clients.py | 2 +- tests/functional/monitoring/conftest.py | 59 +++++++++++++++++++ 8 files changed, 118 insertions(+), 1 deletion(-) diff --git a/conf/deployment/fusion_hci_pc/client_bm_upi_1az_rhcos_nvme_2w.yaml b/conf/deployment/fusion_hci_pc/client_bm_upi_1az_rhcos_nvme_2w.yaml index 9867702a59f..fe536705ecf 100644 --- a/conf/deployment/fusion_hci_pc/client_bm_upi_1az_rhcos_nvme_2w.yaml +++ b/conf/deployment/fusion_hci_pc/client_bm_upi_1az_rhcos_nvme_2w.yaml @@ -7,5 +7,6 @@ ENV_DATA: worker_replicas: 2 mon_type: 'hostpath' osd_type: 'nvme' + quota: 'unrestricted' REPORTING: ocs_must_gather_image: "quay.io/rhceph-dev/ocs-must-gather" diff --git a/conf/deployment/fusion_hci_pc/client_bm_upi_1az_rhcos_nvme_3w.yaml b/conf/deployment/fusion_hci_pc/client_bm_upi_1az_rhcos_nvme_3w.yaml index 5ec19d69af8..53de90f7503 100644 --- a/conf/deployment/fusion_hci_pc/client_bm_upi_1az_rhcos_nvme_3w.yaml +++ b/conf/deployment/fusion_hci_pc/client_bm_upi_1az_rhcos_nvme_3w.yaml @@ -7,5 +7,6 @@ ENV_DATA: worker_replicas: 3 mon_type: 'hostpath' osd_type: 'nvme' + quota: 'unrestricted' REPORTING: ocs_must_gather_image: "quay.io/rhceph-dev/ocs-must-gather" diff --git a/conf/deployment/fusion_hci_pc/hypershift_client_bm_2w.yaml b/conf/deployment/fusion_hci_pc/hypershift_client_bm_2w.yaml index 73e942d82b8..f389510a9e0 100644 --- a/conf/deployment/fusion_hci_pc/hypershift_client_bm_2w.yaml +++ b/conf/deployment/fusion_hci_pc/hypershift_client_bm_2w.yaml @@ -5,6 +5,7 @@ ENV_DATA: worker_replicas: 2 mon_type: 'hostpath' osd_type: 'ssd' + quota: 'unrestricted' REPORTING: ocs_must_gather_image: "quay.io/ocs-dev/ocs-must-gather" ocs_must_gather_latest_tag: 'latest' diff --git a/conf/deployment/fusion_hci_pc/hypershift_client_bm_3w.yaml b/conf/deployment/fusion_hci_pc/hypershift_client_bm_3w.yaml index 4b41d399e51..604c640c9d1 100644 --- a/conf/deployment/fusion_hci_pc/hypershift_client_bm_3w.yaml +++ b/conf/deployment/fusion_hci_pc/hypershift_client_bm_3w.yaml @@ -5,6 +5,7 @@ ENV_DATA: worker_replicas: 3 mon_type: 'hostpath' osd_type: 'ssd' + quota: 'unrestricted' REPORTING: ocs_must_gather_image: "quay.io/ocs-dev/ocs-must-gather" ocs_must_gather_latest_tag: 'latest' diff --git a/ocs_ci/framework/__init__.py b/ocs_ci/framework/__init__.py index be332687753..4ed023303bc 100644 --- a/ocs_ci/framework/__init__.py +++ b/ocs_ci/framework/__init__.py @@ -290,6 +290,18 @@ def get_consumer_indexes_list(self): return consumer_indexes_list + def get_consumer_with_resticted_quota_index(self): + """ + Get the consumer cluster index + of the first consumer which has quota restrictions + """ + consumer_indexes = self.get_consumer_indexes_list() + for index in consumer_indexes: + cluster = self.clusters[index] + if cluster.ENV_DATA["quota"] != "unlimited": + return index + raise ClusterNotFoundException("Didn't find any consumer with resticted quota") + def get_cluster_index_by_name(self, cluster_name): """ Get the cluster index by the cluster name @@ -497,6 +509,21 @@ def __init__(self): switch_index = config.cur_index super().__init__(switch_index) + class RunWithRestrictedQuotaConsumerConfigContextIfAvailable(RunWithConfigContext): + """ + Context manager that makes sure that a given code block is executed + on a Consumer with restricted quota. + If such config is not available, then run with current config context. + """ + + def __init__(self): + try: + switch_index = config.get_consumer_with_resticted_quota_index() + except ClusterNotFoundException: + logger.DEBUG("No consumer with restricted quota found") + switch_index = config.cur_index + super().__init__(switch_index) + class RunWithFirstConsumerConfigContextIfAvailable(RunWithConfigContext): """ Context manager that makes sure that a given code block is executed on First consumer. diff --git a/ocs_ci/ocs/resources/storageconsumer.py b/ocs_ci/ocs/resources/storageconsumer.py index a3b3853d9b4..9fc4ab98fed 100644 --- a/ocs_ci/ocs/resources/storageconsumer.py +++ b/ocs_ci/ocs/resources/storageconsumer.py @@ -6,6 +6,7 @@ from ocs_ci.framework import config from ocs_ci.ocs import constants, ocp +from ocs_ci.helpers import helpers from ocs_ci.ocs.resources.ocs import OCS from ocs_ci.utility.utils import exec_cmd @@ -121,6 +122,32 @@ def get_heartbeat_cronjob(self): ][0] return cronjob + def fill_up_quota_percentage(self, percentage, quota=None): + """ + Create a PVC of such size that the correct percentage of quota is used + + Returns: + PVC object + """ + pvc_name = f"pvc-quota-{percentage}" + if not quota: + quota = config.ENV_DATA["quota"] + quota_value = quota.split(" ")[0] + quota_units = quota.split(" ")[1] + pvc_size_int = quota_value * percentage // 100 + pvc_size = f"{pvc_size_int}{quota_units}" + rbd_storageclass = helpers.default_storage_class(constants.CEPHBLOCKPOOL) + pvc_obj = helpers.create_pvc( + pvc_name=pvc_name, + sc_name=rbd_storageclass, + namespace="default", + size=pvc_size, + do_reload=False, + access_mode=constants.ACCESS_MODE_RWO, + volume_mode=constants.VOLUME_MODE_BLOCK, + ) + return pvc_obj + def get_all_client_clusters(): """ diff --git a/ocs_ci/ocs/ui/page_objects/storage_clients.py b/ocs_ci/ocs/ui/page_objects/storage_clients.py index 9c5af6f72e8..7eedd83802b 100644 --- a/ocs_ci/ocs/ui/page_objects/storage_clients.py +++ b/ocs_ci/ocs/ui/page_objects/storage_clients.py @@ -1,6 +1,6 @@ import logging from selenium.common.exceptions import WebDriverException - +from ocs_ci.framework import config from ocs_ci.ocs.ui.base_ui import take_screenshot, copy_dom, BaseUI logger = logging.getLogger(__name__) diff --git a/tests/functional/monitoring/conftest.py b/tests/functional/monitoring/conftest.py index 33aacc2ab03..4f3405ef607 100644 --- a/tests/functional/monitoring/conftest.py +++ b/tests/functional/monitoring/conftest.py @@ -1209,3 +1209,62 @@ def teardown(): teardown() return measured_op + + +@pytest.fixture +def measure_fill_up_client_quota( + request, + measurement_dir, + threading_lock, +): + """ + Create PVCs on the client cluster where quota is restricted + to reach 85% of the quota, measure the time when it was created and + alerts that were triggered during this event. + + Returns: + dict: Contains information about `start` and `stop` time + for creating and then deleting the PVC + """ + logger.info("Switch to client cluster with restricted quota") + with config.get_consumer_with_resticted_quota_index(): + client_cluster = config.cluster_ctx.MULTICLUSTER["multicluster_index"] + logger.info(f"Client cluster key: {client_cluster}") + cluster_id = exec_cmd( + "oc get clusterversion version -o jsonpath='{.spec.clusterID}'" + ).stdout.decode("utf-8") + client_name = f"storageconsumer-{cluster_id}" + client = storageconsumer.StorageConsumer( + client_name, consumer_context=client_cluster + ) + pvc = None + + def use_up_quota_80_percent(): + nonlocal pvc + nonlocal client + quota = config.ENV_DATA["quota"] + pvc = client.fill_up_quota_percentage(percentage=80, quota=quota) + # run_time of operation + run_time = 60 * 3 + logger.info(f"Waiting for {run_time} seconds") + time.sleep(run_time) + return + + def teardown(): + nonlocal pvc + with config.get_consumer_with_resticted_quota_index(): + pvc.ocp.wait_for_delete(resource_name=pvc.name, timeout=180) + + request.addfinalizer(teardown) + + test_file = os.path.join(measurement_dir, "measure_change_client_version.json") + measured_op = measure_operation( + use_up_quota_80_percent, + test_file, + threading_lock=threading_lock, + metadata={"client_name": client_name}, + ) + + teardown() + + return measured_op From 6bc8d09f822d366022ce450d73ae1e1499ca9975 Mon Sep 17 00:00:00 2001 From: Elena Bondarenko Date: Mon, 6 Jan 2025 16:50:44 +0100 Subject: [PATCH 09/11] Add quota alerting test Signed-off-by: Elena Bondarenko --- .../prometheus/alerts/test_provider_client.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/functional/monitoring/prometheus/alerts/test_provider_client.py b/tests/functional/monitoring/prometheus/alerts/test_provider_client.py index 9a6c42b4fff..8270094539d 100644 --- a/tests/functional/monitoring/prometheus/alerts/test_provider_client.py +++ b/tests/functional/monitoring/prometheus/alerts/test_provider_client.py @@ -89,6 +89,36 @@ def test_change_client_ocs_version_and_stop_heartbeat( ) +@blue_squad +@tier4c +@runs_on_provider +@hci_provider_and_client_required +def test_quota_fillup_80_alert(measure_fill_up_client_quota, threading_lock): + api = prometheus.PrometheusAPI(threading_lock=threading_lock) + + # get alerts from time when manager deployment was scaled down + alerts = measure_fill_up_client_quota.get("prometheus_alerts") + client_name = measure_fill_up_client_quota.get("metadata").get("client_name") + cluster_namespace = config.ENV_DATA["cluster_namespace"] + cluster_name = config.ENV_DATA["storage_cluster_name"] + target_alerts = [] + states = ["firing"] + + for target_alert in target_alerts: + prometheus.check_alert_list( + label=target_alert["label"], + msg=target_alert["msg"], + alerts=alerts, + states=states, + severity=target_alert["severity"], + ) + api.check_alert_cleared( + label=target_alert["label"], + measure_end_time=measure_fill_up_client_quota.get("stop"), + time_min=300, + ) + + def teardown_module(): ocs_obj = OCP() ocs_obj.login_as_sa() From cd3b61bebc0607239ef76840430378b630bf77d9 Mon Sep 17 00:00:00 2001 From: Elena Bondarenko Date: Mon, 6 Jan 2025 16:57:13 +0100 Subject: [PATCH 10/11] Fix tox Signed-off-by: Elena Bondarenko --- ocs_ci/ocs/ui/page_objects/storage_clients.py | 1 - .../monitoring/prometheus/alerts/test_provider_client.py | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/ocs_ci/ocs/ui/page_objects/storage_clients.py b/ocs_ci/ocs/ui/page_objects/storage_clients.py index 7eedd83802b..24931be8d7a 100644 --- a/ocs_ci/ocs/ui/page_objects/storage_clients.py +++ b/ocs_ci/ocs/ui/page_objects/storage_clients.py @@ -1,6 +1,5 @@ import logging from selenium.common.exceptions import WebDriverException -from ocs_ci.framework import config from ocs_ci.ocs.ui.base_ui import take_screenshot, copy_dom, BaseUI logger = logging.getLogger(__name__) diff --git a/tests/functional/monitoring/prometheus/alerts/test_provider_client.py b/tests/functional/monitoring/prometheus/alerts/test_provider_client.py index 8270094539d..1a9e1b237b1 100644 --- a/tests/functional/monitoring/prometheus/alerts/test_provider_client.py +++ b/tests/functional/monitoring/prometheus/alerts/test_provider_client.py @@ -98,9 +98,7 @@ def test_quota_fillup_80_alert(measure_fill_up_client_quota, threading_lock): # get alerts from time when manager deployment was scaled down alerts = measure_fill_up_client_quota.get("prometheus_alerts") - client_name = measure_fill_up_client_quota.get("metadata").get("client_name") - cluster_namespace = config.ENV_DATA["cluster_namespace"] - cluster_name = config.ENV_DATA["storage_cluster_name"] + # client_name = measure_fill_up_client_quota.get("metadata").get("client_name") target_alerts = [] states = ["firing"] From e01d2435bd1aeb33fac282248560cb4b5abb434a Mon Sep 17 00:00:00 2001 From: Elena Bondarenko Date: Thu, 9 Jan 2025 16:35:27 +0100 Subject: [PATCH 11/11] Add test for quota utilization Signed-off-by: Elena Bondarenko --- ocs_ci/ocs/ui/page_objects/storage_clients.py | 15 +++++++++++++++ tests/functional/ui/test_provider_client.py | 8 ++++++++ 2 files changed, 23 insertions(+) diff --git a/ocs_ci/ocs/ui/page_objects/storage_clients.py b/ocs_ci/ocs/ui/page_objects/storage_clients.py index 24931be8d7a..a13232b0939 100644 --- a/ocs_ci/ocs/ui/page_objects/storage_clients.py +++ b/ocs_ci/ocs/ui/page_objects/storage_clients.py @@ -170,3 +170,18 @@ def get_available_storage_from_quota_edit_popup(self): # Text is expected to be 'Available capacity (ocs-storagecluster): N TiB' split_capacity_text = av_capacity_text.split(" ") return f"{split_capacity_text[-2]} {split_capacity_text[-1]}" + + def validate_unlimited_quota_utilization_info(self): + """ + Verify that for every client with unlimited quota + utilization column only shows "-" + """ + quota_elements = self.get_elements(self.storage_clients_loc["client_quota"]) + utilization_elements = self.get_elements( + self.storage_clients_loc["quota_utilization"] + ) + for i in len(quota_elements): + if quota_elements[i].text == "Unlimited": + assert ( + utilization_elements[i].text == "-" + ), f"Quota utilization is shown as {utilization_elements[i].text}" diff --git a/tests/functional/ui/test_provider_client.py b/tests/functional/ui/test_provider_client.py index 28d743b3363..b8fc176f0da 100644 --- a/tests/functional/ui/test_provider_client.py +++ b/tests/functional/ui/test_provider_client.py @@ -108,3 +108,11 @@ def test_quota_values_in_ui(self, setup_ui_class): quota_cli = storageconsumer.get_storageconsumer_quota(client) assert quota_ui == quota_cli, f"Quota in the UI: {quota_ui}, " "quota in the CLI: {quota_cli}" + + def test_usage_for_unlimited_quota_clients(self, setup_ui_class): + """ + Test that clients with unlimited storage don't have + quota usage shown on Clients page + """ + storage_clients_page = PageNavigator().nav_to_storageclients_page() + storage_clients_page.validate_unlimited_quota_utilization_info()