summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDolph Mathews <dolph.mathews@gmail.com>2012-09-11 11:20:16 -0500
committerGerrit Code Review <review@openstack.org>2012-10-04 09:33:32 +0000
commit2ca00dc61796c51457a3db5acf997c3852fac2c4 (patch)
treecc42fc778d77974e12cc6a492ddd732ef85179fc
parent315285e76ad520e89b5616503bdce8e061c77141 (diff)
downloadpython-keystoneclient-2ca00dc61796c51457a3db5acf997c3852fac2c4.tar.gz
v3 Client & test utils
Change-Id: I6cafaad053b7fa1ca31f4f5aed1f86aa97c4e87e
-rw-r--r--keystoneclient/v3/__init__.py1
-rw-r--r--keystoneclient/v3/client.py68
-rw-r--r--tests/v3/__init__.py0
-rw-r--r--tests/v3/utils.py225
4 files changed, 294 insertions, 0 deletions
diff --git a/keystoneclient/v3/__init__.py b/keystoneclient/v3/__init__.py
new file mode 100644
index 0000000..feb2536
--- /dev/null
+++ b/keystoneclient/v3/__init__.py
@@ -0,0 +1 @@
+from keystoneclient.v3.client import Client
diff --git a/keystoneclient/v3/client.py b/keystoneclient/v3/client.py
new file mode 100644
index 0000000..7e99dd5
--- /dev/null
+++ b/keystoneclient/v3/client.py
@@ -0,0 +1,68 @@
+# Copyright 2011 Nebula, Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+import json
+import logging
+
+from keystoneclient.v2_0 import client
+
+
+_logger = logging.getLogger(__name__)
+
+
+class Client(client.Client):
+ """Client for the OpenStack Identity API v3.
+
+ :param string username: Username for authentication. (optional)
+ :param string password: Password for authentication. (optional)
+ :param string token: Token for authentication. (optional)
+ :param string tenant_name: Tenant id. (optional)
+ :param string tenant_id: Tenant name. (optional)
+ :param string auth_url: Keystone service endpoint for authorization.
+ :param string region_name: Name of a region to select when choosing an
+ endpoint from the service catalog.
+ :param string endpoint: A user-supplied endpoint URL for the keystone
+ service. Lazy-authentication is possible for API
+ service calls if endpoint is set at
+ instantiation.(optional)
+ :param integer timeout: Allows customization of the timeout for client
+ http requests. (optional)
+
+ Example::
+
+ >>> from keystoneclient.v3 import client
+ >>> keystone = client.Client(username=USER,
+ password=PASS,
+ tenant_name=TENANT_NAME,
+ auth_url=KEYSTONE_URL)
+ >>> keystone.tenants.list()
+ ...
+ >>> user = keystone.users.get(USER_ID)
+ >>> user.delete()
+
+ """
+
+ def __init__(self, endpoint=None, **kwargs):
+ """ Initialize a new client for the Keystone v2.0 API. """
+ super(Client, self).__init__(endpoint=endpoint, **kwargs)
+
+ # NOTE(gabriel): If we have a pre-defined endpoint then we can
+ # get away with lazy auth. Otherwise auth immediately.
+ if endpoint:
+ self.management_url = endpoint
+ else:
+ self.authenticate()
+
+ def serialize(self, entity):
+ return json.dumps(entity, sort_keys=True)
diff --git a/tests/v3/__init__.py b/tests/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/v3/__init__.py
diff --git a/tests/v3/utils.py b/tests/v3/utils.py
new file mode 100644
index 0000000..ee71c11
--- /dev/null
+++ b/tests/v3/utils.py
@@ -0,0 +1,225 @@
+import json
+import uuid
+import time
+import urlparse
+
+import httplib2
+import mox
+import unittest2 as unittest
+
+from keystoneclient.v3 import client
+
+
+def parameterize(ref):
+ """Rewrites attributes to match the kwarg naming convention in client.
+
+ >>> paramterize({'project_id': 0})
+ {'project': 0}
+
+ """
+ params = ref.copy()
+ for key in ref:
+ if key[-3:] == '_id':
+ params.setdefault(key[:-3], params.pop(key))
+ return params
+
+
+class TestCase(unittest.TestCase):
+ TEST_TENANT_NAME = 'aTenant'
+ TEST_TOKEN = 'aToken'
+ TEST_USER = 'test'
+ TEST_ROOT_URL = 'http://127.0.0.1:5000/'
+ TEST_URL = '%s%s' % (TEST_ROOT_URL, 'v3')
+ TEST_ROOT_ADMIN_URL = 'http://127.0.0.1:35357/'
+ TEST_ADMIN_URL = '%s%s' % (TEST_ROOT_ADMIN_URL, 'v3')
+
+ def setUp(self):
+ super(TestCase, self).setUp()
+ self.mox = mox.Mox()
+ self._original_time = time.time
+ time.time = lambda: 1234
+ httplib2.Http.request = self.mox.CreateMockAnything()
+ self.client = client.Client(username=self.TEST_USER,
+ token=self.TEST_TOKEN,
+ tenant_name=self.TEST_TENANT_NAME,
+ auth_url=self.TEST_URL,
+ endpoint=self.TEST_URL)
+
+ def tearDown(self):
+ time.time = self._original_time
+ super(TestCase, self).tearDown()
+ self.mox.UnsetStubs()
+ self.mox.VerifyAll()
+
+
+class UnauthenticatedTestCase(unittest.TestCase):
+ """ Class used as base for unauthenticated calls """
+ TEST_ROOT_URL = 'http://127.0.0.1:5000/'
+ TEST_URL = '%s%s' % (TEST_ROOT_URL, 'v3')
+ TEST_ROOT_ADMIN_URL = 'http://127.0.0.1:35357/'
+ TEST_ADMIN_URL = '%s%s' % (TEST_ROOT_ADMIN_URL, 'v3')
+
+ def setUp(self):
+ super(UnauthenticatedTestCase, self).setUp()
+ self.mox = mox.Mox()
+ self._original_time = time.time
+ time.time = lambda: 1234
+ httplib2.Http.request = self.mox.CreateMockAnything()
+
+ def tearDown(self):
+ time.time = self._original_time
+ super(UnauthenticatedTestCase, self).tearDown()
+ self.mox.UnsetStubs()
+ self.mox.VerifyAll()
+
+
+class CrudTests(object):
+ key = None
+ collection_key = None
+ model = None
+ manager = None
+
+ def new_ref(self, **kwargs):
+ kwargs.setdefault('id', uuid.uuid4().hex)
+ return kwargs
+
+ def additionalSetUp(self):
+ self.headers = {
+ 'GET': {
+ 'X-Auth-Token': 'aToken',
+ 'User-Agent': 'python-keystoneclient',
+ }
+ }
+
+ self.headers['DELETE'] = self.headers['GET'].copy()
+ self.headers['POST'] = self.headers['GET'].copy()
+ self.headers['POST']['Content-Type'] = 'application/json'
+ self.headers['PATCH'] = self.headers['POST'].copy()
+
+ def serialize(self, entity):
+ if isinstance(entity, dict):
+ return json.dumps({self.key: entity}, sort_keys=True)
+ if isinstance(entity, list):
+ return json.dumps({self.collection_key: entity}, sort_keys=True)
+ raise NotImplementedError('Are you sure you want to serialize that?')
+
+ def test_create(self):
+ ref = self.new_ref()
+ resp = httplib2.Response({
+ 'status': 201,
+ 'body': self.serialize(ref),
+ })
+
+ method = 'POST'
+ req_ref = ref.copy()
+ req_ref.pop('id')
+ httplib2.Http.request(
+ urlparse.urljoin(
+ self.TEST_URL,
+ 'v3/%s' % self.collection_key),
+ method,
+ body=self.serialize(req_ref),
+ headers=self.headers[method]) \
+ .AndReturn((resp, resp['body']))
+ self.mox.ReplayAll()
+
+ returned = self.manager.create(**parameterize(req_ref))
+ self.assertTrue(isinstance(returned, self.model))
+ for attr in ref:
+ self.assertEqual(
+ getattr(returned, attr),
+ ref[attr],
+ 'Expected different %s' % attr)
+
+ def test_get(self):
+ ref = self.new_ref()
+ resp = httplib2.Response({
+ 'status': 200,
+ 'body': self.serialize(ref),
+ })
+ method = 'GET'
+ httplib2.Http.request(
+ urlparse.urljoin(
+ self.TEST_URL,
+ 'v3/%s/%s' % (self.collection_key, ref['id'])),
+ method,
+ headers=self.headers[method]) \
+ .AndReturn((resp, resp['body']))
+ self.mox.ReplayAll()
+
+ returned = self.manager.get(ref['id'])
+ self.assertTrue(isinstance(returned, self.model))
+ for attr in ref:
+ self.assertEqual(
+ getattr(returned, attr),
+ ref[attr],
+ 'Expected different %s' % attr)
+
+ def test_list(self):
+ ref_list = [self.new_ref(), self.new_ref()]
+
+ resp = httplib2.Response({
+ 'status': 200,
+ 'body': self.serialize(ref_list),
+ })
+
+ method = 'GET'
+ httplib2.Http.request(
+ urlparse.urljoin(
+ self.TEST_URL,
+ 'v3/%s' % self.collection_key),
+ method,
+ headers=self.headers[method]) \
+ .AndReturn((resp, resp['body']))
+ self.mox.ReplayAll()
+
+ returned_list = self.manager.list()
+ self.assertTrue(len(returned_list))
+ [self.assertTrue(isinstance(r, self.model)) for r in returned_list]
+
+ def test_update(self):
+ ref = self.new_ref()
+ req_ref = ref.copy()
+ del req_ref['id']
+
+ resp = httplib2.Response({
+ 'status': 200,
+ 'body': self.serialize(ref),
+ })
+
+ method = 'PATCH'
+ httplib2.Http.request(
+ urlparse.urljoin(
+ self.TEST_URL,
+ 'v3/%s/%s' % (self.collection_key, ref['id'])),
+ method,
+ body=self.serialize(req_ref),
+ headers=self.headers[method]) \
+ .AndReturn((resp, resp['body']))
+ self.mox.ReplayAll()
+
+ returned = self.manager.update(ref['id'], **parameterize(req_ref))
+ self.assertTrue(isinstance(returned, self.model))
+ for attr in ref:
+ self.assertEqual(
+ getattr(returned, attr),
+ ref[attr],
+ 'Expected different %s' % attr)
+
+ def test_delete(self):
+ ref = self.new_ref()
+ method = 'DELETE'
+ resp = httplib2.Response({
+ 'status': 204,
+ 'body': '',
+ })
+ httplib2.Http.request(
+ urlparse.urljoin(
+ self.TEST_URL,
+ 'v3/%s/%s' % (self.collection_key, ref['id'])),
+ method,
+ headers=self.headers[method]) \
+ .AndReturn((resp, resp['body']))
+ self.mox.ReplayAll()
+
+ self.manager.delete(ref['id'])