Autonegotiate API version for shell

If OS_VOLUME_API_VERSION is not set, use the highest
supported by both the client and the server.

If OS_VOLUME_API_VERSION exceeds that supported by the server,
use the highest supported by both the client and the server.
A warning message is printed for the user indicating that this
happened.

(This is similar to the behavior of the manila CLI, and is
mostly code from manilaclient tweaked to work in cinderclient.)

Change-Id: Ie1403eca2a191f62169e60c0cde1622575327387
This commit is contained in:
Eric Harney 2019-06-10 13:07:37 -04:00
parent a9d9d34778
commit d1b044b82a
5 changed files with 132 additions and 12 deletions

View File

@ -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):

View File

@ -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:

View File

@ -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
@ -91,7 +92,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):
@ -284,6 +290,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')

View File

@ -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...]

View File

@ -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.