Add custom RPC(Des|S)erializer to common/rpc.py
We need a custom way to serialize / de-serialize some types in the rpc API. Some of this custom serializations are worth moving to Oslo and other aren't. Before going down that road, it is worth testing them and make them a bit more stable. This patch introduces a serializer and a de-serializer for Glance's RPC API. Implements blueprint registry-db-driver Change-Id: I70c429c65d67cbd7ba09176d5f8e4e8f27270c5b
This commit is contained in:
parent
85cbff043f
commit
805ec52dca
|
@ -19,6 +19,7 @@
|
|||
RPC Controller
|
||||
"""
|
||||
import json
|
||||
import datetime
|
||||
import traceback
|
||||
|
||||
from oslo.config import cfg
|
||||
|
@ -26,9 +27,10 @@ from webob import exc
|
|||
|
||||
from glance.common import client
|
||||
from glance.common import exception
|
||||
from glance.common import wsgi
|
||||
import glance.openstack.common.importutils as imp
|
||||
import glance.openstack.common.log as logging
|
||||
|
||||
from glance.openstack.common import timeutils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
@ -49,6 +51,31 @@ CONF = cfg.CONF
|
|||
CONF.register_opts(rpc_opts)
|
||||
|
||||
|
||||
class RPCJSONSerializer(wsgi.JSONResponseSerializer):
|
||||
|
||||
def _sanitizer(self, obj):
|
||||
def to_primitive(_type, _value):
|
||||
return {"_type": _type, "_value": _value}
|
||||
|
||||
if isinstance(obj, datetime.datetime):
|
||||
return to_primitive("datetime", timeutils.strtime(obj))
|
||||
|
||||
return super(RPCJSONSerializer, self)._sanitizer(obj)
|
||||
|
||||
|
||||
class RPCJSONDeserializer(wsgi.JSONRequestDeserializer):
|
||||
|
||||
def _to_datetime(self, obj):
|
||||
return timeutils.parse_strtime(obj)
|
||||
|
||||
def _sanitizer(self, obj):
|
||||
try:
|
||||
_type, _value = obj["_type"], obj["_value"]
|
||||
return getattr(self, "_to_" + _type)(_value)
|
||||
except (KeyError, AttributeError):
|
||||
return obj
|
||||
|
||||
|
||||
class Controller(object):
|
||||
"""
|
||||
Base RPCController.
|
||||
|
@ -174,6 +201,9 @@ class Controller(object):
|
|||
class RPCClient(client.BaseClient):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._serializer = RPCJSONSerializer()
|
||||
self._deserializer = RPCJSONDeserializer()
|
||||
|
||||
self.raise_exc = kwargs.pop("raise_exc", True)
|
||||
self.base_path = kwargs.pop("base_path", '/rpc')
|
||||
super(RPCClient, self).__init__(*args, **kwargs)
|
||||
|
@ -191,11 +221,11 @@ class RPCClient(client.BaseClient):
|
|||
'kwargs': method_kwargs
|
||||
}
|
||||
"""
|
||||
body = json.dumps(commands)
|
||||
body = self._serializer.to_json(commands)
|
||||
response = super(RPCClient, self).do_request('POST',
|
||||
self.base_path,
|
||||
body)
|
||||
return json.loads(response.read())
|
||||
return self._deserializer.from_json(response.read())
|
||||
|
||||
def do_request(self, method, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -517,9 +517,13 @@ class JSONRequestDeserializer(object):
|
|||
|
||||
return False
|
||||
|
||||
def _sanitizer(self, obj):
|
||||
"""Sanitizer method that will be passed to json.loads."""
|
||||
return obj
|
||||
|
||||
def from_json(self, datastring):
|
||||
try:
|
||||
return json.loads(datastring)
|
||||
return json.loads(datastring, object_hook=self._sanitizer)
|
||||
except ValueError:
|
||||
msg = _('Malformed JSON in request body.')
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
@ -533,15 +537,16 @@ class JSONRequestDeserializer(object):
|
|||
|
||||
class JSONResponseSerializer(object):
|
||||
|
||||
def to_json(self, data):
|
||||
def sanitizer(obj):
|
||||
if isinstance(obj, datetime.datetime):
|
||||
return obj.isoformat()
|
||||
if hasattr(obj, "to_dict"):
|
||||
return obj.to_dict()
|
||||
return obj
|
||||
def _sanitizer(self, obj):
|
||||
"""Sanitizer method that will be passed to json.dumps."""
|
||||
if isinstance(obj, datetime.datetime):
|
||||
return obj.isoformat()
|
||||
if hasattr(obj, "to_dict"):
|
||||
return obj.to_dict()
|
||||
return obj
|
||||
|
||||
return json.dumps(data, default=sanitizer)
|
||||
def to_json(self, data):
|
||||
return json.dumps(data, default=self._sanitizer)
|
||||
|
||||
def default(self, response, result):
|
||||
response.content_type = 'application/json'
|
||||
|
|
|
@ -53,6 +53,6 @@ class Controller(rpc.Controller):
|
|||
|
||||
def create_resource():
|
||||
"""Images resource factory method."""
|
||||
deserializer = wsgi.JSONRequestDeserializer()
|
||||
serializer = wsgi.JSONResponseSerializer()
|
||||
deserializer = rpc.RPCJSONDeserializer()
|
||||
serializer = rpc.RPCJSONSerializer()
|
||||
return wsgi.Resource(Controller(), deserializer, serializer)
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import json
|
||||
import datetime
|
||||
|
||||
import mox
|
||||
from oslo.config import cfg
|
||||
|
@ -53,8 +54,8 @@ class FakeResource(object):
|
|||
|
||||
|
||||
def create_api():
|
||||
deserializer = wsgi.JSONRequestDeserializer()
|
||||
serializer = wsgi.JSONResponseSerializer()
|
||||
deserializer = rpc.RPCJSONDeserializer()
|
||||
serializer = rpc.RPCJSONSerializer()
|
||||
controller = rpc.Controller()
|
||||
controller.register(FakeResource())
|
||||
res = wsgi.Resource(controller, deserializer, serializer)
|
||||
|
@ -244,3 +245,104 @@ class TestRPCClient(base.IsolatedUnitTest):
|
|||
self.fail("Exception not raised")
|
||||
except exception.RPCError:
|
||||
pass
|
||||
|
||||
|
||||
class TestRPCJSONSerializer(test_utils.BaseTestCase):
|
||||
|
||||
def test_to_json(self):
|
||||
fixture = {"key": "value"}
|
||||
expected = '{"key": "value"}'
|
||||
actual = rpc.RPCJSONSerializer().to_json(fixture)
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_to_json_with_date_format_value(self):
|
||||
fixture = {"date": datetime.datetime(1900, 3, 8, 2)}
|
||||
expected = ('{"date": {"_value": "1900-03-08T02:00:00.000000", '
|
||||
'"_type": "datetime"}}')
|
||||
actual = rpc.RPCJSONSerializer().to_json(fixture)
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_to_json_with_more_deep_format(self):
|
||||
fixture = {"is_public": True, "name": [{"name1": "test"}]}
|
||||
expected = '{"is_public": true, "name": [{"name1": "test"}]}'
|
||||
actual = rpc.RPCJSONSerializer().to_json(fixture)
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_default(self):
|
||||
fixture = {"key": "value"}
|
||||
response = webob.Response()
|
||||
rpc.RPCJSONSerializer().default(response, fixture)
|
||||
self.assertEqual(response.status_int, 200)
|
||||
content_types = filter(lambda h: h[0] == 'Content-Type',
|
||||
response.headerlist)
|
||||
self.assertEqual(len(content_types), 1)
|
||||
self.assertEqual(response.content_type, 'application/json')
|
||||
self.assertEqual(response.body, '{"key": "value"}')
|
||||
|
||||
|
||||
class TestRPCJSONDeserializer(test_utils.BaseTestCase):
|
||||
|
||||
def test_has_body_no_content_length(self):
|
||||
request = wsgi.Request.blank('/')
|
||||
request.method = 'POST'
|
||||
request.body = '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.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'
|
||||
self.assertTrue('Content-Length' in request.headers)
|
||||
self.assertTrue(rpc.RPCJSONDeserializer().has_body(request))
|
||||
|
||||
def test_no_body_no_content_length(self):
|
||||
request = wsgi.Request.blank('/')
|
||||
self.assertFalse(rpc.RPCJSONDeserializer().has_body(request))
|
||||
|
||||
def test_from_json(self):
|
||||
fixture = '{"key": "value"}'
|
||||
expected = {"key": "value"}
|
||||
actual = rpc.RPCJSONDeserializer().from_json(fixture)
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_from_json_malformed(self):
|
||||
fixture = 'kjasdklfjsklajf'
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
rpc.RPCJSONDeserializer().from_json, fixture)
|
||||
|
||||
def test_default_no_body(self):
|
||||
request = wsgi.Request.blank('/')
|
||||
actual = rpc.RPCJSONDeserializer().default(request)
|
||||
expected = {}
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_default_with_body(self):
|
||||
request = wsgi.Request.blank('/')
|
||||
request.method = 'POST'
|
||||
request.body = '{"key": "value"}'
|
||||
actual = rpc.RPCJSONDeserializer().default(request)
|
||||
expected = {"body": {"key": "value"}}
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_has_body_has_transfer_encoding(self):
|
||||
request = wsgi.Request.blank('/')
|
||||
request.method = 'POST'
|
||||
request.body = 'fake_body'
|
||||
request.headers['transfer-encoding'] = 0
|
||||
self.assertTrue('transfer-encoding' in request.headers)
|
||||
self.assertTrue(rpc.RPCJSONDeserializer().has_body(request))
|
||||
|
||||
def test_to_json_with_date_format_value(self):
|
||||
fixture = ('{"date": {"_value": "1900-03-08T02:00:00.000000",'
|
||||
'"_type": "datetime"}}')
|
||||
expected = {"date": datetime.datetime(1900, 3, 8, 2)}
|
||||
actual = rpc.RPCJSONDeserializer().from_json(fixture)
|
||||
self.assertEqual(actual, expected)
|
||||
|
|
Loading…
Reference in New Issue