26cb0f6fcb
Modify the error message for failed discovery in an attempt to better help the user fixing the issue. Include a reference to the actual exception that occured in the error message. Add SSLError to the list of caught exceptions so that we can log this case, too. Add some unit tests to verify the handling of possible exceptions during version discovery. Change-Id: I9c26ab35d5515a937e016421e26e844212cb0bb3 Closes-Bug: 1749144
226 lines
8.9 KiB
Python
226 lines
8.9 KiB
Python
# 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 abc
|
|
|
|
import six
|
|
import six.moves.urllib.parse as urlparse
|
|
|
|
from keystoneauth1 import _utils as utils
|
|
from keystoneauth1 import discover
|
|
from keystoneauth1 import exceptions
|
|
from keystoneauth1.identity import base
|
|
|
|
|
|
LOG = utils.get_logger(__name__)
|
|
|
|
|
|
@six.add_metaclass(abc.ABCMeta)
|
|
class BaseGenericPlugin(base.BaseIdentityPlugin):
|
|
"""An identity plugin that is not version dependent.
|
|
|
|
Internally we will construct a version dependent plugin with the resolved
|
|
URL and then proxy all calls from the base plugin to the versioned one.
|
|
"""
|
|
|
|
def __init__(self, auth_url,
|
|
tenant_id=None,
|
|
tenant_name=None,
|
|
project_id=None,
|
|
project_name=None,
|
|
project_domain_id=None,
|
|
project_domain_name=None,
|
|
domain_id=None,
|
|
domain_name=None,
|
|
system_scope=None,
|
|
trust_id=None,
|
|
default_domain_id=None,
|
|
default_domain_name=None,
|
|
reauthenticate=True):
|
|
super(BaseGenericPlugin, self).__init__(auth_url=auth_url,
|
|
reauthenticate=reauthenticate)
|
|
|
|
self._project_id = project_id or tenant_id
|
|
self._project_name = project_name or tenant_name
|
|
self._project_domain_id = project_domain_id
|
|
self._project_domain_name = project_domain_name
|
|
self._domain_id = domain_id
|
|
self._domain_name = domain_name
|
|
self._system_scope = system_scope
|
|
self._trust_id = trust_id
|
|
self._default_domain_id = default_domain_id
|
|
self._default_domain_name = default_domain_name
|
|
|
|
self._plugin = None
|
|
|
|
@abc.abstractmethod
|
|
def create_plugin(self, session, version, url, raw_status=None):
|
|
"""Create a plugin from the given parameters.
|
|
|
|
This function will be called multiple times with the version and url
|
|
of a potential endpoint. If a plugin can be constructed that fits the
|
|
params then it should return it. If not return None and then another
|
|
call will be made with other available URLs.
|
|
|
|
:param session: A session object.
|
|
:type session: keystoneauth1.session.Session
|
|
:param tuple version: A tuple of the API version at the URL.
|
|
:param str url: The base URL for this version.
|
|
:param str raw_status: The status that was in the discovery field.
|
|
|
|
:returns: A plugin that can match the parameters or None if nothing.
|
|
"""
|
|
return None
|
|
|
|
@property
|
|
def _has_domain_scope(self):
|
|
"""Are there domain parameters.
|
|
|
|
Domain parameters are v3 only so returns if any are set.
|
|
|
|
:returns: True if a domain parameter is set, false otherwise.
|
|
"""
|
|
return any([self._domain_id, self._domain_name,
|
|
self._project_domain_id, self._project_domain_name])
|
|
|
|
@property
|
|
def _v2_params(self):
|
|
"""Return the parameters that are common to v2 plugins."""
|
|
return {'trust_id': self._trust_id,
|
|
'tenant_id': self._project_id,
|
|
'tenant_name': self._project_name,
|
|
'reauthenticate': self.reauthenticate}
|
|
|
|
@property
|
|
def _v3_params(self):
|
|
"""Return the parameters that are common to v3 plugins."""
|
|
return {'trust_id': self._trust_id,
|
|
'system_scope': self._system_scope,
|
|
'project_id': self._project_id,
|
|
'project_name': self._project_name,
|
|
'project_domain_id': self.project_domain_id,
|
|
'project_domain_name': self.project_domain_name,
|
|
'domain_id': self._domain_id,
|
|
'domain_name': self._domain_name,
|
|
'reauthenticate': self.reauthenticate}
|
|
|
|
@property
|
|
def project_domain_id(self):
|
|
return self._project_domain_id or self._default_domain_id
|
|
|
|
@project_domain_id.setter
|
|
def project_domain_id(self, value):
|
|
self._project_domain_id = value
|
|
|
|
@property
|
|
def project_domain_name(self):
|
|
return self._project_domain_name or self._default_domain_name
|
|
|
|
@project_domain_name.setter
|
|
def project_domain_name(self, value):
|
|
self._project_domain_name = value
|
|
|
|
def _do_create_plugin(self, session):
|
|
plugin = None
|
|
|
|
try:
|
|
disc = self.get_discovery(session,
|
|
self.auth_url,
|
|
authenticated=False)
|
|
except (exceptions.DiscoveryFailure,
|
|
exceptions.HttpError,
|
|
exceptions.SSLError,
|
|
exceptions.ConnectionError) as e:
|
|
LOG.warning('Failed to discover available identity versions when '
|
|
'contacting %s. Attempting to parse version from URL.',
|
|
self.auth_url)
|
|
|
|
url_parts = urlparse.urlparse(self.auth_url)
|
|
path = url_parts.path.lower()
|
|
|
|
if path.startswith('/v2.0'):
|
|
if self._has_domain_scope:
|
|
raise exceptions.DiscoveryFailure(
|
|
'Cannot use v2 authentication with domain scope')
|
|
plugin = self.create_plugin(session, (2, 0), self.auth_url)
|
|
elif path.startswith('/v3'):
|
|
plugin = self.create_plugin(session, (3, 0), self.auth_url)
|
|
else:
|
|
raise exceptions.DiscoveryFailure(
|
|
'Could not find versioned identity endpoints when '
|
|
'attempting to authenticate. Please check that your '
|
|
'auth_url is correct. %s' % e)
|
|
|
|
else:
|
|
# NOTE(jamielennox): version_data is always in oldest to newest
|
|
# order. This is fine normally because we explicitly skip v2 below
|
|
# if there is domain data present. With default_domain params
|
|
# though we want a v3 plugin if available and fall back to v2 so we
|
|
# have to process in reverse order. FIXME(jamielennox): if we ever
|
|
# go for another version we should reverse this logic as we always
|
|
# want to favour the newest available version.
|
|
reverse = self._default_domain_id or self._default_domain_name
|
|
disc_data = disc.version_data(reverse=bool(reverse))
|
|
|
|
v2_with_domain_scope = False
|
|
for data in disc_data:
|
|
version = data['version']
|
|
|
|
if (discover.version_match((2,), version) and
|
|
self._has_domain_scope):
|
|
# NOTE(jamielennox): if there are domain parameters there
|
|
# is no point even trying against v2 APIs.
|
|
v2_with_domain_scope = True
|
|
continue
|
|
|
|
plugin = self.create_plugin(session,
|
|
version,
|
|
data['url'],
|
|
raw_status=data['raw_status'])
|
|
|
|
if plugin:
|
|
break
|
|
if not plugin and v2_with_domain_scope:
|
|
raise exceptions.DiscoveryFailure(
|
|
'Cannot use v2 authentication with domain scope')
|
|
|
|
if plugin:
|
|
return plugin
|
|
|
|
# so there were no URLs that i could use for auth of any version.
|
|
raise exceptions.DiscoveryFailure(
|
|
'Could not find versioned identity endpoints when attempting '
|
|
'to authenticate. Please check that your auth_url is correct.')
|
|
|
|
def get_auth_ref(self, session, **kwargs):
|
|
if not self._plugin:
|
|
self._plugin = self._do_create_plugin(session)
|
|
|
|
return self._plugin.get_auth_ref(session, **kwargs)
|
|
|
|
def get_cache_id_elements(self, _implemented=False):
|
|
# NOTE(jamielennox): implemented here is just a way to make sure that
|
|
# something overrides this method. We don't want the base
|
|
# implementation to respond with a dict without the subclass modifying
|
|
# it to add their own data in case the subclass doesn't support caching
|
|
if not _implemented:
|
|
raise NotImplemented()
|
|
|
|
return {'auth_url': self.auth_url,
|
|
'project_id': self._project_id,
|
|
'project_name': self._project_name,
|
|
'project_domain_id': self.project_domain_id,
|
|
'project_domain_name': self.project_domain_name,
|
|
'domain_id': self._domain_id,
|
|
'domain_name': self._domain_name,
|
|
'trust_id': self._trust_id}
|