diff --git a/manilaclient/tests/functional/test_share_networks.py b/manilaclient/tests/functional/test_share_networks.py index 46f4faa9c..71e7b5217 100644 --- a/manilaclient/tests/functional/test_share_networks.py +++ b/manilaclient/tests/functional/test_share_networks.py @@ -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) diff --git a/manilaclient/tests/functional/test_shares_listing.py b/manilaclient/tests/functional/test_shares_listing.py index c10e5aa27..3f93f7947 100644 --- a/manilaclient/tests/functional/test_shares_listing.py +++ b/manilaclient/tests/functional/test_shares_listing.py @@ -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}) diff --git a/manilaclient/tests/unit/test_utils.py b/manilaclient/tests/unit/test_utils.py new file mode 100644 index 000000000..a3ee2d9c5 --- /dev/null +++ b/manilaclient/tests/unit/test_utils.py @@ -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)) diff --git a/manilaclient/tests/unit/v2/test_shell.py b/manilaclient/tests/unit/v2/test_shell.py index b0392b87e..c21254ac4 100644 --- a/manilaclient/tests/unit/v2/test_shell.py +++ b/manilaclient/tests/unit/v2/test_shell.py @@ -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') diff --git a/manilaclient/utils.py b/manilaclient/utils.py index a235235f7..dac055eb8 100644 --- a/manilaclient/utils.py +++ b/manilaclient/utils.py @@ -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) diff --git a/manilaclient/v2/share_networks.py b/manilaclient/v2/share_networks.py index 637b8139a..05e50198c 100644 --- a/manilaclient/v2/share_networks.py +++ b/manilaclient/v2/share_networks.py @@ -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 diff --git a/manilaclient/v2/share_snapshots.py b/manilaclient/v2/share_snapshots.py index cd47de418..7e149412f 100644 --- a/manilaclient/v2/share_snapshots.py +++ b/manilaclient/v2/share_snapshots.py @@ -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,) diff --git a/manilaclient/v2/shares.py b/manilaclient/v2/shares.py index 5960e9813..2642b2dfd 100644 --- a/manilaclient/v2/shares.py +++ b/manilaclient/v2/shares.py @@ -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,) diff --git a/manilaclient/v2/shell.py b/manilaclient/v2/shell.py index ef0acb792..b11c6d616 100644 --- a/manilaclient/v2/shell.py +++ b/manilaclient/v2/shell.py @@ -1473,27 +1473,27 @@ def do_snapshot_access_list(cs, args): @cliutils.arg( '--name', metavar='', - type=str, + type=six.text_type, default=None, help='Filter results by name.') @cliutils.arg( '--description', metavar='', - type=str, + type=six.text_type, default=None, help='Filter results by description. ' 'Available only for microversion >= 2.36.') @cliutils.arg( '--name~', metavar='', - 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='', - 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='', + type=six.text_type, default=None, help='Filter results by name.') @cliutils.arg( '--description', metavar='', - 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='', - 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='', - 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='', + type=six.text_type, default=None, help='Filter results by name.') @cliutils.arg( '--description', metavar='', - 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='', - 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='', - type=str, + type=six.text_type, default=None, help='Filter results matching a share network description pattern. ' 'Available only for microversion >= 2.36.')