summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xgluster/api.py28
-rwxr-xr-xgluster/gfapi.py289
-rw-r--r--test/functional/libgfapi-python-tests.py231
-rw-r--r--test/unit/gluster/test_gfapi.py103
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()