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
This commit is contained in:
John Dickinson 2012-08-27 14:44:41 -07:00
parent e630e7c9d6
commit eb5f89ac25
3 changed files with 45 additions and 5 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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()