Allow a floating IP to be associated to a specific fixed IP

The current floating IP API extension only accepts the instance ID
and the floating address to be assigned. Where the instance is
connected to more than one network the behaviour is to associated
the floating IP with the first fixed IP of the instances
(and to log a warning)

This change introduces a new extension which wehn loaded adds a
fixed IP address as an optional parameter, allowing the floating
IP to be associated with a specific fixed IP.

Without this extension, or without the optional parameters,
the API behaviour is unchanged.

If specified the fixed IP must be associated with the instance.

DocImpact

Implements blueprint multi-nic-floating-ip-assignment

Change-Id: I9241137ad794cdf7f452ed84e9445f0e11fdd44e
This commit is contained in:
Phil Day 2013-04-04 18:00:49 +01:00
parent b17715174e
commit 1b2c121f13
31 changed files with 311 additions and 14 deletions

View File

@ -232,6 +232,14 @@
"namespace": "http://docs.openstack.org/compute/ext/evacuate/api/v2",
"updated": "2013-01-06T00:00:00+00:00"
},
{
"alias": "os-extended-floating-ips",
"description": "Adds optional fixed_address to the add floating IP command.",
"links": [],
"name": "ExtendedFloatingIps",
"namespace": "http://docs.openstack.org/compute/ext/extended_floating_ips/api/v2",
"updated": "2013-04-19T00:00:00+00:00"
},
{
"alias": "os-fixed-ips",
"description": "Fixed IPs support.",

View File

@ -103,6 +103,9 @@
<extension alias="os-evacuate" updated="2013-01-06T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/evacuate/api/v2" name="Evacuate">
<description>Enables server evacuation.</description>
</extension>
<extension alias="os-extended-floating-ips" updated="2013-04-19T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/extended_floating_ips/api/v2" name="ExtendedFloatingIps">
<description>Adds optional fixed_address to the add floating IP command.</description>
</extension>
<extension alias="os-fixed-ips" updated="2012-10-18T13:25:27-06:00" namespace="http://docs.openstack.org/compute/ext/fixed_ips/api/v2" name="FixedIPs">
<description>Fixed IPs support.</description>
</extension>

View File

@ -0,0 +1,3 @@
{
"pool": "nova"
}

View File

@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<pool>nova</pool>

View File

@ -0,0 +1,9 @@
{
"floating_ip": {
"fixed_ip": null,
"id": 1,
"instance_id": null,
"ip": "10.10.10.1",
"pool": "nova"
}
}

View File

@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<floating_ip instance_id="None" ip="10.10.10.1" fixed_ip="None" id="1" pool="nova"/>

View File

@ -0,0 +1,3 @@
{
"floating_ips": []
}

View File

@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<floating_ips/>

View File

@ -0,0 +1,18 @@
{
"floating_ips": [
{
"fixed_ip": null,
"id": 1,
"instance_id": null,
"ip": "10.10.10.1",
"pool": "nova"
},
{
"fixed_ip": null,
"id": 2,
"instance_id": null,
"ip": "10.10.10.2",
"pool": "nova"
}
]
}

View File

@ -0,0 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<floating_ips>
<floating_ip instance_id="None" ip="10.10.10.1" fixed_ip="None" id="1" pool="nova"/>
<floating_ip instance_id="None" ip="10.10.10.2" fixed_ip="None" id="2" pool="nova"/>
</floating_ips>

View File

@ -0,0 +1,27 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 OpenStack Foundation
#
# 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 extensions
class Extended_floating_ips(extensions.ExtensionDescriptor):
"""Adds optional fixed_address to the add floating IP command."""
name = "ExtendedFloatingIps"
alias = "os-extended-floating-ips"
namespace = ("http://docs.openstack.org/compute/ext/"
"extended_floating_ips/api/v2")
updated = "2013-04-19T00:00:00+00:00"

View File

@ -198,10 +198,11 @@ class FloatingIPController(object):
class FloatingIPActionController(wsgi.Controller):
def __init__(self, *args, **kwargs):
def __init__(self, ext_mgr=None, *args, **kwargs):
super(FloatingIPActionController, self).__init__(*args, **kwargs)
self.compute_api = compute.API()
self.network_api = network.API()
self.ext_mgr = ext_mgr
@wsgi.action('addFloatingIp')
def _add_floating_ip(self, req, id, body):
@ -230,18 +231,27 @@ class FloatingIPActionController(wsgi.Controller):
msg = _('No fixed ips associated to instance')
raise webob.exc.HTTPBadRequest(explanation=msg)
# TODO(tr3buchet): this will associate the floating IP with the
# first fixed_ip an instance has. This should be
# changed to support specifying a particular fixed_ip if
# multiple exist.
if len(fixed_ips) > 1:
msg = _('multiple fixed_ips exist, using the first: %s')
LOG.warning(msg, fixed_ips[0]['address'])
fixed_address = None
if self.ext_mgr.is_loaded('os-extended-floating-ips'):
if 'fixed_address' in body['addFloatingIp']:
fixed_address = body['addFloatingIp']['fixed_address']
for fixed in fixed_ips:
if fixed['address'] == fixed_address:
break
else:
msg = _('Specified fixed address not assigned to instance')
raise webob.exc.HTTPBadRequest(explanation=msg)
if not fixed_address:
fixed_address = fixed_ips[0]['address']
if len(fixed_ips) > 1:
msg = _('multiple fixed_ips exist, using the first: %s')
LOG.warning(msg, fixed_address)
try:
self.network_api.associate_floating_ip(context, instance,
floating_address=address,
fixed_address=fixed_ips[0]['address'])
fixed_address=fixed_address)
except exception.FloatingIpAssociated:
msg = _('floating ip is already associated')
raise webob.exc.HTTPBadRequest(explanation=msg)
@ -318,6 +328,6 @@ class Floating_ips(extensions.ExtensionDescriptor):
return resources
def get_controller_extensions(self):
controller = FloatingIPActionController()
controller = FloatingIPActionController(self.ext_mgr)
extension = extensions.ControllerExtension(self, 'servers', controller)
return [extension]

