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")
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))
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")