summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--nova/tests/unit/volume/test_cinder.py20
-rw-r--r--nova/volume/cinder.py144
2 files changed, 127 insertions, 37 deletions
diff --git a/nova/tests/unit/volume/test_cinder.py b/nova/tests/unit/volume/test_cinder.py
index cfc8fa4ec5..e286336ecb 100644
--- a/nova/tests/unit/volume/test_cinder.py
+++ b/nova/tests/unit/volume/test_cinder.py
@@ -1007,13 +1007,14 @@ class CinderClientTestCase(test.NoDBTestCase):
get_volume_api.assert_called_once_with(
self.mock_session.get_endpoint.return_value)
+ @mock.patch('nova.volume.cinder._get_highest_client_server_version',
+ # Fake the case that cinder is really old.
+ return_value=cinder_api_versions.APIVersion('2.0'))
@mock.patch('cinderclient.client.get_volume_api_from_url',
return_value='3')
- @mock.patch('cinderclient.client.get_highest_client_server_version',
- return_value=2.0) # Fake the case that cinder is really old.
def test_create_v3_client_with_microversion_too_new(self,
- get_highest_version,
- get_volume_api):
+ get_volume_api,
+ get_highest_version):
"""Tests that creating a v3 client and requesting a microversion that
is either too new for the server (or client) to support raises an
exception.
@@ -1023,10 +1024,11 @@ class CinderClientTestCase(test.NoDBTestCase):
get_volume_api.assert_called_once_with(
self.mock_session.get_endpoint.return_value)
get_highest_version.assert_called_once_with(
- self.mock_session.get_endpoint.return_value)
+ self.ctxt, self.mock_session.get_endpoint.return_value)
- @mock.patch('cinderclient.client.get_highest_client_server_version',
- return_value=cinder_api_versions.MAX_VERSION)
+ @mock.patch('nova.volume.cinder._get_highest_client_server_version',
+ return_value=cinder_api_versions.APIVersion(
+ cinder_api_versions.MAX_VERSION))
@mock.patch('cinderclient.client.get_volume_api_from_url',
return_value='3')
def test_create_v3_client_with_microversion_available(self,
@@ -1042,9 +1044,9 @@ class CinderClientTestCase(test.NoDBTestCase):
get_volume_api.assert_called_once_with(
self.mock_session.get_endpoint.return_value)
get_highest_version.assert_called_once_with(
- self.mock_session.get_endpoint.return_value)
+ self.ctxt, self.mock_session.get_endpoint.return_value)
- @mock.patch('cinderclient.client.get_highest_client_server_version',
+ @mock.patch('nova.volume.cinder._get_highest_client_server_version',
new_callable=mock.NonCallableMock) # asserts not called
@mock.patch('cinderclient.client.get_volume_api_from_url',
return_value='3')
diff --git a/nova/volume/cinder.py b/nova/volume/cinder.py
index 9337e71e9a..cd513f7191 100644
--- a/nova/volume/cinder.py
+++ b/nova/volume/cinder.py
@@ -29,10 +29,12 @@ from cinderclient import exceptions as cinder_exception
from keystoneauth1 import exceptions as keystone_exception
from keystoneauth1 import loading as ks_loading
from oslo_log import log as logging
+from oslo_serialization import jsonutils
from oslo_utils import encodeutils
from oslo_utils import excutils
from oslo_utils import strutils
import six
+from six.moves import urllib
from nova import availability_zones as az
import nova.conf
@@ -72,10 +74,103 @@ def _load_auth_plugin(conf):
raise cinder_exception.Unauthorized(401, message=err_msg)
-def _check_microversion(url, microversion):
+def _load_session():
+ global _SESSION
+
+ if not _SESSION:
+ _SESSION = ks_loading.load_session_from_conf_options(
+ CONF, nova.conf.cinder.cinder_group.name)
+
+
+def _get_auth(context):
+ global _ADMIN_AUTH
+ # NOTE(lixipeng): Auth token is none when call
+ # cinder API from compute periodic tasks, context
+ # from them generated from 'context.get_admin_context'
+ # which only set is_admin=True but is without token.
+ # So add load_auth_plugin when this condition appear.
+ if context.is_admin and not context.auth_token:
+ if not _ADMIN_AUTH:
+ _ADMIN_AUTH = _load_auth_plugin(CONF)
+ return _ADMIN_AUTH
+ else:
+ return service_auth.get_auth_plugin(context)
+
+
+# NOTE(efried): Bug #1752152
+# This method is copied/adapted from cinderclient.client.get_server_version so
+# we can use _SESSION.get rather than a raw requests.get to retrieve the
+# version document. This enables HTTPS by gleaning cert info from the session
+# config.
+def _get_server_version(context, url):
+ """Queries the server via the naked endpoint and gets version info.
+
+ :param context: The nova request context for auth.
+ :param url: url of the cinder endpoint
+ :returns: APIVersion object for min and max version supported by
+ the server
+ """
+ min_version = "2.0"
+ current_version = "2.0"
+
+ _load_session()
+ auth = _get_auth(context)
+
+ try:
+ u = urllib.parse.urlparse(url)
+ version_url = None
+
+ # NOTE(andreykurilin): endpoint URL has at least 2 formats:
+ # 1. The classic (legacy) endpoint:
+ # http://{host}:{optional_port}/v{1 or 2 or 3}/{project-id}
+ # http://{host}:{optional_port}/v{1 or 2 or 3}
+ # 3. Under wsgi:
+ # http://{host}:{optional_port}/volume/v{1 or 2 or 3}
+ for ver in ['v1', 'v2', 'v3']:
+ if u.path.endswith(ver) or "/{0}/".format(ver) in u.path:
+ path = u.path[:u.path.rfind(ver)]
+ version_url = '%s://%s%s' % (u.scheme, u.netloc, path)
+ break
+
+ if not version_url:
+ # NOTE(andreykurilin): probably, it is one of the next cases:
+ # * https://volume.example.com/
+ # * https://example.com/volume
+ # leave as is without cropping.
+ version_url = url
+
+ response = _SESSION.get(version_url, auth=auth)
+ data = jsonutils.loads(response.text)
+ versions = data['versions']
+ for version in versions:
+ if '3.' in version['version']:
+ min_version = version['min_version']
+ current_version = version['version']
+ break
+ except cinder_exception.ClientException as e:
+ LOG.warning("Error in server version query:%s\n"
+ "Returning APIVersion 2.0", six.text_type(e.message))
+ return (cinder_api_versions.APIVersion(min_version),
+ cinder_api_versions.APIVersion(current_version))
+
+
+# NOTE(efried): Bug #1752152
+# This method is copied/adapted from
+# cinderclient.client.get_highest_client_server_version. See note on
+# _get_server_version.
+def _get_highest_client_server_version(context, url):
+ """Returns highest APIVersion supported version by client and server."""
+ min_server, max_server = _get_server_version(context, url)
+ max_client = cinder_api_versions.APIVersion(
+ cinder_api_versions.MAX_VERSION)
+ return min(max_server, max_client)
+
+
+def _check_microversion(context, url, microversion):
"""Checks to see if the requested microversion is supported by the current
version of python-cinderclient and the volume API endpoint.
+ :param context: The nova request context for auth.
:param url: Cinder API endpoint URL.
:param microversion: Requested microversion. If not available at the given
API endpoint URL, a CinderAPIVersionNotAvailable exception is raised.
@@ -83,10 +178,7 @@ def _check_microversion(url, microversion):
construct the cinder v3 client object.
:raises: CinderAPIVersionNotAvailable if the microversion is not available.
"""
- max_api_version = cinder_client.get_highest_client_server_version(url)
- # get_highest_client_server_version returns a float which we need to cast
- # to a str and create an APIVersion object to do our version comparison.
- max_api_version = cinder_api_versions.APIVersion(str(max_api_version))
+ max_api_version = _get_highest_client_server_version(context, url)
# Check if the max_api_version matches the requested minimum microversion.
if max_api_version.matches(microversion):
# The requested microversion is supported by the client and the server.
@@ -95,24 +187,9 @@ def _check_microversion(url, microversion):
def _get_cinderclient_parameters(context):
- global _ADMIN_AUTH
- global _SESSION
-
- if not _SESSION:
- _SESSION = ks_loading.load_session_from_conf_options(
- CONF, nova.conf.cinder.cinder_group.name)
+ _load_session()
- # NOTE(lixipeng): Auth token is none when call
- # cinder API from compute periodic tasks, context
- # from them generated from 'context.get_admin_context'
- # which only set is_admin=True but is without token.
- # So add load_auth_plugin when this condition appear.
- if context.is_admin and not context.auth_token:
- if not _ADMIN_AUTH:
- _ADMIN_AUTH = _load_auth_plugin(CONF)
- auth = _ADMIN_AUTH
- else:
- auth = service_auth.get_auth_plugin(context)
+ auth = _get_auth(context)
url = None
@@ -132,13 +209,13 @@ def _get_cinderclient_parameters(context):
def is_microversion_supported(context, microversion):
+ # NOTE(efried): Work around bug #1752152. Call the cinderclient() builder
+ # in a way that just does a microversion check.
+ cinderclient(context, microversion=microversion, check_only=True)
- _, _, url = _get_cinderclient_parameters(context)
-
- _check_microversion(url, microversion)
-
-def cinderclient(context, microversion=None, skip_version_check=False):
+def cinderclient(context, microversion=None, skip_version_check=False,
+ check_only=False):
"""Constructs a cinder client object for making API requests.
:param context: The nova request context for auth.
@@ -150,6 +227,14 @@ def cinderclient(context, microversion=None, skip_version_check=False):
requested, the version discovery check is skipped and the microversion
is used directly. This should only be used if a previous check for the
same microversion was successful.
+ :param check_only: If True, don't build the actual client; just do the
+ setup and version checking.
+ :raises: UnsupportedCinderAPIVersion if a major version other than 3 is
+ requested.
+ :raises: CinderAPIVersionNotAvailable if microversion checking is requested
+ and the specified microversion is higher than what the service can
+ handle.
+ :returns: A cinderclient.client.Client wrapper, unless check_only is False.
"""
endpoint_override = None
@@ -173,7 +258,10 @@ def cinderclient(context, microversion=None, skip_version_check=False):
if skip_version_check:
version = microversion
else:
- version = _check_microversion(url, microversion)
+ version = _check_microversion(context, url, microversion)
+
+ if check_only:
+ return
return cinder_client.Client(version,
session=_SESSION,