diff options
-rwxr-xr-x | gluster/api.py | 28 | ||||
-rwxr-xr-x | gluster/gfapi.py | 289 | ||||
-rw-r--r-- | test/functional/libgfapi-python-tests.py | 231 | ||||
-rw-r--r-- | test/unit/gluster/test_gfapi.py | 103 |
4 files changed, 501 insertions, 150 deletions
diff --git a/gluster/api.py b/gluster/api.py index 6bbd092..560ede8 100755 --- a/gluster/api.py +++ b/gluster/api.py @@ -396,6 +396,34 @@ glfs_setfsuid = ctypes.CFUNCTYPE(ctypes.c_int, glfs_setfsgid = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_uint)(('glfs_setfsgid', client)) +glfs_ftruncate = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p, + ctypes.c_int)(('glfs_ftruncate', client)) + +glfs_fgetxattr = ctypes.CFUNCTYPE(ctypes.c_ssize_t, + ctypes.c_void_p, + ctypes.c_char_p, + ctypes.c_void_p, + ctypes.c_size_t)(('glfs_fgetxattr', client)) + +glfs_fremovexattr = ctypes.CFUNCTYPE(ctypes.c_int, + ctypes.c_void_p, + ctypes.c_char_p)(('glfs_fremovexattr', + client)) + +glfs_fsetxattr = ctypes.CFUNCTYPE(ctypes.c_int, + ctypes.c_void_p, + ctypes.c_char_p, + ctypes.c_void_p, + ctypes.c_size_t, + ctypes.c_int)(('glfs_fsetxattr', client)) + +glfs_flistxattr = ctypes.CFUNCTYPE(ctypes.c_ssize_t, + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_size_t)(('glfs_flistxattr', + client)) + + # TODO: creat and open fails on test_create_file_already_exists & test_open_file_not_exist functional testing, # noqa # when defined via function prototype.. Need to find RCA. For time being, using it from 'api.glfs_' # noqa diff --git a/gluster/gfapi.py b/gluster/gfapi.py index 8a02831..8ba7251 100755 --- a/gluster/gfapi.py +++ b/gluster/gfapi.py @@ -16,12 +16,35 @@ import errno from gluster import api from gluster.exceptions import LibgfapiException +# TODO: Move this utils.py +python_mode_to_os_flags = {} + + +def _populate_mode_to_flags_dict(): + # http://pubs.opengroup.org/onlinepubs/9699919799/functions/fopen.html + for mode in ['r', 'rb']: + python_mode_to_os_flags[mode] = os.O_RDONLY + for mode in ['w', 'wb']: + python_mode_to_os_flags[mode] = os.O_WRONLY | os.O_CREAT | os.O_TRUNC + for mode in ['a', 'ab']: + python_mode_to_os_flags[mode] = os.O_WRONLY | os.O_CREAT | os.O_APPEND + for mode in ['r+', 'rb+', 'r+b']: + python_mode_to_os_flags[mode] = os.O_RDWR + for mode in ['w+', 'wb+', 'w+b']: + python_mode_to_os_flags[mode] = os.O_RDWR | os.O_CREAT | os.O_TRUNC + for mode in ['a+', 'ab+', 'a+b']: + python_mode_to_os_flags[mode] = os.O_RDWR | os.O_CREAT | os.O_APPEND + +_populate_mode_to_flags_dict() + class File(object): - def __init__(self, fd, path=None): + def __init__(self, fd, path=None, mode=None): self.fd = fd self.originalpath = path + self._mode = mode + self._closed = False def __enter__(self): if self.fd is None: @@ -34,19 +57,40 @@ class File(object): def __exit__(self, type, value, tb): self.close() + @property + def fileno(self): + # TODO: Make self.fd private (self._fd) + return self.fd + + @property + def mode(self): + return self._mode + + @property + def name(self): + return self.originalpath + + @property + def closed(self): + return self._closed + def close(self): - ret = api.glfs_close(self.fd) - if ret < 0: - err = ctypes.get_errno() - raise OSError(err, os.strerror(err)) - return ret + """ + Close the file. A closed file cannot be read or written any more. + Calling close() more than once is allowed. + """ + if not self._closed: + ret = api.glfs_close(self.fd) + if ret < 0: + err = ctypes.get_errno() + raise OSError(err, os.strerror(err)) + self._closed = True def discard(self, offset, len): ret = api.client.glfs_discard(self.fd, offset, len) if ret < 0: err = ctypes.get_errno() raise OSError(err, os.strerror(err)) - return ret def dup(self): dupfd = api.glfs_dup(self.fd) @@ -55,25 +99,33 @@ class File(object): raise OSError(err, os.strerror(err)) return File(dupfd, self.originalpath) - def fallocate(self, mode, offset, len): - ret = api.client.glfs_fallocate(self.fd, mode, offset, len) + def fallocate(self, mode, offset, length): + """ + This is a Linux-specific sys call, unlike posix_fallocate() + + Allows the caller to directly manipulate the allocated disk space for + the file for the byte range starting at offset and continuing for + length bytes. + + :param mode: Operation to be performed on the given range + :param offset: Starting offset + :param length: Size in bytes, starting at offset + """ + ret = api.client.glfs_fallocate(self.fd, mode, offset, length) if ret < 0: err = ctypes.get_errno() raise OSError(err, os.strerror(err)) - return ret def fchmod(self, mode): """ Change this file's mode :param mode: new mode - :returns: 0 if success, raises OSError if it fails """ ret = api.glfs_fchmod(self.fd, mode) if ret < 0: err = ctypes.get_errno() raise OSError(err, os.strerror(err)) - return ret def fchown(self, uid, gid): """ @@ -81,35 +133,129 @@ class File(object): :param uid: new user id for file :param gid: new group id for file - :returns: 0 if success, raises OSError if it fails """ ret = api.glfs_fchown(self.fd, uid, gid) if ret < 0: err = ctypes.get_errno() raise OSError(err, os.strerror(err)) - return ret def fdatasync(self): """ - Force write of file - - :returns: 0 if success, raises OSError if it fails + Flush buffer cache pages pertaining to data, but not the ones + pertaining to metadata. """ ret = api.glfs_fdatasync(self.fd) if ret < 0: err = ctypes.get_errno() raise OSError(err, os.strerror(err)) - return ret def fgetsize(self): """ - Return the size of a file, reported by fstat() + Return the size of a file, as reported by fstat() + + :returns: the size of the file in bytes """ return self.fstat().st_size + def fgetxattr(self, key, size=0): + """ + Retrieve the value of the extended attribute identified by key + for the file. + + :param key: Key of extended attribute + :param size: If size is specified as zero, we first determine the + size of xattr and then allocate a buffer accordingly. + If size is non-zero, it is assumed the caller knows + the size of xattr. + :returns: Value of extended attribute corresponding to key specified. + """ + if size == 0: + size = api.glfs_fgetxattr(self.fd, key, None, size) + if size < 0: + err = ctypes.get_errno() + raise OSError(err, os.strerror(err)) + + buf = ctypes.create_string_buffer(size) + rc = api.glfs_fgetxattr(self.fd, key, buf, size) + if rc < 0: + err = ctypes.get_errno() + raise OSError(err, os.strerror(err)) + return buf.value[:rc] + + def flistxattr(self, size=0): + """ + Retrieve list of extended attributes for the file. + + :param size: If size is specified as zero, we first determine the + size of list and then allocate a buffer accordingly. + If size is non-zero, it is assumed the caller knows + the size of the list. + :returns: List of extended attributes. + """ + if size == 0: + size = api.glfs_flistxattr(self.fd, None, 0) + if size < 0: + err = ctypes.get_errno() + raise OSError(err, os.strerror(err)) + + buf = ctypes.create_string_buffer(size) + rc = api.glfs_flistxattr(self.fd, buf, size) + if rc < 0: + err = ctypes.get_errno() + raise OSError(err, os.strerror(err)) + xattrs = [] + # Parsing character by character is ugly, but it seems like the + # easiest way to deal with the "strings separated by NUL in one + # buffer" format. + i = 0 + while i < rc: + new_xa = buf.raw[i] + i += 1 + while i < rc: + next_char = buf.raw[i] + i += 1 + if next_char == '\0': + xattrs.append(new_xa) + break + new_xa += next_char + xattrs.sort() + return xattrs + + def fsetxattr(self, key, value, flags=0): + """ + Set extended attribute of file. + + :param key: The key of extended attribute. + :param value: The valiue of extended attribute. + :param flags: Possible values are 0 (default), 1 and 2 + 0: xattr will be created if it does not exist, or the + value will be replaced if the xattr exists. + 1: Perform a pure create, which fails if the named + attribute already exists. + 2: Perform a pure replace operation, which fails if the + named attribute does not already exist. + """ + ret = api.glfs_fsetxattr(self.fd, key, value, len(value), flags) + if ret < 0: + err = ctypes.get_errno() + raise OSError(err, os.strerror(err)) + + def fremovexattr(self, key): + """ + Remove a extended attribute of the file. + + :param key: The key of extended attribute. + """ + ret = api.glfs_fremovexattr(self.fd, key) + if ret < 0: + err = ctypes.get_errno() + raise OSError(err, os.strerror(err)) + def fstat(self): """ Returns Stat object for this file. + + :return: Returns the stat information of the file. """ s = api.Stat() rc = api.glfs_fstat(self.fd, ctypes.byref(s)) @@ -119,11 +265,28 @@ class File(object): return s def fsync(self): + """ + Flush buffer cache pages pertaining to data and metadata. + """ ret = api.glfs_fsync(self.fd) if ret < 0: err = ctypes.get_errno() raise OSError(err, os.strerror(err)) - return ret + + def ftruncate(self, length): + """ + Truncated the file to a size of length bytes. + + If the file previously was larger than this size, the extra data is + lost. If the file previously was shorter, it is extended, and the + extended part reads as null bytes. + + :param length: Length to truncate the file to in bytes. + """ + ret = api.glfs_ftruncate(self.fd, length) + if ret < 0: + err = ctypes.get_errno() + raise OSError(err, os.strerror(err)) def lseek(self, pos, how): """ @@ -136,31 +299,38 @@ class File(object): to the current position and SEEK_END sets the position relative to the end of the file. :returns: the new offset position - """ - return api.glfs_lseek(self.fd, pos, how) + ret = api.glfs_lseek(self.fd, pos, how) + if ret < 0: + err = ctypes.get_errno() + raise OSError(err, os.strerror(err)) + return ret - def read(self, buflen=-1): + def read(self, size=-1): """ - read file + Read at most size bytes from the file. :param buflen: length of read buffer. If less than 0, then whole file is read. Default is -1. - :returns: buffer of size buflen + :returns: buffer of 'size' length """ - if buflen < 0: - buflen = self.fgetsize() - rbuf = ctypes.create_string_buffer(buflen) - ret = api.glfs_read(self.fd, rbuf, buflen, 0) + if size < 0: + size = self.fgetsize() + rbuf = ctypes.create_string_buffer(size) + ret = api.glfs_read(self.fd, rbuf, size, 0) if ret > 0: return rbuf elif ret < 0: err = ctypes.get_errno() raise OSError(err, os.strerror(err)) - else: - return ret def write(self, data, flags=0): + """ + Write data to the file. + + :param data: The data to be written to file. + :returns: The size in bytes actually written + """ # creating a ctypes.c_ubyte buffer to handle converting bytearray # to the required C data type @@ -341,7 +511,6 @@ class Volume(object): if ret < 0: err = ctypes.get_errno() raise OSError(err, os.strerror(err)) - return ret def chown(self, path, uid, gid): """ @@ -356,7 +525,6 @@ class Volume(object): if ret < 0: err = ctypes.get_errno() raise OSError(err, os.strerror(err)) - return ret def exists(self, path): """ @@ -504,9 +672,52 @@ class Volume(object): if ret < 0: err = ctypes.get_errno() raise OSError(err, os.strerror(err)) - return ret + + def fopen(self, path, mode='r'): + """ + Similar to Python's built-in File object returned by Python's open() + + Unlike Python's open(), fopen() provided here is only for convenience + and it does NOT invoke glibc's fopen and does NOT do any kind of + I/O bufferring as of today. + + :param path: Path of file to be opened + :param mode: Mode to open the file with. This is a string. + :returns: an instance of File class + """ + if not isinstance(mode, basestring): + raise TypeError("Mode must be a string") + try: + flags = python_mode_to_os_flags[mode] + except KeyError: + raise ValueError("Invalid mode") + else: + if (os.O_CREAT & flags) == os.O_CREAT: + fd = api.client.glfs_creat(self.fs, path, flags, 0666) + else: + fd = api.client.glfs_open(self.fs, path, flags) + if not fd: + err = ctypes.get_errno() + raise OSError(err, os.strerror(err)) + return File(fd, path=path, mode=mode) def open(self, path, flags, mode=0777): + """ + Similar to Python's os.open() + + As of today, the only way to consume the raw glfd returned is by + passing it to File class. + + :param path: Path of file to be opened + :param flags: Integer which flags must include one of the following + access modes: os.O_RDONLY, os.O_WRONLY, or os.O_RDWR. + :param mode: specifies the permissions to use in case a new + file is created. + :returns: the raw glfd + """ + if not isinstance(flags, int): + raise TypeError("flags must evaluate to an integer") + if (os.O_CREAT & flags) == os.O_CREAT: # FIXME: # Without direct call to _api the functest fails on creat and open. @@ -518,7 +729,7 @@ class Volume(object): err = ctypes.get_errno() raise OSError(err, os.strerror(err)) - return File(fd, path) + return fd def opendir(self, path): fd = api.glfs_opendir(self.fs, path) @@ -532,21 +743,18 @@ class Volume(object): if ret < 0: err = ctypes.get_errno() raise IOError(err, os.strerror(err)) - return ret def rename(self, opath, npath): ret = api.glfs_rename(self.fs, opath, npath) if ret < 0: err = ctypes.get_errno() raise OSError(err, os.strerror(err)) - return ret def rmdir(self, path): ret = api.glfs_rmdir(self.fs, path) if ret < 0: err = ctypes.get_errno() raise OSError(err, os.strerror(err)) - return ret def rmtree(self, path, ignore_errors=False, onerror=None): """ @@ -594,21 +802,18 @@ class Volume(object): if ret < 0: err = ctypes.get_errno() raise OSError(err, os.strerror(err)) - return ret def setfsgid(self, gid): ret = api.glfs_setfsgid(gid) if ret < 0: err = ctypes.get_errno() raise OSError(err, os.strerror(err)) - return ret def setxattr(self, path, key, value, vlen): ret = api.glfs_setxattr(self.fs, path, key, value, vlen, 0) if ret < 0: err = ctypes.get_errno() raise IOError(err, os.strerror(err)) - return ret def stat(self, path): s = api.Stat() @@ -638,7 +843,6 @@ class Volume(object): if ret < 0: err = ctypes.get_errno() raise OSError(err, os.strerror(err)) - return ret def unlink(self, path): """ @@ -651,7 +855,6 @@ class Volume(object): if ret < 0: err = ctypes.get_errno() raise OSError(err, os.strerror(err)) - return ret def walk(self, top, topdown=True, onerror=None, followlinks=False): """ diff --git a/test/functional/libgfapi-python-tests.py b/test/functional/libgfapi-python-tests.py index c0e760a..1244fd3 100644 --- a/test/functional/libgfapi-python-tests.py +++ b/test/functional/libgfapi-python-tests.py @@ -14,7 +14,7 @@ import os import types import errno -from gluster import gfapi +from gluster.gfapi import File, Volume from gluster.exceptions import LibgfapiException from test import get_test_config from ConfigParser import NoSectionError, NoOptionError @@ -43,7 +43,7 @@ class BinFileOpsTest(unittest.TestCase): @classmethod def setUpClass(cls): - cls.vol = gfapi.Volume(HOST, VOLNAME) + cls.vol = Volume(HOST, VOLNAME) ret = cls.vol.mount() if ret == 0: # Cleanup volume @@ -57,14 +57,14 @@ class BinFileOpsTest(unittest.TestCase): def setUp(self): self.data = bytearray([(k % 128) for k in range(0, 1024)]) self.path = self._testMethodName + ".io" - with self.vol.open(self.path, os.O_CREAT | os.O_WRONLY | os.O_EXCL, - 0644) as fd: - fd.write(self.data) + with File(self.vol.open(self.path, + os.O_CREAT | os.O_WRONLY | os.O_EXCL, 0644)) as f: + f.write(self.data) def test_bin_open_and_read(self): - with self.vol.open(self.path, os.O_RDONLY) as fd: - self.assertTrue(isinstance(fd, gfapi.File)) - buf = fd.read(len(self.data)) + with File(self.vol.open(self.path, os.O_RDONLY)) as f: + self.assertTrue(isinstance(f, File)) + buf = f.read(len(self.data)) self.assertFalse(isinstance(buf, types.IntType)) self.assertEqual(buf, self.data) @@ -77,7 +77,7 @@ class FileOpsTest(unittest.TestCase): @classmethod def setUpClass(cls): - cls.vol = gfapi.Volume(HOST, VOLNAME) + cls.vol = Volume(HOST, VOLNAME) ret = cls.vol.mount() if ret == 0: # Cleanup volume @@ -91,39 +91,117 @@ class FileOpsTest(unittest.TestCase): def setUp(self): self.data = "gluster is awesome" self.path = self._testMethodName + ".io" - with self.vol.open(self.path, os.O_CREAT | os.O_WRONLY | os.O_EXCL, - 0644) as fd: - rc = fd.write(self.data) + with File(self.vol.open(self.path, + os.O_CREAT | os.O_WRONLY | os.O_EXCL, 0644), + path=self.path) as f: + rc = f.write(self.data) self.assertEqual(rc, len(self.data)) - ret = fd.fsync() - self.assertEqual(ret, 0) - self.assertEqual(fd.originalpath, self.path) + f.fsync() + self.assertEqual(f.originalpath, self.path) def tearDown(self): self.path = None self.data = None def test_open_and_read(self): - with self.vol.open(self.path, os.O_RDONLY) as fd: - self.assertTrue(isinstance(fd, gfapi.File)) - buf = fd.read(len(self.data)) + with File(self.vol.open(self.path, os.O_RDONLY)) as f: + self.assertTrue(isinstance(f, File)) + buf = f.read(len(self.data)) self.assertFalse(isinstance(buf, types.IntType)) self.assertEqual(buf.value, self.data) def test_open_file_not_exist(self): try: - f = self.vol.open("filenotexist", os.O_WRONLY) + f = File(self.vol.open("filenotexist", os.O_WRONLY)) except OSError as e: self.assertEqual(e.errno, errno.ENOENT) else: f.close() self.fail("Expected a OSError with errno.ENOENT") + def test_open_err(self): + # flags not int + self.assertRaises(TypeError, self.vol.open, "file", 'w') + # invalid flags + self.assertRaises(OSError, self.vol.open, "file", + 12345) + + def test_fopen_err(self): + # mode not string + self.assertRaises(TypeError, self.vol.fopen, "file", os.O_WRONLY) + # invalid mode + self.assertRaises(ValueError, self.vol.fopen, "file", 'x+') + # file does not exist + self.assertRaises(OSError, self.vol.fopen, "file", 'r') + + def test_fopen(self): + # Default permission should be 0666 + name = uuid4().hex + data = "Gluster is so awesome" + with self.vol.fopen(name, 'w') as f: + f.write(data) + perms = self.vol.stat(name).st_mode & 0777 + self.assertEqual(perms, int(0666)) + + # 'r': Open file for reading. + # If not specified, mode should default to 'r' + with self.vol.fopen(name) as f: + self.assertEqual('r', f.mode) + self.assertEqual(f.lseek(0, os.SEEK_CUR), 0) + self.assertEqual(f.read().value, data) + + # 'r+': Open for reading and writing. + with self.vol.fopen(name, 'r+') as f: + self.assertEqual(f.lseek(0, os.SEEK_CUR), 0) + self.assertEqual('r+', f.mode) + # ftruncate doesn't (and shouldn't) change offset + f.ftruncate(0) + # writes should pass + f.write(data) + f.lseek(0, os.SEEK_SET) + self.assertEqual(f.read().value, data) + + # 'w': Truncate file to zero length or create text file for writing. + self.assertEqual(self.vol.getsize(name), len(data)) + with self.vol.fopen(name, 'w') as f: + self.assertEqual('w', f.mode) + f.fsync() + self.assertEqual(self.vol.getsize(name), 0) + f.write(data) + + # 'w+': Open for reading and writing. The file is created if it does + # not exist, otherwise it is truncated. + with self.vol.fopen(name, 'w+') as f: + self.assertEqual('w+', f.mode) + f.fsync() + self.assertEqual(self.vol.getsize(name), 0) + f.write(data) + f.lseek(0, os.SEEK_SET) + self.assertEqual(f.read().value, data) + + # 'a': Open for appending (writing at end of file). The file is + # created if it does not exist. + with self.vol.fopen(name, 'a') as f: + self.assertEqual('a', f.mode) + # This should be appended at the end + f.write("hello") + with self.vol.fopen(name) as f: + self.assertEqual(f.read().value, data + "hello") + + # 'a+': Open for reading and appending (writing at end of file) + with self.vol.fopen(name, 'a+') as f: + self.assertEqual('a+', f.mode) + # This should be appended at the end + f.write(" world") + f.fsync() + f.lseek(0, os.SEEK_SET) + self.assertEqual(f.read().value, data + "hello world") + def test_create_file_already_exists(self): try: - f = self.vol.open("newfile", os.O_CREAT) + f = File(self.vol.open("newfile", os.O_CREAT)) f.close() - g = self.vol.open("newfile", os.O_CREAT | os.O_EXCL) + g = File(self.vol.open("newfile", os.O_CREAT | os.O_EXCL)) except OSError as e: self.assertEqual(e.errno, errno.EEXIST) else: @@ -132,10 +210,10 @@ class FileOpsTest(unittest.TestCase): def test_write_file_dup_lseek_read(self): try: - f = self.vol.open("dune", os.O_CREAT | os.O_EXCL | os.O_RDWR) + f = File(self.vol.open("dune", os.O_CREAT | os.O_EXCL | os.O_RDWR)) f.write("I must not fear. Fear is the mind-killer.") fdup = f.dup() - self.assertTrue(isinstance(fdup, gfapi.File)) + self.assertTrue(isinstance(fdup, File)) f.close() ret = fdup.lseek(0, os.SEEK_SET) self.assertEqual(ret, 0) @@ -157,8 +235,7 @@ class FileOpsTest(unittest.TestCase): stat = self.vol.stat(self.path) orig_mode = oct(stat.st_mode & 0777) self.assertEqual(orig_mode, '0644L') - ret = self.vol.chmod(self.path, 0600) - self.assertEqual(ret, 0) + self.vol.chmod(self.path, 0600) stat = self.vol.stat(self.path) new_mode = oct(stat.st_mode & 0777) self.assertEqual(new_mode, '0600L') @@ -185,8 +262,7 @@ class FileOpsTest(unittest.TestCase): def test_symlink(self): link = self._testMethodName + ".link" - ret = self.vol.symlink(self.path, link) - self.assertEqual(ret, 0) + self.vol.symlink(self.path, link) islink = self.vol.islink(link) self.assertTrue(islink) @@ -201,8 +277,7 @@ class FileOpsTest(unittest.TestCase): def test_rename(self): newpath = self.path + ".rename" - ret = self.vol.rename(self.path, newpath) - self.assertEqual(ret, 0) + self.vol.rename(self.path, newpath) self.assertRaises(OSError, self.vol.lstat, self.path) def test_stat(self): @@ -211,16 +286,13 @@ class FileOpsTest(unittest.TestCase): self.assertEqual(sb.st_size, len(self.data)) def test_unlink(self): - ret = self.vol.unlink(self.path) - self.assertEqual(ret, 0) + self.vol.unlink(self.path) self.assertRaises(OSError, self.vol.lstat, self.path) def test_xattr(self): key1, key2 = "hello", "world" - ret1 = self.vol.setxattr(self.path, "trusted.key1", key1, len(key1)) - self.assertEqual(ret1, 0) - ret2 = self.vol.setxattr(self.path, "trusted.key2", key2, len(key2)) - self.assertEqual(ret2, 0) + self.vol.setxattr(self.path, "trusted.key1", key1, len(key1)) + self.vol.setxattr(self.path, "trusted.key2", key2, len(key2)) xattrs = self.vol.listxattr(self.path) self.assertFalse(isinstance(xattrs, types.IntType)) @@ -230,13 +302,79 @@ class FileOpsTest(unittest.TestCase): self.assertFalse(isinstance(buf, types.IntType)) self.assertEqual(buf, "hello") - ret3 = self.vol.removexattr(self.path, "trusted.key1") - self.assertEqual(ret3, 0) + self.vol.removexattr(self.path, "trusted.key1") xattrs = self.vol.listxattr(self.path) self.assertFalse(isinstance(xattrs, types.IntType)) self.assertTrue(["trusted.key1"] not in xattrs) + def test_fsetxattr(self): + name = uuid4().hex + with File(self.vol.open(name, os.O_WRONLY | os.O_CREAT)) as f: + f.fsetxattr("user.gluster", "awesome") + self.assertEqual(f.fgetxattr("user.gluster"), "awesome") + # flag = 1 behavior: fail if xattr exists + self.assertRaises(OSError, f.fsetxattr, "user.gluster", + "more awesome", flags=1) + # flag = 1 behavior: pass if xattr does not exist + f.fsetxattr("user.gluster2", "awesome2", flags=1) + self.assertEqual(f.fgetxattr("user.gluster2"), "awesome2") + # flag = 2 behavior: fail if xattr does not exist + self.assertRaises(OSError, f.fsetxattr, "user.whatever", + "awesome", flags=2) + # flag = 2 behavior: pass if xattr exists + f.fsetxattr("user.gluster", "more awesome", flags=2) + self.assertEqual(f.fgetxattr("user.gluster"), "more awesome") + + def test_fremovexattr(self): + name = uuid4().hex + with File(self.vol.open(name, os.O_WRONLY | os.O_CREAT)) as f: + f.fsetxattr("user.gluster", "awesome") + f.fremovexattr("user.gluster") + # The xattr now shouldn't exist + self.assertRaises(OSError, f.fgetxattr, "user.gluster") + # Removing an xattr that does not exist + self.assertRaises(OSError, f.fremovexattr, "user.gluster") + + def test_fgetxattr(self): + name = uuid4().hex + with File(self.vol.open(name, os.O_WRONLY | os.O_CREAT)) as f: + f.fsetxattr("user.gluster", "awesome") + # user does not know the size of value beforehand + self.assertEqual(f.fgetxattr("user.gluster"), "awesome") + # user knows the size of value beforehand + self.assertEqual(f.fgetxattr("user.gluster", 7), "awesome") + + def test_ftruncate(self): + name = uuid4().hex + with File(self.vol.open(name, os.O_WRONLY | os.O_CREAT)) as f: + f.write("123456789") + f.ftruncate(5) + f.fsync() + with File(self.vol.open(name, os.O_RDONLY)) as f: + # The size should be reduced + self.assertEqual(f.fgetsize(), 5) + # So should be the content. + self.assertEqual(f.read().value, "12345") + + def test_flistxattr(self): + name = uuid4().hex + with File(self.vol.open(name, os.O_RDWR | os.O_CREAT)) as f: + f.fsetxattr("user.gluster", "awesome") + f.fsetxattr("user.gluster2", "awesome2") + xattrs = f.flistxattr() + self.assertTrue("user.gluster" in xattrs) + self.assertTrue("user.gluster2" in xattrs) + # Test passing of size + # larger size - should pass + xattrs = f.flistxattr(size=512) + self.assertTrue("user.gluster" in xattrs) + self.assertTrue("user.gluster2" in xattrs) + # smaller size - should fail + self.assertRaises(OSError, f.flistxattr, size=1) + # invalid size - should fail + self.assertRaises(ValueError, f.flistxattr, size=-1) + class DirOpsTest(unittest.TestCase): @@ -246,7 +384,7 @@ class DirOpsTest(unittest.TestCase): @classmethod def setUpClass(cls): - cls.vol = gfapi.Volume(HOST, VOLNAME) + cls.vol = Volume(HOST, VOLNAME) ret = cls.vol.mount() if ret == 0: # Cleanup volume @@ -265,12 +403,11 @@ class DirOpsTest(unittest.TestCase): self.vol.mkdir(self.dir_path, 0755) for x in range(0, 3): f = os.path.join(self.dir_path, self.testfile + str(x)) - with self.vol.open(f, os.O_CREAT | os.O_WRONLY | os.O_EXCL, - 0644) as fd: - rc = fd.write(self.data) + with File(self.vol.open(f, os.O_CREAT | os.O_WRONLY | os.O_EXCL, + 0644)) as f: + rc = f.write(self.data) self.assertEqual(rc, len(self.data)) - ret = fd.fdatasync() - self.assertEqual(ret, 0) + f.fdatasync() def tearDown(self): self.dir_path = None @@ -318,7 +455,7 @@ class TestVolumeInit(unittest.TestCase): def test_mount_unmount_default(self): # Create volume object instance - vol = gfapi.Volume(HOST, VOLNAME) + vol = Volume(HOST, VOLNAME) # Check attribute init self.assertEqual(vol.log_file, None) self.assertEqual(vol.log_level, 7) @@ -348,19 +485,19 @@ class TestVolumeInit(unittest.TestCase): def test_mount_err(self): # Volume does not exist fake_volname = str(uuid4().hex)[:10] - vol = gfapi.Volume(HOST, fake_volname) + vol = Volume(HOST, fake_volname) self.assertRaises(LibgfapiException, vol.mount) self.assertFalse(vol.mounted) # Invalid host - glfs_set_volfile_server will fail fake_hostname = str(uuid4().hex)[:10] - vol = gfapi.Volume(fake_hostname, VOLNAME) + vol = Volume(fake_hostname, VOLNAME) self.assertRaises(LibgfapiException, vol.mount) self.assertFalse(vol.mounted) def test_set_logging(self): # Create volume object instance - vol = gfapi.Volume(HOST, VOLNAME) + vol = Volume(HOST, VOLNAME) # Call set_logging before mount() log_file = "/tmp/%s" % (uuid4().hex) vol.set_logging(log_file, 7) diff --git a/test/unit/gluster/test_gfapi.py b/test/unit/gluster/test_gfapi.py index 8880727..9fcfbf5 100644 --- a/test/unit/gluster/test_gfapi.py +++ b/test/unit/gluster/test_gfapi.py @@ -15,7 +15,7 @@ import os import stat import errno -from gluster import gfapi +from gluster.gfapi import File, Dir, Volume from gluster import api from gluster.exceptions import LibgfapiException from nose import SkipTest @@ -55,7 +55,7 @@ class TestFile(unittest.TestCase): @classmethod def setUpClass(cls): - cls.fd = gfapi.File(2, 'fakefile') + cls.fd = File(2, 'fakefile') @classmethod def tearDownClass(cls): @@ -73,8 +73,7 @@ class TestFile(unittest.TestCase): mock_glfs_fchmod.return_value = 0 with patch("gluster.gfapi.api.glfs_fchmod", mock_glfs_fchmod): - ret = self.fd.fchmod(0600) - self.assertEquals(ret, 0) + self.fd.fchmod(0600) def test_fchmod_fail_exception(self): mock_glfs_fchmod = Mock() @@ -88,8 +87,7 @@ class TestFile(unittest.TestCase): mock_glfs_fchown.return_value = 0 with patch("gluster.gfapi.api.glfs_fchown", mock_glfs_fchown): - ret = self.fd.fchown(9, 11) - self.assertEquals(ret, 0) + self.fd.fchown(9, 11) def test_fchown_fail_exception(self): mock_glfs_fchown = Mock() @@ -104,7 +102,7 @@ class TestFile(unittest.TestCase): with patch("gluster.gfapi.api.glfs_dup", mock_glfs_dup): f = self.fd.dup() - self.assertTrue(isinstance(f, gfapi.File)) + self.assertTrue(isinstance(f, File)) self.assertEqual(f.originalpath, "fakefile") self.assertEqual(f.fd, 2) @@ -113,8 +111,7 @@ class TestFile(unittest.TestCase): mock_glfs_fdatasync.return_value = 4 with patch("gluster.gfapi.api.glfs_fdatasync", mock_glfs_fdatasync): - ret = self.fd.fdatasync() - self.assertEquals(ret, 4) + self.fd.fdatasync() def test_fdatasync_fail_exception(self): mock_glfs_fdatasync = Mock() @@ -140,11 +137,9 @@ class TestFile(unittest.TestCase): def test_fsync_success(self): mock_glfs_fsync = Mock() - mock_glfs_fsync.return_value = 4 with patch("gluster.gfapi.api.glfs_fsync", mock_glfs_fsync): - ret = self.fd.fsync() - self.assertEquals(ret, 4) + self.fd.fsync() def test_fsync_fail_exception(self): mock_glfs_fsync = Mock() @@ -182,8 +177,7 @@ class TestFile(unittest.TestCase): mock_glfs_read.return_value = 0 with patch("gluster.gfapi.api.glfs_read", mock_glfs_read): - b = self.fd.read(5) - self.assertEqual(b, 0) + self.fd.read(5) def test_read_buflen_negative(self): _mock_fgetsize = Mock(return_value=12345) @@ -273,9 +267,9 @@ class TestDir(unittest.TestCase): return 0 with patch("gluster.gfapi.api.glfs_readdir_r", mock_glfs_readdir_r): - fd = gfapi.Dir(2) + fd = Dir(2) ent = fd.next() - self.assertTrue(isinstance(ent, gfapi.Dirent)) + self.assertTrue(isinstance(ent, api.Dirent)) class TestVolume(unittest.TestCase): @@ -305,7 +299,7 @@ class TestVolume(unittest.TestCase): cls._saved_glfs_set_logging = gluster.gfapi.api.glfs_set_logging gluster.gfapi.api.glfs_set_logging = _mock_glfs_set_logging - cls.vol = gfapi.Volume("mockhost", "test") + cls.vol = Volume("mockhost", "test") cls.vol.fs = 12345 cls.vol._mounted = True @@ -320,15 +314,15 @@ class TestVolume(unittest.TestCase): gluster.gfapi.api.glfs_closedir = cls._saved_glfs_closedir def test_initialization_error(self): - self.assertRaises(LibgfapiException, gfapi.Volume, "host", None) - self.assertRaises(LibgfapiException, gfapi.Volume, None, "vol") - self.assertRaises(LibgfapiException, gfapi.Volume, None, None) - self.assertRaises(LibgfapiException, gfapi.Volume, "host", "vol", "ZZ") - self.assertRaises(LibgfapiException, gfapi.Volume, "host", "vol", + self.assertRaises(LibgfapiException, Volume, "host", None) + self.assertRaises(LibgfapiException, Volume, None, "vol") + self.assertRaises(LibgfapiException, Volume, None, None) + self.assertRaises(LibgfapiException, Volume, "host", "vol", "ZZ") + self.assertRaises(LibgfapiException, Volume, "host", "vol", "tcp", "invalid_port") def test_initialization_success(self): - v = gfapi.Volume("host", "vol", "tcp", 9876) + v = Volume("host", "vol", "tcp", 9876) self.assertEqual(v.host, "host") self.assertEqual(v.volname, "vol") self.assertEqual(v.protocol, "tcp") @@ -336,7 +330,7 @@ class TestVolume(unittest.TestCase): self.assertFalse(v.mounted) def test_mount_unmount_success(self): - v = gfapi.Volume("host", "vol") + v = Volume("host", "vol") v.mount() self.assertTrue(v.mounted) self.assertTrue(v.fs) @@ -346,7 +340,7 @@ class TestVolume(unittest.TestCase): def test_mount_multiple(self): _m_glfs_new = Mock() - v = gfapi.Volume("host", "vol") + v = Volume("host", "vol") with patch("gluster.gfapi.api.glfs_new", _m_glfs_new): # Mounting for first time v.mount() @@ -360,7 +354,7 @@ class TestVolume(unittest.TestCase): def test_mount_error(self): # glfs_new() failed _m_glfs_new = Mock(return_value=None) - v = gfapi.Volume("host", "vol") + v = Volume("host", "vol") with patch("gluster.gfapi.api.glfs_new", _m_glfs_new): self.assertRaises(LibgfapiException, v.mount) self.assertFalse(v.fs) @@ -369,7 +363,7 @@ class TestVolume(unittest.TestCase): # glfs_set_volfile_server() failed _m_set_vol = Mock(return_value=-1) - v = gfapi.Volume("host", "vol") + v = Volume("host", "vol") with patch("gluster.gfapi.api.glfs_set_volfile_server", _m_set_vol): self.assertRaises(LibgfapiException, v.mount) self.assertFalse(v.mounted) @@ -379,14 +373,14 @@ class TestVolume(unittest.TestCase): # glfs_init() failed _m_glfs_init = Mock(return_value=-1) - v = gfapi.Volume("host", "vol") + v = Volume("host", "vol") with patch("gluster.gfapi.api.glfs_init", _m_glfs_init): self.assertRaises(LibgfapiException, v.mount) self.assertFalse(v.mounted) _m_glfs_init.assert_caled_once_with(v.fs) def test_unmount_error(self): - v = gfapi.Volume("host", "vol") + v = Volume("host", "vol") v.mount() _m_glfs_fini = Mock(return_value=-1) with patch("gluster.gfapi.api.glfs_fini", _m_glfs_fini): @@ -399,7 +393,7 @@ class TestVolume(unittest.TestCase): _m_set_logging = Mock() # Called after mount() - v = gfapi.Volume("host", "vol") + v = Volume("host", "vol") with patch("gluster.gfapi.api.glfs_set_logging", _m_set_logging): v.mount() v.set_logging("/path/whatever", 7) @@ -407,7 +401,7 @@ class TestVolume(unittest.TestCase): self.assertEqual(v.log_level, 7) def test_set_logging_err(self): - v = gfapi.Volume("host", "vol") + v = Volume("host", "vol") v.fs = 12345 _m_set_logging = Mock(return_value=-1) with patch("gluster.gfapi.api.glfs_set_logging", _m_set_logging): @@ -419,8 +413,7 @@ class TestVolume(unittest.TestCase): mock_glfs_chmod.return_value = 0 with patch("gluster.gfapi.api.glfs_chmod", mock_glfs_chmod): - ret = self.vol.chmod("file.txt", 0600) - self.assertEquals(ret, 0) + self.vol.chmod("file.txt", 0600) def test_chmod_fail_exception(self): mock_glfs_chmod = Mock() @@ -434,8 +427,7 @@ class TestVolume(unittest.TestCase): mock_glfs_chown.return_value = 0 with patch("gluster.gfapi.api.glfs_chown", mock_glfs_chown): - ret = self.vol.chown("file.txt", 9, 11) - self.assertEquals(ret, 0) + self.vol.chown("file.txt", 9, 11) def test_chown_fail_exception(self): mock_glfs_chown = Mock() @@ -449,8 +441,8 @@ class TestVolume(unittest.TestCase): mock_glfs_creat.return_value = 2 with patch("gluster.api.client.glfs_creat", mock_glfs_creat): - with self.vol.open("file.txt", os.O_CREAT, 0644) as fd: - self.assertTrue(isinstance(fd, gfapi.File)) + with File(self.vol.open("file.txt", os.O_CREAT, 0644)) as f: + self.assertTrue(isinstance(f, File)) self.assertEqual(mock_glfs_creat.call_count, 1) mock_glfs_creat.assert_called_once_with(12345, "file.txt", @@ -710,8 +702,7 @@ class TestVolume(unittest.TestCase): mock_glfs_mkdir.return_value = 0 with patch("gluster.gfapi.api.glfs_mkdir", mock_glfs_mkdir): - ret = self.vol.mkdir("testdir", 0775) - self.assertEquals(ret, 0) + self.vol.mkdir("testdir", 0775) def test_mkdir_fail_exception(self): mock_glfs_mkdir = Mock() @@ -725,8 +716,8 @@ class TestVolume(unittest.TestCase): mock_glfs_open.return_value = 2 with patch("gluster.api.client.glfs_open", mock_glfs_open): - with self.vol.open("file.txt", os.O_WRONLY) as fd: - self.assertTrue(isinstance(fd, gfapi.File)) + with File(self.vol.open("file.txt", os.O_WRONLY)) as f: + self.assertTrue(isinstance(f, File)) self.assertEqual(mock_glfs_open.call_count, 1) mock_glfs_open.assert_called_once_with(12345, "file.txt", os.O_WRONLY) @@ -747,8 +738,8 @@ class TestVolume(unittest.TestCase): mock_glfs_open.return_value = 2 with patch("gluster.api.client.glfs_open", mock_glfs_open): - fd = self.vol.open("file.txt", os.O_WRONLY) - self.assertTrue(isinstance(fd, gfapi.File)) + f = File(self.vol.open("file.txt", os.O_WRONLY)) + self.assertTrue(isinstance(f, File)) self.assertEqual(mock_glfs_open.call_count, 1) mock_glfs_open.assert_called_once_with(12345, "file.txt", os.O_WRONLY) @@ -766,7 +757,7 @@ class TestVolume(unittest.TestCase): with patch("gluster.gfapi.api.glfs_opendir", mock_glfs_opendir): d = self.vol.opendir("testdir") - self.assertTrue(isinstance(d, gfapi.Dir)) + self.assertTrue(isinstance(d, Dir)) def test_opendir_fail_exception(self): mock_glfs_opendir = Mock() @@ -780,8 +771,7 @@ class TestVolume(unittest.TestCase): mock_glfs_rename.return_value = 0 with patch("gluster.gfapi.api.glfs_rename", mock_glfs_rename): - ret = self.vol.rename("file.txt", "newfile.txt") - self.assertEquals(ret, 0) + self.vol.rename("file.txt", "newfile.txt") def test_rename_fail_exception(self): mock_glfs_rename = Mock() @@ -796,8 +786,7 @@ class TestVolume(unittest.TestCase): mock_glfs_rmdir.return_value = 0 with patch("gluster.gfapi.api.glfs_rmdir", mock_glfs_rmdir): - ret = self.vol.rmdir("testdir") - self.assertEquals(ret, 0) + self.vol.rmdir("testdir") def test_rmdir_fail_exception(self): mock_glfs_rmdir = Mock() @@ -811,8 +800,7 @@ class TestVolume(unittest.TestCase): mock_glfs_unlink.return_value = 0 with patch("gluster.gfapi.api.glfs_unlink", mock_glfs_unlink): - ret = self.vol.unlink("file.txt") - self.assertEquals(ret, 0) + self.vol.unlink("file.txt") def test_unlink_fail_exception(self): mock_glfs_unlink = Mock() @@ -827,8 +815,7 @@ class TestVolume(unittest.TestCase): with patch("gluster.gfapi.api.glfs_removexattr", mock_glfs_removexattr): - ret = self.vol.removexattr("file.txt", "key1") - self.assertEquals(ret, 0) + self.vol.removexattr("file.txt", "key1") def test_removexattr_fail_exception(self): mock_glfs_removexattr = Mock() @@ -918,8 +905,7 @@ class TestVolume(unittest.TestCase): mock_glfs_setfsuid.return_value = 0 with patch("gluster.gfapi.api.glfs_setfsuid", mock_glfs_setfsuid): - ret = self.vol.setfsuid(1000) - self.assertEquals(ret, 0) + self.vol.setfsuid(1000) def test_setfsuid_fail(self): mock_glfs_setfsuid = Mock() @@ -933,8 +919,7 @@ class TestVolume(unittest.TestCase): mock_glfs_setfsgid.return_value = 0 with patch("gluster.gfapi.api.glfs_setfsgid", mock_glfs_setfsgid): - ret = self.vol.setfsgid(1000) - self.assertEquals(ret, 0) + self.vol.setfsgid(1000) def test_setfsgid_fail(self): mock_glfs_setfsgid = Mock() @@ -948,8 +933,7 @@ class TestVolume(unittest.TestCase): mock_glfs_setxattr.return_value = 0 with patch("gluster.gfapi.api.glfs_setxattr", mock_glfs_setxattr): - ret = self.vol.setxattr("file.txt", "key1", "hello", 5) - self.assertEquals(ret, 0) + self.vol.setxattr("file.txt", "key1", "hello", 5) def test_setxattr_fail_exception(self): mock_glfs_setxattr = Mock() @@ -964,8 +948,7 @@ class TestVolume(unittest.TestCase): mock_glfs_symlink.return_value = 0 with patch("gluster.gfapi.api.glfs_symlink", mock_glfs_symlink): - ret = self.vol.symlink("file.txt", "filelink") - self.assertEquals(ret, 0) + self.vol.symlink("file.txt", "filelink") def test_symlink_fail_exception(self): mock_glfs_symlink = Mock() |