From 81b85a11affd6faad994650080444ad5ed503055 Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Sun, 5 Feb 2017 19:23:58 +0000 Subject: [PATCH] Fix authentication of Nova client Blazar authenticates Nova requests with trust-scoped tokens. Sometime during the Ocata cycle these requests started failing (possibly due to stricter validation in Keystone) with the error: BadRequest: Invalid input for field 'identity/password/user/password': None is not of type 'string' (HTTP 400) This commit changes how the Nova client is configured to use the token_endpoint authentication plugin combined with endpoint_override, which allows to communicate with the Nova endpoint without extra requests to Keystone. This is necessary between trust-scoped tokens cannot re-authenticate with Keystone, which happens with other authentication plugins. Change-Id: Ibb6782140f41aea5e539e11f2618b3af2628fc4c Closes-Bug: #1660564 --- climate/tests/utils/openstack/test_nova.py | 39 ++++++++-------- climate/utils/openstack/nova.py | 53 +++++++--------------- 2 files changed, 36 insertions(+), 56 deletions(-) diff --git a/climate/tests/utils/openstack/test_nova.py b/climate/tests/utils/openstack/test_nova.py index 76a0a598..8470bc9c 100644 --- a/climate/tests/utils/openstack/test_nova.py +++ b/climate/tests/utils/openstack/test_nova.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from keystoneauth1 import session +from keystoneauth1 import token_endpoint from novaclient import client as nova_client from climate import context @@ -32,44 +34,41 @@ class TestCNClient(tests.TestCase): self.ctx = self.patch(self.context, 'current') self.client = self.patch(self.n_client, 'Client') - self.patch(self.base, 'url_for').return_value = 'http://fake.com/' + self.auth = self.patch(token_endpoint, 'Token') + self.session = self.patch(session, 'Session') + self.url = 'http://fake.com/' + self.patch(self.base, 'url_for').return_value = self.url self.version = '2' - self.username = 'fake_user' - self.api_key = self.ctx().auth_token - self.project_id = self.ctx().project_id - self.auth_url = 'fake_auth' - self.mgmt_url = 'fake_mgmt' def test_client_from_kwargs(self): self.ctx.side_effect = RuntimeError + self.auth_token = 'fake_token' + self.endpoint = 'fake_endpoint' kwargs = {'version': self.version, - 'username': self.username, - 'api_key': self.api_key, - 'project_id': self.project_id, - 'auth_url': self.auth_url, - 'mgmt_url': self.mgmt_url} + 'endpoint_override': self.endpoint, + 'auth_token': self.auth_token} self.nova.ClimateNovaClient(**kwargs) + self.auth.assert_called_once_with(self.endpoint, self.auth_token) + self.session.assert_called_once_with(auth=self.auth.return_value) self.client.assert_called_once_with(version=self.version, - username=self.username, - api_key=self.api_key, - project_id=self.project_id, - auth_url=self.auth_url) + endpoint_override=self.endpoint, + session=self.session.return_value) def test_client_from_ctx(self): - kwargs = {'version': self.version} self.nova.ClimateNovaClient(**kwargs) + self.auth.assert_called_once_with(self.url, + self.ctx().auth_token) + self.session.assert_called_once_with(auth=self.auth.return_value) self.client.assert_called_once_with(version=self.version, - username=self.ctx().user_name, - api_key=None, - project_id=self.ctx().project_id, - auth_url='http://fake.com/') + endpoint_override=self.url, + session=self.session.return_value) def test_getattr(self): # TODO(n.s.): Will be done as soon as pypi package will be updated diff --git a/climate/utils/openstack/nova.py b/climate/utils/openstack/nova.py index 45ba69de..b420cb91 100644 --- a/climate/utils/openstack/nova.py +++ b/climate/utils/openstack/nova.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from keystoneauth1 import session +from keystoneauth1 import token_endpoint from novaclient import client as nova_client from novaclient import exceptions as nova_exception from novaclient.v2 import servers @@ -50,55 +52,37 @@ class ClimateNovaClient(object): :param version: service client version which we will use :type version: str - :param username: username - :type username: str - - :param api_key: password - :type api_key: str - :param auth_token: keystone auth token :type auth_token: str - :param project_id: project_id - :type api_key: str - - :param auth_url: auth_url - :type auth_url: str - - :param mgmt_url: management url - :type mgmt_url: str + :param endpoint_override: endpoint url which we will use + :type endpoint_override: str """ ctx = kwargs.pop('ctx', None) auth_token = kwargs.pop('auth_token', None) - mgmt_url = kwargs.pop('mgmt_url', None) + endpoint_override = kwargs.pop('endpoint_override', None) + version = kwargs.pop('version', cfg.CONF.nova_client_version) if ctx is None: try: ctx = context.current() except RuntimeError: pass - kwargs.setdefault('version', cfg.CONF.nova_client_version) if ctx is not None: - kwargs.setdefault('username', ctx.user_name) - kwargs.setdefault('api_key', None) - kwargs.setdefault('project_id', ctx.project_id) - kwargs.setdefault('auth_url', base.url_for( - ctx.service_catalog, CONF.identity_service)) - auth_token = auth_token or ctx.auth_token - mgmt_url = mgmt_url or base.url_for(ctx.service_catalog, - CONF.compute_service) - if not kwargs.get('auth_url', None): - # NOTE(scroiset): novaclient v2.17.0 support only Identity API v2.0 - auth_url = "%s://%s:%s/v2.0" % (CONF.os_auth_protocol, - CONF.os_auth_host, - CONF.os_auth_port) - kwargs['auth_url'] = auth_url + endpoint_override = endpoint_override or \ + base.url_for(ctx.service_catalog, + CONF.compute_service) + auth = token_endpoint.Token(endpoint_override, + auth_token) + sess = session.Session(auth=auth) + + kwargs.setdefault('endpoint_override', endpoint_override) + kwargs.setdefault('session', sess) + kwargs.setdefault('version', version) self.nova = nova_client.Client(**kwargs) - self.nova.client.auth_token = auth_token - self.nova.client.management_url = mgmt_url self.nova.servers = ServerManager(self.nova) @@ -136,8 +120,5 @@ class NovaClientWrapper(object): @property def nova(self): ctx = context.current() - nova = ClimateNovaClient(username=ctx.user_name, - api_key=None, - project_id=ctx.project_id, - ctx=ctx) + nova = ClimateNovaClient(ctx=ctx) return nova