summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cinderclient/api_versions.py3
-rw-r--r--cinderclient/shell.py105
-rw-r--r--cinderclient/tests/unit/v3/test_shell.py16
-rw-r--r--doc/source/user/shell.rst8
-rw-r--r--releasenotes/notes/cli-api-ver-negotiation-9f8fd8b77ae299fd.yaml12
5 files changed, 132 insertions, 12 deletions
diff --git a/cinderclient/api_versions.py b/cinderclient/api_versions.py
index 2769e96..3000aaa 100644
--- a/cinderclient/api_versions.py
+++ b/cinderclient/api_versions.py
@@ -160,6 +160,9 @@ class APIVersion(object):
return "%s.%s" % (self.ver_major, "latest")
return "%s.%s" % (self.ver_major, self.ver_minor)
+ def get_major_version(self):
+ return "%s" % self.ver_major
+
class VersionedMethod(object):
diff --git a/cinderclient/shell.py b/cinderclient/shell.py
index ecc5862..75e42e9 100644
--- a/cinderclient/shell.py
+++ b/cinderclient/shell.py
@@ -516,6 +516,21 @@ class OpenStackCinderShell(object):
else:
return argv
+ @staticmethod
+ def _validate_input_api_version(options):
+ if not options.os_volume_api_version:
+ api_version = api_versions.APIVersion(api_versions.MAX_VERSION)
+ else:
+ api_version = api_versions.get_api_version(
+ options.os_volume_api_version)
+ return api_version
+
+ @staticmethod
+ def downgrade_warning(requested, discovered):
+ logger.warning("API version %s requested, " % requested.get_string())
+ logger.warning("downgrading to %s based on server support." %
+ discovered.get_string())
+
def main(self, argv):
# Parse args once to find version and debug settings
parser = self.get_base_parser()
@@ -527,14 +542,7 @@ class OpenStackCinderShell(object):
do_help = ('help' in argv) or (
'--help' in argv) or ('-h' in argv) or not argv
- if not options.os_volume_api_version:
- use_version = DEFAULT_MAJOR_OS_VOLUME_API_VERSION
- if do_help:
- use_version = api_versions.MAX_VERSION
- api_version = api_versions.get_api_version(use_version)
- else:
- api_version = api_versions.get_api_version(
- options.os_volume_api_version)
+ api_version = self._validate_input_api_version(options)
# build available subcommands based on version
major_version_string = "%s" % api_version.ver_major
@@ -670,9 +678,7 @@ class OpenStackCinderShell(object):
insecure = self.options.insecure
- self.cs = client.Client(
- api_version, os_username,
- os_password, os_project_name, os_auth_url,
+ client_args = dict(
region_name=os_region_name,
tenant_id=os_project_id,
endpoint_type=endpoint_type,
@@ -689,6 +695,11 @@ class OpenStackCinderShell(object):
session=auth_session,
logger=self.ks_logger if auth_session else self.client_logger)
+ self.cs = client.Client(
+ api_version, os_username,
+ os_password, os_project_name, os_auth_url,
+ **client_args)
+
try:
if not utils.isunauthenticated(args.func):
self.cs.authenticate()
@@ -718,6 +729,28 @@ class OpenStackCinderShell(object):
"to the default API version: %s",
endpoint_api_version)
+ API_MAX_VERSION = api_versions.APIVersion(api_versions.MAX_VERSION)
+ if endpoint_api_version[0] == '3':
+ disc_client = client.Client(API_MAX_VERSION,
+ os_username,
+ os_password,
+ os_project_name,
+ os_auth_url,
+ **client_args)
+ self.cs, discovered_version = self._discover_client(
+ disc_client,
+ api_version,
+ args.os_endpoint_type,
+ args.service_type,
+ os_username,
+ os_password,
+ os_project_name,
+ os_auth_url,
+ client_args)
+
+ if discovered_version < api_version:
+ self.downgrade_warning(api_version, discovered_version)
+
profile = osprofiler_profiler and options.profile
if profile:
osprofiler_profiler.init(options.profile)
@@ -731,6 +764,56 @@ class OpenStackCinderShell(object):
print("To display trace use next command:\n"
"osprofiler trace show --html %s " % trace_id)
+ def _discover_client(self,
+ current_client,
+ os_api_version,
+ os_endpoint_type,
+ os_service_type,
+ os_username,
+ os_password,
+ os_project_name,
+ os_auth_url,
+ client_args):
+
+ if (os_api_version.get_major_version() in
+ api_versions.DEPRECATED_VERSIONS):
+ discovered_version = api_versions.DEPRECATED_VERSION
+ os_service_type = 'volume'
+ else:
+ discovered_version = api_versions.discover_version(
+ current_client,
+ os_api_version)
+
+ if not os_endpoint_type:
+ os_endpoint_type = DEFAULT_CINDER_ENDPOINT_TYPE
+
+ if not os_service_type:
+ os_service_type = self._discover_service_type(discovered_version)
+
+ API_MAX_VERSION = api_versions.APIVersion(api_versions.MAX_VERSION)
+
+ if (discovered_version != API_MAX_VERSION or
+ os_service_type != 'volume' or
+ os_endpoint_type != DEFAULT_CINDER_ENDPOINT_TYPE):
+ client_args['service_type'] = os_service_type
+ client_args['endpoint_type'] = os_endpoint_type
+
+ return (client.Client(discovered_version,
+ os_username,
+ os_password,
+ os_project_name,
+ os_auth_url,
+ **client_args),
+ discovered_version)
+ else:
+ return current_client, discovered_version
+
+ def _discover_service_type(self, discovered_version):
+ SERVICE_TYPES = {'1': 'volume', '2': 'volumev2', '3': 'volumev3'}
+ major_version = discovered_version.get_major_version()
+ service_type = SERVICE_TYPES[major_version]
+ return service_type
+
def _run_extension_hooks(self, hook_type, *args, **kwargs):
"""Runs hooks for all registered extensions."""
for extension in self.extensions:
diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py
index cc3372c..f4aa598 100644
--- a/cinderclient/tests/unit/v3/test_shell.py
+++ b/cinderclient/tests/unit/v3/test_shell.py
@@ -46,6 +46,7 @@ import six
from six.moves.urllib import parse
import cinderclient
+from cinderclient import api_versions
from cinderclient import base
from cinderclient import client
from cinderclient import exceptions
@@ -92,7 +93,12 @@ class ShellTest(utils.TestCase):
self.cs = mock.Mock()
def run_command(self, cmd):
- self.shell.main(cmd.split())
+ # Ensure the version negotiation indicates that
+ # all versions are supported
+ with mock.patch('cinderclient.api_versions._get_server_version_range',
+ return_value=(api_versions.APIVersion('3.0'),
+ api_versions.APIVersion('3.99'))):
+ self.shell.main(cmd.split())
def assert_called(self, method, url, body=None,
partial_body=None, **kwargs):
@@ -295,6 +301,14 @@ class ShellTest(utils.TestCase):
mock_print.assert_called_once_with(mock.ANY, key_list,
exclude_unavailable=True, sortby_index=0)
+ @mock.patch("cinderclient.shell.OpenStackCinderShell.downgrade_warning")
+ def test_list_version_downgrade(self, mock_warning):
+ self.run_command('--os-volume-api-version 3.998 list')
+ mock_warning.assert_called_once_with(
+ api_versions.APIVersion('3.998'),
+ api_versions.APIVersion(api_versions.MAX_VERSION)
+ )
+
def test_list_availability_zone(self):
self.run_command('availability-zone-list')
self.assert_called('GET', '/os-availability-zone')
diff --git a/doc/source/user/shell.rst b/doc/source/user/shell.rst
index 0d03b01..50d8feb 100644
--- a/doc/source/user/shell.rst
+++ b/doc/source/user/shell.rst
@@ -40,6 +40,14 @@ For example, in Bash you'd use::
export OS_AUTH_URL=http://auth.example.com:5000/v3
export OS_VOLUME_API_VERSION=3
+If OS_VOLUME_API_VERSION is not set, the highest version
+supported by the server will be used.
+
+If OS_VOLUME_API_VERSION exceeds the highest version
+supported by the server, the highest version supported by
+both the client and server will be used. A warning
+message is printed when this occurs.
+
From there, all shell commands take the form::
cinder <command> [arguments...]
diff --git a/releasenotes/notes/cli-api-ver-negotiation-9f8fd8b77ae299fd.yaml b/releasenotes/notes/cli-api-ver-negotiation-9f8fd8b77ae299fd.yaml
new file mode 100644
index 0000000..4501850
--- /dev/null
+++ b/releasenotes/notes/cli-api-ver-negotiation-9f8fd8b77ae299fd.yaml
@@ -0,0 +1,12 @@
+---
+features:
+ - |
+ Automatic version negotiation for the cinderclient CLI.
+ If an API version is not specified, the CLI will use the newest
+ supported by the client and the server.
+ If an API version newer than the server supports is requested,
+ the CLI will fall back to the newest version supported by the server
+ and issue a warning message.
+ This does not affect cinderclient library usage.
+
+