Fix 'List' command filters do not accept unicode symbols

When using the name or description to filter the share list,
share group list, share network list or snapshot list, if the
name or description is unicode symbols, the query will fail due to the
invalid str value. This change is to fix them.

Change-Id: I73ed2b675542d3aeedc9efdba08067819bf7d3e4
Closes-bug: #1712988
This commit is contained in:
lijunbo
2017-11-09 20:27:54 +08:00
committed by junbo.li
parent aba12d2a98
commit ea93a0b86e
9 changed files with 169 additions and 40 deletions

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright 2015 Mirantis Inc.
# All Rights Reserved.
#
@@ -170,3 +171,23 @@ class ShareNetworksReadWriteTest(base.BaseTestCase):
filters=filters)
self.assertGreater(len(share_networks), 0)
def test_list_share_networks_by_inexact_unicode_option(self):
self.create_share_network(
name=u'网络名称',
description=u'网络描述',
neutron_net_id='fake_neutron_net_id',
neutron_subnet_id='fake_neutron_subnet_id',
)
filters = {'name~': u'名称'}
share_networks = self.admin_client.list_share_networks(
filters=filters)
self.assertGreater(len(share_networks), 0)
filters = {'description~': u'描述'}
share_networks = self.admin_client.list_share_networks(
filters=filters)
self.assertGreater(len(share_networks), 0)

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright 2015 Mirantis Inc.
# All Rights Reserved.
#
@@ -311,6 +312,21 @@ class SharesListReadWriteTest(base.BaseTestCase):
self.assertTrue(
any(self.private_share['id'] == s['ID'] for s in shares))
def test_list_shares_by_inexact_unicode_option(self):
self.create_share(
name=u'共享名称',
description=u'共享描述',
public=True,
client=self.get_user_client(),
cleanup_in_class=True)
filters = {'name~': u'名称'}
shares = self.user_client.list_shares(filters=filters)
self.assertGreater(len(shares), 0)
filters = {'description~': u'描述'}
shares = self.user_client.list_shares(filters=filters)
self.assertGreater(len(shares), 0)
def test_list_shares_by_description(self):
shares = self.user_client.list_shares(
filters={'description': self.private_description})

View File

@@ -0,0 +1,29 @@
# 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 six
import testtools
from manilaclient import utils
class TestCommonUtils(testtools.TestCase):
def test_unicode_key_value_to_string(self):
src = {u'key': u'\u70fd\u7231\u5a77'}
expected = {'key': '\xe7\x83\xbd\xe7\x88\xb1\xe5\xa9\xb7'}
if six.PY2:
self.assertEqual(expected, utils.unicode_key_value_to_string(src))
else:
# u'xxxx' in PY3 is str, we will not get extra 'u' from cli
# output in PY3
self.assertEqual(src, utils.unicode_key_value_to_string(src))

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright 2013 OpenStack Foundation
# Copyright 2014 Mirantis, Inc.
# All Rights Reserved.
@@ -261,6 +262,22 @@ class ShellTest(test_utils.TestCase):
'GET',
'/shares/detail?description%7E=fake_description')
def test_list_filter_by_inexact_unicode_name(self):
for separator in self.separators:
self.run_command('list --name~' + separator +
u'ффф')
self.assert_called(
'GET',
'/shares/detail?name%7E=%D1%84%D1%84%D1%84')
def test_list_filter_by_inexact_unicode_description(self):
for separator in self.separators:
self.run_command('list --description~' + separator +
u'ффф')
self.assert_called(
'GET',
'/shares/detail?description%7E=%D1%84%D1%84%D1%84')
def test_list_filter_by_share_type_not_found(self):
for separator in self.separators:
self.assertRaises(
@@ -800,6 +817,22 @@ class ShellTest(test_utils.TestCase):
'GET',
'/snapshots/detail?description%7E=fake_description')
def test_list_snapshots_filter_by_inexact_unicode_name(self):
for separator in self.separators:
self.run_command('snapshot-list --name~' + separator +
u'ффф')
self.assert_called(
'GET',
'/snapshots/detail?name%7E=%D1%84%D1%84%D1%84')
def test_list_snapshots_filter_by_inexact_unicode_description(self):
for separator in self.separators:
self.run_command('snapshot-list --description~' + separator +
u'ффф')
self.assert_called(
'GET',
'/snapshots/detail?description%7E=%D1%84%D1%84%D1%84')
def test_list_snapshots_with_sort_dir_verify_keys(self):
aliases = ['--sort_dir', '--sort-dir']
for alias in aliases:
@@ -1460,6 +1493,22 @@ class ShellTest(test_utils.TestCase):
'GET',
'/share-networks/detail?description%7E=fake_description')
def test_share_network_list_filter_by_inexact_unicode_name(self):
for separator in self.separators:
self.run_command('share-network-list --name~' + separator +
u'ффф')
self.assert_called(
'GET',
'/share-networks/detail?name%7E=%D1%84%D1%84%D1%84')
def test_share_network_list_filter_by_inexact_unicode_description(self):
for separator in self.separators:
self.run_command('share-network-list --description~' + separator +
u'ффф')
self.assert_called(
'GET',
'/share-networks/detail?description%7E=%D1%84%D1%84%D1%84')
def test_share_network_security_service_add(self):
self.run_command('share-network-security-service-add fake_share_nw '
'fake_security_service')

