Merge "Add more unit tests for ranged GETs"
This commit is contained in:
commit
740636b8e4
|
@ -56,7 +56,8 @@ from swift.common.utils import hash_path, mkdirs, normalize_timestamp, \
|
|||
Timestamp, md5
|
||||
from swift.common import constraints
|
||||
from swift.common.request_helpers import get_reserved_name
|
||||
from swift.common.swob import Request, WsgiBytesIO
|
||||
from swift.common.swob import Request, WsgiBytesIO, \
|
||||
HTTPRequestedRangeNotSatisfiable
|
||||
from swift.common.splice import splice
|
||||
from swift.common.storage_policy import (StoragePolicy, ECStoragePolicy,
|
||||
POLICIES, EC_POLICY)
|
||||
|
@ -3291,6 +3292,7 @@ class TestObjectController(BaseTestCase):
|
|||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 416)
|
||||
self.assertIn(b'Not Satisfiable', resp.body)
|
||||
self.assertEqual('bytes */6', resp.headers['content-range'])
|
||||
|
||||
# Proxy (SLO in particular) can say that if some metadata's present,
|
||||
# it wants the whole thing
|
||||
|
@ -3302,6 +3304,7 @@ class TestObjectController(BaseTestCase):
|
|||
self.assertEqual(resp.status_int, 200)
|
||||
self.assertEqual(resp.body, b'VERIFY')
|
||||
self.assertEqual(resp.headers['content-length'], '6')
|
||||
self.assertNotIn('content-range', resp.headers)
|
||||
|
||||
# If it's not present, Range is still respected
|
||||
req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'GET'})
|
||||
|
@ -3312,6 +3315,7 @@ class TestObjectController(BaseTestCase):
|
|||
self.assertEqual(resp.status_int, 206)
|
||||
self.assertEqual(resp.body, b'ERI')
|
||||
self.assertEqual(resp.headers['content-length'], '3')
|
||||
self.assertEqual('bytes 1-3/6', resp.headers['content-range'])
|
||||
|
||||
# Works like "any", not "all"; also works where we would've 416ed
|
||||
req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'GET'})
|
||||
|
@ -3322,6 +3326,7 @@ class TestObjectController(BaseTestCase):
|
|||
self.assertEqual(resp.status_int, 200)
|
||||
self.assertEqual(resp.body, b'VERIFY')
|
||||
self.assertEqual(resp.headers['content-length'], '6')
|
||||
self.assertNotIn('content-range', resp.headers)
|
||||
|
||||
objfile = os.path.join(
|
||||
self.testdir, 'sda1',
|
||||
|
@ -3374,6 +3379,39 @@ class TestObjectController(BaseTestCase):
|
|||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 200)
|
||||
|
||||
def test_GET_range_not_satisfiable(self):
|
||||
timestamp = normalize_timestamp(time())
|
||||
req = Request.blank('/sda1/p/a/c/zero-byte',
|
||||
environ={'REQUEST_METHOD': 'PUT'},
|
||||
headers={'X-Timestamp': timestamp,
|
||||
'Content-Type': 'application/x-test'})
|
||||
req.body = b'7 bytes'
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 201)
|
||||
|
||||
req = Request.blank('/sda1/p/a/c/zero-byte',
|
||||
environ={'REQUEST_METHOD': 'GET'},
|
||||
headers={'Range': 'bytes=1-20, 30-40'})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 206)
|
||||
self.assertEqual('bytes 1-6/7', resp.headers.get('Content-Range'))
|
||||
self.assertEqual(b' bytes', resp.body)
|
||||
|
||||
req = Request.blank('/sda1/p/a/c/zero-byte',
|
||||
environ={'REQUEST_METHOD': 'GET'},
|
||||
headers={'Range': 'bytes=10-20'})
|
||||
resp = req.get_response(self.object_controller)
|
||||
self.assertEqual(resp.status_int, 416)
|
||||
self.assertEqual('bytes */7', resp.headers.get('Content-Range'))
|
||||
exp_resp_body = b''.join(
|
||||
HTTPRequestedRangeNotSatisfiable()({}, lambda *args: None))
|
||||
self.assertEqual(str(len(exp_resp_body)),
|
||||
resp.headers.get('Content-Length'))
|
||||
self.assertEqual(
|
||||
'"%s"' % md5(b'7 bytes', usedforsecurity=False).hexdigest(),
|
||||
resp.headers.get('Etag'))
|
||||
self.assertEqual(exp_resp_body, resp.body)
|
||||
|
||||
def test_GET_if_match(self):
|
||||
req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||
headers={
|
||||
|
|
|
@ -1623,6 +1623,99 @@ class TestReplicatedObjController(CommonObjectControllerMixin,
|
|||
'Expected 1 ERROR lines, got %r' % (
|
||||
self.logger.logger.records['ERROR'], ))
|
||||
|
||||
def test_GET_resuming_ignores_416(self):
|
||||
# verify that a resuming getter will not try to use the content of a
|
||||
# 416 response (because it's etag will mismatch that from the first
|
||||
# response)
|
||||
self.app.recoverable_node_timeout = 0.01
|
||||
self.app.client_timeout = 0.1
|
||||
self.app.object_chunk_size = 10
|
||||
body = b'length 8'
|
||||
body_short = b'four'
|
||||
body_416 = b'<html><h1>Requested Range Not Satisfiable</h1>' \
|
||||
b'<p>The Range requested is not available.</p></html>'
|
||||
etag = md5(body, usedforsecurity=False).hexdigest()
|
||||
etag_short = md5(body_short, usedforsecurity=False).hexdigest()
|
||||
headers_206 = {
|
||||
'Etag': etag,
|
||||
'Content-Length': len(body),
|
||||
'X-Timestamp': Timestamp(self.ts()).normal,
|
||||
'Content-Range': 'bytes 7-8/8'
|
||||
}
|
||||
headers_416 = {
|
||||
# note: 416 when applying the same range implies different object
|
||||
# length and therefore different etag
|
||||
'Etag': etag_short,
|
||||
'Content-Length': len(body_416),
|
||||
'X-Timestamp': Timestamp(self.ts()).normal,
|
||||
'Content-Range': 'bytes */4'
|
||||
}
|
||||
req = swift.common.swob.Request.blank(
|
||||
'/v1/a/c/o', headers={'Range': 'bytes=7-8'})
|
||||
# make the first response slow...
|
||||
read_sleeps = [0.1, 0]
|
||||
with mocked_http_conn(206, 416, 206, body_iter=[body, body_416, body],
|
||||
headers=[headers_206, headers_416, headers_206],
|
||||
slow=read_sleeps) as log:
|
||||
resp = req.get_response(self.app)
|
||||
self.assertEqual(resp.status_int, 206)
|
||||
resp_body = resp.body
|
||||
self.assertEqual(b'length 8', resp_body)
|
||||
self.assertEqual(len(log.requests), 3)
|
||||
self.assertEqual('bytes=7-8', log.requests[0]['headers']['Range'])
|
||||
self.assertEqual('bytes=7-8', log.requests[1]['headers']['Range'])
|
||||
self.assertEqual('bytes=7-8', log.requests[2]['headers']['Range'])
|
||||
|
||||
def test_GET_resuming(self):
|
||||
self.app.recoverable_node_timeout = 0.01
|
||||
self.app.client_timeout = 0.1
|
||||
self.app.object_chunk_size = 10
|
||||
body = b'length 8'
|
||||
etag = md5(body, usedforsecurity=False).hexdigest()
|
||||
headers_200 = {
|
||||
'Etag': etag,
|
||||
'Content-Length': len(body),
|
||||
'X-Timestamp': Timestamp(self.ts()).normal,
|
||||
}
|
||||
headers_206 = {
|
||||
# note: use of 'X-Backend-Ignore-Range-If-Metadata-Present' in
|
||||
# request means that 200 response did not evaluate the Range and
|
||||
# the proxy modifies requested backend range accordingly
|
||||
'Etag': etag,
|
||||
'Content-Length': len(body),
|
||||
'X-Timestamp': Timestamp(self.ts()).normal,
|
||||
'Content-Range': 'bytes 0-7/8'
|
||||
}
|
||||
req = swift.common.swob.Request.blank(
|
||||
'/v1/a/c/o',
|
||||
headers={'Range': 'bytes=9-10, 20-30',
|
||||
'X-Backend-Ignore-Range-If-Metadata-Present':
|
||||
'X-Static-Large-Object'})
|
||||
# make the first 2 responses slow...
|
||||
read_sleeps = [0.1, 0.1, 0]
|
||||
with mocked_http_conn(200, 206, 206, body_iter=[body, body, body],
|
||||
headers=[headers_200, headers_206, headers_206],
|
||||
slow=read_sleeps) as log:
|
||||
resp = req.get_response(self.app)
|
||||
self.assertEqual(resp.status_int, 200)
|
||||
resp_body = resp.body
|
||||
self.assertEqual(b'length 8', resp_body)
|
||||
self.assertEqual(len(log.requests), 3)
|
||||
# NB: original range is not satisfiable but is ignored
|
||||
self.assertEqual('bytes=9-10, 20-30',
|
||||
log.requests[0]['headers']['Range'])
|
||||
self.assertIn('X-Backend-Ignore-Range-If-Metadata-Present',
|
||||
log.requests[0]['headers'])
|
||||
# backend Range is updated to something that is satisfiable
|
||||
self.assertEqual('bytes=0-7,20-30',
|
||||
log.requests[1]['headers']['Range'])
|
||||
self.assertNotIn('X-Backend-Ignore-Range-If-Metadata-Present',
|
||||
log.requests[1]['headers'])
|
||||
self.assertEqual('bytes=0-7,20-30',
|
||||
log.requests[2]['headers']['Range'])
|
||||
self.assertNotIn('X-Backend-Ignore-Range-If-Metadata-Present',
|
||||
log.requests[2]['headers'])
|
||||
|
||||
def test_GET_transfer_encoding_chunked(self):
|
||||
req = swift.common.swob.Request.blank('/v1/a/c/o')
|
||||
with set_http_connect(200, headers={'transfer-encoding': 'chunked'}):
|
||||
|
|
Loading…
Reference in New Issue