From 9900c7d033645f29ef6bfb4aea75f9a8cf4eaf92 Mon Sep 17 00:00:00 2001 From: Michael MacDonald Date: Thu, 27 Jun 2024 16:37:04 +0000 Subject: [PATCH] ftest for daos pool list Skip-NLT: true Skip-unit-test: true Quick-functional: true Test-tag: ListClientPoolsTest Required-githooks: true Change-Id: I829dd6e5d26ea8ff3f5c9ff0da758dcc92e90774 Signed-off-by: Michael MacDonald --- src/tests/ftest/pool/list_pools.py | 77 +++++++++++------ src/tests/ftest/util/command_utils.py | 6 +- src/tests/ftest/util/daos_utils.py | 107 ++++++++++++++++++++++++ src/tests/ftest/util/daos_utils_base.py | 18 +++- 4 files changed, 178 insertions(+), 30 deletions(-) diff --git a/src/tests/ftest/pool/list_pools.py b/src/tests/ftest/pool/list_pools.py index b1a85a0125d7..265b3d390896 100644 --- a/src/tests/ftest/pool/list_pools.py +++ b/src/tests/ftest/pool/list_pools.py @@ -1,5 +1,5 @@ """ - (C) Copyright 2018-2023 Intel Corporation. + (C) Copyright 2018-2024 Intel Corporation. SPDX-License-Identifier: BSD-2-Clause-Patent """ @@ -9,7 +9,7 @@ class ListPoolsTest(TestWithServers): - """Test class for dmg pool list tests. + """Test class for dmg and daos pool list tests. Test Class Description: This class contains tests for list pool. @@ -20,8 +20,9 @@ class ListPoolsTest(TestWithServers): def run_case(self, rank_lists, svcn=None): """Run test case. - Create pools, call dmg pool list to get the list, and compare against - the UUIDs and service replicas returned at create time. + Create pools, then verify that both dmg and daos tools are able to + list the pools with the same information that was returned when the + pools were created. Args: rank_lists (list): Rank lists. List of list of int. @@ -35,47 +36,71 @@ def run_case(self, rank_lists, svcn=None): # Iterate rank lists to create pools. Store the created pool information # as a dictionary of pool UUID keys with a service replica list value. self.pool = [] - expected_uuids = {} + expected_admin_uuids = {} for rank_list in rank_lists: self.pool.append(self.get_pool(target_list=rank_list, svcn=svcn)) - expected_uuids[self.pool[-1].uuid.lower()] = self.pool[-1].svc_ranks - - # Verify the 'dmg pool info' command lists the correct created pool - # information. The DmgCommand.pool_info() method returns the command - # output as a dictionary of pool UUID keys with service replica list - # values. - detected_uuids = {} + expected_admin_uuids[self.pool[-1].uuid.lower()] = self.pool[-1].svc_ranks + + # Remove the default ACLs that allow the creator to access the pool. + # These ACLs don't apply to dmg, but do apply to users. + offlimits = self.pool[0] + self.get_dmg_command().pool_delete_acl(offlimits.uuid, 'OWNER@') + self.get_dmg_command().pool_delete_acl(offlimits.uuid, 'GROUP@') + expected_user_uuids = expected_admin_uuids.copy() + del expected_user_uuids[offlimits.uuid.lower()] + + # Verify the 'dmg pool list' command lists the correct created pool + # information. + detected_admin_uuids = {} try: for data in self.get_dmg_command().get_pool_list_all(): - detected_uuids[data["uuid"]] = data["svc_reps"] + detected_admin_uuids[data["uuid"]] = data["svc_reps"] except KeyError as error: self.fail("Error parsing dmg pool list output: {}".format(error)) - self.log.info("Expected pool info: %s", str(expected_uuids)) - self.log.info("Detected pool info: %s", str(detected_uuids)) - - # Destroy all the pools - if self.destroy_pools(self.pool): - self.fail("Error destroying pools") - self.pool = [] + self.log.info("Expected admin pool info: %s", str(expected_admin_uuids)) + self.log.info("Detected admin pool info: %s", str(detected_admin_uuids)) # Compare the expected and detected pool information self.assertEqual( - expected_uuids, detected_uuids, + expected_admin_uuids, detected_admin_uuids, "dmg pool info does not list all expected pool UUIDs and their " "service replicas") + # Verify the 'daos pool list' command lists the correct created pool + # information. + detected_user_uuids = {} + try: + for data in self.get_daos_command().get_pool_list_all(): + detected_user_uuids[data["uuid"]] = data["svc_reps"] + except KeyError as error: + self.fail("Error parsing dmg pool list output: {}".format(error)) + + self.log.info("Expected user pool info: %s", str(expected_user_uuids)) + self.log.info("Detected user pool info: %s", str(detected_user_uuids)) + + # Compare the expected and detected pool information + self.assertEqual( + expected_user_uuids, detected_user_uuids, + "daos pool info does not list all expected pool UUIDs and their " + "service replicas") + + # Destroy all the pools + if self.destroy_pools(self.pool): + self.fail("Error destroying pools") + self.pool = [] + def test_list_pools(self): - """JIRA ID: DAOS-3459. + """JIRA ID: DAOS-3459, DAOS-16038. Test Description: - Create pools in different ranks, call dmg pool list and verify the - output list matches the output returned when the pools were - created. + Create pools in different ranks, then verify that both dmg and + daos tools are able to list the pools and that the listed output + matches what was returned when the pools were created. :avocado: tags=all,full_regression :avocado: tags=vm - :avocado: tags=pool + :avocado: tags=pool,daos_cmd :avocado: tags=list_pools,test_list_pools """ ranks = list(range(len(self.hostlist_servers))) diff --git a/src/tests/ftest/util/command_utils.py b/src/tests/ftest/util/command_utils.py index 9c21c21feeb3..2d278cc697b5 100644 --- a/src/tests/ftest/util/command_utils.py +++ b/src/tests/ftest/util/command_utils.py @@ -637,7 +637,11 @@ def set_command(self, sub_command_list=None, **kwargs): if sub_command_list is not None: for sub_command in sub_command_list: full_command.set_sub_command(sub_command) - full_command = full_command.sub_command_class + + if full_command.sub_command_class is not None: + full_command = full_command.sub_command_class + else: + raise CommandFailure(f"invalid sub command: {sub_command}") # Update any argument values for the full command full_command.update_params(**kwargs) diff --git a/src/tests/ftest/util/daos_utils.py b/src/tests/ftest/util/daos_utils.py index 679ca4324143..13dfdbdd9f33 100644 --- a/src/tests/ftest/util/daos_utils.py +++ b/src/tests/ftest/util/daos_utils.py @@ -369,6 +369,113 @@ def pool_list_attrs(self, pool, sys_name=None, verbose=False): ("pool", "list-attrs"), pool=pool, sys_name=sys_name, verbose=verbose) + def pool_list_containers(self, pool, sys_name=None): + """List containers in the pool. + + Args: + pool (str): pool label or UUID + sys_name (str): DAOS system name. Defaults to None. + + Returns: + dict: JSON output + + Raises: + CommandFailure: if the daos pool list-containers command fails. + + """ + return self._get_json_result( + ("pool", "list-containers"), pool=pool, sys_name=sys_name) + + def pool_list(self, no_query=False, verbose=False): + """List pools. + + Args: + no_query (bool, optional): If True, do not query for pool stats. + verbose (bool, optional): If True, use verbose mode. + + Raises: + CommandFailure: if the daos pool list command fails. + + Returns: + dict: the daos json command output converted to a python dictionary + + """ + # Sample verbose JSON Output: + # { + # "response": { + # "status": 0, + # "pools": [ + # { + # "uuid": "517217db-47c4-4bb9-aae5-e38ca7b3dafc", + # "label": "mkp1", + # "svc_reps": [ + # 0 + # ], + # "total_targets": 8, + # "disabled_targets": 0, + # "usage": [ + # { + # "tier_name": "SCM", + # "size": 3000000000, + # "free": 2995801112, + # "imbalance": 0 + # }, + # { + # "tier_name": "NVME", + # "size": 47000000000, + # "free": 26263322624, + # "imbalance": 36 + # } + # ] + # } + # ] + # }, + # "error": null, + # "status": 0 + # } + return self._get_json_result( + ("pool", "list"), no_query=no_query, verbose=verbose) + + def _parse_pool_list(self, key=None, **kwargs): + """Parse the daos pool list json output for the requested information. + + Args: + key (str, optional): pool list json dictionary key in + ["response"]["pools"]. Defaults to None. + + Raises: + CommandFailure: if the daos pool list command fails. + + Returns: + list: list of all the pool items in the daos pool list json output + for the requested json dictionary key. This will be an empty + list if the key does not exist or the json output was not in + the expected format. + + """ + pool_list = self.pool_list(**kwargs) + try: + if pool_list["response"]["pools"] is None: + return [] + if key: + return [pool[key] for pool in pool_list["response"]["pools"]] + return pool_list["response"]["pools"] + except KeyError: + return [] + + def get_pool_list_all(self, **kwargs): + """Get a list of all the pool information from daos pool list. + + Raises: + CommandFailure: if the daos pool list command fails. + + Returns: + list: a list of dictionaries containing information for each pool + from the daos pool list json output + + """ + return self._parse_pool_list(**kwargs) + def container_query(self, pool, cont, sys_name=None): """Query a container. diff --git a/src/tests/ftest/util/daos_utils_base.py b/src/tests/ftest/util/daos_utils_base.py index 9130b214dac9..9f76d97e10ef 100644 --- a/src/tests/ftest/util/daos_utils_base.py +++ b/src/tests/ftest/util/daos_utils_base.py @@ -1,5 +1,5 @@ """ - (C) Copyright 2020-2023 Intel Corporation. + (C) Copyright 2020-2024 Intel Corporation. SPDX-License-Identifier: BSD-2-Clause-Patent """ @@ -47,8 +47,10 @@ def __init__(self): def get_sub_command_class(self): # pylint: disable=redefined-variable-type - """Get the dmg network sub command object.""" - if self.sub_command.value in ("list-containers", "list"): + """Get the daos pool sub command object.""" + if self.sub_command.value == "list": + self.sub_command_class = self.ListSubCommand() + elif self.sub_command.value == "list-containers": self.sub_command_class = self.ListContainersSubCommand() elif self.sub_command.value == "query": self.sub_command_class = self.QuerySubCommand() @@ -65,6 +67,16 @@ def get_sub_command_class(self): else: self.sub_command_class = None + class ListSubCommand(CommandWithParameters): + """Defines an object for the daos pool list command.""" + + def __init__(self): + """Create a daos pool list command object.""" + super().__init__("/run/daos/pool/list/*", "list") + self.sys_name = FormattedParameter("--sys-name={}") + self.no_query = FormattedParameter("--no-query", False) + self.verbose = FormattedParameter("--verbose", False) + class CommonPoolSubCommand(CommandWithParameters): """Defines an object for the common daos pool sub-command.