diff --git a/keystoneauth1/identity/base.py b/keystoneauth1/identity/base.py index 09d0e918..9cb5a18b 100644 --- a/keystoneauth1/identity/base.py +++ b/keystoneauth1/identity/base.py @@ -43,7 +43,6 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin): self.auth_ref = None self.reauthenticate = reauthenticate - self._discovery_cache = {} self._lock = threading.Lock() @abc.abstractmethod diff --git a/keystoneauth1/plugin.py b/keystoneauth1/plugin.py index 3551a0d3..7f91c85f 100644 --- a/keystoneauth1/plugin.py +++ b/keystoneauth1/plugin.py @@ -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 """ - return None + 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. diff --git a/keystoneauth1/tests/unit/test_discovery.py b/keystoneauth1/tests/unit/test_discovery.py index ec5a15c4..d4ad7fdb 100644 --- a/keystoneauth1/tests/unit/test_discovery.py +++ b/keystoneauth1/tests/unit/test_discovery.py @@ -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, diff --git a/keystoneauth1/token_endpoint.py b/keystoneauth1/token_endpoint.py index 675d4c13..9c90eb49 100644 --- a/keystoneauth1/token_endpoint.py +++ b/keystoneauth1/token_endpoint.py @@ -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.