Support filtering port with IP address substring

Neutron currently supports filtering ports by matching the exact
IP address. This patch adds support for substring matching using
"LIKE" SQL operator.

This patch also added a new API extension to show whether or not
the substring matching capability is available.

APIImpact add IP address substring filtering on listing ports
API-ref: I97259b85a2dce5a54bb6ea2cb9d9779ec0a25504

Co-Authored-By: Zhenyu Zheng <zhengzhenyu@huawei.com>

Change-Id: I9549b2ba676e1bad0812682c3f3f3c97de15f5f6
Closes-Bug: #1718605
This commit is contained in:
Hongbin Lu 2017-11-20 23:19:31 +00:00 committed by Hongbin Lu
parent 81b813fd46
commit 5c601bebeb
6 changed files with 169 additions and 1 deletions

View File

@ -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

View File

@ -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 = [
]

View File

@ -46,6 +46,7 @@ from oslo_utils import excutils
from oslo_utils import importutils from oslo_utils import importutils
from oslo_utils import uuidutils from oslo_utils import uuidutils
import sqlalchemy import sqlalchemy
from sqlalchemy import or_
from sqlalchemy.orm import exc as sa_exc from sqlalchemy.orm import exc as sa_exc
from neutron._i18n import _ from neutron._i18n import _
@ -153,7 +154,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
"availability_zone", "availability_zone",
"network_availability_zone", "network_availability_zone",
"default-subnetpools", "default-subnetpools",
"subnet-service-types"] "subnet-service-types",
"ip-substring-filtering"]
@property @property
def supported_extension_aliases(self): def supported_extension_aliases(self):
@ -1849,6 +1851,19 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
return port.id return port.id
return device 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( def filter_hosts_with_network_access(
self, context, network_id, candidate_hosts): self, context, network_id, candidate_hosts):
segments = segments_db.get_network_segments(context, network_id) segments = segments_db.get_network_segments(context, network_id)

View File

@ -15,6 +15,7 @@ NETWORK_API_EXTENSIONS+=",external-net"
NETWORK_API_EXTENSIONS+=",extra_dhcp_opt" NETWORK_API_EXTENSIONS+=",extra_dhcp_opt"
NETWORK_API_EXTENSIONS+=",extraroute" NETWORK_API_EXTENSIONS+=",extraroute"
NETWORK_API_EXTENSIONS+=",flavors" NETWORK_API_EXTENSIONS+=",flavors"
NETWORK_API_EXTENSIONS+=",ip-substring-filtering"
NETWORK_API_EXTENSIONS+=",l3-flavors" NETWORK_API_EXTENSIONS+=",l3-flavors"
NETWORK_API_EXTENSIONS+=",l3-ha" NETWORK_API_EXTENSIONS+=",l3-ha"
NETWORK_API_EXTENSIONS+=",l3_agent_scheduler" NETWORK_API_EXTENSIONS+=",l3_agent_scheduler"

View File

@ -1216,6 +1216,67 @@ class TestMl2PortsV2(test_plugin.TestPortsV2, Ml2PluginV2TestCase):
# make sure that the grenade went off during the commit # make sure that the grenade went off during the commit
self.assertTrue(listener.except_raised) 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): class TestMl2PortsV2WithRevisionPlugin(Ml2PluginV2TestCase):

View File

@ -0,0 +1,4 @@
---
features:
- |
Support substring matching when filtering ports by IP address.