diff options
| author | Thiago da Silva <thiago@redhat.com> | 2014-02-28 17:05:21 -0500 | 
|---|---|---|
| committer | Gerrit Code Review <review@dev.gluster.org> | 2014-03-06 10:10:53 -0800 | 
| commit | e383ea4e4d20dd7ae4140b136c06ff029cbf071d (patch) | |
| tree | 21a7e66294d065b3a45fd5018a73ccf08e81c8e7 | |
| parent | 495d8b5ef6939eefbe8cb0f1c1fed6a927614ed8 (diff) | |
adding new methods to Volume class
These are mostly helper methods similar to functions provided
by the python os module
helpfer functions added: exists, getsize, isdir, isfile, islink
glfs functions added: removexattr, stat
Change-Id: I3581a96224151481292a4e506d8c52b8acf79e49
Signed-off-by: Thiago da Silva <thiago@redhat.com>
| -rw-r--r-- | gluster/gfapi.py | 85 | ||||
| -rw-r--r-- | test/functional/libgfapi-python-tests.py | 60 | ||||
| -rw-r--r-- | test/unit/gluster/test_gfapi.py | 175 | 
3 files changed, 307 insertions, 13 deletions
diff --git a/gluster/gfapi.py b/gluster/gfapi.py index b1f14d2..63baee4 100644 --- a/gluster/gfapi.py +++ b/gluster/gfapi.py @@ -1,9 +1,13 @@  import ctypes  from ctypes.util import find_library  import os +import stat  from contextlib import contextmanager +# Disclaimer: many of the helper functions (e.g., exists, isdir) where copied +# from the python source code +  # Looks like ctypes is having trouble with dependencies, so just force them to  # load with RTLD_GLOBAL until I figure that out.  api = ctypes.CDLL(find_library("gfapi"), ctypes.RTLD_GLOBAL, use_errno=True) @@ -60,6 +64,9 @@ api.glfs_opendir.restype = ctypes.c_void_p  api.glfs_readdir_r.restype = ctypes.c_int  api.glfs_readdir_r.argtypes = [ctypes.c_void_p, ctypes.POINTER(Dirent),                                 ctypes.POINTER(ctypes.POINTER(Dirent))] +api.glfs_stat.restype = ctypes.c_int +api.glfs_stat.argtypes = [ctypes.c_void_p, ctypes.c_char_p, +                          ctypes.POINTER(Stat)]  class File(object): @@ -184,6 +191,23 @@ class Volume(object):          finally:              fileobj.close() +    def exists(self, path): +        """ +        Test whether a path exists. +        Returns False for broken symbolic links. +        """ +        try: +            self.stat(path) +        except OSError: +            return False +        return True + +    def getsize(self, filename): +        """ +        Return the size of a file, reported by stat() +        """ +        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) @@ -192,6 +216,36 @@ class Volume(object):              raise IOError(err, os.strerror(err))          return buf.value[:rc] +    def isdir(self, path): +        """ +        Test whether a path is an existing directory +        """ +        try: +            s = self.stat(path) +        except OSError: +            return False +        return stat.S_ISDIR(s.st_mode) + +    def isfile(self, path): +        """ +        Test whether a path is a regular file +        """ +        try: +            s = self.stat(path) +        except OSError: +            return False +        return stat.S_ISREG(s.st_mode) + +    def islink(self, path): +        """ +        Test whether a path is a symbolic link +        """ +        try: +            s = self.lstat(path) +        except OSError: +            return False +        return stat.S_ISLNK(s.st_mode) +      def listxattr(self, path):          buf = ctypes.create_string_buffer(512)          rc = api.glfs_listxattr(self.fs, path, buf, 512) @@ -217,12 +271,12 @@ class Volume(object):          return xattrs      def lstat(self, path): -        x = Stat() -        rc = api.glfs_lstat(self.fs, path, ctypes.byref(x)) +        s = Stat() +        rc = api.glfs_lstat(self.fs, path, ctypes.byref(s))          if rc < 0:              err = ctypes.get_errno()              raise OSError(err, os.strerror(err)) -        return x +        return s      def mkdir(self, path, mode):          ret = api.glfs_mkdir(self.fs, path, mode) @@ -252,6 +306,13 @@ class Volume(object):              raise OSError(err, os.strerror(err))          return Dir(fd) +    def removexattr(self, path, key): +        ret = api.glfs_removexattr(self.fs, path, key) +        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: @@ -273,6 +334,24 @@ class Volume(object):              raise IOError(err, os.strerror(err))          return ret +    def stat(self, path): +        s = Stat() +        rc = api.glfs_stat(self.fs, path, ctypes.byref(s)) +        if rc < 0: +            err = ctypes.get_errno() +            raise OSError(err, os.strerror(err)) +        return s + +    def symlink(self, source, link_name): +        """ +        Create a symbolic link 'link_name' which points to 'source' +        """ +        ret = api.glfs_symlink(self.fs, source, link_name) +        if ret < 0: +            err = ctypes.get_errno() +            raise OSError(err, os.strerror(err)) +        return ret +      def unlink(self, path):          ret = api.glfs_unlink(self.fs, path)          if ret < 0: diff --git a/test/functional/libgfapi-python-tests.py b/test/functional/libgfapi-python-tests.py index 08742f3..5a5f2df 100644 --- a/test/functional/libgfapi-python-tests.py +++ b/test/functional/libgfapi-python-tests.py @@ -3,7 +3,6 @@ import os  import types  import loremipsum -from nose import SkipTest  from gluster import gfapi @@ -57,7 +56,8 @@ class FileOpsTest(unittest.TestCase):          self.data = loremipsum.get_sentence()          self.path = self._testMethodName + ".io"          with self.vol.creat(self.path, os.O_WRONLY | os.O_EXCL, 0644) as fd: -            fd.write(self.data) +            rc = fd.write(self.data) +            self.assertEqual(rc, len(self.data))      def tearDown(self):          self.path = None @@ -70,6 +70,37 @@ class FileOpsTest(unittest.TestCase):              self.assertFalse(isinstance(buf, types.IntType))              self.assertEqual(buf.value, self.data) +    def test_exists(self): +        e = self.vol.exists(self.path) +        self.assertTrue(e) + +    def test_exists_false(self): +        e = self.vol.exists("filedoesnotexist") +        self.assertFalse(e) + +    def test_getsize(self): +        size = self.vol.getsize(self.path) +        self.assertEqual(size, len(self.data)) + +    def test_isfile(self): +        isfile = self.vol.isfile(self.path) +        self.assertTrue(isfile) + +    def test_isdir_false(self): +        isdir = self.vol.isdir(self.path) +        self.assertFalse(isdir) + +    def test_symlink(self): +        link = self._testMethodName + ".link" +        ret = self.vol.symlink(self.path, link) +        self.assertEqual(ret, 0) +        islink = self.vol.islink(link) +        self.assertTrue(islink) + +    def test_islink_false(self): +        islink = self.vol.islink(self.path) +        self.assertFalse(islink) +      def test_lstat(self):          sb = self.vol.lstat(self.path)          self.assertFalse(isinstance(sb, types.IntType)) @@ -81,6 +112,11 @@ class FileOpsTest(unittest.TestCase):          self.assertEqual(ret, 0)          self.assertRaises(OSError, self.vol.lstat, self.path) +    def test_stat(self): +        sb = self.vol.stat(self.path) +        self.assertFalse(isinstance(sb, types.IntType)) +        self.assertEqual(sb.st_size, len(self.data)) +      def test_unlink(self):          ret = self.vol.unlink(self.path)          self.assertEqual(ret, 0) @@ -89,9 +125,9 @@ class FileOpsTest(unittest.TestCase):      def test_xattr(self):          key1, key2 = "hello", "world"          ret1 = self.vol.setxattr(self.path, "trusted.key1", key1, len(key1)) -        self.assertEqual(0, ret1) +        self.assertEqual(ret1, 0)          ret2 = self.vol.setxattr(self.path, "trusted.key2", key2, len(key2)) -        self.assertEqual(0, ret2) +        self.assertEqual(ret2, 0)          xattrs = self.vol.listxattr(self.path)          self.assertFalse(isinstance(xattrs, types.IntType)) @@ -101,6 +137,13 @@ 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) + +        xattrs = self.vol.listxattr(self.path) +        self.assertFalse(isinstance(xattrs, types.IntType)) +        self.assertEqual(xattrs, ["trusted.key2"]) +  class DirOpsTest(unittest.TestCase): @@ -129,12 +172,21 @@ class DirOpsTest(unittest.TestCase):          with self.vol.creat(                  self.file_path, os.O_WRONLY | os.O_EXCL, 0644) as fd:              rc = fd.write(self.data) +            self.assertEqual(rc, len(self.data))      def tearDown(self):          self.dir_path = None          self.file_path = None          self.data = None +    def test_isdir(self): +        isdir = self.vol.isdir(self.dir_path) +        self.assertTrue(isdir) + +    def test_isfile_false(self): +        isfile = self.vol.isfile(self.dir_path) +        self.assertFalse(isfile) +      def test_dir_listing(self):          fd = self.vol.opendir(self.dir_path)          self.assertTrue(isinstance(fd, gfapi.Dir)) diff --git a/test/unit/gluster/test_gfapi.py b/test/unit/gluster/test_gfapi.py index 549357b..97e6203 100644 --- a/test/unit/gluster/test_gfapi.py +++ b/test/unit/gluster/test_gfapi.py @@ -1,7 +1,7 @@  import unittest  import gluster -import mock  import os +import stat  from gluster import gfapi  from nose import SkipTest @@ -185,7 +185,7 @@ class TestVolume(unittest.TestCase):      def tearDown(self):          gluster.gfapi.api.glfs_new = self._saved_glfs_new          gluster.gfapi.api.glfs_set_volfile_server = \ -                self._saved_glfs_set_volfile_server +            self._saved_glfs_set_volfile_server          gluster.gfapi.api.glfs_fini = self._saved_glfs_fini          gluster.gfapi.api.glfs_close = self._saved_glfs_close          gluster.gfapi.api.glfs_closedir = self._saved_glfs_closedir @@ -210,11 +210,121 @@ class TestVolume(unittest.TestCase):              with vol.creat("file.txt", os.O_WRONLY, 0644) as fd:                  self.assertEqual(fd, None) -          with patch("gluster.gfapi.api.glfs_creat", mock_glfs_creat):              vol = gfapi.Volume("localhost", "test")              self.assertRaises(OSError, assert_creat) +    def test_exists_true(self): +        mock_glfs_stat = Mock() +        mock_glfs_stat.return_value = 0 + +        with patch("gluster.gfapi.api.glfs_stat", mock_glfs_stat): +            vol = gfapi.Volume("localhost", "test") +            ret = vol.exists("file.txt") +            self.assertTrue(ret) + +    def test_not_exists_false(self): +        mock_glfs_stat = Mock() +        mock_glfs_stat.return_value = -1 + +        with patch("gluster.gfapi.api.glfs_stat", mock_glfs_stat): +            vol = gfapi.Volume("localhost", "test") +            ret = vol.exists("file.txt") +            self.assertFalse(ret) + +    def test_isdir_true(self): +        mock_glfs_stat = Mock() +        s = gfapi.Stat() +        s.st_mode = stat.S_IFDIR +        mock_glfs_stat.return_value = s + +        with patch("gluster.gfapi.Volume.stat", mock_glfs_stat): +            vol = gfapi.Volume("localhost", "test") +            ret = vol.isdir("dir") +            self.assertTrue(ret) + +    def test_isdir_false(self): +        mock_glfs_stat = Mock() +        s = gfapi.Stat() +        s.st_mode = stat.S_IFREG +        mock_glfs_stat.return_value = s + +        with patch("gluster.gfapi.Volume.stat", mock_glfs_stat): +            vol = gfapi.Volume("localhost", "test") +            ret = vol.isdir("file") +            self.assertFalse(ret) + +    def test_isdir_false_nodir(self): +        mock_glfs_stat = Mock() +        mock_glfs_stat.return_value = -1 + +        with patch("gluster.gfapi.api.glfs_stat", mock_glfs_stat): +            vol = gfapi.Volume("localhost", "test") +            ret = vol.isdir("dirdoesnotexist") +            self.assertFalse(ret) + +    def test_isfile_true(self): +        mock_glfs_stat = Mock() +        s = gfapi.Stat() +        s.st_mode = stat.S_IFREG +        mock_glfs_stat.return_value = s + +        with patch("gluster.gfapi.Volume.stat", mock_glfs_stat): +            vol = gfapi.Volume("localhost", "test") +            ret = vol.isfile("file") +            self.assertTrue(ret) + +    def test_isfile_false(self): +        mock_glfs_stat = Mock() +        s = gfapi.Stat() +        s.st_mode = stat.S_IFDIR +        mock_glfs_stat.return_value = s + +        with patch("gluster.gfapi.Volume.stat", mock_glfs_stat): +            vol = gfapi.Volume("localhost", "test") +            ret = vol.isfile("dir") +            self.assertFalse(ret) + +    def test_isfile_false_nofile(self): +        mock_glfs_stat = Mock() +        mock_glfs_stat.return_value = -1 + +        with patch("gluster.gfapi.api.glfs_stat", mock_glfs_stat): +            vol = gfapi.Volume("localhost", "test") +            ret = vol.isfile("filedoesnotexist") +            self.assertFalse(ret) + +    def test_islink_true(self): +        mock_glfs_lstat = Mock() +        s = gfapi.Stat() +        s.st_mode = stat.S_IFLNK +        mock_glfs_lstat.return_value = s + +        with patch("gluster.gfapi.Volume.lstat", mock_glfs_lstat): +            vol = gfapi.Volume("localhost", "test") +            ret = vol.islink("solnk") +            self.assertTrue(ret) + +    def test_islink_false(self): +        mock_glfs_lstat = Mock() +        s = gfapi.Stat() +        s.st_mode = stat.S_IFREG +        mock_glfs_lstat.return_value = s + +        with patch("gluster.gfapi.Volume.lstat", mock_glfs_lstat): +            vol = gfapi.Volume("localhost", "test") +            ret = vol.islink("file") +            self.assertFalse(ret) + +    def test_islink_false_nolink(self): +        mock_glfs_lstat = Mock() +        mock_glfs_lstat.return_value = -1 + +        with patch("gluster.gfapi.api.glfs_lstat", mock_glfs_lstat): +            vol = gfapi.Volume("localhost", "test") +            ret = vol.islink("linkdoesnotexist") +            self.assertFalse(ret) +      def test_getxattr_success(self):          def mock_glfs_getxattr(fs, path, key, buf, maxlen):              buf.value = "fake_xattr" @@ -236,7 +346,7 @@ class TestVolume(unittest.TestCase):      def test_listxattr_success(self):          def mock_glfs_listxattr(fs, path, buf, buflen):              buf.raw = "key1\0key2\0" -            return  10 +            return 10          with patch("gluster.gfapi.api.glfs_listxattr", mock_glfs_listxattr):              vol = gfapi.Volume("localhost", "test") @@ -269,6 +379,23 @@ class TestVolume(unittest.TestCase):              vol = gfapi.Volume("localhost", "test")              self.assertRaises(OSError, vol.lstat, "file.txt") +    def test_stat_success(self): +        mock_glfs_stat = Mock() +        mock_glfs_stat.return_value = 0 + +        with patch("gluster.gfapi.api.glfs_stat", mock_glfs_stat): +            vol = gfapi.Volume("localhost", "test") +            stat = vol.stat("file.txt") +            self.assertTrue(isinstance(stat, gfapi.Stat)) + +    def test_stat_fail_exception(self): +        mock_glfs_stat = Mock() +        mock_glfs_stat.return_value = -1 + +        with patch("gluster.gfapi.api.glfs_stat", mock_glfs_stat): +            vol = gfapi.Volume("localhost", "test") +            self.assertRaises(OSError, vol.stat, "file.txt") +      def test_mkdir_success(self):          mock_glfs_mkdir = Mock()          mock_glfs_mkdir.return_value = 0 @@ -296,7 +423,7 @@ class TestVolume(unittest.TestCase):                  self.assertTrue(isinstance(fd, gfapi.File))                  self.assertEqual(mock_glfs_open.call_count, 1)                  mock_glfs_open.assert_called_once_with(2, -                    "file.txt", os.O_WRONLY) +                                                       "file.txt", os.O_WRONLY)      def test_open_fail_exception(self):          mock_glfs_open = Mock() @@ -378,6 +505,25 @@ class TestVolume(unittest.TestCase):              vol = gfapi.Volume("localhost", "test")              self.assertRaises(OSError, vol.unlink, "file.txt") +    def test_removexattr_success(self): +        mock_glfs_removexattr = Mock() +        mock_glfs_removexattr.return_value = 0 + +        with patch("gluster.gfapi.api.glfs_removexattr", +                   mock_glfs_removexattr): +            vol = gfapi.Volume("localhost", "test") +            ret = vol.removexattr("file.txt", "key1") +            self.assertEquals(ret, 0) + +    def test_removexattr_fail_exception(self): +        mock_glfs_removexattr = Mock() +        mock_glfs_removexattr.return_value = -1 + +        with patch("gluster.gfapi.api.glfs_removexattr", +                   mock_glfs_removexattr): +            vol = gfapi.Volume("localhost", "test") +            self.assertRaises(IOError, vol.removexattr, "file.txt", "key1") +      def test_setxattr_success(self):          mock_glfs_setxattr = Mock()          mock_glfs_setxattr.return_value = 0 @@ -394,4 +540,21 @@ class TestVolume(unittest.TestCase):          with patch("gluster.gfapi.api.glfs_setxattr", mock_glfs_setxattr):              vol = gfapi.Volume("localhost", "test")              self.assertRaises(IOError, vol.setxattr, "file.txt", -                    "key1", "hello", 5) +                              "key1", "hello", 5) + +    def test_symlink_success(self): +        mock_glfs_symlink = Mock() +        mock_glfs_symlink.return_value = 0 + +        with patch("gluster.gfapi.api.glfs_symlink", mock_glfs_symlink): +            vol = gfapi.Volume("localhost", "test") +            ret = vol.symlink("file.txt", "filelink") +            self.assertEquals(ret, 0) + +    def test_symlink_fail_exception(self): +        mock_glfs_symlink = Mock() +        mock_glfs_symlink.return_value = -1 + +        with patch("gluster.gfapi.api.glfs_symlink", mock_glfs_symlink): +            vol = gfapi.Volume("localhost", "test") +            self.assertRaises(OSError, vol.symlink, "file.txt", "filelink")  | 
