From e383ea4e4d20dd7ae4140b136c06ff029cbf071d Mon Sep 17 00:00:00 2001 From: Thiago da Silva Date: Fri, 28 Feb 2014 17:05:21 -0500 Subject: 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 --- gluster/gfapi.py | 85 ++++++++++++++- test/functional/libgfapi-python-tests.py | 60 ++++++++++- 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") -- cgit