Refactor service user authentication

Currently we have very similar service user authentication code
partially duplicated in the following areas:

  * nova/image/glance.py
  * nova/network/neutron.py
  * nova/volume/cinder.py
  * nova/api/metadata/vendordata_dynamic.py

This attempts to commonize and reuse code as much as possible from the
service_auth module in preparation of vTPM live migration patches.

Change-Id: I3a5c00e434eb6ce7956a717dffd11f38f19c5f7d
Signed-off-by: melanie witt <melwittt@gmail.com>
This commit is contained in:
melanie witt
2026-01-20 18:13:58 -08:00
parent 637f66f677
commit 66bb1e77f2
12 changed files with 160 additions and 157 deletions
+1 -1
View File
@@ -116,7 +116,7 @@ class _CyborgClient(object):
ARQ_URL = "/accelerator_requests" ARQ_URL = "/accelerator_requests"
def __init__(self, context): def __init__(self, context):
auth = service_auth.get_auth_plugin(context) auth = service_auth.get_service_user_token_auth_plugin(context)
self._client = utils.get_ksa_adapter('accelerator', ksa_auth=auth) self._client = utils.get_ksa_adapter('accelerator', ksa_auth=auth)
def _call_cyborg(self, func, *args, **kwargs): def _call_cyborg(self, func, *args, **kwargs):
+7 -16
View File
@@ -18,19 +18,16 @@
import sys import sys
from keystoneauth1 import exceptions as ks_exceptions from keystoneauth1 import exceptions as ks_exceptions
from keystoneauth1 import loading as ks_loading
from oslo_log import log as logging from oslo_log import log as logging
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from nova.api.metadata import vendordata from nova.api.metadata import vendordata
import nova.conf import nova.conf
from nova import service_auth
CONF = nova.conf.CONF CONF = nova.conf.CONF
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
_SESSION = None
_ADMIN_AUTH = None
def _load_ks_session(conf): def _load_ks_session(conf):
"""Load session. """Load session.
@@ -38,24 +35,18 @@ def _load_ks_session(conf):
This is either an authenticated session or a requests session, depending on This is either an authenticated session or a requests session, depending on
what's configured. what's configured.
""" """
global _ADMIN_AUTH auth = service_auth.get_service_auth_plugin(
global _SESSION nova.conf.vendordata.vendordata_group.name)
if not _ADMIN_AUTH: if not auth:
_ADMIN_AUTH = ks_loading.load_auth_from_conf_options(
conf, nova.conf.vendordata.vendordata_group.name)
if not _ADMIN_AUTH:
LOG.warning('Passing insecure dynamic vendordata requests ' LOG.warning('Passing insecure dynamic vendordata requests '
'because of missing or incorrect service account ' 'because of missing or incorrect service account '
'configuration.') 'configuration.')
if not _SESSION: session = service_auth.get_service_auth_session(
_SESSION = ks_loading.load_session_from_conf_options( nova.conf.vendordata.vendordata_group.name, auth=auth)
conf, nova.conf.vendordata.vendordata_group.name,
auth=_ADMIN_AUTH)
return _SESSION return session
class DynamicVendorData(vendordata.VendorDataDriver): class DynamicVendorData(vendordata.VendorDataDriver):
+4 -11
View File
@@ -34,7 +34,6 @@ import glanceclient
from glanceclient.common import utils as glance_utils from glanceclient.common import utils as glance_utils
import glanceclient.exc import glanceclient.exc
from glanceclient.v2 import schemas from glanceclient.v2 import schemas
from keystoneauth1 import loading as ks_loading
from oslo_log import log as logging from oslo_log import log as logging
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from oslo_utils import excutils from oslo_utils import excutils
@@ -52,20 +51,14 @@ from nova import utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
CONF = nova.conf.CONF CONF = nova.conf.CONF
_SESSION = None
def _session_and_auth(context): def _session_and_auth(context):
# Session is cached, but auth needs to be pulled from context each time. # Session is cached, but auth needs to be pulled from context each time.
global _SESSION session = service_auth.get_service_auth_session(
nova.conf.glance.glance_group.name)
auth = service_auth.get_service_user_token_auth_plugin(context)
if not _SESSION: return session, auth
_SESSION = ks_loading.load_session_from_conf_options(
CONF, nova.conf.glance.glance_group.name)
auth = service_auth.get_auth_plugin(context)
return _SESSION, auth
def _glanceclient_from_endpoint(context, endpoint, version): def _glanceclient_from_endpoint(context, endpoint, version):
+10 -31
View File
@@ -24,7 +24,6 @@ import inspect
import time import time
import typing as ty import typing as ty
from keystoneauth1 import loading as ks_loading
from neutronclient.common import exceptions as neutron_client_exc from neutronclient.common import exceptions as neutron_client_exc
from neutronclient.v2_0 import client as clientv20 from neutronclient.v2_0 import client as clientv20
from oslo_concurrency import lockutils from oslo_concurrency import lockutils
@@ -55,26 +54,15 @@ CONF = nova.conf.CONF
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
_SESSION = None
_ADMIN_AUTH = None
def _load_auth_plugin():
def reset_state(): auth_plugin = service_auth.get_service_auth_plugin(
global _ADMIN_AUTH nova.conf.neutron.NEUTRON_GROUP)
global _SESSION
_ADMIN_AUTH = None
_SESSION = None
def _load_auth_plugin(conf):
auth_plugin = ks_loading.load_auth_from_conf_options(conf,
nova.conf.neutron.NEUTRON_GROUP)
if auth_plugin: if auth_plugin:
return auth_plugin return auth_plugin
if conf.neutron.auth_type is None: if CONF.neutron.auth_type is None:
# If we're coming in through a REST API call for something like # If we're coming in through a REST API call for something like
# creating a server, the end user is going to get a 500 response # creating a server, the end user is going to get a 500 response
# which is accurate since the system is mis-configured, but we should # which is accurate since the system is mis-configured, but we should
@@ -84,7 +72,7 @@ def _load_auth_plugin(conf):
'service endpoint. See the networking service install guide ' 'service endpoint. See the networking service install guide '
'for details: ' 'for details: '
'https://docs.openstack.org/neutron/latest/install/') 'https://docs.openstack.org/neutron/latest/install/')
err_msg = _('Unknown auth type: %s') % conf.neutron.auth_type err_msg = _('Unknown auth type: %s') % CONF.neutron.auth_type
raise neutron_client_exc.Unauthorized(message=err_msg) raise neutron_client_exc.Unauthorized(message=err_msg)
@@ -223,33 +211,24 @@ def _get_auth_plugin(context, admin=False):
# neutron admin tenant credentials if it is an admin context. This is to # neutron admin tenant credentials if it is an admin context. This is to
# support some services (metadata API) where an admin context is used # support some services (metadata API) where an admin context is used
# without an auth token. # without an auth token.
global _ADMIN_AUTH
user_auth = None user_auth = None
if admin or (context.is_admin and not context.auth_token): if admin or (context.is_admin and not context.auth_token):
if not _ADMIN_AUTH: user_auth = _load_auth_plugin()
_ADMIN_AUTH = _load_auth_plugin(CONF)
user_auth = _ADMIN_AUTH
if context.auth_token or user_auth: if context.auth_token or user_auth:
# When user_auth = None, user_auth will be extracted from the context. # When user_auth = None, user_auth will be extracted from the context.
return service_auth.get_auth_plugin(context, user_auth=user_auth) return service_auth.get_service_user_token_auth_plugin(
context, user_auth=user_auth)
# We did not get a user token and we should not be using # We did not get a user token and we should not be using
# an admin token so log an error # an admin token so log an error
raise exception.Unauthorized() raise exception.Unauthorized()
def _get_session():
global _SESSION
if not _SESSION:
_SESSION = ks_loading.load_session_from_conf_options(
CONF, nova.conf.neutron.NEUTRON_GROUP)
return _SESSION
def get_client(context, admin=False): def get_client(context, admin=False):
auth_plugin = _get_auth_plugin(context, admin=admin) auth_plugin = _get_auth_plugin(context, admin=admin)
session = _get_session() session = service_auth.get_service_auth_session(
nova.conf.neutron.NEUTRON_GROUP)
client_args = dict(session=session, client_args = dict(session=session,
auth=auth_plugin, auth=auth_plugin,
global_request_id=context.global_id, global_request_id=context.global_id,
+67 -19
View File
@@ -11,6 +11,11 @@
# under the License. # under the License.
import typing as ty
if ty.TYPE_CHECKING:
import keystoneauth1.plugin
from keystoneauth1 import loading as ks_loading from keystoneauth1 import loading as ks_loading
from keystoneauth1 import service_token from keystoneauth1 import service_token
from oslo_log import log as logging from oslo_log import log as logging
@@ -21,35 +26,78 @@ import nova.conf
CONF = nova.conf.CONF CONF = nova.conf.CONF
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
_SERVICE_AUTH = None # Auth plugins and auth sessions keyed by configuration group name
_AUTHS = {}
_SESSIONS = {}
def reset_globals(): def reset_globals():
"""For async unit test consistency.""" """For async unit test consistency."""
global _SERVICE_AUTH global _AUTHS, _SESSIONS
_SERVICE_AUTH = None _AUTHS = {}
_SESSIONS = {}
def get_auth_plugin(context, user_auth=None): def get_service_auth_plugin(
conf_group: str,
) -> 'keystoneauth1.plugin.BaseAuthPlugin':
"""Get an auth plugin for authentication as the service user."""
auth = _AUTHS.get(conf_group)
if not auth:
auth = ks_loading.load_auth_from_conf_options(CONF, conf_group)
_AUTHS[conf_group] = auth
return auth
def get_service_auth_session(
conf_group: str,
auth: ty.Optional['keystoneauth1.plugin.BaseAuthPlugin'] = None,
) -> 'keystoneauth1.session.Session':
"""Get a session for authentication as the service user.
An auth plugin can be optionally passed in to use to authenticate the
session.
"""
session = _SESSIONS.get(conf_group)
if not session:
session = ks_loading.load_session_from_conf_options(
CONF, conf_group, auth=auth)
_SESSIONS[conf_group] = session
return session
def get_service_user_token_auth_plugin(context, user_auth=None):
"""Dynamically get an auth plugin based on service user token config.
This function will use [service_user]send_service_user_token configuration
to determine whether to return either:
* The user's auth from the RequestContext
or
* A wrapper around both the user's auth and the service user's auth
The user's auth may be optionally passed in to use instead grabbing it
from the RequestContext. This comes up in cases where we have an anonymous
RequestContext such as using get_admin_context() in nova-manage commands to
call other service APIs.
This function should only be used for passing service user tokens to APIs.
"""
# user_auth may be passed in when the RequestContext is anonymous, such as # user_auth may be passed in when the RequestContext is anonymous, such as
# when get_admin_context() is used for API calls by nova-manage. # when get_admin_context() is used for API calls by nova-manage.
user_auth = user_auth or context.get_auth_plugin() user_auth = user_auth or context.get_auth_plugin()
if CONF.service_user.send_service_user_token: if CONF.service_user.send_service_user_token:
global _SERVICE_AUTH service_auth = get_service_auth_plugin(
if not _SERVICE_AUTH: nova.conf.service_token.SERVICE_USER_GROUP)
_SERVICE_AUTH = ks_loading.load_auth_from_conf_options(
CONF,
group=
nova.conf.service_token.SERVICE_USER_GROUP)
if _SERVICE_AUTH is None:
# This indicates a misconfiguration so log a warning and
# return the user_auth.
LOG.warning('Unable to load auth from [service_user] '
'configuration. Ensure "auth_type" is set.')
return user_auth
return service_token.ServiceTokenAuthWrapper(
user_auth=user_auth,
service_auth=_SERVICE_AUTH)
if service_auth is None:
# This indicates a misconfiguration so log a warning and
# return the user_auth.
LOG.warning('Unable to load auth from [service_user] '
'configuration. Ensure "auth_type" is set.')
return user_auth
return service_token.ServiceTokenAuthWrapper(
user_auth=user_auth, service_auth=service_auth)
return user_auth return user_auth
+4
View File
@@ -62,6 +62,7 @@ from nova.pci import request
from nova import quota from nova import quota
from nova.scheduler.client import report from nova.scheduler.client import report
from nova.scheduler import utils as scheduler_utils from nova.scheduler import utils as scheduler_utils
import nova.service_auth
from nova.tests import fixtures as nova_fixtures from nova.tests import fixtures as nova_fixtures
from nova.tests.unit import matchers from nova.tests.unit import matchers
from nova import utils from nova import utils
@@ -335,6 +336,9 @@ class TestCase(base.BaseTestCase):
# Reset the global identity client # Reset the global identity client
nova.limit.utils.IDENTITY_CLIENT = None nova.limit.utils.IDENTITY_CLIENT = None
# Reset the global service auths and sessions
nova.service_auth.reset_globals()
def _setup_cells(self): def _setup_cells(self):
"""Setup a normal cellsv2 environment. """Setup a normal cellsv2 environment.
+5 -4
View File
@@ -361,7 +361,7 @@ class TestGetImageService(test.NoDBTestCase):
class TestCreateGlanceClient(test.NoDBTestCase): class TestCreateGlanceClient(test.NoDBTestCase):
@mock.patch.object(service_auth, 'get_auth_plugin') @mock.patch.object(service_auth, 'get_service_user_token_auth_plugin')
@mock.patch.object(ks_loading, 'load_session_from_conf_options') @mock.patch.object(ks_loading, 'load_session_from_conf_options')
@mock.patch('glanceclient.Client') @mock.patch('glanceclient.Client')
def test_glanceclient_with_ks_session(self, mock_client, mock_load, def test_glanceclient_with_ks_session(self, mock_client, mock_load,
@@ -375,14 +375,15 @@ class TestCreateGlanceClient(test.NoDBTestCase):
mock_client.side_effect = ["a", "b"] mock_client.side_effect = ["a", "b"]
# Reset the cache, so we know its empty before we start # Reset the cache, so we know its empty before we start
glance._SESSION = None service_auth.reset_globals()
result1 = glance._glanceclient_from_endpoint(ctx, endpoint, 2) result1 = glance._glanceclient_from_endpoint(ctx, endpoint, 2)
result2 = glance._glanceclient_from_endpoint(ctx, endpoint, 2) result2 = glance._glanceclient_from_endpoint(ctx, endpoint, 2)
# Ensure that session is only loaded once. # Ensure that session is only loaded once.
mock_load.assert_called_once_with(glance.CONF, "glance") mock_load.assert_called_once_with(glance.CONF, "glance", auth=None)
self.assertEqual(session, glance._SESSION) self.assertEqual(session,
service_auth.get_service_auth_session('glance'))
# Ensure new client created every time # Ensure new client created every time
client_call = mock.call(2, auth="fake_auth", client_call = mock.call(2, auth="fake_auth",
endpoint_override=endpoint, session=session, endpoint_override=endpoint, session=session,
+15 -16
View File
@@ -73,7 +73,7 @@ class TestNeutronClient(test.NoDBTestCase):
def setUp(self): def setUp(self):
super(TestNeutronClient, self).setUp() super(TestNeutronClient, self).setUp()
neutronapi.reset_state() service_auth.reset_globals()
self.addCleanup(service_auth.reset_globals) self.addCleanup(service_auth.reset_globals)
def test_ksa_adapter_loading_defaults(self): def test_ksa_adapter_loading_defaults(self):
@@ -142,12 +142,9 @@ class TestNeutronClient(test.NoDBTestCase):
self.assertIsInstance(cl.httpclient.auth, self.assertIsInstance(cl.httpclient.auth,
service_token.ServiceTokenAuthWrapper) service_token.ServiceTokenAuthWrapper)
@mock.patch('nova.service_auth._SERVICE_AUTH') @mock.patch('nova.service_auth.get_service_auth_plugin')
@mock.patch('nova.network.neutron._ADMIN_AUTH')
@mock.patch.object(ks_loading, 'load_auth_from_conf_options') @mock.patch.object(ks_loading, 'load_auth_from_conf_options')
def test_admin_with_service_token( def test_admin_with_service_token(self, mock_load, mock_service_auth):
self, mock_load, mock_admin_auth, mock_service_auth
):
self.flags(send_service_user_token=True, group='service_user') self.flags(send_service_user_token=True, group='service_user')
admin_context = context.get_admin_context() admin_context = context.get_admin_context()
@@ -155,8 +152,10 @@ class TestNeutronClient(test.NoDBTestCase):
cl = neutronapi.get_client(admin_context) cl = neutronapi.get_client(admin_context)
self.assertIsInstance(cl.httpclient.auth, self.assertIsInstance(cl.httpclient.auth,
service_token.ServiceTokenAuthWrapper) service_token.ServiceTokenAuthWrapper)
self.assertEqual(mock_admin_auth, cl.httpclient.auth.user_auth) self.assertEqual(mock_service_auth.return_value,
self.assertEqual(mock_service_auth, cl.httpclient.auth.service_auth) cl.httpclient.auth.user_auth)
self.assertEqual(mock_service_auth.return_value,
cl.httpclient.auth.service_auth)
@mock.patch.object(client.Client, "list_networks", @mock.patch.object(client.Client, "list_networks",
side_effect=exceptions.Unauthorized()) side_effect=exceptions.Unauthorized())
@@ -215,7 +214,7 @@ class TestNeutronClient(test.NoDBTestCase):
neutronapi.get_client, neutronapi.get_client,
my_context) my_context)
@mock.patch('nova.network.neutron._ADMIN_AUTH') @mock.patch('nova.service_auth.get_service_auth_plugin')
@mock.patch.object(client.Client, "list_networks", new=mock.Mock()) @mock.patch.object(client.Client, "list_networks", new=mock.Mock())
def test_reuse_admin_token(self, m): def test_reuse_admin_token(self, m):
self.flags(endpoint_override='http://anyhost/', group='neutron') self.flags(endpoint_override='http://anyhost/', group='neutron')
@@ -227,7 +226,7 @@ class TestNeutronClient(test.NoDBTestCase):
def token_vals(*args, **kwargs): def token_vals(*args, **kwargs):
return tokens.pop() return tokens.pop()
m.get_token.side_effect = token_vals m.return_value.get_token.side_effect = token_vals
client1 = neutronapi.get_client(my_context, True) client1 = neutronapi.get_client(my_context, True)
client1.list_networks(retrieve_all=False) client1.list_networks(retrieve_all=False)
@@ -243,7 +242,7 @@ class TestNeutronClient(test.NoDBTestCase):
mock_load_from_conf.return_value = None mock_load_from_conf.return_value = None
from neutronclient.common import exceptions as neutron_client_exc from neutronclient.common import exceptions as neutron_client_exc
self.assertRaises(neutron_client_exc.Unauthorized, self.assertRaises(neutron_client_exc.Unauthorized,
neutronapi._load_auth_plugin, CONF) neutronapi._load_auth_plugin)
mock_log_err.assert_called() mock_log_err.assert_called()
self.assertIn('The [neutron] section of your nova configuration file', self.assertIn('The [neutron] section of your nova configuration file',
mock_log_err.call_args[0][0]) mock_log_err.call_args[0][0])
@@ -9247,7 +9246,7 @@ class TestNeutronClientForAdminScenarios(test.NoDBTestCase):
auth_token='token') auth_token='token')
# clean global # clean global
neutronapi.reset_state() service_auth.reset_globals()
if admin_context: if admin_context:
# Note that the context does not contain a token but is # Note that the context does not contain a token but is
@@ -9260,7 +9259,7 @@ class TestNeutronClientForAdminScenarios(test.NoDBTestCase):
# the context has an auth_token. # the context has an auth_token.
context_client = neutronapi.get_client(my_context, True) context_client = neutronapi.get_client(my_context, True)
admin_auth = neutronapi._ADMIN_AUTH admin_auth = service_auth.get_service_auth_plugin('neutron')
self.assertEqual(CONF.neutron.auth_url, admin_auth.auth_url) self.assertEqual(CONF.neutron.auth_url, admin_auth.auth_url)
self.assertEqual(CONF.neutron.password, admin_auth.password) self.assertEqual(CONF.neutron.password, admin_auth.password)
@@ -9278,12 +9277,12 @@ class TestNeutronClientForAdminScenarios(test.NoDBTestCase):
self.assertIsNone(admin_auth.tenant_id) self.assertIsNone(admin_auth.tenant_id)
self.assertIsNone(admin_auth.user_id) self.assertIsNone(admin_auth.user_id)
self.assertEqual(CONF.neutron.timeout, auth_session = service_auth.get_service_auth_session('neutron')
neutronapi._SESSION.timeout) self.assertEqual(CONF.neutron.timeout, auth_session.timeout)
self.assertEqual( self.assertEqual(
token_value, token_value,
context_client.httpclient.auth.get_token(neutronapi._SESSION)) context_client.httpclient.auth.get_token(auth_session))
self.assertEqual( self.assertEqual(
CONF.neutron.endpoint_override, CONF.neutron.endpoint_override,
context_client.httpclient.get_endpoint()) context_client.httpclient.get_endpoint())
+7 -4
View File
@@ -21,6 +21,7 @@ from requests_mock.contrib import fixture
import nova.conf import nova.conf
from nova import context from nova import context
from nova import exception from nova import exception
from nova import service_auth
from nova import test from nova import test
from nova.volume import cinder from nova.volume import cinder
@@ -73,7 +74,7 @@ class BaseCinderTestCase(object):
def setUp(self): def setUp(self):
super(BaseCinderTestCase, self).setUp() super(BaseCinderTestCase, self).setUp()
cinder.reset_globals() service_auth.reset_globals()
self.requests = self.useFixture(fixture.Fixture()) self.requests = self.useFixture(fixture.Fixture())
self.api = cinder.API() self.api = cinder.API()
@@ -84,7 +85,7 @@ class BaseCinderTestCase(object):
def flags(self, *args, **kwargs): def flags(self, *args, **kwargs):
super(BaseCinderTestCase, self).flags(*args, **kwargs) super(BaseCinderTestCase, self).flags(*args, **kwargs)
cinder.reset_globals() service_auth.reset_globals()
def create_client(self): def create_client(self):
return cinder.cinderclient(self.context) return cinder.cinderclient(self.context)
@@ -128,7 +129,8 @@ class CinderV1TestCase(test.NoDBTestCase):
return_value='http://localhost:8776/v1/%(project_id)s') return_value='http://localhost:8776/v1/%(project_id)s')
fake_session = mock.Mock(get_endpoint=get_endpoint) fake_session = mock.Mock(get_endpoint=get_endpoint)
ctxt = context.get_context() ctxt = context.get_context()
with mock.patch.object(cinder, '_SESSION', fake_session): with mock.patch.object(service_auth, 'get_service_auth_session',
return_value=fake_session):
self.assertRaises(exception.UnsupportedCinderAPIVersion, self.assertRaises(exception.UnsupportedCinderAPIVersion,
cinder.cinderclient, ctxt) cinder.cinderclient, ctxt)
get_api_version.assert_called_once_with(get_endpoint.return_value) get_api_version.assert_called_once_with(get_endpoint.return_value)
@@ -148,7 +150,8 @@ class CinderV2TestCase(test.NoDBTestCase):
return_value='http://localhost:8776/v2/%(project_id)s') return_value='http://localhost:8776/v2/%(project_id)s')
fake_session = mock.Mock(get_endpoint=get_endpoint) fake_session = mock.Mock(get_endpoint=get_endpoint)
ctxt = context.get_context() ctxt = context.get_context()
with mock.patch.object(cinder, '_SESSION', fake_session): with mock.patch.object(service_auth, 'get_service_auth_session',
return_value=fake_session):
self.assertRaises(exception.UnsupportedCinderAPIVersion, self.assertRaises(exception.UnsupportedCinderAPIVersion,
cinder.cinderclient, ctxt) cinder.cinderclient, ctxt)
get_api_version.assert_called_once_with(get_endpoint.return_value) get_api_version.assert_called_once_with(get_endpoint.return_value)
+10 -8
View File
@@ -28,41 +28,43 @@ class ServiceAuthTestCase(test.NoDBTestCase):
self.addCleanup(service_auth.reset_globals) self.addCleanup(service_auth.reset_globals)
@mock.patch.object(ks_loading, 'load_auth_from_conf_options') @mock.patch.object(ks_loading, 'load_auth_from_conf_options')
def test_get_auth_plugin_no_wraps(self, mock_load): def test_get_service_user_token_auth_plugin_no_wraps(self, mock_load):
context = mock.MagicMock() context = mock.MagicMock()
context.get_auth_plugin.return_value = "fake" context.get_auth_plugin.return_value = "fake"
result = service_auth.get_auth_plugin(context) result = service_auth.get_service_user_token_auth_plugin(context)
self.assertEqual("fake", result) self.assertEqual("fake", result)
mock_load.assert_not_called() mock_load.assert_not_called()
@mock.patch.object(ks_loading, 'load_auth_from_conf_options') @mock.patch.object(ks_loading, 'load_auth_from_conf_options')
def test_get_auth_plugin_wraps(self, mock_load): def test_get_service_user_token_auth_plugin_wraps(self, mock_load):
self.flags(send_service_user_token=True, group='service_user') self.flags(send_service_user_token=True, group='service_user')
result = service_auth.get_auth_plugin(self.ctx) result = service_auth.get_service_user_token_auth_plugin(self.ctx)
self.assertIsInstance(result, service_token.ServiceTokenAuthWrapper) self.assertIsInstance(result, service_token.ServiceTokenAuthWrapper)
@mock.patch.object(ks_loading, 'load_auth_from_conf_options', @mock.patch.object(ks_loading, 'load_auth_from_conf_options',
return_value=None) return_value=None)
def test_get_auth_plugin_wraps_bad_config(self, mock_load): def test_get_service_user_token_auth_plugin_wraps_bad_config(
self, mock_load):
"""Tests the case that send_service_user_token is True but there """Tests the case that send_service_user_token is True but there
is some misconfiguration with the [service_user] section which makes is some misconfiguration with the [service_user] section which makes
KSA return None for the service user auth. KSA return None for the service user auth.
""" """
self.flags(send_service_user_token=True, group='service_user') self.flags(send_service_user_token=True, group='service_user')
result = service_auth.get_auth_plugin(self.ctx) result = service_auth.get_service_user_token_auth_plugin(self.ctx)
self.assertEqual(1, mock_load.call_count) self.assertEqual(1, mock_load.call_count)
self.assertNotIsInstance(result, service_token.ServiceTokenAuthWrapper) self.assertNotIsInstance(result, service_token.ServiceTokenAuthWrapper)
@mock.patch.object(ks_loading, 'load_auth_from_conf_options', @mock.patch.object(ks_loading, 'load_auth_from_conf_options',
new=mock.Mock()) new=mock.Mock())
def test_get_auth_plugin_user_auth(self): def test_get_service_user_token_auth_plugin_user_auth(self):
self.flags(send_service_user_token=True, group='service_user') self.flags(send_service_user_token=True, group='service_user')
user_auth = mock.Mock() user_auth = mock.Mock()
result = service_auth.get_auth_plugin(self.ctx, user_auth=user_auth) result = service_auth.get_service_user_token_auth_plugin(
self.ctx, user_auth=user_auth)
self.assertEqual(user_auth, result.user_auth) self.assertEqual(user_auth, result.user_auth)
+12 -9
View File
@@ -27,6 +27,7 @@ from oslo_utils import timeutils
import nova.conf import nova.conf
from nova import context from nova import context
from nova import exception from nova import exception
from nova import service_auth
from nova import test from nova import test
from nova.tests.unit.fake_instance import fake_instance_obj from nova.tests.unit.fake_instance import fake_instance_obj
from nova.volume import cinder from nova.volume import cinder
@@ -1219,7 +1220,7 @@ class CinderClientTestCase(test.NoDBTestCase):
def setUp(self): def setUp(self):
super(CinderClientTestCase, self).setUp() super(CinderClientTestCase, self).setUp()
cinder.reset_globals() service_auth.reset_globals()
self.ctxt = context.RequestContext('fake-user', 'fake-project') self.ctxt = context.RequestContext('fake-user', 'fake-project')
# Mock out the keystoneauth stuff. # Mock out the keystoneauth stuff.
self.mock_session = mock.Mock(autospec=session.Session) self.mock_session = mock.Mock(autospec=session.Session)
@@ -1301,27 +1302,29 @@ class CinderClientTestCase(test.NoDBTestCase):
def test_load_auth_plugin_failed(self, mock_load_from_conf, mock_log_err): def test_load_auth_plugin_failed(self, mock_load_from_conf, mock_log_err):
mock_load_from_conf.return_value = None mock_load_from_conf.return_value = None
self.assertRaises(cinder_exception.Unauthorized, self.assertRaises(cinder_exception.Unauthorized,
cinder._load_auth_plugin, CONF) cinder._load_auth_plugin)
mock_log_err.assert_called() mock_log_err.assert_called()
self.assertIn('The [cinder] section of your nova configuration file', self.assertIn('The [cinder] section of your nova configuration file',
mock_log_err.call_args[0][0]) mock_log_err.call_args[0][0])
@mock.patch('nova.volume.cinder._ADMIN_AUTH') @mock.patch('nova.service_auth.get_service_auth_plugin')
def test_admin_context_without_token(self, def test_admin_context_without_token(self,
mock_admin_auth): mock_admin_auth):
mock_admin_auth.return_value = '_FAKE_ADMIN_AUTH'
admin_ctx = context.get_admin_context() admin_ctx = context.get_admin_context()
params = cinder._get_cinderclient_parameters(admin_ctx) params = cinder._get_cinderclient_parameters(admin_ctx)
self.assertEqual(params[0], mock_admin_auth) self.assertEqual(params[0], mock_admin_auth.return_value)
@mock.patch('nova.service_auth._SERVICE_AUTH') @mock.patch('nova.service_auth.get_service_user_token_auth_plugin')
@mock.patch('nova.volume.cinder._ADMIN_AUTH') @mock.patch('nova.service_auth.get_service_auth_plugin')
def test_admin_context_without_user_token_but_with_service_token( def test_admin_context_without_user_token_but_with_service_token(
self, mock_admin_auth, mock_service_auth self, mock_admin_auth, mock_service_auth
): ):
self.flags(send_service_user_token=True, group='service_user') self.flags(send_service_user_token=True, group='service_user')
admin_ctx = context.get_admin_context() admin_ctx = context.get_admin_context()
params = cinder._get_cinderclient_parameters(admin_ctx) params = cinder._get_cinderclient_parameters(admin_ctx)
self.assertEqual(mock_admin_auth, params[0].user_auth) self.assertEqual(
self.assertEqual(mock_service_auth, params[0].service_auth) mock_service_auth.return_value.user_auth, params[0].user_auth)
self.assertEqual(
mock_service_auth.return_value.service_auth,
params[0].service_auth)
+18 -38
View File
@@ -28,7 +28,6 @@ from cinderclient import api_versions as cinder_api_versions
from cinderclient import client as cinder_client from cinderclient import client as cinder_client
from cinderclient import exceptions as cinder_exception from cinderclient import exceptions as cinder_exception
from keystoneauth1 import exceptions as keystone_exception from keystoneauth1 import exceptions as keystone_exception
from keystoneauth1 import loading as ks_loading
from oslo_log import log as logging from oslo_log import log as logging
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from oslo_utils import excutils from oslo_utils import excutils
@@ -46,45 +45,23 @@ CONF = nova.conf.CONF
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
_ADMIN_AUTH = None
_SESSION = None
def _load_auth_plugin():
def reset_globals(): auth_plugin = service_auth.get_service_auth_plugin(
"""Testing method to reset globals. nova.conf.cinder.cinder_group.name)
"""
global _ADMIN_AUTH
global _SESSION
_ADMIN_AUTH = None
_SESSION = None
def _load_auth_plugin(conf):
auth_plugin = ks_loading.load_auth_from_conf_options(conf,
nova.conf.cinder.cinder_group.name)
if auth_plugin: if auth_plugin:
return auth_plugin return auth_plugin
if conf.cinder.auth_type is None: if CONF.cinder.auth_type is None:
LOG.error('The [cinder] section of your nova configuration file ' LOG.error('The [cinder] section of your nova configuration file '
'must be configured for authentication with the ' 'must be configured for authentication with the '
'block-storage service endpoint.') 'block-storage service endpoint.')
err_msg = _('Unknown auth type: %s') % conf.cinder.auth_type err_msg = _('Unknown auth type: %s') % CONF.cinder.auth_type
raise cinder_exception.Unauthorized(401, message=err_msg) raise cinder_exception.Unauthorized(401, message=err_msg)
def _load_session():
global _SESSION
if not _SESSION:
_SESSION = ks_loading.load_session_from_conf_options(
CONF, nova.conf.cinder.cinder_group.name)
def _get_auth(context): def _get_auth(context):
global _ADMIN_AUTH
# NOTE(lixipeng): Auth token is none when call # NOTE(lixipeng): Auth token is none when call
# cinder API from compute periodic tasks, context # cinder API from compute periodic tasks, context
# from them generated from 'context.get_admin_context' # from them generated from 'context.get_admin_context'
@@ -92,17 +69,16 @@ def _get_auth(context):
# So add load_auth_plugin when this condition appear. # So add load_auth_plugin when this condition appear.
user_auth = None user_auth = None
if context.is_admin and not context.auth_token: if context.is_admin and not context.auth_token:
if not _ADMIN_AUTH: user_auth = _load_auth_plugin()
_ADMIN_AUTH = _load_auth_plugin(CONF)
user_auth = _ADMIN_AUTH
# When user_auth = None, user_auth will be extracted from the context. # When user_auth = None, user_auth will be extracted from the context.
return service_auth.get_auth_plugin(context, user_auth=user_auth) return service_auth.get_service_user_token_auth_plugin(
context, user_auth=user_auth)
# NOTE(efried): Bug #1752152 # NOTE(efried): Bug #1752152
# This method is copied/adapted from cinderclient.client.get_server_version so # This method is copied/adapted from cinderclient.client.get_server_version so
# we can use _SESSION.get rather than a raw requests.get to retrieve the # we can use Session.get rather than a raw requests.get to retrieve the
# version document. This enables HTTPS by gleaning cert info from the session # version document. This enables HTTPS by gleaning cert info from the session
# config. # config.
def _get_server_version(context, url): def _get_server_version(context, url):
@@ -116,7 +92,8 @@ def _get_server_version(context, url):
min_version = "2.0" min_version = "2.0"
current_version = "2.0" current_version = "2.0"
_load_session() session = service_auth.get_service_auth_session(
nova.conf.cinder.cinder_group.name)
auth = _get_auth(context) auth = _get_auth(context)
try: try:
@@ -142,7 +119,7 @@ def _get_server_version(context, url):
# leave as is without cropping. # leave as is without cropping.
version_url = url version_url = url
response = _SESSION.get(version_url, auth=auth) response = session.get(version_url, auth=auth)
data = jsonutils.loads(response.text) data = jsonutils.loads(response.text)
versions = data['versions'] versions = data['versions']
for version in versions: for version in versions:
@@ -190,7 +167,8 @@ def _check_microversion(context, url, microversion):
def _get_cinderclient_parameters(context): def _get_cinderclient_parameters(context):
_load_session() session = service_auth.get_service_auth_session(
nova.conf.cinder.cinder_group.name)
auth = _get_auth(context) auth = _get_auth(context)
@@ -208,7 +186,7 @@ def _get_cinderclient_parameters(context):
if CONF.cinder.endpoint_template: if CONF.cinder.endpoint_template:
url = CONF.cinder.endpoint_template % context.to_dict() url = CONF.cinder.endpoint_template % context.to_dict()
else: else:
url = _SESSION.get_endpoint(auth, **service_parameters) url = session.get_endpoint(auth, **service_parameters)
return auth, service_parameters, url return auth, service_parameters, url
@@ -268,8 +246,10 @@ def cinderclient(context, microversion=None, skip_version_check=False,
if check_only: if check_only:
return return
session = service_auth.get_service_auth_session(
nova.conf.cinder.cinder_group.name)
return cinder_client.Client(version, return cinder_client.Client(version,
session=_SESSION, session=session,
auth=auth, auth=auth,
endpoint_override=endpoint_override, endpoint_override=endpoint_override,
connect_retries=CONF.cinder.http_retries, connect_retries=CONF.cinder.http_retries,