Pass thru credentials to allow re-authentication

This is a backport of 4 fixes squashed into one because:

1. They need to all be merged together as they build on each
   other as problems were found in each change after it was
   merged on master.
2. The 3rd change won't pass Jenkins on it's own so it has
   to be squashed with the 4th and final change, so I'm just
   going to squash the entire topic branch together.

The change bugs fixed and cherry pick commit hashes are left
intact for each change.

Closes-Bug: #1241275
(cherry picked from commit 51e5f52e4c)

Cache Neutron Client for Admin Scenarios

Closes-Bug: #1250580
(cherry picked from commit 85332012de)

Users with admin role in Nova should not re-auth with Neutron

Closes-Bug: 1250763
(cherry picked from commit 1c1371c78b)

Fix Neutron Authentication for Metadata Service

Closes-Bug:  1255577
(cherry picked from commit 652620d12f)

============

Change-Id: I2858562b180f3e058a2da9d67bef02af80927177
This commit is contained in:
Drew Thorstensen 2013-10-21 09:52:28 -05:00 committed by Matt Riedemann
parent 53acc09fb9
commit bdc7519862
3 changed files with 182 additions and 46 deletions

View File

@ -15,57 +15,60 @@
# License for the specific language governing permissions and limitations
# under the License.
from neutronclient import client
from neutronclient.common import exceptions
from neutronclient.v2_0 import client as clientv20
from oslo.config import cfg
from nova.openstack.common import excutils
from nova.openstack.common.gettextutils import _
from nova.openstack.common import local
from nova.openstack.common import log as logging
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
def _get_auth_token():
try:
httpclient = client.HTTPClient(
username=CONF.neutron_admin_username,
tenant_name=CONF.neutron_admin_tenant_name,
region_name=CONF.neutron_region_name,
password=CONF.neutron_admin_password,
auth_url=CONF.neutron_admin_auth_url,
timeout=CONF.neutron_url_timeout,
auth_strategy=CONF.neutron_auth_strategy,
ca_cert=CONF.neutron_ca_certificates_file,
insecure=CONF.neutron_api_insecure)
httpclient.authenticate()
return httpclient.auth_token
except exceptions.NeutronClientException as e:
with excutils.save_and_reraise_exception():
LOG.error(_('Neutron client authentication failed: %s'), e)
def _get_client(token=None):
if not token and CONF.neutron_auth_strategy:
token = _get_auth_token()
params = {
'endpoint_url': CONF.neutron_url,
'timeout': CONF.neutron_url_timeout,
'insecure': CONF.neutron_api_insecure,
'ca_cert': CONF.neutron_ca_certificates_file,
}
if token:
params['token'] = token
else:
params['auth_strategy'] = None
else:
params['username'] = CONF.neutron_admin_username
params['tenant_name'] = CONF.neutron_admin_tenant_name
params['password'] = CONF.neutron_admin_password
params['auth_url'] = CONF.neutron_admin_auth_url
params['auth_strategy'] = CONF.neutron_auth_strategy
return clientv20.Client(**params)
def get_client(context, admin=False):
if admin:
token = None
else:
# NOTE(dprince): In the case where no auth_token is present
# we allow use of neutron admin tenant credentials if
# it is an admin context.
# This is to support some services (metadata API) where
# an admin context is used without an auth token.
if admin or (context.is_admin and not context.auth_token):
# NOTE(dims): We need to use admin token, let us cache a
# thread local copy for re-using this client
# multiple times and to avoid excessive calls
# to neutron to fetch tokens. Some of the hackiness in this code
# will go away once BP auth-plugins is implemented.
# That blue print will ensure that tokens can be shared
# across clients as well
if not hasattr(local.strong_store, 'neutron_client'):
local.strong_store.neutron_client = _get_client(token=None)
return local.strong_store.neutron_client
# We got a user token that we can use that as-is
if context.auth_token:
token = context.auth_token
return _get_client(token=token)
return _get_client(token=token)
# We did not get a user token and we should not be using
# an admin token so log an error
raise exceptions.Unauthorized()

View File