View File

@ -20,6 +20,7 @@ from lxml import etree
import webob
from nova.api.openstack.compute.contrib import floating_ips
from nova.api.openstack import extensions
from nova import compute
from nova.compute import utils as compute_utils
from nova import context
@ -154,8 +155,10 @@ class FloatingIpTest(test.TestCase):
self.context = context.get_admin_context()
self._create_floating_ips()
self.ext_mgr = extensions.ExtensionManager()
self.ext_mgr.extensions = {}
self.controller = floating_ips.FloatingIPController()
self.manager = floating_ips.FloatingIPActionController()
self.manager = floating_ips.FloatingIPActionController(self.ext_mgr)
def tearDown(self):
self._delete_floating_ip()
@ -315,8 +318,10 @@ class FloatingIpTest(test.TestCase):
self.controller.delete(req, 1)
def test_floating_ip_associate(self):
fixed_address = '192.168.1.100'
def fake_associate_floating_ip(*args, **kwargs):
pass
self.assertEqual(fixed_address, kwargs['fixed_address'])
self.stubs.Set(network.api.API, "associate_floating_ip",
fake_associate_floating_ip)
@ -326,8 +331,26 @@ class FloatingIpTest(test.TestCase):
rsp = self.manager._add_floating_ip(req, 'test_inst', body)
self.assertTrue(rsp.status_int == 202)
def test_associate_not_allocated_floating_ip_to_instance(self):
def test_not_extended_floating_ip_associate_fixed(self):
# Check that fixed_address is ignored if os-extended-floating-ips
# is not loaded
fixed_address_requested = '192.168.1.101'
fixed_address_allocated = '192.168.1.100'
def fake_associate_floating_ip(*args, **kwargs):
self.assertEqual(fixed_address_allocated,
kwargs['fixed_address'])
self.stubs.Set(network.api.API, "associate_floating_ip",
fake_associate_floating_ip)
body = dict(addFloatingIp=dict(address=self.floating_ip,
fixed_address=fixed_address_requested))
req = fakes.HTTPRequest.blank('/v2/fake/servers/test_inst/action')
rsp = self.manager._add_floating_ip(req, 'test_inst', body)
self.assertTrue(rsp.status_int == 202)
def test_associate_not_allocated_floating_ip_to_instance(self):
def fake_associate_floating_ip(self, context, instance,
floating_address, fixed_address,
affect_auto_assigned=False):
@ -510,6 +533,106 @@ class FloatingIpTest(test.TestCase):
body)
class ExtendedFloatingIpTest(test.TestCase):
floating_ip = "10.10.10.10"
floating_ip_2 = "10.10.10.11"
def _create_floating_ips(self, floating_ips=None):
"""Create a floating ip object."""
if floating_ips is None:
floating_ips = [self.floating_ip]
elif not isinstance(floating_ips, (list, tuple)):
floating_ips = [floating_ips]
def make_ip_dict(ip):
"""Shortcut for creating floating ip dict."""
return
dict_ = {'pool': 'nova', 'host': 'fake_host'}
return db.floating_ip_bulk_create(
self.context, [dict(address=ip, **dict_) for ip in floating_ips],
)
def _delete_floating_ip(self):
db.floating_ip_destroy(self.context, self.floating_ip)
def setUp(self):
super(ExtendedFloatingIpTest, self).setUp()
self.stubs.Set(compute.api.API, "get",
compute_api_get)
self.stubs.Set(network.api.API, "get_floating_ip",
network_api_get_floating_ip)
self.stubs.Set(network.api.API, "get_floating_ip_by_address",
network_api_get_floating_ip_by_address)
self.stubs.Set(network.api.API, "get_floating_ips_by_project",
network_api_get_floating_ips_by_project)
self.stubs.Set(network.api.API, "release_floating_ip",
network_api_release)
self.stubs.Set(network.api.API, "disassociate_floating_ip",
network_api_disassociate)
self.stubs.Set(network.api.API, "get_instance_id_by_floating_address",
get_instance_by_floating_ip_addr)
self.stubs.Set(compute_utils, "get_nw_info_for_instance",
stub_nw_info(self.stubs))
self.flags(
osapi_compute_extension=[
'nova.api.openstack.compute.contrib.select_extensions'],
osapi_compute_ext_list=['Floating_ips', 'Extended_floating_ips'])
fake_network.stub_out_nw_api_get_instance_nw_info(self.stubs,
spectacular=True)
self.stubs.Set(db, 'instance_get',
fake_instance_get)
self.context = context.get_admin_context()
self._create_floating_ips()
self.ext_mgr = extensions.ExtensionManager()
self.ext_mgr.extensions = {}
self.ext_mgr.extensions['os-floating-ips'] = True
self.ext_mgr.extensions['os-extended-floating-ips'] = True
self.controller = floating_ips.FloatingIPController()
self.manager = floating_ips.FloatingIPActionController(self.ext_mgr)
def tearDown(self):
self._delete_floating_ip()
super(ExtendedFloatingIpTest, self).tearDown()
def test_extended_floating_ip_associate_fixed(self):
fixed_address = '192.168.1.101'
def fake_associate_floating_ip(*args, **kwargs):
self.assertEqual(fixed_address, kwargs['fixed_address'])
self.stubs.Set(network.api.API, "associate_floating_ip",
fake_associate_floating_ip)
body = dict(addFloatingIp=dict(address=self.floating_ip,
fixed_address=fixed_address))
req = fakes.HTTPRequest.blank('/v2/fake/servers/test_inst/action')
rsp = self.manager._add_floating_ip(req, 'test_inst', body)
self.assertTrue(rsp.status_int == 202)
def test_extended_floating_ip_associate_fixed_not_allocated(self):
def fake_associate_floating_ip(*args, **kwargs):
pass
self.stubs.Set(network.api.API, "associate_floating_ip",
fake_associate_floating_ip)
body = dict(addFloatingIp=dict(address=self.floating_ip,
fixed_address='11.11.11.11'))
req = webob.Request.blank('/v2/fake/servers/test_inst/action')
req.method = "POST"
req.body = jsonutils.dumps(body)
req.headers["content-type"] = "application/json"
resp = req.get_response(fakes.wsgi_app(init_only=('servers',)))
res_dict = jsonutils.loads(resp.body)
self.assertEqual(resp.status_int, 400)
self.assertEqual(res_dict['badRequest']['message'],
"Specified fixed address not assigned to instance")
class FloatingIpSerializerTest(test.TestCase):
def test_default_serializer(self):
serializer = floating_ips.FloatingIPTemplate()

