Merge "Fix 499 client disconnected on COPY EC object"

This commit is contained in:
Jenkins 2015-07-24 06:55:05 +00:00 committed by Gerrit Code Review
commit 8a2f2edf1c
4 changed files with 71 additions and 5 deletions

View File

@ -1129,6 +1129,7 @@ class Response(object):
self.request = request
self.body = body
self.app_iter = app_iter
self.response_iter = None
self.status = status
self.boundary = "%.32x" % random.randint(0, 256 ** 16)
if request:
@ -1324,6 +1325,17 @@ class Response(object):
return [body]
return ['']
def fix_conditional_response(self):
"""
You may call this once you have set the content_length to the whole
object length and body or app_iter to reset the content_length
properties on the request.
It is ok to not call this method, the conditional resposne will be
maintained for you when you __call__ the response.
"""
self.response_iter = self._response_iter(self.app_iter, self._body)
def absolute_location(self):
"""
Attempt to construct an absolute location.
@ -1374,12 +1386,15 @@ class Response(object):
if not self.request:
self.request = Request(env)
self.environ = env
app_iter = self._response_iter(self.app_iter, self._body)
if not self.response_iter:
self.response_iter = self._response_iter(self.app_iter, self._body)
if 'location' in self.headers and \
not env.get('swift.leave_relative_location'):
self.location = self.absolute_location()
start_response(self.status, self.headers.items())
return app_iter
return self.response_iter
class HTTPException(Response, Exception):

View File

@ -2203,11 +2203,10 @@ class ECObjectController(BaseObjectController):
resp = self.best_response(
req, statuses, reasons, bodies, 'Object',
headers=headers)
self._fix_response_headers(resp)
self._fix_response(resp)
return resp
def _fix_response_headers(self, resp):
def _fix_response(self, resp):
# EC fragment archives each have different bytes, hence different
# etags. However, they all have the original object's etag stored in
# sysmeta, so we copy that here so the client gets it.
@ -2215,6 +2214,7 @@ class ECObjectController(BaseObjectController):
'X-Object-Sysmeta-Ec-Etag')
resp.headers['Content-Length'] = resp.headers.get(
'X-Object-Sysmeta-Ec-Content-Length')
resp.fix_conditional_response()
return resp

View File

@ -243,6 +243,23 @@ class TestObject(unittest.TestCase):
self.assertEqual(resp.status, 200)
self.assertEqual(dest_contents, source_contents)
# copy source to dest with COPY and range
def copy(url, token, parsed, conn):
conn.request('COPY', '%s/%s' % (parsed.path, source), '',
{'X-Auth-Token': token,
'Destination': dest,
'Range': 'bytes=1-2'})
return check_response(conn)
resp = retry(copy)
resp.read()
self.assertEqual(resp.status, 201)
# contents of dest should be the same as source
resp = retry(get_dest)
dest_contents = resp.read()
self.assertEqual(resp.status, 200)
self.assertEqual(dest_contents, source_contents[1:3])
# delete the copy
resp = retry(delete)
resp.read()

View File

@ -1458,6 +1458,40 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
self.assertEquals(resp.status_int, 201)
self.assertTrue(response_time < response_sleep)
def test_COPY_with_ranges(self):
req = swift.common.swob.Request.blank(
'/v1/a/c/o', method='COPY',
headers={'Destination': 'c1/o',
'Range': 'bytes=5-10'})
# turn a real body into fragments
segment_size = self.policy.ec_segment_size
real_body = ('asdf' * segment_size)[:-10]
# split it up into chunks
chunks = [real_body[x:x + segment_size]
for x in range(0, len(real_body), segment_size)]
# we need only first chunk to rebuild 5-10 range
fragments = self.policy.pyeclib_driver.encode(chunks[0])
fragment_payloads = []
fragment_payloads.append(fragments)
node_fragments = zip(*fragment_payloads)
self.assertEqual(len(node_fragments), self.replicas()) # sanity
headers = {'X-Object-Sysmeta-Ec-Content-Length': str(len(real_body))}
responses = [(200, ''.join(node_fragments[i]), headers)
for i in range(POLICIES.default.ec_ndata)]
responses += [(201, '', {})] * self.obj_ring.replicas
status_codes, body_iter, headers = zip(*responses)
expect_headers = {
'X-Obj-Metadata-Footer': 'yes',
'X-Obj-Multiphase-Commit': 'yes'
}
with set_http_connect(*status_codes, body_iter=body_iter,
headers=headers, expect_headers=expect_headers):
resp = req.get_response(self.app)
self.assertEquals(resp.status_int, 201)
if __name__ == '__main__':
unittest.main()