OpenStack版本比较之Keystone
目的本文主要比较OpenStack中Essex与Folsom版本的Keystone在依赖包、数据库结构、配置方面的差异,为Keystone从Essex向Folsom升级做些前期准备工作。这些比较大部分是在源代码库上通过git命令进行的,首先要clone一个keystone的本地库,命令如下:
view plaincopyprint?
[*]git clone git://github.com/openstack/keystone.git
[*]cd keystone
git clone git://github.com/openstack/keystone.git
cd keystone
依赖包的差异
tools/pip-requires文件包含keystone的依赖库文件信息,通过以下命令比较发现Folsom版只是添加了一个依赖包iso8601版本须>=0.1.4,该包同时也是nova E版和F版的依赖包,因此依赖包的差异可以忽略,升级Keystone可以不安装其它依赖包。
view plaincopyprint?
[*]$ git diff 2012.1:tools/pip-requires 2012.2:tools/pip-requires
[*]diff --git a/2012.1:tools/pip-requires b/2012.2:tools/pip-requires
[*]index 0e13534..ec5562d 100644
[*]--- a/2012.1:tools/pip-requires
[*]+++ b/2012.2:tools/pip-requires
[*]@@ -1,4 +1,4 @@
[*]-# keystonelight dependencies
[*]+# keystone dependencies
[*]pam==0.1.4
[*]WebOb==1.0.8
[*]eventlet
[*]@@ -10,3 +10,4 @@ sqlalchemy
[*]sqlalchemy-migrate
[*]passlib
[*]lxml
[*]+iso8601>=0.1.4
$ git diff 2012.1:tools/pip-requires 2012.2:tools/pip-requires
diff --git a/2012.1:tools/pip-requires b/2012.2:tools/pip-requires
index 0e13534..ec5562d 100644
--- a/2012.1:tools/pip-requires
+++ b/2012.2:tools/pip-requires
@@ -1,4 +1,4 @@
-# keystonelight dependencies
+# keystone dependencies
pam==0.1.4
WebOb==1.0.8
eventlet
@@ -10,3 +10,4 @@ sqlalchemy
sqlalchemy-migrate
passlib
lxml
+iso8601>=0.1.4
数据库结构的差异
与数据库相关的源文件
要比较数据库结构的差异首先要找到keystone中与数据库相关的源文件,可通过以下命令实现:
view plaincopyprint?
[*]$ git grep "sql.ModelBase"
[*]keystone/catalog/backends/sql.py:class Service(sql.ModelBase, sql.DictBase):
[*]keystone/catalog/backends/sql.py:class Endpoint(sql.ModelBase, sql.DictBase):
[*]keystone/common/sql/migrate_repo/versions/001_add_initial_tables.py: sql.ModelBase.metadata.create_all(migrate_engine)
[*]keystone/contrib/ec2/backends/sql.py:class Ec2Credential(sql.ModelBase, sql.DictBase):
[*]keystone/identity/backends/sql.py:class User(sql.ModelBase, sql.DictBase):
[*]keystone/identity/backends/sql.py:class Tenant(sql.ModelBase, sql.DictBase):
[*]keystone/identity/backends/sql.py:class Role(sql.ModelBase, sql.DictBase):
[*]keystone/identity/backends/sql.py:class Metadata(sql.ModelBase, sql.DictBase):
[*]keystone/identity/backends/sql.py:class UserTenantMembership(sql.ModelBase, sql.DictBase):
[*]keystone/token/backends/sql.py:class TokenModel(sql.ModelBase, sql.DictBase):
[*]tests/test_backend_sql.py: sql.ModelBase.metadata.bind = engine
[*]tests/test_backend_sql.py: sql.ModelBase.metadata.create_all(engine)
[*]
$ git grep "sql.ModelBase"
keystone/catalog/backends/sql.py:class Service(sql.ModelBase, sql.DictBase):
keystone/catalog/backends/sql.py:class Endpoint(sql.ModelBase, sql.DictBase):
keystone/common/sql/migrate_repo/versions/001_add_initial_tables.py: sql.ModelBase.metadata.create_all(migrate_engine)
keystone/contrib/ec2/backends/sql.py:class Ec2Credential(sql.ModelBase, sql.DictBase):
keystone/identity/backends/sql.py:class User(sql.ModelBase, sql.DictBase):
keystone/identity/backends/sql.py:class Tenant(sql.ModelBase, sql.DictBase):
keystone/identity/backends/sql.py:class Role(sql.ModelBase, sql.DictBase):
keystone/identity/backends/sql.py:class Metadata(sql.ModelBase, sql.DictBase):
keystone/identity/backends/sql.py:class UserTenantMembership(sql.ModelBase, sql.DictBase):
keystone/token/backends/sql.py:class TokenModel(sql.ModelBase, sql.DictBase):
tests/test_backend_sql.py: sql.ModelBase.metadata.bind = engine
tests/test_backend_sql.py: sql.ModelBase.metadata.create_all(engine)
service及endpoint表比较
这两个表的定义在源文件keystone/catalog/backends/sql.py中,分别为Service及Endpoint类,从下面的结果可以看出这两个类完全没变,只是在类Catalog中对它们的操作做了些修改,因此在数据库方面这两个表应该是没在差异的。
view plaincopyprint?
[*]$ git diff 2012.1:keystone/catalog/backends/sql.py 2012.2:keystone/catalog/backends/sql.py > tables_diff
[*]$ cat tables_diff
[*]diff --git a/2012.1:keystone/catalog/backends/sql.py b/2012.2:keystone/catalog/backends/sql.py
[*]index 3c553e0..bd0f687 100644
[*]--- a/2012.1:keystone/catalog/backends/sql.py
[*]+++ b/2012.2:keystone/catalog/backends/sql.py
[*]@@ -15,14 +15,11 @@
[*]# License for the specific language governing permissions and limitations
[*]# under the License.
[*]
[*]-import sqlalchemy.exc
[*]-import webob.exc
[*]-
[*]from keystone import catalog
[*]-from keystone import config
[*]-from keystone import exception
[*]from keystone.common import sql
[*]from keystone.common.sql import migration
[*]+from keystone import config
[*]+from keystone import exception
[*]
[*]
[*]CONF = config.CONF
[*]@@ -96,11 +93,9 @@ class Catalog(sql.Base, catalog.Driver):
[*]
[*]def delete_service(self, service_id):
[*]session = self.get_session()
[*]- service_ref = session.query(Service).filter_by(id=service_id).first()
[*]- if not service_ref:
[*]- raise exception.ServiceNotFound(service_id=service_id)
[*]with session.begin():
[*]- session.delete(service_ref)
[*]+ if not session.query(Service).filter_by(id=service_id).delete():
[*]+ raise exception.ServiceNotFound(service_id=service_id)
[*]session.flush()
[*]
[*]def create_service(self, service_id, service_ref):
[*]@@ -114,6 +109,7 @@ class Catalog(sql.Base, catalog.Driver):
[*]# Endpoints
[*]def create_endpoint(self, endpoint_id, endpoint_ref):
[*]session = self.get_session()
[*]+ self.get_service(endpoint_ref['service_id'])
[*]new_endpoint = Endpoint.from_dict(endpoint_ref)
[*]with session.begin():
[*]session.add(new_endpoint)
[*]@@ -122,18 +118,17 @@ class Catalog(sql.Base, catalog.Driver):
[*]
[*]def delete_endpoint(self, endpoint_id):
[*]session = self.get_session()
[*]- endpoint_ref = session.query(Endpoint)
[*]- endpoint_ref = endpoint_ref.filter_by(id=endpoint_id).first()
[*]- if not endpoint_ref:
[*]- raise exception.EndpointNotFound(endpoint_id=endpoint_id)
[*]with session.begin():
[*]- session.delete(endpoint_ref)
[*]+ if not session.query(Endpoint).filter_by(id=endpoint_id).delete():
[*]+ raise exception.EndpointNotFound(endpoint_id=endpoint_id)
[*]session.flush()
[*]
[*]def get_endpoint(self, endpoint_id):
[*]session = self.get_session()
[*]endpoint_ref = session.query(Endpoint)
[*]endpoint_ref = endpoint_ref.filter_by(id=endpoint_id).first()
[*]+ if not endpoint_ref:
[*]+ raise exception.EndpointNotFound(endpoint_id=endpoint_id)
[*]return endpoint_ref.to_dict()
[*]
[*]def list_endpoints(self):
[*]@@ -163,6 +158,7 @@ class Catalog(sql.Base, catalog.Driver):
[*]internal_url = ep['internalurl'].replace('$(', '%(')
[*]public_url = ep['publicurl'].replace('$(', '%(')
[*]admin_url = ep['adminurl'].replace('$(', '%(')
[*]+ catalog['id'] = ep['id']
[*]catalog['name'] = srv_name
[*]catalog['publicURL'] = public_url % d
[*]catalog['adminURL'] = admin_url % d
[*]
$ git diff 2012.1:keystone/catalog/backends/sql.py 2012.2:keystone/catalog/backends/sql.py > tables_diff
$ cat tables_diff
diff --git a/2012.1:keystone/catalog/backends/sql.py b/2012.2:keystone/catalog/backends/sql.py
index 3c553e0..bd0f687 100644
--- a/2012.1:keystone/catalog/backends/sql.py
+++ b/2012.2:keystone/catalog/backends/sql.py
@@ -15,14 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-import sqlalchemy.exc
-import webob.exc
-
from keystone import catalog
-from keystone import config
-from keystone import exception
from keystone.common import sql
from keystone.common.sql import migration
+from keystone import config
+from keystone import exception
CONF = config.CONF
@@ -96,11 +93,9 @@ class Catalog(sql.Base, catalog.Driver):
def delete_service(self, service_id):
session = self.get_session()
- service_ref = session.query(Service).filter_by(id=service_id).first()
- if not service_ref:
- raise exception.ServiceNotFound(service_id=service_id)
with session.begin():
- session.delete(service_ref)
+ if not session.query(Service).filter_by(id=service_id).delete():
+ raise exception.ServiceNotFound(service_id=service_id)
session.flush()
def create_service(self, service_id, service_ref):
@@ -114,6 +109,7 @@ class Catalog(sql.Base, catalog.Driver):
# Endpoints
def create_endpoint(self, endpoint_id, endpoint_ref):
session = self.get_session()
+ self.get_service(endpoint_ref['service_id'])
new_endpoint = Endpoint.from_dict(endpoint_ref)
with session.begin():
session.add(new_endpoint)
@@ -122,18 +118,17 @@ class Catalog(sql.Base, catalog.Driver):
def delete_endpoint(self, endpoint_id):
session = self.get_session()
- endpoint_ref = session.query(Endpoint)
- endpoint_ref = endpoint_ref.filter_by(id=endpoint_id).first()
- if not endpoint_ref:
- raise exception.EndpointNotFound(endpoint_id=endpoint_id)
with session.begin():
- session.delete(endpoint_ref)
+ if not session.query(Endpoint).filter_by(id=endpoint_id).delete():
+ raise exception.EndpointNotFound(endpoint_id=endpoint_id)
session.flush()
def get_endpoint(self, endpoint_id):
session = self.get_session()
endpoint_ref = session.query(Endpoint)
endpoint_ref = endpoint_ref.filter_by(id=endpoint_id).first()
+ if not endpoint_ref:
+ raise exception.EndpointNotFound(endpoint_id=endpoint_id)
return endpoint_ref.to_dict()
def list_endpoints(self):
@@ -163,6 +158,7 @@ class Catalog(sql.Base, catalog.Driver):
internal_url = ep['internalurl'].replace('$(', '%(')
public_url = ep['publicurl'].replace('$(', '%(')
admin_url = ep['adminurl'].replace('$(', '%(')
+ catalog['id'] = ep['id']
catalog['name'] = srv_name
catalog['publicURL'] = public_url % d
catalog['adminURL'] = admin_url % d
user、tenant、role、metadata、user_tenant_membership表的比较
这几个表的定义均在源文件keystone/identity/backends/sql.py的相关类中。从下面的输出来看,在F版中的主要变化是给表user、tenant、role的name列均添加了not null属性,另外在Identity类中对这些表的操作做了不少修改,但应该不会导致相关表的结构变化。
view plaincopyprint?
[*]$ git diff 2012.1:keystone/identity/backends/sql.py 2012.2:keystone/identity/backends/sql.py > tables_diff
[*]$ cat tables_diff
[*]diff --git a/2012.1:keystone/identity/backends/sql.py b/2012.2:keystone/identity/backends/sql.py
[*]index e4281a8..a3c8d1f 100644
[*]--- a/2012.1:keystone/identity/backends/sql.py
[*]+++ b/2012.2:keystone/identity/backends/sql.py
[*]@@ -17,11 +17,12 @@
[*]import copy
[*]import functools
[*]
[*]-from keystone import identity
[*]-from keystone import exception
[*]+from keystone import clean
[*]from keystone.common import sql
[*]-from keystone.common import utils
[*]from keystone.common.sql import migration
[*]+from keystone.common import utils
[*]+from keystone import exception
[*]+from keystone import identity
[*]
[*]
[*]def _filter_user(user_ref):
[*]@@ -45,7 +46,7 @@ def handle_conflicts(type='object'):
[*]try:
[*]return method(*args, **kwargs)
[*]except sql.IntegrityError as e:
[*]- raise exception.Conflict(type=type, details=str(e))
[*]+ raise exception.Conflict(type=type, details=e.message)
[*]return wrapper
[*]return decorator
[*]
[*]@@ -53,7 +54,7 @@ def handle_conflicts(type='object'):
[*]class User(sql.ModelBase, sql.DictBase):
[*]__tablename__ = 'user'
[*]id = sql.Column(sql.String(64), primary_key=True)
[*]- name = sql.Column(sql.String(64), unique=True)
[*]+ name = sql.Column(sql.String(64), unique=True, nullable=False)
[*]#password = sql.Column(sql.String(64))
[*]extra = sql.Column(sql.JsonBlob())
[*]
[*]@@ -79,7 +80,7 @@ class User(sql.ModelBase, sql.DictBase):
[*]class Tenant(sql.ModelBase, sql.DictBase):
[*]__tablename__ = 'tenant'
[*]id = sql.Column(sql.String(64), primary_key=True)
[*]- name = sql.Column(sql.String(64), unique=True)
[*]+ name = sql.Column(sql.String(64), unique=True, nullable=False)
[*]extra = sql.Column(sql.JsonBlob())
[*]
[*]@classmethod
[*]@@ -104,7 +105,7 @@ class Tenant(sql.ModelBase, sql.DictBase):
[*]class Role(sql.ModelBase, sql.DictBase):
[*]__tablename__ = 'role'
[*]id = sql.Column(sql.String(64), primary_key=True)
[*]- name = sql.Column(sql.String(64), unique=True)
[*]+ name = sql.Column(sql.String(64), unique=True, nullable=False)
[*]
[*]
[*]class Metadata(sql.ModelBase, sql.DictBase):
[*]@@ -134,6 +135,20 @@ class Identity(sql.Base, identity.Driver):
[*]def db_sync(self):
[*]migration.db_sync()
[*]
[*]+ def _check_password(self, password, user_ref):
[*]+ """Check the specified password against the data store.
[*]+
[*]+ This is modeled on ldap/core.py. The idea is to make it easier to
[*]+ subclass Identity so that you can still use it to store all the data,
[*]+ but use some other means to check the password.
[*]+ Note that we'll pass in the entire user_ref in case the subclass
[*]+ needs things like user_ref.get('name')
[*]+ For further justification, please see the follow up suggestion at
[*]+ https://blueprints.launchpad.net/keystone/+spec/sql-identiy-pam
[*]+
[*]+ """
[*]+ return utils.check_password(password, user_ref.get('password'))
[*]+
[*]# Identity interface
[*]def authenticate(self, user_id=None, tenant_id=None, password=None):
[*]"""Authenticate based on a user, tenant and password.
[*]@@ -142,56 +157,69 @@ class Identity(sql.Base, identity.Driver):
[*]in the list of tenants on the user.
[*]
[*]"""
[*]- user_ref = self._get_user(user_id)
[*]- if (not user_ref
[*]- or not utils.check_password(password, user_ref.get('password'))):
[*]+ user_ref = None
[*]+ tenant_ref = None
[*]+ metadata_ref = {}
[*]+
[*]+ try:
[*]+ user_ref = self._get_user(user_id)
[*]+ except exception.UserNotFound:
[*]raise AssertionError('Invalid user / password')
[*]
[*]- tenants = self.get_tenants_for_user(user_id)
[*]- if tenant_id and tenant_id not in tenants:
[*]- raise AssertionError('Invalid tenant')
[*]+ if not utils.check_password(password, user_ref.get('password')):
[*]+ raise AssertionError('Invalid user / password')
[*]+
[*]+ if tenant_id is not None:
[*]+ if tenant_id not in self.get_tenants_for_user(user_id):
[*]+ raise AssertionError('Invalid tenant')
[*]+
[*]+ try:
[*]+ tenant_ref = self.get_tenant(tenant_id)
[*]+ metadata_ref = self.get_metadata(user_id, tenant_id)
[*]+ except exception.TenantNotFound:
[*]+ tenant_ref = None
[*]+ metadata_ref = {}
[*]+ except exception.MetadataNotFound:
[*]+ metadata_ref = {}
[*]
[*]- tenant_ref = self.get_tenant(tenant_id)
[*]- if tenant_ref:
[*]- metadata_ref = self.get_metadata(user_id, tenant_id)
[*]- else:
[*]- metadata_ref = {}
[*]return (_filter_user(user_ref), tenant_ref, metadata_ref)
[*]
[*]def get_tenant(self, tenant_id):
[*]session = self.get_session()
[*]tenant_ref = session.query(Tenant).filter_by(id=tenant_id).first()
[*]- if not tenant_ref:
[*]- return
[*]+ if tenant_ref is None:
[*]+ raise exception.TenantNotFound(tenant_id=tenant_id)
[*]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()
[*]if not tenant_ref:
[*]- return
[*]+ raise exception.TenantNotFound(tenant_id=tenant_name)
[*]return tenant_ref.to_dict()
[*]
[*]def get_tenant_users(self, tenant_id):
[*]session = self.get_session()
[*]+ self.get_tenant(tenant_id)
[*]user_refs = session.query(User)\
[*]- .join(UserTenantMembership)\
[*]- .filter(UserTenantMembership.tenant_id == tenant_id)\
[*]- .all()
[*]+ .join(UserTenantMembership)\
[*]+ .filter(UserTenantMembership.tenant_id ==
[*]+ tenant_id)\
[*]+ .all()
[*]return
[*]
[*]def _get_user(self, user_id):
[*]session = self.get_session()
[*]user_ref = session.query(User).filter_by(id=user_id).first()
[*]if not user_ref:
[*]- return
[*]+ raise exception.UserNotFound(user_id=user_id)
[*]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()
[*]if not user_ref:
[*]- return
[*]+ raise exception.UserNotFound(user_id=user_name)
[*]return user_ref.to_dict()
[*]
[*]def get_user(self, user_id):
[*]@@ -206,11 +234,16 @@ class Identity(sql.Base, identity.Driver):
[*].filter_by(user_id=user_id)\
[*].filter_by(tenant_id=tenant_id)\
[*].first()
[*]- return getattr(metadata_ref, 'data', {})
[*]+ if metadata_ref is None:
[*]+ raise exception.MetadataNotFound()
[*]+ return metadata_ref.data
[*]
[*]def get_role(self, role_id):
[*]session = self.get_session()
[*]- return session.query(Role).filter_by(id=role_id).first()
[*]+ role_ref = session.query(Role).filter_by(id=role_id).first()
[*]+ if role_ref is None:
[*]+ raise exception.RoleNotFound(role_id=role_id)
[*]+ return role_ref
[*]
[*]def list_users(self):
[*]session = self.get_session()
[*]@@ -225,6 +258,8 @@ class Identity(sql.Base, identity.Driver):
[*]# These should probably be part of the high-level API
[*]def add_user_to_tenant(self, tenant_id, user_id):
[*]session = self.get_session()
[*]+ self.get_tenant(tenant_id)
[*]+ self.get_user(user_id)
[*]q = session.query(UserTenantMembership)\
[*].filter_by(user_id=user_id)\
[*].filter_by(tenant_id=tenant_id)
[*]@@ -239,10 +274,14 @@ class Identity(sql.Base, identity.Driver):
[*]
[*]def remove_user_from_tenant(self, tenant_id, user_id):
[*]session = self.get_session()
[*]
[*]+ self.get_tenant(tenant_id)
[*]+ self.get_user(user_id)
[*]membership_ref = session.query(UserTenantMembership)\
[*].filter_by(user_id=user_id)\
[*].filter_by(tenant_id=tenant_id)\
[*].first()
[*]+ if membership_ref is None:
[*]+ raise exception.NotFound('User not found in tenant')
[*]with session.begin():
[*]session.delete(membership_ref)
[*]session.flush()
[*]@@ -254,37 +293,50 @@ class Identity(sql.Base, identity.Driver):
[*]
[*]def get_tenants_for_user(self, user_id):
[*]session = self.get_session()
[*]+ self.get_user(user_id)
[*]membership_refs = session.query(UserTenantMembership)\
[*]- .filter_by(user_id=user_id)\
[*]- .all()
[*]+ .filter_by(user_id=user_id)\
[*]+ .all()
[*]return
[*]
[*]def get_roles_for_user_and_tenant(self, user_id, tenant_id):
[*]- metadata_ref = self.get_metadata(user_id, tenant_id)
[*]- if not metadata_ref:
[*]+ self.get_user(user_id)
[*]+ self.get_tenant(tenant_id)
[*]+ try:
[*]+ metadata_ref = self.get_metadata(user_id, tenant_id)
[*]+ except exception.MetadataNotFound:
[*]metadata_ref = {}
[*]return metadata_ref.get('roles', [])
[*]
[*]def add_role_to_user_and_tenant(self, user_id, tenant_id, role_id):
[*]- metadata_ref = self.get_metadata(user_id, tenant_id)
[*]- is_new = False
[*]- if not metadata_ref:
[*]- is_new = True
[*]+ self.get_user(user_id)
[*]+ self.get_tenant(tenant_id)
[*]+ self.get_role(role_id)
[*]+ try:
[*]+ metadata_ref = self.get_metadata(user_id, tenant_id)
[*]+ is_new = False
[*]+ except exception.MetadataNotFound:
[*]metadata_ref = {}
[*]+ is_new = True
[*]roles = set(metadata_ref.get('roles', []))
[*]+ if role_id in roles:
[*]+ msg = ('User %s already has role %s in tenant %s'
[*]+ % (user_id, role_id, tenant_id))
[*]+ raise exception.Conflict(type='role grant', details=msg)
[*]roles.add(role_id)
[*]metadata_ref['roles'] = list(roles)
[*]- if not is_new:
[*]- self.update_metadata(user_id, tenant_id, metadata_ref)
[*]- else:
[*]+ if is_new:
[*]self.create_metadata(user_id, tenant_id, metadata_ref)
[*]+ else:
[*]+ self.update_metadata(user_id, tenant_id, metadata_ref)
[*]
[*]def remove_role_from_user_and_tenant(self, user_id, tenant_id, role_id):
[*]- metadata_ref = self.get_metadata(user_id, tenant_id)
[*]- is_new = False
[*]- if not metadata_ref:
[*]- is_new = True
[*]+ try:
[*]+ metadata_ref = self.get_metadata(user_id, tenant_id)
[*]+ is_new = False
[*]+ except exception.MetadataNotFound:
[*]metadata_ref = {}
[*]+ is_new = True
[*]roles = set(metadata_ref.get('roles', []))
[*]if role_id not in roles:
[*]msg = 'Cannot remove role that has not been granted, %s' % role_id
[*]@@ -292,14 +344,15 @@ class Identity(sql.Base, identity.Driver):
[*]
[*]roles.remove(role_id)
[*]metadata_ref['roles'] = list(roles)
[*]- if not is_new:
[*]- self.update_metadata(user_id, tenant_id, metadata_ref)
[*]- else:
[*]+ if is_new:
[*]self.create_metadata(user_id, tenant_id, metadata_ref)
[*]+ else:
[*]+ self.update_metadata(user_id, tenant_id, metadata_ref)
[*]
[*]# CRUD
[*]@handle_conflicts(type='user')
[*]def create_user(self, user_id, user):
[*]+ user['name'] = clean.user_name(user['name'])
[*]user = _ensure_hashed_password(user)
[*]session = self.get_session()
[*]with session.begin():
[*]@@ -310,9 +363,15 @@ class Identity(sql.Base, identity.Driver):
[*]
[*]@handle_conflicts(type='user')
[*]def update_user(self, user_id, user):
[*]+ if 'name' in user:
[*]+ user['name'] = clean.user_name(user['name'])
[*]session = self.get_session()
[*]+ if 'id' in user and user_id != user['id']:
[*]+ raise exception.ValidationError('Cannot change user ID')
[*]with session.begin():
[*]user_ref = session.query(User).filter_by(id=user_id).first()
[*]+ if user_ref is None:
[*]+ raise exception.UserNotFound(user_id=user_id)
[*]old_user_dict = user_ref.to_dict()
[*]user = _ensure_hashed_password(user)
[*]for k in user:
[*]@@ -326,21 +385,17 @@ class Identity(sql.Base, identity.Driver):
[*]
[*]def delete_user(self, user_id):
[*]session = self.get_session()
[*]- user_ref = session.query(User).filter_by(id=user_id).first()
[*]- membership_refs = session.query(UserTenantMembership)\
[*]- .filter_by(user_id=user_id)\
[*]- .all()
[*]-
[*]with session.begin():
[*]- if membership_refs:
[*]- for membership_ref in membership_refs:
[*]- session.delete(membership_ref)
[*]-
[*]- session.delete(user_ref)
[*]- session.flush()
[*]+ session.query(UserTenantMembership)\
[*]+ .filter_by(user_id=user_id).delete(False)
[*]+ session.query(Metadata)\
[*]+ .filter_by(user_id=user_id).delete(False)
[*]+ if not session.query(User).filter_by(id=user_id).delete(False):
[*]+ raise exception.UserNotFound(user_id=user_id)
[*]
[*]@handle_conflicts(type='tenant')
[*]def create_tenant(self, tenant_id, tenant):
[*]+ tenant['name'] = clean.tenant_name(tenant['name'])
[*]session = self.get_session()
[*]with session.begin():
[*]tenant_ref = Tenant.from_dict(tenant)
[*]@@ -350,9 +405,13 @@ class Identity(sql.Base, identity.Driver):
[*]
[*]@handle_conflicts(type='tenant')
[*]def update_tenant(self, tenant_id, tenant):
[*]+ if 'name' in tenant:
[*]+ tenant['name'] = clean.tenant_name(tenant['name'])
[*]session = self.get_session()
[*]with session.begin():
[*]tenant_ref = session.query(Tenant).filter_by(id=tenant_id).first()
[*]+ if tenant_ref is None:
[*]+ raise exception.TenantNotFound(tenant_id=tenant_id)
[*]old_tenant_dict = tenant_ref.to_dict()
[*]for k in tenant:
[*]old_tenant_dict = tenant
[*]@@ -365,10 +424,13 @@ class Identity(sql.Base, identity.Driver):
[*]
[*]def delete_tenant(self, tenant_id):
[*]session = self.get_session()
[*]- tenant_ref = session.query(Tenant).filter_by(id=tenant_id).first()
[*]with session.begin():
[*]- session.delete(tenant_ref)
[*]- session.flush()
[*]+ session.query(UserTenantMembership)\
[*]+ .filter_by(tenant_id=tenant_id).delete(False)
[*]+ session.query(Metadata)\
[*]+ .filter_by(tenant_id=tenant_id).delete(False)
[*]+ if not session.query(Tenant).filter_by(id=tenant_id).delete(False):
[*]+ raise exception.TenantNotFound(tenant_id=tenant_id)
[*]
[*]@handle_conflicts(type='metadata')
[*]def create_metadata(self, user_id, tenant_id, metadata):
[*]@@ -412,6 +474,8 @@ class Identity(sql.Base, identity.Driver):
[*]session = self.get_session()
[*]with session.begin():
[*]role_ref = session.query(Role).filter_by(id=role_id).first()
[*]+ if role_ref is None:
[*]+ raise exception.RoleNotFound(role_id=role_id)
[*]for k in role:
[*]role_ref = role
[*]session.flush()
[*]@@ -419,6 +483,7 @@ class Identity(sql.Base, identity.Driver):
[*]
[*]def delete_role(self, role_id):
[*]session = self.get_session()
[*]- role_ref = session.query(Role).filter_by(id=role_id).first()
[*]with session.begin():
[*]- session.delete(role_ref)
[*]+ if not session.query(Role).filter_by(id=role_id).delete():
[*]+ raise exception.RoleNotFound(role_id=role_id)
[*]+ session.flush()
[*]
$ git diff 2012.1:keystone/identity/backends/sql.py2012.2:keystone/identity/backends/sql.py> tables_diff
$ cat tables_diff
diff --git a/2012.1:keystone/identity/backends/sql.py b/2012.2:keystone/identity/backends/sql.py
index e4281a8..a3c8d1f 100644
--- a/2012.1:keystone/identity/backends/sql.py
+++ b/2012.2:keystone/identity/backends/sql.py
@@ -17,11 +17,12 @@
import copy
import functools
-from keystone import identity
-from keystone import exception
+from keystone import clean
from keystone.common import sql
-from keystone.common import utils
from keystone.common.sql import migration
+from keystone.common import utils
+from keystone import exception
+from keystone import identity
def _filter_user(user_ref):
@@ -45,7 +46,7 @@ def handle_conflicts(type='object'):
try:
return method(*args, **kwargs)
except sql.IntegrityError as e:
- raise exception.Conflict(type=type, details=str(e))
+ raise exception.Conflict(type=type, details=e.message)
return wrapper
return decorator
@@ -53,7 +54,7 @@ def handle_conflicts(type='object'):
class User(sql.ModelBase, sql.DictBase):
__tablename__ = 'user'
id = sql.Column(sql.String(64), primary_key=True)
- name = sql.Column(sql.String(64), unique=True)
+ name = sql.Column(sql.String(64), unique=True, nullable=False)
#password = sql.Column(sql.String(64))
extra = sql.Column(sql.JsonBlob())
@@ -79,7 +80,7 @@ class User(sql.ModelBase, sql.DictBase):
class Tenant(sql.ModelBase, sql.DictBase):
__tablename__ = 'tenant'
id = sql.Column(sql.String(64), primary_key=True)
- name = sql.Column(sql.String(64), unique=True)
+ name = sql.Column(sql.String(64), unique=True, nullable=False)
extra = sql.Column(sql.JsonBlob())
@classmethod
@@ -104,7 +105,7 @@ class Tenant(sql.ModelBase, sql.DictBase):
class Role(sql.ModelBase, sql.DictBase):
__tablename__ = 'role'
id = sql.Column(sql.String(64), primary_key=True)
- name = sql.Column(sql.String(64), unique=True)
+ name = sql.Column(sql.String(64), unique=True, nullable=False)
class Metadata(sql.ModelBase, sql.DictBase):
@@ -134,6 +135,20 @@ class Identity(sql.Base, identity.Driver):
def db_sync(self):
migration.db_sync()
+ def _check_password(self, password, user_ref):
+ """Check the specified password against the data store.
+
+ This is modeled on ldap/core.py.The idea is to make it easier to
+ subclass Identity so that you can still use it to store all the data,
+ but use some other means to check the password.
+ Note that we'll pass in the entire user_ref in case the subclass
+ needs things like user_ref.get('name')
+ For further justification, please see the follow up suggestion at
+ https://blueprints.launchpad.net/keystone/+spec/sql-identiy-pam
+
+ """
+ return utils.check_password(password, user_ref.get('password'))
+
# Identity interface
def authenticate(self, user_id=None, tenant_id=None, password=None):
"""Authenticate based on a user, tenant and password.
@@ -142,56 +157,69 @@ class Identity(sql.Base, identity.Driver):
in the list of tenants on the user.
"""
- user_ref = self._get_user(user_id)
- if (not user_ref
- or not utils.check_password(password, user_ref.get('password'))):
+ user_ref = None
+ tenant_ref = None
+ metadata_ref = {}
+
+ try:
+ user_ref = self._get_user(user_id)
+ except exception.UserNotFound:
raise AssertionError('Invalid user / password')
- tenants = self.get_tenants_for_user(user_id)
- if tenant_id and tenant_id not in tenants:
- raise AssertionError('Invalid tenant')
+ if not utils.check_password(password, user_ref.get('password')):
+ raise AssertionError('Invalid user / password')
+
+ if tenant_id is not None:
+ if tenant_id not in self.get_tenants_for_user(user_id):
+ raise AssertionError('Invalid tenant')
+
+ try:
+ tenant_ref = self.get_tenant(tenant_id)
+ metadata_ref = self.get_metadata(user_id, tenant_id)
+ except exception.TenantNotFound:
+ tenant_ref = None
+ metadata_ref = {}
+ except exception.MetadataNotFound:
+ metadata_ref = {}
- tenant_ref = self.get_tenant(tenant_id)
- if tenant_ref:
- metadata_ref = self.get_metadata(user_id, tenant_id)
- else:
- metadata_ref = {}
return (_filter_user(user_ref), tenant_ref, metadata_ref)
def get_tenant(self, tenant_id):
session = self.get_session()
tenant_ref = session.query(Tenant).filter_by(id=tenant_id).first()
- if not tenant_ref:
- return
+ if tenant_ref is None:
+ raise exception.TenantNotFound(tenant_id=tenant_id)
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()
if not tenant_ref:
- return
+ raise exception.TenantNotFound(tenant_id=tenant_name)
return tenant_ref.to_dict()
def get_tenant_users(self, tenant_id):
session = self.get_session()
+ self.get_tenant(tenant_id)
user_refs = session.query(User)\
- .join(UserTenantMembership)\
- .filter(UserTenantMembership.tenant_id == tenant_id)\
- .all()
+ .join(UserTenantMembership)\
+ .filter(UserTenantMembership.tenant_id ==
+ tenant_id)\
+ .all()
return
def _get_user(self, user_id):
session = self.get_session()
user_ref = session.query(User).filter_by(id=user_id).first()
if not user_ref:
- return
+ raise exception.UserNotFound(user_id=user_id)
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()
if not user_ref:
- return
+ raise exception.UserNotFound(user_id=user_name)
return user_ref.to_dict()
def get_user(self, user_id):
@@ -206,11 +234,16 @@ class Identity(sql.Base, identity.Driver):
.filter_by(user_id=user_id)\
.filter_by(tenant_id=tenant_id)\
.first()
- return getattr(metadata_ref, 'data', {})
+ if metadata_ref is None:
+ raise exception.MetadataNotFound()
+ return metadata_ref.data
def get_role(self, role_id):
session = self.get_session()
- return session.query(Role).filter_by(id=role_id).first()
+ role_ref = session.query(Role).filter_by(id=role_id).first()
+ if role_ref is None:
+ raise exception.RoleNotFound(role_id=role_id)
+ return role_ref
def list_users(self):
session = self.get_session()
@@ -225,6 +258,8 @@ class Identity(sql.Base, identity.Driver):
# These should probably be part of the high-level API
def add_user_to_tenant(self, tenant_id, user_id):
session = self.get_session()
+ self.get_tenant(tenant_id)
+ self.get_user(user_id)
q = session.query(UserTenantMembership)\
.filter_by(user_id=user_id)\
.filter_by(tenant_id=tenant_id)
@@ -239,10 +274,14 @@ class Identity(sql.Base, identity.Driver):
def remove_user_from_tenant(self, tenant_id, user_id):
session = self.get_session()
+ self.get_tenant(tenant_id)
+ self.get_user(user_id)
membership_ref = session.query(UserTenantMembership)\
.filter_by(user_id=user_id)\
.filter_by(tenant_id=tenant_id)\
.first()
+ if membership_ref is None:
+ raise exception.NotFound('User not found in tenant')
with session.begin():
session.delete(membership_ref)
session.flush()
@@ -254,37 +293,50 @@ class Identity(sql.Base, identity.Driver):
def get_tenants_for_user(self, user_id):
session = self.get_session()
+ self.get_user(user_id)
membership_refs = session.query(UserTenantMembership)\
- .filter_by(user_id=user_id)\
- .all()
+ .filter_by(user_id=user_id)\
+ .all()
return
def get_roles_for_user_and_tenant(self, user_id, tenant_id):
- metadata_ref = self.get_metadata(user_id, tenant_id)
- if not metadata_ref:
+ self.get_user(user_id)
+ self.get_tenant(tenant_id)
+ try:
+ metadata_ref = self.get_metadata(user_id, tenant_id)
+ except exception.MetadataNotFound:
metadata_ref = {}
return metadata_ref.get('roles', [])
def add_role_to_user_and_tenant(self, user_id, tenant_id, role_id):
- metadata_ref = self.get_metadata(user_id, tenant_id)
- is_new = False
- if not metadata_ref:
- is_new = True
+ self.get_user(user_id)
+ self.get_tenant(tenant_id)
+ self.get_role(role_id)
+ try:
+ metadata_ref = self.get_metadata(user_id, tenant_id)
+ is_new = False
+ except exception.MetadataNotFound:
metadata_ref = {}
+ is_new = True
roles = set(metadata_ref.get('roles', []))
+ if role_id in roles:
+ msg = ('User %s already has role %s in tenant %s'
+ % (user_id, role_id, tenant_id))
+ raise exception.Conflict(type='role grant', details=msg)
roles.add(role_id)
metadata_ref['roles'] = list(roles)
- if not is_new:
- self.update_metadata(user_id, tenant_id, metadata_ref)
- else:
+ if is_new:
self.create_metadata(user_id, tenant_id, metadata_ref)
+ else:
+ self.update_metadata(user_id, tenant_id, metadata_ref)
def remove_role_from_user_and_tenant(self, user_id, tenant_id, role_id):
- metadata_ref = self.get_metadata(user_id, tenant_id)
- is_new = False
- if not metadata_ref:
- is_new = True
+ try:
+ metadata_ref = self.get_metadata(user_id, tenant_id)
+ is_new = False
+ except exception.MetadataNotFound:
metadata_ref = {}
+ is_new = True
roles = set(metadata_ref.get('roles', []))
if role_id not in roles:
msg = 'Cannot remove role that has not been granted, %s' % role_id
@@ -292,14 +344,15 @@ class Identity(sql.Base, identity.Driver):
roles.remove(role_id)
metadata_ref['roles'] = list(roles)
- if not is_new:
- self.update_metadata(user_id, tenant_id, metadata_ref)
- else:
+ if is_new:
self.create_metadata(user_id, tenant_id, metadata_ref)
+ else:
+ self.update_metadata(user_id, tenant_id, metadata_ref)
# CRUD
@handle_conflicts(type='user')
def create_user(self, user_id, user):
+ user['name'] = clean.user_name(user['name'])
user = _ensure_hashed_password(user)
session = self.get_session()
with session.begin():
@@ -310,9 +363,15 @@ class Identity(sql.Base, identity.Driver):
@handle_conflicts(type='user')
def update_user(self, user_id, user):
+ if 'name' in user:
+ user['name'] = clean.user_name(user['name'])
session = self.get_session()
+ if 'id' in user and user_id != user['id']:
+ raise exception.ValidationError('Cannot change user ID')
with session.begin():
user_ref = session.query(User).filter_by(id=user_id).first()
+ if user_ref is None:
+ raise exception.UserNotFound(user_id=user_id)
old_user_dict = user_ref.to_dict()
user = _ensure_hashed_password(user)
for k in user:
@@ -326,21 +385,17 @@ class Identity(sql.Base, identity.Driver):
def delete_user(self, user_id):
session = self.get_session()
- user_ref = session.query(User).filter_by(id=user_id).first()
- membership_refs = session.query(UserTenantMembership)\
- .filter_by(user_id=user_id)\
- .all()
-
with session.begin():
- if membership_refs:
- for membership_ref in membership_refs:
- session.delete(membership_ref)
-
- session.delete(user_ref)
- session.flush()
+ session.query(UserTenantMembership)\
+ .filter_by(user_id=user_id).delete(False)
+ session.query(Metadata)\
+ .filter_by(user_id=user_id).delete(False)
+ if not session.query(User).filter_by(id=user_id).delete(False):
+ raise exception.UserNotFound(user_id=user_id)
@handle_conflicts(type='tenant')
def create_tenant(self, tenant_id, tenant):
+ tenant['name'] = clean.tenant_name(tenant['name'])
session = self.get_session()
with session.begin():
tenant_ref = Tenant.from_dict(tenant)
@@ -350,9 +405,13 @@ class Identity(sql.Base, identity.Driver):
@handle_conflicts(type='tenant')
def update_tenant(self, tenant_id, tenant):
+ if 'name' in tenant:
+ tenant['name'] = clean.tenant_name(tenant['name'])
session = self.get_session()
with session.begin():
tenant_ref = session.query(Tenant).filter_by(id=tenant_id).first()
+ if tenant_ref is None:
+ raise exception.TenantNotFound(tenant_id=tenant_id)
old_tenant_dict = tenant_ref.to_dict()
for k in tenant:
old_tenant_dict = tenant
@@ -365,10 +424,13 @@ class Identity(sql.Base, identity.Driver):
def delete_tenant(self, tenant_id):
session = self.get_session()
- tenant_ref = session.query(Tenant).filter_by(id=tenant_id).first()
with session.begin():
- session.delete(tenant_ref)
- session.flush()
+ session.query(UserTenantMembership)\
+ .filter_by(tenant_id=tenant_id).delete(False)
+ session.query(Metadata)\
+ .filter_by(tenant_id=tenant_id).delete(False)
+ if not session.query(Tenant).filter_by(id=tenant_id).delete(False):
+ raise exception.TenantNotFound(tenant_id=tenant_id)
@handle_conflicts(type='metadata')
def create_metadata(self, user_id, tenant_id, metadata):
@@ -412,6 +474,8 @@ class Identity(sql.Base, identity.Driver):
session = self.get_session()
with session.begin():
role_ref = session.query(Role).filter_by(id=role_id).first()
+ if role_ref is None:
+ raise exception.RoleNotFound(role_id=role_id)
for k in role:
role_ref = role
session.flush()
@@ -419,6 +483,7 @@ class Identity(sql.Base, identity.Driver):
def delete_role(self, role_id):
session = self.get_session()
- role_ref = session.query(Role).filter_by(id=role_id).first()
with session.begin():
- session.delete(role_ref)
+ if not session.query(Role).filter_by(id=role_id).delete():
+ raise exception.RoleNotFound(role_id=role_id)
+ session.flush()
ec2_credential表比较
该表在源文件keystone/contrib/ec2/backends/sql.py文件中定义,从下面的输出来看该表没有变化。
view plaincopyprint?
[*]$ git diff 2012.1:keystone/contrib/ec2/backends/sql.py 2012.2:keystone/contrib/ec2/backends/sql.py > tables_diff
[*]$ cat tables_diff
[*]diff --git a/2012.1:keystone/contrib/ec2/backends/sql.py b/2012.2:keystone/contrib/ec2/backends/sql.py
[*]index d84c381..c3af464 100644
[*]--- a/2012.1:keystone/contrib/ec2/backends/sql.py
[*]+++ b/2012.2:keystone/contrib/ec2/backends/sql.py
[*]@@ -15,7 +15,6 @@
[*]# under the License.
[*]
[*]from keystone.common import sql
[*]-from keystone.common.sql import migration
[*]
[*]
[*]class Ec2Credential(sql.ModelBase, sql.DictBase):
$ git diff 2012.1:keystone/contrib/ec2/backends/sql.py2012.2:keystone/contrib/ec2/backends/sql.py > tables_diff
$ cat tables_diff
diff --git a/2012.1:keystone/contrib/ec2/backends/sql.py b/2012.2:keystone/contrib/ec2/backends/sql.py
index d84c381..c3af464 100644
--- a/2012.1:keystone/contrib/ec2/backends/sql.py
+++ b/2012.2:keystone/contrib/ec2/backends/sql.py
@@ -15,7 +15,6 @@
# under the License.
from keystone.common import sql
-from keystone.common.sql import migration
class Ec2Credential(sql.ModelBase, sql.DictBase):
token表的差异
该表在源文件keystone/token/backends/sql.py中定义,从下面的输出来看F版中该表添加了一个新的布尔列valid默认值为True。
view plaincopyprint?
[*]$ git diff 2012.1:keystone/token/backends/sql.py 2012.2:keystone/token/backends/sql.py > tables_diff
[*]$ cat tables_diff
[*]diff --git a/2012.1:keystone/token/backends/sql.py b/2012.2:keystone/token/backends/sql.py
[*]index 7a9a551..02e8947 100644
[*]--- a/2012.1:keystone/token/backends/sql.py
[*]+++ b/2012.2:keystone/token/backends/sql.py
[*]@@ -16,9 +16,12 @@
[*]
[*]import copy
[*]import datetime
[*]+import hashlib
[*]
[*]+from keystone.common import cms
[*]from keystone.common import sql
[*]from keystone import exception
[*]+from keystone.openstack.common import timeutils
[*]from keystone import token
[*]
[*]
[*]@@ -27,6 +30,7 @@ class TokenModel(sql.ModelBase, sql.DictBase):
[*]id = sql.Column(sql.String(64), primary_key=True)
[*]expires = sql.Column(sql.DateTime(), default=None)
[*]extra = sql.Column(sql.JsonBlob())
[*]+ valid = sql.Column(sql.Boolean(), default=True)
[*]
[*]@classmethod
[*]def from_dict(cls, token_dict):
[*]@@ -49,21 +53,31 @@ class Token(sql.Base, token.Driver):
[*]# Public interface
[*]def get_token(self, token_id):
[*]session = self.get_session()
[*]- token_ref = session.query(TokenModel).filter_by(id=token_id).first()
[*]+ token_ref = session.query(TokenModel)\
[*]+ .filter_by(id=self.token_to_key(token_id),
[*]+ valid=True).first()
[*]now = datetime.datetime.utcnow()
[*]if token_ref and (not token_ref.expires or now < token_ref.expires):
[*]return token_ref.to_dict()
[*]else:
[*]raise exception.TokenNotFound(token_id=token_id)
[*]
[*]+ def token_to_key(self, token_id):
[*]+ if len(token_id) > cms.UUID_TOKEN_LENGTH:
[*]+ hash = hashlib.md5()
[*]+ hash.update(token_id)
[*]+ return hash.hexdigest()
[*]+ else:
[*]+ return token_id
[*]+
[*]def create_token(self, token_id, data):
[*]data_copy = copy.deepcopy(data)
[*]if 'expires' not in data_copy:
[*]data_copy['expires'] = self._get_default_expire_time()
[*]
[*]token_ref = TokenModel.from_dict(data_copy)
[*]- token_ref.id = token_id
[*]-
[*]+ token_ref.id = self.token_to_key(token_id)
[*]+ token_ref.valid = True
[*]session = self.get_session()
[*]with session.begin():
[*]session.add(token_ref)
[*]@@ -72,12 +86,45 @@ class Token(sql.Base, token.Driver):
[*]
[*]def delete_token(self, token_id):
[*]session = self.get_session()
[*]- token_ref = session.query(TokenModel)\
[*]- .filter_by(id=token_id)\
[*]- .first()
[*]- if not token_ref:
[*]- raise exception.TokenNotFound(token_id=token_id)
[*]-
[*]+ key = self.token_to_key(token_id)
[*]with session.begin():
[*]- session.delete(token_ref)
[*]+ token_ref = session.query(TokenModel).filter_by(id=key,
[*]+ valid=True).first()
[*]+ if not token_ref:
[*]+ raise exception.TokenNotFound(token_id=token_id)
[*]+ token_ref.valid = False
[*]session.flush()
[*]+
[*]+ def list_tokens(self, user_id, tenant_id=None):
[*]+ session = self.get_session()
[*]+ tokens = []
[*]+ now = timeutils.utcnow()
[*]+ for token_ref in session.query(TokenModel)\
[*]+ .filter(TokenModel.expires > now)\
[*]+ .filter_by(valid=True):
[*]+ token_ref_dict = token_ref.to_dict()
[*]+ if 'user' not in token_ref_dict:
[*]+ continue
[*]+ if token_ref_dict['user'].get('id') != user_id:
[*]+ continue
[*]+ if tenant_id is not None:
[*]+ if 'tenant' not in token_ref_dict:
[*]+ continue
[*]+ if token_ref_dict['tenant'].get('id') != tenant_id:
[*]+ continue
[*]+ tokens.append(token_ref['id'])
[*]+ return tokens
[*]+
[*]+ def list_revoked_tokens(self):
[*]+ session = self.get_session()
[*]+ tokens = []
[*]+ now = timeutils.utcnow()
[*]+ for token_ref in session.query(TokenModel)\
[*]+ .filter(TokenModel.expires > now)\
[*]+ .filter_by(valid=False):
[*]+ record = {
[*]+ 'id': token_ref['id'],
[*]+ 'expires': token_ref['expires'],
[*]+ }
[*]+ tokens.append(record)
[*]+ return tokens
[*]
$ git diff 2012.1:keystone/token/backends/sql.py2012.2:keystone/token/backends/sql.py > tables_diff
$ cat tables_diff
diff --git a/2012.1:keystone/token/backends/sql.py b/2012.2:keystone/token/backends/sql.py
index 7a9a551..02e8947 100644
--- a/2012.1:keystone/token/backends/sql.py
+++ b/2012.2:keystone/token/backends/sql.py
@@ -16,9 +16,12 @@
import copy
import datetime
+import hashlib
+from keystone.common import cms
from keystone.common import sql
from keystone import exception
+from keystone.openstack.common import timeutils
from keystone import token
@@ -27,6 +30,7 @@ class TokenModel(sql.ModelBase, sql.DictBase):
id = sql.Column(sql.String(64), primary_key=True)
expires = sql.Column(sql.DateTime(), default=None)
extra = sql.Column(sql.JsonBlob())
+ valid = sql.Column(sql.Boolean(), default=True)
@classmethod
def from_dict(cls, token_dict):
@@ -49,21 +53,31 @@ class Token(sql.Base, token.Driver):
# Public interface
def get_token(self, token_id):
session = self.get_session()
- token_ref = session.query(TokenModel).filter_by(id=token_id).first()
+ token_ref = session.query(TokenModel)\
+ .filter_by(id=self.token_to_key(token_id),
+ valid=True).first()
now = datetime.datetime.utcnow()
if token_ref and (not token_ref.expires or now < token_ref.expires):
return token_ref.to_dict()
else:
raise exception.TokenNotFound(token_id=token_id)
+ def token_to_key(self, token_id):
+ if len(token_id) > cms.UUID_TOKEN_LENGTH:
+ hash = hashlib.md5()
+ hash.update(token_id)
+ return hash.hexdigest()
+ else:
+ return token_id
+
def create_token(self, token_id, data):
data_copy = copy.deepcopy(data)
if 'expires' not in data_copy:
data_copy['expires'] = self._get_default_expire_time()
token_ref = TokenModel.from_dict(data_copy)
- token_ref.id = token_id
-
+ token_ref.id = self.token_to_key(token_id)
+ token_ref.valid = True
session = self.get_session()
with session.begin():
session.add(token_ref)
@@ -72,12 +86,45 @@ class Token(sql.Base, token.Driver):
def delete_token(self, token_id):
session = self.get_session()
- token_ref = session.query(TokenModel)\
- .filter_by(id=token_id)\
- .first()
- if not token_ref:
- raise exception.TokenNotFound(token_id=token_id)
-
+ key = self.token_to_key(token_id)
with session.begin():
- session.delete(token_ref)
+ token_ref = session.query(TokenModel).filter_by(id=key,
+ valid=True).first()
+ if not token_ref:
+ raise exception.TokenNotFound(token_id=token_id)
+ token_ref.valid = False
session.flush()
+
+ def list_tokens(self, user_id, tenant_id=None):
+ session = self.get_session()
+ tokens = []
+ now = timeutils.utcnow()
+ for token_ref in session.query(TokenModel)\
+ .filter(TokenModel.expires > now)\
+ .filter_by(valid=True):
+ token_ref_dict = token_ref.to_dict()
+ if 'user' not in token_ref_dict:
+ continue
+ if token_ref_dict['user'].get('id') != user_id:
+ continue
+ if tenant_id is not None:
+ if 'tenant' not in token_ref_dict:
+ continue
+ if token_ref_dict['tenant'].get('id') != tenant_id:
+ continue
+ tokens.append(token_ref['id'])
+ return tokens
+
+ def list_revoked_tokens(self):
+ session = self.get_session()
+ tokens = []
+ now = timeutils.utcnow()
+ for token_ref in session.query(TokenModel)\
+ .filter(TokenModel.expires > now)\
+ .filter_by(valid=False):
+ record = {
+ 'id': token_ref['id'],
+ 'expires': token_ref['expires'],
+ }
+ tokens.append(record)
+ return tokens
migrate_version表的差异
该表似乎跟Keystone进行数据库升级有关,具体差异不清楚。
配置差异
从下面的示例配置文件的比较来看主要是增加了几个filter中间件
view plaincopyprint?
[*]$ git diff 2012.1:etc/keystone.conf 2012.2:etc/keystone.conf.sample > tables_diff
[*]$ cat tables_diff
[*]diff --git a/2012.1:etc/keystone.conf b/2012.2:etc/keystone.conf.sample
[*]index 3ecf641..1d48676 100644
[*]--- a/2012.1:etc/keystone.conf
[*]+++ b/2012.2:etc/keystone.conf.sample
[*]@@ -1,53 +1,128 @@
[*]
[*]-#bind_host = 0.0.0.0
[*]-public_port = 5000
[*]-admin_port = 35357
[*]-admin_token = ADMIN
[*]-compute_port = 8774
[*]-verbose = True
[*]-debug = True
[*]-#log_config = ./etc/logging.conf.sample
[*]-
[*]-# ================= Syslog Options ============================
[*]-# Send logs to syslog (/dev/log) instead of to file specified
[*]-# by `log-file`
[*]-use_syslog = False
[*]-
[*]-# Facility to use. If unset defaults to LOG_USER.
[*]-# syslog_log_facility = LOG_LOCAL0
[*]+# A "shared secret" between keystone and other openstack services
[*]+# admin_token = ADMIN
[*]+
[*]+# The IP address of the network interface to listen on
[*]+# bind_host = 0.0.0.0
[*]+
[*]+# The port number which the public service listens on
[*]+# public_port = 5000
[*]+
[*]+# The port number which the public admin listens on
[*]+# admin_port = 35357
[*]+
[*]+# The port number which the OpenStack Compute service listens on
[*]+# compute_port = 8774
[*]+
[*]+# === Logging Options ===
[*]+# Print debugging output
[*]+# verbose = False
[*]+
[*]+# Print more verbose output
[*]+# (includes plaintext request logging, potentially including passwords)
[*]+# debug = False
[*]+
[*]+# Name of log file to output to. If not set, logging will go to stdout.
[*]+# log_file = keystone.log
[*]+
[*]+# The directory to keep log files in (will be prepended to --logfile)
[*]+# log_dir = /var/log/keystone
[*]+
[*]+# Use syslog for logging.
[*]+# use_syslog = False
[*]+
[*]+# syslog facility to receive log lines
[*]+# syslog_log_facility = LOG_USER
[*]+
[*]+# If this option is specified, the logging configuration file specified is
[*]+# used and overrides any other logging options specified. Please see the
[*]+# Python logging module documentation for details on logging configuration
[*]+# files.
[*]+# log_config = logging.conf
[*]+
[*]+# A logging.Formatter log message format string which may use any of the
[*]+# available logging.LogRecord attributes.
[*]+# log_format = %(asctime)s %(levelname)8s [%(name)s] %(message)s
[*]+
[*]+# Format string for %(asctime)s in log records.
[*]+# log_date_format = %Y-%m-%d %H:%M:%S
[*]+
[*]+# onready allows you to send a notification when the process is ready to serve
[*]+# For example, to have it notify using systemd, one could set shell command:
[*]+# onready = systemd-notify --ready
[*]+# or a module with notify() method:
[*]+# onready = keystone.common.systemd
[*]
[*]
[*]-connection = sqlite:///keystone.db
[*]-idle_timeout = 200
[*]+# The SQLAlchemy connection string used to connect to the database
[*]+# connection = sqlite:///keystone.db
[*]
[*]-
[*]-#url = ldap://localhost
[*]-#tree_dn = dc=example,dc=com
[*]-#user_tree_dn = ou=Users,dc=example,dc=com
[*]-#role_tree_dn = ou=Roles,dc=example,dc=com
[*]-#tenant_tree_dn = ou=Groups,dc=example,dc=com
[*]-#user = dc=Manager,dc=example,dc=com
[*]-#password = freeipa4all
[*]-#suffix = cn=example,cn=com
[*]+# the timeout before idle sql connections are reaped
[*]+# idle_timeout = 200
[*]
[*]
[*]-driver = keystone.identity.backends.sql.Identity
[*]+# driver = keystone.identity.backends.sql.Identity
[*]
[*]
[*]-driver = keystone.catalog.backends.templated.TemplatedCatalog
[*]-template_file = ./etc/default_catalog.templates
[*]+# dynamic, sql-based backend (supports API/CLI-based management commands)
[*]+# driver = keystone.catalog.backends.sql.Catalog
[*]+
[*]+# static, file-based backend (does *NOT* support any management commands)
[*]+# driver = keystone.catalog.backends.templated.TemplatedCatalog
[*]+
[*]+# template_file = default_catalog.templates
[*]
[*]
[*]-driver = keystone.token.backends.kvs.Token
[*]+# driver = keystone.token.backends.kvs.Token
[*]
[*]# Amount of time a token should remain valid (in seconds)
[*]-expiration = 86400
[*]+# expiration = 86400
[*]
[*]
[*]-driver = keystone.policy.backends.rules.Policy
[*]+# driver = keystone.policy.backends.rules.Policy
[*]
[*]
[*]-driver = keystone.contrib.ec2.backends.kvs.Ec2
[*]+# driver = keystone.contrib.ec2.backends.kvs.Ec2
[*]+
[*]+
[*]+#enable = True
[*]+#certfile = /etc/keystone/ssl/certs/keystone.pem
[*]+#keyfile = /etc/keystone/ssl/private/keystonekey.pem
[*]+#ca_certs = /etc/keystone/ssl/certs/ca.pem
[*]+#cert_required = True
[*]+
[*]+
[*]+#token_format = UUID
[*]+#certfile = /etc/keystone/ssl/certs/signing_cert.pem
[*]+#keyfile = /etc/keystone/ssl/private/signing_key.pem
[*]+#ca_certs = /etc/keystone/ssl/certs/ca.pem
[*]+#key_size = 1024
[*]+#valid_days = 3650
[*]+#ca_password = None
[*]+#token_format = PKI
[*]+
[*]+
[*]+# url = ldap://localhost
[*]+# user = dc=Manager,dc=example,dc=com
[*]+# password = None
[*]+# suffix = cn=example,cn=com
[*]+# use_dumb_member = False
[*]+
[*]+# user_tree_dn = ou=Users,dc=example,dc=com
[*]+# user_objectclass = inetOrgPerson
[*]+# user_id_attribute = cn
[*]+# user_name_attribute = sn
[*]+
[*]+# tenant_tree_dn = ou=Groups,dc=example,dc=com
[*]+# tenant_objectclass = groupOfNames
[*]+# tenant_id_attribute = cn
[*]+# tenant_member_attribute = member
[*]+# tenant_name_attribute = ou
[*]+
[*]+# role_tree_dn = ou=Roles,dc=example,dc=com
[*]+# role_objectclass = organizationalRole
[*]+# role_id_attribute = cn
[*]+# role_member_attribute = roleOccupant
[*]
[*]
[*]paste.filter_factory = keystone.common.wsgi:Debug.factory
[*]@@ -64,12 +139,27 @@ paste.filter_factory = keystone.middleware:XmlBodyMiddleware.factory
[*]
[*]paste.filter_factory = keystone.middleware:JsonBodyMiddleware.factory
[*]
[*]+
[*]+paste.filter_factory = keystone.contrib.user_crud:CrudExtension.factory
[*]+
[*]
[*]paste.filter_factory = keystone.contrib.admin_crud:CrudExtension.factory
[*]
[*]
[*]paste.filter_factory = keystone.contrib.ec2:Ec2Extension.factory
[*]
[*]+
[*]+paste.filter_factory = keystone.contrib.s3:S3Extension.factory
[*]+
[*]+
[*]+paste.filter_factory = keystone.middleware:NormalizingFilter.factory
[*]+
[*]+
[*]+paste.filter_factory = keystone.contrib.stats:StatsMiddleware.factory
[*]+
[*]+
[*]+paste.filter_factory = keystone.contrib.stats:StatsExtension.factory
[*]+
[*]
[*]paste.app_factory = keystone.service:public_app_factory
[*]
[*]@@ -77,10 +167,10 @@ paste.app_factory = keystone.service:public_app_factory
[*]paste.app_factory = keystone.service:admin_app_factory
[*]
[*]
[*]-pipeline = token_auth admin_token_auth xml_body json_body debug ec2_extension public_service
[*]+pipeline = stats_monitoring url_normalize token_auth admin_token_auth xml_body json_body debug ec2_extension user_crud_extension public_service
[*]
[*]
[*]-pipeline = token_auth admin_token_auth xml_body json_body debug ec2_extension crud_extension admin_service
[*]+pipeline = stats_monitoring url_normalize token_auth admin_token_auth xml_body json_body debug stats_reporting ec2_extension s3_extension crud_extension admin_service
[*]
[*]
[*]paste.app_factory = keystone.service:public_version_app_factory
[*]@@ -89,10 +179,10 @@ paste.app_factory = keystone.service:public_version_app_factory
[*]paste.app_factory = keystone.service:admin_version_app_factory
[*]
[*]
[*]-pipeline = xml_body public_version_service
[*]+pipeline = stats_monitoring url_normalize xml_body public_version_service
[*]
[*]
[*]-pipeline = xml_body admin_version_service
[*]+pipeline = stats_monitoring url_normalize xml_body admin_version_service
[*]
[*]
[*]use = egg:Paste#urlmap
页:
[1]