]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
BigSwitch: Use backend floating IP endpoint
authorKevin Benton <blak111@gmail.com>
Fri, 14 Feb 2014 12:25:40 +0000 (12:25 +0000)
committerKevin Benton <blak111@gmail.com>
Sun, 23 Feb 2014 19:56:45 +0000 (19:56 +0000)
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
neutron/plugins/bigswitch/servermanager.py
neutron/tests/unit/bigswitch/test_capabilities.py [new file with mode: 0644]

index a80385d4f86d002b873d2bd00690c9d1a9addff9..af9d0feaab92d0b2620589b7bc28825fc775028c 100644 (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"))
index 73650833eb43f74e377f5feb9c4f4a4bee32beb8..df2caef0e55d633cc30a3d0bc885688100c53bb0 100644 (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)
diff --git a/neutron/tests/unit/bigswitch/test_capabilities.py b/neutron/tests/unit/bigswitch/test_capabilities.py
new file mode 100644 (file)
index 0000000..8b94586
--- /dev/null
@@ -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)