summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPrashanth Pai <ppai@redhat.com>2015-06-10 20:45:20 +0530
committerThiago da Silva <thiago@redhat.com>2015-06-18 08:49:35 -0400
commit509abefca5902e4f0decf40368a90265d1a598bd (patch)
tree151568be15e44e8b9c4603b5e0f48c3c1716dc18
parent658ebecb84951fd8e418f15a5c8e2c8b1901b9d4 (diff)
Add missing File APIs
Volume.fopen() method mimics Python's built-in File object Example: with v.fopen("/path/to/file", "w") as f: f.write("hello world") fopen() returns a File object. Volume.open() method mimics os.open() Python API. Example: with File(v.open("/path/to/file", os.O_WRONLY | os.O_CREAT)) as f: f.write("hello world") open() returns the raw glfd that (as of today) needs to be passed to File class. In future, more APIs will be provided to directly use the glfd returned. Unlike their C versions, these methods do not return anything on success. If methods fail, exceptions are raised. Added docstrings to methods in File class. Change-Id: Ie46fc77a899806d396762e6740e1538ea298d6e2 Signed-off-by: Prashanth Pai <ppai@redhat.com> Signed-off-by: Thiago da Silva <thiago@redhat.com>
-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()