Merge "Support filter attribute with empty string"

This commit is contained in:
Zuul 2018-05-08 23:57:27 +00:00 committed by Gerrit Code Review
commit d1a580541a
7 changed files with 80 additions and 6 deletions

View File

@ -75,12 +75,14 @@ def get_filters_from_dict(data, attr_info, skips=None):
becomes:
{'check': [u'a', u'b'], 'name': [u'Bob']}
"""
is_empty_string_supported = is_empty_string_filtering_supported()
skips = skips or []
res = {}
for key, values in data.items():
if key in skips or hasattr(model_base.BASEV2, key):
continue
values = [v for v in values if v]
values = [v for v in values
if v or (v == "" and is_empty_string_supported)]
key_attr_info = attr_info.get(key, {})
if 'convert_list_to' in key_attr_info:
values = key_attr_info['convert_list_to'](values)
@ -92,6 +94,11 @@ def get_filters_from_dict(data, attr_info, skips=None):
return res
def is_empty_string_filtering_supported():
return 'empty-string-filtering' in (extensions.PluginAwareExtensionManager.
get_instance().extensions)
def get_previous_link(request, items, id_key):
params = request.GET.copy()
params.pop('marker', None)

View File

@ -0,0 +1,30 @@
# 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/565342/ change is released.
"""
ALIAS = 'empty-string-filtering'
IS_SHIM_EXTENSION = True
IS_STANDARD_ATTR_EXTENSION = False
NAME = 'Empty String Filtering Extension'
DESCRIPTION = 'Allow filtering by attributes with empty string value'
UPDATED_TIMESTAMP = '2018-04-09T10:00:00-00:00'
RESOURCE_ATTRIBUTE_MAP = {}
SUB_RESOURCE_ATTRIBUTE_MAP = {}
ACTION_MAP = {}
REQUIRED_EXTENSIONS = []
OPTIONAL_EXTENSIONS = []
ACTION_STATUS = {}

View File

@ -0,0 +1,19 @@
# 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 _empty_string_filtering_lib as apidef
class Empty_string_filtering(extensions.APIExtensionDescriptor):
api_definition = apidef

View File

@ -158,7 +158,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
"default-subnetpools",
"subnet-service-types",
"ip-substring-filtering",
"port-security-groups-filtering"]
"port-security-groups-filtering",
"empty-string-filtering"]
@property
def supported_extension_aliases(self):

View File

@ -10,6 +10,7 @@ NETWORK_API_EXTENSIONS+=",default-subnetpools"
NETWORK_API_EXTENSIONS+=",dhcp_agent_scheduler"
NETWORK_API_EXTENSIONS+=",dns-integration"
NETWORK_API_EXTENSIONS+=",dvr"
NETWORK_API_EXTENSIONS+=",empty-string-filtering"
NETWORK_API_EXTENSIONS+=",ext-gw-mode"
NETWORK_API_EXTENSIONS+=",external-net"
NETWORK_API_EXTENSIONS+=",extra_dhcp_opt"

View File

@ -86,6 +86,7 @@ class APIv2TestBase(base.BaseTestCase):
self._plugin_patcher = mock.patch(plugin, autospec=True)
self.plugin = self._plugin_patcher.start()
instance = self.plugin.return_value
instance.supported_extension_aliases = ['empty-string-filtering']
instance._NeutronPluginBaseV2__native_pagination_support = True
instance._NeutronPluginBaseV2__native_sorting_support = True
tools.make_mock_plugin_json_encodable(instance)
@ -202,7 +203,7 @@ class APIv2TestCase(APIv2TestBase):
instance.get_networks.return_value = []
self.api.get(_get_path('networks'), {'name': ''})
filters = {}
filters = {'name': ['']}
kwargs = self._get_collection_kwargs(filters=filters)
instance.get_networks.assert_called_once_with(mock.ANY, **kwargs)
@ -211,7 +212,7 @@ class APIv2TestCase(APIv2TestBase):
instance.get_networks.return_value = []
self.api.get(_get_path('networks'), {'name': ['', '']})
filters = {}
filters = {'name': ['', '']}
kwargs = self._get_collection_kwargs(filters=filters)
instance.get_networks.assert_called_once_with(mock.ANY, **kwargs)
@ -220,7 +221,7 @@ class APIv2TestCase(APIv2TestBase):
instance.get_networks.return_value = []
self.api.get(_get_path('networks'), {'name': ['bar', '']})
filters = {'name': ['bar']}
filters = {'name': ['bar', '']}
kwargs = self._get_collection_kwargs(filters=filters)
instance.get_networks.assert_called_once_with(mock.ANY, **kwargs)
@ -1552,11 +1553,21 @@ class FiltersTestCase(base.BaseTestCase):
self.assertEqual({}, api_common.get_filters(request, None,
["fields"]))
def test_blank_values(self):
@mock.patch('neutron.api.api_common.is_empty_string_filtering_supported',
return_value=False)
def test_blank_values(self, mock_is_supported):
path = '/?foo=&bar=&baz=&qux='
request = webob.Request.blank(path)
self.assertEqual({}, api_common.get_filters(request, {}))
@mock.patch('neutron.api.api_common.is_empty_string_filtering_supported',
return_value=True)
def test_blank_values_with_filtering_supported(self, mock_is_supported):
path = '/?foo=&bar=&baz=&qux='
request = webob.Request.blank(path)
self.assertEqual({'foo': [''], 'bar': [''], 'baz': [''], 'qux': ['']},
api_common.get_filters(request, {}))
def test_no_attr_info(self):
path = '/?foo=4&bar=3&baz=2&qux=1'
request = webob.Request.blank(path)

View File

@ -0,0 +1,5 @@
---
features:
- |
Add support for filtering attributes with value as empty string. A shim
extension is added to indicate if this feature is supported.