Skip to content

Commit d1b044b

Browse files
committed
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
1 parent a9d9d34 commit d1b044b

5 files changed

Lines changed: 132 additions & 12 deletions

File tree

cinderclient/api_versions.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ def get_string(self):
160160
return "%s.%s" % (self.ver_major, "latest")
161161
return "%s.%s" % (self.ver_major, self.ver_minor)
162162

163+
def get_major_version(self):
164+
return "%s" % self.ver_major
165+
163166

164167
class VersionedMethod(object):
165168

cinderclient/shell.py

Lines changed: 94 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,21 @@ def _delimit_metadata_args(self, argv):
516516
else:
517517
return argv
518518

519+
@staticmethod
520+
def _validate_input_api_version(options):
521+
if not options.os_volume_api_version:
522+
api_version = api_versions.APIVersion(api_versions.MAX_VERSION)
523+
else:
524+
api_version = api_versions.get_api_version(
525+
options.os_volume_api_version)
526+
return api_version
527+
528+
@staticmethod
529+
def downgrade_warning(requested, discovered):
530+
logger.warning("API version %s requested, " % requested.get_string())
531+
logger.warning("downgrading to %s based on server support." %
532+
discovered.get_string())
533+
519534
def main(self, argv):
520535
# Parse args once to find version and debug settings
521536
parser = self.get_base_parser()
@@ -527,14 +542,7 @@ def main(self, argv):
527542
do_help = ('help' in argv) or (
528543
'--help' in argv) or ('-h' in argv) or not argv
529544

530-
if not options.os_volume_api_version:
531-
use_version = DEFAULT_MAJOR_OS_VOLUME_API_VERSION
532-
if do_help:
533-
use_version = api_versions.MAX_VERSION
534-
api_version = api_versions.get_api_version(use_version)
535-
else:
536-
api_version = api_versions.get_api_version(
537-
options.os_volume_api_version)
545+
api_version = self._validate_input_api_version(options)
538546

539547
# build available subcommands based on version
540548
major_version_string = "%s" % api_version.ver_major
@@ -670,9 +678,7 @@ def main(self, argv):
670678

671679
insecure = self.options.insecure
672680

673-
self.cs = client.Client(
674-
api_version, os_username,
675-
os_password, os_project_name, os_auth_url,
681+
client_args = dict(
676682
region_name=os_region_name,
677683
tenant_id=os_project_id,
678684
endpoint_type=endpoint_type,
@@ -689,6 +695,11 @@ def main(self, argv):
689695
session=auth_session,
690696
logger=self.ks_logger if auth_session else self.client_logger)
691697

698+
self.cs = client.Client(
699+
api_version, os_username,
700+
os_password, os_project_name, os_auth_url,
701+
**client_args)
702+
692703
try:
693704
if not utils.isunauthenticated(args.func):
694705
self.cs.authenticate()
@@ -718,6 +729,28 @@ def main(self, argv):
718729
"to the default API version: %s",
719730
endpoint_api_version)
720731

732+
API_MAX_VERSION = api_versions.APIVersion(api_versions.MAX_VERSION)
733+
if endpoint_api_version[0] == '3':
734+
disc_client = client.Client(API_MAX_VERSION,
735+
os_username,
736+
os_password,
737+
os_project_name,
738+
os_auth_url,
739+
**client_args)
740+
self.cs, discovered_version = self._discover_client(
741+
disc_client,
742+
api_version,
743+
args.os_endpoint_type,
744+
args.service_type,
745+
os_username,
746+
os_password,
747+
os_project_name,
748+
os_auth_url,
749+
client_args)
750+
751+
if discovered_version < api_version:
752+
self.downgrade_warning(api_version, discovered_version)
753+
721754
profile = osprofiler_profiler and options.profile
722755
if profile:
723756
osprofiler_profiler.init(options.profile)
@@ -731,6 +764,56 @@ def main(self, argv):
731764
print("To display trace use next command:\n"
732765
"osprofiler trace show --html %s " % trace_id)
733766

