Merge "Fix suffix-byte-range responses for zero-byte EC objects."
This commit is contained in:
commit
02a2788438
@ -1122,20 +1122,30 @@ class ECAppIter(object):
|
|||||||
return headers, frag_iters
|
return headers, frag_iters
|
||||||
|
|
||||||
def _actual_range(self, req_start, req_end, entity_length):
|
def _actual_range(self, req_start, req_end, entity_length):
|
||||||
|
# Takes 3 args: (requested-first-byte, requested-last-byte,
|
||||||
|
# actual-length).
|
||||||
|
#
|
||||||
|
# Returns a 3-tuple (first-byte, last-byte, satisfiable).
|
||||||
|
#
|
||||||
|
# It is possible to get (None, None, True). This means that the last
|
||||||
|
# N>0 bytes of a 0-byte object were requested, and we are able to
|
||||||
|
# satisfy that request by returning nothing.
|
||||||
try:
|
try:
|
||||||
rng = Range("bytes=%s-%s" % (
|
rng = Range("bytes=%s-%s" % (
|
||||||
req_start if req_start is not None else '',
|
req_start if req_start is not None else '',
|
||||||
req_end if req_end is not None else ''))
|
req_end if req_end is not None else ''))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return (None, None)
|
return (None, None, False)
|
||||||
|
|
||||||
rfl = rng.ranges_for_length(entity_length)
|
rfl = rng.ranges_for_length(entity_length)
|
||||||
if not rfl:
|
if rfl and entity_length == 0:
|
||||||
return (None, None)
|
return (None, None, True)
|
||||||
|
elif not rfl:
|
||||||
|
return (None, None, False)
|
||||||
else:
|
else:
|
||||||
# ranges_for_length() adds 1 to the last byte's position
|
# ranges_for_length() adds 1 to the last byte's position
|
||||||
# because webob once made a mistake
|
# because webob once made a mistake
|
||||||
return (rfl[0][0], rfl[0][1] - 1)
|
return (rfl[0][0], rfl[0][1] - 1, True)
|
||||||
|
|
||||||
def _fill_out_range_specs_from_obj_length(self, range_specs):
|
def _fill_out_range_specs_from_obj_length(self, range_specs):
|
||||||
# Add a few fields to each range spec:
|
# Add a few fields to each range spec:
|
||||||
@ -1162,21 +1172,23 @@ class ECAppIter(object):
|
|||||||
# _fill_out_range_specs_from_fa_length() requires the beginnings of
|
# _fill_out_range_specs_from_fa_length() requires the beginnings of
|
||||||
# the response bodies.
|
# the response bodies.
|
||||||
for spec in range_specs:
|
for spec in range_specs:
|
||||||
cstart, cend = self._actual_range(
|
cstart, cend, csat = self._actual_range(
|
||||||
spec['req_client_start'],
|
spec['req_client_start'],
|
||||||
spec['req_client_end'],
|
spec['req_client_end'],
|
||||||
self.obj_length)
|
self.obj_length)
|
||||||
spec['resp_client_start'] = cstart
|
spec['resp_client_start'] = cstart
|
||||||
spec['resp_client_end'] = cend
|
spec['resp_client_end'] = cend
|
||||||
spec['satisfiable'] = (cstart is not None and cend is not None)
|
spec['satisfiable'] = csat
|
||||||
|
|
||||||
sstart, send = self._actual_range(
|
sstart, send, _junk = self._actual_range(
|
||||||
spec['req_segment_start'],
|
spec['req_segment_start'],
|
||||||
spec['req_segment_end'],
|
spec['req_segment_end'],
|
||||||
self.obj_length)
|
self.obj_length)
|
||||||
|
|
||||||
seg_size = self.policy.ec_segment_size
|
seg_size = self.policy.ec_segment_size
|
||||||
if spec['req_segment_start'] is None and sstart % seg_size != 0:
|
if (spec['req_segment_start'] is None and
|
||||||
|
sstart is not None and
|
||||||
|
sstart % seg_size != 0):
|
||||||
# Segment start may, in the case of a suffix request, need
|
# Segment start may, in the case of a suffix request, need
|
||||||
# to be rounded up (not down!) to the nearest segment boundary.
|
# to be rounded up (not down!) to the nearest segment boundary.
|
||||||
# This reflects the trimming of leading garbage (partial
|
# This reflects the trimming of leading garbage (partial
|
||||||
@ -1198,7 +1210,7 @@ class ECAppIter(object):
|
|||||||
# are omitted from the response entirely and also to put the right
|
# are omitted from the response entirely and also to put the right
|
||||||
# Content-Range headers in a multipart/byteranges response.
|
# Content-Range headers in a multipart/byteranges response.
|
||||||
for spec in range_specs:
|
for spec in range_specs:
|
||||||
fstart, fend = self._actual_range(
|
fstart, fend, _junk = self._actual_range(
|
||||||
spec['req_fragment_start'],
|
spec['req_fragment_start'],
|
||||||
spec['req_fragment_end'],
|
spec['req_fragment_end'],
|
||||||
fa_length)
|
fa_length)
|
||||||
@ -1360,6 +1372,11 @@ class ECAppIter(object):
|
|||||||
client_end = (min(client_end, self.obj_length - 1)
|
client_end = (min(client_end, self.obj_length - 1)
|
||||||
if client_end is not None
|
if client_end is not None
|
||||||
else self.obj_length - 1)
|
else self.obj_length - 1)
|
||||||
|
if segment_start is None:
|
||||||
|
num_segments = 0
|
||||||
|
start_overrun = 0
|
||||||
|
end_overrun = 0
|
||||||
|
else:
|
||||||
num_segments = int(
|
num_segments = int(
|
||||||
math.ceil(float(segment_end + 1 - segment_start)
|
math.ceil(float(segment_end + 1 - segment_start)
|
||||||
/ self.policy.ec_segment_size))
|
/ self.policy.ec_segment_size))
|
||||||
|
@ -7127,6 +7127,7 @@ class TestObjectECRangedGET(unittest.TestCase):
|
|||||||
cls.obj_name = 'range-get-test'
|
cls.obj_name = 'range-get-test'
|
||||||
cls.tiny_obj_name = 'range-get-test-tiny'
|
cls.tiny_obj_name = 'range-get-test-tiny'
|
||||||
cls.aligned_obj_name = 'range-get-test-aligned'
|
cls.aligned_obj_name = 'range-get-test-aligned'
|
||||||
|
cls.zero_byte_obj_name = 'range-get-test-zero-byte'
|
||||||
|
|
||||||
# Note: only works if called with unpatched policies
|
# Note: only works if called with unpatched policies
|
||||||
prolis = _test_sockets[0]
|
prolis = _test_sockets[0]
|
||||||
@ -7162,7 +7163,8 @@ class TestObjectECRangedGET(unittest.TestCase):
|
|||||||
|
|
||||||
for obj_name, obj in ((cls.obj_name, cls.obj),
|
for obj_name, obj in ((cls.obj_name, cls.obj),
|
||||||
(cls.tiny_obj_name, cls.tiny_obj),
|
(cls.tiny_obj_name, cls.tiny_obj),
|
||||||
(cls.aligned_obj_name, cls.aligned_obj)):
|
(cls.aligned_obj_name, cls.aligned_obj),
|
||||||
|
(cls.zero_byte_obj_name, b"")):
|
||||||
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
||||||
fd = sock.makefile()
|
fd = sock.makefile()
|
||||||
fd.write('PUT /v1/a/ec-con/%s HTTP/1.1\r\n'
|
fd.write('PUT /v1/a/ec-con/%s HTTP/1.1\r\n'
|
||||||
@ -7427,6 +7429,13 @@ class TestObjectECRangedGET(unittest.TestCase):
|
|||||||
self.assertEqual(headers['Content-Range'], 'bytes 8092-8191/8192')
|
self.assertEqual(headers['Content-Range'], 'bytes 8092-8191/8192')
|
||||||
self.assertEqual(len(gotten_obj), 100)
|
self.assertEqual(len(gotten_obj), 100)
|
||||||
|
|
||||||
|
def test_suffix_zero_byte_object(self):
|
||||||
|
status, headers, gotten_obj = self._get_obj("bytes=-100",
|
||||||
|
self.zero_byte_obj_name)
|
||||||
|
self.assertEqual(status, 200)
|
||||||
|
self.assertEqual(len(gotten_obj), 0)
|
||||||
|
self.assertEqual(gotten_obj, b"")
|
||||||
|
|
||||||
def test_suffix_two_segs(self):
|
def test_suffix_two_segs(self):
|
||||||
# Ask for enough data that we need the last two segments. The last
|
# Ask for enough data that we need the last two segments. The last
|
||||||
# segment is short, though, so this ensures we compensate for that.
|
# segment is short, though, so this ensures we compensate for that.
|
||||||
|
Loading…
Reference in New Issue
Block a user