Add version discovery support to BaseAuthPlugin

The new 'none' auth plugin and the old 'admin_token' plugin
are subclasses of BaseAuthPluign, not BaseIdentityPlugin.
That means if someone does:

  s = session.Session(noauth.NoAuth())
  a = adapter.Adapter(s, endpoint_override='https://example.com')

to get an Adapter on an endpoint using the none plugin, then does
either:

  a.get_api_major_version()

or:

  a.get_endpoint_data()

it will fail because the none plugin doesn't have those methods.

There is, however, nothing about those methods that necessarily needs
authentication. That is, they can work just fine in contexts without
a keystone token or without authentication of any sort.

Ironic/Bifrost is specifically a usecase here, as standalone Ironic
wants to use the 'none' plugin, but consuming the API still needs to
get microversion info from the given endpoint.

Add methods to BaseAuthPlugin that take less arguments since the ones
about finding services in catalogs make zero sense in none/admin_token
context.

Change-Id: Id9bd19cca68206fc64d23b0eaa95aa3e5b01b676
This commit is contained in:
Monty Taylor 2017-09-05 11:55:26 -05:00
parent 4b15f5706a
commit 46286b1cf9
No known key found for this signature in database
GPG Key ID: 7BAE94BC7141A594
4 changed files with 179 additions and 2 deletions

View File

@ -43,7 +43,6 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin):
self.auth_ref = None
self.reauthenticate = reauthenticate
self._discovery_cache = {}
self._lock = threading.Lock()
@abc.abstractmethod

View File

