From 2313ddfc8c89074b3aaf269097236c5d93b59fdf Mon Sep 17 00:00:00 2001 From: Clay Gerrard Date: Tue, 11 Sep 2012 12:13:46 -0500 Subject: [PATCH] add tenant_id and make projectid optional Add a new optional tenant_id keyword argument to the client classes cinderclient.client.HTTPClient and cinderclient.v1.client.Client to support authentication with tenant_id instead of projectid (which is acctually used as "tenantName" in the auth request body). Keystone can provide tokens without specifiying the tenant in any form, but a tenantName _or_ tenantId is required to generate the catalog (the keystone service code seems to prefer tenantName if both are specified). When using cinderclient programatically, it may be more convienent, depending on the context to authenticate with out specificying the tenant, or by tenant_id instead of tenant_name. Either way it's impractial to make the requirement in the client for projectid (tenantName) if the auth system has no such limitation. The new client signature is backwards compatible. There is no change in behavior for the shell client. Change-Id: I0c1bdbdabd9acc29133a48a798750323011f1f18 --- cinderclient/client.py | 5 ++- cinderclient/v1/client.py | 8 ++--- tests/v1/test_auth.py | 70 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 5 deletions(-) diff --git a/cinderclient/client.py b/cinderclient/client.py index b8eaa6263..ee429ffb6 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -39,7 +39,7 @@ class HTTPClient(httplib2.Http): USER_AGENT = 'python-cinderclient' def __init__(self, user, password, projectid, auth_url, insecure=False, - timeout=None, proxy_tenant_id=None, + timeout=None, tenant_id=None, proxy_tenant_id=None, proxy_token=None, region_name=None, endpoint_type='publicURL', service_type=None, service_name=None, volume_service_name=None): @@ -47,6 +47,7 @@ class HTTPClient(httplib2.Http): self.user = user self.password = password self.projectid = projectid + self.tenant_id = tenant_id self.auth_url = auth_url.rstrip('/') self.version = 'v1' self.region_name = region_name @@ -282,6 +283,8 @@ class HTTPClient(httplib2.Http): if self.projectid: body['auth']['tenantName'] = self.projectid + elif self.tenant_id: + body['auth']['tenantId'] = self.tenant_id self._authenticate(url, body) diff --git a/cinderclient/v1/client.py b/cinderclient/v1/client.py index a8d405ceb..1412ed864 100644 --- a/cinderclient/v1/client.py +++ b/cinderclient/v1/client.py @@ -21,10 +21,9 @@ class Client(object): """ - # FIXME(jesse): project_id isn't required to authenticate - def __init__(self, username, api_key, project_id, auth_url, - insecure=False, timeout=None, proxy_tenant_id=None, - proxy_token=None, region_name=None, + def __init__(self, username, api_key, project_id=None, auth_url='', + insecure=False, timeout=None, tenant_id=None, + proxy_tenant_id=None, proxy_token=None, region_name=None, endpoint_type='publicURL', extensions=None, service_type='compute', service_name=None, volume_service_name=None): @@ -51,6 +50,7 @@ class Client(object): auth_url, insecure=insecure, timeout=timeout, + tenant_id=tenant_id, proxy_token=proxy_token, proxy_tenant_id=proxy_tenant_id, region_name=region_name, diff --git a/tests/v1/test_auth.py b/tests/v1/test_auth.py index 6cced17da..bdc4b66db 100644 --- a/tests/v1/test_auth.py +++ b/tests/v1/test_auth.py @@ -78,6 +78,76 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase): test_auth_call() + def test_authenticate_tenant_id(self): + cs = client.Client("username", "password", auth_url="auth_url/v2.0", + tenant_id='tenant_id', service_type='compute') + resp = { + "access": { + "token": { + "expires": "12345", + "id": "FAKE_ID", + "tenant": { + "description": None, + "enabled": True, + "id": "tenant_id", + "name": "demo" + } # tenant associated with token + }, + "serviceCatalog": [ + { + "type": "compute", + "endpoints": [ + { + "region": "RegionOne", + "adminURL": "http://localhost:8774/v1", + "internalURL": "http://localhost:8774/v1", + "publicURL": "http://localhost:8774/v1/", + }, + ], + }, + ], + }, + } + auth_response = httplib2.Response({ + "status": 200, + "body": json.dumps(resp), }) + + mock_request = mock.Mock(return_value=(auth_response, + json.dumps(resp))) + + @mock.patch.object(httplib2.Http, "request", mock_request) + def test_auth_call(): + cs.client.authenticate() + headers = { + 'User-Agent': cs.client.USER_AGENT, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + } + body = { + 'auth': { + 'passwordCredentials': { + 'username': cs.client.user, + 'password': cs.client.password, + }, + 'tenantId': cs.client.tenant_id, + }, + } + + token_url = cs.client.auth_url + "/tokens" + mock_request.assert_called_with(token_url, "POST", + headers=headers, + body=json.dumps(body)) + + endpoints = resp["access"]["serviceCatalog"][0]['endpoints'] + public_url = endpoints[0]["publicURL"].rstrip('/') + self.assertEqual(cs.client.management_url, public_url) + token_id = resp["access"]["token"]["id"] + self.assertEqual(cs.client.auth_token, token_id) + tenant_id = resp["access"]["token"]["tenant"]["id"] + self.assertEqual(cs.client.tenant_id, tenant_id) + + test_auth_call() + def test_authenticate_failure(self): cs = client.Client("username", "password", "project_id", "auth_url/v2.0")