From 900a759de9f48be36828704192f2c5532f0cc895 Mon Sep 17 00:00:00 2001 From: ppeng Date: Wed, 30 Apr 2025 15:58:57 -0400 Subject: [PATCH] Add "ceph osd pool ls detail" cmd keywords - Add "ceph osd pool ls detail" keywords - Add "ceph osd pool ls detail" table parser - Add unit_tests for parser "ceph osd pool ls detail" table - Add get_ceph_osd_pool(pool_name) in output - replace return False with raise Change-Id: Ia6345a16c25626bdb4a2e2615654a312270fe936 Signed-off-by: ppeng --- .../ceph/ceph_osd_pool_ls_detail_keywords.py | 86 +++++++++++++++++++ .../ceph_osd_pool_ls_detail_table_parser.py | 65 ++++++++++++++ .../object/ceph_osd_pool_ls_detail_object.py | 64 ++++++++++++++ .../object/ceph_osd_pool_ls_detail_output.py | 77 +++++++++++++++++ unit_tests/parser/ceph/__init__.py | 0 ...ph_osd_pool_ls_detail_table_parser_test.py | 22 +++++ 6 files changed, 314 insertions(+) create mode 100644 keywords/ceph/ceph_osd_pool_ls_detail_keywords.py create mode 100644 keywords/ceph/ceph_osd_pool_ls_detail_table_parser.py create mode 100644 keywords/ceph/object/ceph_osd_pool_ls_detail_object.py create mode 100644 keywords/ceph/object/ceph_osd_pool_ls_detail_output.py create mode 100644 unit_tests/parser/ceph/__init__.py create mode 100644 unit_tests/parser/ceph/ceph_osd_pool_ls_detail_table_parser_test.py diff --git a/keywords/ceph/ceph_osd_pool_ls_detail_keywords.py b/keywords/ceph/ceph_osd_pool_ls_detail_keywords.py new file mode 100644 index 00000000..daf7feee --- /dev/null +++ b/keywords/ceph/ceph_osd_pool_ls_detail_keywords.py @@ -0,0 +1,86 @@ +import time + +from framework.ssh.ssh_connection import SSHConnection +from keywords.base_keyword import BaseKeyword +from keywords.ceph.object.ceph_osd_pool_ls_detail_object import CephOsdPoolLsDetailObject +from keywords.ceph.object.ceph_osd_pool_ls_detail_output import CephOsdPoolLsDetailOutput + + +class CephOsdPoolLsDetailKeywords(BaseKeyword): + """ + Class for ceph osd pool ls detail keywords + """ + + def __init__(self, ssh_connection: SSHConnection): + """ + Constructor + + Args: + ssh_connection (SSHConnection): SSHConnection + """ + self.ssh_connection = ssh_connection + + def get_ceph_osd_pool_ls_detail_list(self) -> list[CephOsdPoolLsDetailObject]: + """ + Gets the "ceph osd pool ls detail" command as a list of `CephOsdPoolLsDetailObject` objects. + + Args: None + + Returns: list of 'CephOsdPoolLsDetailObject' objects. + + """ + output = self.ssh_connection.send("ceph osd pool ls detail") + self.validate_success_return_code(self.ssh_connection) + ceph_osd_pool_ls_detail_objects = CephOsdPoolLsDetailOutput(output).get_ceph_osd_pool_list() + + return ceph_osd_pool_ls_detail_objects + + def wait_for_ceph_osd_pool_replicated_size_update(self, pool_name: str, expected_replicated_size: int, timeout: int = 600) -> bool: + """ + Waits timeout amount of time for "ceph osd pool ls detail" replicated size update + + Args: + pool_name (str): the pool name + expected_replicated_size (int): the expected replicated size value + timeout (int): amount of timeout + + Returns: + bool: True if the pool replicated size is updated as expected + + """ + output = self.ssh_connection.send("ceph osd pool ls detail") + pool_object = CephOsdPoolLsDetailOutput(output).get_ceph_osd_pool(pool_name) + replicated_update_timeout = time.time() + timeout + + while time.time() < replicated_update_timeout: + ceph_osd_pool_replication_size = pool_object.get_replicated_size() + if ceph_osd_pool_replication_size == expected_replicated_size: + return True + time.sleep(10) + + raise ValueError(f"replicated value is {ceph_osd_pool_replication_size}, not as expected value {expected_replicated_size}") + + def wait_for_ceph_osd_pool_min_size_update(self, pool_name: str, expected_min_size: int, timeout: int = 600) -> bool: + """ + Waits timeout amount of time for "ceph osd pool ls detail" min_size update + + Args: + pool_name (str): the pool name + expected_min_size (int): the expected replicated size value + timeout (int): amount of timeout + + Returns: + bool: True if the pool min_size is updated as expected + + """ + output = self.ssh_connection.send("ceph osd pool ls detail") + pool_object = CephOsdPoolLsDetailOutput(output).get_ceph_osd_pool(pool_name) + mini_update_timeout = time.time() + timeout + + while time.time() < mini_update_timeout: + ceph_osd_pool_min_size = pool_object.get_min_size() + if ceph_osd_pool_min_size == expected_min_size: + return True + time.sleep(10) + + raise ValueError(f"replicated value is {ceph_osd_pool_min_size}, not as expected value {expected_min_size}") diff --git a/keywords/ceph/ceph_osd_pool_ls_detail_table_parser.py b/keywords/ceph/ceph_osd_pool_ls_detail_table_parser.py new file mode 100644 index 00000000..485f31c8 --- /dev/null +++ b/keywords/ceph/ceph_osd_pool_ls_detail_table_parser.py @@ -0,0 +1,65 @@ +class CephOsdPoolLsDetailTableParser: + """ + Class for "ceph osd pool ls detail" table parsing. + + The "ceph osd pool ls detail" table is a string formatted as a table, resulting from the execution of the command + 'ceph osd pool ls detail' in a Linux terminal. + This class receives a "ceph osd pool ls detail" table, as shown below, in its constructor and returns a list of dictionaries. + Each of these dictionaries has the following keys: + 1) pool_id; + 2) pool_name; + 3) replicated_size; + 4) min_size; + could create more if needed + + An example of a "ceph osd pool ls detail" table: + + pool 1 '.mgr' replicated size 2 min_size 1 crush_rule 9 object_hash rjenkins pg_num 32 pgp_num 32 autoscale_mode + on last_change 119 lfor 0/0/28 flags hashpspool stripe_width 0 application mgr read_balance_score 1.25 + pool 2 'kube-cephfs-metadata' replicated size 2 min_size 1 crush_rule 11 object_hash rjenkins pg_num 16 pgp_num 16 + autoscale_mode on last_change 122 lfor 0/0/28 flags hashpspool stripe_width 0 pg_autoscale_bias 4 pg_num_min 16 + recovery_priority 5 application cephfs read_balance_score 1.56 + pool 3 'kube-rbd' replicated size 2 min_size 1 crush_rule 10 object_hash rjenkins pg_num 32 pgp_num 32 + autoscale_mode on last_change 121 lfor 0/0/30 flags hashpspool,selfmanaged_snaps stripe_width 0 application rbd + read_balance_score 1.25 + pool 4 'kube-cephfs-data' replicated size 2 min_size 1 crush_rule 12 object_hash rjenkins pg_num 32 pgp_num 32 + autoscale_mode on last_change 123 lfor 0/0/30 flags hashpspool stripe_width 0 application cephfs + read_balance_score 2.03 + '\n' + + Note: 1) The table has no headers. We can learn about the headers through the names of the dictionary keys. + 2) there is '\n' at the end of the table + + """ + + def __init__(self, command_output): + self.command_output = command_output + + def get_output_values_list(self): + """ + Getter for output values list. + + Returns: the output values list as a list of dictionaries with the following keys: + 1) pool_id; + 2) pool_name; + 3) replicated_size; + 4) min_size; + + """ + if not self.command_output: + return [] + + # remove '\n' from output + cleaned_list = [item for item in self.command_output if item != "\n"] + output_values = [] + for line in cleaned_list: + values = line.split() + list_item = { + "pool_id": int(values[1].strip()), + "pool_name": values[2].strip().replace("'", ""), + "replicated_size": int(values[5].strip()), + "min_size": int(values[7].strip()), + } + output_values.append(list_item) + + return output_values diff --git a/keywords/ceph/object/ceph_osd_pool_ls_detail_object.py b/keywords/ceph/object/ceph_osd_pool_ls_detail_object.py new file mode 100644 index 00000000..72c734b1 --- /dev/null +++ b/keywords/ceph/object/ceph_osd_pool_ls_detail_object.py @@ -0,0 +1,64 @@ +class CephOsdPoolLsDetailObject: + """ + Class to handle the data provided by the 'ceph osd pool ls detail' command execution. + + This command generates the + output table shown below, where each object of this class represents a single row in that table. + + 'ceph osd pool ls detail' + pool 1 '.mgr' replicated size 2 min_size 1 crush_rule 9 object_hash rjenkins pg_num 32 pgp_num 32 autoscale_mode on + last_change 119 lfor 0/0/28 flags hashpspool stripe_width 0 application mgr read_balance_score 1.25 + pool 2 'kube-cephfs-metadata' replicated size 2 min_size 1 crush_rule 11 object_hash rjenkins pg_num 16 pgp_num 16 + autoscale_mode on last_change 122 lfor 0/0/28 flags hashpspool stripe_width 0 pg_autoscale_bias 4 pg_num_min 16 + recovery_priority 5 application cephfs read_balance_score 1.56 + pool 3 'kube-rbd' replicated size 2 min_size 1 crush_rule 10 object_hash rjenkins pg_num 32 pgp_num 32 + autoscale_mode on last_change 121 lfor 0/0/30 flags hashpspool,selfmanaged_snaps stripe_width 0 application rbd + read_balance_score 1.25 + pool 4 'kube-cephfs-data' replicated size 2 min_size 1 crush_rule 12 object_hash rjenkins pg_num 32 pgp_num 32 + autoscale_mode on last_change 123 lfor 0/0/30 flags hashpspool stripe_width 0 application cephfs + read_balance_score 2.03 + '\n' + + """ + + def __init__(self): + self.pool_id = -1 + self.pool_name = None + self.replicated_size = -1 + self.min_size = -1 + + def get_pool_id(self) -> int: + """ + Getter for pool id like: 1; 2; 3 etc. + + Returns: pool int + + """ + return self.pool_id + + def get_pool_name(self) -> str: + """ + Getter for pool name like: ".mgr"; "kube-cephfs-metadata"; "kube-rbd"; "kube-cephfs-data" + + Returns: pool name + + """ + return self.pool_name + + def get_replicated_size(self) -> int: + """ + Getter for replicated size + + Returns: replicated size + + """ + return self.replicated_size + + def get_min_size(self) -> int: + """ + Getter for min_size + + Returns: min_size + + """ + return self.min_size diff --git a/keywords/ceph/object/ceph_osd_pool_ls_detail_output.py b/keywords/ceph/object/ceph_osd_pool_ls_detail_output.py new file mode 100644 index 00000000..891bb4b4 --- /dev/null +++ b/keywords/ceph/object/ceph_osd_pool_ls_detail_output.py @@ -0,0 +1,77 @@ +from keywords.ceph.ceph_osd_pool_ls_detail_table_parser import CephOsdPoolLsDetailTableParser +from keywords.ceph.object.ceph_osd_pool_ls_detail_object import CephOsdPoolLsDetailObject + + +class CephOsdPoolLsDetailOutput: + """ + This class parses the output of the command 'ceph osd pool la detail' + + The parsing result is a 'CephOsdPoolLsDetailObject' instance. + + Example: + 'ceph osd pool ls detail' + pool 1 '.mgr' replicated size 2 min_size 1 crush_rule 9 object_hash rjenkins pg_num 32 pgp_num 32 autoscale_mode + on last_change 119 lfor 0/0/28 flags hashpspool stripe_width 0 application mgr read_balance_score 1.25 + pool 2 'kube-cephfs-metadata' replicated size 2 min_size 1 crush_rule 11 object_hash rjenkins pg_num 16 pgp_num + 16 autoscale_mode on last_change 122 lfor 0/0/28 flags hashpspool stripe_width 0 pg_autoscale_bias 4 pg_num_min + 16 recovery_priority 5 application cephfs read_balance_score 1.56 + pool 3 'kube-rbd' replicated size 2 min_size 1 crush_rule 10 object_hash rjenkins pg_num 32 pgp_num 32 + autoscale_mode on last_change 121 lfor 0/0/30 flags hashpspool,selfmanaged_snaps stripe_width 0 application rbd + read_balance_score 1.25 + pool 4 'kube-cephfs-data' replicated size 2 min_size 1 crush_rule 12 object_hash rjenkins pg_num 32 pgp_num 32 + autoscale_mode on last_change 123 lfor 0/0/30 flags hashpspool stripe_width 0 application cephfs + read_balance_score 2.03 + '\n' + """ + + def __init__(self, ceph_osd_pool_ls_detail_output: list[str]): + """ + Constructor + + This constructor receives a list of strings, resulting from the execution of the command + ceph osd pool ls detail, and generates a list of CephOsdPoolLsDetailObject objects. + + Args: + ceph_osd_pool_ls_detail_output (list[str]): list of strings resulting from the execution of the command `ceph osd pool ls detail`. + + """ + ceph_osd_pool_table_parser = CephOsdPoolLsDetailTableParser(ceph_osd_pool_ls_detail_output) + output_values = ceph_osd_pool_table_parser.get_output_values_list() + self.ceph_osd_pool_ls_detail_objects: list[CephOsdPoolLsDetailObject] = [] + for item_list in output_values: + ceph_osd_pool_ls_detail_object = CephOsdPoolLsDetailObject() + ceph_osd_pool_ls_detail_object.pool_id = item_list["pool_id"] + ceph_osd_pool_ls_detail_object.pool_name = item_list["pool_name"] + ceph_osd_pool_ls_detail_object.replicated_size = item_list["replicated_size"] + ceph_osd_pool_ls_detail_object.min_size = item_list["min_size"] + self.ceph_osd_pool_ls_detail_objects.append(ceph_osd_pool_ls_detail_object) + + def get_ceph_osd_pool_list(self): + """ + Gets the list of `CephOsdPoolLsDetailObject` objects. + + Args: none. + + Returns (list): The list of `CephOsdPoolLsDetailObject` objects. + + """ + return self.ceph_osd_pool_ls_detail_objects + + def get_ceph_osd_pool(self, pool_name: str) -> CephOsdPoolLsDetailObject: + """ + This function will get the pool with the name specified from this get_ceph_osd_pool_list. + + Args: + pool_name (str): The name of the pool of interest. + + Returns: + CephOsdPoolLsDetailObject: The pool object with the name specified. + + Raises: + ValueError: If the pool with the specified name does not exist in the output. + """ + for pool_object in self.ceph_osd_pool_ls_detail_objects: + if pool_object.get_pool_name() == pool_name: + return pool_object + else: + raise ValueError(f"There is no pool with the name {pool_name}.") diff --git a/unit_tests/parser/ceph/__init__.py b/unit_tests/parser/ceph/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/unit_tests/parser/ceph/ceph_osd_pool_ls_detail_table_parser_test.py b/unit_tests/parser/ceph/ceph_osd_pool_ls_detail_table_parser_test.py new file mode 100644 index 00000000..23fb2846 --- /dev/null +++ b/unit_tests/parser/ceph/ceph_osd_pool_ls_detail_table_parser_test.py @@ -0,0 +1,22 @@ +from keywords.ceph.object.ceph_osd_pool_ls_detail_output import CephOsdPoolLsDetailOutput + +ceph_osd_pool_ls_detail_cmd_output = ["pool 1 '.mgr' replicated size 2 min_size 1 crush_rule 9 object_hash rjenkins pg_num 32 pgp_num 32 autoscale_mode on last_change 119 lfor 0/0/28 flags hashpspool stripe_width 0 application mgr read_balance_score 1.25", "pool 2 'kube-cephfs-metadata' replicated size 2 min_size 1 crush_rule 11 object_hash rjenkins pg_num 16 pgp_num 16 autoscale_mode on last_change 122 lfor 0/0/28 flags hashpspool stripe_width 0 pg_autoscale_bias 4 pg_num_min 16 recovery_priority 5 application cephfs read_balance_score 1.56", "pool 3 'kube-rbd' replicated size 2 min_size 1 crush_rule 10 object_hash rjenkins pg_num 32 pgp_num 32 autoscale_mode on last_change 121 lfor 0/0/30 flags hashpspool,selfmanaged_snaps stripe_width 0 application rbd read_balance_score 1.25", "pool 4 'kube-cephfs-data' replicated size 2 min_size 1 crush_rule 12 object_hash rjenkins pg_num 32 pgp_num 32 autoscale_mode on last_change 123 lfor 0/0/30 flags hashpspool stripe_width 0 application cephfs read_balance_score 2.03", "\n"] + + +def test_ceph_osd_pool_ls_detail_output_parser(): + """ + Tests the "ceph osd pool ls detail" parser with a well formated input table. + + In this case, the parser must + be able to correctly generate a list of dictionaries. + + """ + ceph_osd_pool_ls_detail_output = CephOsdPoolLsDetailOutput(ceph_osd_pool_ls_detail_cmd_output) + ceph_osd_pool_objects = ceph_osd_pool_ls_detail_output.get_ceph_osd_pool_list() + assert len(ceph_osd_pool_objects), 4 + + mgr_object = ceph_osd_pool_ls_detail_output.get_ceph_osd_pool(".mgr") + assert mgr_object.get_pool_id() == 1 + assert mgr_object.get_pool_name() == ".mgr" + assert mgr_object.get_replicated_size() == 2 + assert mgr_object.get_min_size() == 1