From 118c9629e57f353aa49ba29c4b27e0e240788463 Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Fri, 22 Apr 2016 21:56:44 -0700 Subject: [PATCH] Expose allow parameters for URL discovery The Discover class can fiilter API versions by experimental status, deprecated status, and unknown status, and potentially more designations in the future. The parameters that control this were not exposed in the Session or Adapter, so users could not take advantage of this filtering through normal means. This patch creates an 'allow' parameter for the Adapter that will get passed down as keyword arguments into Discover.raw_version_data(). Now, given an unversioned endpoint like: $ openstack endpoint show cinder +--------------+----------------------------------+ | Field | Value | +--------------+----------------------------------+ | adminurl | http://192.168.122.183:8776 | | enabled | True | | id | 485107c1d92b41829c331a2dc82aaaeb | | internalurl | http://192.168.122.183:8776 | | publicurl | http://192.168.122.183:8776 | | region | RegionOne | | service_id | 01b4f36a173d4c59b31fc95763095373 | | service_name | cinder | | service_type | volume | +--------------+----------------------------------+ an Adapter can be used like this (this example would be expected to fail since it disallows the deprecated volume V1 API): auth = Password() sess = session.Session(auth=auth) adptr = adapter.Adapter(sess) adptr.get('/volumes', endpoint_filter={'service_type': 'volume', 'interface': 'public', 'version': 1}, allow={'allow_deprecated': False})) This is inspired by an abandoned patch to keystoneclient[1] that exposed this information as a tuple. The problem with exposing it like that is that raw_version_data() defaults allow_deprecated to True, so including 'deprecated' in the tuple or not including it would have the same result. Using a dict allows us to keep the Discover interface the same. [1] https://review.openstack.org/#/c/130159 Co-authored-by: Endre Karlson Change-Id: I54c29e1c2a4a2b02a3967f4ea108b8d2533616eb Closes-bug: #1394245 --- doc/source/using-sessions.rst | 19 ++++++++++++++++++- keystoneauth1/adapter.py | 7 ++++++- keystoneauth1/identity/base.py | 6 ++++-- keystoneauth1/session.py | 7 +++++-- keystoneauth1/tests/unit/test_session.py | 8 +++++++- 5 files changed, 40 insertions(+), 7 deletions(-) diff --git a/doc/source/using-sessions.rst b/doc/source/using-sessions.rst index 08866c56..60fda6fb 100644 --- a/doc/source/using-sessions.rst +++ b/doc/source/using-sessions.rst @@ -158,7 +158,24 @@ The endpoint filter is a simple key-value filter and can be provided with any number of arguments. It is then up to the auth plugin to correctly use the parameters it understands. -The session object determines the URL matching the filter and append to it the +If you want to further limit your service discovery by allowing experimental +APIs or disallowing deprecated APIs, you can use the ``allow`` parameter:: + + >>> resp = session.get('//volumes', + endpoint_filter={'service_type': 'volume', + 'interface': 'public', + 'version': 1}, + allow={'allow_deprecated': False}) + +The discoverable types of endpoints that `allow` can recognize are: + +- `allow_deprecated`: Allow experimental version endpoints. + +- `allow_experimental`: Allow deprecated version endpoints. + +- `allow_unknown`: Allow endpoints with an unrecognised status. + +The session object determines the URL matching the filters and append to it the provided path and so create a valid request. If multiple URL matches are found then any one may be chosen. diff --git a/keystoneauth1/adapter.py b/keystoneauth1/adapter.py index bf0ec519..a82b9bf9 100644 --- a/keystoneauth1/adapter.py +++ b/keystoneauth1/adapter.py @@ -43,13 +43,15 @@ class Adapter(object): :param logger: A logging object to use for requests that pass through this adapter. :type logger: logging.Logger + :param dict allow: Extra filters to pass when discovering API versions. + (optional) """ @positional() def __init__(self, session, service_type=None, service_name=None, interface=None, region_name=None, endpoint_override=None, version=None, auth=None, user_agent=None, - connect_retries=None, logger=None): + connect_retries=None, logger=None, allow={}): # NOTE(jamielennox): when adding new parameters to adapter please also # add them to the adapter call in httpclient.HTTPClient.__init__ as # well as to load_adapter_from_argparse below if the argument is @@ -66,6 +68,7 @@ class Adapter(object): self.auth = auth self.connect_retries = connect_retries self.logger = logger + self.allow = allow def _set_endpoint_filter_kwargs(self, kwargs): if self.service_type: @@ -94,6 +97,8 @@ class Adapter(object): kwargs.setdefault('connect_retries', self.connect_retries) if self.logger: kwargs.setdefault('logger', self.logger) + if self.allow: + kwargs.setdefault('allow', self.allow) return self.session.request(url, method, **kwargs) diff --git a/keystoneauth1/identity/base.py b/keystoneauth1/identity/base.py index 622bd9fd..eda92a6c 100644 --- a/keystoneauth1/identity/base.py +++ b/keystoneauth1/identity/base.py @@ -158,7 +158,7 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin): def get_endpoint(self, session, service_type=None, interface=None, region_name=None, service_name=None, version=None, - **kwargs): + allow={}, **kwargs): """Return a valid endpoint for a service. If a valid token is not present then a new one will be fetched using @@ -180,6 +180,8 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin): (optional) :param tuple version: The minimum version number required for this endpoint. (optional) + :param dict allow: Extra filters to pass when discovering API + versions. (optional) :raises keystoneauth1.exceptions.http.HttpError: An error from an invalid HTTP response. @@ -237,7 +239,7 @@ class BaseIdentityPlugin(plugin.BaseAuthPlugin): 'Fallback to using that endpoint as the base url.', url) else: - url = disc.url_for(version) + url = disc.url_for(version, **allow) return url diff --git a/keystoneauth1/session.py b/keystoneauth1/session.py index d8882799..63e826c0 100644 --- a/keystoneauth1/session.py +++ b/keystoneauth1/session.py @@ -291,7 +291,7 @@ class Session(object): endpoint_filter=None, auth=None, requests_auth=None, raise_exc=True, allow_reauth=True, log=True, endpoint_override=None, connect_retries=0, logger=_logger, - **kwargs): + allow={}, **kwargs): """Send an HTTP request with the specified characteristics. Wrapper around `requests.Session.request` to handle tasks such as @@ -357,6 +357,8 @@ class Session(object): If not provided the keystoneauth1.session default logger will be used. :type logger: logging.Logger + :param dict allow: Extra filters to pass when discovering API + versions. (optional) :param kwargs: any other parameter that can be passed to :meth:`requests.Session.request` (such as `headers`). Except: @@ -398,7 +400,8 @@ class Session(object): if endpoint_override: base_url = endpoint_override % _StringFormatter(self, auth) elif endpoint_filter: - base_url = self.get_endpoint(auth, **endpoint_filter) + base_url = self.get_endpoint(auth, allow=allow, + **endpoint_filter) if not base_url: raise exceptions.EndpointNotFound() diff --git a/keystoneauth1/tests/unit/test_session.py b/keystoneauth1/tests/unit/test_session.py index 94e545be..cc6bc799 100644 --- a/keystoneauth1/tests/unit/test_session.py +++ b/keystoneauth1/tests/unit/test_session.py @@ -717,6 +717,9 @@ class AdapterTest(utils.TestCase): REGION_NAME = uuid.uuid4().hex USER_AGENT = uuid.uuid4().hex VERSION = uuid.uuid4().hex + ALLOW = {'allow_deprecated': False, + 'allow_experimental': True, + 'allow_unknown': True} TEST_URL = CalledAuthPlugin.ENDPOINT @@ -730,7 +733,8 @@ class AdapterTest(utils.TestCase): interface=self.INTERFACE, region_name=self.REGION_NAME, user_agent=self.USER_AGENT, - version=self.VERSION) + version=self.VERSION, + allow=self.ALLOW) def _verify_endpoint_called(self, adpt): self.assertEqual(self.SERVICE_TYPE, @@ -752,6 +756,8 @@ class AdapterTest(utils.TestCase): self.assertEqual(resp.text, response) self._verify_endpoint_called(adpt) + self.assertEqual(self.ALLOW, + adpt.auth.endpoint_arguments['allow']) self.assertTrue(adpt.auth.get_token_called) self.assertRequestHeaderEqual('User-Agent', self.USER_AGENT)