From 4e3fb3191927e07b37fe0937102dc17334e906d1 Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Wed, 21 Mar 2018 22:39:24 +0000 Subject: [PATCH] Introduce floating IP pool resource Add support for listing floating ip pools (subnets). A new API resource ``floatingip-pools`` is introduced. This API endpoint can return a list floating ip pools which are essentially mappings between network UUIDs and subnet CIDRs. Users can use this API to find out the pool to create the floating IPs. Related patches: * neutron-lib: https://review.openstack.org/#/c/556674/ * tempest-plugin: https://review.openstack.org/#/c/562038/ APIImpact add floatingip pools api Change-Id: Iaa995630645042520df67d95271e14f11ffcff8c Partial-Bug: #1653932 --- etc/policy.json | 1 + neutron/db/l3_fip_pools_db.py | 77 +++++++++ neutron/extensions/floatingip_pools.py | 53 ++++++ .../services/l3_router/l3_router_plugin.py | 6 +- .../tests/contrib/hooks/api_all_extensions | 1 + neutron/tests/etc/policy.json | 1 + .../unit/extensions/test_floatingip_pools.py | 155 ++++++++++++++++++ ...-floatingip-pool-api-6927362ef87fdbe5.yaml | 8 + 8 files changed, 300 insertions(+), 2 deletions(-) create mode 100644 neutron/db/l3_fip_pools_db.py create mode 100644 neutron/extensions/floatingip_pools.py create mode 100644 neutron/tests/unit/extensions/test_floatingip_pools.py create mode 100644 releasenotes/notes/add-floatingip-pool-api-6927362ef87fdbe5.yaml diff --git a/etc/policy.json b/etc/policy.json index a1cc26efe98..c517996149e 100644 --- a/etc/policy.json +++ b/etc/policy.json @@ -150,6 +150,7 @@ "create_floatingip": "rule:regular_user", "create_floatingip:floating_ip_address": "rule:admin_only", "get_floatingip": "rule:admin_or_owner", + "get_floatingip_pool": "rule:regular_user", "update_floatingip": "rule:admin_or_owner", "delete_floatingip": "rule:admin_or_owner", diff --git a/neutron/db/l3_fip_pools_db.py b/neutron/db/l3_fip_pools_db.py new file mode 100644 index 00000000000..035f867fa56 --- /dev/null +++ b/neutron/db/l3_fip_pools_db.py @@ -0,0 +1,77 @@ +# 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 neutron_lib.api.definitions import fip64 +from neutron_lib.api import extensions +from neutron_lib import constants as lib_const +from neutron_lib.db import utils as lib_db_utils +from neutron_lib.plugins import directory + +from neutron.extensions import floatingip_pools as fip_pools_ext +from neutron.objects import base as base_obj +from neutron.objects import network as net_obj +from neutron.objects import subnet as subnet_obj + + +class FloatingIPPoolsDbMixin(object): + """Class to support floating IP pool.""" + + _is_v6_supported = None + + @staticmethod + def _make_floatingip_pool_dict(context, subnet, fields=None): + res = {'subnet_id': subnet.id, + 'subnet_name': subnet.name, + 'tenant_id': context.tenant_id, + 'network_id': subnet.network_id, + 'cidr': str(subnet.cidr)} + + return lib_db_utils.resource_fields(res, fields) + + def get_floatingip_pools(self, context, filters=None, fields=None, + sorts=None, limit=None, marker=None, + page_reverse=False): + """Return information for available floating IP pools""" + pager = base_obj.Pager(sorts, limit, page_reverse, marker) + net_ids = [n.network_id + for n in net_obj.ExternalNetwork.get_objects(context)] + # NOTE(hongbin): Use elevated context to make sure we have enough + # permission to retrieve subnets that are not in current tenant + # but belongs to external networks shared with current tenant. + admin_context = context.elevated() + subnet_objs = subnet_obj.Subnet.get_objects(admin_context, + _pager=pager, + network_id=net_ids) + return [self._make_floatingip_pool_dict(context, obj, fields) + for obj in subnet_objs + if (obj.ip_version == lib_const.IP_VERSION_4 or + self.is_v6_supported)] + + @property + def is_v6_supported(self): + supported = self._is_v6_supported + if supported is None: + supported = False + for plugin in directory.get_plugins().values(): + if extensions.is_extension_supported(plugin, fip64.ALIAS): + supported = True + break + self._is_v6_supported = supported + + return supported + + +class FloatingIPPoolsMixin(FloatingIPPoolsDbMixin, + fip_pools_ext.FloatingIPPoolPluginBase): + pass diff --git a/neutron/extensions/floatingip_pools.py b/neutron/extensions/floatingip_pools.py new file mode 100644 index 00000000000..6c965ee2ce4 --- /dev/null +++ b/neutron/extensions/floatingip_pools.py @@ -0,0 +1,53 @@ +# 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. + +import abc +import itertools + +from neutron_lib.api.definitions import floatingip_pools as apidef +from neutron_lib.api import extensions as api_extensions +from neutron_lib.plugins import constants +import six + +from neutron.api.v2 import resource_helper + + +class Floatingip_pools(api_extensions.APIExtensionDescriptor): + """Neutron floating IP pool api extension.""" + + api_definition = apidef + + @classmethod + def get_resources(cls): + """Returns Ext Resources.""" + plural_mappings = resource_helper.build_plural_mappings( + {}, itertools.chain(apidef.RESOURCE_ATTRIBUTE_MAP)) + + resources = resource_helper.build_resource_info( + plural_mappings, + apidef.RESOURCE_ATTRIBUTE_MAP, + constants.L3, + translate_name=True, + allow_bulk=True) + + return resources + + +@six.add_metaclass(abc.ABCMeta) +class FloatingIPPoolPluginBase(object): + + @abc.abstractmethod + def get_floatingip_pools(self, context, filters=None, fields=None, + sorts=None, limit=None, marker=None, + page_reverse=False): + """List all floating ip pools.""" + pass diff --git a/neutron/services/l3_router/l3_router_plugin.py b/neutron/services/l3_router/l3_router_plugin.py index ba56aa6718b..2c518663709 100644 --- a/neutron/services/l3_router/l3_router_plugin.py +++ b/neutron/services/l3_router/l3_router_plugin.py @@ -32,6 +32,7 @@ from neutron.db import dns_db from neutron.db import extraroute_db from neutron.db import l3_dvr_ha_scheduler_db from neutron.db import l3_dvrscheduler_db +from neutron.db import l3_fip_pools_db from neutron.db import l3_fip_port_details from neutron.db import l3_fip_qos from neutron.db import l3_gwmode_db @@ -69,7 +70,8 @@ class L3RouterPlugin(service_base.ServicePluginBase, l3_dvr_ha_scheduler_db.L3_DVR_HA_scheduler_db_mixin, dns_db.DNSDbMixin, l3_fip_qos.FloatingQoSDbMixin, - l3_fip_port_details.Fip_port_details_db_mixin): + l3_fip_port_details.Fip_port_details_db_mixin, + l3_fip_pools_db.FloatingIPPoolsMixin): """Implementation of the Neutron L3 Router Service Plugin. @@ -84,7 +86,7 @@ class L3RouterPlugin(service_base.ServicePluginBase, "extraroute", "l3_agent_scheduler", "l3-ha", "router_availability_zone", "l3-flavors", "qos-fip", - "fip-port-details"] + "fip-port-details", "floatingip-pools"] __native_pagination_support = True __native_sorting_support = True diff --git a/neutron/tests/contrib/hooks/api_all_extensions b/neutron/tests/contrib/hooks/api_all_extensions index 10cddd9c8eb..43a7efa1640 100644 --- a/neutron/tests/contrib/hooks/api_all_extensions +++ b/neutron/tests/contrib/hooks/api_all_extensions @@ -20,6 +20,7 @@ NETWORK_API_EXTENSIONS+=",extraroute" NETWORK_API_EXTENSIONS+=",filter-validation" NETWORK_API_EXTENSIONS+=",fip-port-details" NETWORK_API_EXTENSIONS+=",flavors" +NETWORK_API_EXTENSIONS+=",floatingip-pools" NETWORK_API_EXTENSIONS+=",ip-substring-filtering" NETWORK_API_EXTENSIONS+=",l3-flavors" NETWORK_API_EXTENSIONS+=",l3-ha" diff --git a/neutron/tests/etc/policy.json b/neutron/tests/etc/policy.json index a1cc26efe98..c517996149e 100644 --- a/neutron/tests/etc/policy.json +++ b/neutron/tests/etc/policy.json @@ -150,6 +150,7 @@ "create_floatingip": "rule:regular_user", "create_floatingip:floating_ip_address": "rule:admin_only", "get_floatingip": "rule:admin_or_owner", + "get_floatingip_pool": "rule:regular_user", "update_floatingip": "rule:admin_or_owner", "delete_floatingip": "rule:admin_or_owner", diff --git a/neutron/tests/unit/extensions/test_floatingip_pools.py b/neutron/tests/unit/extensions/test_floatingip_pools.py new file mode 100644 index 00000000000..9995527bb4f --- /dev/null +++ b/neutron/tests/unit/extensions/test_floatingip_pools.py @@ -0,0 +1,155 @@ +# +# 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. +# + +import ddt +import mock + +from neutron_lib.api.definitions import floatingip_pools as apidef +from neutron_lib import constants as lib_const +from neutron_lib import context +from neutron_lib.plugins import constants as plugin_constants +from neutron_lib.plugins import directory +from oslo_config import cfg +from oslo_utils import uuidutils + +from neutron.db import l3_fip_pools_db +from neutron.extensions import l3 +from neutron.objects import network as net_obj +from neutron.objects import subnet as subnet_obj +from neutron.tests.unit.extensions import test_l3 + + +class FloatingIPPoolsTestExtensionManager(object): + + def get_resources(self): + return l3.L3.get_resources() + + def get_actions(self): + return [] + + def get_request_extensions(self): + return [] + + +class TestFloatingIPPoolsIntPlugin( + test_l3.TestL3NatIntPlugin, + l3_fip_pools_db.FloatingIPPoolsDbMixin): + supported_extension_aliases = ["external-net", "router", + apidef.ALIAS] + + +class TestFloatingIPPoolsL3NatServicePlugin( + test_l3.TestL3NatServicePlugin, + l3_fip_pools_db.FloatingIPPoolsDbMixin): + supported_extension_aliases = ["router", apidef.ALIAS] + + +@ddt.ddt +class FloatingIPPoolsDBTestCaseBase(test_l3.L3NatTestCaseMixin): + + def test_get_floatingip_pools_ipv4(self): + self._test_get_floatingip_pools(lib_const.IP_VERSION_4, False) + + @ddt.data(True, False) + def test_get_floatingip_pools_ipv6(self, fake_is_v6_supported): + self._test_get_floatingip_pools(lib_const.IP_VERSION_6, + fake_is_v6_supported) + + def _test_get_floatingip_pools(self, ip_version, is_v6_supported): + fake_network_id = uuidutils.generate_uuid() + fake_subnet_id = uuidutils.generate_uuid() + fake_ext_network = mock.Mock(network_id=fake_network_id) + if ip_version == lib_const.IP_VERSION_4: + fake_cidr = '10.0.0.0/24' + else: + fake_cidr = 'fe80:cafe::/64' + fake_subnet = mock.Mock(id=fake_subnet_id, + network_id=fake_network_id, + cidr=fake_cidr, + ip_version=ip_version, + tenant_id='fake_tenant', + project_id='fake_tenant') + fake_subnet.name = 'fake_subnet' + self.plugin._is_v6_supported = is_v6_supported + with mock.patch.object( + subnet_obj.Subnet, 'get_objects', + return_value=[fake_subnet] + ) as mock_subnet_get_objects, mock.patch.object( + net_obj.ExternalNetwork, 'get_objects', + return_value=[fake_ext_network] + ) as mock_extnet_get_objects, mock.patch.object( + self.ctxt, 'elevated', + return_value=self.admin_ctxt + ) as mock_context_elevated: + fip_pools = self.plugin.get_floatingip_pools(self.ctxt) + + expected_fip_pools = [] + if ip_version == lib_const.IP_VERSION_4 or is_v6_supported: + expected_fip_pools = [{'cidr': fake_cidr, + 'subnet_id': fake_subnet_id, + 'subnet_name': 'fake_subnet', + 'network_id': fake_network_id, + 'project_id': 'fake_tenant', + 'tenant_id': 'fake_tenant'}] + self.assertEqual(expected_fip_pools, fip_pools) + mock_subnet_get_objects.assert_called_once_with( + self.admin_ctxt, _pager=mock.ANY, network_id=[fake_network_id]) + mock_extnet_get_objects.assert_called_once_with(self.ctxt) + mock_context_elevated.assert_called_once_with() + + +class FloatingIPPoolsDBIntTestCase(test_l3.L3BaseForIntTests, + FloatingIPPoolsDBTestCaseBase): + + def setUp(self, plugin=None): + if not plugin: + plugin = ('neutron.tests.unit.extensions.test_floatingip_pools.' + 'TestFloatingIPPoolsIntPlugin') + # for these tests we need to enable overlapping ips + cfg.CONF.set_default('allow_overlapping_ips', True) + cfg.CONF.set_default('max_routes', 3) + ext_mgr = FloatingIPPoolsTestExtensionManager() + super(test_l3.L3BaseForIntTests, self).setUp( + plugin=plugin, + ext_mgr=ext_mgr) + + self.setup_notification_driver() + self.ctxt = context.Context('fake_user', 'fake_tenant') + self.admin_ctxt = self.ctxt.elevated() + + +class FloatingIPPoolsDBSepTestCase(test_l3.L3BaseForSepTests, + FloatingIPPoolsDBTestCaseBase): + + def setUp(self): + # the plugin without L3 support + plugin = 'neutron.tests.unit.extensions.test_l3.TestNoL3NatPlugin' + # the L3 service plugin + l3_plugin = ('neutron.tests.unit.extensions.test_floatingip_pools.' + 'TestFloatingIPPoolsL3NatServicePlugin') + service_plugins = {'l3_plugin_name': l3_plugin} + + # for these tests we need to enable overlapping ips + cfg.CONF.set_default('allow_overlapping_ips', True) + cfg.CONF.set_default('max_routes', 3) + ext_mgr = FloatingIPPoolsTestExtensionManager() + super(test_l3.L3BaseForSepTests, self).setUp( + plugin=plugin, + ext_mgr=ext_mgr, + service_plugins=service_plugins) + + self.setup_notification_driver() + self.plugin = directory.get_plugin(plugin_constants.L3) + self.ctxt = context.Context('fake_user', 'fake_tenant') + self.admin_ctxt = self.ctxt.elevated() diff --git a/releasenotes/notes/add-floatingip-pool-api-6927362ef87fdbe5.yaml b/releasenotes/notes/add-floatingip-pool-api-6927362ef87fdbe5.yaml new file mode 100644 index 00000000000..07c3a9622b6 --- /dev/null +++ b/releasenotes/notes/add-floatingip-pool-api-6927362ef87fdbe5.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add support for listing floating ip pools (subnets) in L3 plugin. + A new API resource ``floatingip-pools`` is introduced. + This API endpoint can return a list of floating ip pools which are + essentially mappings between network UUIDs and subnet CIDRs. + Users can use this API to find out the pool to create the floating IPs.