View File

@ -167,6 +167,7 @@ class ExtensionControllerTest(ExtensionTestCase):
"DeferredDelete",
"DiskConfig",
"ExtendedAvailabilityZone",
"ExtendedFloatingIps",
"ExtendedIps",
"ExtendedIpsMac",
"ExtendedVIFNet",

View File

@ -232,6 +232,14 @@
"namespace": "http://docs.openstack.org/compute/ext/evacuate/api/v2",
"updated": "%(timestamp)s"
},
{
"alias": "os-extended-floating-ips",
"description": "%(text)s",
"links": [],
"name": "ExtendedFloatingIps",
"namespace": "http://docs.openstack.org/compute/ext/extended_floating_ips/api/v2",
"updated": "%(timestamp)s"
},
{
"alias": "os-fixed-ips",
"description": "Fixed IPs support.",

View File

@ -87,6 +87,9 @@
<extension alias="os-evacuate" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/evacuate/api/v2" name="Evacuate">
<description>%(text)s</description>
</extension>
<extension alias="os-extended-floating-ips" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/extended_floating_ips/api/v2" name="ExtendedFloatingIps">
<description>%(text)s</description>
</extension>
<extension alias="os-fixed-ips" name="FixedIPs" namespace="http://docs.openstack.org/compute/ext/fixed_ips/api/v2" updated="2012-10-18T13:25:27-06:00">
<description>Fixed IPs support.</description>
</extension>

View File

@ -0,0 +1,3 @@
{
"pool": "%(pool)s"
}

View File

@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<pool>%(pool)s</pool>

View File

@ -0,0 +1,9 @@
{
"floating_ip": {
"fixed_ip": null,
"id": 1,
"instance_id": null,
"ip": "10.10.10.1",
"pool": "nova"
}
}

View File

@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<floating_ip instance_id="None" ip="10.10.10.1" fixed_ip="None" id="1" pool="nova"/>

View File

@ -0,0 +1,9 @@
{
"floating_ip": {
"fixed_ip": null,
"id": 1,
"instance_id": null,
"ip": "10.10.10.1",
"pool": "nova"
}
}

View File

@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<floating_ip instance_id="None" ip="10.10.10.1" fixed_ip="None" id="1" pool="nova"/>

View File

@ -0,0 +1,3 @@
{
"floating_ips": []
}

View File

@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<floating_ips/>

View File

@ -0,0 +1,19 @@
{
"floating_ips": [
{
"fixed_ip": null,
"id": 1,
"instance_id": null,
"ip": "10.10.10.1",
"pool": "nova"
},
{
"fixed_ip": null,
"id": 2,
"instance_id": null,
"ip": "10.10.10.2",
"pool": "nova"
}
]
}

View File

@ -0,0 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<floating_ips>
<floating_ip instance_id="None" ip="10.10.10.1" fixed_ip="None" id="1" pool="nova"/>
<floating_ip instance_id="None" ip="10.10.10.2" fixed_ip="None" id="2" pool="nova"/>
</floating_ips>

View File

@ -83,12 +83,15 @@ class ApiSampleTestBase(integrated_helpers._IntegratedTestBase):
extension_name = None
def setUp(self):
extends = []
self.flags(use_ipv6=False,
osapi_compute_link_prefix=self._get_host(),
osapi_glance_link_prefix=self._get_glance_host())
if not self.all_extensions:
if hasattr(self, 'extends_name'):
extends = [self.extends_name]
ext = [self.extension_name] if self.extension_name else []
self.flags(osapi_compute_extension=ext)
self.flags(osapi_compute_extension=ext + extends)
super(ApiSampleTestBase, self).setUp()
fake_network.stub_compute_with_ips(self.stubs)
self.generate_samples = os.getenv('GENERATE_SAMPLES') is not None
@ -1351,10 +1354,21 @@ class FloatingIpsJsonTest(ApiSampleTestBase):
self.assertEqual(response.status, 202)
class ExtendedFloatingIpsJsonTest(FloatingIpsJsonTest):
extends_name = ("nova.api.openstack.compute.contrib."
"floating_ips.Floating_ips")
extension_name = ("nova.api.openstack.compute.contrib."
"extended_floating_ips.Extended_floating_ips")
class FloatingIpsXmlTest(FloatingIpsJsonTest):
ctype = 'xml'
class ExtendedFloatingIpsXmlTest(ExtendedFloatingIpsJsonTest):
ctype = 'xml'
class FloatingIpsBulkJsonTest(ApiSampleTestBase):
extension_name = "nova.api.openstack.compute.contrib." \
"floating_ips_bulk.Floating_ips_bulk"