Exemplo n.º 1
0
class ContainerAttributeTest(TestWithServers):
    """
    Tests DAOS container attribute get/set/list.
    :avocado: recursive
    """
    def __init__(self, *args, **kwargs):
        """Initialize a ContainerAttributeTest object."""
        super().__init__(*args, **kwargs)
        self.expected_cont_uuid = None
        self.daos_cmd = None

    @staticmethod
    def create_data_set():
        """Create the large attribute dictionary.

        Returns:
            dict: a large attribute dictionary

        """
        data_set = {}
        for index in range(1024):
            size = random.randint(1, 100)
            key = str(index).encode("utf-8")
            data_set[key] = get_random_bytes(size)
        return data_set

    def verify_list_attr(self, indata, outdata, mode="sync"):
        """
        Args:
            indata: Dict used to set attr
            outdata: Dict obtained from list attr
            mode: sync or async

        Verify the length of the Attribute names
        """
        length = 0
        for key in indata.keys():
            length += len(key)

        if mode == "async":
            length += 1

        attributes_list = list(outdata.keys())
        size = 0
        for attr in attributes_list:
            size += len(attr)

        self.log.info("Verifying list_attr output:")
        self.log.info("  set_attr names:  %s", list(indata.keys()))
        self.log.info("  set_attr size:   %s", length)
        self.log.info("  list_attr names: %s", attributes_list)
        self.log.info("  list_attr size:  %s", size)

        if length != size:
            self.fail(
                "FAIL: Size is not matching for Names in list attr, Expected "
                "len={} and received len={}".format(length, size))
        # verify the Attributes names in list_attr retrieve
        for key in list(indata.keys()):
            found = False
            for attr in attributes_list:
                # Workaround
                # decode() is used to be able to compare bytes with strings
                # We get strings from daos_command, while in pydaos we get bytes
                if key.decode() == attr:
                    found = True
                    break
            if not found:
                self.fail(
                    "FAIL: Name does not match after list attr, Expected "
                    "buf={} and received buf={}".format(key, attributes_list))

    def verify_get_attr(self, indata, outdata):
        """
        verify the Attributes value after get_attr
        """
        decoded = {}
        for key, val in outdata.items():
            if isinstance(val, bytes):
                # The API returns the values as bytes already.
                decoded[key.decode()] = val
            else:
                # The JSON output encodes the bytes as base64, so
                # we need to decode them for comparison.
                decoded[key] = base64.b64decode(val)

        self.log.info("Verifying get_attr output:")
        self.log.info("  get_attr data: %s", indata)
        self.log.info("  set_attr data: %s", decoded)

        for attr, value in indata.items():
            if value != decoded.get(attr.decode(), None):
                self.fail("FAIL: Value does not match after get({}), Expected "
                          "val={} and received val={}".format(
                              attr, value, decoded.get(attr.decode(), None)))

    def test_container_large_attributes(self):
        """
        Test ID: DAOS-1359

        Test description: Test large randomly created container attribute.

        :avocado: tags=all,full_regression
        :avocado: tags=container,attribute
        :avocado: tags=large_conattribute
        :avocado: tags=container_attribute
        """
        self.add_pool()
        self.add_container(self.pool)
        self.container.open()
        self.daos_cmd = DaosCommand(self.bin)
        attr_dict = self.create_data_set()

        try:
            self.container.container.set_attr(data=attr_dict)

            # Workaround
            # Due to DAOS-7093 skip the usage of pydaos cont list attr
            # size, buf = self.container.container.list_attr()

            data = self.daos_cmd.container_list_attrs(pool=self.pool.uuid,
                                                      cont=self.container.uuid,
                                                      verbose=False)
            self.verify_list_attr(attr_dict, data['response'])

            data = self.daos_cmd.container_list_attrs(pool=self.pool.uuid,
                                                      cont=self.container.uuid,
                                                      verbose=True)
            self.verify_get_attr(attr_dict, data['response'])
        except DaosApiError as excep:
            print(excep)
            print(traceback.format_exc())
            self.fail("Test was expected to pass but it failed.\n")

    def test_container_attribute(self):
        """
        Test basic container attribute tests.
        :avocado: tags=all,tiny,full_regression
        :avocado: tags=container,attribute
        :avocado: tags=sync_conattribute
        :avocado: tags=container_attribute
        """
        self.add_pool()
        self.add_container(self.pool)
        self.container.open()
        self.daos_cmd = DaosCommand(self.bin)

        expected_for_param = []
        name = self.params.get("name", '/run/attrtests/name_handles/*/')
        expected_for_param.append(name[1])
        value = self.params.get("value", '/run/attrtests/value_handles/*/')
        expected_for_param.append(value[1])

        # Convert any test yaml string to bytes
        if isinstance(name[0], str):
            name[0] = name[0].encode("utf-8")
        if isinstance(value[0], str):
            value[0] = value[0].encode("utf-8")

        attr_dict = {name[0]: value[0]}

        expected_result = 'PASS'
        for result in expected_for_param:
            if result == 'FAIL':
                expected_result = 'FAIL'
                break
        try:
            self.container.container.set_attr(data=attr_dict)

            data = self.daos_cmd.container_list_attrs(pool=self.pool.uuid,
                                                      cont=self.container.uuid)
            self.verify_list_attr(attr_dict, data['response'])

            # Request something that doesn't exist
            if name[0] is not None and b"Negative" in name[0]:
                name[0] = b"rubbish"

            attr_value_dict = self.container.container.get_attr([name[0]])

            # Raise an exception if the attr value is empty
            # This is expected to happen on Negative test cases
            if not attr_value_dict[name[0]]:
                raise DaosApiError("Attr value is empty. "
                                   "Did you set the value?")
            self.verify_get_attr(attr_dict, attr_value_dict)

            if expected_result in ['FAIL']:
                self.fail("Test was expected to fail but it passed.\n")

        except (DaosApiError, DaosTestError) as excep:
            print(excep)
            print(traceback.format_exc())
            if expected_result == 'PASS':
                self.fail("Test was expected to pass but it failed.\n")

    def test_container_attribute_async(self):
        """
        Test basic container attribute tests.

        :avocado: tags=all,small,full_regression
        :avocado: tags=container,attribute
        :avocado: tags=async_conattribute
        :avocado: tags=container_attribute
        """
        global GLOB_SIGNAL
        global GLOB_RC

        self.add_pool()
        self.add_container(self.pool)
        self.container.open()
        self.daos_cmd = DaosCommand(self.bin)

        expected_for_param = []
        name = self.params.get("name", '/run/attrtests/name_handles/*/')
        expected_for_param.append(name[1])
        value = self.params.get("value", '/run/attrtests/value_handles/*/')
        expected_for_param.append(value[1])

        # Convert any test yaml string to bytes
        if isinstance(name[0], str):
            name[0] = name[0].encode("utf-8")
        if isinstance(value[0], str):
            value[0] = value[0].encode("utf-8")

        attr_dict = {name[0]: value[0]}
        expected_result = 'PASS'
        for result in expected_for_param:
            if result == 'FAIL':
                expected_result = 'FAIL'
                break
        try:
            GLOB_SIGNAL = threading.Event()
            self.container.container.set_attr(data=attr_dict, cb_func=cb_func)
            GLOB_SIGNAL.wait()
            if GLOB_RC != 0 and expected_result in ['PASS']:
                self.fail("RC not as expected after set_attr First {0}".format(
                    GLOB_RC))

            # Workaround
            # Due to DAOS-7093 skip the usage of pydaos cont list attr
            # GLOB_SIGNAL = threading.Event()
            #
            # size, buf = self.container.container.list_attr(cb_func=cb_func)
            #
            data = self.daos_cmd.container_list_attrs(pool=self.pool.uuid,
                                                      cont=self.container.uuid)

            # GLOB_SIGNAL.wait()
            # if GLOB_RC != 0 and expected_result in ['PASS']:
            #     self.fail("RC not as expected after list_attr First {0}"
            #               .format(GLOB_RC))

            if expected_result in ['PASS']:
                # Workaround: async mode is not used for list_attr
                self.verify_list_attr(attr_dict, data['response'])

            # Request something that doesn't exist
            if name[0] is not None and b"Negative" in name[0]:
                name[0] = b"rubbish"

            GLOB_SIGNAL = threading.Event()
            self.container.container.get_attr([name[0]], cb_func=cb_func)
            GLOB_SIGNAL.wait()

            if GLOB_RC != 0 and expected_result in ['PASS']:
                self.fail(
                    "RC not as expected after get_attr {0}".format(GLOB_RC))

            # not verifying the get_attr since its not available asynchronously
            # Therefore we want to avoid passing negative test
            # e.g. rubbish getting assigned.

            if value[0] is not None:
                if GLOB_RC == 0 and expected_result in ['FAIL']:
                    if name[0] != b"rubbish":
                        self.fail("Test was expected to fail but it passed.\n")

        except DaosApiError as excep:
            print(excep)
            print(traceback.format_exc())
            if expected_result == 'PASS':
                self.fail("Test was expected to pass but it failed.\n")
