Correctly determine keystone v3 endpoint

The auth_uri argument in the keystone_authtoken section of the
configuration can, depending on the authentication plugin in use,
specify the URL with or without a version. When a version is given,
it may be v2.0 or v3. And for some plugins this setting may not even be
used. To help reduce the coupling between heat and keystonemiddleware's
configuration, this change adds a new "auth_uri" setting in the
[clients_keystone] section of the configuration that can be used to define
the unversioned keystone endpoint that heat should use. The keystone
discovery service is used to obtain the v3 URL from this endpoint. If this
new configuration item isn't set, then the legacy behavior that derives
the v3 endpoint from the middleware's setting is used.

UpgradeImpact: heat.conf [clients_keystone] auth_uri should be set
               to the unversioned keystone endpoint for wait conditions
               and wait handles to continue working.
Change-Id: I57d9749bea0b5797a9fc786e8fe991bbc63301ef
Partial-Bug: #1446918
This commit is contained in:
Miguel Grinberg 2015-07-16 15:55:42 -07:00 committed by Steve Baker
parent 9d6c60ea95
commit 487a211a8a
6 changed files with 126 additions and 22 deletions

View File

@ -14,6 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from keystoneclient import discover as ks_discover
from oslo_config import cfg
from oslo_utils import importutils
from webob import exc
@ -33,10 +34,18 @@ class AuthUrlFilter(wsgi.Middleware):
if 'auth_uri' in self.conf:
return self.conf['auth_uri']
else:
# Import auth_token to have keystone_authtoken settings setup.
auth_token_module = 'keystonemiddleware.auth_token'
importutils.import_module(auth_token_module)
return cfg.CONF.keystone_authtoken.auth_uri
# Look for the keystone auth_uri in the configuration. First we
# check the [clients_keystone] section, and if it is not set we
# look in [keystone_authtoken]
if cfg.CONF.clients_keystone.auth_uri:
discover = ks_discover.Discover(
auth_url=cfg.CONF.clients_keystone.auth_uri)
return discover.url_for('3.0')
else:
# Import auth_token to have keystone_authtoken settings setup.
auth_token_module = 'keystonemiddleware.auth_token'
importutils.import_module(auth_token_module)
return cfg.CONF.keystone_authtoken.auth_uri
def _validate_auth_url(self, auth_url):
"""Validate auth_url to ensure it can be used."""

View File

