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