Exemplo n.º 2
0
class ContSecurityTestBase(TestWithServers):
    """Container security test cases.

    Test Class Description:
        Test methods to verify the Container security with acl by
        using daos tool.

    :avocado: recursive
    """
    def __init__(self, *args, **kwargs):
        """Initialize a ContSecurityTestBase object."""
        super().__init__(*args, **kwargs)
        self.dmg = None
        self.daos_tool = None
        self.user_uid = None
        self.user_gid = None
        self.current_user = None
        self.current_group = None
        self.pool_uuid = None
        self.container_uuid = None

    def setUp(self):
        """Set up each test case."""
        super().setUp()
        self.user_uid = os.geteuid()
        self.user_gid = os.getegid()
        self.current_user = pwd.getpwuid(self.user_uid)[0]
        self.current_group = grp.getgrgid(self.user_uid)[0]
        self.co_prop = self.params.get("container_properties",
                                       "/run/container/*")
        self.dmg = self.get_dmg_command()
        self.daos_tool = DaosCommand(self.bin)

    @fail_on(CommandFailure)
    def create_pool_with_dmg(self):
        """Create a pool with the dmg tool.

        Obtains the pool uuid from the operation's result

        Returns:
            pool_uuid (str): Pool UUID, randomly generated.
        """
        self.prepare_pool()
        pool_uuid = self.pool.pool.get_uuid_str()

        return pool_uuid

    def create_container_with_daos(self, pool, acl_type=None, acl_file=None):
        """Create a container with the daos tool.

        Also, obtains the container uuid from the operation's result.

        Args:
            pool (TestPool): Pool object.
            acl_type (str, optional): valid or invalid.

        Returns:
            container_uuid: Container UUID created.

        """
        file_name = None
        get_acl_file = None
        expected_acl_types = [None, "valid", "invalid"]

        if acl_file is None:
            if acl_type not in expected_acl_types:
                self.fail("    Invalid '{}' acl type passed.".format(acl_type))
            if acl_type:
                get_acl_file = "acl_{}.txt".format(acl_type)
                file_name = os.path.join(self.tmp, get_acl_file)
            else:
                get_acl_file = ""
        else:
            file_name = acl_file

        try:
            self.container = TestContainer(pool=pool,
                                           daos_command=self.daos_tool)
            self.container.get_params(self)
            self.container.create(acl_file=file_name)
            container_uuid = self.container.uuid
        except TestFail as error:
            if acl_type != "invalid":
                raise DaosTestError(
                    "Could not create expected container ") from error
            container_uuid = None

        return container_uuid

    def get_container_acl_list(self,
                               pool_uuid,
                               container_uuid,
                               verbose=False,
                               outfile=None):
        """Get daos container acl list by daos container get-acl.

        Args:
            pool_uuid (str): Pool uuid.
            container_uuid (str): Container uuid.
            verbose (bool, optional): Verbose mode.
            outfile (str, optional): Write ACL to file

        Return:
            cont_permission_list: daos container acl list.

        """
        if not general_utils.check_uuid_format(pool_uuid):
            self.fail("    Invalid Pool UUID '{}' provided.".format(pool_uuid))

        if not general_utils.check_uuid_format(container_uuid):
            self.fail("    Invalid Container UUID '{}' provided.".format(
                container_uuid))

        result = self.daos_tool.container_get_acl(pool_uuid, container_uuid,
                                                  verbose, outfile)

        cont_permission_list = []
        for line in result.stdout_text.splitlines():
            if not line.startswith("A:"):
                continue
            elif line.startswith("A::"):
                found_user = re.search(r"A::(.+)@:(.*)", line)
                if found_user:
                    cont_permission_list.append(line)
            elif line.startswith("A:G:"):
                found_group = re.search(r"A:G:(.+)@:(.*)", line)
                if found_group:
                    cont_permission_list.append(line)
        return cont_permission_list

    def overwrite_container_acl(self, acl_file):
        """Overwrite existing container acl-entries with acl_file.

        Args:
            acl_file (str): acl filename.

        Return:
            result (str): daos_tool.container_overwrite_acl.
        """
        self.daos_tool.exit_status_exception = False
        result = self.daos_tool.container_overwrite_acl(
            self.pool_uuid, self.container_uuid, acl_file)
        return result

    def update_container_acl(self, entry):
        """Update container acl entry.

        Args:
            entry (str): acl entry to be updated.

        Return:
            result (str): daos_tool.container_update_acl.
        """
        self.daos_tool.exit_status_exception = False
        result = self.daos_tool.container_update_acl(self.pool_uuid,
                                                     self.container_uuid,
                                                     entry=entry)
        return result

    def test_container_destroy(self, pool_uuid, container_uuid):
        """Test container destroy/delete.

        Args:
            pool_uuid (str): pool uuid.
            container_uuid (str): container uuid.

        Return:
            result (str): daos_tool.container_destroy result.
        """
        self.daos_tool.exit_status_exception = False
        result = self.daos_tool.container_destroy(pool_uuid, container_uuid,
                                                  True)
        return result

    def set_container_attribute(self, pool_uuid, container_uuid, attr, value):
        """Write/Set container attribute.

        Args:
            pool_uuid (str): pool uuid.
            container_uuid (str): container uuid.
            attr (str): container attribute.
            value (str): container attribute value to be set.

        Return:
            result (str): daos_tool.container_set_attr result.
        """
        self.daos_tool.exit_status_exception = False
        result = self.daos_tool.container_set_attr(pool_uuid, container_uuid,
                                                   attr, value)
        return result

    def get_container_attribute(self, pool_uuid, container_uuid, attr):
        """Get container attribute.

        Args:
            pool_uuid (str): pool uuid.
            container_uuid (str): container uuid.
            attr (str): container attribute.

        Return:
            CmdResult: Object that contains exit status, stdout, and other
                information.
        """
        self.daos_tool.exit_status_exception = False
        self.daos_tool.container_get_attr(pool_uuid, container_uuid, attr)
        return self.daos_tool.result

    def list_container_attribute(self, pool_uuid, container_uuid):
        """List container attribute.

        Args:
            pool_uuid (str): pool uuid.
            container_uuid (str): container uuid.

        Return:
            result (str): daos_tool.container_list_attrs result.
        """
        self.daos_tool.exit_status_exception = False
        result = self.daos_tool.container_list_attrs(pool_uuid, container_uuid)
        return result

    def set_container_property(self, pool_uuid, container_uuid, prop, value):
        """Write/Set container property.

        Args:
            pool_uuid (str): pool uuid.
            container_uuid (str): container uuid.
            prop (str): container property name.
            value (str): container property value to be set.

        Return:
            result (str): daos_tool.container_set_prop result.
        """
        self.daos_tool.exit_status_exception = False
        result = self.daos_tool.container_set_prop(pool_uuid, container_uuid,
                                                   prop, value)
        return result

    def get_container_property(self, pool_uuid, container_uuid):
        """Get container property.

        Args:
            pool_uuid (str): pool uuid.
            container_uuid (str): container uuid.

        Return:
            result (str): daos_tool.container_get_prop.
        """
        self.daos_tool.exit_status_exception = False
        result = self.daos_tool.container_get_prop(pool_uuid, container_uuid)
        return result

    def set_container_owner(self, pool_uuid, container_uuid, user, group):
        """Set container owner.

        Args:
            pool_uuid (str): pool uuid.
            container_uuid (str): container uuid.
            user (str): container user-name to be set owner to.
            group (str): container group-name to be set owner to.

        Return:
            result (str): daos_tool.container_set_owner.
        """
        self.daos_tool.exit_status_exception = False
        result = self.daos_tool.container_set_owner(pool_uuid, container_uuid,
                                                    user, group)
        return result

    def compare_acl_lists(self, get_acl_list, expected_list):
        """Compare two permission lists.

        Args:
            get_acl_list (str list): list of permissions obtained by get-acl
            expected_list (str list): list of expected permissions

        Returns:
            True or False if both permission lists are identical or not

        """
        self.log.info("    ===> get-acl ACL:  %s", get_acl_list)
        self.log.info("    ===> Expected ACL: %s", expected_list)

        exp_list = expected_list[:]
        if len(get_acl_list) != len(exp_list):
            return False
        for acl in get_acl_list:
            if acl in exp_list:
                exp_list.remove(acl)
            else:
                return False
        return True

    def get_base_acl_entries(self, test_user):
        """Get container acl entries per cont enforcement order for test_user.

        Args:
            test_user (str): test user.

        Returns (list str):
            List of base container acl entries for the test_user.

        """
        if test_user == "OWNER":
            base_acl_entries = [
                secTestBase.acl_entry("user", "OWNER", ""),
                secTestBase.acl_entry("user", self.current_user, ""),
                secTestBase.acl_entry("group", "GROUP", "rwcdtTaAo"),
                secTestBase.acl_entry("group", self.current_group,
                                      "rwcdtTaAo"),
                secTestBase.acl_entry("user", "EVERYONE", "rwcdtTaAo")
            ]
        elif test_user == "user":
            base_acl_entries = [
                "",
                secTestBase.acl_entry("user", self.current_user, ""),
                secTestBase.acl_entry("group", "GROUP", "rwcdtTaAo"),
                secTestBase.acl_entry("group", self.current_group, ""),
                secTestBase.acl_entry("user", "EVERYONE", "rwcdtTaAo")
            ]
        elif test_user == "group":
            base_acl_entries = [
                "", "",
                secTestBase.acl_entry("group", "GROUP", ""),
                secTestBase.acl_entry("group", self.current_group, ""),
                secTestBase.acl_entry("user", "EVERYONE", "rwcdtTaAo")
            ]
        elif test_user == "GROUP":
            base_acl_entries = [
                "", "", "",
                secTestBase.acl_entry("group", self.current_group, ""),
                secTestBase.acl_entry("user", "EVERYONE", "rwcdtTaAo")
            ]
        elif test_user == "EVERYONE":
            base_acl_entries = [
                "", "", "", "",
                secTestBase.acl_entry("user", "EVERYONE", "")
            ]
        else:
            base_acl_entries = ["", "", "", "", ""]
        return base_acl_entries

    def cleanup(self, types):
        """Remove all temporal acl files created during the test.

        Args:
            types (list): types of acl files [valid, invalid]

        """
        for typ in types:
            get_acl_file = "acl_{}.txt".format(typ)
            file_name = os.path.join(self.tmp, get_acl_file)
            cmd = "rm -r {}".format(file_name)
            general_utils.run_command(cmd)

    def error_handling(self, results, err_msg):
        """Handle errors when test fails and when command unexpectedly passes.

        Args:
            results (CmdResult): object containing stdout, stderr and
                exit status.
            err_msg (str): error message string to look for in stderr.

        Returns:
            list: list of test errors encountered.

        """
        test_errs = []
        if results.exit_status == 0:
            test_errs.append("{} passed unexpectedly: {}".format(
                results.command, results.stdout_text))
        elif results.exit_status == 1:
            # REMOVE BELOW IF Once DAOS-5635 is resolved
            if results.stdout_text and err_msg in results.stdout_text:
                self.log.info("Found expected error %s", results.stdout_text)
            # REMOVE ABOVE IF Once DAOS-5635 is resolved
            elif results.stderr_text and err_msg in results.stderr_text:
                self.log.info("Found expected error %s", results.stderr_text)
            else:
                self.fail("{} seems to have failed with \
                    unexpected error: {}".format(results.command, results))
        return test_errs

    def acl_file_diff(self, prev_acl, flag=True):
        """Compare current content of acl-file with helper function.

        If provided  prev_acl file information is different from current acl
        file information test will fail if flag=True. If flag=False, test will
        fail in the case that the acl contents are found to have no difference.

        Args:
            prev_acl (list): list of acl entries within acl-file.
                Defaults to True.
            flag (bool, optional): if True, test will fail when acl-file
                contents are different, else test will fail when acl-file
                contents are same. Defaults to True.

        """
        current_acl = self.get_container_acl_list(self.pool.uuid,
                                                  self.container.uuid)
        if self.compare_acl_lists(prev_acl, current_acl) != flag:
            self.fail("Previous ACL:\n{} \nPost command ACL:\n{}".format(
                prev_acl, current_acl))
