Merge "Fix 499 client disconnected on COPY EC object"
This commit is contained in:
commit
8a2f2edf1c
@ -1129,6 +1129,7 @@ class Response(object):
|
|||||||
self.request = request
|
self.request = request
|
||||||
self.body = body
|
self.body = body
|
||||||
self.app_iter = app_iter
|
self.app_iter = app_iter
|
||||||
|
self.response_iter = None
|
||||||
self.status = status
|
self.status = status
|
||||||
self.boundary = "%.32x" % random.randint(0, 256 ** 16)
|
self.boundary = "%.32x" % random.randint(0, 256 ** 16)
|
||||||
if request:
|
if request:
|
||||||
@ -1324,6 +1325,17 @@ class Response(object):
|
|||||||
return [body]
|
return [body]
|
||||||
return ['']
|
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):
|
def absolute_location(self):
|
||||||
"""
|
"""
|
||||||
Attempt to construct an absolute location.
|
Attempt to construct an absolute location.
|
||||||
@ -1374,12 +1386,15 @@ class Response(object):
|
|||||||
if not self.request:
|
if not self.request:
|
||||||
self.request = Request(env)
|
self.request = Request(env)
|
||||||
self.environ = 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 \
|
if 'location' in self.headers and \
|
||||||
not env.get('swift.leave_relative_location'):
|
not env.get('swift.leave_relative_location'):
|
||||||
self.location = self.absolute_location()
|
self.location = self.absolute_location()
|
||||||
start_response(self.status, self.headers.items())
|
start_response(self.status, self.headers.items())
|
||||||
return app_iter
|
return self.response_iter
|
||||||
|
|
||||||
|
|
||||||
class HTTPException(Response, Exception):
|
class HTTPException(Response, Exception):
|
||||||
|
@ -2203,11 +2203,10 @@ class ECObjectController(BaseObjectController):
|
|||||||
resp = self.best_response(
|
resp = self.best_response(
|
||||||
req, statuses, reasons, bodies, 'Object',
|
req, statuses, reasons, bodies, 'Object',
|
||||||
headers=headers)
|
headers=headers)
|
||||||
|
self._fix_response(resp)
|
||||||
self._fix_response_headers(resp)
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
def _fix_response_headers(self, resp):
|
def _fix_response(self, resp):
|
||||||
# EC fragment archives each have different bytes, hence different
|
# EC fragment archives each have different bytes, hence different
|
||||||
# etags. However, they all have the original object's etag stored in
|
# etags. However, they all have the original object's etag stored in
|
||||||
# sysmeta, so we copy that here so the client gets it.
|
# sysmeta, so we copy that here so the client gets it.
|
||||||
@ -2215,6 +2214,7 @@ class ECObjectController(BaseObjectController):
|
|||||||
'X-Object-Sysmeta-Ec-Etag')
|
'X-Object-Sysmeta-Ec-Etag')
|
||||||
resp.headers['Content-Length'] = resp.headers.get(
|
resp.headers['Content-Length'] = resp.headers.get(
|
||||||
'X-Object-Sysmeta-Ec-Content-Length')
|
'X-Object-Sysmeta-Ec-Content-Length')
|
||||||
|
resp.fix_conditional_response()
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
@ -243,6 +243,23 @@ class TestObject(unittest.TestCase):
|
|||||||
self.assertEqual(resp.status, 200)
|
self.assertEqual(resp.status, 200)
|
||||||
self.assertEqual(dest_contents, source_contents)
|
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
|
# delete the copy
|
||||||
resp = retry(delete)
|
resp = retry(delete)
|
||||||
resp.read()
|
resp.read()
|
||||||
|
@ -1458,6 +1458,40 @@ class TestECObjController(BaseObjectControllerMixin, unittest.TestCase):
|
|||||||
self.assertEquals(resp.status_int, 201)
|
self.assertEquals(resp.status_int, 201)
|
||||||
self.assertTrue(response_time < response_sleep)
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user