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.