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 commit51e5f52e4c
) Cache Neutron Client for Admin Scenarios Closes-Bug: #1250580 (cherry picked from commit85332012de
) Users with admin role in Nova should not re-auth with Neutron Closes-Bug: 1250763 (cherry picked from commit1c1371c78b
) Fix Neutron Authentication for Metadata Service Closes-Bug: 1255577 (cherry picked from commit652620d12f
) ============ Change-Id: I2858562b180f3e058a2da9d67bef02af80927177
This commit is contained in:
parent
53acc09fb9
commit
bdc7519862
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue