BigSwitch: Use backend floating IP endpoint

Adds new floating IP REST calls for backend
controllers that support it. Adds backend
capability discovery mechanism.

Implements: blueprint bsn-floating-ip-endpoints
Change-Id: I2301d62a05d256867255865556625603918e84cf
This commit is contained in:
Kevin Benton 2014-02-14 12:25:40 +00:00
parent a872951552
commit 6047d27013
3 changed files with 125 additions and 4 deletions

View File

@ -842,7 +842,11 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
# create floatingip on the network controller
try:
self._send_floatingip_update(context)
if 'floatingip' in self.servers.get_capabilities():
self.servers.rest_create_floatingip(
new_fl_ip['tenant_id'], new_fl_ip)
else:
self._send_floatingip_update(context)
except servermanager.RemoteRestError as e:
with excutils.save_and_reraise_exception():
LOG.error(
@ -860,7 +864,11 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
self).update_floatingip(context, id, floatingip)
# update network on network controller
self._send_floatingip_update(context)
if 'floatingip' in self.servers.get_capabilities():
self.servers.rest_update_floatingip(new_fl_ip['tenant_id'],
new_fl_ip, id)
else:
self._send_floatingip_update(context)
return new_fl_ip
def delete_floatingip(self, context, id):
@ -868,10 +876,15 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
with context.session.begin(subtransactions=True):
# delete floating IP in DB
old_fip = super(NeutronRestProxyV2, self).get_floatingip(context,
id)
super(NeutronRestProxyV2, self).delete_floatingip(context, id)
# update network on network controller
self._send_floatingip_update(context)
if 'floatingip' in self.servers.get_capabilities():
self.servers.rest_delete_floatingip(old_fip['tenant_id'], id)
else:
self._send_floatingip_update(context)
def disassociate_floatingips(self, context, port_id):
LOG.debug(_("NeutronRestProxyV2: diassociate_floatingips() called"))

View File

@ -45,11 +45,13 @@ from neutron.openstack.common import log as logging
LOG = logging.getLogger(__name__)
# The following are used to invoke the API on the external controller
CAPABILITIES_PATH = "/capabilities"
NET_RESOURCE_PATH = "/tenants/%s/networks"
PORT_RESOURCE_PATH = "/tenants/%s/networks/%s/ports"
ROUTER_RESOURCE_PATH = "/tenants/%s/routers"
ROUTER_INTF_OP_PATH = "/tenants/%s/routers/%s/interfaces"
NETWORKS_PATH = "/tenants/%s/networks/%s"
FLOATINGIPS_PATH = "/tenants/%s/floatingips/%s"
PORTS_PATH = "/tenants/%s/networks/%s/ports/%s"
ATTACHMENT_PATH = "/tenants/%s/networks/%s/ports/%s/attachment"
ROUTERS_PATH = "/tenants/%s/routers/%s"
@ -81,10 +83,23 @@ class ServerProxy(object):
self.auth = None
self.neutron_id = neutron_id
self.failed = False
self.capabilities = []
if auth:
self.auth = 'Basic ' + base64.encodestring(auth).strip()
def rest_call(self, action, resource, data, headers):
def get_capabilities(self):
try:
body = self.rest_call('GET', CAPABILITIES_PATH)[3]
self.capabilities = json.loads(body)
except Exception:
LOG.error(_("Couldn't retrieve capabilities. "
"Newer API calls won't be supported."))
LOG.info(_("The following capabilities were received "
"for %(server)s: %(cap)s"), {'server': self.server,
'cap': self.capabilities})
return self.capabilities
def rest_call(self, action, resource, data='', headers=None):
uri = self.base_uri + resource
body = json.dumps(data)
if not headers:
@ -180,6 +195,19 @@ class ServerPool(object):
]
LOG.debug(_("ServerPool: initialization done"))
def get_capabilities(self):
# lookup on first try
try:
return self.capabilities
except AttributeError:
# each server should return a list of capabilities it supports
# e.g. ['floatingip']
capabilities = [set(server.get_capabilities())
for server in self.servers]
# Pool only supports what all of the servers support
self.capabilities = set.intersection(*capabilities)
return self.capabilities
def server_proxy_for(self, server, port):
return ServerProxy(server, port, self.ssl, self.auth, self.neutron_id,
self.timeout, self.base_uri, self.name)
@ -318,3 +346,18 @@ class ServerPool(object):
# Controller has no update operation for the port endpoint
# the create PUT method will replace
self.rest_create_port(tenant_id, net_id, port)
def rest_create_floatingip(self, tenant_id, floatingip):
resource = FLOATINGIPS_PATH % (tenant_id, floatingip['id'])
errstr = _("Unable to create floating IP: %s")
self.rest_action('PUT', resource, errstr=errstr)
def rest_update_floatingip(self, tenant_id, floatingip, oldid):
resource = FLOATINGIPS_PATH % (tenant_id, oldid)
errstr = _("Unable to update floating IP: %s")
self.rest_action('PUT', resource, errstr=errstr)
def rest_delete_floatingip(self, tenant_id, oldid):
resource = FLOATINGIPS_PATH % (tenant_id, oldid)
errstr = _("Unable to delete floating IP: %s")
self.rest_action('DELETE', resource, errstr=errstr)

View File

@ -0,0 +1,65 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 Big Switch Networks, 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.
#
# @author Kevin Benton
from contextlib import nested
import mock
from neutron.tests.unit.bigswitch import test_router_db
PLUGIN = 'neutron.plugins.bigswitch.plugin'
SERVERMANAGER = PLUGIN + '.servermanager'
SERVERPOOL = SERVERMANAGER + '.ServerPool'
SERVERRESTCALL = SERVERMANAGER + '.ServerProxy.rest_call'
class CapabilitiesTests(test_router_db.RouterDBTestCase):
def test_floating_ip_capability(self):
with nested(
mock.patch(SERVERRESTCALL,
return_value=(200, None, None, '["floatingip"]')),
mock.patch(SERVERPOOL + '.rest_create_floatingip',
return_value=(200, None, None, None)),
mock.patch(SERVERPOOL + '.rest_delete_floatingip',
return_value=(200, None, None, None))
) as (mock_rest, mock_create, mock_delete):
with self.floatingip_with_assoc() as fip:
pass
mock_create.assert_has_calls(
[mock.call(fip['floatingip']['tenant_id'], fip['floatingip'])]
)
mock_delete.assert_has_calls(
[mock.call(fip['floatingip']['tenant_id'],
fip['floatingip']['id'])]
)
def test_floating_ip_capability_neg(self):
with nested(
mock.patch(SERVERRESTCALL,
return_value=(200, None, None, '[""]')),
mock.patch(SERVERPOOL + '.rest_update_network',
return_value=(200, None, None, None))
) as (mock_rest, mock_netupdate):
with self.floatingip_with_assoc() as fip:
pass
updates = [call[0][2]['floatingips']
for call in mock_netupdate.call_args_list]
all_floats = [f['floating_ip_address']
for floats in updates for f in floats]
self.assertIn(fip['floatingip']['floating_ip_address'], all_floats)