From 13ec79bf48a185a6f359d1e6c013bdfe24b7da53 Mon Sep 17 00:00:00 2001 From: termie Date: Fri, 6 Jan 2012 23:10:18 -0800 Subject: keystoneclient tests working against sql backend --- keystonelight/backends/kvs.py | 6 +- keystonelight/backends/sql/core.py | 197 ++++++++++++++++++++++++++----------- keystonelight/keystone_compat.py | 27 ++--- keystonelight/service.py | 17 +++- keystonelight/test.py | 12 ++- 5 files changed, 180 insertions(+), 79 deletions(-) diff --git a/keystonelight/backends/kvs.py b/keystonelight/backends/kvs.py index 4394f347..c15e449c 100644 --- a/keystonelight/backends/kvs.py +++ b/keystonelight/backends/kvs.py @@ -64,10 +64,12 @@ class KvsIdentity(object): return role_ref def list_users(self): - return self.db.get('user_list', []) + user_ids = self.db.get('user_list', []) + return [self.get_user(x) for x in user_ids] def list_roles(self): - return self.db.get('role_list', []) + role_ids = self.db.get('role_list', []) + return [self.get_role(x) for x in role_ids] # These should probably be part of the high-level API def add_user_to_tenant(self, tenant_id, user_id): diff --git a/keystonelight/backends/sql/core.py b/keystonelight/backends/sql/core.py index 211d9ca2..62528976 100644 --- a/keystonelight/backends/sql/core.py +++ b/keystonelight/backends/sql/core.py @@ -63,11 +63,12 @@ class DictBase(object): Includes attributes from joins. """ - local = dict(self) - joined = dict([(k, v) for k, v in self.__dict__.iteritems() - if not k[0] == '_']) - local.update(joined) - return local.iteritems() + return dict([(k, getattr(self, k)) for k in self]) + #local = dict(self) + #joined = dict([(k, v) for k, v in self.__dict__.iteritems() + # if not k[0] == '_']) + #local.update(joined) + #return local.iteritems() # Tables @@ -75,13 +76,51 @@ class User(Base, DictBase): __tablename__ = 'user' id = sql.Column(sql.String(64), primary_key=True) name = sql.Column(sql.String(64), unique=True) - password = sql.Column(sql.String(64)) + #password = sql.Column(sql.String(64)) + extra = sql.Column(JsonBlob()) + + @classmethod + def from_dict(cls, user_dict): + # shove any non-indexed properties into extra + extra = {} + for k, v in user_dict.copy().iteritems(): + # TODO(termie): infer this somehow + if k not in ['id', 'name']: + extra[k] = user_dict.pop(k) + + user_dict['extra'] = extra + return cls(**user_dict) + + def to_dict(self): + extra_copy = self.extra.copy() + extra_copy['id'] = self.id + extra_copy['name'] = self.name + return extra_copy class Tenant(Base, DictBase): __tablename__ = 'tenant' id = sql.Column(sql.String(64), primary_key=True) name = sql.Column(sql.String(64), unique=True) + extra = sql.Column(JsonBlob()) + + @classmethod + def from_dict(cls, tenant_dict): + # shove any non-indexed properties into extra + extra = {} + for k, v in tenant_dict.copy().iteritems(): + # TODO(termie): infer this somehow + if k not in ['id', 'name']: + extra[k] = tenant_dict.pop(k) + + tenant_dict['extra'] = extra + return cls(**tenant_dict) + + def to_dict(self): + extra_copy = self.extra.copy() + extra_copy['id'] = self.id + extra_copy['name'] = self.name + return extra_copy class Role(Base, DictBase): @@ -143,7 +182,7 @@ class SqlBase(object): engine_args = { "pool_recycle": CONF.sql.idle_timeout, - "echo": False, + "echo": True, } if "sqlite" in connection_dict.drivername: @@ -177,6 +216,7 @@ class SqlIdentity(SqlBase): raise AssertionError('Invalid tenant') tenant_ref = self.get_tenant(tenant_id) + print 'ETESTSET', tenant_ref if tenant_ref: extras_ref = self.get_extras(user_id, tenant_id) else: @@ -186,29 +226,37 @@ class SqlIdentity(SqlBase): def get_tenant(self, tenant_id): session = self.get_session() tenant_ref = session.query(Tenant).filter_by(id=tenant_id).first() - return tenant_ref + if not tenant_ref: + return + return tenant_ref.to_dict() def get_tenant_by_name(self, tenant_name): session = self.get_session() tenant_ref = session.query(Tenant).filter_by(name=tenant_name).first() - return tenant_ref + if not tenant_ref: + return + return tenant_ref.to_dict() def get_user(self, user_id): session = self.get_session() user_ref = session.query(User).filter_by(id=user_id).first() - return user_ref + if not user_ref: + return + return user_ref.to_dict() def get_user_by_name(self, user_name): session = self.get_session() user_ref = session.query(User).filter_by(name=user_name).first() - return user_ref + if not user_ref: + return + return user_ref.to_dict() def get_extras(self, user_id, tenant_id): session = self.get_session() extras_ref = session.query(Extras)\ - .filter_by(user_id=user_id)\ - .filter_by(tenant_id=tenant_id)\ - .first() + .filter_by(user_id=user_id)\ + .filter_by(tenant_id=tenant_id)\ + .first() return getattr(extras_ref, 'data', None) def get_role(self, role_id): @@ -219,7 +267,7 @@ class SqlIdentity(SqlBase): def list_users(self): session = self.get_session() user_refs = session.query(User) - return list(user_refs) + return [x.to_dict() for x in user_refs] def list_roles(self): session = self.get_session() @@ -233,11 +281,13 @@ class SqlIdentity(SqlBase): session.add(UserTenantMembership(user_id=user_id, tenant_id=tenant_id)) def remove_user_from_tenant(self, tenant_id, user_id): - user_ref = self.get_user(user_id) - tenants = set(user_ref.get('tenants', [])) - tenants.remove(tenant_id) - user_ref['tenants'] = list(tenants) - self.update_user(user_id, user_ref) + session = self.get_session() + membership_ref = session.query(UserTenantMembership)\ + .filter_by(user_id=user_id)\ + .filter_by(tenant_id=tenant_id)\ + .first() + with session.begin(): + session.delete(membership_ref) def get_tenants_for_user(self, user_id): session = self.get_session() @@ -255,65 +305,84 @@ class SqlIdentity(SqlBase): def add_role_to_user_and_tenant(self, user_id, tenant_id, role_id): extras_ref = self.get_extras(user_id, tenant_id) + is_new = False if not extras_ref: + is_new = True extras_ref = {} roles = set(extras_ref.get('roles', [])) roles.add(role_id) extras_ref['roles'] = list(roles) - self.update_extras(user_id, tenant_id, extras_ref) + if not is_new: + self.update_extras(user_id, tenant_id, extras_ref) + else: + self.create_extras(user_id, tenant_id, extras_ref) def remove_role_from_user_and_tenant(self, user_id, tenant_id, role_id): extras_ref = self.get_extras(user_id, tenant_id) + is_new = False if not extras_ref: + is_new = True extras_ref = {} roles = set(extras_ref.get('roles', [])) roles.remove(role_id) extras_ref['roles'] = list(roles) - self.update_extras(user_id, tenant_id, extras_ref) + if not is_new: + self.update_extras(user_id, tenant_id, extras_ref) + else: + self.create_extras(user_id, tenant_id, extras_ref) # CRUD def create_user(self, id, user): session = self.get_session() with session.begin(): - session.add(User(**user)) - return user + user_ref = User.from_dict(user) + session.add(user_ref) + return user_ref.to_dict() def update_user(self, id, user): - # get the old name and delete it too - old_user = self.db.get('user-%s' % id) - self.db.delete('user_name-%s' % old_user['name']) - self.db.set('user-%s' % id, user) - self.db.set('user_name-%s' % user['name'], user) - return user + session = self.get_session() + with session.begin(): + user_ref = session.query(User).filter_by(id=id).first() + old_user_dict = user_ref.to_dict() + for k in user: + old_user_dict[k] = user[k] + new_user = User.from_dict(old_user_dict) + + user_ref.name = new_user.name + user_ref.extra = new_user.extra + return user_ref def delete_user(self, id): - old_user = self.db.get('user-%s' % id) - self.db.delete('user_name-%s' % old_user['name']) - self.db.delete('user-%s' % id) - user_list = set(self.db.get('user_list', [])) - user_list.remove(id) - self.db.set('user_list', list(user_list)) - return None + session = self.get_session() + user_ref = session.query(User).filter_by(id=id).first() + with session.begin(): + session.delete(user_ref) def create_tenant(self, id, tenant): session = self.get_session() with session.begin(): - session.add(Tenant(**tenant)) - return tenant + tenant_ref = Tenant.from_dict(tenant) + session.add(tenant_ref) + return tenant_ref.to_dict() def update_tenant(self, id, tenant): - # get the old name and delete it too - old_tenant = self.db.get('tenant-%s' % id) - self.db.delete('tenant_name-%s' % old_tenant['name']) - self.db.set('tenant-%s' % id, tenant) - self.db.set('tenant_name-%s' % tenant['name'], tenant) - return tenant + session = self.get_session() + with session.begin(): + tenant_ref = session.query(Tenant).filter_by(id=id).first() + old_tenant_dict = tenant_ref.to_dict() + for k in tenant: + old_tenant_dict[k] = tenant[k] + new_tenant = Tenant.from_dict(old_tenant_dict) + + tenant_ref.name = new_tenant.name + tenant_ref.extra = new_tenant.extra + return tenant_ref def delete_tenant(self, id): - old_tenant = self.db.get('tenant-%s' % id) - self.db.delete('tenant_name-%s' % old_tenant['name']) - self.db.delete('tenant-%s' % id) - return None + session = self.get_session() + tenant_ref = session.query(Tenant).filter_by(id=id).first() + with session.begin(): + session.delete(tenant_ref) def create_extras(self, user_id, tenant_id, extras): session = self.get_session() @@ -322,8 +391,17 @@ class SqlIdentity(SqlBase): return extras def update_extras(self, user_id, tenant_id, extras): - self.db.set('extras-%s-%s' % (tenant_id, user_id), extras) - return extras + session = self.get_session() + with session.begin(): + extras_ref = session.query(Extras)\ + .filter_by(user_id=user_id)\ + .filter_by(tenant_id=tenant_id)\ + .first() + data = extras_ref.data.copy() + for k in extras: + data[k] = extras[k] + extras_ref.data = data + return extras_ref def delete_extras(self, user_id, tenant_id): self.db.delete('extras-%s-%s' % (tenant_id, user_id)) @@ -336,15 +414,18 @@ class SqlIdentity(SqlBase): return role def update_role(self, id, role): - self.db.set('role-%s' % id, role) - return role + session = self.get_session() + with session.begin(): + role_ref = session.query(Role).filter_by(id=id).first() + for k in role: + role_ref[k] = role[k] + return role_ref def delete_role(self, id): - self.db.delete('role-%s' % id) - role_list = set(self.db.get('role_list', [])) - role_list.remove(id) - self.db.set('role_list', list(role_list)) - return None + session = self.get_session() + role_ref = session.query(Role).filter_by(id=id).first() + with session.begin(): + session.delete(role_ref) class SqlToken(SqlBase): diff --git a/keystonelight/keystone_compat.py b/keystonelight/keystone_compat.py index b720477c..c909881d 100644 --- a/keystonelight/keystone_compat.py +++ b/keystonelight/keystone_compat.py @@ -511,8 +511,10 @@ class KeystoneTenantController(service.BaseApplication): assert token_ref is not None user_ref = token_ref['user'] + tenant_ids = self.identity_api.get_tenants_for_user( + context, user_ref['id']) tenant_refs = [] - for tenant_id in user_ref['tenants']: + for tenant_id in tenant_ids: tenant_refs.append(self.identity_api.get_tenant( context=context, tenant_id=tenant_id)) @@ -537,9 +539,9 @@ class KeystoneTenantController(service.BaseApplication): return {'tenant': tenant} # CRUD Extension - def create_tenant(self, context, **kw): + def create_tenant(self, context, tenant): + tenant_ref = self._normalize_dict(tenant) self.assert_admin(context) - tenant_ref = kw.get('tenant') tenant_id = (tenant_ref.get('id') and tenant_ref.get('id') or uuid.uuid4().hex) @@ -590,23 +592,21 @@ class KeystoneUserController(service.BaseApplication): # NOTE(termie): i can't imagine that this really wants all the data # about every single user in the system... self.assert_admin(context) - user_list = self.identity_api.list_users(context) - return {'users': [{'id': x} for x in user_list]} + user_refs = self.identity_api.list_users(context) + return {'users': user_refs} # CRUD extension def create_user(self, context, user): + user = self._normalize_dict(user) self.assert_admin(context) - tenant_id = user.get('tenantId') - tenants = [] - if tenant_id: - tenants.append(tenant_id) + tenant_id = user.get('tenantId', None) user_id = uuid.uuid4().hex user_ref = user.copy() - #user_ref.pop('tenantId', None) user_ref['id'] = user_id - user_ref['tenants'] = tenants new_user_ref = self.identity_api.create_user( context, user_id, user_ref) + if tenant_id: + self.identity_api.add_user_to_tenant(tenant_id, user_id) return {'user': new_user_ref} # NOTE(termie): this is really more of a patch than a put @@ -656,6 +656,8 @@ class KeystoneRoleController(service.BaseApplication): return {'role': role_ref} def create_role(self, context, role): + role = self._normalize_dict(role) + self.assert_admin(context) role_id = uuid.uuid4().hex role['id'] = role_id role_ref = self.identity_api.create_role(context, role_id, role) @@ -669,8 +671,7 @@ class KeystoneRoleController(service.BaseApplication): self.assert_admin(context) roles = self.identity_api.list_roles(context) # TODO(termie): probably inefficient at some point - return {'roles': [self.identity_api.get_role(context, x) - for x in roles]} + return {'roles': roles} # COMPAT(diablo): CRUD extension def get_role_refs(self, context, user_id): diff --git a/keystonelight/service.py b/keystonelight/service.py index 50e8377c..0f7dddfe 100644 --- a/keystonelight/service.py +++ b/keystonelight/service.py @@ -47,6 +47,13 @@ URLMAP = HIGH_LEVEL_CALLS.copy() URLMAP.update(LOW_LEVEL_CALLS) +class SmarterEncoder(json.JSONEncoder): + def default(self, obj): + if not isinstance(obj, dict) and hasattr(obj, 'iteritems'): + return dict(obj.iteritems()) + return super(SmarterEncoder, self).default(obj) + + class BaseApplication(wsgi.Application): @webob.dec.wsgify def __call__(self, req): @@ -76,11 +83,18 @@ class BaseApplication(wsgi.Application): elif isinstance(result, webob.exc.WSGIHTTPException): return result - return json.dumps(result) + return self._serialize(result) + + def _serialize(self, result): + return json.dumps(result, cls=SmarterEncoder) def _normalize_arg(self, arg): return str(arg).replace(':', '_').replace('-', '_') + def _normalize_dict(self, d): + return dict([(self._normalize_arg(k), v) + for (k, v) in d.iteritems()]) + def assert_admin(self, context): if not context['is_admin']: user_token_ref = self.token_api.get_token( @@ -88,6 +102,7 @@ class BaseApplication(wsgi.Application): creds = user_token_ref['extras'].copy() creds['user_id'] = user_token_ref['user'].get('id') creds['tenant_id'] = user_token_ref['tenant'].get('id') + print creds # Accept either is_admin or the admin role assert self.policy_api.can_haz(context, ('is_admin:1', 'roles:admin'), diff --git a/keystonelight/test.py b/keystonelight/test.py index 8af08880..1196b78f 100644 --- a/keystonelight/test.py +++ b/keystonelight/test.py @@ -8,9 +8,12 @@ import time from paste import deploy +from keystonelight import catalog from keystonelight import config +from keystonelight import identity from keystonelight import logging from keystonelight import models +from keystonelight import token from keystonelight import utils from keystonelight import wsgi @@ -114,20 +117,19 @@ class TestCase(unittest.TestCase): # TODO(termie): doing something from json, probably based on Django's # loaddata will be much preferred. for tenant in fixtures.TENANTS: - rv = self.identity_api.create_tenant( - tenant['id'], models.Tenant(**tenant)) + rv = self.identity_api.create_tenant(tenant['id'], tenant) setattr(self, 'tenant_%s' % tenant['id'], rv) for user in fixtures.USERS: user_copy = user.copy() tenants = user_copy.pop('tenants') - rv = self.identity_api.create_user(user['id'], models.User(**user_copy)) + rv = self.identity_api.create_user(user['id'], user_copy) for tenant_id in tenants: self.identity_api.add_user_to_tenant(tenant_id, user['id']) setattr(self, 'user_%s' % user['id'], rv) for role in fixtures.ROLES: - rv = self.identity_api.create_role(role['id'], models.Role(**role)) + rv = self.identity_api.create_role(role['id'], role) setattr(self, 'role_%s' % role['id'], rv) for extras in fixtures.EXTRAS: @@ -137,7 +139,7 @@ class TestCase(unittest.TestCase): del extras_ref['user_id'] del extras_ref['tenant_id'] rv = self.identity_api.create_extras( - extras['user_id'], extras['tenant_id'], models.Extras(**extras_ref)) + extras['user_id'], extras['tenant_id'], extras_ref) setattr(self, 'extras_%s%s' % (extras['user_id'], extras['tenant_id']), rv) -- cgit