summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2012-01-10 12:31:48 +0000
committerGerrit Code Review <review@openstack.org>2012-01-10 12:31:48 +0000
commitfa43949c05ff4e5165baa2f0a0f1555e94bcf212 (patch)
tree257727bde45442607e07ea2f27f554e74f0be243
parentef6058a44649715d5215958475c444ad811fe491 (diff)
parent26b24b7e2305d9aa179a9d257b715e6d67b75573 (diff)
downloadnova-fa43949c05ff4e5165baa2f0a0f1555e94bcf212.tar.gz
nova-fa43949c05ff4e5165baa2f0a0f1555e94bcf212.tar.xz
nova-fa43949c05ff4e5165baa2f0a0f1555e94bcf212.zip
Merge "Adds support for floating ip pools"
-rwxr-xr-xbin/nova-manage20
-rw-r--r--nova/api/openstack/v2/contrib/floating_ip_pools.py104
-rw-r--r--nova/api/openstack/v2/contrib/floating_ips.py19
-rw-r--r--nova/db/api.py12
-rw-r--r--nova/db/sqlalchemy/api.py13
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/067_add_pool_and_interface_to_floating_ip.py46
-rw-r--r--nova/db/sqlalchemy/models.py3
-rw-r--r--nova/network/api.py13
-rwxr-xr-xnova/network/linux_net.py11
-rw-r--r--nova/network/manager.py46
-rw-r--r--nova/tests/api/ec2/test_cloud.py11
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_floating_ip_pools.py73
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_floating_ips.py17
-rw-r--r--nova/tests/api/openstack/v2/test_extensions.py2
-rw-r--r--nova/tests/db/fakes.py7
-rw-r--r--nova/tests/test_network.py19
16 files changed, 371 insertions, 45 deletions
diff --git a/bin/nova-manage b/bin/nova-manage
index a61b5c1dd..79683fef7 100755
--- a/bin/nova-manage
+++ b/bin/nova-manage
@@ -1,6 +1,7 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
@@ -103,6 +104,8 @@ flags.DECLARE('multi_host', 'nova.network.manager')
flags.DECLARE('network_size', 'nova.network.manager')
flags.DECLARE('vlan_start', 'nova.network.manager')
flags.DECLARE('vpn_start', 'nova.network.manager')
+flags.DECLARE('default_floating_pool', 'nova.network.manager')
+flags.DECLARE('public_interface', 'nova.network.linux_net')
flags.DECLARE('libvirt_type', 'nova.virt.libvirt.connection')
flags.DEFINE_flag(flags.HelpFlag())
flags.DEFINE_flag(flags.HelpshortFlag())
@@ -684,14 +687,23 @@ class FixedIpCommands(object):
class FloatingIpCommands(object):
"""Class for managing floating ip."""
- @args('--ip_range', dest="range", metavar='<range>', help='IP range')
- def create(self, range):
+ @args('--ip_range', dest="ip_range", metavar='<range>', help='IP range')
+ @args('--pool', dest="pool", metavar='<pool>', help='Optional pool')
+ @args('--interface', dest="interface", metavar='<interface>',
+ help='Optional interface')
+ def create(self, ip_range, pool=None, interface=None):
"""Creates floating ips for zone by range"""
- addresses = netaddr.IPNetwork(range)
+ addresses = netaddr.IPNetwork(ip_range)
admin_context = context.get_admin_context()
+ if not pool:
+ pool = FLAGS.default_floating_pool
+ if not interface:
+ interface = FLAGS.public_interface
for address in addresses.iter_hosts():
db.floating_ip_create(admin_context,
- {'address': str(address)})
+ {'address': str(address),
+ 'pool': pool,
+ 'interface': interface})
@args('--ip_range', dest="ip_range", metavar='<range>', help='IP range')
def delete(self, ip_range):
diff --git a/nova/api/openstack/v2/contrib/floating_ip_pools.py b/nova/api/openstack/v2/contrib/floating_ip_pools.py
new file mode 100644
index 000000000..9d6386f25
--- /dev/null
+++ b/nova/api/openstack/v2/contrib/floating_ip_pools.py
@@ -0,0 +1,104 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
+#
+# 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
+
+from nova.api.openstack import wsgi
+from nova.api.openstack import xmlutil
+from nova.api.openstack.v2 import extensions
+from nova import log as logging
+from nova import network
+
+
+LOG = logging.getLogger('nova.api.openstack.v2.contrib.floating_ip_poolss')
+
+
+def _translate_floating_ip_view(pool):
+ return {
+ 'name': pool['name'],
+ }
+
+
+def _translate_floating_ip_pools_view(pools):
+ return {
+ 'floating_ip_pools': [_translate_floating_ip_view(pool)
+ for pool in pools]
+ }
+
+
+class FloatingIPPoolsController(object):
+ """The Floating IP Pool API controller for the OpenStack API."""
+
+ def __init__(self):
+ self.network_api = network.API()
+ super(FloatingIPPoolsController, self).__init__()
+
+ def index(self, req):
+ """Return a list of pools."""
+ context = req.environ['nova.context']
+ pools = self.network_api.get_floating_ip_pools(context)
+ return _translate_floating_ip_pools_view(pools)
+
+
+def make_float_ip(elem):
+ elem.set('name')
+
+
+class FloatingIPPoolTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('floating_ip_pool',
+ selector='floating_ip_pool')
+ make_float_ip(root)
+ return xmlutil.MasterTemplate(root, 1)
+
+
+class FloatingIPPoolsTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('floating_ip_pools')
+ elem = xmlutil.SubTemplateElement(root, 'floating_ip_pool',
+ selector='floating_ip_pools')
+ make_float_ip(elem)
+ return xmlutil.MasterTemplate(root, 1)
+
+
+class FloatingIPPoolsSerializer(xmlutil.XMLTemplateSerializer):
+ def index(self):
+ return FloatingIPPoolsTemplate()
+
+
+class Floating_ip_pools(extensions.ExtensionDescriptor):
+ """Floating IPs support"""
+
+ name = "Floating_ip_pools"
+ alias = "os-floating-ip-pools"
+ namespace = \
+ "http://docs.openstack.org/compute/ext/floating_ip_pools/api/v1.1"
+ updated = "2012-01-04T00:00:00+00:00"
+
+ def get_resources(self):
+ resources = []
+
+ body_serializers = {
+ 'application/xml': FloatingIPPoolsSerializer(),
+ }
+
+ serializer = wsgi.ResponseSerializer(body_serializers)
+
+ res = extensions.ResourceExtension('os-floating-ip-pools',
+ FloatingIPPoolsController(),
+ serializer=serializer,
+ member_actions={})
+ resources.append(res)
+
+ return resources
diff --git a/nova/api/openstack/v2/contrib/floating_ips.py b/nova/api/openstack/v2/contrib/floating_ips.py
index 39700d382..3a6f4ee34 100644
--- a/nova/api/openstack/v2/contrib/floating_ips.py
+++ b/nova/api/openstack/v2/contrib/floating_ips.py
@@ -1,6 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
+# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
# Copyright 2011 Grid Dynamics
# Copyright 2011 Eldar Nugaev, Kirill Shileev, Ilya Alekseyev
#
@@ -34,6 +35,7 @@ LOG = logging.getLogger('nova.api.openstack.v2.contrib.floating_ips')
def make_float_ip(elem):
elem.set('id')
elem.set('ip')
+ elem.set('pool')
elem.set('fixed_ip')
elem.set('instance_id')
@@ -56,8 +58,11 @@ class FloatingIPsTemplate(xmlutil.TemplateBuilder):
def _translate_floating_ip_view(floating_ip):
- result = {'id': floating_ip['id'],
- 'ip': floating_ip['address']}
+ result = {
+ 'id': floating_ip['id'],
+ 'ip': floating_ip['address'],
+ 'pool': floating_ip['pool'],
+ }
try:
result['fixed_ip'] = floating_ip['fixed_ip']['address']
except (TypeError, KeyError):
@@ -106,13 +111,19 @@ class FloatingIPController(object):
def create(self, req, body=None):
context = req.environ['nova.context']
+ pool = None
+ if body and 'pool' in body:
+ pool = body['pool']
try:
- address = self.network_api.allocate_floating_ip(context)
+ address = self.network_api.allocate_floating_ip(context, pool)
ip = self.network_api.get_floating_ip_by_address(context, address)
except rpc.RemoteError as ex:
# NOTE(tr3buchet) - why does this block exist?
if ex.exc_type == 'NoMoreFloatingIps':
- msg = _("No more floating ips available.")
+ if pool:
+ msg = _("No more floating ips in pool %s.") % pool
+ else:
+ msg = _("No more floating ips available.")
raise webob.exc.HTTPBadRequest(explanation=msg)
else:
raise
diff --git a/nova/db/api.py b/nova/db/api.py
index e3ba9a1b5..5f9f9f43e 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -1,5 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
@@ -237,13 +238,18 @@ def floating_ip_get(context, id):
return IMPL.floating_ip_get(context, id)
-def floating_ip_allocate_address(context, project_id):
- """Allocate free floating ip and return the address.
+def floating_ip_get_pools(context):
+ """Returns a list of floating ip pools"""
+ return IMPL.floating_ip_get_pools(context)
+
+
+def floating_ip_allocate_address(context, project_id, pool):
+ """Allocate free floating ip from specified pool and return the address.
Raises if one is not available.
"""
- return IMPL.floating_ip_allocate_address(context, project_id)
+ return IMPL.floating_ip_allocate_address(context, project_id, pool)
def floating_ip_create(context, values):
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index eba393256..83a022292 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -1,5 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
@@ -489,7 +490,16 @@ def floating_ip_get(context, id):
@require_context
-def floating_ip_allocate_address(context, project_id):
+def floating_ip_get_pools(context):
+ session = get_session()
+ pools = []
+ for result in session.query(models.FloatingIp.pool).distinct():
+ pools.append({'name': result[0]})
+ return pools
+
+
+@require_context
+def floating_ip_allocate_address(context, project_id, pool):
authorize_project_context(context, project_id)
session = get_session()
with session.begin():
@@ -497,6 +507,7 @@ def floating_ip_allocate_address(context, project_id):
session=session, read_deleted="no").\
filter_by(fixed_ip_id=None).\
filter_by(project_id=None).\
+ filter_by(pool=pool).\
with_lockmode('update').\
first()
# NOTE(vish): if with_lockmode isn't supported, as in sqlite,
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/067_add_pool_and_interface_to_floating_ip.py b/nova/db/sqlalchemy/migrate_repo/versions/067_add_pool_and_interface_to_floating_ip.py
new file mode 100644
index 000000000..205d7e034
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/067_add_pool_and_interface_to_floating_ip.py
@@ -0,0 +1,46 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
+# 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.from sqlalchemy import *
+
+from sqlalchemy import Column, MetaData, Table, String
+
+from nova import flags
+
+
+flags.DECLARE('default_floating_pool', 'nova.network.manager')
+flags.DECLARE('public_interface', 'nova.network.linux_net')
+FLAGS = flags.FLAGS
+
+meta = MetaData()
+
+pool_column = Column('pool', String(255))
+interface_column = Column('interface', String(255))
+
+
+def upgrade(migrate_engine):
+ meta.bind = migrate_engine
+ table = Table('floating_ips', meta, autoload=True)
+ table.create_column(pool_column)
+ table.create_column(interface_column)
+ table.update().values(pool=FLAGS.default_floating_pool,
+ interface=FLAGS.public_interface).execute()
+
+
+def downgrade(migrate_engine):
+ meta.bind = migrate_engine
+ table = Table('floating_ips', meta, autoload=True)
+ table.c.pool.drop()
+ table.c.interface.drop()
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 4a27e8034..fad41334e 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -1,5 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# Copyright 2011 Piston Cloud Computing, Inc.
@@ -711,6 +712,8 @@ class FloatingIp(BASE, NovaBase):
project_id = Column(String(255))
host = Column(String(255)) # , ForeignKey('hosts.id'))
auto_assigned = Column(Boolean, default=False, nullable=False)
+ pool = Column(String(255))
+ interface = Column(String(255))
class AuthToken(BASE, NovaBase):
diff --git a/nova/network/api.py b/nova/network/api.py
index 64292eb04..82b3739a3 100644
--- a/nova/network/api.py
+++ b/nova/network/api.py
@@ -1,5 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
@@ -39,6 +40,11 @@ class API(base.Base):
{'method': 'get_floating_ip',
'args': {'id': id}})
+ def get_floating_ip_pools(self, context):
+ return rpc.call(context,
+ FLAGS.network_topic,
+ {'method': 'get_floating_pools'})
+
def get_floating_ip_by_address(self, context, address):
return rpc.call(context,
FLAGS.network_topic,
@@ -62,8 +68,8 @@ class API(base.Base):
{'method': 'get_vifs_by_instance',
'args': {'instance_id': instance_id}})
- def allocate_floating_ip(self, context):
- """Adds a floating ip to a project. (allocates)"""
+ def allocate_floating_ip(self, context, pool=None):
+ """Adds a floating ip to a project from a pool. (allocates)"""
# NOTE(vish): We don't know which network host should get the ip
# when we allocate, so just send it to any one. This
# will probably need to move into a network supervisor
@@ -71,7 +77,8 @@ class API(base.Base):
return rpc.call(context,
FLAGS.network_topic,
{'method': 'allocate_floating_ip',
- 'args': {'project_id': context.project_id}})
+ 'args': {'project_id': context.project_id,
+ 'pool': pool}})
def release_floating_ip(self, context, address,
affect_auto_assigned=False):
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index 3784a1117..5b0c80eec 100755
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -1,5 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
@@ -432,21 +433,21 @@ def init_host(ip_range=None):
iptables_manager.apply()
-def bind_floating_ip(floating_ip, check_exit_code=True):
+def bind_floating_ip(floating_ip, device, check_exit_code=True):
"""Bind ip to public interface."""
_execute('ip', 'addr', 'add', str(floating_ip) + '/32',
- 'dev', FLAGS.public_interface,
+ 'dev', device,
run_as_root=True, check_exit_code=check_exit_code)
if FLAGS.send_arp_for_ha:
_execute('arping', '-U', floating_ip,
- '-A', '-I', FLAGS.public_interface,
+ '-A', '-I', device,
'-c', 1, run_as_root=True, check_exit_code=False)
-def unbind_floating_ip(floating_ip):
+def unbind_floating_ip(floating_ip, device):
"""Unbind a public ip from public interface."""
_execute('ip', 'addr', 'del', str(floating_ip) + '/32',
- 'dev', FLAGS.public_interface, run_as_root=True)
+ 'dev', device, run_as_root=True)
def ensure_metadata_ip():
diff --git a/nova/network/manager.py b/nova/network/manager.py
index ecea7806b..eff312f5b 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -1,5 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
@@ -94,6 +95,8 @@ flags.DEFINE_integer('network_size', 256,
'Number of addresses in each private subnet')
flags.DEFINE_string('floating_range', '4.4.4.0/24',
'Floating IP address block')
+flags.DEFINE_string('default_floating_pool', 'nova',
+ 'Default pool for floating ips')
flags.DEFINE_string('fixed_range', '10.0.0.0/8', 'Fixed IP address block')
flags.DEFINE_string('fixed_range_v6', 'fd00::/48', 'Fixed IPv6 address block')
flags.DEFINE_string('gateway', None, 'Default IPv4 gateway')
@@ -201,7 +204,9 @@ class FloatingIP(object):
fixed_address = floating_ip['fixed_ip']['address']
# NOTE(vish): The False here is because we ignore the case
# that the ip is already bound.
- self.driver.bind_floating_ip(floating_ip['address'], False)
+ self.driver.bind_floating_ip(floating_ip['address'],
+ floating_ip['interface'],
+ False)
self.driver.ensure_floating_forward(floating_ip['address'],
fixed_address)
@@ -286,8 +291,8 @@ class FloatingIP(object):
'project': context.project_id})
raise exception.NotAuthorized()
- def allocate_floating_ip(self, context, project_id):
- """Gets an floating ip from the pool."""
+ def allocate_floating_ip(self, context, project_id, pool=None):
+ """Gets a floating ip from the pool."""
# NOTE(tr3buchet): all network hosts in zone now use the same pool
LOG.debug("QUOTA: %s" % quota.allowed_floating_ips(context, 1))
if quota.allowed_floating_ips(context, 1) < 1:
@@ -296,9 +301,10 @@ class FloatingIP(object):
context.project_id)
raise exception.QuotaError(_('Address quota exceeded. You cannot '
'allocate any more addresses'))
- # TODO(vish): add floating ips through manage command
+ pool = pool or FLAGS.default_floating_pool
return self.db.floating_ip_allocate_address(context,
- project_id)
+ project_id,
+ pool)
def deallocate_floating_ip(self, context, address,
affect_auto_assigned=False):
@@ -337,7 +343,6 @@ class FloatingIP(object):
# make sure floating ip isn't already associated
if floating_ip['fixed_ip_id']:
- floating_address = floating_ip['address']
raise exception.FloatingIpAssociated(address=floating_address)
fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_address)
@@ -348,20 +353,22 @@ class FloatingIP(object):
host = instance['host']
else:
host = fixed_ip['network']['host']
- LOG.info("%s", self.host)
+ interface = floating_ip['interface']
if host == self.host:
# i'm the correct host
self._associate_floating_ip(context, floating_address,
- fixed_address)
+ fixed_address, interface)
else:
# send to correct host
rpc.cast(context,
self.db.queue_get_for(context, FLAGS.network_topic, host),
{'method': '_associate_floating_ip',
- 'args': {'floating_address': floating_ip['address'],
- 'fixed_address': fixed_ip['address']}})
+ 'args': {'floating_address': floating_address,
+ 'fixed_address': fixed_address,
+ 'interface': interface}})
- def _associate_floating_ip(self, context, floating_address, fixed_address):
+ def _associate_floating_ip(self, context, floating_address, fixed_address,
+ interface):
"""Performs db and driver calls to associate floating ip & fixed ip"""
# associate floating ip
self.db.floating_ip_fixed_ip_associate(context,
@@ -369,7 +376,7 @@ class FloatingIP(object):
fixed_address,
self.host)
# gogo driver time
- self.driver.bind_floating_ip(floating_address)
+ self.driver.bind_floating_ip(floating_address, interface)
self.driver.ensure_floating_forward(floating_address, fixed_address)
def disassociate_floating_ip(self, context, address,
@@ -401,29 +408,36 @@ class FloatingIP(object):
host = instance['host']
else:
host = fixed_ip['network']['host']
+ interface = floating_ip['interface']
if host == self.host:
# i'm the correct host
- self._disassociate_floating_ip(context, address)
+ self._disassociate_floating_ip(context, address, interface)
else:
# send to correct host
rpc.cast(context,
self.db.queue_get_for(context, FLAGS.network_topic, host),
{'method': '_disassociate_floating_ip',
- 'args': {'address': address}})
+ 'args': {'address': address,
+ 'interface': interface}})
- def _disassociate_floating_ip(self, context, address):
+ def _disassociate_floating_ip(self, context, address, interface):
"""Performs db and driver calls to disassociate floating ip"""
# disassociate floating ip
fixed_address = self.db.floating_ip_disassociate(context, address)
# go go driver time
- self.driver.unbind_floating_ip(address)
+ self.driver.unbind_floating_ip(address, interface)
self.driver.remove_floating_forward(address, fixed_address)
def get_floating_ip(self, context, id):
"""Returns a floating IP as a dict"""
return dict(self.db.floating_ip_get(context, id).iteritems())
+ def get_floating_pools(self, context):
+ """Returns list of floating pools"""
+ pools = self.db.floating_ip_get_pools(context)
+ return [dict(pool.iteritems()) for pool in pools]
+
def get_floating_ip_by_address(self, context, address):
"""Returns a floating IP as a dict"""
return dict(self.db.floating_ip_get_by_address(context,
diff --git a/nova/tests/api/ec2/test_cloud.py b/nova/tests/api/ec2/test_cloud.py
index 421c16adb..6015069bc 100644
--- a/nova/tests/api/ec2/test_cloud.py
+++ b/nova/tests/api/ec2/test_cloud.py
@@ -1,5 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
@@ -153,7 +154,7 @@ class CloudTestCase(test.TestCase):
address = "10.10.10.10"
db.floating_ip_create(self.context,
{'address': address,
- 'host': self.network.host})
+ 'pool': 'nova'})
self.cloud.allocate_address(self.context)
self.cloud.describe_addresses(self.context)
self.cloud.release_address(self.context,
@@ -165,7 +166,7 @@ class CloudTestCase(test.TestCase):
allocate = self.cloud.allocate_address
db.floating_ip_create(self.context,
{'address': address,
- 'host': self.network.host})
+ 'pool': 'nova'})
self.assertEqual(allocate(self.context)['publicIp'], address)
db.floating_ip_destroy(self.context, address)
self.assertRaises(exception.NoMoreFloatingIps,
@@ -177,7 +178,7 @@ class CloudTestCase(test.TestCase):
allocate = self.cloud.allocate_address
db.floating_ip_create(self.context,
{'address': address,
- 'host': self.network.host,
+ 'pool': 'nova',
'project_id': self.project_id})
result = self.cloud.release_address(self.context, address)
self.assertEqual(result['releaseResponse'], ['Address released.'])
@@ -185,7 +186,9 @@ class CloudTestCase(test.TestCase):
def test_associate_disassociate_address(self):
"""Verifies associate runs cleanly without raising an exception"""
address = "10.10.10.10"
- db.floating_ip_create(self.context, {'address': address})
+ db.floating_ip_create(self.context,
+ {'address': address,
+ 'pool': 'nova'})
self.cloud.allocate_address(self.context)
# TODO(jkoelker) Probably need to query for instance_type_id and
# make sure we get a valid one
diff --git a/nova/tests/api/openstack/v2/contrib/test_floating_ip_pools.py b/nova/tests/api/openstack/v2/contrib/test_floating_ip_pools.py
new file mode 100644
index 000000000..d061f9af3
--- /dev/null
+++ b/nova/tests/api/openstack/v2/contrib/test_floating_ip_pools.py
@@ -0,0 +1,73 @@
+# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
+# 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.
+
+from lxml import etree
+
+from nova.api.openstack.v2.contrib import floating_ip_pools
+from nova import context
+from nova import network
+from nova import test
+from nova.tests.api.openstack import fakes
+
+
+def fake_get_floating_ip_pools(self, context):
+ return [{'name': 'nova'},
+ {'name': 'other'}]
+
+
+class FloatingIpPoolTest(test.TestCase):
+ def setUp(self):
+ super(FloatingIpPoolTest, self).setUp()
+ self.stubs.Set(network.api.API, "get_floating_ip_pools",
+ fake_get_floating_ip_pools)
+
+ self.context = context.RequestContext('fake', 'fake')
+ self.controller = floating_ip_pools.FloatingIPPoolsController()
+
+ def test_translate_floating_ip_pools_view(self):
+ pools = fake_get_floating_ip_pools(None, self.context)
+ view = floating_ip_pools._translate_floating_ip_pools_view(pools)
+ self.assertTrue('floating_ip_pools' in view)
+ self.assertEqual(view['floating_ip_pools'][0]['name'],
+ pools[0]['name'])
+ self.assertEqual(view['floating_ip_pools'][1]['name'],
+ pools[1]['name'])
+
+ def test_floating_ips_pools_list(self):
+ req = fakes.HTTPRequest.blank('/v2/fake/os-floating-ip-pools')
+ res_dict = self.controller.index(req)
+
+ pools = fake_get_floating_ip_pools(None, self.context)
+ response = {'floating_ip_pools': pools}
+ self.assertEqual(res_dict, response)
+
+
+class FloatingIpPoolSerializerTest(test.TestCase):
+ def test_index_serializer(self):
+ serializer = floating_ip_pools.FloatingIPPoolsSerializer()
+ text = serializer.serialize(dict(
+ floating_ip_pools=[
+ dict(name='nova'),
+ dict(name='other')
+ ]), 'index')
+
+ tree = etree.fromstring(text)
+
+ self.assertEqual('floating_ip_pools', tree.tag)
+ self.assertEqual(2, len(tree))
+ self.assertEqual('floating_ip_pool', tree[0].tag)
+ self.assertEqual('floating_ip_pool', tree[1].tag)
+ self.assertEqual('nova', tree[0].get('name'))
+ self.assertEqual('other', tree[1].get('name'))
diff --git a/nova/tests/api/openstack/v2/contrib/test_floating_ips.py b/nova/tests/api/openstack/v2/contrib/test_floating_ips.py
index d1a0d5795..ffdace53c 100644
--- a/nova/tests/api/openstack/v2/contrib/test_floating_ips.py
+++ b/nova/tests/api/openstack/v2/contrib/test_floating_ips.py
@@ -1,3 +1,4 @@
+# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
# Copyright 2011 Eldar Nugaev
# All Rights Reserved.
#
@@ -30,11 +31,13 @@ FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
def network_api_get_floating_ip(self, context, id):
return {'id': 1, 'address': '10.10.10.10',
+ 'pool': 'nova',
'fixed_ip': None}
def network_api_get_floating_ip_by_address(self, context, address):
return {'id': 1, 'address': '10.10.10.10',
+ 'pool': 'nova',
'fixed_ip': {'address': '10.0.0.1',
'instance': {'uuid': FAKE_UUID}}}
@@ -42,9 +45,11 @@ def network_api_get_floating_ip_by_address(self, context, address):
def network_api_get_floating_ips_by_project(self, context):
return [{'id': 1,
'address': '10.10.10.10',
+ 'pool': 'nova',
'fixed_ip': {'address': '10.0.0.1',
'instance': {'uuid': FAKE_UUID}}},
{'id': 2,
+ 'pool': 'nova', 'interface': 'eth0',
'address': '10.10.10.11'}]
@@ -107,6 +112,7 @@ class FloatingIpTest(test.TestCase):
host = "fake_host"
return db.floating_ip_create(self.context,
{'address': self.floating_ip,
+ 'pool': 'nova',
'host': host})
def _delete_floating_ip(self):
@@ -151,7 +157,8 @@ class FloatingIpTest(test.TestCase):
self.assertEqual(view['floating_ip']['instance_id'], None)
def test_translate_floating_ip_view_dict(self):
- floating_ip = {'id': 0, 'address': '10.0.0.10', 'fixed_ip': None}
+ floating_ip = {'id': 0, 'address': '10.0.0.10', 'pool': 'nova',
+ 'fixed_ip': None}
view = floating_ips._translate_floating_ip_view(floating_ip)
self.assertTrue('floating_ip' in view)
@@ -161,10 +168,12 @@ class FloatingIpTest(test.TestCase):
response = {'floating_ips': [{'instance_id': FAKE_UUID,
'ip': '10.10.10.10',
+ 'pool': 'nova',
'fixed_ip': '10.0.0.1',
'id': 1},
{'instance_id': None,
'ip': '10.10.10.11',
+ 'pool': 'nova',
'fixed_ip': None,
'id': 2}]}
self.assertEqual(res_dict, response)
@@ -180,6 +189,7 @@ class FloatingIpTest(test.TestCase):
def test_show_associated_floating_ip(self):
def get_floating_ip(self, context, id):
return {'id': 1, 'address': '10.10.10.10',
+ 'pool': 'nova',
'fixed_ip': {'address': '10.0.0.1',
'instance': {'uuid': FAKE_UUID}}}
self.stubs.Set(network.api.API, "get_floating_ip", get_floating_ip)
@@ -207,7 +217,7 @@ class FloatingIpTest(test.TestCase):
pass
def fake2(*args, **kwargs):
- return {'id': 1, 'address': '10.10.10.10'}
+ return {'id': 1, 'address': '10.10.10.10', 'pool': 'nova'}
self.stubs.Set(network.api.API, "allocate_floating_ip",
fake1)
@@ -223,7 +233,8 @@ class FloatingIpTest(test.TestCase):
"id": 1,
"instance_id": None,
"ip": "10.10.10.10",
- "fixed_ip": None}
+ "fixed_ip": None,
+ "pool": 'nova'}
self.assertEqual(ip, expected)
def test_floating_ip_release(self):
diff --git a/nova/tests/api/openstack/v2/test_extensions.py b/nova/tests/api/openstack/v2/test_extensions.py
index 51af89da3..70a936727 100644
--- a/nova/tests/api/openstack/v2/test_extensions.py
+++ b/nova/tests/api/openstack/v2/test_extensions.py
@@ -1,5 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
#
@@ -110,6 +111,7 @@ class ExtensionControllerTest(ExtensionTestCase):
"FlavorExtraData",
"Floating_ips",
"Floating_ip_dns",
+ "Floating_ip_pools",
"Fox In Socks",
"Hosts",
"Keypairs",
diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py
index a28d6ded0..ef3162eb4 100644
--- a/nova/tests/db/fakes.py
+++ b/nova/tests/db/fakes.py
@@ -1,5 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
# Copyright 2010 OpenStack, LLC
# All Rights Reserved.
#
@@ -88,6 +89,7 @@ def stub_out_db_network_api(stubs):
'fixed_ip_id': None,
'fixed_ip': None,
'project_id': None,
+ 'pool': 'nova',
'auto_assigned': False}
virtual_interface_fields = {'id': 0,
@@ -101,9 +103,10 @@ def stub_out_db_network_api(stubs):
virtual_interfacees = [virtual_interface_fields]
networks = [network_fields]
- def fake_floating_ip_allocate_address(context, project_id):
+ def fake_floating_ip_allocate_address(context, project_id, pool):
ips = filter(lambda i: i['fixed_ip_id'] is None \
- and i['project_id'] is None,
+ and i['project_id'] is None \
+ and i['pool'] == pool,
floating_ips)
if not ips:
raise exception.NoMoreFloatingIps()
diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py
index 4eee3c8d4..274d5dd86 100644
--- a/nova/tests/test_network.py
+++ b/nova/tests/test_network.py
@@ -1,6 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Rackspace
+# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -108,6 +109,8 @@ flavor = {'id': 0,
floating_ip_fields = {'id': 0,
'address': '192.168.10.100',
+ 'pool': 'nova',
+ 'interface': 'eth0',
'fixed_ip_id': 0,
'project_id': None,
'auto_assigned': False}
@@ -580,21 +583,29 @@ class VlanNetworkTestCase(test.TestCase):
# floating ip that's already associated
def fake2(*args, **kwargs):
return {'address': '10.0.0.1',
+ 'pool': 'nova',
+ 'interface': 'eth0',
'fixed_ip_id': 1}
# floating ip that isn't associated
def fake3(*args, **kwargs):
return {'address': '10.0.0.1',
+ 'pool': 'nova',
+ 'interface': 'eth0',
'fixed_ip_id': None}
# fixed ip with remote host
def fake4(*args, **kwargs):
return {'address': '10.0.0.1',
+ 'pool': 'nova',
+ 'interface': 'eth0',
'network': {'multi_host': False, 'host': 'jibberjabber'}}
# fixed ip with local host
def fake5(*args, **kwargs):
return {'address': '10.0.0.1',
+ 'pool': 'nova',
+ 'interface': 'eth0',
'network': {'multi_host': False, 'host': 'testhost'}}
def fake6(*args, **kwargs):
@@ -641,21 +652,29 @@ class VlanNetworkTestCase(test.TestCase):
# floating ip that isn't associated
def fake2(*args, **kwargs):
return {'address': '10.0.0.1',
+ 'pool': 'nova',
+ 'interface': 'eth0',
'fixed_ip_id': None}
# floating ip that is associated
def fake3(*args, **kwargs):
return {'address': '10.0.0.1',
+ 'pool': 'nova',
+ 'interface': 'eth0',
'fixed_ip_id': 1}
# fixed ip with remote host
def fake4(*args, **kwargs):
return {'address': '10.0.0.1',
+ 'pool': 'nova',
+ 'interface': 'eth0',
'network': {'multi_host': False, 'host': 'jibberjabber'}}
# fixed ip with local host
def fake5(*args, **kwargs):
return {'address': '10.0.0.1',
+ 'pool': 'nova',
+ 'interface': 'eth0',
'network': {'multi_host': False, 'host': 'testhost'}}
def fake6(*args, **kwargs):