From 59c2dea30f0fc4db438c4515883f2667c989939c Mon Sep 17 00:00:00 2001 From: termie Date: Thu, 10 Nov 2011 11:03:40 -0800 Subject: add crud methods to identity manager --- keystonelight/identity.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/keystonelight/identity.py b/keystonelight/identity.py index a144e938..1b2a2f30 100644 --- a/keystonelight/identity.py +++ b/keystonelight/identity.py @@ -32,3 +32,31 @@ class Manager(object): def get_extras(self, context, user_id, tenant_id): return self.driver.get_extras(user_id, tenant_id) + + # CRUD operations + def create_user(self, context, user_id, data): + return self.driver.create_user(user_id, data) + + def update_user(self, context, user_id, data): + return self.driver.update_user(user_id, data) + + def delete_user(self, context, user_id): + return self.driver.delete_user(user_id) + + def create_tenant(self, context, tenant_id, data): + return self.driver.create_tenant(tenant_id, data) + + def update_tenant(self, context, tenant_id, data): + return self.driver.update_tenant(tenant_id, data) + + def delete_tenant(self, context, tenant_id): + return self.driver.delete_tenant(tenant_id) + + def create_extras(self, context, user_id, tenant_id, data): + return self.driver.create_extras(user_id, tenant_id, data) + + def update_extras(self, context, user_id, tenant_id, data): + return self.driver.update_extras(user_id, tenant_id, data) + + def delete_extras(self, context, user_id, tenant_id): + return self.driver.delete_extras(user_id, tenant_id) -- cgit From 716c450fbfb372245c7fb4b24567df7782a4b28b Mon Sep 17 00:00:00 2001 From: termie Date: Thu, 10 Nov 2011 11:03:54 -0800 Subject: make a composite app --- tests/default.conf | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/tests/default.conf b/tests/default.conf index 68388b27..39ad034e 100644 --- a/tests/default.conf +++ b/tests/default.conf @@ -1,9 +1,25 @@ [DEFAULT] -catalog_driver = keystonelight.backends.kvs.KvsCatalog +catalog_driver = keystonelight.backends.templated.TemplatedCatalog identity_driver = keystonelight.backends.kvs.KvsIdentity token_driver = keystonelight.backends.kvs.KvsToken +public_port = 5000 admin_token = ADMIN +# config for TemplatedCatalog, using camelCase because I don't want to do +# translations for keystone compat +catalog.RegionOne.identity.publicURL = http://localhost:$(public_port)s/v2.0 +catalog.RegionOne.identity.adminURL = http://localhost:$(public_port)s/v2.0 +catalog.RegionOne.identity.internalURL = http://localhost:$(public_port)s/v2.0 +catalog.RegionOne.identity.name = 'Identity Service' + +# fake compute service for now to help novaclient tests work +compute_port = 3000 +catalog.RegionOne.compute.publicURL = http://localhost:$(compute_port)s/v1.1/$(tenant_id)s +catalog.RegionOne.compute.adminURL = http://localhost:$(compute_port)s/v1.1/$(tenant_id)s +catalog.RegionOne.compute.internalURL = http://localhost:$(compute_port)s/v1.1/$(tenant_id)s +catalog.RegionOne.compute.name = 'Compute Service' + + [filter:debug] paste.filter_factory = keystonelight.wsgi:Debug.factory @@ -19,5 +35,16 @@ paste.filter_factory = keystonelight.service:JsonBodyMiddleware.factory [app:keystonelight] paste.app_factory = keystonelight.service:app_factory -[pipeline:main] +[app:keystone] +paste.app_factory = keystonelight.keystone_compat:app_factory + +[pipeline:keystone_api] +pipeline = token_auth admin_token_auth json_body debug keystone + +[pipeline:keystonelight_api] pipeline = token_auth admin_token_auth json_body debug keystonelight + +[composite:main] +use = egg:Paste#urlmap +/ = keystonelight_api +/v2.0 = keystone_api -- cgit From d7f364e2098a10a8922996618acc11a8c341a117 Mon Sep 17 00:00:00 2001 From: termie Date: Thu, 10 Nov 2011 11:17:07 -0800 Subject: move around middleware --- keystonelight/middleware.py | 96 +++++++++++++++++++++++++ keystonelight/service.py | 126 +++------------------------------ tests/default.conf | 6 +- tests/test_identity_api.py | 166 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 275 insertions(+), 119 deletions(-) create mode 100644 keystonelight/middleware.py create mode 100644 tests/test_identity_api.py diff --git a/keystonelight/middleware.py b/keystonelight/middleware.py new file mode 100644 index 00000000..f32d59c6 --- /dev/null +++ b/keystonelight/middleware.py @@ -0,0 +1,96 @@ +import json + +from keystonelight import wsgi + + +# Header used to transmit the auth token +AUTH_TOKEN_HEADER = 'X-Auth-Token' + + +# Environment variable used to pass the request context +CONTEXT_ENV = 'openstack.context' + + +# Environment variable used to pass the request params +PARAMS_ENV = 'openstack.params' + + +class TokenAuthMiddleware(wsgi.Middleware): + def process_request(self, request): + token = request.headers.get(AUTH_TOKEN_HEADER) + context = request.environ.get(CONTEXT_ENV, {}) + context['token_id'] = token + request.environ[CONTEXT_ENV] = context + + +class AdminTokenAuthMiddleware(wsgi.Middleware): + """A trivial filter that checks for a pre-defined admin token. + + Sets 'is_admin' to true in the context, expected to be checked by + methods that are admin-only. + + """ + + def process_request(self, request): + token = request.headers.get(AUTH_TOKEN_HEADER) + context = request.environ.get(CONTEXT_ENV, {}) + context['is_admin'] = (token == self.options['admin_token']) + request.environ[CONTEXT_ENV] = context + + +class PostParamsMiddleware(wsgi.Middleware): + """Middleware to allow method arguments to be passed as POST parameters. + + Filters out the parameters `self`, `context` and anything beginning with + an underscore. + + """ + + def process_request(self, request): + params_parsed = request.params + params = {} + for k, v in params_parsed.iteritems(): + if k in ('self', 'context'): + continue + if k.startswith('_'): + continue + params[k] = v + + request.environ[PARAMS_ENV] = params + + +class JsonBodyMiddleware(wsgi.Middleware): + """Middleware to allow method arguments to be passed as serialized JSON. + + Accepting arguments as JSON is useful for accepting data that may be more + complex than simple primitives. + + In this case we accept it as urlencoded data under the key 'json' as in + json= but this could be extended to accept raw JSON + in the POST body. + + Filters out the parameters `self`, `context` and anything beginning with + an underscore. + + """ + + def process_request(self, request): + #if 'json' not in request.params: + # return + + params_json = request.body + if not params_json: + return + + params_parsed = json.loads(params_json) + params = {} + for k, v in params_parsed.iteritems(): + if k in ('self', 'context'): + continue + if k.startswith('_'): + continue + params[k] = v + + request.environ[PARAMS_ENV] = params + + diff --git a/keystonelight/service.py b/keystonelight/service.py index 797a3415..e4e3e9a0 100644 --- a/keystonelight/service.py +++ b/keystonelight/service.py @@ -44,84 +44,6 @@ class BaseApplication(wsgi.Application): return json.dumps(result) -class TokenAuthMiddleware(wsgi.Middleware): - def process_request(self, request): - token = request.headers.get('X-Auth-Token') - context = request.environ.get('openstack.context', {}) - context['token_id'] = token - request.environ['openstack.context'] = context - - -class AdminTokenAuthMiddleware(wsgi.Middleware): - """A trivial filter that checks for a pre-defined admin token. - - Sets 'is_admin' to true in the context, expected to be checked by - methods that are admin-only. - - """ - def process_request(self, request): - token = request.headers.get('X-Auth-Token') - context = request.environ.get('openstack.context', {}) - context['is_admin'] = (token == self.options['admin_token']) - request.environ['openstack.context'] = context - - -class PostParamsMiddleware(wsgi.Middleware): - """Middleware to allow method arguments to be passed as POST parameters. - - Filters out the parameters `self`, `context` and anything beginning with - an underscore. - - """ - - def process_request(self, request): - params_parsed = request.params - params = {} - for k, v in params_parsed.iteritems(): - if k in ('self', 'context'): - continue - if k.startswith('_'): - continue - params[k] = v - - request.environ['openstack.params'] = params - - -class JsonBodyMiddleware(wsgi.Middleware): - """Middleware to allow method arguments to be passed as serialized JSON. - - Accepting arguments as JSON is useful for accepting data that may be more - complex than simple primitives. - - In this case we accept it as urlencoded data under the key 'json' as in - json= but this could be extended to accept raw JSON - in the POST body. - - Filters out the parameters `self`, `context` and anything beginning with - an underscore. - - """ - - def process_request(self, request): - #if 'json' not in request.params: - # return - - params_json = request.body - if not params_json: - return - - params_parsed = json.loads(params_json) - params = {} - for k, v in params_parsed.iteritems(): - if k in ('self', 'context'): - continue - if k.startswith('_'): - continue - params[k] = v - - request.environ['openstack.params'] = params - - class TokenController(BaseApplication): """Validate and pass through calls to TokenManager.""" @@ -169,51 +91,23 @@ class IdentityController(BaseApplication): class Router(wsgi.Router): def __init__(self, options): self.options = options - token_controller = utils.import_object( - options['token_controller'], - options=options) - identity_controller = utils.import_object( - options['identity_controller'], - options=options) + self.identity_controller = IdentityController(options) + self.token_controller = TokenController(options) mapper = routes.Mapper() - mapper.connect('/v2.0/tokens', controller=identity_controller, + mapper.connect('/v2.0/tokens', + controller=self.identity_controller, action='authenticate') - mapper.connect('/v2.0/tokens/{token_id}', controller=token_controller, + mapper.connect('/v2.0/tokens/{token_id}', + controller=self.token_controller, action='revoke_token', conditions=dict(method=['DELETE'])) - mapper.connect("/v2.0/tenants", controller=identity_controller, - action="get_tenants", conditions=dict(method=["GET"])) + mapper.connect("/v2.0/tenants", + controller=self.identity_controller, + action="get_tenants", + conditions=dict(method=["GET"])) super(Router, self).__init__(mapper) -class AdminRouter(wsgi.Router): - def __init__(self, options): - self.options = options - token_controller = utils.import_object( - options['token_controller'], - options=options) - identity_controller = utils.import_object( - options['identity_controller'], - options=options) - mapper = routes.Mapper() - - mapper.connect('/v2.0/tokens', controller=identity_controller, - action='authenticate') - mapper.connect('/v2.0/tokens/{token_id}', controller=token_controller, - action='validate_token', - conditions=dict(method=['GET'])) - mapper.connect('/v2.0/tokens/{token_id}', controller=token_controller, - action='revoke_token', - conditions=dict(method=['DELETE'])) - super(AdminRouter, self).__init__(mapper) - - -def identity_app_factory(global_conf, **local_conf): - conf = global_conf.copy() - conf.update(local_conf) - return Router(conf) - - def app_factory(global_conf, **local_conf): conf = global_conf.copy() conf.update(local_conf) diff --git a/tests/default.conf b/tests/default.conf index 39ad034e..220a350f 100644 --- a/tests/default.conf +++ b/tests/default.conf @@ -24,13 +24,13 @@ catalog.RegionOne.compute.name = 'Compute Service' paste.filter_factory = keystonelight.wsgi:Debug.factory [filter:token_auth] -paste.filter_factory = keystonelight.service:TokenAuthMiddleware.factory +paste.filter_factory = keystonelight.middleware:TokenAuthMiddleware.factory [filter:admin_token_auth] -paste.filter_factory = keystonelight.service:AdminTokenAuthMiddleware.factory +paste.filter_factory = keystonelight.middleware:AdminTokenAuthMiddleware.factory [filter:json_body] -paste.filter_factory = keystonelight.service:JsonBodyMiddleware.factory +paste.filter_factory = keystonelight.middleware:JsonBodyMiddleware.factory [app:keystonelight] paste.app_factory = keystonelight.service:app_factory diff --git a/tests/test_identity_api.py b/tests/test_identity_api.py new file mode 100644 index 00000000..ed1135ef --- /dev/null +++ b/tests/test_identity_api.py @@ -0,0 +1,166 @@ +import uuid + +from keystonelight import models +from keystonelight import test +from keystonelight import utils +from keystonelight.backends import kvs + + +class IdentityApi(test.TestCase): + def setUp(self): + super(IdentityApi, self).setUp() + self.options = self.appconfig('default') + app = self.loadapp('default') + self.app = app + + self.identity_backend = utils.import_object( + self.options['identity_driver'], options=self.options) + self.token_backend = utils.import_object( + self.options['token_driver'], options=self.options) + self.catalog_backend = utils.import_object( + self.options['catalog_driver'], options=self.options) + self._load_fixtures() + + def _load_fixtures(self): + self.tenant_bar = self.identity_backend._create_tenant( + 'bar', + models.Tenant(id='bar', name='BAR')) + self.user_foo = self.identity_backend._create_user( + 'foo', + models.User(id='foo', + name='FOO', + password='foo2', + tenants=[self.tenant_bar['id']])) + self.extras_foobar = self.identity_backend._create_extras( + 'foo', 'bar', + {'extra': 'extra'}) + + def test_authenticate_bad_user(self): + self.assertRaises(AssertionError, + self.identity_api.authenticate, + user_id=self.user_foo['id'] + 'WRONG', + tenant_id=self.tenant_bar['id'], + password=self.user_foo['password']) + + def test_authenticate_bad_password(self): + self.assertRaises(AssertionError, + self.identity_api.authenticate, + user_id=self.user_foo['id'], + tenant_id=self.tenant_bar['id'], + password=self.user_foo['password'] + 'WRONG') + + def test_authenticate_invalid_tenant(self): + self.assertRaises(AssertionError, + self.identity_api.authenticate, + user_id=self.user_foo['id'], + tenant_id=self.tenant_bar['id'] + 'WRONG', + password=self.user_foo['password']) + + def test_authenticate_no_tenant(self): + user_ref, tenant_ref, extras_ref = self.identity_api.authenticate( + user_id=self.user_foo['id'], + password=self.user_foo['password']) + self.assertDictEquals(user_ref, self.user_foo) + self.assert_(tenant_ref is None) + self.assert_(extras_ref is None) + + def test_authenticate(self): + user_ref, tenant_ref, extras_ref = self.identity_api.authenticate( + user_id=self.user_foo['id'], + tenant_id=self.tenant_bar['id'], + password=self.user_foo['password']) + self.assertDictEquals(user_ref, self.user_foo) + self.assertDictEquals(tenant_ref, self.tenant_bar) + self.assertDictEquals(extras_ref, self.extras_foobar) + + def test_get_tenant_bad_tenant(self): + tenant_ref = self.identity_api.get_tenant( + tenant_id=self.tenant_bar['id'] + 'WRONG') + self.assert_(tenant_ref is None) + + def test_get_tenant(self): + tenant_ref = self.identity_api.get_tenant(tenant_id=self.tenant_bar['id']) + self.assertDictEquals(tenant_ref, self.tenant_bar) + + def test_get_tenant_by_name_bad_tenant(self): + tenant_ref = self.identity_api.get_tenant( + tenant_id=self.tenant_bar['name'] + 'WRONG') + self.assert_(tenant_ref is None) + + def test_get_tenant_by_name(self): + tenant_ref = self.identity_api.get_tenant_by_name( + tenant_name=self.tenant_bar['name']) + self.assertDictEquals(tenant_ref, self.tenant_bar) + + def test_get_user_bad_user(self): + user_ref = self.identity_api.get_user( + user_id=self.user_foo['id'] + 'WRONG') + self.assert_(user_ref is None) + + def test_get_user(self): + user_ref = self.identity_api.get_user(user_id=self.user_foo['id']) + self.assertDictEquals(user_ref, self.user_foo) + + def test_get_extras_bad_user(self): + extras_ref = self.identity_api.get_extras( + user_id=self.user_foo['id'] + 'WRONG', + tenant_id=self.tenant_bar['id']) + self.assert_(extras_ref is None) + + def test_get_extras_bad_tenant(self): + extras_ref = self.identity_api.get_extras( + user_id=self.user_foo['id'], + tenant_id=self.tenant_bar['id'] + 'WRONG') + self.assert_(extras_ref is None) + + def test_get_extras(self): + extras_ref = self.identity_api.get_extras( + user_id=self.user_foo['id'], + tenant_id=self.tenant_bar['id']) + self.assertDictEquals(extras_ref, self.extras_foobar) + + +class KvsToken(test.TestCase): + def setUp(self): + super(KvsToken, self).setUp() + options = self.appconfig('default') + self.token_api = kvs.KvsToken(options=options, db={}) + + def test_token_crud(self): + token_id = uuid.uuid4().hex + data = {'id': token_id, + 'a': 'b'} + data_ref = self.token_api.create_token(token_id, data) + self.assertDictEquals(data_ref, data) + + new_data_ref = self.token_api.get_token(token_id) + self.assertEquals(new_data_ref, data) + + self.token_api.delete_token(token_id) + deleted_data_ref = self.token_api.get_token(token_id) + self.assert_(deleted_data_ref is None) + + +class KvsCatalog(test.TestCase): + def setUp(self): + super(KvsCatalog, self).setUp() + options = self.appconfig('default') + self.catalog_api = kvs.KvsCatalog(options=options, db={}) + self._load_fixtures() + + def _load_fixtures(self): + self.catalog_foobar = self.catalog_api._create_catalog( + 'foo', 'bar', + {'RegionFoo': {'service_bar': {'foo': 'bar'}}}) + + def test_get_catalog_bad_user(self): + catalog_ref = self.catalog_api.get_catalog('foo' + 'WRONG', 'bar') + self.assert_(catalog_ref is None) + + def test_get_catalog_bad_tenant(self): + catalog_ref = self.catalog_api.get_catalog('foo', 'bar' + 'WRONG') + self.assert_(catalog_ref is None) + + def test_get_catalog(self): + catalog_ref = self.catalog_api.get_catalog('foo', 'bar') + self.assertDictEquals(catalog_ref, self.catalog_foobar) -- cgit From 4b4969f7dc9f895cf03286fbcd6ff006503b6d71 Mon Sep 17 00:00:00 2001 From: termie Date: Thu, 10 Nov 2011 14:12:25 -0800 Subject: update service to middleware in confs --- tests/keystone_compat_diablo.conf | 6 +++--- tests/keystoneclient_compat_master.conf | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/keystone_compat_diablo.conf b/tests/keystone_compat_diablo.conf index d9052631..94024df3 100644 --- a/tests/keystone_compat_diablo.conf +++ b/tests/keystone_compat_diablo.conf @@ -9,13 +9,13 @@ admin_token = ADMIN paste.filter_factory = keystonelight.wsgi:Debug.factory [filter:token_auth] -paste.filter_factory = keystonelight.service:TokenAuthMiddleware.factory +paste.filter_factory = keystonelight.middleware:TokenAuthMiddleware.factory [filter:admin_token_auth] -paste.filter_factory = keystonelight.service:AdminTokenAuthMiddleware.factory +paste.filter_factory = keystonelight.middleware:AdminTokenAuthMiddleware.factory [filter:json_body] -paste.filter_factory = keystonelight.service:JsonBodyMiddleware.factory +paste.filter_factory = keystonelight.middleware:JsonBodyMiddleware.factory [app:keystone] paste.app_factory = keystonelight.keystone_compat:app_factory diff --git a/tests/keystoneclient_compat_master.conf b/tests/keystoneclient_compat_master.conf index e006e821..e9861b6b 100644 --- a/tests/keystoneclient_compat_master.conf +++ b/tests/keystoneclient_compat_master.conf @@ -24,13 +24,13 @@ catalog.RegionOne.compute.name = 'Compute Service' paste.filter_factory = keystonelight.wsgi:Debug.factory [filter:token_auth] -paste.filter_factory = keystonelight.service:TokenAuthMiddleware.factory +paste.filter_factory = keystonelight.middleware:TokenAuthMiddleware.factory [filter:admin_token_auth] -paste.filter_factory = keystonelight.service:AdminTokenAuthMiddleware.factory +paste.filter_factory = keystonelight.middleware:AdminTokenAuthMiddleware.factory [filter:json_body] -paste.filter_factory = keystonelight.service:JsonBodyMiddleware.factory +paste.filter_factory = keystonelight.middleware:JsonBodyMiddleware.factory [app:keystone] paste.app_factory = keystonelight.keystone_compat:app_factory -- cgit From 84644998b3d4b6321bd27ca21ded89b371ef1957 Mon Sep 17 00:00:00 2001 From: termie Date: Thu, 10 Nov 2011 14:57:42 -0800 Subject: get some initial identity api tests working --- bin/ksl | 1 - keystonelight/client.py | 67 ++++++++++++++++++ keystonelight/middleware.py | 2 - keystonelight/service.py | 35 +++++----- tests/test_identity_api.py | 161 ++++++++++---------------------------------- 5 files changed, 122 insertions(+), 144 deletions(-) create mode 100644 keystonelight/client.py diff --git a/bin/ksl b/bin/ksl index 2005ee44..e8f2587a 100755 --- a/bin/ksl +++ b/bin/ksl @@ -33,7 +33,6 @@ class LoadData(BaseApp): pass - CMDS = {'loaddata': LoadData, } diff --git a/keystonelight/client.py b/keystonelight/client.py new file mode 100644 index 00000000..7b971584 --- /dev/null +++ b/keystonelight/client.py @@ -0,0 +1,67 @@ + +"""Client library for KeystoneLight API.""" + +import json + +import httplib2 +import webob + +from keystonelight import wsgi + + +class Client(object): + def __init__(self, token=None): + self.token = token + + def request(self, method, path, headers, body): + raise NotImplemented + + def get(self, path, headers=None): + return self.request('GET', path=path, headers=headers) + + def post(self, path, headers=None, body=None): + return self.request('POST', path=path, headers=headers, body=body) + + def put(self, path, headers=None, body=None): + return self.request('PUT', path=path, headers=headers, body=body) + + def _build_headers(self, headers=None): + if headers is None: + headers = {} + + if self.token: + headers.setdefault('X-Auth-Token', self.token) + + return headers + + +class HttpClient(Client): + def __init__(self, endpoint=None, token=None): + self.endpoint = endpoint + super(HttpClient, self).__init__(token=token) + + def request(self, method, path, headers=None, body=None): + if type(body) is type({}): + body = json.dumps(body) + headers = self._build_headers(headers) + h = httplib.Http() + resp, content = h.request(path, method=method, headers=headers, body=body) + return webob.Response(content, status=resp.status, headerlist=resp.headers) + + +class TestClient(Client): + def __init__(self, app=None, token=None): + self.app = app + super(TestClient, self).__init__(token=token) + + def request(self, method, path, headers=None, body=None): + if type(body) is type({}): + body = json.dumps(body) + headers = self._build_headers(headers) + req = wsgi.Request.blank(path) + req.method = method + for k, v in headers.iteritems(): + req.headers[k] = v + if body: + req.body = body + return req.get_response(self.app) diff --git a/keystonelight/middleware.py b/keystonelight/middleware.py index f32d59c6..29e655bd 100644 --- a/keystonelight/middleware.py +++ b/keystonelight/middleware.py @@ -92,5 +92,3 @@ class JsonBodyMiddleware(wsgi.Middleware): params[k] = v request.environ[PARAMS_ENV] = params - - diff --git a/keystonelight/service.py b/keystonelight/service.py index e4e3e9a0..a4c46e9e 100644 --- a/keystonelight/service.py +++ b/keystonelight/service.py @@ -71,21 +71,26 @@ class IdentityController(BaseApplication): self.options = options def authenticate(self, context, **kwargs): - tenant, user, extras = self.identity_api.authenticate(context, - **kwargs) - token = self.token_api.create_token(context, - dict(tenant=tenant, - user=user, - extras=extras)) - logging.debug('TOKEN: %s', token) - return token + user_ref, tenant_ref, extras_ref = self.identity_api.authenticate( + context, **kwargs) + # TODO(termie): strip password from return values + token_ref = self.token_api.create_token(context, + dict(tenant=tenant_ref, + user=user_ref, + extras=extras_ref)) + logging.debug('TOKEN: %s', token_ref) + return token_ref def get_tenants(self, context): - token_id = context.get('token') - token = self.token_api.validate_token(context, token_id) + token_id = context.get('token_id') + token_ref = self.token_api.get_token(context, token_id) + assert token_ref + tenants_ref = [] + for tenant_id in token_ref['user']['tenants']: + tenants_ref.append(self.identity_api.get_tenant(context, + tenant_id)) - return self.identity_api.get_tenants(context, - user_id=token['user']) + return tenants_ref class Router(wsgi.Router): @@ -94,14 +99,14 @@ class Router(wsgi.Router): self.identity_controller = IdentityController(options) self.token_controller = TokenController(options) mapper = routes.Mapper() - mapper.connect('/v2.0/tokens', + mapper.connect('/tokens', controller=self.identity_controller, action='authenticate') - mapper.connect('/v2.0/tokens/{token_id}', + mapper.connect('/tokens/{token_id}', controller=self.token_controller, action='revoke_token', conditions=dict(method=['DELETE'])) - mapper.connect("/v2.0/tenants", + mapper.connect("/tenants", controller=self.identity_controller, action="get_tenants", conditions=dict(method=["GET"])) diff --git a/tests/test_identity_api.py b/tests/test_identity_api.py index ed1135ef..21b25781 100644 --- a/tests/test_identity_api.py +++ b/tests/test_identity_api.py @@ -1,5 +1,7 @@ +import json import uuid +from keystonelight import client from keystonelight import models from keystonelight import test from keystonelight import utils @@ -35,132 +37,39 @@ class IdentityApi(test.TestCase): 'foo', 'bar', {'extra': 'extra'}) - def test_authenticate_bad_user(self): - self.assertRaises(AssertionError, - self.identity_api.authenticate, - user_id=self.user_foo['id'] + 'WRONG', - tenant_id=self.tenant_bar['id'], - password=self.user_foo['password']) - - def test_authenticate_bad_password(self): - self.assertRaises(AssertionError, - self.identity_api.authenticate, - user_id=self.user_foo['id'], - tenant_id=self.tenant_bar['id'], - password=self.user_foo['password'] + 'WRONG') - - def test_authenticate_invalid_tenant(self): - self.assertRaises(AssertionError, - self.identity_api.authenticate, - user_id=self.user_foo['id'], - tenant_id=self.tenant_bar['id'] + 'WRONG', - password=self.user_foo['password']) - - def test_authenticate_no_tenant(self): - user_ref, tenant_ref, extras_ref = self.identity_api.authenticate( - user_id=self.user_foo['id'], - password=self.user_foo['password']) - self.assertDictEquals(user_ref, self.user_foo) - self.assert_(tenant_ref is None) - self.assert_(extras_ref is None) + def _login(self): + c = client.TestClient(self.app) + post_data = {'user_id': self.user_foo['id'], + 'tenant_id': self.tenant_bar['id'], + 'password': self.user_foo['password']} + resp = c.post('/tokens', body=post_data) + token = json.loads(resp.body) + return token def test_authenticate(self): - user_ref, tenant_ref, extras_ref = self.identity_api.authenticate( - user_id=self.user_foo['id'], - tenant_id=self.tenant_bar['id'], - password=self.user_foo['password']) - self.assertDictEquals(user_ref, self.user_foo) - self.assertDictEquals(tenant_ref, self.tenant_bar) - self.assertDictEquals(extras_ref, self.extras_foobar) - - def test_get_tenant_bad_tenant(self): - tenant_ref = self.identity_api.get_tenant( - tenant_id=self.tenant_bar['id'] + 'WRONG') - self.assert_(tenant_ref is None) - - def test_get_tenant(self): - tenant_ref = self.identity_api.get_tenant(tenant_id=self.tenant_bar['id']) - self.assertDictEquals(tenant_ref, self.tenant_bar) - - def test_get_tenant_by_name_bad_tenant(self): - tenant_ref = self.identity_api.get_tenant( - tenant_id=self.tenant_bar['name'] + 'WRONG') - self.assert_(tenant_ref is None) - - def test_get_tenant_by_name(self): - tenant_ref = self.identity_api.get_tenant_by_name( - tenant_name=self.tenant_bar['name']) - self.assertDictEquals(tenant_ref, self.tenant_bar) + c = client.TestClient(self.app) + post_data = {'user_id': self.user_foo['id'], + 'tenant_id': self.tenant_bar['id'], + 'password': self.user_foo['password']} + resp = c.post('/tokens', body=post_data) + data = json.loads(resp.body) + self.assertEquals(self.user_foo['id'], data['user']['id']) + self.assertEquals(self.tenant_bar['id'], data['tenant']['id']) + self.assertDictEquals(self.extras_foobar, data['extras']) - def test_get_user_bad_user(self): - user_ref = self.identity_api.get_user( - user_id=self.user_foo['id'] + 'WRONG') - self.assert_(user_ref is None) - - def test_get_user(self): - user_ref = self.identity_api.get_user(user_id=self.user_foo['id']) - self.assertDictEquals(user_ref, self.user_foo) - - def test_get_extras_bad_user(self): - extras_ref = self.identity_api.get_extras( - user_id=self.user_foo['id'] + 'WRONG', - tenant_id=self.tenant_bar['id']) - self.assert_(extras_ref is None) - - def test_get_extras_bad_tenant(self): - extras_ref = self.identity_api.get_extras( - user_id=self.user_foo['id'], - tenant_id=self.tenant_bar['id'] + 'WRONG') - self.assert_(extras_ref is None) - - def test_get_extras(self): - extras_ref = self.identity_api.get_extras( - user_id=self.user_foo['id'], - tenant_id=self.tenant_bar['id']) - self.assertDictEquals(extras_ref, self.extras_foobar) - - -class KvsToken(test.TestCase): - def setUp(self): - super(KvsToken, self).setUp() - options = self.appconfig('default') - self.token_api = kvs.KvsToken(options=options, db={}) - - def test_token_crud(self): - token_id = uuid.uuid4().hex - data = {'id': token_id, - 'a': 'b'} - data_ref = self.token_api.create_token(token_id, data) - self.assertDictEquals(data_ref, data) - - new_data_ref = self.token_api.get_token(token_id) - self.assertEquals(new_data_ref, data) - - self.token_api.delete_token(token_id) - deleted_data_ref = self.token_api.get_token(token_id) - self.assert_(deleted_data_ref is None) - - -class KvsCatalog(test.TestCase): - def setUp(self): - super(KvsCatalog, self).setUp() - options = self.appconfig('default') - self.catalog_api = kvs.KvsCatalog(options=options, db={}) - self._load_fixtures() - - def _load_fixtures(self): - self.catalog_foobar = self.catalog_api._create_catalog( - 'foo', 'bar', - {'RegionFoo': {'service_bar': {'foo': 'bar'}}}) - - def test_get_catalog_bad_user(self): - catalog_ref = self.catalog_api.get_catalog('foo' + 'WRONG', 'bar') - self.assert_(catalog_ref is None) - - def test_get_catalog_bad_tenant(self): - catalog_ref = self.catalog_api.get_catalog('foo', 'bar' + 'WRONG') - self.assert_(catalog_ref is None) - - def test_get_catalog(self): - catalog_ref = self.catalog_api.get_catalog('foo', 'bar') - self.assertDictEquals(catalog_ref, self.catalog_foobar) + def test_authenticate_no_tenant(self): + c = client.TestClient(self.app) + post_data = {'user_id': self.user_foo['id'], + 'password': self.user_foo['password']} + resp = c.post('/tokens', body=post_data) + data = json.loads(resp.body) + self.assertEquals(self.user_foo['id'], data['user']['id']) + self.assertEquals(None, data['tenant']) + self.assertEquals(None, data['extras']) + + def test_get_tenants(self): + token = self._login() + c = client.TestClient(self.app, token['id']) + resp = c.get('/tenants') + data = json.loads(resp.body) + self.assertDictEquals(self.tenant_bar, data[0]) -- cgit From e10512b2b879e4e70a59722d00dcb21f6a940dff Mon Sep 17 00:00:00 2001 From: termie Date: Thu, 10 Nov 2011 15:57:54 -0800 Subject: more dyanmic client --- keystonelight/client.py | 33 ++++++++++++++++++++ keystonelight/service.py | 77 ++++++++++++++++++++++++++++++++++++---------- tests/test_identity_api.py | 6 ++-- 3 files changed, 96 insertions(+), 20 deletions(-) diff --git a/keystonelight/client.py b/keystonelight/client.py index 7b971584..25128ba3 100644 --- a/keystonelight/client.py +++ b/keystonelight/client.py @@ -6,9 +6,13 @@ import json import httplib2 import webob +from keystonelight import service from keystonelight import wsgi +URLMAP = service.URLMAP + + class Client(object): def __init__(self, token=None): self.token = token @@ -34,6 +38,35 @@ class Client(object): return headers + def __getattr__(self, key): + """Lazy way to define a bunch of dynamic urls based on URLMAP. + + Turns something like + + c.authenticate(user_id='foo', password='bar') + + into + + c.request('POST', '/token', body={'user_id': 'foo', 'password': 'bar'}) + + """ + if key not in URLMAP: + raise AttributeError(key) + + method, path = URLMAP[key] + + def _internal(method_=method, path_=path, **kw): + path_ = path_ % kw + params = {'method': method_, + 'path': path_} + if method.lower() in ('put', 'post'): + params['body'] = kw + return self.request(**params) + + setattr(self, key, _internal) + + return getattr(self, key) + class HttpClient(Client): def __init__(self, endpoint=None, token=None): diff --git a/keystonelight/service.py b/keystonelight/service.py index a4c46e9e..536a2a05 100644 --- a/keystonelight/service.py +++ b/keystonelight/service.py @@ -1,7 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# this is the web service frontend - import json import logging @@ -15,6 +11,41 @@ from keystonelight import utils from keystonelight import wsgi +HIGH_LEVEL_CALLS = { + 'authenticate': ('POST', '/tokens'), + 'get_tenants': ('GET', '/user/%(user_id)s/tenants'), + 'get_user': ('GET', '/user/%(user_id)s'), + 'get_tenant': ('GET', '/tenant/%(tenant_id)s'), + 'get_tenant_by_name': ('GET', '/tenant_name/%(tenant_name)s'), + 'get_extras': ('GET', '/extras/%(tenant_id)s-%(user_id)s'), + 'get_token': ('GET', '/token/%(token_id)s'), + } + +# NOTE(termie): creates are seperate from updates to allow duplication checks +LOW_LEVEL_CALLS = { + # tokens + 'create_token': ('POST', '/token'), + 'delete_token': ('DELETE', '/token/%(token_id)s'), + # users + 'create_user': ('POST', '/user'), + 'update_user': ('PUT', '/user/%(user_id)s'), + 'delete_user': ('DELETE', '/user/%(user_id)s'), + # tenants + 'create_tenant': ('POST', '/tenant'), + 'update_tenant': ('PUT', '/tenant/%(tenant_id)s'), + 'delete_tenant': ('DELETE', '/tenant/%(tenant_id)s'), + # extras + # NOTE(termie): these separators are probably going to bite us eventually + 'create_extras': ('POST', '/extras'), + 'update_extras': ('PUT', '/extras/%(tenant_id)s-%(user_id)s'), + 'delete_extras': ('DELETE', '/extras/%(tenant_id)s-%(user_id)s'), + } + + +URLMAP = HIGH_LEVEL_CALLS.copy() +URLMAP.update(LOW_LEVEL_CALLS) + + class BaseApplication(wsgi.Application): @webob.dec.wsgify def __call__(self, req): @@ -81,10 +112,11 @@ class IdentityController(BaseApplication): logging.debug('TOKEN: %s', token_ref) return token_ref - def get_tenants(self, context): + def get_tenants(self, context, user_id=None): token_id = context.get('token_id') token_ref = self.token_api.get_token(context, token_id) assert token_ref + assert token_ref['user']['id'] == user_id tenants_ref = [] for tenant_id in token_ref['user']['tenants']: tenants_ref.append(self.identity_api.get_tenant(context, @@ -98,20 +130,31 @@ class Router(wsgi.Router): self.options = options self.identity_controller = IdentityController(options) self.token_controller = TokenController(options) - mapper = routes.Mapper() - mapper.connect('/tokens', - controller=self.identity_controller, - action='authenticate') - mapper.connect('/tokens/{token_id}', - controller=self.token_controller, - action='revoke_token', - conditions=dict(method=['DELETE'])) - mapper.connect("/tenants", - controller=self.identity_controller, - action="get_tenants", - conditions=dict(method=["GET"])) + + mapper = self._build_map(URLMAP) super(Router, self).__init__(mapper) + def _build_map(self, urlmap): + """Build a routes.Mapper based on URLMAP.""" + mapper = routes.Mapper() + for k, v in urlmap.iteritems(): + # NOTE(termie): hack + if 'token' in k: + controller = self.token_controller + else: + controller = self.identity_controller + action = k + method, path = v + path = path.replace('%(', '{').replace(')s', '}') + print path + + mapper.connect(path, + controller=controller, + action=action, + conditions=dict(method=[method])) + + return mapper + def app_factory(global_conf, **local_conf): conf = global_conf.copy() diff --git a/tests/test_identity_api.py b/tests/test_identity_api.py index 21b25781..885d4314 100644 --- a/tests/test_identity_api.py +++ b/tests/test_identity_api.py @@ -51,7 +51,7 @@ class IdentityApi(test.TestCase): post_data = {'user_id': self.user_foo['id'], 'tenant_id': self.tenant_bar['id'], 'password': self.user_foo['password']} - resp = c.post('/tokens', body=post_data) + resp = c.authenticate(**post_data) data = json.loads(resp.body) self.assertEquals(self.user_foo['id'], data['user']['id']) self.assertEquals(self.tenant_bar['id'], data['tenant']['id']) @@ -61,7 +61,7 @@ class IdentityApi(test.TestCase): c = client.TestClient(self.app) post_data = {'user_id': self.user_foo['id'], 'password': self.user_foo['password']} - resp = c.post('/tokens', body=post_data) + resp = c.authenticate(**post_data) data = json.loads(resp.body) self.assertEquals(self.user_foo['id'], data['user']['id']) self.assertEquals(None, data['tenant']) @@ -70,6 +70,6 @@ class IdentityApi(test.TestCase): def test_get_tenants(self): token = self._login() c = client.TestClient(self.app, token['id']) - resp = c.get('/tenants') + resp = c.get_tenants(user_id=self.user_foo['id']) data = json.loads(resp.body) self.assertDictEquals(self.tenant_bar, data[0]) -- cgit From f2e73bc9b20b26947980067bcf95c9989e37907d Mon Sep 17 00:00:00 2001 From: termie Date: Mon, 14 Nov 2011 09:33:17 -0800 Subject: re-indent service.py --- keystonelight/service.py | 187 +++++++++++++++++++++++------------------------ 1 file changed, 93 insertions(+), 94 deletions(-) diff --git a/keystonelight/service.py b/keystonelight/service.py index 536a2a05..682f5151 100644 --- a/keystonelight/service.py +++ b/keystonelight/service.py @@ -47,116 +47,115 @@ URLMAP.update(LOW_LEVEL_CALLS) class BaseApplication(wsgi.Application): - @webob.dec.wsgify - def __call__(self, req): - arg_dict = req.environ['wsgiorg.routing_args'][1] - action = arg_dict['action'] - del arg_dict['action'] - del arg_dict['controller'] - logging.debug('arg_dict: %s', arg_dict) + @webob.dec.wsgify + def __call__(self, req): + arg_dict = req.environ['wsgiorg.routing_args'][1] + action = arg_dict['action'] + del arg_dict['action'] + del arg_dict['controller'] + logging.debug('arg_dict: %s', arg_dict) - context = req.environ.get('openstack.context', {}) - # allow middleware up the stack to override the params - params = {} - if 'openstack.params' in req.environ: - params = req.environ['openstack.params'] - params.update(arg_dict) + context = req.environ.get('openstack.context', {}) + # allow middleware up the stack to override the params + params = {} + if 'openstack.params' in req.environ: + params = req.environ['openstack.params'] + params.update(arg_dict) - # TODO(termie): do some basic normalization on methods - method = getattr(self, action) + # TODO(termie): do some basic normalization on methods + method = getattr(self, action) - # NOTE(vish): make sure we have no unicode keys for py2.6. - params = dict([(str(k), v) for (k, v) in params.iteritems()]) - result = method(context, **params) + # NOTE(vish): make sure we have no unicode keys for py2.6. + params = dict([(str(k), v) for (k, v) in params.iteritems()]) + result = method(context, **params) - if result is None or type(result) is str or type(result) is unicode: - return result + if result is None or type(result) is str or type(result) is unicode: + return result - return json.dumps(result) + return json.dumps(result) class TokenController(BaseApplication): - """Validate and pass through calls to TokenManager.""" + """Validate and pass through calls to TokenManager.""" - def __init__(self, options): - self.token_api = token.Manager(options=options) - self.options = options + def __init__(self, options): + self.token_api = token.Manager(options=options) + self.options = options - def validate_token(self, context, token_id): - token_info = self.token_api.validate_token(context, token_id) - if not token_info: - raise webob.exc.HTTPUnauthorized() - return token_info + def validate_token(self, context, token_id): + token_info = self.token_api.validate_token(context, token_id) + if not token_info: + raise webob.exc.HTTPUnauthorized() + return token_info class IdentityController(BaseApplication): - """Validate and pass calls through to IdentityManager. - - IdentityManager will also pretty much just pass calls through to - a specific driver. - """ - - def __init__(self, options): - self.identity_api = identity.Manager(options=options) - self.token_api = token.Manager(options=options) - self.options = options - - def authenticate(self, context, **kwargs): - user_ref, tenant_ref, extras_ref = self.identity_api.authenticate( - context, **kwargs) - # TODO(termie): strip password from return values - token_ref = self.token_api.create_token(context, - dict(tenant=tenant_ref, - user=user_ref, - extras=extras_ref)) - logging.debug('TOKEN: %s', token_ref) - return token_ref - - def get_tenants(self, context, user_id=None): - token_id = context.get('token_id') - token_ref = self.token_api.get_token(context, token_id) - assert token_ref - assert token_ref['user']['id'] == user_id - tenants_ref = [] - for tenant_id in token_ref['user']['tenants']: - tenants_ref.append(self.identity_api.get_tenant(context, - tenant_id)) - - return tenants_ref + """Validate and pass calls through to IdentityManager. + + IdentityManager will also pretty much just pass calls through to + a specific driver. + """ + + def __init__(self, options): + self.identity_api = identity.Manager(options=options) + self.token_api = token.Manager(options=options) + self.options = options + + def authenticate(self, context, **kwargs): + user_ref, tenant_ref, extras_ref = self.identity_api.authenticate( + context, **kwargs) + # TODO(termie): strip password from return values + token_ref = self.token_api.create_token(context, + dict(tenant=tenant_ref, + user=user_ref, + extras=extras_ref)) + logging.debug('TOKEN: %s', token_ref) + return token_ref + + def get_tenants(self, context, user_id=None): + token_id = context.get('token_id') + token_ref = self.token_api.get_token(context, token_id) + assert token_ref + assert token_ref['user']['id'] == user_id + tenants_ref = [] + for tenant_id in token_ref['user']['tenants']: + tenants_ref.append(self.identity_api.get_tenant(context, + tenant_id)) + + return tenants_ref class Router(wsgi.Router): - def __init__(self, options): - self.options = options - self.identity_controller = IdentityController(options) - self.token_controller = TokenController(options) - - mapper = self._build_map(URLMAP) - super(Router, self).__init__(mapper) - - def _build_map(self, urlmap): - """Build a routes.Mapper based on URLMAP.""" - mapper = routes.Mapper() - for k, v in urlmap.iteritems(): - # NOTE(termie): hack - if 'token' in k: - controller = self.token_controller - else: - controller = self.identity_controller - action = k - method, path = v - path = path.replace('%(', '{').replace(')s', '}') - print path - - mapper.connect(path, - controller=controller, - action=action, - conditions=dict(method=[method])) - - return mapper + def __init__(self, options): + self.options = options + self.identity_controller = IdentityController(options) + self.token_controller = TokenController(options) + + mapper = self._build_map(URLMAP) + super(Router, self).__init__(mapper) + + def _build_map(self, urlmap): + """Build a routes.Mapper based on URLMAP.""" + mapper = routes.Mapper() + for k, v in urlmap.iteritems(): + # NOTE(termie): hack + if 'token' in k: + controller = self.token_controller + else: + controller = self.identity_controller + action = k + method, path = v + path = path.replace('%(', '{').replace(')s', '}') + + mapper.connect(path, + controller=controller, + action=action, + conditions=dict(method=[method])) + + return mapper def app_factory(global_conf, **local_conf): - conf = global_conf.copy() - conf.update(local_conf) - return Router(conf) + conf = global_conf.copy() + conf.update(local_conf) + return Router(conf) -- cgit