From eb5f89ac25fb3edf5d6f0dcc238d4dea4cbbc7c8 Mon Sep 17 00:00:00 2001 From: John Dickinson Date: Mon, 27 Aug 2012 14:44:41 -0700 Subject: [PATCH] fallocate call error handling fallocate() failures properly return HTTPInsufficientStorage from object-server before reading from wsgi.input, allowing the proxy server to error_limit that node. Change-Id: Idfc293bbab2cff1e508edf58045108ca1ef5cec1 --- swift/common/utils.py | 10 ++++++---- swift/obj/server.py | 6 +++++- test/unit/obj/test_server.py | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/swift/common/utils.py b/swift/common/utils.py index ad31279caf..727ca43690 100644 --- a/swift/common/utils.py +++ b/swift/common/utils.py @@ -99,7 +99,7 @@ def load_libc_function(func_name, log_error=True): :param func_name: name of the function to pull from libc. """ try: - libc = ctypes.CDLL(ctypes.util.find_library('c')) + libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True) return getattr(libc, func_name) except AttributeError: if log_error: @@ -127,6 +127,9 @@ def get_param(req, name, default=None): class FallocateWrapper(object): def __init__(self): + ## fallocate is prefered because we need the on-disk size to match + ## the allocated size. Older versions of sqlite require that the + ## two sizes match. However, fallocate is Linux only. for func in ('fallocate', 'posix_fallocate'): self.func_name = func self.fallocate = load_libc_function(func, log_error=False) @@ -146,7 +149,7 @@ class FallocateWrapper(object): def fallocate(fd, size): """ - Pre-allocate disk space for a file file. + Pre-allocate disk space for a file. :param fd: file descriptor :param size: size to allocate (in bytes) @@ -157,9 +160,8 @@ def fallocate(fd, size): if size > 0: # 1 means "FALLOC_FL_KEEP_SIZE", which means it pre-allocates invisibly ret = _sys_fallocate(fd, 1, 0, ctypes.c_uint64(size)) - # XXX: in (not very thorough) testing, errno always seems to be 0? err = ctypes.get_errno() - if ret and err not in (0, errno.ENOSYS): + if ret and err not in (0, errno.ENOSYS, errno.EOPNOTSUPP): raise OSError(err, 'Unable to fallocate(%s)' % size) diff --git a/swift/obj/server.py b/swift/obj/server.py index ea4fe07d40..ec59dce734 100644 --- a/swift/obj/server.py +++ b/swift/obj/server.py @@ -590,7 +590,11 @@ class ObjectController(object): last_sync = 0 with file.mkstemp() as (fd, tmppath): if 'content-length' in request.headers: - fallocate(fd, int(request.headers['content-length'])) + try: + fallocate(fd, int(request.headers['content-length'])) + except OSError: + return HTTPInsufficientStorage(drive=device, + request=request) reader = request.environ['wsgi.input'].read for chunk in iter(lambda: reader(self.network_chunk_size), ''): upload_size += len(chunk) diff --git a/test/unit/obj/test_server.py b/test/unit/obj/test_server.py index 7c8358b880..0d94dbadeb 100644 --- a/test/unit/obj/test_server.py +++ b/test/unit/obj/test_server.py @@ -2161,5 +2161,39 @@ class TestObjectController(unittest.TestCase): tpool.execute = was_tpool_exe object_server.get_hashes = was_get_hashes + def test_PUT_with_full_drive(self): + + class IgnoredBody(): + + def __init__(self): + self.read_called = False + + def read(self, size=-1): + if not self.read_called: + self.read_called = True + return 'VERIFY' + return '' + + def fake_fallocate(fd, size): + raise OSError(42, 'Unable to fallocate(%d)' % size) + + orig_fallocate = object_server.fallocate + try: + object_server.fallocate = fake_fallocate + timestamp = normalize_timestamp(time()) + body_reader = IgnoredBody() + req = Request.blank('/sda1/p/a/c/o', + environ={'REQUEST_METHOD': 'PUT', + 'wsgi.input': body_reader}, + headers={'X-Timestamp': timestamp, + 'Content-Length': '6', + 'Content-Type': 'application/octet-stream', + 'Expect': '100-continue'}) + resp = self.object_controller.PUT(req) + self.assertEquals(resp.status_int, 507) + self.assertFalse(body_reader.read_called) + finally: + object_server.fallocate = orig_fallocate + if __name__ == '__main__': unittest.main()