diff --git a/neutron/extensions/ip_substring_port_filtering.py b/neutron/extensions/ip_substring_port_filtering.py new file mode 100644 index 00000000000..4d64a55344b --- /dev/null +++ b/neutron/extensions/ip_substring_port_filtering.py @@ -0,0 +1,23 @@ +# Copyright (c) 2017 Huawei Technology, 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. + +from neutron_lib.api import extensions + +from neutron.extensions import ip_substring_port_filtering_lib as apidef + + +class Ip_substring_port_filtering(extensions.APIExtensionDescriptor): + """Extension class supporting IP substring port filtering.""" + + api_definition = apidef diff --git a/neutron/extensions/ip_substring_port_filtering_lib.py b/neutron/extensions/ip_substring_port_filtering_lib.py new file mode 100644 index 00000000000..8861eb9cf5d --- /dev/null +++ b/neutron/extensions/ip_substring_port_filtering_lib.py @@ -0,0 +1,64 @@ +# 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. + +""" +TODO(hongbin): This module should be deleted once neutron-lib containing +https://review.openstack.org/#/c/525284/ change is released. +""" + +# The alias of the extension. +ALIAS = 'ip-substring-filtering' + +# Whether or not this extension is simply signaling behavior to the user +# or it actively modifies the attribute map. +IS_SHIM_EXTENSION = True + +# Whether the extension is marking the adoption of standardattr model for +# legacy resources, or introducing new standardattr attributes. False or +# None if the standardattr model is adopted since the introduction of +# resource extension. +# If this is True, the alias for the extension should be prefixed with +# 'standard-attr-'. +IS_STANDARD_ATTR_EXTENSION = False + +# The name of the extension. +NAME = 'IP address substring filtering' + +# The description of the extension. +DESCRIPTION = "Provides IP address substring filtering when listing ports" + +# A timestamp of when the extension was introduced. +UPDATED_TIMESTAMP = "2017-11-28T09:00:00-00:00" + +# The resource attribute map for the extension. +RESOURCE_ATTRIBUTE_MAP = { +} + +# The subresource attribute map for the extension. +SUB_RESOURCE_ATTRIBUTE_MAP = { +} + +# The action map. +ACTION_MAP = { +} + +# The action status. +ACTION_STATUS = { +} + +# The list of required extensions. +REQUIRED_EXTENSIONS = [ +] + +# The list of optional extensions. +OPTIONAL_EXTENSIONS = [ +] diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index 3619e797490..6599b84e527 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -46,6 +46,7 @@ from oslo_utils import excutils from oslo_utils import importutils from oslo_utils import uuidutils import sqlalchemy +from sqlalchemy import or_ from sqlalchemy.orm import exc as sa_exc from neutron._i18n import _ @@ -153,7 +154,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, "availability_zone", "network_availability_zone", "default-subnetpools", - "subnet-service-types"] + "subnet-service-types", + "ip-substring-filtering"] @property def supported_extension_aliases(self): @@ -1849,6 +1851,19 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, return port.id return device + def _get_ports_query(self, context, filters=None, *args, **kwargs): + filters = filters or {} + fixed_ips = filters.get('fixed_ips', {}) + ip_addresses_s = fixed_ips.get('ip_address_substr') + query = super(Ml2Plugin, self)._get_ports_query(context, filters, + *args, **kwargs) + if ip_addresses_s: + substr_filter = or_(*[models_v2.Port.fixed_ips.any( + models_v2.IPAllocation.ip_address.like('%%%s%%' % ip)) + for ip in ip_addresses_s]) + query = query.filter(substr_filter) + return query + def filter_hosts_with_network_access( self, context, network_id, candidate_hosts): segments = segments_db.get_network_segments(context, network_id) diff --git a/neutron/tests/contrib/hooks/api_all_extensions b/neutron/tests/contrib/hooks/api_all_extensions index c9075a32ac4..083e7b8f1cd 100644 --- a/neutron/tests/contrib/hooks/api_all_extensions +++ b/neutron/tests/contrib/hooks/api_all_extensions @@ -15,6 +15,7 @@ NETWORK_API_EXTENSIONS+=",external-net" NETWORK_API_EXTENSIONS+=",extra_dhcp_opt" NETWORK_API_EXTENSIONS+=",extraroute" NETWORK_API_EXTENSIONS+=",flavors" +NETWORK_API_EXTENSIONS+=",ip-substring-filtering" NETWORK_API_EXTENSIONS+=",l3-flavors" NETWORK_API_EXTENSIONS+=",l3-ha" NETWORK_API_EXTENSIONS+=",l3_agent_scheduler" diff --git a/neutron/tests/unit/plugins/ml2/test_plugin.py b/neutron/tests/unit/plugins/ml2/test_plugin.py index 4456d8a6a7a..e6c12e15ccc 100644 --- a/neutron/tests/unit/plugins/ml2/test_plugin.py +++ b/neutron/tests/unit/plugins/ml2/test_plugin.py @@ -1216,6 +1216,67 @@ class TestMl2PortsV2(test_plugin.TestPortsV2, Ml2PluginV2TestCase): # make sure that the grenade went off during the commit self.assertTrue(listener.except_raised) + def test_list_ports_filtered_by_fixed_ip_substring(self): + # for this test we need to enable overlapping ips + cfg.CONF.set_default('allow_overlapping_ips', True) + with self.port() as port1, self.port(): + fixed_ips = port1['port']['fixed_ips'][0] + query_params = """ +fixed_ips=ip_address_substr%%3D%s&fixed_ips=subnet_id%%3D%s +""".strip() % (fixed_ips['ip_address'][:-1], + fixed_ips['subnet_id']) + self._test_list_resources('port', [port1], + query_params=query_params) + query_params = """ +fixed_ips=ip_address_substr%%3D%s&fixed_ips=subnet_id%%3D%s +""".strip() % (fixed_ips['ip_address'][1:], + fixed_ips['subnet_id']) + self._test_list_resources('port', [port1], + query_params=query_params) + query_params = """ +fixed_ips=ip_address_substr%%3D%s&fixed_ips=subnet_id%%3D%s +""".strip() % ('192.168.', + fixed_ips['subnet_id']) + self._test_list_resources('port', [], + query_params=query_params) + + def test_list_ports_filtered_by_fixed_ip_substring_dual_stack(self): + with self.subnet() as subnet: + # Get a IPv4 and IPv6 address + tenant_id = subnet['subnet']['tenant_id'] + net_id = subnet['subnet']['network_id'] + res = self._create_subnet( + self.fmt, + tenant_id=tenant_id, + net_id=net_id, + cidr='2607:f0d0:1002:51::/124', + ip_version=6, + gateway_ip=constants.ATTR_NOT_SPECIFIED) + subnet2 = self.deserialize(self.fmt, res) + kwargs = {"fixed_ips": + [{'subnet_id': subnet['subnet']['id']}, + {'subnet_id': subnet2['subnet']['id']}]} + res = self._create_port(self.fmt, net_id=net_id, **kwargs) + port1 = self.deserialize(self.fmt, res) + res = self._create_port(self.fmt, net_id=net_id, **kwargs) + port2 = self.deserialize(self.fmt, res) + fixed_ips = port1['port']['fixed_ips'] + self.assertEqual(2, len(fixed_ips)) + query_params = """ +fixed_ips=ip_address_substr%%3D%s&fixed_ips=ip_address%%3D%s +""".strip() % (fixed_ips[0]['ip_address'][:-1], + fixed_ips[1]['ip_address']) + self._test_list_resources('port', [port1], + query_params=query_params) + query_params = """ +fixed_ips=ip_address_substr%%3D%s&fixed_ips=ip_address%%3D%s +""".strip() % ('192.168.', + fixed_ips[1]['ip_address']) + self._test_list_resources('port', [], + query_params=query_params) + self._delete('ports', port1['port']['id']) + self._delete('ports', port2['port']['id']) + class TestMl2PortsV2WithRevisionPlugin(Ml2PluginV2TestCase): diff --git a/releasenotes/notes/ip-substring-port-filtering-f5c3d89c4a91e867.yaml b/releasenotes/notes/ip-substring-port-filtering-f5c3d89c4a91e867.yaml new file mode 100644 index 00000000000..de833814a48 --- /dev/null +++ b/releasenotes/notes/ip-substring-port-filtering-f5c3d89c4a91e867.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Support substring matching when filtering ports by IP address.