Port rpc and wsgi to Python 3
* rpc: allow also exceptions from the builtins module. On Python 3, builtin exceptions are part of the builtins module. The exceptions module was removed in Python 3. * Fix usage of reraise(): translate_exception() returns an instance which is the second parameter of the reraise() function, not the first parameter. The first parameter is the exception type. * test_rpc: add json_dump_as_bytes() helper to serialize JSON as bytes. * JSONRequestDeserializer: don't compare None to int, it raises a TypeError on Python 3. * Fix Unicode versus bytes issues: HTTP body type is bytes. Encode JSON to UTF-8 for example. Replace StringIO with BytesIO. * Replace filter() with a list-comprehension to get a list on Python 3. * test_client, test_image_cache_client: use bytes for HTTP body. * tox.ini: add glance.tests.unit.common.test_rpc to Python 3. Change-Id: I9960d6a810375474f89c1788b7016a8fbb0770e0
This commit is contained in:
parent
27ca72587e
commit
3ba4a591ee
@ -42,6 +42,7 @@ rpc_opts = [
|
||||
# from oslo rpc.
|
||||
cfg.ListOpt('allowed_rpc_exception_modules',
|
||||
default=['glance.common.exception',
|
||||
'builtins',
|
||||
'exceptions',
|
||||
],
|
||||
help='Modules of exceptions that are permitted to be recreated'
|
||||
|
@ -774,7 +774,7 @@ class JSONRequestDeserializer(object):
|
||||
is_valid_encoding = request_encoding in self.valid_transfer_encoding
|
||||
if is_valid_encoding and request.is_body_readable:
|
||||
return True
|
||||
elif request.content_length > 0:
|
||||
elif request.content_length is not None and request.content_length > 0:
|
||||
return True
|
||||
|
||||
return False
|
||||
@ -813,7 +813,10 @@ class JSONResponseSerializer(object):
|
||||
|
||||
def default(self, response, result):
|
||||
response.content_type = 'application/json'
|
||||
response.body = self.to_json(result)
|
||||
body = self.to_json(result)
|
||||
if isinstance(body, six.text_type):
|
||||
body = body.encode('utf-8')
|
||||
response.body = body
|
||||
|
||||
|
||||
def translate_exception(req, e):
|
||||
@ -879,7 +882,8 @@ class Resource(object):
|
||||
request, **action_args)
|
||||
except webob.exc.WSGIHTTPException as e:
|
||||
exc_info = sys.exc_info()
|
||||
six.reraise(translate_exception(request, e), None, exc_info[2])
|
||||
e = translate_exception(request, e)
|
||||
six.reraise(type(e), e, exc_info[2])
|
||||
except Exception as e:
|
||||
LOG.exception(_LE("Caught error: %s"), six.text_type(e))
|
||||
response = webob.exc.HTTPInternalServerError()
|
||||
|
@ -62,7 +62,7 @@ class TestClient(testtools.TestCase):
|
||||
|
||||
# Lets fake the response
|
||||
# returned by http_client
|
||||
fake = utils.FakeHTTPResponse(data="Ok")
|
||||
fake = utils.FakeHTTPResponse(data=b"Ok")
|
||||
http_client.HTTPConnection.getresponse().AndReturn(fake)
|
||||
self.mock.ReplayAll()
|
||||
|
||||
@ -80,7 +80,7 @@ class TestClient(testtools.TestCase):
|
||||
|
||||
# Lets fake the response
|
||||
# returned by http_client
|
||||
fake = utils.FakeHTTPResponse(data="Ok")
|
||||
fake = utils.FakeHTTPResponse(data=b"Ok")
|
||||
http_client.HTTPConnection.getresponse().AndReturn(fake)
|
||||
self.mock.ReplayAll()
|
||||
|
||||
|
@ -19,6 +19,7 @@ import datetime
|
||||
from oslo_config import cfg
|
||||
from oslo_serialization import jsonutils
|
||||
import routes
|
||||
import six
|
||||
import webob
|
||||
|
||||
from glance.common import exception
|
||||
@ -30,6 +31,13 @@ from glance.tests import utils as test_utils
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
def json_dump_as_bytes(obj):
|
||||
body = jsonutils.dumps(obj)
|
||||
if isinstance(body, six.text_type):
|
||||
body = body.encode('utf-8')
|
||||
return body
|
||||
|
||||
|
||||
class FakeResource(object):
|
||||
"""
|
||||
Fake resource defining some methods that
|
||||
@ -118,7 +126,7 @@ class TestRPCController(base.IsolatedUnitTest):
|
||||
api = create_api()
|
||||
req = webob.Request.blank('/rpc')
|
||||
req.method = 'POST'
|
||||
req.body = jsonutils.dumps([
|
||||
req.body = json_dump_as_bytes([
|
||||
{
|
||||
"command": "get_images",
|
||||
"kwargs": {"keyword": 1}
|
||||
@ -133,7 +141,7 @@ class TestRPCController(base.IsolatedUnitTest):
|
||||
api = create_api()
|
||||
req = webob.Request.blank('/rpc')
|
||||
req.method = 'POST'
|
||||
req.body = jsonutils.dumps([
|
||||
req.body = json_dump_as_bytes([
|
||||
{
|
||||
"command": "get_all_images",
|
||||
"kwargs": {"keyword": 1}
|
||||
@ -153,27 +161,27 @@ class TestRPCController(base.IsolatedUnitTest):
|
||||
req.content_type = 'application/json'
|
||||
|
||||
# Body is not a list, it should fail
|
||||
req.body = jsonutils.dumps({})
|
||||
req.body = json_dump_as_bytes({})
|
||||
res = req.get_response(api)
|
||||
self.assertEqual(400, res.status_int)
|
||||
|
||||
# cmd is not dict, it should fail.
|
||||
req.body = jsonutils.dumps([None])
|
||||
req.body = json_dump_as_bytes([None])
|
||||
res = req.get_response(api)
|
||||
self.assertEqual(400, res.status_int)
|
||||
|
||||
# No command key, it should fail.
|
||||
req.body = jsonutils.dumps([{}])
|
||||
req.body = json_dump_as_bytes([{}])
|
||||
res = req.get_response(api)
|
||||
self.assertEqual(400, res.status_int)
|
||||
|
||||
# kwargs not dict, it should fail.
|
||||
req.body = jsonutils.dumps([{"command": "test", "kwargs": 200}])
|
||||
req.body = json_dump_as_bytes([{"command": "test", "kwargs": 200}])
|
||||
res = req.get_response(api)
|
||||
self.assertEqual(400, res.status_int)
|
||||
|
||||
# Command does not exist, it should fail.
|
||||
req.body = jsonutils.dumps([{"command": "test"}])
|
||||
req.body = json_dump_as_bytes([{"command": "test"}])
|
||||
res = req.get_response(api)
|
||||
self.assertEqual(404, res.status_int)
|
||||
|
||||
@ -183,14 +191,15 @@ class TestRPCController(base.IsolatedUnitTest):
|
||||
req.method = 'POST'
|
||||
req.content_type = 'application/json'
|
||||
|
||||
req.body = jsonutils.dumps([{"command": "raise_value_error"}])
|
||||
req.body = json_dump_as_bytes([{"command": "raise_value_error"}])
|
||||
res = req.get_response(api)
|
||||
self.assertEqual(200, res.status_int)
|
||||
|
||||
returned = jsonutils.loads(res.body)[0]
|
||||
self.assertEqual('exceptions.ValueError', returned['_error']['cls'])
|
||||
err_cls = 'builtins.ValueError' if six.PY3 else 'exceptions.ValueError'
|
||||
self.assertEqual(err_cls, returned['_error']['cls'])
|
||||
|
||||
req.body = jsonutils.dumps([{"command": "raise_weird_error"}])
|
||||
req.body = json_dump_as_bytes([{"command": "raise_weird_error"}])
|
||||
res = req.get_response(api)
|
||||
self.assertEqual(200, res.status_int)
|
||||
|
||||
@ -209,6 +218,8 @@ class TestRPCClient(base.IsolatedUnitTest):
|
||||
|
||||
def fake_request(self, method, url, body, headers):
|
||||
req = webob.Request.blank(url.path)
|
||||
if isinstance(body, six.text_type):
|
||||
body = body.encode('utf-8')
|
||||
req.body = body
|
||||
req.method = method
|
||||
|
||||
@ -281,11 +292,11 @@ class TestRPCJSONSerializer(test_utils.BaseTestCase):
|
||||
response = webob.Response()
|
||||
rpc.RPCJSONSerializer().default(response, fixture)
|
||||
self.assertEqual(200, response.status_int)
|
||||
content_types = filter(lambda h: h[0] == 'Content-Type',
|
||||
response.headerlist)
|
||||
content_types = [h for h in response.headerlist
|
||||
if h[0] == 'Content-Type']
|
||||
self.assertEqual(1, len(content_types))
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual('{"key": "value"}', response.body)
|
||||
self.assertEqual(b'{"key": "value"}', response.body)
|
||||
|
||||
|
||||
class TestRPCJSONDeserializer(test_utils.BaseTestCase):
|
||||
@ -293,21 +304,21 @@ class TestRPCJSONDeserializer(test_utils.BaseTestCase):
|
||||
def test_has_body_no_content_length(self):
|
||||
request = wsgi.Request.blank('/')
|
||||
request.method = 'POST'
|
||||
request.body = 'asdf'
|
||||
request.body = b'asdf'
|
||||
request.headers.pop('Content-Length')
|
||||
self.assertFalse(rpc.RPCJSONDeserializer().has_body(request))
|
||||
|
||||
def test_has_body_zero_content_length(self):
|
||||
request = wsgi.Request.blank('/')
|
||||
request.method = 'POST'
|
||||
request.body = 'asdf'
|
||||
request.body = b'asdf'
|
||||
request.headers['Content-Length'] = 0
|
||||
self.assertFalse(rpc.RPCJSONDeserializer().has_body(request))
|
||||
|
||||
def test_has_body_has_content_length(self):
|
||||
request = wsgi.Request.blank('/')
|
||||
request.method = 'POST'
|
||||
request.body = 'asdf'
|
||||
request.body = b'asdf'
|
||||
self.assertIn('Content-Length', request.headers)
|
||||
self.assertTrue(rpc.RPCJSONDeserializer().has_body(request))
|
||||
|
||||
@ -335,7 +346,7 @@ class TestRPCJSONDeserializer(test_utils.BaseTestCase):
|
||||
def test_default_with_body(self):
|
||||
request = wsgi.Request.blank('/')
|
||||
request.method = 'POST'
|
||||
request.body = '{"key": "value"}'
|
||||
request.body = b'{"key": "value"}'
|
||||
actual = rpc.RPCJSONDeserializer().default(request)
|
||||
expected = {"body": {"key": "value"}}
|
||||
self.assertEqual(expected, actual)
|
||||
@ -343,7 +354,7 @@ class TestRPCJSONDeserializer(test_utils.BaseTestCase):
|
||||
def test_has_body_has_transfer_encoding(self):
|
||||
request = wsgi.Request.blank('/')
|
||||
request.method = 'POST'
|
||||
request.body = 'fake_body'
|
||||
request.body = b'fake_body'
|
||||
request.headers['transfer-encoding'] = ''
|
||||
self.assertIn('transfer-encoding', request.headers)
|
||||
self.assertTrue(rpc.RPCJSONDeserializer().has_body(request))
|
||||
|
@ -34,21 +34,21 @@ class CacheClientTestCase(utils.BaseTestCase):
|
||||
"/cached_images/test_id")
|
||||
|
||||
def test_get_cached_images(self):
|
||||
expected_data = '{"cached_images": "some_images"}'
|
||||
expected_data = b'{"cached_images": "some_images"}'
|
||||
self.client.do_request.return_value = utils.FakeHTTPResponse(
|
||||
data=expected_data)
|
||||
self.assertEqual("some_images", self.client.get_cached_images())
|
||||
self.client.do_request.assert_called_with("GET", "/cached_images")
|
||||
|
||||
def test_get_queued_images(self):
|
||||
expected_data = '{"queued_images": "some_images"}'
|
||||
expected_data = b'{"queued_images": "some_images"}'
|
||||
self.client.do_request.return_value = utils.FakeHTTPResponse(
|
||||
data=expected_data)
|
||||
self.assertEqual("some_images", self.client.get_queued_images())
|
||||
self.client.do_request.assert_called_with("GET", "/queued_images")
|
||||
|
||||
def test_delete_all_cached_images(self):
|
||||
expected_data = '{"num_deleted": 4}'
|
||||
expected_data = b'{"num_deleted": 4}'
|
||||
self.client.do_request.return_value = utils.FakeHTTPResponse(
|
||||
data=expected_data)
|
||||
self.assertEqual(4, self.client.delete_all_cached_images())
|
||||
@ -67,7 +67,7 @@ class CacheClientTestCase(utils.BaseTestCase):
|
||||
"/queued_images/test_id")
|
||||
|
||||
def test_delete_all_queued_images(self):
|
||||
expected_data = '{"num_deleted": 4}'
|
||||
expected_data = b'{"num_deleted": 4}'
|
||||
self.client.do_request.return_value = utils.FakeHTTPResponse(
|
||||
data=expected_data)
|
||||
self.assertEqual(4, self.client.delete_all_queued_images())
|
||||
|
@ -570,8 +570,8 @@ class FakeAuthMiddleware(wsgi.Middleware):
|
||||
|
||||
class FakeHTTPResponse(object):
|
||||
def __init__(self, status=200, headers=None, data=None, *args, **kwargs):
|
||||
data = data or 'I am a teapot, short and stout\n'
|
||||
self.data = six.StringIO(data)
|
||||
data = data or b'I am a teapot, short and stout\n'
|
||||
self.data = six.BytesIO(data)
|
||||
self.read = self.data.read
|
||||
self.status = status
|
||||
self.headers = headers or {'content-length': len(data)}
|
||||
|
1
tox.ini
1
tox.ini
@ -23,6 +23,7 @@ commands =
|
||||
glance.tests.unit.common.test_config \
|
||||
glance.tests.unit.common.test_exception \
|
||||
glance.tests.unit.common.test_property_utils \
|
||||
glance.tests.unit.common.test_rpc \
|
||||
glance.tests.unit.common.test_scripts \
|
||||
glance.tests.unit.common.test_semver \
|
||||
glance.tests.unit.common.test_swift_store_utils \
|
||||
|
Loading…
Reference in New Issue
Block a user