@ -372,7 +372,8 @@ class API(base.Base):
if (not self.last_neutron_extension_sync or
((time.time() - self.last_neutron_extension_sync)
>= CONF.neutron_extension_sync_interval)):
neutron = neutronv2.get_client(context.get_admin_context())
neutron = neutronv2.get_client(context.get_admin_context(),
admin=True)
extensions_list = neutron.list_extensions()['extensions']
self.last_neutron_extension_sync = time.time()
self.extensions.clear()

View File

@ -33,6 +33,7 @@ from nova.network import neutronv2
from nova.network.neutronv2 import api as neutronapi
from nova.network.neutronv2 import constants
from nova.openstack.common import jsonutils
from nova.openstack.common import local
from nova import test
from nova import utils
@ -101,6 +102,7 @@ class TestNeutronClient(test.TestCase):
auth_token='token')
self.mox.StubOutWithMock(client.Client, "__init__")
client.Client.__init__(
auth_strategy=None,
endpoint_url=CONF.neutron_url,
token=my_context.auth_token,
timeout=CONF.neutron_url_timeout,
@ -109,6 +111,33 @@ class TestNeutronClient(test.TestCase):
self.mox.ReplayAll()
neutronv2.get_client(my_context)
def test_withouttoken(self):
my_context = context.RequestContext('userid', 'my_tenantid')
self.assertRaises(exceptions.Unauthorized,
neutronv2.get_client,
my_context)
def test_withtoken_context_is_admin(self):
self.flags(neutron_url='http://anyhost/')
self.flags(neutron_url_timeout=30)
my_context = context.RequestContext('userid',
'my_tenantid',
auth_token='token',
is_admin=True)
self.mox.StubOutWithMock(client.Client, "__init__")
client.Client.__init__(
auth_strategy=None,
endpoint_url=CONF.neutron_url,
token=my_context.auth_token,
timeout=CONF.neutron_url_timeout,
insecure=False,
ca_cert=None).AndReturn(None)
self.mox.ReplayAll()
# Note that although we have admin set in the context we
# are not asking for an admin client, and so we auth with
# our own token
neutronv2.get_client(my_context)
def test_withouttoken_keystone_connection_error(self):
self.flags(neutron_auth_strategy='keystone')
self.flags(neutron_url='http://anyhost/')
@ -117,21 +146,6 @@ class TestNeutronClient(test.TestCase):
neutronv2.get_client,
my_context)
def test_withouttoken_keystone_not_auth(self):
self.flags(neutron_auth_strategy=None)
self.flags(neutron_url='http://anyhost/')
self.flags(neutron_url_timeout=30)
my_context = context.RequestContext('userid', 'my_tenantid')
self.mox.StubOutWithMock(client.Client, "__init__")
client.Client.__init__(
endpoint_url=CONF.neutron_url,
auth_strategy=None,
timeout=CONF.neutron_url_timeout,
insecure=False,
ca_cert=None).AndReturn(None)
self.mox.ReplayAll()
neutronv2.get_client(my_context)
class TestNeutronv2Base(test.TestCase):
@ -596,6 +610,12 @@ class TestNeutronv2(TestNeutronv2Base):
def test_refresh_neutron_extensions_cache(self):
api = neutronapi.API()
# Note: Don't want the default get_client from setUp()
self.mox.ResetAll()
neutronv2.get_client(mox.IgnoreArg(),
admin=True).AndReturn(
self.moxed_client)
self.moxed_client.list_extensions().AndReturn(
{'extensions': [{'name': 'nvp-qos'}]})
self.mox.ReplayAll()
@ -604,6 +624,12 @@ class TestNeutronv2(TestNeutronv2Base):
def test_populate_neutron_extension_values_rxtx_factor(self):
api = neutronapi.API()
# Note: Don't want the default get_client from setUp()
self.mox.ResetAll()
neutronv2.get_client(mox.IgnoreArg(),
admin=True).AndReturn(
self.moxed_client)
self.moxed_client.list_extensions().AndReturn(
{'extensions': [{'name': 'nvp-qos'}]})
self.mox.ReplayAll()
@ -797,6 +823,9 @@ class TestNeutronv2(TestNeutronv2Base):
{'networks': self.nets2})
self.moxed_client.list_networks(shared=True).AndReturn(
{'networks': []})
neutronv2.get_client(mox.IgnoreArg(),
admin=True).AndReturn(
self.moxed_client)
port_req_body = {
'port': {
'network_id': self.nets2[0]['id'],
@ -1709,7 +1738,7 @@ class TestNeutronv2Portbinding(TestNeutronv2Base):
def test_populate_neutron_extension_values_binding(self):
api = neutronapi.API()
neutronv2.get_client(mox.IgnoreArg()).AndReturn(
neutronv2.get_client(mox.IgnoreArg(), admin=True).AndReturn(
self.moxed_client)
self.moxed_client.list_extensions().AndReturn(
{'extensions': [{'name': constants.PORTBINDING_EXT}]})
@ -1795,3 +1824,106 @@ class TestNeutronv2ExtraDhcpOpts(TestNeutronv2Base):
self._allocate_for_instance(1, dhcp_options=dhcp_opts)
CONF.set_override('dhcp_options_enabled', False)
class TestNeutronClientForAdminScenarios(test.TestCase):
def test_get_cached_neutron_client_for_admin(self):
self.flags(neutron_url='http://anyhost/')
self.flags(neutron_url_timeout=30)
my_context = context.RequestContext('userid',
'my_tenantid',
auth_token='token')
# Make multiple calls and ensure we get the same
# client back again and again
client = neutronv2.get_client(my_context, True)
client2 = neutronv2.get_client(my_context, True)
client3 = neutronv2.get_client(my_context, True)
self.assertEqual(client, client2)
self.assertEqual(client, client3)
# clear the cache
local.strong_store.neutron_client = None
# A new client should be created now
client4 = neutronv2.get_client(my_context, True)
self.assertNotEqual(client, client4)
def test_get_neutron_client_for_non_admin(self):
self.flags(neutron_url='http://anyhost/')
self.flags(neutron_url_timeout=30)
my_context = context.RequestContext('userid',
'my_tenantid',
auth_token='token')
# Multiple calls should return different clients
client = neutronv2.get_client(my_context)
client2 = neutronv2.get_client(my_context)
self.assertNotEqual(client, client2)
def test_get_neutron_client_for_non_admin_and_no_token(self):
self.flags(neutron_url='http://anyhost/')
self.flags(neutron_url_timeout=30)
my_context = context.RequestContext('userid',
'my_tenantid')
self.assertRaises(exceptions.Unauthorized,
neutronv2.get_client,
my_context)
def test_get_client_for_admin(self):
self.flags(neutron_auth_strategy=None)
self.flags(neutron_url='http://anyhost/')
self.flags(neutron_url_timeout=30)
my_context = context.RequestContext('userid', 'my_tenantid',
auth_token='token')
self.mox.StubOutWithMock(client.Client, "__init__")
client.Client.__init__(
auth_url=CONF.neutron_admin_auth_url,
password=CONF.neutron_admin_password,
tenant_name=CONF.neutron_admin_tenant_name,
username=CONF.neutron_admin_username,
endpoint_url=CONF.neutron_url,
auth_strategy=None,
timeout=CONF.neutron_url_timeout,
insecure=False,
ca_cert=None).AndReturn(None)
self.mox.ReplayAll()
# clear the cache
if hasattr(local.strong_store, 'neutron_client'):
delattr(local.strong_store, 'neutron_client')
# Note that the context is not elevated, but the True is passed in
# which will force an elevation to admin credentials even though
# the context has an auth_token.
neutronv2.get_client(my_context, True)
def test_get_client_for_admin_context(self):
self.flags(neutron_auth_strategy=None)
self.flags(neutron_url='http://anyhost/')
self.flags(neutron_url_timeout=30)
my_context = context.get_admin_context()
self.mox.StubOutWithMock(client.Client, "__init__")
client.Client.__init__(
auth_url=CONF.neutron_admin_auth_url,
password=CONF.neutron_admin_password,
tenant_name=CONF.neutron_admin_tenant_name,
username=CONF.neutron_admin_username,
endpoint_url=CONF.neutron_url,
auth_strategy=None,
timeout=CONF.neutron_url_timeout,
insecure=False,
ca_cert=None).AndReturn(None)
self.mox.ReplayAll()
# clear the cache
if hasattr(local.strong_store, 'neutron_client'):
delattr(local.strong_store, 'neutron_client')
# Note that the context does not contain a token but is
# an admin context which will force an elevation to admin
# credentials.
neutronv2.get_client(my_context)