From 491937c1a09daf3da976ed19ef645c02ab2fc2ff Mon Sep 17 00:00:00 2001 From: rabi Date: Fri, 3 Jun 2016 16:41:31 +0530 Subject: [PATCH] Add keystone_session property to context Add keystone_session property to context which is refreshed when auth_token expires. Change-Id: I834c8e96038bb919c6e09b4fea003c576414561f --- heat/common/context.py | 18 ++++++++++ heat/engine/clients/__init__.py | 8 ----- heat/engine/clients/client_plugin.py | 50 ++++++++-------------------- heat/engine/clients/os/aodh.py | 4 +-- heat/engine/clients/os/barbican.py | 3 +- heat/tests/clients/test_clients.py | 24 ++++++++----- heat/tests/fakes.py | 5 +++ 7 files changed, 54 insertions(+), 58 deletions(-) diff --git a/heat/common/context.py b/heat/common/context.py index 9fcf2ea723..f3f5ac5064 100644 --- a/heat/common/context.py +++ b/heat/common/context.py @@ -15,6 +15,7 @@ from keystoneauth1 import access from keystoneauth1.identity import access as access_plugin from keystoneauth1.identity import generic from keystoneauth1 import loading as ks_loading +from keystoneauth1 import session from keystoneauth1 import token_endpoint from oslo_config import cfg from oslo_context import context @@ -24,6 +25,7 @@ from oslo_middleware import request_id as oslo_request_id from oslo_utils import importutils import six +from heat.common import config from heat.common import endpoint_utils from heat.common import exception from heat.common.i18n import _LE, _LW @@ -101,6 +103,8 @@ class RequestContext(context.RequestContext): self.auth_url = auth_url self._session = None self._clients = None + self._keystone_session = session.Session( + **config.get_ssl_options('keystone')) self.trust_id = trust_id self.trustor_user_id = trustor_user_id self.policy = policy.Enforcer() @@ -129,12 +133,26 @@ class RequestContext(context.RequestContext): self._session = db_api.get_session() return self._session + @property + def keystone_session(self): + if self.auth_needs_refresh(): + self.reload_auth_plugin() + self.clients.invalidate_plugins() + self._keystone_session.auth = self.auth_plugin + return self._keystone_session + @property def clients(self): if self._clients is None: self._clients = clients.Clients(self) return self._clients + def auth_needs_refresh(self): + auth_ref = self.auth_plugin.get_auth_ref(self._keystone_session) + return (cfg.CONF.reauthentication_auth_method == 'trusts' + and auth_ref.will_expire_soon( + cfg.CONF.stale_token_duration)) + def to_dict(self): user_idt = '{user} {tenant}'.format(user=self.user_id or '-', tenant=self.tenant_id or '-') diff --git a/heat/engine/clients/__init__.py b/heat/engine/clients/__init__.py index fd15ce1fde..10393e2c71 100644 --- a/heat/engine/clients/__init__.py +++ b/heat/engine/clients/__init__.py @@ -83,14 +83,6 @@ class OpenStackClients(object): return client LOG.warning(_LW('Requested client "%s" not found'), name) - @property - def auth_token(self): - # Always use the auth_token from the keystone() client, as - # this may be refreshed if the context contains credentials - # which allow reissuing of a new token before the context - # auth_token expiry (e.g trust_id or username/password) - return self.client('keystone').auth_token - class ClientBackend(object): """Class for delaying choosing the backend client module. diff --git a/heat/engine/clients/client_plugin.py b/heat/engine/clients/client_plugin.py index 8d1eb89db9..85d58bd6ec 100644 --- a/heat/engine/clients/client_plugin.py +++ b/heat/engine/clients/client_plugin.py @@ -19,7 +19,6 @@ import weakref from keystoneauth1 import exceptions from keystoneauth1.identity import generic from keystoneauth1 import plugin -from keystoneauth1 import session from oslo_config import cfg import requests import six @@ -107,7 +106,6 @@ class ClientPlugin(object): self._context = weakref.ref(context) self._clients = weakref.ref(context.clients) self.invalidate() - self._keystone_session_obj = None @property def context(self): @@ -121,18 +119,6 @@ class ClientPlugin(object): _get_client_option = staticmethod(config.get_client_option) - @property - def _keystone_session(self): - # FIXME(jamielennox): This session object is essentially static as the - # options won't change. Further it is allowed to be shared by multiple - # authentication requests so there is no reason to construct it fresh - # for every client plugin. It should be global and shared amongst them. - if not self._keystone_session_obj: - self._keystone_session_obj = session.Session( - **config.get_ssl_options('keystone')) - - return self._keystone_session_obj - def invalidate(self): """Invalidate/clear any cached client.""" self._client_instances = {} @@ -141,18 +127,9 @@ class ClientPlugin(object): if not version: version = self.default_version - if version in self._client_instances: - auth_ref = self.context.auth_plugin.get_auth_ref( - self._keystone_session) - if (cfg.CONF.reauthentication_auth_method == 'trusts' - and auth_ref.will_expire_soon( - cfg.CONF.stale_token_duration)): - # If the token is near expiry, force creating a new client, - # which will get a new token via another call to auth_token - # We also have to invalidate all other cached clients - self.clients.invalidate_plugins() - else: - return self._client_instances[version] + if (version in self._client_instances + and not self.context.auth_needs_refresh()): + return self._client_instances[version] # Back-ward compatibility if version is None: @@ -174,15 +151,17 @@ class ClientPlugin(object): @property def auth_token(self): - # NOTE(jamielennox): use the session defined by the keystoneclient - # options as traditionally the token was always retrieved from - # keystoneclient. - return self.context.auth_plugin.get_token(self._keystone_session) + # Always use the auth_token from the keystone_session, as + # this may be refreshed if the context contains credentials + # which allow reissuing of a new token before the context + # auth_token expiry (e.g trust_id or username/password) + return self.context.keystone_session.get_token() def url_for(self, **kwargs): + keystone_session = self.context.keystone_session + def get_endpoint(): - auth_plugin = self.context.auth_plugin - return auth_plugin.get_endpoint(self._keystone_session, **kwargs) + return keystone_session.get_endpoint(**kwargs) # NOTE(jamielennox): use the session defined by the keystoneclient # options as traditionally the token was always retrieved from @@ -198,12 +177,11 @@ class ClientPlugin(object): try: url = get_endpoint() except exceptions.EmptyCatalog: - auth_plugin = self.context.auth_plugin - endpoint = auth_plugin.get_endpoint( + endpoint = keystone_session.get_endpoint( None, interface=plugin.AUTH_INTERFACE) - token = auth_plugin.get_token(None) + token = keystone_session.get_token(None) token_obj = generic.Token(endpoint, token) - auth_ref = token_obj.get_access(self._keystone_session) + auth_ref = token_obj.get_access(keystone_session) if auth_ref.has_service_catalog(): self.context.reload_auth_plugin() url = get_endpoint() diff --git a/heat/engine/clients/os/aodh.py b/heat/engine/clients/os/aodh.py index 11e2781bd1..5967f9051f 100644 --- a/heat/engine/clients/os/aodh.py +++ b/heat/engine/clients/os/aodh.py @@ -30,13 +30,11 @@ class AodhClientPlugin(client_plugin.ClientPlugin): default_version = V2 def _create(self, version=None): - interface = self._get_client_option(CLIENT_NAME, 'endpoint_type') - self._keystone_session.auth = self.context.auth_plugin return ac.Client( version, - session=self._keystone_session, + session=self.context.keystone_session, interface=interface, service_type=self.ALARMING) diff --git a/heat/engine/clients/os/barbican.py b/heat/engine/clients/os/barbican.py index de7cb7ded2..c5183413b4 100644 --- a/heat/engine/clients/os/barbican.py +++ b/heat/engine/clients/os/barbican.py @@ -29,9 +29,8 @@ class BarbicanClientPlugin(client_plugin.ClientPlugin): endpoint_type = self._get_client_option(CLIENT_NAME, 'endpoint_type') endpoint = self.url_for(service_type=self.KEY_MANAGER, endpoint_type=endpoint_type) - self._keystone_session.auth = self.context.auth_plugin client = barbican_client.Client( - session=self._keystone_session, endpoint=endpoint) + session=self.context.keystone_session, endpoint=endpoint) return client diff --git a/heat/tests/clients/test_clients.py b/heat/tests/clients/test_clients.py index c1b0b5c78f..bc1306bd1d 100644 --- a/heat/tests/clients/test_clients.py +++ b/heat/tests/clients/test_clients.py @@ -287,14 +287,14 @@ class ClientPluginTest(common.HeatTestCase): c = clients.Clients(con) con.clients = c - con.auth_plugin = mock.Mock(name="auth_plugin") - con.auth_plugin.get_endpoint = mock.Mock(name="get_endpoint") - con.auth_plugin.get_endpoint.return_value = 'http://192.0.2.1/foo' + con.keystone_session = mock.Mock(name="keystone_Session") + con.keystone_session.get_endpoint = mock.Mock(name="get_endpoint") + con.keystone_session.get_endpoint.return_value = 'http://192.0.2.1/foo' plugin = FooClientsPlugin(con) self.assertEqual('http://192.0.2.1/foo', plugin.url_for(service_type='foo')) - self.assertTrue(con.auth_plugin.get_endpoint.called) + self.assertTrue(con.keystone_session.get_endpoint.called) @mock.patch.object(generic, "Token", name="v3_token") def test_get_missing_service_catalog(self, mock_v3): @@ -309,10 +309,10 @@ class ClientPluginTest(common.HeatTestCase): c = clients.Clients(con) con.clients = c - con.auth_plugin = mock.Mock(name="auth_plugin") + con.keystone_session = mock.Mock(name="keystone_session") get_endpoint_side_effects = [ keystone_exc.EmptyCatalog(), None, 'http://192.0.2.1/bar'] - con.auth_plugin.get_endpoint = mock.Mock( + con.keystone_session.get_endpoint = mock.Mock( name="get_endpoint", side_effect=get_endpoint_side_effects) mock_token_obj = mock.Mock() @@ -337,9 +337,9 @@ class ClientPluginTest(common.HeatTestCase): c = clients.Clients(con) con.clients = c - con.auth_plugin = mock.Mock(name="auth_plugin") + con.keystone_session = mock.Mock(name="keystone_session") get_endpoint_side_effects = [keystone_exc.EmptyCatalog(), None] - con.auth_plugin.get_endpoint = mock.Mock( + con.keystone_session.get_endpoint = mock.Mock( name="get_endpoint", side_effect=get_endpoint_side_effects) mock_token_obj = mock.Mock() @@ -380,7 +380,13 @@ class ClientPluginTest(common.HeatTestCase): self.assertEqual(2, plugin._create.call_count) def test_create_client_on_invalidate(self): - con = mock.Mock() + cfg.CONF.set_override('reauthentication_auth_method', 'trusts', + enforce_type=True) + con = utils.dummy_context() + auth_ref = mock.Mock() + self.patchobject(con.auth_plugin, 'get_auth_ref', + return_value=auth_ref) + auth_ref.will_expire_soon.return_value = False plugin = FooClientsPlugin(con) plugin._create = mock.Mock() plugin.client() diff --git a/heat/tests/fakes.py b/heat/tests/fakes.py index 48e763e7ae..cea2f80024 100644 --- a/heat/tests/fakes.py +++ b/heat/tests/fakes.py @@ -20,6 +20,7 @@ places where actual behavior differs from the spec. from keystoneauth1 import plugin from keystoneauth1 import session +import mock from heat.common import context @@ -89,6 +90,10 @@ class FakeAuth(plugin.BaseAuthPlugin): return 'http://example.com:1234/v1' + def get_auth_ref(self, session): + auth_ref = mock.Mock() + return auth_ref + class FakeKeystoneClient(object): def __init__(self, username='test_username', password='password',