summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPrashanth Pai <ppai@redhat.com>2015-06-14 23:57:42 +0530
committerThiago da Silva <thiago@redhat.com>2015-06-18 05:52:33 -0700
commitddbd7b570d0a9f599b417a499c912c5b13a003c9 (patch)
tree2dca993a300775af2367978c94ac34d7e332e3a1
parent509abefca5902e4f0decf40368a90265d1a598bd (diff)
Add missing Path based operation APIs
* Added commonly used path based operations that were missing from the Volume class. * Fixed function prototypes at places where it should be ssize_t and not size_t. This caused overflow error at times. * Added doc strings wherever they were missing. Change-Id: I6ce28038da3cd0f89ab075045bb4092dd95e21c4 Signed-off-by: Prashanth Pai <ppai@redhat.com>
-rwxr-xr-xgluster/api.py27
-rwxr-xr-xgluster/gfapi.py182
-rw-r--r--test/functional/libgfapi-python-tests.py129
-rw-r--r--test/unit/gluster/test_gfapi.py11
4 files changed, 307 insertions, 42 deletions
diff --git a/gluster/api.py b/gluster/api.py
index 560ede8..62135c5 100755
--- a/gluster/api.py
+++ b/gluster/api.py
@@ -320,26 +320,26 @@ glfs_fsync = ctypes.CFUNCTYPE(
glfs_lseek = ctypes.CFUNCTYPE(ctypes.c_ulong, ctypes.c_void_p, ctypes.c_ulong,
ctypes.c_int)(('glfs_lseek', client))
-glfs_read = ctypes.CFUNCTYPE(ctypes.c_size_t,
+glfs_read = ctypes.CFUNCTYPE(ctypes.c_ssize_t,
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_size_t,
ctypes.c_int)(('glfs_read', client))
-glfs_write = ctypes.CFUNCTYPE(ctypes.c_size_t,
+glfs_write = ctypes.CFUNCTYPE(ctypes.c_ssize_t,
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_size_t,
ctypes.c_int)(('glfs_write', client))
-glfs_getxattr = ctypes.CFUNCTYPE(ctypes.c_size_t,
+glfs_getxattr = ctypes.CFUNCTYPE(ctypes.c_ssize_t,
ctypes.c_void_p,
ctypes.c_char_p,
ctypes.c_char_p,
ctypes.c_void_p,
ctypes.c_size_t)(('glfs_getxattr', client))
-glfs_listxattr = ctypes.CFUNCTYPE(ctypes.c_size_t,
+glfs_listxattr = ctypes.CFUNCTYPE(ctypes.c_ssize_t,
ctypes.c_void_p,
ctypes.c_char_p,
ctypes.c_void_p,
@@ -423,6 +423,25 @@ glfs_flistxattr = ctypes.CFUNCTYPE(ctypes.c_ssize_t,
ctypes.c_size_t)(('glfs_flistxattr',
client))
+glfs_access = ctypes.CFUNCTYPE(ctypes.c_int,
+ ctypes.c_void_p,
+ ctypes.c_char_p,
+ ctypes.c_int)(('glfs_access', client))
+
+glfs_readlink = ctypes.CFUNCTYPE(ctypes.c_int,
+ ctypes.c_void_p,
+ ctypes.c_char_p,
+ ctypes.c_char_p,
+ ctypes.c_size_t)(('glfs_readlink', client))
+
+glfs_chdir = ctypes.CFUNCTYPE(ctypes.c_int,
+ ctypes.c_void_p,
+ ctypes.c_char_p)(('glfs_chdir', client))
+
+glfs_getcwd = ctypes.CFUNCTYPE(ctypes.c_char_p,
+ ctypes.c_void_p,
+ ctypes.c_char_p,
+ ctypes.c_size_t)(('glfs_getcwd', client))
# TODO: creat and open fails on test_create_file_already_exists & test_open_file_not_exist functional testing, # noqa
diff --git a/gluster/gfapi.py b/gluster/gfapi.py
index 8ba7251..96088ff 100755
--- a/gluster/gfapi.py
+++ b/gluster/gfapi.py
@@ -499,6 +499,33 @@ class Volume(object):
self.log_file = log_file
self.log_level = log_level
+ def access(self, path, mode):
+ """
+ Use the real uid/gid to test for access to path.
+
+ :param path: Path to be checked.
+ :param mode: mode should be F_OK to test the existence of path, or
+ it can be the inclusive OR of one or more of R_OK, W_OK,
+ and X_OK to test permissions
+ :returns: True if access is allowed, False if not
+ """
+ ret = api.glfs_access(self.fs, path, mode)
+ if ret == 0:
+ return True
+ else:
+ return False
+
+ def chdir(self, path):
+ """
+ Change the current working directory to the given path.
+
+ :param path: Path to change current working directory to
+ """
+ ret = api.glfs_chdir(self.fs, path)
+ if ret < 0:
+ err = ctypes.get_errno()
+ raise OSError(err, os.strerror(err))
+
def chmod(self, path, mode):
"""
Change mode of path
@@ -551,6 +578,18 @@ class Volume(object):
"""
return self.stat(path).st_ctime
+ def getcwd(self):
+ """
+ Returns current working directory.
+ """
+ PATH_MAX = 4096
+ buf = ctypes.create_string_buffer(PATH_MAX)
+ ret = api.glfs_getcwd(self.fs, buf, PATH_MAX)
+ if ret < 0:
+ err = ctypes.get_errno()
+ raise OSError(err, os.strerror(err))
+ return buf.value
+
def getmtime(self, path):
"""
Returns the time when changes were made to the content of the path
@@ -564,12 +603,30 @@ class Volume(object):
"""
return self.stat(filename).st_size
- def getxattr(self, path, key, maxlen):
- buf = ctypes.create_string_buffer(maxlen)
- rc = api.glfs_getxattr(self.fs, path, key, buf, maxlen)
+ def getxattr(self, path, key, size=0):
+ """
+ Retrieve the value of the extended attribute identified by key
+ for path specified.
+
+ :param path: Path to file or directory
+ :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_getxattr(self.fs, path, key, None, 0)
+ if size < 0:
+ err = ctypes.get_errno()
+ raise OSError(err, os.strerror(err))
+
+ buf = ctypes.create_string_buffer(size)
+ rc = api.glfs_getxattr(self.fs, path, key, buf, size)
if rc < 0:
err = ctypes.get_errno()
- raise IOError(err, os.strerror(err))
+ raise OSError(err, os.strerror(err))
return buf.value[:rc]
def isdir(self, path):
@@ -618,12 +675,28 @@ class Volume(object):
dir_list.append(name)
return dir_list
- def listxattr(self, path):
- buf = ctypes.create_string_buffer(512)
- rc = api.glfs_listxattr(self.fs, path, buf, 512)
+ def listxattr(self, path, size=0):
+ """
+ Retrieve list of extended attribute keys for the specified path.
+
+ :param path: Path to file or directory.
+ :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 attribute keys.
+ """
+ if size == 0:
+ size = api.glfs_listxattr(self.fs, path, None, 0)
+ if size < 0:
+ err = ctypes.get_errno()
+ raise OSError(err, os.strerror(err))
+
+ buf = ctypes.create_string_buffer(size)
+ rc = api.glfs_listxattr(self.fs, path, buf, size)
if rc < 0:
err = ctypes.get_errno()
- raise IOError(err, os.strerror(err))
+ 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
@@ -643,6 +716,11 @@ class Volume(object):
return xattrs
def lstat(self, path):
+ """
+ Return stat information of path. If path is a symbolic link, then it
+ returns information about the link itself, not the file that it refers
+ to.
+ """
s = api.Stat()
rc = api.glfs_lstat(self.fs, path, ctypes.byref(s))
if rc < 0:
@@ -668,6 +746,9 @@ class Volume(object):
self.mkdir(name, mode)
def mkdir(self, path, mode=0777):
+ """
+ Create a directory
+ """
ret = api.glfs_mkdir(self.fs, path, mode)
if ret < 0:
err = ctypes.get_errno()
@@ -732,25 +813,67 @@ class Volume(object):
return fd
def opendir(self, path):
+ """
+ Open a directory.
+
+ :param path: Path to the directory
+ :returns: Returns a instance of Dir class
+ """
fd = api.glfs_opendir(self.fs, path)
if not fd:
err = ctypes.get_errno()
raise OSError(err, os.strerror(err))
return Dir(fd)
+ def readlink(self, path):
+ """
+ Read contents of symbolic link path.
+
+ :param path: Path of symbolic link
+ :returns: Contents of symlink
+ """
+ PATH_MAX = 4096
+ buf = ctypes.create_string_buffer(PATH_MAX)
+ ret = api.glfs_readlink(self.fs, path, buf, PATH_MAX)
+ if ret < 0:
+ err = ctypes.get_errno()
+ raise OSError(err, os.strerror(err))
+ return buf.value[:ret]
+
+ def remove(self, path):
+ """
+ Remove (delete) the file path. If path is a directory,
+ OSError is raised.
+ """
+ return self.unlink(path)
+
def removexattr(self, path, key):
+ """
+ Remove a extended attribute of the file.
+
+ :param path: Path to the file or directory.
+ :param key: The key of extended attribute.
+ """
ret = api.glfs_removexattr(self.fs, path, key)
if ret < 0:
err = ctypes.get_errno()
- raise IOError(err, os.strerror(err))
+ raise OSError(err, os.strerror(err))
- def rename(self, opath, npath):
- ret = api.glfs_rename(self.fs, opath, npath)
+ def rename(self, src, dst):
+ """
+ Rename the file or directory from src to dst.
+ """
+ ret = api.glfs_rename(self.fs, src, dst)
if ret < 0:
err = ctypes.get_errno()
raise OSError(err, os.strerror(err))
def rmdir(self, path):
+ """
+ Remove (delete) the directory path. Only works when the directory is
+ empty, otherwise, OSError is raised. In order to remove whole
+ directory trees, rmtree() can be used.
+ """
ret = api.glfs_rmdir(self.fs, path)
if ret < 0:
err = ctypes.get_errno()
@@ -798,24 +921,51 @@ class Volume(object):
onerror(self.rmdir, path, e)
def setfsuid(self, uid):
+ """
+ setfsuid() changes the value of the caller's filesystem user ID-the
+ user ID that the Linux kernel uses to check for all accesses to the
+ filesystem.
+ """
ret = api.glfs_setfsuid(uid)
if ret < 0:
err = ctypes.get_errno()
raise OSError(err, os.strerror(err))
def setfsgid(self, gid):
+ """
+ setfsgid() changes the value of the caller's filesystem group ID-the
+ group ID that the Linux kernel uses to check for all accesses to the
+ filesystem.
+ """
ret = api.glfs_setfsgid(gid)
if ret < 0:
err = ctypes.get_errno()
raise OSError(err, os.strerror(err))
- def setxattr(self, path, key, value, vlen):
- ret = api.glfs_setxattr(self.fs, path, key, value, vlen, 0)
+ def setxattr(self, path, key, value, flags=0):
+ """
+ Set extended attribute of the path.
+
+ :param path: Path to file or directory.
+ :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_setxattr(self.fs, path, key, value, len(value), flags)
if ret < 0:
err = ctypes.get_errno()
- raise IOError(err, os.strerror(err))
+ raise OSError(err, os.strerror(err))
def stat(self, path):
+ """
+ Returns stat information of path.
+ """
s = api.Stat()
rc = api.glfs_stat(self.fs, path, ctypes.byref(s))
if rc < 0:
@@ -825,8 +975,8 @@ class Volume(object):
def statvfs(self, path):
"""
- To get status information about the file system that contains the file
- named by the path argument.
+ Returns information about a mounted glusterfs volume. path is the
+ pathname of any file within the mounted filesystem.
"""
s = api.Statvfs()
rc = api.glfs_statvfs(self.fs, path, ctypes.byref(s))
diff --git a/test/functional/libgfapi-python-tests.py b/test/functional/libgfapi-python-tests.py
index 1244fd3..c4a9797 100644
--- a/test/functional/libgfapi-python-tests.py
+++ b/test/functional/libgfapi-python-tests.py
@@ -289,24 +289,72 @@ class FileOpsTest(unittest.TestCase):
self.vol.unlink(self.path)
self.assertRaises(OSError, self.vol.lstat, self.path)
- def test_xattr(self):
- key1, key2 = "hello", "world"
- 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))
- self.assertTrue(set(["trusted.key1", "trusted.key2"]) <= set(xattrs))
-
- buf = self.vol.getxattr(self.path, "trusted.key1", 32)
- self.assertFalse(isinstance(buf, types.IntType))
- self.assertEqual(buf, "hello")
-
- self.vol.removexattr(self.path, "trusted.key1")
-
+ def test_setxattr(self):
+ value = "hello world"
+ self.vol.setxattr(self.path, "trusted.key1", value)
+ self.assertEqual(self.vol.getxattr(self.path, "trusted.key1"),
+ value)
+
+ # flag = 1 behavior: fail if xattr exists
+ self.assertRaises(OSError, self.vol.setxattr, self.path,
+ "trusted.key1", "whatever", flags=1)
+ # flag = 1 behavior: pass if xattr does not exist
+ self.vol.setxattr(self.path, "trusted.key2", "awesome", flags=1)
+ self.assertEqual(self.vol.getxattr(self.path, "trusted.key2"),
+ "awesome")
+
+ # flag = 2 behavior: fail if xattr does not exist
+ self.assertRaises(OSError, self.vol.setxattr, self.path,
+ "trusted.key3", "whatever", flags=2)
+ # flag = 2 behavior: pass if xattr exists
+ self.vol.setxattr(self.path, "trusted.key2",
+ "more awesome", flags=2)
+ self.assertEqual(self.vol.getxattr(self.path, "trusted.key2"),
+ "more awesome")
+
+ def test_getxattr(self):
+ self.vol.setxattr(self.path, "user.gluster", "awesome")
+ # user does not know the size of value beforehand
+ self.assertEqual(self.vol.getxattr(self.path, "user.gluster"),
+ "awesome")
+ # user knows the size of value beforehand
+ self.assertEqual(self.vol.getxattr(self.path, "user.gluster", size=7),
+ "awesome")
+ # size is larger
+ self.assertEqual(self.vol.getxattr(self.path, "user.gluster", size=20),
+ "awesome")
+ # size is smaller
+ self.assertRaises(OSError, self.vol.getxattr, self.path,
+ "user.gluster", size=1)
+ # size is negative
+ self.assertRaises(ValueError, self.vol.getxattr, self.path,
+ "user.gluster", size=-7)
+
+ def test_listxattr(self):
+ self.vol.setxattr(self.path, "user.gluster", "awesome")
+ self.vol.setxattr(self.path, "user.gluster2", "awesome2")
xattrs = self.vol.listxattr(self.path)
- self.assertFalse(isinstance(xattrs, types.IntType))
- self.assertTrue(["trusted.key1"] not in xattrs)
+ self.assertTrue("user.gluster" in xattrs)
+ self.assertTrue("user.gluster2" in xattrs)
+ # Test passing of size
+ # larger size - should pass
+ xattrs = self.vol.listxattr(self.path, size=512)
+ self.assertTrue("user.gluster" in xattrs)
+ self.assertTrue("user.gluster2" in xattrs)
+ # smaller size - should fail
+ self.assertRaises(OSError, self.vol.listxattr, self.path, size=1)
+ # invalid size - should fail
+ self.assertRaises(ValueError, self.vol.listxattr, self.path, size=-1)
+
+ def test_removexattr(self):
+ self.vol.setxattr(self.path, "user.gluster", "awesome")
+ self.vol.removexattr(self.path, "user.gluster")
+ # The xattr now shouldn't exist
+ self.assertRaises(OSError, self.vol.getxattr,
+ self.path, "user.gluster")
+ # Removing an xattr that does not exist
+ self.assertRaises(OSError, self.vol.removexattr,
+ self.path, "user.gluster")
def test_fsetxattr(self):
name = uuid4().hex
@@ -344,6 +392,14 @@ class FileOpsTest(unittest.TestCase):
self.assertEqual(f.fgetxattr("user.gluster"), "awesome")
# user knows the size of value beforehand
self.assertEqual(f.fgetxattr("user.gluster", 7), "awesome")
+ # size is larger
+ self.assertEqual(f.fgetxattr("user.gluster", 70), "awesome")
+ # size is smaller
+ self.assertRaises(OSError, f.fgetxattr,
+ "user.gluster", size=1)
+ # size is negative
+ self.assertRaises(ValueError, f.fgetxattr,
+ "user.gluster", size=-7)
def test_ftruncate(self):
name = uuid4().hex
@@ -375,6 +431,45 @@ class FileOpsTest(unittest.TestCase):
# invalid size - should fail
self.assertRaises(ValueError, f.flistxattr, size=-1)
+ def test_access(self):
+ file_name = uuid4().hex
+ with File(self.vol.open(file_name, os.O_WRONLY | os.O_CREAT)) as f:
+ f.write("I'm whatever Gotham needs me to be")
+ f.fsync()
+ # Check that file exists
+ self.assertTrue(self.vol.access(file_name, os.F_OK))
+ # Check that file does not exist
+ self.assertFalse(self.vol.access("nonexistentfile", os.F_OK))
+ dir_name = uuid4().hex
+ self.vol.mkdir(dir_name)
+ # Check that directory exists
+ self.assertTrue(self.vol.access(dir_name, os.F_OK))
+ # Check if there is execute and write permission
+ self.assertTrue(self.vol.access(file_name, os.W_OK | os.X_OK))
+
+ def test_getcwd_and_chdir(self):
+ # CWD should be root at first
+ self.assertEqual(self.vol.getcwd(), '/')
+ dir_structure = "/%s/%s" % (uuid4().hex, uuid4().hex)
+ self.vol.makedirs(dir_structure)
+ # Change directory
+ self.vol.chdir(dir_structure)
+ # The changed directory should now be CWD
+ self.assertEqual(self.vol.getcwd(), dir_structure)
+ self.vol.chdir("../..")
+ self.assertEqual(self.vol.getcwd(), '/')
+
+ def test_readlink(self):
+ file_name = uuid4().hex
+ with File(self.vol.open(file_name, os.O_WRONLY | os.O_CREAT)) as f:
+ f.write("It's not who I am underneath,"
+ "but what I do that defines me.")
+ f.fsync()
+ # Create a symlink
+ link_name = uuid4().hex
+ self.vol.symlink(file_name, link_name)
+ self.assertEqual(self.vol.readlink(link_name), file_name)
+
class DirOpsTest(unittest.TestCase):
diff --git a/test/unit/gluster/test_gfapi.py b/test/unit/gluster/test_gfapi.py
index 9fcfbf5..1463f76 100644
--- a/test/unit/gluster/test_gfapi.py
+++ b/test/unit/gluster/test_gfapi.py
@@ -562,7 +562,7 @@ class TestVolume(unittest.TestCase):
mock_glfs_getxattr.return_value = -1
with patch("gluster.gfapi.api.glfs_getxattr", mock_glfs_getxattr):
- self.assertRaises(IOError, self.vol.getxattr, "file.txt",
+ self.assertRaises(OSError, self.vol.getxattr, "file.txt",
"key1", 32)
def test_listdir_success(self):
@@ -597,7 +597,8 @@ class TestVolume(unittest.TestCase):
def test_listxattr_success(self):
def mock_glfs_listxattr(fs, path, buf, buflen):
- buf.raw = "key1\0key2\0"
+ if buf:
+ buf.raw = "key1\0key2\0"
return 10
with patch("gluster.gfapi.api.glfs_listxattr", mock_glfs_listxattr):
@@ -610,7 +611,7 @@ class TestVolume(unittest.TestCase):
mock_glfs_listxattr.return_value = -1
with patch("gluster.gfapi.api.glfs_listxattr", mock_glfs_listxattr):
- self.assertRaises(IOError, self.vol.listxattr, "file.txt")
+ self.assertRaises(OSError, self.vol.listxattr, "file.txt")
def test_lstat_success(self):
mock_glfs_lstat = Mock()
@@ -823,7 +824,7 @@ class TestVolume(unittest.TestCase):
with patch("gluster.gfapi.api.glfs_removexattr",
mock_glfs_removexattr):
- self.assertRaises(IOError, self.vol.removexattr, "file.txt",
+ self.assertRaises(OSError, self.vol.removexattr, "file.txt",
"key1")
def test_rmtree_success(self):
@@ -940,7 +941,7 @@ class TestVolume(unittest.TestCase):
mock_glfs_setxattr.return_value = -1
with patch("gluster.gfapi.api.glfs_setxattr", mock_glfs_setxattr):
- self.assertRaises(IOError, self.vol.setxattr, "file.txt",
+ self.assertRaises(OSError, self.vol.setxattr, "file.txt",
"key1", "hello", 5)
def test_symlink_success(self):