470 lines
17 KiB
Python
Raw Normal View History

# 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 io
2013-09-02 23:42:41 -04:00
import re
import sys
from unittest import mock
2013-09-02 23:42:41 -04:00
import ddt
2013-09-02 23:42:41 -04:00
import fixtures
from tempest.lib.cli import output_parser
2013-09-02 23:42:41 -04:00
from testtools import matchers
Nova Style API Version Support for Client The Manila client needs the following changes to support microversions: * Maintain backwards compatibility with Kilo. When the client detects that the server doesn't support microversions it will fall back to using the v1 API. * The --os-share-api-version option supports overriding the version. * If 1.0 is specified as the version the client will load the v1 client and use the server's v1 API. * The client will send a request for the server's API version and determine if the client's supported versions and the server's supported versions overlap. If not the client will display an error and quit. See diagram 1 below. * The client supports the @wraps annotation. The annotation is used with the v2/shell.py commands and any class that inherits from the Manager class in manilaclient/base.py. * If an appropriate command version isn't found for commands using @wraps then the client will display an error and quit. following commit: ab49d645befd04c84272f0d24e1b604012d191dd. Diagram 1: Client: 2.5 2.8 |-------------| Server1: 2.0 2.5 |-------------| Client uses version 2.5 Server2: 2.7 2.10 |-------------| Client uses version 2.8 Server3: 2.9 2.12 |-------------| Client displays error and quits Server4: 1.0 (Kilo Server) |-| Client detects pre-microversion server and loads v1 client Example usage of wraps annotation: * Support 2.0 - 2.4: @api_versions.wraps("2.0", "2.4") * Support 2.5 - latest: @api_versions.wraps("2.5") Implements: blueprint manila-client-advanced-microversion-support Change-Id: I3733fe85424e39566addc070d42609e508259f19
2015-09-01 19:12:01 -04:00
import manilaclient
from manilaclient.common import cliutils
from manilaclient.common import constants
2013-09-03 14:37:34 +03:00
from manilaclient import exceptions
from manilaclient import shell
from manilaclient.tests.unit import utils
from manilaclient.tests.unit.v2 import fakes
2013-09-02 23:42:41 -04:00
@ddt.ddt
class OpenstackManilaShellTest(utils.TestCase):
2013-09-02 23:42:41 -04:00
FAKE_ENV = {
'OS_USERNAME': 'username',
'OS_PASSWORD': 'password',
'OS_TENANT_NAME': 'tenant_name',
'OS_AUTH_URL': 'http://no.where',
}
# Patch os.environ to avoid required auth info.
def set_env_vars(self, env_vars):
for k, v in env_vars.items():
self.useFixture(fixtures.EnvironmentVariable(k, v))
2013-09-02 23:42:41 -04:00
Nova Style API Version Support for Client The Manila client needs the following changes to support microversions: * Maintain backwards compatibility with Kilo. When the client detects that the server doesn't support microversions it will fall back to using the v1 API. * The --os-share-api-version option supports overriding the version. * If 1.0 is specified as the version the client will load the v1 client and use the server's v1 API. * The client will send a request for the server's API version and determine if the client's supported versions and the server's supported versions overlap. If not the client will display an error and quit. See diagram 1 below. * The client supports the @wraps annotation. The annotation is used with the v2/shell.py commands and any class that inherits from the Manager class in manilaclient/base.py. * If an appropriate command version isn't found for commands using @wraps then the client will display an error and quit. following commit: ab49d645befd04c84272f0d24e1b604012d191dd. Diagram 1: Client: 2.5 2.8 |-------------| Server1: 2.0 2.5 |-------------| Client uses version 2.5 Server2: 2.7 2.10 |-------------| Client uses version 2.8 Server3: 2.9 2.12 |-------------| Client displays error and quits Server4: 1.0 (Kilo Server) |-| Client detects pre-microversion server and loads v1 client Example usage of wraps annotation: * Support 2.0 - 2.4: @api_versions.wraps("2.0", "2.4") * Support 2.5 - latest: @api_versions.wraps("2.5") Implements: blueprint manila-client-advanced-microversion-support Change-Id: I3733fe85424e39566addc070d42609e508259f19
2015-09-01 19:12:01 -04:00
def shell_discover_client(self,
current_client,
os_api_version,
os_endpoint_type,
os_service_type,
client_args):
return current_client, manilaclient.API_MAX_VERSION
2013-09-02 23:42:41 -04:00
def shell(self, argstr):
orig = sys.stdout
try:
sys.stdout = io.StringIO()
_shell = shell.OpenStackManilaShell()
Nova Style API Version Support for Client The Manila client needs the following changes to support microversions: * Maintain backwards compatibility with Kilo. When the client detects that the server doesn't support microversions it will fall back to using the v1 API. * The --os-share-api-version option supports overriding the version. * If 1.0 is specified as the version the client will load the v1 client and use the server's v1 API. * The client will send a request for the server's API version and determine if the client's supported versions and the server's supported versions overlap. If not the client will display an error and quit. See diagram 1 below. * The client supports the @wraps annotation. The annotation is used with the v2/shell.py commands and any class that inherits from the Manager class in manilaclient/base.py. * If an appropriate command version isn't found for commands using @wraps then the client will display an error and quit. following commit: ab49d645befd04c84272f0d24e1b604012d191dd. Diagram 1: Client: 2.5 2.8 |-------------| Server1: 2.0 2.5 |-------------| Client uses version 2.5 Server2: 2.7 2.10 |-------------| Client uses version 2.8 Server3: 2.9 2.12 |-------------| Client displays error and quits Server4: 1.0 (Kilo Server) |-| Client detects pre-microversion server and loads v1 client Example usage of wraps annotation: * Support 2.0 - 2.4: @api_versions.wraps("2.0", "2.4") * Support 2.5 - latest: @api_versions.wraps("2.5") Implements: blueprint manila-client-advanced-microversion-support Change-Id: I3733fe85424e39566addc070d42609e508259f19
2015-09-01 19:12:01 -04:00
_shell._discover_client = self.shell_discover_client
2013-09-02 23:42:41 -04:00
_shell.main(argstr.split())
except SystemExit:
exc_type, exc_value, exc_traceback = sys.exc_info()
self.assertEqual(exc_value.code, 0)
finally:
out = sys.stdout.getvalue()
sys.stdout.close()
sys.stdout = orig
return out
@ddt.data(
{},
{'OS_AUTH_URL': 'http://foo.bar'},
{'OS_AUTH_URL': 'http://foo.bar', 'OS_USERNAME': 'foo'},
{'OS_AUTH_URL': 'http://foo.bar', 'OS_USERNAME': 'foo_user',
'OS_PASSWORD': 'foo_password'},
{'OS_TENANT_NAME': 'foo_tenant', 'OS_USERNAME': 'foo_user',
'OS_PASSWORD': 'foo_password'},
{'OS_TOKEN': 'foo_token'},
{'OS_MANILA_BYPASS_URL': 'http://foo.foo'},
)
def test_main_failure(self, env_vars):
self.set_env_vars(env_vars)
with mock.patch.object(shell, 'client') as mock_client:
self.assertRaises(exceptions.CommandError, self.shell, 'list')
self.assertFalse(mock_client.Client.called)
@ddt.data(None, 'foo_key')
def test_main_success(self, os_key):
env_vars = {
'OS_AUTH_URL': 'http://foo.bar',
'OS_USERNAME': 'foo_username',
'OS_USER_ID': 'foo_user_id',
'OS_PASSWORD': 'foo_password',
'OS_TENANT_NAME': 'foo_tenant',
'OS_TENANT_ID': 'foo_tenant_id',
'OS_PROJECT_NAME': 'foo_project',
'OS_PROJECT_ID': 'foo_project_id',
'OS_PROJECT_DOMAIN_ID': 'foo_project_domain_id',
'OS_PROJECT_DOMAIN_NAME': 'foo_project_domain_name',
'OS_PROJECT_DOMAIN_ID': 'foo_project_domain_id',
'OS_USER_DOMAIN_NAME': 'foo_user_domain_name',
'OS_USER_DOMAIN_ID': 'foo_user_domain_id',
'OS_CERT': 'foo_cert',
'OS_KEY': os_key,
}
self.set_env_vars(env_vars)
cert = env_vars['OS_CERT']
if os_key:
cert = (cert, env_vars['OS_KEY'])
with mock.patch.object(shell, 'client') as mock_client:
self.shell('list')
Nova Style API Version Support for Client The Manila client needs the following changes to support microversions: * Maintain backwards compatibility with Kilo. When the client detects that the server doesn't support microversions it will fall back to using the v1 API. * The --os-share-api-version option supports overriding the version. * If 1.0 is specified as the version the client will load the v1 client and use the server's v1 API. * The client will send a request for the server's API version and determine if the client's supported versions and the server's supported versions overlap. If not the client will display an error and quit. See diagram 1 below. * The client supports the @wraps annotation. The annotation is used with the v2/shell.py commands and any class that inherits from the Manager class in manilaclient/base.py. * If an appropriate command version isn't found for commands using @wraps then the client will display an error and quit. following commit: ab49d645befd04c84272f0d24e1b604012d191dd. Diagram 1: Client: 2.5 2.8 |-------------| Server1: 2.0 2.5 |-------------| Client uses version 2.5 Server2: 2.7 2.10 |-------------| Client uses version 2.8 Server3: 2.9 2.12 |-------------| Client displays error and quits Server4: 1.0 (Kilo Server) |-| Client detects pre-microversion server and loads v1 client Example usage of wraps annotation: * Support 2.0 - 2.4: @api_versions.wraps("2.0", "2.4") * Support 2.5 - latest: @api_versions.wraps("2.5") Implements: blueprint manila-client-advanced-microversion-support Change-Id: I3733fe85424e39566addc070d42609e508259f19
2015-09-01 19:12:01 -04:00
mock_client.Client.assert_called_with(
manilaclient.API_MAX_VERSION,
username=env_vars['OS_USERNAME'],
password=env_vars['OS_PASSWORD'],
project_name=env_vars['OS_PROJECT_NAME'],
auth_url=env_vars['OS_AUTH_URL'],
insecure=False,
region_name='',
tenant_id=env_vars['OS_PROJECT_ID'],
endpoint_type='publicURL',
extensions=mock.ANY,
service_type=constants.V2_SERVICE_TYPE,
service_name='',
retries=0,
http_log_debug=False,
cacert=None,
use_keyring=False,
force_new_token=False,
user_id=env_vars['OS_USER_ID'],
user_domain_id=env_vars['OS_USER_DOMAIN_ID'],
user_domain_name=env_vars['OS_USER_DOMAIN_NAME'],
project_domain_id=env_vars['OS_PROJECT_DOMAIN_ID'],
project_domain_name=env_vars['OS_PROJECT_DOMAIN_NAME'],
cert=cert,
input_auth_token='',
service_catalog_url='',
)
@ddt.data(
{"env_vars": {"OS_MANILA_BYPASS_URL": "http://foo.url",
"OS_TOKEN": "foo_token"},
"kwargs": {"--os-token": "bar_token",
"--bypass-url": "http://bar.url"},
"expected": {"input_auth_token": "bar_token",
"service_catalog_url": "http://bar.url"}},
{"env_vars": {"OS_MANILA_BYPASS_URL": "http://foo.url",
"OS_TOKEN": "foo_token"},
"kwargs": {},
"expected": {"input_auth_token": "foo_token",
"service_catalog_url": "http://foo.url"}},
{"env_vars": {},
"kwargs": {"--os-token": "bar_token",
"--bypass-url": "http://bar.url"},
"expected": {"input_auth_token": "bar_token",
"service_catalog_url": "http://bar.url"}},
{"env_vars": {"MANILACLIENT_BYPASS_URL": "http://foo.url",
"OS_TOKEN": "foo_token"},
"kwargs": {},
"expected": {"input_auth_token": "foo_token",
"service_catalog_url": "http://foo.url"}},
{"env_vars": {"OS_TOKEN": "foo_token"},
"kwargs": {"--bypass-url": "http://bar.url"},
"expected": {"input_auth_token": "foo_token",
"service_catalog_url": "http://bar.url"}},
{"env_vars": {"MANILACLIENT_BYPASS_URL": "http://foo.url",
"OS_MANILA_BYPASS_URL": "http://bar.url",
"OS_TOKEN": "foo_token"},
"kwargs": {"--os-token": "bar_token"},
"expected": {"input_auth_token": "bar_token",
"service_catalog_url": "http://bar.url"}},
)
@ddt.unpack
def test_main_success_with_token(self, env_vars, kwargs, expected):
self.set_env_vars(env_vars)
with mock.patch.object(shell, "client") as mock_client:
cmd = ""
for k, v in kwargs.items():
cmd += "%s=%s " % (k, v)
cmd += "list"
self.shell(cmd)
mock_client.Client.assert_called_with(
manilaclient.API_MAX_VERSION,
username="",
password="",
project_name="",
auth_url="",
insecure=False,
region_name="",
tenant_id="",
endpoint_type="publicURL",
extensions=mock.ANY,
service_type=constants.V2_SERVICE_TYPE,
service_name="",
retries=0,
http_log_debug=False,
cacert=None,
use_keyring=False,
force_new_token=False,
user_id="",
user_domain_id="",
user_domain_name="",
project_domain_id="",
project_domain_name="",
cert=None,
input_auth_token=expected["input_auth_token"],
service_catalog_url=expected["service_catalog_url"],
)
@ddt.data(
# default without any env var or kwargs
{
"env_vars": {"OS_TOKEN": "foo_token",
"OS_MANILA_BYPASS_URL": "http://bar.url"},
"kwargs": {},
"expected": {"input_auth_token": "foo_token",
"service_catalog_url": "http://bar.url",
"os_endpoint_type": "publicURL"}
},
# only env var
{
"env_vars": {"OS_TOKEN": "foo_token",
"OS_MANILA_BYPASS_URL": "http://bar.url",
"OS_MANILA_ENDPOINT_TYPE": "custom-endpoint-type"},
"kwargs": {},
"expected": {"input_auth_token": "foo_token",
"service_catalog_url": "http://bar.url",
"os_endpoint_type": "custom-endpoint-type"},
},
# only kwargs
{
"env_vars": {"OS_TOKEN": "foo_token",
"OS_MANILA_BYPASS_URL": "http://bar.url"},
"kwargs": {"--endpoint-type": "custom-kwargs-endpoint-type"},
"expected": {"input_auth_token": "foo_token",
"service_catalog_url": "http://bar.url",
"os_endpoint_type": "custom-kwargs-endpoint-type"},
},
# env var *and* kwargs (kwargs should win)
{
"env_vars": {"OS_TOKEN": "foo_token",
"OS_MANILA_BYPASS_URL": "http://bar.url",
"os_endpoint_type": "custom-env-endpoint-type"},
"kwargs": {"--endpoint-type": "custom-kwargs-endpoint-type"},
"expected": {"input_auth_token": "foo_token",
"service_catalog_url": "http://bar.url",
"os_endpoint_type": "custom-kwargs-endpoint-type"},
}
)
@ddt.unpack
def test_main_success_with_os_endpoint(self, env_vars, kwargs, expected):
self.set_env_vars(env_vars)
with mock.patch.object(shell, "client") as mock_client:
cmd = ""
for k, v in kwargs.items():
cmd += "%s=%s " % (k, v)
cmd += "list"
self.shell(cmd)
mock_client.Client.assert_called_with(
manilaclient.API_MAX_VERSION,
username="",
password="",
project_name="",
auth_url="",
insecure=False,
region_name="",
tenant_id="",
endpoint_type=expected["os_endpoint_type"],
extensions=mock.ANY,
service_type=constants.V2_SERVICE_TYPE,
service_name="",
retries=0,
http_log_debug=False,
cacert=None,
use_keyring=False,
force_new_token=False,
user_id="",
user_domain_id="",
user_domain_name="",
project_domain_id="",
project_domain_name="",
cert=None,
input_auth_token=expected["input_auth_token"],
service_catalog_url=expected["service_catalog_url"],
)
2013-09-02 23:42:41 -04:00
def test_help_unknown_command(self):
self.assertRaises(exceptions.CommandError, self.shell, 'help foofoo')
@ddt.data('list --help', '--help list', 'help list')
def test_help_on_subcommand(self, cmd):
2013-09-02 23:42:41 -04:00
required = [
2013-09-05 13:32:38 +03:00
'.*?^usage: manila list',
'.*?^List NAS shares with filters.',
2013-09-02 23:42:41 -04:00
]
help_text = self.shell(cmd)
2013-09-02 23:42:41 -04:00
for r in required:
self.assertThat(help_text,
matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
def test_common_args_in_help_message(self):
expected_args = (
'--version', '', '--debug', '--os-cache', '--os-reset-cache',
'--os-user-id', '--os-username', '--os-password',
'--os-tenant-name', '--os-project-name', '--os-tenant-id',
'--os-project-id', '--os-user-domain-id', '--os-user-domain-name',
'--os-project-domain-id', '--os-project-domain-name',
'--os-auth-url', '--os-region-name', '--service-type',
'--service-name', '--share-service-name', '--endpoint-type',
'--os-share-api-version', '--os-cacert', '--retries', '--os-cert',
'--os-key',
)
help_text = self.shell('help')
for expected_arg in expected_args:
self.assertIn(expected_arg, help_text)
class CustomOpenStackManilaShell(shell.OpenStackManilaShell):
@staticmethod
@cliutils.arg(
'--default-is-none',
'--default_is_none',
type=str,
metavar='<redefined_metavar>',
action='single_alias',
help='Default value is None and metavar set.',
default=None)
def do_foo(cs, args):
cliutils.print_dict({'key': args.default_is_none})
@staticmethod
@cliutils.arg(
'--default-is-not-none',
'--default_is_not_none',
type=str,
action='single_alias',
help='Default value is not None and metavar not set.',
default='bar')
def do_bar(cs, args):
cliutils.print_dict({'key': args.default_is_not_none})
@staticmethod
@cliutils.arg(
'--list-like',
'--list_like',
nargs='*',
action='single_alias',
help='Default value is None, metavar not set and result is list.',
default=None)
def do_quuz(cs, args):
cliutils.print_dict({'key': args.list_like})
@ddt.ddt
class AllowOnlyOneAliasAtATimeActionTest(utils.TestCase):
FAKE_ENV = {
'OS_USERNAME': 'username',
'OS_PASSWORD': 'password',
'OS_TENANT_NAME': 'tenant_name',
'OS_AUTH_URL': 'http://no.where',
}
def setUp(self):
super(self.__class__, self).setUp()
for k, v in self.FAKE_ENV.items():
self.useFixture(fixtures.EnvironmentVariable(k, v))
self.mock_object(
shell.client, 'get_client_class',
mock.Mock(return_value=fakes.FakeClient))
def shell_discover_client(self,
current_client,
os_api_version,
os_endpoint_type,
os_service_type,
client_args):
return current_client, manilaclient.API_MAX_VERSION
def shell(self, argstr):
orig = sys.stdout
try:
sys.stdout = io.StringIO()
_shell = CustomOpenStackManilaShell()
_shell._discover_client = self.shell_discover_client
_shell.main(argstr.split())
except SystemExit:
exc_type, exc_value, exc_traceback = sys.exc_info()
self.assertEqual(exc_value.code, 0)
finally:
out = sys.stdout.getvalue()
sys.stdout.close()
sys.stdout = orig
return out
@ddt.data(
('--default-is-none foo', 'foo'),
('--default-is-none foo --default-is-none foo', 'foo'),
('--default-is-none foo --default_is_none foo', 'foo'),
('--default_is_none None', 'None'),
)
@ddt.unpack
def test_foo_success(self, options_str, expected_result):
output = self.shell('foo %s' % options_str)
parsed_output = output_parser.details(output)
self.assertEqual({'key': expected_result}, parsed_output)
@ddt.data(
'--default-is-none foo --default-is-none bar',
'--default-is-none foo --default_is_none bar',
'--default-is-none foo --default_is_none FOO',
)
def test_foo_error(self, options_str):
self.assertRaises(
matchers.MismatchError, self.shell, 'foo %s' % options_str)
@ddt.data(
('--default-is-not-none bar', 'bar'),
('--default_is_not_none bar --default-is-not-none bar', 'bar'),
('--default_is_not_none bar --default_is_not_none bar', 'bar'),
('--default-is-not-none not_bar', 'not_bar'),
('--default_is_not_none None', 'None'),
)
@ddt.unpack
def test_bar_success(self, options_str, expected_result):
output = self.shell('bar %s' % options_str)
parsed_output = output_parser.details(output)
self.assertEqual({'key': expected_result}, parsed_output)
@ddt.data(
'--default-is-not-none foo --default-is-not-none bar',
'--default-is-not-none foo --default_is_not_none bar',
'--default-is-not-none bar --default_is_not_none BAR',
)
def test_bar_error(self, options_str):
self.assertRaises(
matchers.MismatchError, self.shell, 'bar %s' % options_str)
@ddt.data(
('--list-like q=w', "['q=w']"),
('--list-like q=w --list_like q=w', "['q=w']"),
('--list-like q=w e=r t=y --list_like e=r t=y q=w',
"['e=r', 'q=w', 't=y']"),
('--list_like q=w e=r t=y', "['e=r', 'q=w', 't=y']"),
)
@ddt.unpack
def test_quuz_success(self, options_str, expected_result):
output = self.shell('quuz %s' % options_str)
parsed_output = output_parser.details(output)
self.assertEqual({'key': expected_result}, parsed_output)
@ddt.data(
'--list-like q=w --list_like e=r t=y',
)
def test_quuz_error(self, options_str):
self.assertRaises(
matchers.MismatchError, self.shell, 'quuz %s' % options_str)