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:
parent
e630e7c9d6
commit
eb5f89ac25
@ -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)
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user