Exemplo n.º 3
0
class ContainerQueryAttributeTest(TestWithServers):
    # pylint: disable=anomalous-backslash-in-string
    """Test class for daos container query and attribute tests.

    Test Class Description:
        Query test: Create a pool, create a container, and call daos container
        query. From the output, verify the pool/container UUID matches the one
        that was returned when creating the pool/container.

        Attribute test:
        1. Prepare 7 types of strings; alphabets, numbers, special characters,
        etc.
        2. Create attributes with each of these 7 types in attr and value;
        i.e., 14 total attributes are created.
        3. Call get-attr for each of the 14 attrs and verify the returned
        values.
        4. Call list-attrs and verify the returned attrs.

    :avocado: recursive
    """
    def __init__(self, *args, **kwargs):
        """Initialize a ContainerQueryAttribute object."""
        super(ContainerQueryAttributeTest, self).__init__(*args, **kwargs)
        self.expected_cont_uuid = None
        self.daos_cmd = None

    def create_pool_container(self):
        """Create a pool and a container in the pool.

        Save some variables so that we can use them in the tests.
        """
        self.add_pool()
        self.daos_cmd = DaosCommand(self.bin)
        self.expected_cont_uuid = self.daos_cmd.get_output(
            "container_create", pool=self.pool.uuid)[0]

    def test_container_query_attr(self):
        """JIRA ID: DAOS-4640

        Test Description:
            Test daos container query and attribute commands as described
            above.

        Use Cases:
            Test container query, set-attr, get-attr, and list-attrs.

        :avocado: tags=all,pool,small,full_regression,cont_query_attr
        """
        # Test pool query.
        self.create_pool_container()
        # Call daos container query, obtain pool and container UUID, and
        # compare against those used when creating the pool and the container.
        kwargs = {"pool": self.pool.uuid, "cont": self.expected_cont_uuid}
        query_output = self.daos_cmd.get_output("container_query", **kwargs)[0]
        actual_pool_uuid = query_output[0]
        actual_cont_uuid = query_output[1]
        self.assertEqual(actual_pool_uuid, self.pool.uuid.lower())
        self.assertEqual(actual_cont_uuid, self.expected_cont_uuid)

        # Test container set-attr, get-attr, and list-attrs with different
        # types of characters.
        test_strings = [
            "abcd",
            "1234",
            "abc123",
            "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij",
            # Characters that don't require backslash. The backslashes in here
            # are required for the code to work, but not by daos.
            "~@#$%^*-=_+[]\{\}:/?,.",
            # Characters that require backslash.
            "\`\&\(\)\\\;\\'\\\"\!\<\>",
            # Characters that include space.
            "\"aa bb\""
        ]
        # We added backslashes for the code to work, but get-attr output
        # does not contain them, so prepare the expected output that does not
        # include backslashes.
        escape_to_not = {}
        escape_to_not[test_strings[-3]] = "~@#$%^*-=_+[]{}:/?,."
        # We still need a backslash before the double quote for the code to
        # work.
        escape_to_not[test_strings[-2]] = "`&()\;'\"!<>"
        escape_to_not[test_strings[-1]] = "aa bb"
        # Prepare attr-value paris. Use the test_strings in value for the first
        # 7 and in attr for the next 7.
        attr_values = []
        j = 0
        for i in range(2):
            for test_string in test_strings:
                if i == 0:
                    attr_values.append(["attr" + str(j), test_string])
                else:
                    attr_values.append([test_string, "attr" + str(j)])
                j += 1

        # Set and verify get-attr.
        errors = []
        expected_attrs = []
        for attr_value in attr_values:
            self.daos_cmd.container_set_attr(pool=actual_pool_uuid,
                                             cont=actual_cont_uuid,
                                             attr=attr_value[0],
                                             val=attr_value[1])
            kwargs["attr"] = attr_value[0]
            output = self.daos_cmd.container_get_attr(**kwargs)
            actual_val = output["value"]
            if attr_value[1] in escape_to_not:
                # Special character string.
                if actual_val != escape_to_not[attr_value[1]]:
                    errors.append(
                        "Unexpected output for get_attr: {} != {}\n".format(
                            actual_val, escape_to_not[attr_value[1]]))
            else:
                # Standard character string.
                if actual_val != attr_value[1]:
                    errors.append(
                        "Unexpected output for get_attr: {} != {}\n".format(
                            actual_val, attr_value[1]))
            # Collect comparable attr as a preparation of list-attrs test.
            if attr_value[0] in escape_to_not:
                expected_attrs.append(escape_to_not[attr_value[0]])
            else:
                expected_attrs.append(attr_value[0])
        self.assertEqual(len(errors), 0, "; ".join(errors))

        # Verify that attr-lists works with test_strings.
        expected_attrs.sort()
        kwargs = {"pool": actual_pool_uuid, "cont": actual_cont_uuid}
        data = self.daos_cmd.container_list_attrs(**kwargs)
        actual_attrs = data["attrs"]
        actual_attrs.sort()
        self.log.debug(str(actual_attrs))
        self.assertEqual(actual_attrs, expected_attrs)

    def test_list_attrs_long(self):
        """JIRA ID: DAOS-4640

        Test Description:
            Set many attributes and verify list-attrs works.

        Use Cases:
            Test daos container list-attrs with 50 attributes.

        :avocado: tags=all,pool,small,full_regression,cont_list_attrs
        """
        self.create_pool_container()
        expected_attrs = []
        vals = []
        for i in range(50):
            expected_attrs.append("attr" + str(i))
            vals.append("val" + str(i))
        for expected_attr, val in zip(expected_attrs, vals):
            _ = self.daos_cmd.container_set_attr(pool=self.pool.uuid,
                                                 cont=self.expected_cont_uuid,
                                                 attr=expected_attr,
                                                 val=val)
        expected_attrs.sort()
        kwargs = {"pool": self.pool.uuid, "cont": self.expected_cont_uuid}
        data = self.daos_cmd.container_list_attrs(**kwargs)
        actual_attrs = data["attrs"]
        actual_attrs.sort()
        self.assertEqual(expected_attrs, actual_attrs,
                         "Unexpected output from list_attrs")