@ -294,6 +294,12 @@ heat_client_opts = [
help=_('Optional heat url in format like'
' http://0.0.0.0:8004/v1/%(tenant_id)s.'))]
keystone_client_opts = [
cfg.StrOpt('auth_uri',
default='',
help=_('Unversioned keystone url in format like'
' http://0.0.0.0:5000.'))]
client_http_log_debug_opts = [
cfg.BoolOpt('http_log_debug',
default=False,
@ -351,6 +357,7 @@ def list_opts():
yield client_specific_group, clients_opts
yield 'clients_heat', heat_client_opts
yield 'clients_keystone', keystone_client_opts
yield 'clients_nova', client_http_log_debug_opts
yield 'clients_cinder', client_http_log_debug_opts

View File

@ -15,6 +15,7 @@ from keystoneclient import access
from keystoneclient.auth.identity import access as access_plugin
from keystoneclient.auth.identity import v3
from keystoneclient.auth import token_endpoint
from keystoneclient import discover as ks_discover
from oslo_config import cfg
from oslo_context import context
from oslo_log import log as logging
@ -121,14 +122,29 @@ class RequestContext(context.RequestContext):
return cls(**values)
@property
def _keystone_v3_endpoint(self):
def keystone_v3_endpoint(self):
if self.auth_url:
auth_uri = self.auth_url
auth_uri = self.auth_url.replace('v2.0', 'v3')
else:
importutils.import_module('keystonemiddleware.auth_token')
auth_uri = cfg.CONF.keystone_authtoken.auth_uri
return auth_uri.replace('v2.0', 'v3')
# Look for the keystone auth_uri in the configuration. First we
# check the [clients_keystone] section, and if it is not set we
# look in [keystone_authtoken]
if cfg.CONF.clients_keystone.auth_uri:
discover = ks_discover.Discover(
auth_url=cfg.CONF.clients_keystone.auth_uri)
auth_uri = discover.url_for('3.0')
else:
# Import auth_token to have keystone_authtoken settings setup.
importutils.import_module('keystonemiddleware.auth_token')
if cfg.CONF.keystone_authtoken.auth_uri:
auth_uri = cfg.CONF.keystone_authtoken.auth_uri.replace(
'v2.0', 'v3')
else:
LOG.error('Keystone API endpoint not provided. Set '
'auth_uri in section [clients_keystone] '
'of the configuration file.')
raise exception.AuthorizationFailure()
return auth_uri
def _create_auth_plugin(self):
if self.trust_id:
@ -139,14 +155,14 @@ class RequestContext(context.RequestContext):
return v3.Password(username=username,
password=password,
user_domain_id='default',
auth_url=self._keystone_v3_endpoint,
auth_url=self.keystone_v3_endpoint,
trust_id=self.trust_id)
if self.auth_token_info:
auth_ref = access.AccessInfo.factory(body=self.auth_token_info,
auth_token=self.auth_token)
return access_plugin.AccessInfoPlugin(
auth_url=self._keystone_v3_endpoint,
auth_url=self.keystone_v3_endpoint,
auth_ref=auth_ref)
if self.auth_token:
@ -154,7 +170,7 @@ class RequestContext(context.RequestContext):
# only have a token but don't load a service catalog then
# url_for wont work. Stub with the keystone endpoint so at
# least it might be right.
return token_endpoint.Token(endpoint=self._keystone_v3_endpoint,
return token_endpoint.Token(endpoint=self.keystone_v3_endpoint,
token=self.auth_token)
if self.password:
@ -162,7 +178,7 @@ class RequestContext(context.RequestContext):
password=self.password,
project_id=self.tenant_id,
user_domain_id='default',
auth_url=self._keystone_v3_endpoint)
auth_url=self.keystone_v3_endpoint)
LOG.error(_LE("Keystone v3 API connection failed, no password "
"trust or auth_token!"))

View File

@ -76,14 +76,7 @@ class KeystoneClientV3(object):
self._domain_admin_client = None
self.session = session.Session.construct(self._ssl_options())
if self.context.auth_url:
self.v3_endpoint = self.context.auth_url.replace('v2.0', 'v3')
else:
# Import auth_token to have keystone_authtoken settings setup.
importutils.import_module('keystonemiddleware.auth_token')
self.v3_endpoint = cfg.CONF.keystone_authtoken.auth_uri.replace(
'v2.0', 'v3')
self.v3_endpoint = self.context.keystone_v3_endpoint
if self.context.trust_id:
# Create a client with the specified trust_id, this

View File

@ -40,9 +40,27 @@ class AuthUrlFilterTest(common.HeatTestCase):
self.config = {'auth_uri': 'foobar'}
self.middleware = auth_url.AuthUrlFilter(self.app, self.config)
@mock.patch.object(auth_url.cfg, 'CONF')
def test_adds_default_auth_url_from_clients_keystone(self, mock_cfg):
self.config = {}
mock_cfg.clients_keystone.auth_uri = 'foobar'
mock_cfg.keystone_authtoken.auth_uri = 'this-should-be-ignored'
mock_cfg.auth_password.multi_cloud = False
with mock.patch('keystoneclient.discover.Discover') as discover:
class MockDiscover(object):
def url_for(self, endpoint):
return 'foobar/v3'
discover.return_value = MockDiscover()
self.middleware = auth_url.AuthUrlFilter(self.app, self.config)
req = webob.Request.blank('/tenant_id/')
self.middleware(req)
self.assertIn('X-Auth-Url', req.headers)
self.assertEqual('foobar/v3', req.headers['X-Auth-Url'])
@mock.patch.object(auth_url.cfg, 'CONF')
def test_adds_default_auth_url_from_keystone_authtoken(self, mock_cfg):
self.config = {}
mock_cfg.clients_keystone.auth_uri = ''
mock_cfg.keystone_authtoken.auth_uri = 'foobar'
mock_cfg.auth_password.multi_cloud = False
self.middleware = auth_url.AuthUrlFilter(self.app, self.config)

View File

@ -17,6 +17,7 @@ import mock
from oslo_config import cfg
from oslo_middleware import request_id
from oslo_policy import opts as policy_opts
from oslo_utils import importutils
import webob
from heat.common import context
@ -112,6 +113,66 @@ class TestRequestContext(common.HeatTestCase):
ctx = context.RequestContext(roles=['notadmin'])
self.assertFalse(ctx.is_admin)
def test_keystone_v3_endpoint_in_context(self):
"""Ensure that the context is the preferred source for the
auth_uri.
"""
cfg.CONF.set_override('auth_uri', 'http://xyz',
group='clients_keystone')
policy_check = 'heat.common.policy.Enforcer.check_is_admin'
with mock.patch(policy_check) as pc:
pc.return_value = False
ctx = context.RequestContext(
auth_url='http://example.com:5000/v2.0')
self.assertEqual(ctx.keystone_v3_endpoint,
'http://example.com:5000/v3')
def test_keystone_v3_endpoint_in_clients_keystone_config(self):
"""Ensure that the [clients_keystone] section of the configuration is
the preferred source when the context does not have the auth_uri.
"""
cfg.CONF.set_override('auth_uri', 'http://xyz',
group='clients_keystone')
importutils.import_module('keystonemiddleware.auth_token')
cfg.CONF.set_override('auth_uri', 'http://abc/v2.0',
group='keystone_authtoken')
policy_check = 'heat.common.policy.Enforcer.check_is_admin'
with mock.patch(policy_check) as pc:
pc.return_value = False
with mock.patch('keystoneclient.discover.Discover') as discover:
class MockDiscover(object):
def url_for(self, endpoint):
return 'http://xyz/v3'
discover.return_value = MockDiscover()
ctx = context.RequestContext(auth_url=None)
self.assertEqual(ctx.keystone_v3_endpoint, 'http://xyz/v3')
def test_keystone_v3_endpoint_in_keystone_authtoken_config(self):
"""Ensure that the [keystone_authtoken] section of the configuration
is used when the auth_uri is not defined in the context or the
[clients_keystone] section.
"""
importutils.import_module('keystonemiddleware.auth_token')
cfg.CONF.set_override('auth_uri', 'http://abc/v2.0',
group='keystone_authtoken')
policy_check = 'heat.common.policy.Enforcer.check_is_admin'
with mock.patch(policy_check) as pc:
pc.return_value = False
ctx = context.RequestContext(auth_url=None)
self.assertEqual(ctx.keystone_v3_endpoint, 'http://abc/v3')
def test_keystone_v3_endpoint_not_set_in_config(self):
"""Ensure an exception is raised when the auth_uri cannot be obtained
from any source.
"""
policy_check = 'heat.common.policy.Enforcer.check_is_admin'
with mock.patch(policy_check) as pc:
pc.return_value = False
ctx = context.RequestContext(auth_url=None)
self.assertRaises(exception.AuthorizationFailure, getattr, ctx,
'keystone_v3_endpoint')
class RequestContextMiddlewareTest(common.HeatTestCase):