From ca3f3cc1aa5be2a04120a1a4df2c1e79be927fa5 Mon Sep 17 00:00:00 2001 From: Kevin Benton Date: Fri, 14 Feb 2014 12:25:40 +0000 Subject: [PATCH] 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 --- neutron/plugins/bigswitch/plugin.py | 19 +++++- neutron/plugins/bigswitch/servermanager.py | 45 ++++++++++++- .../tests/unit/bigswitch/test_capabilities.py | 65 +++++++++++++++++++ 3 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 neutron/tests/unit/bigswitch/test_capabilities.py diff --git a/neutron/plugins/bigswitch/plugin.py b/neutron/plugins/bigswitch/plugin.py index a80385d4f..af9d0feaa 100644 --- a/neutron/plugins/bigswitch/plugin.py +++ b/neutron/plugins/bigswitch/plugin.py @@ -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")) diff --git a/neutron/plugins/bigswitch/servermanager.py b/neutron/plugins/bigswitch/servermanager.py index 73650833e..df2caef0e 100644 --- a/neutron/plugins/bigswitch/servermanager.py +++ b/neutron/plugins/bigswitch/servermanager.py @@ -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) diff --git a/neutron/tests/unit/bigswitch/test_capabilities.py b/neutron/tests/unit/bigswitch/test_capabilities.py new file mode 100644 index 000000000..8b9458631 --- /dev/null +++ b/neutron/tests/unit/bigswitch/test_capabilities.py @@ -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) -- 2.45.2