From e1014faed100537c4013142257d90ee24e8344c6 Mon Sep 17 00:00:00 2001 From: wangxiyuan Date: Wed, 30 Nov 2016 14:36:16 +0800 Subject: [PATCH] Support Keystone V3 with HttpClient The http way doesn't work with keystone v3. This patch update the HttpClient to support it. Closes-bug: #1546280 Change-Id: Iefa1aafb796609aca076ed6ab73d02c92b9198d0 --- cinderclient/client.py | 55 +++++++++++++------ cinderclient/tests/unit/test_http.py | 48 ++++++++++++++++ ...ne-v3-for-httpClient-d48ebb24880f5821.yaml | 4 ++ 3 files changed, 91 insertions(+), 16 deletions(-) create mode 100644 releasenotes/notes/support-keystone-v3-for-httpClient-d48ebb24880f5821.yaml diff --git a/cinderclient/client.py b/cinderclient/client.py index a4554a2..06a7d0d 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -205,7 +205,8 @@ class HTTPClient(object): bypass_url=None, retries=None, http_log_debug=False, cacert=None, auth_system='keystone', auth_plugin=None, api_version=None, - logger=None): + logger=None, user_domain_name='Default', + project_domain_name='Default'): self.user = user self.password = password self.projectid = projectid @@ -236,6 +237,8 @@ class HTTPClient(object): self.proxy_token = proxy_token self.proxy_tenant_id = proxy_tenant_id self.timeout = timeout + self.user_domain_name = user_domain_name + self.project_domain_name = project_domain_name if insecure: self.verify_cert = False @@ -419,8 +422,8 @@ class HTTPClient(object): We may get redirected to another site, fail or actually get back a service catalog with a token and our endpoints. """ - - if resp.status_code == 200: # content must always present + # content must always present + if resp.status_code == 200 or resp.status_code == 201: try: self.auth_url = url self.auth_ref = access.create(resp=resp, body=body) @@ -497,10 +500,10 @@ class HTTPClient(object): path, query, frag)) auth_url = self.auth_url - if self.version == "v2.0": + if self.version == "v2.0" or self.version == "v3": while auth_url: if not self.auth_system or self.auth_system == 'keystone': - auth_url = self._v2_auth(auth_url) + auth_url = self._v2_or_v3_auth(auth_url) else: auth_url = self._plugin_auth(auth_url) @@ -526,7 +529,7 @@ class HTTPClient(object): except exceptions.AuthorizationFailure: if auth_url.find('v2.0') < 0: auth_url = auth_url + '/v2.0' - self._v2_auth(auth_url) + self._v2_or_v3_auth(auth_url) if self.bypass_url: self.set_management_url(self.bypass_url) @@ -559,23 +562,43 @@ class HTTPClient(object): def _plugin_auth(self, auth_url): return self.auth_plugin.authenticate(self, auth_url) - def _v2_auth(self, url): + def _v2_or_v3_auth(self, url): """Authenticate against a v2.0 auth service.""" - body = {"auth": { - "passwordCredentials": {"username": self.user, - "password": self.password}}} + if self.version == "v3": + body = { + "auth": { + "identity": { + "methods": ["password"], + "password": {"user": { + "domain": {"name": self.user_domain_name}, + "name": self.user, + "password": self.password}}}, + } + } + scope = {"project": {"domain": {"name": self.project_domain_name}}} + if self.projectid: + scope['project']['name'] = self.projectid + elif self.tenant_id: + scope['project']['id'] = self.tenant_id - if self.projectid: - body['auth']['tenantName'] = self.projectid - elif self.tenant_id: - body['auth']['tenantId'] = self.tenant_id + body["auth"]["scope"] = scope + else: + body = {"auth": { + "passwordCredentials": {"username": self.user, + "password": self.password}}} + if self.projectid: + body['auth']['tenantName'] = self.projectid + elif self.tenant_id: + body['auth']['tenantId'] = self.tenant_id self._authenticate(url, body) def _authenticate(self, url, body): """Authenticate and extract the service catalog.""" - token_url = url + "/tokens" - + if self.version == 'v3': + token_url = url + "/auth/tokens" + else: + token_url = url + "/tokens" # Make sure we follow redirects when trying to reach Keystone resp, body = self.request( token_url, diff --git a/cinderclient/tests/unit/test_http.py b/cinderclient/tests/unit/test_http.py index 2a57e07..1336a03 100644 --- a/cinderclient/tests/unit/test_http.py +++ b/cinderclient/tests/unit/test_http.py @@ -11,6 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json import mock import requests @@ -25,6 +26,12 @@ fake_response = utils.TestResponse({ }) mock_request = mock.Mock(return_value=(fake_response)) +fake_201_response = utils.TestResponse({ + "status_code": 201, + "text": '{"hi": "there"}', +}) +mock_201_request = mock.Mock(return_value=(fake_201_response)) + refused_response = utils.TestResponse({ "status_code": 400, "text": '[Errno 111] Connection refused', @@ -293,6 +300,47 @@ class ClientTest(utils.TestCase): test_auth_call() + def test_auth_with_keystone_v3(self): + cl = get_authed_client() + cl.auth_url = 'http://example.com:5000/v3' + + @mock.patch.object(cl, "_extract_service_catalog", mock.Mock()) + @mock.patch.object(requests, "request", mock_201_request) + def test_auth_call(): + cl.authenticate() + headers = { + "Content-Type": "application/json", + 'Accept': 'application/json', + "User-Agent": cl.USER_AGENT + } + data = { + "auth": { + "scope": { + "project": { + "domain": {"name": "Default"}, + "name": "project_id" + } + }, + "identity": { + "methods": ["password"], + "password": { + "user": {"domain": {"name": "Default"}, + "password": "password", "name": "username" + } + } + } + } + } + mock_201_request.assert_called_with( + "POST", + "http://example.com:5000/v3/auth/tokens", + headers=headers, + allow_redirects=True, + data=json.dumps(data), + **self.TEST_REQUEST_BASE) + + test_auth_call() + def test_get_retry_timeout_error(self): cl = get_authed_client(retries=1) diff --git a/releasenotes/notes/support-keystone-v3-for-httpClient-d48ebb24880f5821.yaml b/releasenotes/notes/support-keystone-v3-for-httpClient-d48ebb24880f5821.yaml new file mode 100644 index 0000000..81be20d --- /dev/null +++ b/releasenotes/notes/support-keystone-v3-for-httpClient-d48ebb24880f5821.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Support Keystone V3 authentication for httpClient.