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
This commit is contained in:
Clay Gerrard
2012-09-11 12:13:46 -05:00
parent a4d4601432
commit 2313ddfc8c
3 changed files with 78 additions and 5 deletions

View File

@@ -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)

View File

@@ -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,

View File

@@ -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")