summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPrashanth Pai <ppai@redhat.com>2016-05-30 17:42:15 +0530
committerPrashanth Pai <ppai@redhat.com>2016-06-01 12:42:25 +0530
commit972c24f8b11d5a3e7e6fc341453d9733b2bb47b5 (patch)
treed38c5cdf723c1a029455147ddbb19cf126c3186e
parent123c2b7dc51d012f6d2924f680eeec748187a300 (diff)
Implement os.utime() like API and zerofill
This patch: * Implements Volume.utime() which is very similar to os.utime() present in Python. https://docs.python.org/2/library/os.html#os.utime * Implements File.zerofill() which exposes glfs_zerofill. * Fixes function prototype of fallocate and discard. Adds functional tests for the same. Change-Id: Icb8d3a571998c31d6bf9b139ca253af59f6fc3f4 Signed-off-by: Prashanth Pai <ppai@redhat.com>
-rwxr-xr-xgluster/api.py31
-rwxr-xr-xgluster/gfapi.py45
-rw-r--r--test/functional/libgfapi-python-tests.py59
-rw-r--r--test/unit/gluster/test_gfapi.py34
4 files changed, 160 insertions, 9 deletions
diff --git a/gluster/api.py b/gluster/api.py
index 7163076..3fd9d91 100755
--- a/gluster/api.py
+++ b/gluster/api.py
@@ -115,6 +115,13 @@ class Dirent (ctypes.Structure):
]
+class Timespec (ctypes.Structure):
+ _fields_ = [
+ ('tv_sec', ctypes.c_long),
+ ('tv_nsec', ctypes.c_long)
+ ]
+
+
# Here is the reference card of libgfapi library exported
# apis with its different versions.
#
@@ -474,12 +481,22 @@ glfs_getcwd = gfapi_prototype('glfs_getcwd', ctypes.c_char_p,
ctypes.c_char_p,
ctypes.c_size_t)
+glfs_fallocate = gfapi_prototype('glfs_fallocate', ctypes.c_int,
+ ctypes.c_void_p,
+ ctypes.c_int,
+ ctypes.c_size_t)
+
+glfs_discard = gfapi_prototype('glfs_discard', ctypes.c_int,
+ ctypes.c_void_p,
+ ctypes.c_int,
+ ctypes.c_size_t)
-# TODO: # discard and fallocate fails with "AttributeError: /lib64/libgfapi.so.0: undefined symbol: glfs_discard", # noqa
-# for time being, using it from api.* # noqa
-# glfs_discard = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.c_ulong, ctypes.c_size_t)(('glfs_discard', client)) # noqa
-# _glfs_fallocate = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_ulong, ctypes.c_size_t) # noqa
-# (('glfs_fallocate', client)) # noqa
+glfs_zerofill = gfapi_prototype('glfs_zerofill', ctypes.c_int,
+ ctypes.c_void_p,
+ ctypes.c_int,
+ ctypes.c_size_t)
-# glfs_discard = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.c_ulong, ctypes.c_size_t)(('glfs_discard', client)) # noqa
-# glfs_fallocate = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_ulong, ctypes.c_size_t)(('glfs_fallocate', client)) # noqa
+glfs_utimens = gfapi_prototype('glfs_utimens', ctypes.c_int,
+ ctypes.c_void_p,
+ ctypes.c_char_p,
+ ctypes.POINTER(Timespec))
diff --git a/gluster/gfapi.py b/gluster/gfapi.py
index c84b262..c409f62 100755
--- a/gluster/gfapi.py
+++ b/gluster/gfapi.py
@@ -11,6 +11,8 @@
import ctypes
import os
+import math
+import time
import stat
import errno
from gluster import api
@@ -87,7 +89,7 @@ class File(object):
self._closed = True
def discard(self, offset, len):
- ret = api.client.glfs_discard(self.fd, offset, len)
+ ret = api.glfs_discard(self.fd, offset, len)
if ret < 0:
err = ctypes.get_errno()
raise OSError(err, os.strerror(err))
@@ -111,7 +113,7 @@ class File(object):
:param offset: Starting offset
:param length: Size in bytes, starting at offset
"""
- ret = api.client.glfs_fallocate(self.fd, mode, offset, length)
+ ret = api.glfs_fallocate(self.fd, mode, offset, length)
if ret < 0:
err = ctypes.get_errno()
raise OSError(err, os.strerror(err))
@@ -370,6 +372,12 @@ class File(object):
raise OSError(err, os.strerror(err))
return ret
+ def zerofill(self, offset, length):
+ ret = api.glfs_zerofill(self.fd, offset, length)
+ if ret < 0:
+ err = ctypes.get_errno()
+ raise OSError(err, os.strerror(err))
+
class Dir(object):
@@ -1036,6 +1044,39 @@ class Volume(object):
err = ctypes.get_errno()
raise OSError(err, os.strerror(err))
+ def utime(self, path, times):
+ """
+ Set the access and modified times of the file specified by path. If
+ times is None, then the file's access and modified times are set to
+ the current time. (The effect is similar to running the Unix program
+ touch on the path.) Otherwise, times must be a 2-tuple of numbers,
+ of the form (atime, mtime) which is used to set the access and
+ modified times, respectively.
+ """
+ if times is None:
+ now = time.time()
+ times = (now, now)
+ else:
+ if type(times) is not tuple or len(times) != 2:
+ raise TypeError("utime() arg 2 must be a tuple (atime, mtime)")
+
+ timespec_array = (api.Timespec * 2)()
+
+ # Set atime
+ decimal, whole = math.modf(times[0])
+ timespec_array[0].tv_sec = int(whole)
+ timespec_array[0].tv_nsec = int(decimal * 1e9)
+
+ # Set mtime
+ decimal, whole = math.modf(times[1])
+ timespec_array[1].tv_sec = int(whole)
+ timespec_array[1].tv_nsec = int(decimal * 1e9)
+
+ ret = api.glfs_utimens(self.fs, path, timespec_array)
+ if ret < 0:
+ err = ctypes.get_errno()
+ raise OSError(err, os.strerror(err))
+
def walk(self, top, topdown=True, onerror=None, followlinks=False):
"""
Directory tree generator. Yields a 3-tuple dirpath, dirnames, filenames
diff --git a/test/functional/libgfapi-python-tests.py b/test/functional/libgfapi-python-tests.py
index 591fe7b..c29f400 100644
--- a/test/functional/libgfapi-python-tests.py
+++ b/test/functional/libgfapi-python-tests.py
@@ -443,6 +443,65 @@ class FileOpsTest(unittest.TestCase):
# So should be the content.
self.assertEqual(f.read(), "12345")
+ def test_fallocate(self):
+ name = uuid4().hex
+ with File(self.vol.open(name, os.O_WRONLY | os.O_CREAT)) as f:
+ f.fallocate(0, 0, 10)
+ f.fsync()
+ # Stat information should now show the allocated size.
+ self.assertEqual(f.fstat().st_size, 10)
+
+ def test_discard(self):
+ name = uuid4().hex
+ with File(self.vol.open(name, os.O_WRONLY | os.O_CREAT)) as f:
+ f.fallocate(0, 0, 10)
+ f.fsync()
+ self.assertEqual(f.fstat().st_size, 10)
+ # We can't really know if the blocks were actually returned
+ # to filesystem. This functional test only tests if glfs_discard
+ # interfacing is proper and that it returns successfully.
+ f.discard(4, 5)
+
+ def test_zerofill(self):
+ name = uuid4().hex
+ with File(self.vol.open(name, os.O_RDWR | os.O_CREAT)) as f:
+ f.write('0123456789')
+ f.fsync()
+ self.assertEqual(f.fstat().st_size, 10)
+ f.lseek(0, os.SEEK_SET)
+ self.assertEqual(f.read(), '0123456789')
+ f.zerofill(3, 6)
+ f.lseek(0, os.SEEK_SET)
+ data = f.read()
+ self.assertEqual(data, '012\x00\x00\x00\x00\x00\x009')
+ self.assertEqual(len(data), 10)
+
+ def test_utime(self):
+ # Create a file
+ name = uuid4().hex
+ self.vol.fopen(name, 'w').close()
+
+ # Test times arg being invalid
+ for junk in ('a', 1234.1234, (1, 2, 3), (1)):
+ self.assertRaises(TypeError, self.vol.utime, name, junk)
+
+ # Test normal success
+ # Mission Report: December 16th, 1991
+ (atime, mtime) = (692884800, 692884800)
+ self.vol.utime(name, (atime, mtime))
+ st = self.vol.stat(name)
+ self.assertEqual(st.st_atime, atime)
+ self.assertEqual(st.st_mtime, mtime)
+
+ # Test times = None
+ self.vol.utime(name, None)
+ new_st = self.vol.stat(name)
+ self.assertTrue(new_st.st_atime > st.st_atime)
+ self.assertTrue(new_st.st_mtime > st.st_mtime)
+
+ # Non-existent file
+ self.assertRaises(OSError, self.vol.utime, 'non-existent-file', None)
+
def test_flistxattr(self):
name = uuid4().hex
with File(self.vol.open(name, os.O_RDWR | os.O_CREAT)) as f:
diff --git a/test/unit/gluster/test_gfapi.py b/test/unit/gluster/test_gfapi.py
index 84f6261..3934a6f 100644
--- a/test/unit/gluster/test_gfapi.py
+++ b/test/unit/gluster/test_gfapi.py
@@ -13,6 +13,8 @@ import unittest
import gluster
import os
import stat
+import time
+import math
import errno
from gluster.gfapi import File, Dir, Volume
@@ -997,3 +999,35 @@ class TestVolume(unittest.TestCase):
for (path, dirs, files) in self.vol.walk("dir1",
onerror=mock_onerror):
pass
+
+ def test_utime(self):
+ # Test times arg being invalid.
+ for junk in ('a', 1234.1234, (1, 2, 3), (1)):
+ self.assertRaises(TypeError, self.vol.utime, '/path', junk)
+
+ # Test times = None
+ mock_glfs_utimens = Mock(return_value=1)
+ mock_time = Mock(return_value=12345.6789)
+ with patch("gluster.gfapi.api.glfs_utimens", mock_glfs_utimens):
+ with patch("time.time", mock_time):
+ self.vol.utime('/path', None)
+ self.assertTrue(mock_glfs_utimens.called)
+ self.assertTrue(mock_time.called)
+
+ # Test times passed as arg
+ mock_glfs_utimens.reset_mock()
+ atime = time.time()
+ mtime = time.time()
+ with patch("gluster.gfapi.api.glfs_utimens", mock_glfs_utimens):
+ self.vol.utime('/path', (atime, mtime))
+ self.assertTrue(mock_glfs_utimens.called)
+ self.assertEqual(mock_glfs_utimens.call_args[0][1], '/path')
+ # verify atime and mtime
+ self.assertEqual(mock_glfs_utimens.call_args[0][2][0].tv_sec,
+ int(atime))
+ self.assertEqual(mock_glfs_utimens.call_args[0][2][0].tv_nsec,
+ int(math.modf(atime)[0] * 1e9))
+ self.assertEqual(mock_glfs_utimens.call_args[0][2][1].tv_sec,
+ int(mtime))
+ self.assertEqual(mock_glfs_utimens.call_args[0][2][1].tv_nsec,
+ int(math.modf(mtime)[0] * 1e9))