summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCerberus <matt.dietz@rackspace.com>2010-10-01 00:58:17 +0000
committerTarmac <>2010-10-01 00:58:17 +0000
commit30747bba76ddc2b51b5b0bf564557e86a5d634c3 (patch)
treee420ee053b66d5c72eeb78855874121be9867128
parent2212683888d717d55531136cf4917da015a2bb9c (diff)
parent75c5ba6aae6a57a61771ed78b6797c90f7da6940 (diff)
downloadnova-30747bba76ddc2b51b5b0bf564557e86a5d634c3.tar.gz
nova-30747bba76ddc2b51b5b0bf564557e86a5d634c3.tar.xz
nova-30747bba76ddc2b51b5b0bf564557e86a5d634c3.zip
Includes changes for creating instances via the Rackspace API. Utilizes much of the existing EC2 functionality to power the Rackspace side of things, at least for now.
-rw-r--r--nova/api/cloud.py42
-rw-r--r--nova/api/ec2/cloud.py8
-rw-r--r--nova/api/rackspace/context.py33
-rw-r--r--nova/api/rackspace/servers.py171
-rw-r--r--nova/db/sqlalchemy/models.py2
-rw-r--r--nova/tests/api/rackspace/auth.py8
-rw-r--r--nova/tests/api/rackspace/flavors.py1
-rw-r--r--nova/tests/api/rackspace/images.py1
-rw-r--r--nova/tests/api/rackspace/servers.py101
-rw-r--r--nova/tests/api/rackspace/sharedipgroups.py1
-rw-r--r--nova/tests/api/rackspace/test_helper.py15
-rw-r--r--nova/wsgi.py20
12 files changed, 326 insertions, 77 deletions
diff --git a/nova/api/cloud.py b/nova/api/cloud.py
new file mode 100644
index 000000000..345677d4f
--- /dev/null
+++ b/nova/api/cloud.py
@@ -0,0 +1,42 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Methods for API calls to control instances via AMQP.
+"""
+
+
+from nova import db
+from nova import flags
+from nova import rpc
+
+FLAGS = flags.FLAGS
+
+
+def reboot(instance_id, context=None):
+ """Reboot the given instance.
+
+ #TODO(gundlach) not actually sure what context is used for by ec2 here
+ -- I think we can just remove it and use None all the time.
+ """
+ instance_ref = db.instance_get_by_ec2_id(None, instance_id)
+ host = instance_ref['host']
+ rpc.cast(db.queue_get_for(context, FLAGS.compute_topic, host),
+ {"method": "reboot_instance",
+ "args": {"context": None,
+ "instance_id": instance_ref['id']}})
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index d3f54367b..79c95788b 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -36,6 +36,7 @@ from nova import quota
from nova import rpc
from nova import utils
from nova.compute.instance_types import INSTANCE_TYPES
+from nova.api import cloud
from nova.api.ec2 import images
@@ -684,12 +685,7 @@ class CloudController(object):
def reboot_instances(self, context, instance_id, **kwargs):
"""instance_id is a list of instance ids"""
for id_str in instance_id:
- instance_ref = db.instance_get_by_ec2_id(context, id_str)
- host = instance_ref['host']
- rpc.cast(db.queue_get_for(context, FLAGS.compute_topic, host),
- {"method": "reboot_instance",
- "args": {"context": None,
- "instance_id": instance_ref['id']}})
+ cloud.reboot(id_str, context=context)
return True
def update_instance(self, context, instance_id, **kwargs):
diff --git a/nova/api/rackspace/context.py b/nova/api/rackspace/context.py
new file mode 100644
index 000000000..77394615b
--- /dev/null
+++ b/nova/api/rackspace/context.py
@@ -0,0 +1,33 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 OpenStack LLC.
+# 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.
+
+"""
+APIRequestContext
+"""
+
+import random
+
+class Project(object):
+ def __init__(self, user_id):
+ self.id = user_id
+
+class APIRequestContext(object):
+ """ This is an adapter class to get around all of the assumptions made in
+ the FlatNetworking """
+ def __init__(self, user_id):
+ self.user_id = user_id
+ self.project = Project(user_id)
diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py
index 08d533563..11efd8aef 100644
--- a/nova/api/rackspace/servers.py
+++ b/nova/api/rackspace/servers.py
@@ -17,35 +17,45 @@
import time
+import webob
from webob import exc
from nova import flags
from nova import rpc
from nova import utils
from nova import wsgi
+from nova.api import cloud
from nova.api.rackspace import _id_translator
+from nova.api.rackspace import context
from nova.api.rackspace import faults
+from nova.compute import instance_types
from nova.compute import power_state
import nova.api.rackspace
import nova.image.service
FLAGS = flags.FLAGS
+flags.DEFINE_string('rs_network_manager', 'nova.network.manager.FlatManager',
+ 'Networking for rackspace')
+def _instance_id_translator():
+ """ Helper method for initializing an id translator for Rackspace instance
+ ids """
+ return _id_translator.RackspaceAPIIdTranslator( "instance", 'nova')
-def translator_instance():
+def _image_service():
""" Helper method for initializing the image id translator """
service = nova.image.service.ImageService.load()
- return _id_translator.RackspaceAPIIdTranslator(
- "image", service.__class__.__name__)
+ return (service, _id_translator.RackspaceAPIIdTranslator(
+ "image", service.__class__.__name__))
def _filter_params(inst_dict):
""" Extracts all updatable parameters for a server update request """
- keys = ['name', 'adminPass']
+ keys = dict(name='name', admin_pass='adminPass')
new_attrs = {}
- for k in keys:
- if inst_dict.has_key(k):
- new_attrs[k] = inst_dict[k]
+ for k, v in keys.items():
+ if inst_dict.has_key(v):
+ new_attrs[k] = inst_dict[v]
return new_attrs
def _entity_list(entities):
@@ -84,7 +94,6 @@ def _entity_inst(inst):
class Controller(wsgi.Controller):
""" The Server API controller for the Openstack API """
-
_serialization_metadata = {
'application/xml': {
@@ -122,8 +131,11 @@ class Controller(wsgi.Controller):
def show(self, req, id):
""" Returns server details by server id """
+ inst_id_trans = _instance_id_translator()
+ inst_id = inst_id_trans.from_rs_id(id)
+
user_id = req.environ['nova.context']['user']['id']
- inst = self.db_driver.instance_get(None, id)
+ inst = self.db_driver.instance_get_by_ec2_id(None, inst_id)
if inst:
if inst.user_id == user_id:
return _entity_detail(inst)
@@ -131,8 +143,11 @@ class Controller(wsgi.Controller):
def delete(self, req, id):
""" Destroys a server """
+ inst_id_trans = _instance_id_translator()
+ inst_id = inst_id_trans.from_rs_id(id)
+
user_id = req.environ['nova.context']['user']['id']
- instance = self.db_driver.instance_get(None, id)
+ instance = self.db_driver.instance_get_by_ec2_id(None, inst_id)
if instance and instance['user_id'] == user_id:
self.db_driver.instance_destroy(None, id)
return faults.Fault(exc.HTTPAccepted())
@@ -140,10 +155,15 @@ class Controller(wsgi.Controller):
def create(self, req):
""" Creates a new server for a given user """
- if not req.environ.has_key('inst_dict'):
+
+ env = self._deserialize(req.body, req)
+ if not env:
return faults.Fault(exc.HTTPUnprocessableEntity())
- inst = self._build_server_instance(req)
+ try:
+ inst = self._build_server_instance(req, env)
+ except Exception, e:
+ return faults.Fault(exc.HTTPUnprocessableEntity())
rpc.cast(
FLAGS.compute_topic, {
@@ -153,62 +173,127 @@ class Controller(wsgi.Controller):
def update(self, req, id):
""" Updates the server name or password """
- if not req.environ.has_key('inst_dict'):
+ inst_id_trans = _instance_id_translator()
+ inst_id = inst_id_trans.from_rs_id(id)
+ user_id = req.environ['nova.context']['user']['id']
+
+ inst_dict = self._deserialize(req.body, req)
+
+ if not inst_dict:
return faults.Fault(exc.HTTPUnprocessableEntity())
- instance = self.db_driver.instance_get(None, id)
- if not instance:
+ instance = self.db_driver.instance_get_by_ec2_id(None, inst_id)
+ if not instance or instance.user_id != user_id:
return faults.Fault(exc.HTTPNotFound())
- attrs = req.environ['nova.context'].get('model_attributes', None)
- if attrs:
- self.db_driver.instance_update(None, id, _filter_params(attrs))
+ self.db_driver.instance_update(None, id,
+ _filter_params(inst_dict['server']))
return faults.Fault(exc.HTTPNoContent())
def action(self, req, id):
""" multi-purpose method used to reboot, rebuild, and
resize a server """
- if not req.environ.has_key('inst_dict'):
- return faults.Fault(exc.HTTPUnprocessableEntity())
-
- def _build_server_instance(self, req):
+ input_dict = self._deserialize(req.body, req)
+ try:
+ reboot_type = input_dict['reboot']['type']
+ except Exception:
+ raise faults.Fault(webob.exc.HTTPNotImplemented())
+ opaque_id = _instance_id_translator().from_rs_id(id)
+ cloud.reboot(opaque_id)
+
+ def _build_server_instance(self, req, env):
"""Build instance data structure and save it to the data store."""
ltime = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
inst = {}
- env = req.environ['inst_dict']
+ inst_id_trans = _instance_id_translator()
+
+ user_id = req.environ['nova.context']['user']['id']
+
+ flavor_id = env['server']['flavorId']
+
+ instance_type, flavor = [(k, v) for k, v in
+ instance_types.INSTANCE_TYPES.iteritems()
+ if v['flavorid'] == flavor_id][0]
image_id = env['server']['imageId']
- opaque_id = translator_instance().from_rs_id(image_id)
+
+ img_service, image_id_trans = _image_service()
- inst['name'] = env['server']['server_name']
- inst['image_id'] = opaque_id
- inst['instance_type'] = env['server']['flavorId']
+ opaque_image_id = image_id_trans.to_rs_id(image_id)
+ image = img_service.show(opaque_image_id)
- user_id = req.environ['nova.context']['user']['id']
- inst['user_id'] = user_id
+ if not image:
+ raise Exception, "Image not found"
+ inst['server_name'] = env['server']['name']
+ inst['image_id'] = opaque_image_id
+ inst['user_id'] = user_id
inst['launch_time'] = ltime
inst['mac_address'] = utils.generate_mac()
+ inst['project_id'] = user_id
+
+ inst['state_description'] = 'scheduling'
+ inst['kernel_id'] = image.get('kernelId', FLAGS.default_kernel)
+ inst['ramdisk_id'] = image.get('ramdiskId', FLAGS.default_ramdisk)
+ inst['reservation_id'] = utils.generate_uid('r')
- inst['project_id'] = env['project']['id']
- inst['reservation_id'] = reservation
- reservation = utils.generate_uid('r')
+ inst['display_name'] = env['server']['name']
+ inst['display_description'] = env['server']['name']
- address = self.network.allocate_ip(
- inst['user_id'],
- inst['project_id'],
- mac=inst['mac_address'])
+ #TODO(dietz) this may be ill advised
+ key_pair_ref = self.db_driver.key_pair_get_all_by_user(
+ None, user_id)[0]
- inst['private_dns_name'] = str(address)
- inst['bridge_name'] = network.BridgedNetwork.get_network_for_project(
- inst['user_id'],
- inst['project_id'],
- 'default')['bridge_name']
+ inst['key_data'] = key_pair_ref['public_key']
+ inst['key_name'] = key_pair_ref['name']
+
+ #TODO(dietz) stolen from ec2 api, see TODO there
+ inst['security_group'] = 'default'
+
+ # Flavor related attributes
+ inst['instance_type'] = instance_type
+ inst['memory_mb'] = flavor['memory_mb']
+ inst['vcpus'] = flavor['vcpus']
+ inst['local_gb'] = flavor['local_gb']
ref = self.db_driver.instance_create(None, inst)
- inst['id'] = ref.id
+ inst['id'] = inst_id_trans.to_rs_id(ref.ec2_id)
+
+ # TODO(dietz): this isn't explicitly necessary, but the networking
+ # calls depend on an object with a project_id property, and therefore
+ # should be cleaned up later
+ api_context = context.APIRequestContext(user_id)
+
+ inst['mac_address'] = utils.generate_mac()
+ #TODO(dietz) is this necessary?
+ inst['launch_index'] = 0
+
+ inst['hostname'] = ref.ec2_id
+ self.db_driver.instance_update(None, inst['id'], inst)
+
+ network_manager = utils.import_object(FLAGS.rs_network_manager)
+ address = network_manager.allocate_fixed_ip(api_context,
+ inst['id'])
+
+ # TODO(vish): This probably should be done in the scheduler
+ # network is setup when host is assigned
+ network_topic = self._get_network_topic(user_id)
+ rpc.call(network_topic,
+ {"method": "setup_fixed_ip",
+ "args": {"context": None,
+ "address": address}})
return inst
-
+ def _get_network_topic(self, user_id):
+ """Retrieves the network host for a project"""
+ network_ref = self.db_driver.project_get_network(None,
+ user_id)
+ host = network_ref['host']
+ if not host:
+ host = rpc.call(FLAGS.network_topic,
+ {"method": "set_network_host",
+ "args": {"context": None,
+ "project_id": user_id}})
+ return self.db_driver.queue_get_for(None, FLAGS.network_topic, host)
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 01e58b05e..6cb377476 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -199,6 +199,8 @@ class Instance(BASE, NovaBase):
id = Column(Integer, primary_key=True)
ec2_id = Column(String(10), unique=True)
+ admin_pass = Column(String(255))
+
user_id = Column(String(255))
project_id = Column(String(255))
diff --git a/nova/tests/api/rackspace/auth.py b/nova/tests/api/rackspace/auth.py
index a6e10970f..56677c2f4 100644
--- a/nova/tests/api/rackspace/auth.py
+++ b/nova/tests/api/rackspace/auth.py
@@ -1,12 +1,14 @@
-import webob
-import webob.dec
+import datetime
import unittest
+
import stubout
+import webob
+import webob.dec
+
import nova.api
import nova.api.rackspace.auth
from nova import auth
from nova.tests.api.rackspace import test_helper
-import datetime
class Test(unittest.TestCase):
def setUp(self):
diff --git a/nova/tests/api/rackspace/flavors.py b/nova/tests/api/rackspace/flavors.py
index 7bd1ea1c4..d25a2e2be 100644
--- a/nova/tests/api/rackspace/flavors.py
+++ b/nova/tests/api/rackspace/flavors.py
@@ -38,7 +38,6 @@ class FlavorsTest(unittest.TestCase):
def test_get_flavor_list(self):
req = webob.Request.blank('/v1.0/flavors')
res = req.get_response(nova.api.API())
- print res
def test_get_flavor_by_id(self):
pass
diff --git a/nova/tests/api/rackspace/images.py b/nova/tests/api/rackspace/images.py
index 560d8c898..4c9987e8b 100644
--- a/nova/tests/api/rackspace/images.py
+++ b/nova/tests/api/rackspace/images.py
@@ -15,6 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import stubout
import unittest
from nova.api.rackspace import images
diff --git a/nova/tests/api/rackspace/servers.py b/nova/tests/api/rackspace/servers.py
index 9fd8e5e88..69ad2c1d3 100644
--- a/nova/tests/api/rackspace/servers.py
+++ b/nova/tests/api/rackspace/servers.py
@@ -26,6 +26,7 @@ import nova.api.rackspace
from nova.api.rackspace import servers
import nova.db.api
from nova.db.sqlalchemy.models import Instance
+import nova.rpc
from nova.tests.api.test_helper import *
from nova.tests.api.rackspace import test_helper
@@ -52,8 +53,11 @@ class ServersTest(unittest.TestCase):
test_helper.stub_for_testing(self.stubs)
test_helper.stub_out_rate_limiting(self.stubs)
test_helper.stub_out_auth(self.stubs)
+ test_helper.stub_out_id_translator(self.stubs)
+ test_helper.stub_out_key_pair_funcs(self.stubs)
+ test_helper.stub_out_image_service(self.stubs)
self.stubs.Set(nova.db.api, 'instance_get_all', return_servers)
- self.stubs.Set(nova.db.api, 'instance_get', return_server)
+ self.stubs.Set(nova.db.api, 'instance_get_by_ec2_id', return_server)
self.stubs.Set(nova.db.api, 'instance_get_all_by_user',
return_servers)
@@ -67,9 +71,6 @@ class ServersTest(unittest.TestCase):
self.assertEqual(res_dict['server']['id'], '1')
self.assertEqual(res_dict['server']['name'], 'server1')
- def test_get_backup_schedule(self):
- pass
-
def test_get_server_list(self):
req = webob.Request.blank('/v1.0/servers')
res = req.get_response(nova.api.API())
@@ -82,24 +83,86 @@ class ServersTest(unittest.TestCase):
self.assertEqual(s.get('imageId', None), None)
i += 1
- #def test_create_instance(self):
- # test_helper.stub_out_image_translator(self.stubs)
- # body = dict(server=dict(
- # name='server_test', imageId=2, flavorId=2, metadata={},
- # personality = {}
- # ))
- # req = webob.Request.blank('/v1.0/servers')
- # req.method = 'POST'
- # req.body = json.dumps(body)
+ def test_create_instance(self):
+ def server_update(context, id, params):
+ pass
+
+ def instance_create(context, inst):
+ class Foo(object):
+ ec2_id = 1
+ return Foo()
+
+ def fake_method(*args, **kwargs):
+ pass
+
+ def project_get_network(context, user_id):
+ return dict(id='1', host='localhost')
+
+ def queue_get_for(context, *args):
+ return 'network_topic'
+
+ self.stubs.Set(nova.db.api, 'project_get_network', project_get_network)
+ self.stubs.Set(nova.db.api, 'instance_create', instance_create)
+ self.stubs.Set(nova.rpc, 'cast', fake_method)
+ self.stubs.Set(nova.rpc, 'call', fake_method)
+ self.stubs.Set(nova.db.api, 'instance_update',
+ server_update)
+ self.stubs.Set(nova.db.api, 'queue_get_for', queue_get_for)
+ self.stubs.Set(nova.network.manager.FlatManager, 'allocate_fixed_ip',
+ fake_method)
+
+ test_helper.stub_out_id_translator(self.stubs)
+ body = dict(server=dict(
+ name='server_test', imageId=2, flavorId=2, metadata={},
+ personality = {}
+ ))
+ req = webob.Request.blank('/v1.0/servers')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+
+ res = req.get_response(nova.api.API())
+
+ self.assertEqual(res.status_int, 200)
+
+ def test_update_no_body(self):
+ req = webob.Request.blank('/v1.0/servers/1')
+ req.method = 'PUT'
+ res = req.get_response(nova.api.API())
+ self.assertEqual(res.status_int, 422)
+
+ def test_update_bad_params(self):
+ """ Confirm that update is filtering params """
+ inst_dict = dict(cat='leopard', name='server_test', adminPass='bacon')
+ self.body = json.dumps(dict(server=inst_dict))
- # res = req.get_response(nova.api.API())
+ def server_update(context, id, params):
+ self.update_called = True
+ filtered_dict = dict(name='server_test', admin_pass='bacon')
+ self.assertEqual(params, filtered_dict)
- # print res
- def test_update_server_password(self):
- pass
+ self.stubs.Set(nova.db.api, 'instance_update',
+ server_update)
- def test_update_server_name(self):
- pass
+ req = webob.Request.blank('/v1.0/servers/1')
+ req.method = 'PUT'
+ req.body = self.body
+ req.get_response(nova.api.API())
+
+ def test_update_server(self):
+ inst_dict = dict(name='server_test', adminPass='bacon')
+ self.body = json.dumps(dict(server=inst_dict))
+
+ def server_update(context, id, params):
+ filtered_dict = dict(name='server_test', admin_pass='bacon')
+ self.assertEqual(params, filtered_dict)
+
+ self.stubs.Set(nova.db.api, 'instance_update',
+ server_update)
+
+ req = webob.Request.blank('/v1.0/servers/1')
+ req.method = 'PUT'
+ req.body = self.body
+ req.get_response(nova.api.API())
def test_create_backup_schedules(self):
req = webob.Request.blank('/v1.0/servers/1/backup_schedules')
diff --git a/nova/tests/api/rackspace/sharedipgroups.py b/nova/tests/api/rackspace/sharedipgroups.py
index b4b281db7..1906b54f5 100644
--- a/nova/tests/api/rackspace/sharedipgroups.py
+++ b/nova/tests/api/rackspace/sharedipgroups.py
@@ -15,6 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import stubout
import unittest
from nova.api.rackspace import sharedipgroups
diff --git a/nova/tests/api/rackspace/test_helper.py b/nova/tests/api/rackspace/test_helper.py
index aa7fb382c..2cf154f63 100644
--- a/nova/tests/api/rackspace/test_helper.py
+++ b/nova/tests/api/rackspace/test_helper.py
@@ -9,6 +9,7 @@ from nova import utils
from nova import flags
import nova.api.rackspace.auth
import nova.api.rackspace._id_translator
+from nova.image import service
from nova.wsgi import Router
FLAGS = flags.FLAGS
@@ -40,7 +41,19 @@ def fake_wsgi(self, req):
req.environ['inst_dict'] = json.loads(req.body)
return self.application
-def stub_out_image_translator(stubs):
+def stub_out_key_pair_funcs(stubs):
+ def key_pair(context, user_id):
+ return [dict(name='key', public_key='public_key')]
+ stubs.Set(nova.db.api, 'key_pair_get_all_by_user',
+ key_pair)
+
+def stub_out_image_service(stubs):
+ def fake_image_show(meh, id):
+ return dict(kernelId=1, ramdiskId=1)
+
+ stubs.Set(nova.image.service.LocalImageService, 'show', fake_image_show)
+
+def stub_out_id_translator(stubs):
class FakeTranslator(object):
def __init__(self, id_type, service_name):
pass
diff --git a/nova/wsgi.py b/nova/wsgi.py
index da9374542..b91d91121 100644
--- a/nova/wsgi.py
+++ b/nova/wsgi.py
@@ -230,6 +230,15 @@ class Controller(object):
serializer = Serializer(request.environ, _metadata)
return serializer.to_content_type(data)
+ def _deserialize(self, data, request):
+ """
+ Deserialize the request body to the response type requested in request.
+ Uses self._serialization_metadata if it exists, which is a dict mapping
+ MIME types to information needed to serialize to that type.
+ """
+ _metadata = getattr(type(self), "_serialization_metadata", {})
+ serializer = Serializer(request.environ, _metadata)
+ return serializer.deserialize(data)
class Serializer(object):
"""
@@ -272,10 +281,13 @@ class Serializer(object):
The string must be in the format of a supported MIME type.
"""
datastring = datastring.strip()
- is_xml = (datastring[0] == '<')
- if not is_xml:
- return json.loads(datastring)
- return self._from_xml(datastring)
+ try:
+ is_xml = (datastring[0] == '<')
+ if not is_xml:
+ return json.loads(datastring)
+ return self._from_xml(datastring)
+ except:
+ return None
def _from_xml(self, datastring):
xmldata = self.metadata.get('application/xml', {})