View File

@@ -51,3 +51,21 @@ def get_function_name(func):
return "%s.%s" % (func.__module__, func.__name__)
else:
return "%s.%s" % (func.__module__, func.__qualname__)
def _encode(src):
"""remove extra 'u' in PY2."""
if six.PY2 and isinstance(src, unicode):
return src.encode('utf-8')
return src
def unicode_key_value_to_string(src):
"""Recursively converts dictionary keys to strings."""
if isinstance(src, dict):
return dict((_encode(k),
_encode(unicode_key_value_to_string(v)))
for k, v in src.items())
if isinstance(src, list):
return [unicode_key_value_to_string(l) for l in src]
return _encode(src)

View File

@@ -13,15 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
try:
from urllib import urlencode # noqa
except ImportError:
from urllib.parse import urlencode # noqa
from six.moves.urllib import parse
from manilaclient import api_versions
from manilaclient import base
from manilaclient.common.apiclient import base as common_base
from manilaclient import exceptions
from manilaclient import utils
RESOURCES_PATH = '/share-networks'
@@ -227,13 +225,13 @@ class ShareNetworkManager(base.ManagerWithFind):
if not search_opts:
search_opts = {}
query_string = ""
if search_opts:
query_string = urlencode(
sorted([(k, v) for (k, v) in list(search_opts.items()) if v]))
if query_string:
query_string = "?%s" % query_string
else:
query_string = ''
search_opts = utils.unicode_key_value_to_string(search_opts)
params = sorted(
[(k, v) for (k, v) in list(search_opts.items()) if v])
if params:
query_string = "?%s" % parse.urlencode(params)
if detailed:
path = RESOURCES_PATH + "/detail" + query_string

View File

@@ -14,15 +14,13 @@
# under the License.
"""Interface for shares extension."""
try:
from urllib import urlencode # noqa
except ImportError:
from urllib.parse import urlencode # noqa
from six.moves.urllib import parse
from manilaclient import api_versions
from manilaclient import base
from manilaclient.common.apiclient import base as common_base
from manilaclient.common import constants
from manilaclient import utils
class ShareSnapshot(common_base.Resource):
@@ -153,13 +151,13 @@ class ShareSnapshotManager(base.ManagerWithFind):
'sort_dir must be one of the following: %s.'
% ', '.join(constants.SORT_DIR_VALUES))
query_string = ""
if search_opts:
query_string = urlencode(
sorted([(k, v) for (k, v) in list(search_opts.items()) if v]))
if query_string:
query_string = "?%s" % (query_string,)
else:
query_string = ''
search_opts = utils.unicode_key_value_to_string(search_opts)
params = sorted(
[(k, v) for (k, v) in list(search_opts.items()) if v])
if params:
query_string = "?%s" % parse.urlencode(params)
if detailed:
path = "/snapshots/detail%s" % (query_string,)

