diff options
| author | Prashanth Pai <ppai@redhat.com> | 2015-06-14 23:57:42 +0530 | 
|---|---|---|
| committer | Thiago da Silva <thiago@redhat.com> | 2015-06-18 05:52:33 -0700 | 
| commit | ddbd7b570d0a9f599b417a499c912c5b13a003c9 (patch) | |
| tree | 2dca993a300775af2367978c94ac34d7e332e3a1 | |
| parent | 509abefca5902e4f0decf40368a90265d1a598bd (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-x | gluster/api.py | 27 | ||||
| -rwxr-xr-x | gluster/gfapi.py | 182 | ||||
| -rw-r--r-- | test/functional/libgfapi-python-tests.py | 129 | ||||
| -rw-r--r-- | test/unit/gluster/test_gfapi.py | 11 | 
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):  | 
