Merge "Pass thru credentials to allow re-authentication" into stable/havana
This commit is contained in:
commit
c082ec7595
|
@ -15,57 +15,60 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from neutronclient import client
|
|
||||||
from neutronclient.common import exceptions
|
from neutronclient.common import exceptions
|
||||||
from neutronclient.v2_0 import client as clientv20
|
from neutronclient.v2_0 import client as clientv20
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from nova.openstack.common import excutils
|
from nova.openstack.common import local
|
||||||
from nova.openstack.common.gettextutils import _
|
|
||||||
from nova.openstack.common import log as logging
|
from nova.openstack.common import log as logging
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
LOG = logging.getLogger(__name__)
|
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):
|
def _get_client(token=None):
|
||||||
if not token and CONF.neutron_auth_strategy:
|
|
||||||
token = _get_auth_token()
|
|
||||||
params = {
|
params = {
|
||||||
'endpoint_url': CONF.neutron_url,
|
'endpoint_url': CONF.neutron_url,
|
||||||
'timeout': CONF.neutron_url_timeout,
|
'timeout': CONF.neutron_url_timeout,
|
||||||
'insecure': CONF.neutron_api_insecure,
|
'insecure': CONF.neutron_api_insecure,
|
||||||
'ca_cert': CONF.neutron_ca_certificates_file,
|
'ca_cert': CONF.neutron_ca_certificates_file,
|
||||||
}
|
}
|
||||||
|
|
||||||
if token:
|
if token:
|
||||||
params['token'] = token
|
params['token'] = token
|
||||||
else:
|
|
||||||
params['auth_strategy'] = None
|
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)
|
return clientv20.Client(**params)
|
||||||
|
|
||||||
|
|
||||||
def get_client(context, admin=False):
|
def get_client(context, admin=False):
|
||||||
if admin:
|
# NOTE(dprince): In the case where no auth_token is present
|
||||||
token = None
|
# we allow use of neutron admin tenant credentials if
|
||||||
else:
|
# 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
|
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
|
if (not self.last_neutron_extension_sync or
|
||||||
((time.time() - self.last_neutron_extension_sync)
|
((time.time() - self.last_neutron_extension_sync)
|
||||||
>= CONF.neutron_extension_sync_interval)):
|
>= 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']
|
extensions_list = neutron.list_extensions()['extensions']
|
||||||
self.last_neutron_extension_sync = time.time()
|
self.last_neutron_extension_sync = time.time()
|
||||||
self.extensions.clear()
|
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 api as neutronapi
|
||||||
from nova.network.neutronv2 import constants
|
from nova.network.neutronv2 import constants
|
||||||
from nova.openstack.common import jsonutils
|
from nova.openstack.common import jsonutils
|
||||||
|
from nova.openstack.common import local
|
||||||
from nova import test
|
from nova import test
|
||||||
from nova import utils
|
from nova import utils
|
||||||
|
|
||||||
|
@ -101,6 +102,7 @@ class TestNeutronClient(test.TestCase):
|
||||||
auth_token='token')
|
auth_token='token')
|
||||||
self.mox.StubOutWithMock(client.Client, "__init__")
|
self.mox.StubOutWithMock(client.Client, "__init__")
|
||||||
client.Client.__init__(
|
client.Client.__init__(
|
||||||
|
auth_strategy=None,
|
||||||
endpoint_url=CONF.neutron_url,
|
endpoint_url=CONF.neutron_url,
|
||||||
token=my_context.auth_token,
|
token=my_context.auth_token,
|
||||||
timeout=CONF.neutron_url_timeout,
|
timeout=CONF.neutron_url_timeout,
|
||||||
|
@ -109,6 +111,33 @@ class TestNeutronClient(test.TestCase):
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
neutronv2.get_client(my_context)
|
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):
|
def test_withouttoken_keystone_connection_error(self):
|
||||||
self.flags(neutron_auth_strategy='keystone')
|
self.flags(neutron_auth_strategy='keystone')
|
||||||
self.flags(neutron_url='http://anyhost/')
|
self.flags(neutron_url='http://anyhost/')
|
||||||
|
@ -117,21 +146,6 @@ class TestNeutronClient(test.TestCase):
|
||||||
neutronv2.get_client,
|
neutronv2.get_client,
|
||||||
my_context)
|
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):
|
class TestNeutronv2Base(test.TestCase):
|
||||||
|
|
||||||
|
@ -596,6 +610,12 @@ class TestNeutronv2(TestNeutronv2Base):
|
||||||
|
|
||||||
def test_refresh_neutron_extensions_cache(self):
|
def test_refresh_neutron_extensions_cache(self):
|
||||||
api = neutronapi.API()
|
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(
|
self.moxed_client.list_extensions().AndReturn(
|
||||||
{'extensions': [{'name': 'nvp-qos'}]})
|
{'extensions': [{'name': 'nvp-qos'}]})
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
@ -604,6 +624,12 @@ class TestNeutronv2(TestNeutronv2Base):
|
||||||
|
|
||||||
def test_populate_neutron_extension_values_rxtx_factor(self):
|
def test_populate_neutron_extension_values_rxtx_factor(self):
|
||||||
api = neutronapi.API()
|
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(
|
self.moxed_client.list_extensions().AndReturn(
|
||||||
{'extensions': [{'name': 'nvp-qos'}]})
|
{'extensions': [{'name': 'nvp-qos'}]})
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
@ -797,6 +823,9 @@ class TestNeutronv2(TestNeutronv2Base):
|
||||||
{'networks': self.nets2})
|
{'networks': self.nets2})
|
||||||
self.moxed_client.list_networks(shared=True).AndReturn(
|
self.moxed_client.list_networks(shared=True).AndReturn(
|
||||||
{'networks': []})
|
{'networks': []})
|
||||||
|
neutronv2.get_client(mox.IgnoreArg(),
|
||||||
|
admin=True).AndReturn(
|
||||||
|
self.moxed_client)
|
||||||
port_req_body = {
|
port_req_body = {
|
||||||
'port': {
|
'port': {
|
||||||
'network_id': self.nets2[0]['id'],
|
'network_id': self.nets2[0]['id'],
|
||||||
|
@ -1709,7 +1738,7 @@ class TestNeutronv2Portbinding(TestNeutronv2Base):
|
||||||
|
|
||||||
def test_populate_neutron_extension_values_binding(self):
|
def test_populate_neutron_extension_values_binding(self):
|
||||||
api = neutronapi.API()
|
api = neutronapi.API()
|
||||||
neutronv2.get_client(mox.IgnoreArg()).AndReturn(
|
neutronv2.get_client(mox.IgnoreArg(), admin=True).AndReturn(
|
||||||
self.moxed_client)
|
self.moxed_client)
|
||||||
self.moxed_client.list_extensions().AndReturn(
|
self.moxed_client.list_extensions().AndReturn(
|
||||||
{'extensions': [{'name': constants.PORTBINDING_EXT}]})
|
{'extensions': [{'name': constants.PORTBINDING_EXT}]})
|
||||||
|
@ -1795,3 +1824,106 @@ class TestNeutronv2ExtraDhcpOpts(TestNeutronv2Base):
|
||||||
|
|
||||||
self._allocate_for_instance(1, dhcp_options=dhcp_opts)
|
self._allocate_for_instance(1, dhcp_options=dhcp_opts)
|
||||||
CONF.set_override('dhcp_options_enabled', False)
|
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