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:
Flaper Fesp 2013-06-17 11:46:28 +02:00
parent 85cbff043f
commit 805ec52dca
4 changed files with 153 additions and 16 deletions

View File

@ -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):
"""

View File

@ -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'

View File

@ -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)

View File

@ -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)