767+
def _discover_client(self,
768+
current_client,
769+
os_api_version,
770+
os_endpoint_type,
771+
os_service_type,
772+
os_username,
773+
os_password,
774+
os_project_name,
775+
os_auth_url,
776+
client_args):
777+
778+
if (os_api_version.get_major_version() in
779+
api_versions.DEPRECATED_VERSIONS):
780+
discovered_version = api_versions.DEPRECATED_VERSION
781+
os_service_type = 'volume'
782+
else:
783+
discovered_version = api_versions.discover_version(
784+
current_client,
785+
os_api_version)
786+
787+
if not os_endpoint_type:
788+
os_endpoint_type = DEFAULT_CINDER_ENDPOINT_TYPE
789+
790+
if not os_service_type:
791+
os_service_type = self._discover_service_type(discovered_version)
792+
793+
API_MAX_VERSION = api_versions.APIVersion(api_versions.MAX_VERSION)
794+
795+
if (discovered_version != API_MAX_VERSION or
796+
os_service_type != 'volume' or
797+
os_endpoint_type != DEFAULT_CINDER_ENDPOINT_TYPE):
798+
client_args['service_type'] = os_service_type
799+
client_args['endpoint_type'] = os_endpoint_type
800+
801+
return (client.Client(discovered_version,
802+
os_username,
803+
os_password,
804+
os_project_name,
805+
os_auth_url,
806+
**client_args),
807+
discovered_version)
808+
else:
809+
return current_client, discovered_version
810+
811+
def _discover_service_type(self, discovered_version):
812+
SERVICE_TYPES = {'1': 'volume', '2': 'volumev2', '3': 'volumev3'}
813+
major_version = discovered_version.get_major_version()
814+
service_type = SERVICE_TYPES[major_version]
815+
return service_type
816+
734817
def _run_extension_hooks(self, hook_type, *args, **kwargs):
735818
"""Runs hooks for all registered extensions."""
736819
for extension in self.extensions:

cinderclient/tests/unit/v3/test_shell.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
from six.moves.urllib import parse
4747

4848
import cinderclient
49+
from cinderclient import api_versions
4950
from cinderclient import base
5051
from cinderclient import client
5152
from cinderclient import exceptions
@@ -91,7 +92,12 @@ def setUp(self):
9192
self.cs = mock.Mock()
9293

9394
def run_command(self, cmd):
94-
self.shell.main(cmd.split())
95+
# Ensure the version negotiation indicates that
96+
# all versions are supported
97+
with mock.patch('cinderclient.api_versions._get_server_version_range',
98+
return_value=(api_versions.APIVersion('3.0'),
99+
api_versions.APIVersion('3.99'))):
100+
self.shell.main(cmd.split())
95101

96102
def assert_called(self, method, url, body=None,
97103
partial_body=None, **kwargs):
@@ -284,6 +290,14 @@ def test_list_duplicate_fields(self, mock_print):
284290
mock_print.assert_called_once_with(mock.ANY, key_list,
285291
exclude_unavailable=True, sortby_index=0)
286292

293+
@mock.patch("cinderclient.shell.OpenStackCinderShell.downgrade_warning")
294+
def test_list_version_downgrade(self, mock_warning):
295+
self.run_command('--os-volume-api-version 3.998 list')
296+
mock_warning.assert_called_once_with(
297+
api_versions.APIVersion('3.998'),
298+
api_versions.APIVersion(api_versions.MAX_VERSION)
299+
)
300+
287301
def test_list_availability_zone(self):
288302
self.run_command('availability-zone-list')
289303
self.assert_called('GET', '/os-availability-zone')

doc/source/user/shell.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ For example, in Bash you'd use::
4040
export OS_AUTH_URL=http://auth.example.com:5000/v3
4141
export OS_VOLUME_API_VERSION=3
4242

43+
If OS_VOLUME_API_VERSION is not set, the highest version
44+
supported by the server will be used.
45+
46+
If OS_VOLUME_API_VERSION exceeds the highest version
47+
supported by the server, the highest version supported by
48+
both the client and server will be used. A warning
49+
message is printed when this occurs.
50+
4351
From there, all shell commands take the form::
4452

4553
cinder <command> [arguments...]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
features:
3+
- |
4+
Automatic version negotiation for the cinderclient CLI.
5+
If an API version is not specified, the CLI will use the newest
6+
supported by the client and the server.
7+
If an API version newer than the server supports is requested,
8+
the CLI will fall back to the newest version supported by the server
9+
and issue a warning message.
10+
This does not affect cinderclient library usage.
11+
12+

0 commit comments

Comments
 (0)