@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1 import discover
# NOTE(jamielennox): The AUTH_INTERFACE is a special value that can be
# requested from get_endpoint. If a plugin receives this as the value of
# 'interface' it should return the initial URL that was passed to the plugin.
@ -27,6 +29,9 @@ class BaseAuthPlugin(object):
"""
def __init__(self):
self._discovery_cache = {}
def get_token(self, session, **kwargs):
"""Obtain a token.
@ -94,6 +99,58 @@ class BaseAuthPlugin(object):
return {IDENTITY_AUTH_HEADER_NAME: token}
def get_endpoint_data(self, session,
endpoint_override=None,
discover_versions=True,
**kwargs):
"""Return a valid endpoint data for a the service.
:param session: A session object that can be used for communication.
:type session: keystoneauth1.session.Session
:param str endpoint_override: URL to use for version discovery.
:param bool discover_versions: Whether to get version metadata from
the version discovery document even
if it major api version info can be
inferred from the url.
(optional, defaults to True)
:param kwargs: Ignored.
:raises keystoneauth1.exceptions.http.HttpError: An error from an
invalid HTTP response.
:return: Valid EndpointData or None if not available.
:rtype: `keystoneauth1.discover.EndpointData` or None
"""
if not endpoint_override:
return None
endpoint_data = discover.EndpointData(catalog_url=endpoint_override)
if endpoint_data.api_version and not discover_versions:
return endpoint_data
return endpoint_data.get_versioned_data(
session, cache=self._discovery_cache,
discover_versions=discover_versions)
def get_api_major_version(self, session, endpoint_override=None, **kwargs):
"""Get the major API version from the endpoint.
:param session: A session object that can be used for communication.
:type session: keystoneauth1.session.Session
:param str endpoint_override: URL to use for version discovery.
:param kwargs: Ignored.
:raises keystoneauth1.exceptions.http.HttpError: An error from an
invalid HTTP response.
:return: Valid EndpointData or None if not available.
:rtype: `keystoneauth1.discover.EndpointData` or None
"""
endpoint_data = self.get_endpoint_data(
session, endpoint_override=endpoint_override,
discover_versions=False, **kwargs)
return endpoint_data.api_version
def get_endpoint(self, session, **kwargs):
"""Return an endpoint for the client.
@ -114,7 +171,11 @@ class BaseAuthPlugin(object):
service or None if not available.
:rtype: string
"""
endpoint_data = self.get_endpoint_data(
session, discover_versions=False, **kwargs)
if not endpoint_data:
return None
return endpoint_data.url
def get_connection_params(self, session, **kwargs):
"""Return any additional connection parameters required for the plugin.

View File

@ -16,11 +16,14 @@ import re
from testtools import matchers
from keystoneauth1 import adapter
from keystoneauth1 import discover
from keystoneauth1 import exceptions
from keystoneauth1 import fixture
from keystoneauth1 import noauth
from keystoneauth1 import session
from keystoneauth1.tests.unit import utils
from keystoneauth1 import token_endpoint
BASE_HOST = 'http://keystone.example.com'
@ -556,6 +559,92 @@ class VersionDataTests(utils.TestCase):
# Badly-formatted next_min_version
test_exc({'next_min_version': 'bogus', 'not_before': '2019-07-01'})
def test_endpoint_data_noauth_discover(self):
mock = self.requests_mock.get(
V3_URL, status_code=200, json=V3_VERSION_ENTRY)
plugin = noauth.NoAuth()
data = plugin.get_endpoint_data(self.session, endpoint_override=V3_URL)
self.assertEqual(data.api_version, (3, 0))
self.assertEqual(data.url, V3_URL)
self.assertEqual(
plugin.get_api_major_version(
self.session, endpoint_override=V3_URL),
(3, 0))
self.assertEqual(
plugin.get_endpoint(self.session, endpoint_override=V3_URL),
V3_URL)
self.assertTrue(mock.called_once)
def test_endpoint_data_noauth_no_discover(self):
plugin = noauth.NoAuth()
data = plugin.get_endpoint_data(
self.session, endpoint_override=V3_URL, discover_versions=False)
self.assertEqual(data.api_version, (3, 0))
self.assertEqual(data.url, V3_URL)
self.assertEqual(
plugin.get_api_major_version(
self.session, endpoint_override=V3_URL),
(3, 0))
self.assertEqual(
plugin.get_endpoint(self.session, endpoint_override=V3_URL),
V3_URL)
def test_endpoint_data_noauth_adapter(self):
mock = self.requests_mock.get(
V3_URL, status_code=200, json=V3_VERSION_ENTRY)
client = adapter.Adapter(
session.Session(noauth.NoAuth()),
endpoint_override=V3_URL)
data = client.get_endpoint_data()
self.assertEqual(data.api_version, (3, 0))
self.assertEqual(data.url, V3_URL)
self.assertEqual(client.get_api_major_version(), (3, 0))
self.assertEqual(client.get_endpoint(), V3_URL)
self.assertTrue(mock.called_once)
def test_endpoint_data_token_endpoint_discover(self):
mock = self.requests_mock.get(
V3_URL, status_code=200, json=V3_VERSION_ENTRY)
plugin = token_endpoint.Token(endpoint=V3_URL, token='bogus')
data = plugin.get_endpoint_data(self.session)
self.assertEqual(data.api_version, (3, 0))
self.assertEqual(data.url, V3_URL)
self.assertEqual(plugin.get_api_major_version(self.session), (3, 0))
self.assertEqual(plugin.get_endpoint(self.session), V3_URL)
self.assertTrue(mock.called_once)
def test_endpoint_data_token_endpoint_no_discover(self):
plugin = token_endpoint.Token(endpoint=V3_URL, token='bogus')
data = plugin.get_endpoint_data(self.session, discover_versions=False)
self.assertEqual(data.api_version, (3, 0))
self.assertEqual(data.url, V3_URL)
self.assertEqual(plugin.get_api_major_version(self.session), (3, 0))
self.assertEqual(plugin.get_endpoint(self.session), V3_URL)
def test_endpoint_data_token_endpoint_adapter(self):
mock = self.requests_mock.get(
V3_URL, status_code=200, json=V3_VERSION_ENTRY)
plugin = token_endpoint.Token(endpoint=V3_URL, token='bogus')
client = adapter.Adapter(session.Session(plugin))
data = client.get_endpoint_data()
self.assertEqual(data.api_version, (3, 0))
self.assertEqual(data.url, V3_URL)
self.assertEqual(client.get_api_major_version(), (3, 0))
self.assertEqual(client.get_endpoint(), V3_URL)
self.assertTrue(mock.called_once)
def test_data_for_url(self):
mock = self.requests_mock.get(V3_URL,
status_code=200,

View File

@ -21,6 +21,7 @@ class Token(plugin.BaseAuthPlugin):
"""
def __init__(self, endpoint, token):
super(Token, self).__init__()
# NOTE(jamielennox): endpoint is reserved for when plugins
# can be used to provide that information
self.endpoint = endpoint
@ -29,6 +30,33 @@ class Token(plugin.BaseAuthPlugin):
def get_token(self, session):
return self.token
def get_endpoint_data(self, session,
endpoint_override=None,
discover_versions=True, **kwargs):
"""Return a valid endpoint data for a the service.
:param session: A session object that can be used for communication.
:type session: keystoneauth1.session.Session
:param str endpoint_override: URL to use for version discovery other
than the endpoint stored in the plugin.
(optional, defaults to None)
:param bool discover_versions: Whether to get version metadata from
the version discovery document even
if it major api version info can be
inferred from the url.
(optional, defaults to True)
:param kwargs: Ignored.
:raises keystoneauth1.exceptions.http.HttpError: An error from an
invalid HTTP response.
:return: Valid EndpointData or None if not available.
:rtype: `keystoneauth1.discover.EndpointData` or None
"""
return super(Token, self).get_endpoint_data(
session, endpoint_override=endpoint_override or self.endpoint,
discover_versions=discover_versions, **kwargs)
def get_endpoint(self, session, **kwargs):
"""Return the supplied endpoint.