Merge "py3: adapt proxy base controller"
This commit is contained in:
commit
6c30796a9f
@ -846,6 +846,10 @@ class ResumingGetter(object):
|
|||||||
# we sent out exactly the first range's worth of bytes, so
|
# we sent out exactly the first range's worth of bytes, so
|
||||||
# we're done with it
|
# we're done with it
|
||||||
raise RangeAlreadyComplete()
|
raise RangeAlreadyComplete()
|
||||||
|
|
||||||
|
if end < 0:
|
||||||
|
raise HTTPRequestedRangeNotSatisfiable()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
begin += num_bytes
|
begin += num_bytes
|
||||||
if end is not None and begin == end + 1:
|
if end is not None and begin == end + 1:
|
||||||
@ -853,8 +857,8 @@ class ResumingGetter(object):
|
|||||||
# we're done with it
|
# we're done with it
|
||||||
raise RangeAlreadyComplete()
|
raise RangeAlreadyComplete()
|
||||||
|
|
||||||
if end is not None and (begin > end or end < 0):
|
if end is not None and (begin > end or end < 0):
|
||||||
raise HTTPRequestedRangeNotSatisfiable()
|
raise HTTPRequestedRangeNotSatisfiable()
|
||||||
|
|
||||||
req_range.ranges = [(begin, end)] + req_range.ranges[1:]
|
req_range.ranges = [(begin, end)] + req_range.ranges[1:]
|
||||||
self.backend_headers['Range'] = str(req_range)
|
self.backend_headers['Range'] = str(req_range)
|
||||||
@ -2078,6 +2082,12 @@ class Controller(object):
|
|||||||
return None, response
|
return None, response
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# We used to let the empty input fall through, where json.loads
|
||||||
|
# threw and error. Unfortunately, on py3 it throws JSONDecodeError
|
||||||
|
# that is a pain to catch. So we emulate py2, simple and works
|
||||||
|
# as long as bodies are not iterators.
|
||||||
|
if len(response.body) == 0:
|
||||||
|
raise ValueError('No JSON object could be decoded')
|
||||||
data = json.loads(response.body)
|
data = json.loads(response.body)
|
||||||
if not isinstance(data, list):
|
if not isinstance(data, list):
|
||||||
raise ValueError('not a list')
|
raise ValueError('not a list')
|
||||||
|
@ -18,6 +18,9 @@ import json
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import unittest
|
import unittest
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
from swift.proxy.controllers.base import headers_to_container_info, \
|
from swift.proxy.controllers.base import headers_to_container_info, \
|
||||||
headers_to_account_info, headers_to_object_info, get_container_info, \
|
headers_to_account_info, headers_to_object_info, get_container_info, \
|
||||||
get_cache_key, get_account_info, get_info, get_object_info, \
|
get_cache_key, get_account_info, get_info, get_object_info, \
|
||||||
@ -375,7 +378,10 @@ class TestFuncs(unittest.TestCase):
|
|||||||
self.assertEqual(resp['bytes'], 3333)
|
self.assertEqual(resp['bytes'], 3333)
|
||||||
self.assertEqual(resp['object_count'], 10)
|
self.assertEqual(resp['object_count'], 10)
|
||||||
self.assertEqual(resp['status'], 404)
|
self.assertEqual(resp['status'], 404)
|
||||||
self.assertEqual(resp['versions'], "\xe1\xbd\x8a\x39")
|
if six.PY3:
|
||||||
|
self.assertEqual(resp['versions'], u'\u1f4a9')
|
||||||
|
else:
|
||||||
|
self.assertEqual(resp['versions'], "\xe1\xbd\x8a\x39")
|
||||||
|
|
||||||
def test_get_container_info_env(self):
|
def test_get_container_info_env(self):
|
||||||
cache_key = get_cache_key("account", "cont")
|
cache_key = get_cache_key("account", "cont")
|
||||||
@ -705,9 +711,9 @@ class TestFuncs(unittest.TestCase):
|
|||||||
def test_best_response_overrides(self):
|
def test_best_response_overrides(self):
|
||||||
base = Controller(self.app)
|
base = Controller(self.app)
|
||||||
responses = [
|
responses = [
|
||||||
(302, 'Found', '', 'The resource has moved temporarily.'),
|
(302, 'Found', '', b'The resource has moved temporarily.'),
|
||||||
(100, 'Continue', '', ''),
|
(100, 'Continue', '', b''),
|
||||||
(404, 'Not Found', '', 'Custom body'),
|
(404, 'Not Found', '', b'Custom body'),
|
||||||
]
|
]
|
||||||
server_type = "Base DELETE"
|
server_type = "Base DELETE"
|
||||||
req = Request.blank('/v1/a/c/o', method='DELETE')
|
req = Request.blank('/v1/a/c/o', method='DELETE')
|
||||||
@ -726,7 +732,7 @@ class TestFuncs(unittest.TestCase):
|
|||||||
resp = base.best_response(req, statuses, reasons, bodies, server_type,
|
resp = base.best_response(req, statuses, reasons, bodies, server_type,
|
||||||
headers=headers, overrides=overrides)
|
headers=headers, overrides=overrides)
|
||||||
self.assertEqual(resp.status, '404 Not Found')
|
self.assertEqual(resp.status, '404 Not Found')
|
||||||
self.assertEqual(resp.body, 'Custom body')
|
self.assertEqual(resp.body, b'Custom body')
|
||||||
|
|
||||||
def test_range_fast_forward(self):
|
def test_range_fast_forward(self):
|
||||||
req = Request.blank('/')
|
req = Request.blank('/')
|
||||||
@ -854,7 +860,7 @@ class TestFuncs(unittest.TestCase):
|
|||||||
if self.chunks:
|
if self.chunks:
|
||||||
return self.chunks.pop(0)
|
return self.chunks.pop(0)
|
||||||
else:
|
else:
|
||||||
return ''
|
return b''
|
||||||
|
|
||||||
def getheader(self, header):
|
def getheader(self, header):
|
||||||
if header.lower() == "content-length":
|
if header.lower() == "content-length":
|
||||||
@ -864,7 +870,7 @@ class TestFuncs(unittest.TestCase):
|
|||||||
return [('content-length', self.getheader('content-length'))]
|
return [('content-length', self.getheader('content-length'))]
|
||||||
|
|
||||||
source = TestSource((
|
source = TestSource((
|
||||||
'abcd', '1234', 'abc', 'd1', '234abcd1234abcd1', '2'))
|
b'abcd', b'1234', b'abc', b'd1', b'234abcd1234abcd1', b'2'))
|
||||||
req = Request.blank('/v1/a/c/o')
|
req = Request.blank('/v1/a/c/o')
|
||||||
node = {}
|
node = {}
|
||||||
handler = GetOrHeadHandler(self.app, req, None, None, None, None, {},
|
handler = GetOrHeadHandler(self.app, req, None, None, None, None, {},
|
||||||
@ -873,7 +879,7 @@ class TestFuncs(unittest.TestCase):
|
|||||||
app_iter = handler._make_app_iter(req, node, source)
|
app_iter = handler._make_app_iter(req, node, source)
|
||||||
client_chunks = list(app_iter)
|
client_chunks = list(app_iter)
|
||||||
self.assertEqual(client_chunks, [
|
self.assertEqual(client_chunks, [
|
||||||
'abcd1234', 'abcd1234', 'abcd1234', 'abcd12'])
|
b'abcd1234', b'abcd1234', b'abcd1234', b'abcd12'])
|
||||||
|
|
||||||
def test_client_chunk_size_resuming(self):
|
def test_client_chunk_size_resuming(self):
|
||||||
|
|
||||||
@ -890,7 +896,7 @@ class TestFuncs(unittest.TestCase):
|
|||||||
else:
|
else:
|
||||||
return chunk
|
return chunk
|
||||||
else:
|
else:
|
||||||
return ''
|
return b''
|
||||||
|
|
||||||
def getheader(self, header):
|
def getheader(self, header):
|
||||||
# content-length for the whole object is generated dynamically
|
# content-length for the whole object is generated dynamically
|
||||||
@ -904,11 +910,11 @@ class TestFuncs(unittest.TestCase):
|
|||||||
|
|
||||||
node = {'ip': '1.2.3.4', 'port': 6200, 'device': 'sda'}
|
node = {'ip': '1.2.3.4', 'port': 6200, 'device': 'sda'}
|
||||||
|
|
||||||
source1 = TestSource(['abcd', '1234', None,
|
source1 = TestSource([b'abcd', b'1234', None,
|
||||||
'efgh', '5678', 'lots', 'more', 'data'])
|
b'efgh', b'5678', b'lots', b'more', b'data'])
|
||||||
# incomplete reads of client_chunk_size will be re-fetched
|
# incomplete reads of client_chunk_size will be re-fetched
|
||||||
source2 = TestSource(['efgh', '5678', 'lots', None])
|
source2 = TestSource([b'efgh', b'5678', b'lots', None])
|
||||||
source3 = TestSource(['lots', 'more', 'data'])
|
source3 = TestSource([b'lots', b'more', b'data'])
|
||||||
req = Request.blank('/v1/a/c/o')
|
req = Request.blank('/v1/a/c/o')
|
||||||
handler = GetOrHeadHandler(
|
handler = GetOrHeadHandler(
|
||||||
self.app, req, 'Object', None, None, None, {},
|
self.app, req, 'Object', None, None, None, {},
|
||||||
@ -927,7 +933,7 @@ class TestFuncs(unittest.TestCase):
|
|||||||
client_chunks = list(app_iter)
|
client_chunks = list(app_iter)
|
||||||
self.assertEqual(range_headers, ['bytes=8-27', 'bytes=16-27'])
|
self.assertEqual(range_headers, ['bytes=8-27', 'bytes=16-27'])
|
||||||
self.assertEqual(client_chunks, [
|
self.assertEqual(client_chunks, [
|
||||||
'abcd1234', 'efgh5678', 'lotsmore', 'data'])
|
b'abcd1234', b'efgh5678', b'lotsmore', b'data'])
|
||||||
|
|
||||||
def test_client_chunk_size_resuming_chunked(self):
|
def test_client_chunk_size_resuming_chunked(self):
|
||||||
|
|
||||||
@ -946,7 +952,7 @@ class TestFuncs(unittest.TestCase):
|
|||||||
else:
|
else:
|
||||||
return chunk
|
return chunk
|
||||||
else:
|
else:
|
||||||
return ''
|
return b''
|
||||||
|
|
||||||
def getheader(self, header):
|
def getheader(self, header):
|
||||||
return self.headers.get(header.lower())
|
return self.headers.get(header.lower())
|
||||||
@ -956,8 +962,8 @@ class TestFuncs(unittest.TestCase):
|
|||||||
|
|
||||||
node = {'ip': '1.2.3.4', 'port': 6200, 'device': 'sda'}
|
node = {'ip': '1.2.3.4', 'port': 6200, 'device': 'sda'}
|
||||||
|
|
||||||
source1 = TestChunkedSource(['abcd', '1234', 'abc', None])
|
source1 = TestChunkedSource([b'abcd', b'1234', b'abc', None])
|
||||||
source2 = TestChunkedSource(['efgh5678'])
|
source2 = TestChunkedSource([b'efgh5678'])
|
||||||
req = Request.blank('/v1/a/c/o')
|
req = Request.blank('/v1/a/c/o')
|
||||||
handler = GetOrHeadHandler(
|
handler = GetOrHeadHandler(
|
||||||
self.app, req, 'Object', None, None, None, {},
|
self.app, req, 'Object', None, None, None, {},
|
||||||
@ -967,7 +973,7 @@ class TestFuncs(unittest.TestCase):
|
|||||||
with mock.patch.object(handler, '_get_source_and_node',
|
with mock.patch.object(handler, '_get_source_and_node',
|
||||||
lambda: (source2, node)):
|
lambda: (source2, node)):
|
||||||
client_chunks = list(app_iter)
|
client_chunks = list(app_iter)
|
||||||
self.assertEqual(client_chunks, ['abcd1234', 'efgh5678'])
|
self.assertEqual(client_chunks, [b'abcd1234', b'efgh5678'])
|
||||||
|
|
||||||
def test_disconnected_warning(self):
|
def test_disconnected_warning(self):
|
||||||
self.app.logger = mock.Mock()
|
self.app.logger = mock.Mock()
|
||||||
@ -980,7 +986,7 @@ class TestFuncs(unittest.TestCase):
|
|||||||
self.status = 200
|
self.status = 200
|
||||||
|
|
||||||
def read(self, _read_size):
|
def read(self, _read_size):
|
||||||
return 'the cake is a lie'
|
return b'the cake is a lie'
|
||||||
|
|
||||||
def getheader(self, header):
|
def getheader(self, header):
|
||||||
return self.headers.get(header.lower())
|
return self.headers.get(header.lower())
|
||||||
@ -1047,7 +1053,8 @@ class TestFuncs(unittest.TestCase):
|
|||||||
req = Request.blank('/v1/a/c', method='GET')
|
req = Request.blank('/v1/a/c', method='GET')
|
||||||
resp_headers = {'X-Backend-Record-Type': 'shard'}
|
resp_headers = {'X-Backend-Record-Type': 'shard'}
|
||||||
with mocked_http_conn(
|
with mocked_http_conn(
|
||||||
200, 200, body_iter=iter(['', json.dumps(shard_ranges)]),
|
200, 200,
|
||||||
|
body_iter=iter([b'', json.dumps(shard_ranges).encode('ascii')]),
|
||||||
headers=resp_headers
|
headers=resp_headers
|
||||||
) as fake_conn:
|
) as fake_conn:
|
||||||
actual = base._get_shard_ranges(req, 'a', 'c')
|
actual = base._get_shard_ranges(req, 'a', 'c')
|
||||||
@ -1076,7 +1083,9 @@ class TestFuncs(unittest.TestCase):
|
|||||||
req = Request.blank('/v1/a/c/o', method='PUT')
|
req = Request.blank('/v1/a/c/o', method='PUT')
|
||||||
resp_headers = {'X-Backend-Record-Type': 'shard'}
|
resp_headers = {'X-Backend-Record-Type': 'shard'}
|
||||||
with mocked_http_conn(
|
with mocked_http_conn(
|
||||||
200, 200, body_iter=iter(['', json.dumps(shard_ranges[1:2])]),
|
200, 200,
|
||||||
|
body_iter=iter([b'',
|
||||||
|
json.dumps(shard_ranges[1:2]).encode('ascii')]),
|
||||||
headers=resp_headers
|
headers=resp_headers
|
||||||
) as fake_conn:
|
) as fake_conn:
|
||||||
actual = base._get_shard_ranges(req, 'a', 'c', '1_test')
|
actual = base._get_shard_ranges(req, 'a', 'c', '1_test')
|
||||||
@ -1101,7 +1110,7 @@ class TestFuncs(unittest.TestCase):
|
|||||||
req = Request.blank('/v1/a/c/o', method='PUT')
|
req = Request.blank('/v1/a/c/o', method='PUT')
|
||||||
# empty response
|
# empty response
|
||||||
headers = {'X-Backend-Record-Type': 'shard'}
|
headers = {'X-Backend-Record-Type': 'shard'}
|
||||||
with mocked_http_conn(200, 200, body_iter=iter(['', body]),
|
with mocked_http_conn(200, 200, body_iter=iter([b'', body]),
|
||||||
headers=headers):
|
headers=headers):
|
||||||
actual = base._get_shard_ranges(req, 'a', 'c', '1_test')
|
actual = base._get_shard_ranges(req, 'a', 'c', '1_test')
|
||||||
self.assertIsNone(actual)
|
self.assertIsNone(actual)
|
||||||
@ -1109,19 +1118,21 @@ class TestFuncs(unittest.TestCase):
|
|||||||
return lines
|
return lines
|
||||||
|
|
||||||
def test_get_shard_ranges_empty_body(self):
|
def test_get_shard_ranges_empty_body(self):
|
||||||
error_lines = self._check_get_shard_ranges_bad_data('')
|
error_lines = self._check_get_shard_ranges_bad_data(b'')
|
||||||
self.assertIn('Problem with listing response', error_lines[0])
|
self.assertIn('Problem with listing response', error_lines[0])
|
||||||
self.assertIn('No JSON', error_lines[0])
|
self.assertIn('No JSON', error_lines[0])
|
||||||
self.assertFalse(error_lines[1:])
|
self.assertFalse(error_lines[1:])
|
||||||
|
|
||||||
def test_get_shard_ranges_not_a_list(self):
|
def test_get_shard_ranges_not_a_list(self):
|
||||||
error_lines = self._check_get_shard_ranges_bad_data(json.dumps({}))
|
body = json.dumps({}).encode('ascii')
|
||||||
|
error_lines = self._check_get_shard_ranges_bad_data(body)
|
||||||
self.assertIn('Problem with listing response', error_lines[0])
|
self.assertIn('Problem with listing response', error_lines[0])
|
||||||
self.assertIn('not a list', error_lines[0])
|
self.assertIn('not a list', error_lines[0])
|
||||||
self.assertFalse(error_lines[1:])
|
self.assertFalse(error_lines[1:])
|
||||||
|
|
||||||
def test_get_shard_ranges_key_missing(self):
|
def test_get_shard_ranges_key_missing(self):
|
||||||
error_lines = self._check_get_shard_ranges_bad_data(json.dumps([{}]))
|
body = json.dumps([{}]).encode('ascii')
|
||||||
|
error_lines = self._check_get_shard_ranges_bad_data(body)
|
||||||
self.assertIn('Failed to get shard ranges', error_lines[0])
|
self.assertIn('Failed to get shard ranges', error_lines[0])
|
||||||
self.assertIn('KeyError', error_lines[0])
|
self.assertIn('KeyError', error_lines[0])
|
||||||
self.assertFalse(error_lines[1:])
|
self.assertFalse(error_lines[1:])
|
||||||
@ -1129,8 +1140,8 @@ class TestFuncs(unittest.TestCase):
|
|||||||
def test_get_shard_ranges_invalid_shard_range(self):
|
def test_get_shard_ranges_invalid_shard_range(self):
|
||||||
sr = ShardRange('a/c', Timestamp.now())
|
sr = ShardRange('a/c', Timestamp.now())
|
||||||
bad_sr_data = dict(sr, name='bad_name')
|
bad_sr_data = dict(sr, name='bad_name')
|
||||||
error_lines = self._check_get_shard_ranges_bad_data(
|
body = json.dumps([bad_sr_data]).encode('ascii')
|
||||||
json.dumps([bad_sr_data]))
|
error_lines = self._check_get_shard_ranges_bad_data(body)
|
||||||
self.assertIn('Failed to get shard ranges', error_lines[0])
|
self.assertIn('Failed to get shard ranges', error_lines[0])
|
||||||
self.assertIn('ValueError', error_lines[0])
|
self.assertIn('ValueError', error_lines[0])
|
||||||
self.assertFalse(error_lines[1:])
|
self.assertFalse(error_lines[1:])
|
||||||
@ -1139,9 +1150,9 @@ class TestFuncs(unittest.TestCase):
|
|||||||
base = Controller(self.app)
|
base = Controller(self.app)
|
||||||
req = Request.blank('/v1/a/c/o', method='PUT')
|
req = Request.blank('/v1/a/c/o', method='PUT')
|
||||||
sr = ShardRange('a/c', Timestamp.now())
|
sr = ShardRange('a/c', Timestamp.now())
|
||||||
body = json.dumps([dict(sr)])
|
body = json.dumps([dict(sr)]).encode('ascii')
|
||||||
with mocked_http_conn(
|
with mocked_http_conn(
|
||||||
200, 200, body_iter=iter(['', body])):
|
200, 200, body_iter=iter([b'', body])):
|
||||||
actual = base._get_shard_ranges(req, 'a', 'c', '1_test')
|
actual = base._get_shard_ranges(req, 'a', 'c', '1_test')
|
||||||
self.assertIsNone(actual)
|
self.assertIsNone(actual)
|
||||||
error_lines = self.app.logger.get_lines_for_level('error')
|
error_lines = self.app.logger.get_lines_for_level('error')
|
||||||
@ -1154,10 +1165,10 @@ class TestFuncs(unittest.TestCase):
|
|||||||
base = Controller(self.app)
|
base = Controller(self.app)
|
||||||
req = Request.blank('/v1/a/c/o', method='PUT')
|
req = Request.blank('/v1/a/c/o', method='PUT')
|
||||||
sr = ShardRange('a/c', Timestamp.now())
|
sr = ShardRange('a/c', Timestamp.now())
|
||||||
body = json.dumps([dict(sr)])
|
body = json.dumps([dict(sr)]).encode('ascii')
|
||||||
headers = {'X-Backend-Record-Type': 'object'}
|
headers = {'X-Backend-Record-Type': 'object'}
|
||||||
with mocked_http_conn(
|
with mocked_http_conn(
|
||||||
200, 200, body_iter=iter(['', body]),
|
200, 200, body_iter=iter([b'', body]),
|
||||||
headers=headers):
|
headers=headers):
|
||||||
actual = base._get_shard_ranges(req, 'a', 'c', '1_test')
|
actual = base._get_shard_ranges(req, 'a', 'c', '1_test')
|
||||||
self.assertIsNone(actual)
|
self.assertIsNone(actual)
|
||||||
|
1
tox.ini
1
tox.ini
@ -87,6 +87,7 @@ commands =
|
|||||||
test/unit/container/test_sync_store.py \
|
test/unit/container/test_sync_store.py \
|
||||||
test/unit/obj/test_replicator.py \
|
test/unit/obj/test_replicator.py \
|
||||||
test/unit/obj/test_server.py \
|
test/unit/obj/test_server.py \
|
||||||
|
test/unit/proxy/controllers/test_base.py \
|
||||||
test/unit/proxy/controllers/test_info.py \
|
test/unit/proxy/controllers/test_info.py \
|
||||||
test/unit/proxy/controllers/test_obj.py}
|
test/unit/proxy/controllers/test_obj.py}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user