diff --git a/heat/api/aws/exception.py b/heat/api/aws/exception.py
index 5f5ba2ecec..1a8834339c 100644
--- a/heat/api/aws/exception.py
+++ b/heat/api/aws/exception.py
@@ -18,7 +18,7 @@
import webob.exc
-from heat.common import wsgi
+from heat.common import serializers
from heat.openstack.common.gettextutils import _
from heat.openstack.common.rpc import common as rpc_common
@@ -44,7 +44,7 @@ class HeatAPIException(webob.exc.HTTPError):
paste pipeline. We serialize in XML by default (as AWS does)
'''
webob.exc.HTTPError.__init__(self, detail=detail)
- serializer = wsgi.XMLResponseSerializer()
+ serializer = serializers.XMLResponseSerializer()
serializer.default(self, self.get_unserialized_body())
def get_unserialized_body(self):
diff --git a/heat/api/middleware/fault.py b/heat/api/middleware/fault.py
index 8e3d31ec0f..3894b4b7be 100644
--- a/heat/api/middleware/fault.py
+++ b/heat/api/middleware/fault.py
@@ -27,6 +27,7 @@ import webob
cfg.CONF.import_opt('debug', 'heat.openstack.common.log')
+from heat.common import serializers
from heat.common import exception
from heat.openstack.common import log as logging
import heat.openstack.common.rpc.common as rpc_common
@@ -44,9 +45,9 @@ class Fault(object):
@webob.dec.wsgify(RequestClass=wsgi.Request)
def __call__(self, req):
if req.content_type == 'application/xml':
- serializer = wsgi.XMLResponseSerializer()
+ serializer = serializers.XMLResponseSerializer()
else:
- serializer = wsgi.JSONResponseSerializer()
+ serializer = serializers.JSONResponseSerializer()
resp = webob.Response(request=req)
default_webob_exc = webob.exc.HTTPInternalServerError()
resp.status_code = self.error.get('code', default_webob_exc.code)
diff --git a/heat/api/openstack/v1/actions.py b/heat/api/openstack/v1/actions.py
index b5b93dcefe..819edfe7bf 100644
--- a/heat/api/openstack/v1/actions.py
+++ b/heat/api/openstack/v1/actions.py
@@ -14,6 +14,7 @@
from webob import exc
from heat.api.openstack.v1 import util
+from heat.common import serializers
from heat.common import wsgi
from heat.rpc import client as rpc_client
@@ -62,5 +63,5 @@ def create_resource(options):
Actions action factory method.
"""
deserializer = wsgi.JSONRequestDeserializer()
- serializer = wsgi.JSONResponseSerializer()
+ serializer = serializers.JSONResponseSerializer()
return wsgi.Resource(ActionController(options), deserializer, serializer)
diff --git a/heat/api/openstack/v1/build_info.py b/heat/api/openstack/v1/build_info.py
index dfe8b06c67..2cdfa6a3da 100644
--- a/heat/api/openstack/v1/build_info.py
+++ b/heat/api/openstack/v1/build_info.py
@@ -14,6 +14,7 @@
from oslo.config import cfg
from heat.api.openstack.v1 import util
+from heat.common import serializers
from heat.common import wsgi
from heat.rpc import client as rpc_client
@@ -46,6 +47,6 @@ def create_resource(options):
BuildInfo factory method.
"""
deserializer = wsgi.JSONRequestDeserializer()
- serializer = wsgi.JSONResponseSerializer()
+ serializer = serializers.JSONResponseSerializer()
return wsgi.Resource(BuildInfoController(options), deserializer,
serializer)
diff --git a/heat/api/openstack/v1/events.py b/heat/api/openstack/v1/events.py
index a894467ec4..e49bfcfbc0 100644
--- a/heat/api/openstack/v1/events.py
+++ b/heat/api/openstack/v1/events.py
@@ -17,6 +17,7 @@ from webob import exc
from heat.api.openstack.v1 import util
from heat.common import identifier
+from heat.common import serializers
from heat.common import wsgi
from heat.rpc import api as engine_api
from heat.rpc import client as rpc_client
@@ -128,5 +129,5 @@ def create_resource(options):
Events resource factory method.
"""
deserializer = wsgi.JSONRequestDeserializer()
- serializer = wsgi.JSONResponseSerializer()
+ serializer = serializers.JSONResponseSerializer()
return wsgi.Resource(EventController(options), deserializer, serializer)
diff --git a/heat/api/openstack/v1/resources.py b/heat/api/openstack/v1/resources.py
index d650c93840..c324dc9ac4 100644
--- a/heat/api/openstack/v1/resources.py
+++ b/heat/api/openstack/v1/resources.py
@@ -15,6 +15,7 @@ import itertools
from heat.api.openstack.v1 import util
from heat.common import identifier
+from heat.common import serializers
from heat.common import wsgi
from heat.rpc import api as engine_api
from heat.rpc import client as rpc_client
@@ -113,5 +114,5 @@ def create_resource(options):
Resources resource factory method.
"""
deserializer = wsgi.JSONRequestDeserializer()
- serializer = wsgi.JSONResponseSerializer()
+ serializer = serializers.JSONResponseSerializer()
return wsgi.Resource(ResourceController(options), deserializer, serializer)
diff --git a/heat/api/openstack/v1/software_configs.py b/heat/api/openstack/v1/software_configs.py
index 0ba6592522..2bc5cdc75d 100644
--- a/heat/api/openstack/v1/software_configs.py
+++ b/heat/api/openstack/v1/software_configs.py
@@ -14,6 +14,7 @@
from webob import exc
from heat.api.openstack.v1 import util
+from heat.common import serializers
from heat.common import wsgi
from heat.rpc import client as rpc_client
@@ -77,6 +78,6 @@ def create_resource(options):
Software configs resource factory method.
"""
deserializer = wsgi.JSONRequestDeserializer()
- serializer = wsgi.JSONResponseSerializer()
+ serializer = serializers.JSONResponseSerializer()
return wsgi.Resource(
SoftwareConfigController(options), deserializer, serializer)
diff --git a/heat/api/openstack/v1/software_deployments.py b/heat/api/openstack/v1/software_deployments.py
index 27fda9c394..58d18a206c 100644
--- a/heat/api/openstack/v1/software_deployments.py
+++ b/heat/api/openstack/v1/software_deployments.py
@@ -14,6 +14,7 @@
from webob import exc
from heat.api.openstack.v1 import util
+from heat.common import serializers
from heat.common import wsgi
from heat.rpc import client as rpc_client
@@ -109,6 +110,6 @@ def create_resource(options):
Software deployments resource factory method.
"""
deserializer = wsgi.JSONRequestDeserializer()
- serializer = wsgi.JSONResponseSerializer()
+ serializer = serializers.JSONResponseSerializer()
return wsgi.Resource(
SoftwareDeploymentController(options), deserializer, serializer)
diff --git a/heat/api/openstack/v1/stacks.py b/heat/api/openstack/v1/stacks.py
index 500088b72e..78cba7e309 100644
--- a/heat/api/openstack/v1/stacks.py
+++ b/heat/api/openstack/v1/stacks.py
@@ -21,6 +21,7 @@ from heat.api.openstack.v1 import util
from heat.api.openstack.v1.views import stacks_view
from heat.common import environment_format
from heat.common import identifier
+from heat.common import serializers
from heat.common import template_format
from heat.common import urlfetch
from heat.common import wsgi
@@ -378,7 +379,7 @@ class StackController(object):
return self.rpc_client.generate_template(req.context, type_name)
-class StackSerializer(wsgi.JSONResponseSerializer):
+class StackSerializer(serializers.JSONResponseSerializer):
"""Handles serialization of specific controller method responses."""
def _populate_response_header(self, response, location, status):
diff --git a/heat/common/serializers.py b/heat/common/serializers.py
new file mode 100644
index 0000000000..d94448ab79
--- /dev/null
+++ b/heat/common/serializers.py
@@ -0,0 +1,89 @@
+#
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Utility methods for serializing responses
+"""
+
+import datetime
+import json
+
+from lxml import etree
+
+from heat.openstack.common import log as logging
+
+logger = logging.getLogger(__name__)
+
+
+class JSONResponseSerializer(object):
+
+ def to_json(self, data):
+ def sanitizer(obj):
+ if isinstance(obj, datetime.datetime):
+ return obj.isoformat()
+ return obj
+
+ response = json.dumps(data, default=sanitizer)
+ logger.debug("JSON response : %s" % response)
+ return response
+
+ def default(self, response, result):
+ response.content_type = 'application/json'
+ response.body = self.to_json(result)
+
+
+# Escape XML serialization for these keys, as the AWS API defines them as
+# JSON inside XML when the response format is XML.
+JSON_ONLY_KEYS = ('TemplateBody', 'Metadata')
+
+
+class XMLResponseSerializer(object):
+
+ def object_to_element(self, obj, element):
+ if isinstance(obj, list):
+ for item in obj:
+ subelement = etree.SubElement(element, "member")
+ self.object_to_element(item, subelement)
+ elif isinstance(obj, dict):
+ for key, value in obj.items():
+ subelement = etree.SubElement(element, key)
+ if key in JSON_ONLY_KEYS:
+ if value:
+ # Need to use json.dumps for the JSON inside XML
+ # otherwise quotes get mangled and json.loads breaks
+ try:
+ subelement.text = json.dumps(value)
+ except TypeError:
+ subelement.text = str(value)
+ else:
+ self.object_to_element(value, subelement)
+ else:
+ element.text = str(obj)
+
+ def to_xml(self, data):
+ # Assumption : root node is dict with single key
+ root = data.keys()[0]
+ eltree = etree.Element(root)
+ self.object_to_element(data.get(root), eltree)
+ response = etree.tostring(eltree)
+ logger.debug("XML response : %s" % response)
+ return response
+
+ def default(self, response, result):
+ response.content_type = 'application/xml'
+ response.body = self.to_xml(result)
diff --git a/heat/common/wsgi.py b/heat/common/wsgi.py
index d521855d9f..0e027f36a1 100644
--- a/heat/common/wsgi.py
+++ b/heat/common/wsgi.py
@@ -20,7 +20,6 @@
Utility methods for working with WSGI servers
"""
-import datetime
import errno
import json
import logging
@@ -34,7 +33,6 @@ from eventlet.green import socket
from eventlet.green import ssl
import eventlet.greenio
import eventlet.wsgi
-from lxml import etree
from oslo.config import cfg
from paste import deploy
import routes
@@ -43,6 +41,7 @@ import webob.dec
import webob.exc
from heat.common import exception
+from heat.common import serializers
from heat.openstack.common import gettextutils
from heat.openstack.common import importutils
@@ -565,65 +564,6 @@ class JSONRequestDeserializer(object):
return {}
-class JSONResponseSerializer(object):
-
- def to_json(self, data):
- def sanitizer(obj):
- if isinstance(obj, datetime.datetime):
- return obj.isoformat()
- return obj
-
- response = json.dumps(data, default=sanitizer)
- logging.debug("JSON response : %s" % response)
- return response
-
- def default(self, response, result):
- response.content_type = 'application/json'
- response.body = self.to_json(result)
-
-
-# Escape XML serialization for these keys, as the AWS API defines them as
-# JSON inside XML when the response format is XML.
-JSON_ONLY_KEYS = ('TemplateBody', 'Metadata')
-
-
-class XMLResponseSerializer(object):
-
- def object_to_element(self, obj, element):
- if isinstance(obj, list):
- for item in obj:
- subelement = etree.SubElement(element, "member")
- self.object_to_element(item, subelement)
- elif isinstance(obj, dict):
- for key, value in obj.items():
- subelement = etree.SubElement(element, key)
- if key in JSON_ONLY_KEYS:
- if value:
- # Need to use json.dumps for the JSON inside XML
- # otherwise quotes get mangled and json.loads breaks
- try:
- subelement.text = json.dumps(value)
- except TypeError:
- subelement.text = str(value)
- else:
- self.object_to_element(value, subelement)
- else:
- element.text = str(obj)
-
- def to_xml(self, data):
- # Assumption : root node is dict with single key
- root = data.keys()[0]
- eltree = etree.Element(root)
- self.object_to_element(data.get(root), eltree)
- response = etree.tostring(eltree)
- logging.debug("XML response : %s" % response)
- return response
-
- def default(self, response, result):
- response.content_type = 'application/xml'
- response.body = self.to_xml(result)
-
-
class Resource(object):
"""
WSGI app that handles (de)serialization and controller dispatch.
@@ -708,9 +648,9 @@ class Resource(object):
serializer = self.serializer
if serializer is None:
if content_type == "JSON":
- serializer = JSONResponseSerializer()
+ serializer = serializers.JSONResponseSerializer()
else:
- serializer = XMLResponseSerializer()
+ serializer = serializers.XMLResponseSerializer()
response = webob.Response(request=request)
self.dispatch(serializer, action, response, action_result)
diff --git a/heat/tests/test_common_serializers.py b/heat/tests/test_common_serializers.py
new file mode 100644
index 0000000000..9c03b0c599
--- /dev/null
+++ b/heat/tests/test_common_serializers.py
@@ -0,0 +1,109 @@
+#
+# Copyright 2010-2011 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import datetime
+import webob
+
+from heat.common import serializers
+from heat.tests.common import HeatTestCase
+
+
+class JSONResponseSerializerTest(HeatTestCase):
+
+ def test_to_json(self):
+ fixture = {"key": "value"}
+ expected = '{"key": "value"}'
+ actual = serializers.JSONResponseSerializer().to_json(fixture)
+ self.assertEqual(expected, actual)
+
+ def test_to_json_with_date_format_value(self):
+ fixture = {"date": datetime.datetime(1, 3, 8, 2)}
+ expected = '{"date": "0001-03-08T02:00:00"}'
+ actual = serializers.JSONResponseSerializer().to_json(fixture)
+ self.assertEqual(expected, actual)
+
+ def test_to_json_with_more_deep_format(self):
+ fixture = {"is_public": True, "name": [{"name1": "test"}]}
+ expected = '{"is_public": true, "name": [{"name1": "test"}]}'
+ actual = serializers.JSONResponseSerializer().to_json(fixture)
+ self.assertEqual(expected, actual)
+
+ def test_default(self):
+ fixture = {"key": "value"}
+ response = webob.Response()
+ serializers.JSONResponseSerializer().default(response, fixture)
+ self.assertEqual(200, response.status_int)
+ content_types = filter(lambda h: h[0] == 'Content-Type',
+ response.headerlist)
+ self.assertEqual(1, len(content_types))
+ self.assertEqual('application/json', response.content_type)
+ self.assertEqual('{"key": "value"}', response.body)
+
+
+class XMLResponseSerializerTest(HeatTestCase):
+
+ def test_to_xml(self):
+ fixture = {"key": "value"}
+ expected = 'value'
+ actual = serializers.XMLResponseSerializer().to_xml(fixture)
+ self.assertEqual(expected, actual)
+
+ def test_to_xml_with_date_format_value(self):
+ fixture = {"date": datetime.datetime(1, 3, 8, 2)}
+ expected = '0001-03-08 02:00:00'
+ actual = serializers.XMLResponseSerializer().to_xml(fixture)
+ self.assertEqual(expected, actual)
+
+ def test_to_xml_with_list(self):
+ fixture = {"name": ["1", "2"]}
+ expected = '12'
+ actual = serializers.XMLResponseSerializer().to_xml(fixture)
+ self.assertEqual(expected, actual)
+
+ def test_to_xml_with_more_deep_format(self):
+ # Note we expect tree traversal from one root key, which is compatible
+ # with the AWS format responses we need to serialize
+ fixture = {"aresponse":
+ {"is_public": True, "name": [{"name1": "test"}]}}
+ expected = ('True'
+ 'test'
+ '')
+ actual = serializers.XMLResponseSerializer().to_xml(fixture)
+ self.assertEqual(expected, actual)
+
+ def test_to_xml_with_json_only_keys(self):
+ # Certain keys are excluded from serialization because CFN
+ # format demands a json blob in the XML body
+ fixture = {"aresponse":
+ {"is_public": True,
+ "TemplateBody": {"name1": "test"},
+ "Metadata": {"name2": "test2"}}}
+ expected = ('True'
+ '{"name1": "test"}'
+ '{"name2": "test2"}')
+ actual = serializers.XMLResponseSerializer().to_xml(fixture)
+ self.assertEqual(expected, actual)
+
+ def test_default(self):
+ fixture = {"key": "value"}
+ response = webob.Response()
+ serializers.XMLResponseSerializer().default(response, fixture)
+ self.assertEqual(200, response.status_int)
+ content_types = filter(lambda h: h[0] == 'Content-Type',
+ response.headerlist)
+ self.assertEqual(1, len(content_types))
+ self.assertEqual('application/xml', response.content_type)
+ self.assertEqual('value', response.body)
diff --git a/heat/tests/test_wsgi.py b/heat/tests/test_wsgi.py
index f1c17f1c44..d68f23853c 100644
--- a/heat/tests/test_wsgi.py
+++ b/heat/tests/test_wsgi.py
@@ -15,7 +15,6 @@
# under the License.
-import datetime
import json
from oslo.config import cfg
import stubout
@@ -258,94 +257,6 @@ class ResourceExceptionHandlingTest(HeatTestCase):
self.assertNotIn(str(e), self.logger.output)
-class JSONResponseSerializerTest(HeatTestCase):
-
- def test_to_json(self):
- fixture = {"key": "value"}
- expected = '{"key": "value"}'
- actual = wsgi.JSONResponseSerializer().to_json(fixture)
- self.assertEqual(expected, actual)
-
- def test_to_json_with_date_format_value(self):
- fixture = {"date": datetime.datetime(1, 3, 8, 2)}
- expected = '{"date": "0001-03-08T02:00:00"}'
- actual = wsgi.JSONResponseSerializer().to_json(fixture)
- self.assertEqual(expected, actual)
-
- def test_to_json_with_more_deep_format(self):
- fixture = {"is_public": True, "name": [{"name1": "test"}]}
- expected = '{"is_public": true, "name": [{"name1": "test"}]}'
- actual = wsgi.JSONResponseSerializer().to_json(fixture)
- self.assertEqual(expected, actual)
-
- def test_default(self):
- fixture = {"key": "value"}
- response = webob.Response()
- wsgi.JSONResponseSerializer().default(response, fixture)
- self.assertEqual(200, response.status_int)
- content_types = filter(lambda h: h[0] == 'Content-Type',
- response.headerlist)
- self.assertEqual(1, len(content_types))
- self.assertEqual('application/json', response.content_type)
- self.assertEqual('{"key": "value"}', response.body)
-
-
-class XMLResponseSerializerTest(HeatTestCase):
-
- def test_to_xml(self):
- fixture = {"key": "value"}
- expected = 'value'
- actual = wsgi.XMLResponseSerializer().to_xml(fixture)
- self.assertEqual(expected, actual)
-
- def test_to_xml_with_date_format_value(self):
- fixture = {"date": datetime.datetime(1, 3, 8, 2)}
- expected = '0001-03-08 02:00:00'
- actual = wsgi.XMLResponseSerializer().to_xml(fixture)
- self.assertEqual(expected, actual)
-
- def test_to_xml_with_list(self):
- fixture = {"name": ["1", "2"]}
- expected = '12'
- actual = wsgi.XMLResponseSerializer().to_xml(fixture)
- self.assertEqual(expected, actual)
-
- def test_to_xml_with_more_deep_format(self):
- # Note we expect tree traversal from one root key, which is compatible
- # with the AWS format responses we need to serialize
- fixture = {"aresponse":
- {"is_public": True, "name": [{"name1": "test"}]}}
- expected = ('True'
- 'test'
- '')
- actual = wsgi.XMLResponseSerializer().to_xml(fixture)
- self.assertEqual(expected, actual)
-
- def test_to_xml_with_json_only_keys(self):
- # Certain keys are excluded from serialization because CFN
- # format demands a json blob in the XML body
- fixture = {"aresponse":
- {"is_public": True,
- "TemplateBody": {"name1": "test"},
- "Metadata": {"name2": "test2"}}}
- expected = ('True'
- '{"name1": "test"}'
- '{"name2": "test2"}')
- actual = wsgi.XMLResponseSerializer().to_xml(fixture)
- self.assertEqual(expected, actual)
-
- def test_default(self):
- fixture = {"key": "value"}
- response = webob.Response()
- wsgi.XMLResponseSerializer().default(response, fixture)
- self.assertEqual(200, response.status_int)
- content_types = filter(lambda h: h[0] == 'Content-Type',
- response.headerlist)
- self.assertEqual(1, len(content_types))
- self.assertEqual('application/xml', response.content_type)
- self.assertEqual('value', response.body)
-
-
class JSONRequestDeserializerTest(HeatTestCase):
def test_has_body_no_content_length(self):