From 70e4d73778d448cb7f122bc0a2a0c43a78fff46a Mon Sep 17 00:00:00 2001 From: John Tran Date: Wed, 8 Jun 2011 15:23:33 -0700 Subject: added a test for allocate_address & added error handling for api instead of returning 'UnknownError', will give information 'AllocateAddressError: NoMoreAddresses --- nova/api/ec2/__init__.py | 6 ++++++ nova/tests/test_cloud.py | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 1915d007d..459ecb442 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -348,6 +348,12 @@ class Executor(wsgi.Application): LOG.debug(_('KeyPairExists raised: %s'), unicode(ex), context=context) return self._error(req, context, type(ex).__name__, unicode(ex)) + except rpc.RemoteError as ex: + LOG.debug(_('RemoteError raised: %s'), ex.exc_type, + context=context) + if ex.exc_type == 'NoMoreAddresses': + return self._error(req, context, 'AllocateAddressError', + ex.exc_type) except Exception as ex: extra = {'environment': req.environ} LOG.exception(_('Unexpected error raised: %s'), unicode(ex), diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index ba133c860..d6d90e873 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -115,6 +115,16 @@ class CloudTestCase(test.TestCase): public_ip=address) db.floating_ip_destroy(self.context, address) + def test_allocate_address(self): + address = "10.10.10.10" + allocate = self.cloud.allocate_address + db.floating_ip_create(self.context, + {'address': address, + 'host': self.network.host}) + self.assertEqual(allocate(self.context)['publicIp'], address) + db.floating_ip_destroy(self.context, address) + self.assertRaises(rpc.RemoteError, allocate, self.context) + def test_associate_disassociate_address(self): """Verifies associate runs cleanly without raising an exception""" address = "10.10.10.10" -- cgit From 3764be9d65483a9e431b69f37e3516fa20961362 Mon Sep 17 00:00:00 2001 From: John Tran Date: Wed, 8 Jun 2011 17:15:35 -0700 Subject: raises exception.NoFloatingIpsDefined instead of UnknownError --- nova/api/ec2/cloud.py | 8 ++++++-- nova/tests/test_cloud.py | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 316298c39..6c5dba8ed 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -39,6 +39,7 @@ from nova import flags from nova import ipv6 from nova import log as logging from nova import network +from nova import rpc from nova import utils from nova import volume from nova.api.ec2 import ec2utils @@ -872,8 +873,11 @@ class CloudController(object): def allocate_address(self, context, **kwargs): LOG.audit(_("Allocate address"), context=context) - public_ip = self.network_api.allocate_floating_ip(context) - return {'publicIp': public_ip} + try: + public_ip = self.network_api.allocate_floating_ip(context) + return {'publicIp': public_ip} + except rpc.RemoteError: + raise exception.NoFloatingIpsDefined def release_address(self, context, public_ip, **kwargs): LOG.audit(_("Release address %s"), public_ip, context=context) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index d6d90e873..7cb13c919 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -123,7 +123,8 @@ class CloudTestCase(test.TestCase): 'host': self.network.host}) self.assertEqual(allocate(self.context)['publicIp'], address) db.floating_ip_destroy(self.context, address) - self.assertRaises(rpc.RemoteError, allocate, self.context) + self.assertRaises(exception.NoFloatingIpsDefined, allocate, + self.context) def test_associate_disassociate_address(self): """Verifies associate runs cleanly without raising an exception""" -- cgit From b11cf9bc7b1b9792bdab77aa72dc6163f3e44ca1 Mon Sep 17 00:00:00 2001 From: John Tran Date: Wed, 8 Jun 2011 17:17:40 -0700 Subject: removing custom exception, instead using NoFloatingIpsDefined --- nova/api/ec2/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 459ecb442..1915d007d 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -348,12 +348,6 @@ class Executor(wsgi.Application): LOG.debug(_('KeyPairExists raised: %s'), unicode(ex), context=context) return self._error(req, context, type(ex).__name__, unicode(ex)) - except rpc.RemoteError as ex: - LOG.debug(_('RemoteError raised: %s'), ex.exc_type, - context=context) - if ex.exc_type == 'NoMoreAddresses': - return self._error(req, context, 'AllocateAddressError', - ex.exc_type) except Exception as ex: extra = {'environment': req.environ} LOG.exception(_('Unexpected error raised: %s'), unicode(ex), -- cgit From 8096ee6c79c608fd84e016d5da7663549a95896f Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Thu, 9 Jun 2011 03:35:59 +0000 Subject: Support multiple glance-api servers --- nova/flags.py | 6 ++++-- nova/image/__init__.py | 7 ++++++- nova/image/glance.py | 24 +++++++++++++++++------- nova/virt/images.py | 7 +++++-- nova/virt/xenapi/vm_utils.py | 13 +++++++++---- 5 files changed, 41 insertions(+), 16 deletions(-) diff --git a/nova/flags.py b/nova/flags.py index d5090edba..51f0536e8 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -270,8 +270,10 @@ DEFINE_list('region_list', DEFINE_string('connection_type', 'libvirt', 'libvirt, xenapi or fake') DEFINE_string('aws_access_key_id', 'admin', 'AWS Access ID') DEFINE_string('aws_secret_access_key', 'admin', 'AWS Access Key') -DEFINE_integer('glance_port', 9292, 'glance port') -DEFINE_string('glance_host', '$my_ip', 'glance host') +# NOTE(sirp): my_ip interpolation doesn't work within nested structures +DEFINE_list('glance_api_servers', + [('127.0.0.1', 9292)], + 'list of glance servers available to nova') DEFINE_integer('s3_port', 3333, 's3 port') DEFINE_string('s3_host', '$my_ip', 's3 host (for infrastructure)') DEFINE_string('s3_dmz', '$my_ip', 's3 dmz ip (for instances)') diff --git a/nova/image/__init__.py b/nova/image/__init__.py index 93d83df24..a27d649d4 100644 --- a/nova/image/__init__.py +++ b/nova/image/__init__.py @@ -22,6 +22,7 @@ import nova from nova import exception from nova import utils from nova import flags +from nova.image import glance as glance_image_service FLAGS = flags.FLAGS @@ -48,6 +49,8 @@ def get_default_image_service(): return ImageService() +# FIXME(sirp): perhaps this should be moved to nova/images/glance so that we +# keep Glance specific code together for the most part def get_glance_client(image_href): """Get the correct glance client and id for the given image_href. @@ -62,7 +65,9 @@ def get_glance_client(image_href): """ image_href = image_href or 0 if str(image_href).isdigit(): - glance_client = GlanceClient(FLAGS.glance_host, FLAGS.glance_port) + glance_host, glance_port = \ + glance_image_service.pick_glance_api_server() + glance_client = GlanceClient(glance_host, glance_port) return (glance_client, int(image_href)) try: diff --git a/nova/image/glance.py b/nova/image/glance.py index 61308431d..b68415844 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -20,6 +20,7 @@ from __future__ import absolute_import import datetime +import random from glance.common import exception as glance_exception @@ -39,6 +40,15 @@ FLAGS = flags.FLAGS GlanceClient = utils.import_class('glance.client.Client') +def pick_glance_api_server(): + """Return which Glance API server to use for the request + + Returns (host, port) + """ + host, port = random.choice(FLAGS.glance_api_servers) + return host, port + + class GlanceImageService(service.BaseImageService): """Provides storage and retrieval of disk image objects within Glance.""" @@ -50,13 +60,13 @@ class GlanceImageService(service.BaseImageService): SERVICE_IMAGE_ATTRS = service.BaseImageService.BASE_IMAGE_ATTRS +\ GLANCE_ONLY_ATTRS - def __init__(self, client=None): - # FIXME(sirp): can we avoid dependency-injection here by using - # stubbing out a fake? - if client is None: - self.client = GlanceClient(FLAGS.glance_host, FLAGS.glance_port) - else: - self.client = client + @property + def client(self): + # NOTE(sirp): we want to load balance each request across glance + # servers. Since GlanceImageService is a long-lived object, `client` + # is made to choose a new server each time via this property. + glance_host, glance_port = pick_glance_api_server() + return GlanceClient(glance_host, glance_port) def index(self, context, filters=None, marker=None, limit=None): """Calls out to Glance for a list of images available.""" diff --git a/nova/virt/images.py b/nova/virt/images.py index de7ac61df..e6f3d3c9e 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -23,6 +23,7 @@ Handling of VM disk images. from nova import context from nova import flags +from nova.image import glance as glance_image_service import nova.image from nova import log as logging from nova import utils @@ -48,7 +49,9 @@ def fetch(image_href, path, _user, _project): # of retrieving the image using this method. def image_url(image): if FLAGS.image_service == "nova.image.glance.GlanceImageService": - return "http://%s:%s/images/%s" % (FLAGS.glance_host, - FLAGS.glance_port, image) + glance_host, glance_port = \ + glance_image_service.pick_glance_api_server() + return "http://%s:%s/images/%s" % (glance_host, glance_port, image) + return "http://%s:%s/_images/%s/image" % (FLAGS.s3_host, FLAGS.s3_port, image) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 98668e6ae..ccde6cbfe 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -33,6 +33,7 @@ import glance.client from nova import exception from nova import flags import nova.image +from nova.image import glance as glance_image_service from nova import log as logging from nova import utils from nova.auth.manager import AuthManager @@ -358,10 +359,12 @@ class VMHelper(HelperBase): os_type = instance.os_type or FLAGS.default_os_type + glance_host, glance_port = \ + glance_image_service.pick_glance_api_server() params = {'vdi_uuids': vdi_uuids, 'image_id': image_id, - 'glance_host': FLAGS.glance_host, - 'glance_port': FLAGS.glance_port, + 'glance_host': glance_host, + 'glance_port': glance_port, 'sr_path': cls.get_sr_path(session), 'os_type': os_type} @@ -409,9 +412,11 @@ class VMHelper(HelperBase): # here (under Python 2.6+) and pass them as arguments uuid_stack = [str(uuid.uuid4()) for i in xrange(2)] + glance_host, glance_port = \ + glance_image_service.pick_glance_api_server() params = {'image_id': image, - 'glance_host': FLAGS.glance_host, - 'glance_port': FLAGS.glance_port, + 'glance_host': glance_host, + 'glance_port': glance_port, 'uuid_stack': uuid_stack, 'sr_path': cls.get_sr_path(session)} -- cgit From 463e0388308760dbf3bf2b3fa901d8076d002f91 Mon Sep 17 00:00:00 2001 From: John Tran Date: Thu, 9 Jun 2011 00:01:42 -0700 Subject: matched the inner exception specifically, instead of catching all RemoteError exceptions --- nova/api/ec2/cloud.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 6c5dba8ed..84a83d8e6 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -876,8 +876,11 @@ class CloudController(object): try: public_ip = self.network_api.allocate_floating_ip(context) return {'publicIp': public_ip} - except rpc.RemoteError: - raise exception.NoFloatingIpsDefined + except rpc.RemoteError as ex: + if ex.exc_type == 'NoMoreAddresses': + raise exception.NoFloatingIpsDefined + else: + raise def release_address(self, context, public_ip, **kwargs): LOG.audit(_("Release address %s"), public_ip, context=context) -- cgit From eda8a1aaa2cf7cc31c7fda4723849feee3bc6766 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Thu, 9 Jun 2011 14:43:24 +0000 Subject: Fixing the tests --- nova/image/glance.py | 12 ++++++++++-- nova/tests/image/test_glance.py | 11 ++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/nova/image/glance.py b/nova/image/glance.py index b68415844..f82c0f4a3 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -60,14 +60,22 @@ class GlanceImageService(service.BaseImageService): SERVICE_IMAGE_ATTRS = service.BaseImageService.BASE_IMAGE_ATTRS +\ GLANCE_ONLY_ATTRS - @property - def client(self): + _client = None + + def _get_client(self): # NOTE(sirp): we want to load balance each request across glance # servers. Since GlanceImageService is a long-lived object, `client` # is made to choose a new server each time via this property. + if self._client is not None: + return self._client glance_host, glance_port = pick_glance_api_server() return GlanceClient(glance_host, glance_port) + def _set_client(self, client): + self._client = client + + client = property(_get_client, _set_client) + def index(self, context, filters=None, marker=None, limit=None): """Calls out to Glance for a list of images available.""" # NOTE(sirp): We need to use `get_images_detailed` and not diff --git a/nova/tests/image/test_glance.py b/nova/tests/image/test_glance.py index 041da1e13..033b8389c 100644 --- a/nova/tests/image/test_glance.py +++ b/nova/tests/image/test_glance.py @@ -17,6 +17,7 @@ import datetime +import stubout import unittest from nova import context @@ -60,12 +61,16 @@ class BaseGlanceTest(unittest.TestCase): NOW_DATETIME = datetime.datetime(2010, 10, 11, 10, 30, 22) def setUp(self): - # FIXME(sirp): we can probably use stubs library here rather than - # dependency injection + self.stubs = stubout.StubOutForTesting() self.client = StubGlanceClient(None) - self.service = glance.GlanceImageService(self.client) + self.service = glance.GlanceImageService() + self.stubs.Set(self.service, 'client', self.client) self.context = context.RequestContext(None, None) + def tearDown(self): + self.stubs.UnsetAll() + super(BaseGlanceTest, self).tearDown() + def assertDateTimesFilled(self, image_meta): self.assertEqual(image_meta['created_at'], self.NOW_DATETIME) self.assertEqual(image_meta['updated_at'], self.NOW_DATETIME) -- cgit From e307bf5dd60dc84587f76d88956499ee1f1013fb Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Thu, 9 Jun 2011 21:36:20 +0000 Subject: Fixing code per review comments --- nova/flags.py | 4 ++-- nova/image/glance.py | 7 +++++-- nova/tests/image/test_glance.py | 9 +-------- nova/virt/images.py | 12 ------------ nova/virt/xenapi/vm_utils.py | 3 ++- 5 files changed, 10 insertions(+), 25 deletions(-) diff --git a/nova/flags.py b/nova/flags.py index 545cf90fe..acfcf8d68 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -272,8 +272,8 @@ DEFINE_string('aws_access_key_id', 'admin', 'AWS Access ID') DEFINE_string('aws_secret_access_key', 'admin', 'AWS Access Key') # NOTE(sirp): my_ip interpolation doesn't work within nested structures DEFINE_list('glance_api_servers', - [('127.0.0.1', 9292)], - 'list of glance servers available to nova') + ['127.0.0.1:9292'], + 'list of glance api servers available to nova (host:port)') DEFINE_integer('s3_port', 3333, 's3 port') DEFINE_string('s3_host', '$my_ip', 's3 host (for infrastructure)') DEFINE_string('s3_dmz', '$my_ip', 's3 dmz ip (for instances)') diff --git a/nova/image/glance.py b/nova/image/glance.py index f82c0f4a3..5712215bb 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -45,7 +45,9 @@ def pick_glance_api_server(): Returns (host, port) """ - host, port = random.choice(FLAGS.glance_api_servers) + host_port = random.choice(FLAGS.glance_api_servers) + host, port_str = host_port.split(':') + port = int(port_str) return host, port @@ -60,7 +62,8 @@ class GlanceImageService(service.BaseImageService): SERVICE_IMAGE_ATTRS = service.BaseImageService.BASE_IMAGE_ATTRS +\ GLANCE_ONLY_ATTRS - _client = None + def __init__(self, client=None): + self._client = client def _get_client(self): # NOTE(sirp): we want to load balance each request across glance diff --git a/nova/tests/image/test_glance.py b/nova/tests/image/test_glance.py index 033b8389c..223e7ae57 100644 --- a/nova/tests/image/test_glance.py +++ b/nova/tests/image/test_glance.py @@ -17,7 +17,6 @@ import datetime -import stubout import unittest from nova import context @@ -61,16 +60,10 @@ class BaseGlanceTest(unittest.TestCase): NOW_DATETIME = datetime.datetime(2010, 10, 11, 10, 30, 22) def setUp(self): - self.stubs = stubout.StubOutForTesting() self.client = StubGlanceClient(None) - self.service = glance.GlanceImageService() - self.stubs.Set(self.service, 'client', self.client) + self.service = glance.GlanceImageService(client=self.client) self.context = context.RequestContext(None, None) - def tearDown(self): - self.stubs.UnsetAll() - super(BaseGlanceTest, self).tearDown() - def assertDateTimesFilled(self, image_meta): self.assertEqual(image_meta['created_at'], self.NOW_DATETIME) self.assertEqual(image_meta['updated_at'], self.NOW_DATETIME) diff --git a/nova/virt/images.py b/nova/virt/images.py index e6f3d3c9e..40bf6107c 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -43,15 +43,3 @@ def fetch(image_href, path, _user, _project): elevated = context.get_admin_context() metadata = image_service.get(elevated, image_id, image_file) return metadata - - -# TODO(vish): xenapi should use the glance client code directly instead -# of retrieving the image using this method. -def image_url(image): - if FLAGS.image_service == "nova.image.glance.GlanceImageService": - glance_host, glance_port = \ - glance_image_service.pick_glance_api_server() - return "http://%s:%s/images/%s" % (glance_host, glance_port, image) - - return "http://%s:%s/_images/%s/image" % (FLAGS.s3_host, FLAGS.s3_port, - image) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index ccde6cbfe..b9d4346e4 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -581,7 +581,8 @@ class VMHelper(HelperBase): Returns: A single filename if image_type is KERNEL_RAMDISK A list of dictionaries that describe VDIs, otherwise """ - url = images.image_url(image) + url = "http://%s:%s/_images/%s/image" % (FLAGS.s3_host, FLAGS.s3_port, + image) LOG.debug(_("Asking xapi to fetch %(url)s as %(access)s") % locals()) if image_type == ImageType.KERNEL_RAMDISK: fn = 'get_kernel' -- cgit From 361fd763eb0cf3e62e0184dafd0f4a024e1871f5 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Thu, 9 Jun 2011 21:50:34 +0000 Subject: Adding caveat --- nova/image/glance.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nova/image/glance.py b/nova/image/glance.py index 5712215bb..6e058ab2f 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -43,6 +43,10 @@ GlanceClient = utils.import_class('glance.client.Client') def pick_glance_api_server(): """Return which Glance API server to use for the request + This method provides a very primitive form of load-balancing suitable for + testing and sandbox environments. In production, it would be better to use + one IP and route that to a real load-balancer. + Returns (host, port) """ host_port = random.choice(FLAGS.glance_api_servers) -- cgit From e763a0ac8981bdbee44c054c6be08b9f1a5d634d Mon Sep 17 00:00:00 2001 From: John Tran Date: Fri, 10 Jun 2011 10:24:24 -0700 Subject: style change --- nova/tests/test_cloud.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 7cb13c919..c8313a5d3 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -123,7 +123,8 @@ class CloudTestCase(test.TestCase): 'host': self.network.host}) self.assertEqual(allocate(self.context)['publicIp'], address) db.floating_ip_destroy(self.context, address) - self.assertRaises(exception.NoFloatingIpsDefined, allocate, + self.assertRaises(exception.NoFloatingIpsDefined, + allocate, self.context) def test_associate_disassociate_address(self): -- cgit From 0e7a2042cc5922bb014a77080ec0bdb93bbf575c Mon Sep 17 00:00:00 2001 From: John Tran Date: Fri, 10 Jun 2011 10:28:03 -0700 Subject: raise instance instead of class --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 84a83d8e6..5ed473b73 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -878,7 +878,7 @@ class CloudController(object): return {'publicIp': public_ip} except rpc.RemoteError as ex: if ex.exc_type == 'NoMoreAddresses': - raise exception.NoFloatingIpsDefined + raise exception.NoFloatingIpsDefined() else: raise -- cgit From 05fecdf873a5c02dcb13c841304df872411d4183 Mon Sep 17 00:00:00 2001 From: John Tran Date: Fri, 10 Jun 2011 11:10:58 -0700 Subject: added new exception more descriptive of not having available floating addresses avail for allocation --- nova/api/ec2/cloud.py | 2 +- nova/exception.py | 4 ++++ nova/tests/test_cloud.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 5ed473b73..e1c65ae40 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -878,7 +878,7 @@ class CloudController(object): return {'publicIp': public_ip} except rpc.RemoteError as ex: if ex.exc_type == 'NoMoreAddresses': - raise exception.NoFloatingIpsDefined() + raise exception.NoMoreFloatingIps() else: raise diff --git a/nova/exception.py b/nova/exception.py index 69b3e0359..1571dd032 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -376,6 +376,10 @@ class NoFloatingIpsDefinedForInstance(NoFloatingIpsDefined): message = _("Zero floating ips defined for instance %(instance_id)s.") +class NoMoreFloatingIps(NotFound): + message = _("Zero floating ips available.") + + class KeypairNotFound(NotFound): message = _("Keypair %(keypair_name)s not found for user %(user_id)s") diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index c8313a5d3..13046f861 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -123,7 +123,7 @@ class CloudTestCase(test.TestCase): 'host': self.network.host}) self.assertEqual(allocate(self.context)['publicIp'], address) db.floating_ip_destroy(self.context, address) - self.assertRaises(exception.NoFloatingIpsDefined, + self.assertRaises(exception.NoMoreFloatingIps, allocate, self.context) -- cgit From e986887d513855d5a5fd6ca90998860f67fcb1d3 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 10 Jun 2011 15:28:17 -0400 Subject: force utf-8 encoding on toprettyxml call for XMLDictSerializer --- nova/api/openstack/wsgi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index ddf4e6fa9..6760735c4 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -225,7 +225,7 @@ class XMLDictSerializer(DictSerializer): if not xmlns and self.xmlns: node.setAttribute('xmlns', self.xmlns) - return node.toprettyxml(indent=' ') + return node.toprettyxml(indent=' ', encoding='utf-8') def _to_xml_node(self, doc, metadata, nodename, data): """Recursive method to convert data members to XML nodes.""" -- cgit -- cgit