View File

@@ -19,17 +19,15 @@ import ipaddress
from oslo_utils import uuidutils
import re
import six
from six.moves.urllib import parse
import string
try:
from urllib import urlencode # noqa
except ImportError:
from urllib.parse import urlencode # noqa
from manilaclient import api_versions
from manilaclient import base
from manilaclient.common.apiclient import base as common_base
from manilaclient.common import constants
from manilaclient import exceptions
from manilaclient import utils
from manilaclient.v2 import share_instances
@@ -399,13 +397,13 @@ class ShareManager(base.ManagerWithFind):
else:
search_opts['export_location_path'] = export_location
query_string = ""
if search_opts:
query_string = urlencode(
sorted([(k, v) for (k, v) in list(search_opts.items()) if v]))
if query_string:
query_string = "?%s" % (query_string,)
else:
query_string = ''
search_opts = utils.unicode_key_value_to_string(search_opts)
params = sorted(
[(k, v) for (k, v) in list(search_opts.items()) if v])
if params:
query_string = "?%s" % parse.urlencode(params)
if detailed:
path = "/shares/detail%s" % (query_string,)

View File

@@ -1473,27 +1473,27 @@ def do_snapshot_access_list(cs, args):
@cliutils.arg(
'--name',
metavar='<name>',
type=str,
type=six.text_type,
default=None,
help='Filter results by name.')
@cliutils.arg(
'--description',
metavar='<description>',
type=str,
type=six.text_type,
default=None,
help='Filter results by description. '
'Available only for microversion >= 2.36.')
@cliutils.arg(
'--name~',
metavar='<name~>',
type=str,
type=six.text_type,
default=None,
help='Filter results matching a share name pattern. '
'Available only for microversion >= 2.36.')
@cliutils.arg(
'--description~',
metavar='<description~>',
type=str,
type=six.text_type,
default=None,
help='Filter results matching a share description pattern. '
'Available only for microversion >= 2.36.')
@@ -1886,12 +1886,13 @@ def do_share_instance_export_location_show(cs, args):
@cliutils.arg(
'--name',
metavar='<name>',
type=six.text_type,
default=None,
help='Filter results by name.')
@cliutils.arg(
'--description',
metavar='<description>',
type=str,
type=six.text_type,
default=None,
help='Filter results by description. '
'Available only for microversion >= 2.36.')
@@ -1959,14 +1960,14 @@ def do_share_instance_export_location_show(cs, args):
@cliutils.arg(
'--name~',
metavar='<name~>',
type=str,
type=six.text_type,
default=None,
help='Filter results matching a share snapshot name pattern. '
'Available only for microversion >= 2.36.')
@cliutils.arg(
'--description~',
metavar='<description~>',
type=str,
type=six.text_type,
default=None,
help='Filter results matching a share snapshot description pattern. '
'Available only for microversion >= 2.36.')
@@ -2740,12 +2741,13 @@ def do_share_network_list(cs, args):
@cliutils.arg(
'--name',
metavar='<name>',
type=six.text_type,
default=None,
help='Filter results by name.')
@cliutils.arg(
'--description',
metavar='<description>',
type=str,
type=six.text_type,
default=None,
help='Filter results by description. '
'Available only for microversion >= 2.36.')
@@ -2837,14 +2839,14 @@ def do_share_network_list(cs, args):
@cliutils.arg(
'--name~',
metavar='<name~>',
type=str,
type=six.text_type,
default=None,
help='Filter results matching a share network name pattern. '
'Available only for microversion >= 2.36.')
@cliutils.arg(
'--description~',
metavar='<description~>',
type=str,
type=six.text_type,
default=None,
help='Filter results matching a share network description pattern. '
'Available only for microversion >= 2.36.')