OpenStack版本比较之Keystone

时间:2023-02-03 22:41:07

目的

本文主要比较OpenStack中Essex与Folsom版本的Keystone在依赖包、数据库结构、配置方面的差异,为Keystone从Essex向Folsom升级做些前期准备工作。这些比较大部分是在源代码库上通过git命令进行的,首先要clone一个keystone的本地库,命令如下:

[plain] view plaincopy
  1. git clone git://github.com/openstack/keystone.git  
  2. cd keystone  

依赖包的差异

tools/pip-requires文件包含keystone的依赖库文件信息,通过以下命令比较发现Folsom版只是添加了一个依赖包iso8601版本须>=0.1.4,该包同时也是nova E版和F版的依赖包,因此依赖包的差异可以忽略,升级Keystone可以不安装其它依赖包。

[plain] view plaincopy
  1. [ugyn@localhost keystone]$ git diff 2012.1:tools/pip-requires 2012.2:tools/pip-requires  
  2. diff --git a/2012.1:tools/pip-requires b/2012.2:tools/pip-requires  
  3. index 0e13534..ec5562d 100644  
  4. --- a/2012.1:tools/pip-requires  
  5. +++ b/2012.2:tools/pip-requires  
  6. @@ -1,4 +1,4 @@  
  7. -# keystonelight dependencies  
  8. +# keystone dependencies  
  9.  pam==0.1.4  
  10.  WebOb==1.0.8  
  11.  eventlet  
  12. @@ -10,3 +10,4 @@ sqlalchemy  
  13.  sqlalchemy-migrate  
  14.  passlib  
  15.  lxml  
  16. +iso8601>=0.1.4  

数据库结构的差异

与数据库相关的源文件

要比较数据库结构的差异首先要找到keystone中与数据库相关的源文件,可通过以下命令实现:

[plain] view plaincopy
  1. [ugyn@localhost keystone]$ git grep "sql.ModelBase"  
  2. keystone/catalog/backends/sql.py:class Service(sql.ModelBase, sql.DictBase):  
  3. keystone/catalog/backends/sql.py:class Endpoint(sql.ModelBase, sql.DictBase):  
  4. keystone/common/sql/migrate_repo/versions/001_add_initial_tables.py:    sql.ModelBase.metadata.create_all(migrate_engine)  
  5. keystone/contrib/ec2/backends/sql.py:class Ec2Credential(sql.ModelBase, sql.DictBase):  
  6. keystone/identity/backends/sql.py:class User(sql.ModelBase, sql.DictBase):  
  7. keystone/identity/backends/sql.py:class Tenant(sql.ModelBase, sql.DictBase):  
  8. keystone/identity/backends/sql.py:class Role(sql.ModelBase, sql.DictBase):  
  9. keystone/identity/backends/sql.py:class Metadata(sql.ModelBase, sql.DictBase):  
  10. keystone/identity/backends/sql.py:class UserTenantMembership(sql.ModelBase, sql.DictBase):  
  11. keystone/token/backends/sql.py:class TokenModel(sql.ModelBase, sql.DictBase):  
  12. tests/test_backend_sql.py:        sql.ModelBase.metadata.bind = engine  
  13. tests/test_backend_sql.py:        sql.ModelBase.metadata.create_all(engine)  
  14.       

service及endpoint表比较

这两个表的定义在源文件keystone/catalog/backends/sql.py中,分别为Service及Endpoint类,从下面的结果可以看出这两个类完全没变,只是在类Catalog中对它们的操作做了些修改,因此在数据库方面这两个表应该是没在差异的。

[plain] view plaincopy
  1. [ugyn@localhost keystone]$ git diff 2012.1:keystone/catalog/backends/sql.py 2012.2:keystone/catalog/backends/sql.py > tables_diff  
  2. [ugyn@localhost keystone]$ cat tables_diff  
  3. diff --git a/2012.1:keystone/catalog/backends/sql.py b/2012.2:keystone/catalog/backends/sql.py  
  4. index 3c553e0..bd0f687 100644  
  5. --- a/2012.1:keystone/catalog/backends/sql.py  
  6. +++ b/2012.2:keystone/catalog/backends/sql.py  
  7. @@ -15,14 +15,11 @@  
  8.  # License for the specific language governing permissions and limitations  
  9.  # under the License.  
  10.   
  11. -import sqlalchemy.exc  
  12. -import webob.exc  
  13. -  
  14.  from keystone import catalog  
  15. -from keystone import config  
  16. -from keystone import exception  
  17.  from keystone.common import sql  
  18.  from keystone.common.sql import migration  
  19. +from keystone import config  
  20. +from keystone import exception  
  21.   
  22.   
  23.  CONF = config.CONF  
  24. @@ -96,11 +93,9 @@ class Catalog(sql.Base, catalog.Driver):  
  25.   
  26.      def delete_service(self, service_id):  
  27.          session = self.get_session()  
  28. -        service_ref = session.query(Service).filter_by(id=service_id).first()  
  29. -        if not service_ref:  
  30. -            raise exception.ServiceNotFound(service_id=service_id)  
  31.          with session.begin():  
  32. -            session.delete(service_ref)  
  33. +            if not session.query(Service).filter_by(id=service_id).delete():  
  34. +                raise exception.ServiceNotFound(service_id=service_id)  
  35.              session.flush()  
  36.   
  37.      def create_service(self, service_id, service_ref):  
  38. @@ -114,6 +109,7 @@ class Catalog(sql.Base, catalog.Driver):  
  39.      # Endpoints  
  40.      def create_endpoint(self, endpoint_id, endpoint_ref):  
  41.          session = self.get_session()  
  42. +        self.get_service(endpoint_ref['service_id'])  
  43.          new_endpoint = Endpoint.from_dict(endpoint_ref)  
  44.          with session.begin():  
  45.              session.add(new_endpoint)  
  46. @@ -122,18 +118,17 @@ class Catalog(sql.Base, catalog.Driver):  
  47.   
  48.      def delete_endpoint(self, endpoint_id):  
  49.          session = self.get_session()  
  50. -        endpoint_ref = session.query(Endpoint)  
  51. -        endpoint_ref = endpoint_ref.filter_by(id=endpoint_id).first()  
  52. -        if not endpoint_ref:  
  53. -            raise exception.EndpointNotFound(endpoint_id=endpoint_id)  
  54.          with session.begin():  
  55. -            session.delete(endpoint_ref)  
  56. +            if not session.query(Endpoint).filter_by(id=endpoint_id).delete():  
  57. +                raise exception.EndpointNotFound(endpoint_id=endpoint_id)  
  58.              session.flush()  
  59.   
  60.      def get_endpoint(self, endpoint_id):  
  61.          session = self.get_session()  
  62.          endpoint_ref = session.query(Endpoint)  
  63.          endpoint_ref = endpoint_ref.filter_by(id=endpoint_id).first()  
  64. +        if not endpoint_ref:  
  65. +            raise exception.EndpointNotFound(endpoint_id=endpoint_id)  
  66.          return endpoint_ref.to_dict()  
  67.   
  68.      def list_endpoints(self):  
  69. @@ -163,6 +158,7 @@ class Catalog(sql.Base, catalog.Driver):  
  70.              internal_url = ep['internalurl'].replace('$(', '%(')  
  71.              public_url = ep['publicurl'].replace('$(', '%(')  
  72.              admin_url = ep['adminurl'].replace('$(', '%(')  
  73. +            catalog[region][srv_type]['id'] = ep['id']  
  74.              catalog[region][srv_type]['name'] = srv_name  
  75.              catalog[region][srv_type]['publicURL'] = public_url % d  
  76.              catalog[region][srv_type]['adminURL'] = admin_url % d  
  77.       

user、tenant、role、metadata、user_tenant_membership表的比较

这几个表的定义均在源文件keystone/identity/backends/sql.py的相关类中。从下面的输出来看,在F版中的主要变化是给表user、tenant、role的name列均添加了not null属性,另外在Identity类中对这些表的操作做了不少修改,但应该不会导致相关表的结构变化。

[plain] view plaincopy
  1. [ugyn@localhost keystone]$ git diff 2012.1:keystone/identity/backends/sql.py  2012.2:keystone/identity/backends/sql.py  > tables_diff  
  2. [ugyn@localhost keystone]$ cat tables_diff  
  3. diff --git a/2012.1:keystone/identity/backends/sql.py b/2012.2:keystone/identity/backends/sql.py  
  4. index e4281a8..a3c8d1f 100644  
  5. --- a/2012.1:keystone/identity/backends/sql.py  
  6. +++ b/2012.2:keystone/identity/backends/sql.py  
  7. @@ -17,11 +17,12 @@  
  8.  import copy  
  9.  import functools  
  10.   
  11. -from keystone import identity  
  12. -from keystone import exception  
  13. +from keystone import clean  
  14.  from keystone.common import sql  
  15. -from keystone.common import utils  
  16.  from keystone.common.sql import migration  
  17. +from keystone.common import utils  
  18. +from keystone import exception  
  19. +from keystone import identity  
  20.   
  21.   
  22.  def _filter_user(user_ref):  
  23. @@ -45,7 +46,7 @@ def handle_conflicts(type='object'):  
  24.              try:  
  25.                  return method(*args, **kwargs)  
  26.              except sql.IntegrityError as e:  
  27. -                raise exception.Conflict(type=type, details=str(e))  
  28. +                raise exception.Conflict(type=type, details=e.message)  
  29.          return wrapper  
  30.      return decorator  
  31.   
  32. @@ -53,7 +54,7 @@ def handle_conflicts(type='object'):  
  33.  class User(sql.ModelBase, sql.DictBase):  
  34.      __tablename__ = 'user'  
  35.      id = sql.Column(sql.String(64), primary_key=True)  
  36. -    name = sql.Column(sql.String(64), unique=True)  
  37. +    name = sql.Column(sql.String(64), unique=True, nullable=False)  
  38.      #password = sql.Column(sql.String(64))  
  39.      extra = sql.Column(sql.JsonBlob())  
  40.   
  41. @@ -79,7 +80,7 @@ class User(sql.ModelBase, sql.DictBase):  
  42.  class Tenant(sql.ModelBase, sql.DictBase):  
  43.      __tablename__ = 'tenant'  
  44.      id = sql.Column(sql.String(64), primary_key=True)  
  45. -    name = sql.Column(sql.String(64), unique=True)  
  46. +    name = sql.Column(sql.String(64), unique=True, nullable=False)  
  47.      extra = sql.Column(sql.JsonBlob())  
  48.   
  49.      @classmethod  
  50. @@ -104,7 +105,7 @@ class Tenant(sql.ModelBase, sql.DictBase):  
  51.  class Role(sql.ModelBase, sql.DictBase):  
  52.      __tablename__ = 'role'  
  53.      id = sql.Column(sql.String(64), primary_key=True)  
  54. -    name = sql.Column(sql.String(64), unique=True)  
  55. +    name = sql.Column(sql.String(64), unique=True, nullable=False)  
  56.   
  57.   
  58.  class Metadata(sql.ModelBase, sql.DictBase):  
  59. @@ -134,6 +135,20 @@ class Identity(sql.Base, identity.Driver):  
  60.      def db_sync(self):  
  61.          migration.db_sync()  
  62.   
  63. +    def _check_password(self, password, user_ref):  
  64. +        """Check the specified password against the data store.  
  65. +  
  66. +        This is modeled on ldap/core.py.  The idea is to make it easier to  
  67. +        subclass Identity so that you can still use it to store all the data,  
  68. +        but use some other means to check the password.  
  69. +        Note that we'll pass in the entire user_ref in case the subclass  
  70. +        needs things like user_ref.get('name')  
  71. +        For further justification, please see the follow up suggestion at  
  72. +        https://blueprints.launchpad.net/keystone/+spec/sql-identiy-pam  
  73. +  
  74. +        """  
  75. +        return utils.check_password(password, user_ref.get('password'))  
  76. +  
  77.      # Identity interface  
  78.      def authenticate(self, user_id=None, tenant_id=None, password=None):  
  79.          """Authenticate based on a user, tenant and password.  
  80. @@ -142,56 +157,69 @@ class Identity(sql.Base, identity.Driver):  
  81.          in the list of tenants on the user.  
  82.   
  83.          """  
  84. -        user_ref = self._get_user(user_id)  
  85. -        if (not user_ref  
  86. -            or not utils.check_password(password, user_ref.get('password'))):  
  87. +        user_ref = None  
  88. +        tenant_ref = None  
  89. +        metadata_ref = {}  
  90. +  
  91. +        try:  
  92. +            user_ref = self._get_user(user_id)  
  93. +        except exception.UserNotFound:  
  94.              raise AssertionError('Invalid user / password')  
  95.   
  96. -        tenants = self.get_tenants_for_user(user_id)  
  97. -        if tenant_id and tenant_id not in tenants:  
  98. -            raise AssertionError('Invalid tenant')  
  99. +        if not utils.check_password(password, user_ref.get('password')):  
  100. +            raise AssertionError('Invalid user / password')  
  101. +  
  102. +        if tenant_id is not None:  
  103. +            if tenant_id not in self.get_tenants_for_user(user_id):  
  104. +                raise AssertionError('Invalid tenant')  
  105. +  
  106. +            try:  
  107. +                tenant_ref = self.get_tenant(tenant_id)  
  108. +                metadata_ref = self.get_metadata(user_id, tenant_id)  
  109. +            except exception.TenantNotFound:  
  110. +                tenant_ref = None  
  111. +                metadata_ref = {}  
  112. +            except exception.MetadataNotFound:  
  113. +                metadata_ref = {}  
  114.   
  115. -        tenant_ref = self.get_tenant(tenant_id)  
  116. -        if tenant_ref:  
  117. -            metadata_ref = self.get_metadata(user_id, tenant_id)  
  118. -        else:  
  119. -            metadata_ref = {}  
  120.          return (_filter_user(user_ref), tenant_ref, metadata_ref)  
  121.   
  122.      def get_tenant(self, tenant_id):  
  123.          session = self.get_session()  
  124.          tenant_ref = session.query(Tenant).filter_by(id=tenant_id).first()  
  125. -        if not tenant_ref:  
  126. -            return  
  127. +        if tenant_ref is None:  
  128. +            raise exception.TenantNotFound(tenant_id=tenant_id)  
  129.          return tenant_ref.to_dict()  
  130.   
  131.      def get_tenant_by_name(self, tenant_name):  
  132.          session = self.get_session()  
  133.          tenant_ref = session.query(Tenant).filter_by(name=tenant_name).first()  
  134.          if not tenant_ref:  
  135. -            return  
  136. +            raise exception.TenantNotFound(tenant_id=tenant_name)  
  137.          return tenant_ref.to_dict()  
  138.   
  139.      def get_tenant_users(self, tenant_id):  
  140.          session = self.get_session()  
  141. +        self.get_tenant(tenant_id)  
  142.          user_refs = session.query(User)\  
  143. -                .join(UserTenantMembership)\  
  144. -                .filter(UserTenantMembership.tenant_id == tenant_id)\  
  145. -                .all()  
  146. +            .join(UserTenantMembership)\  
  147. +            .filter(UserTenantMembership.tenant_id ==  
  148. +                    tenant_id)\  
  149. +            .all()  
  150.          return [_filter_user(user_ref.to_dict()) for user_ref in user_refs]  
  151.   
  152.      def _get_user(self, user_id):  
  153.          session = self.get_session()  
  154.          user_ref = session.query(User).filter_by(id=user_id).first()  
  155.          if not user_ref:  
  156. -            return  
  157. +            raise exception.UserNotFound(user_id=user_id)  
  158.          return user_ref.to_dict()  
  159.   
  160.      def _get_user_by_name(self, user_name):  
  161.          session = self.get_session()  
  162.          user_ref = session.query(User).filter_by(name=user_name).first()  
  163.          if not user_ref:  
  164. -            return  
  165. +            raise exception.UserNotFound(user_id=user_name)  
  166.          return user_ref.to_dict()  
  167.   
  168.      def get_user(self, user_id):  
  169. @@ -206,11 +234,16 @@ class Identity(sql.Base, identity.Driver):  
  170.                                .filter_by(user_id=user_id)\  
  171.                                .filter_by(tenant_id=tenant_id)\  
  172.                                .first()  
  173. -        return getattr(metadata_ref, 'data', {})  
  174. +        if metadata_ref is None:  
  175. +            raise exception.MetadataNotFound()  
  176. +        return metadata_ref.data  
  177.   
  178.      def get_role(self, role_id):  
  179.          session = self.get_session()  
  180. -        return session.query(Role).filter_by(id=role_id).first()  
  181. +        role_ref = session.query(Role).filter_by(id=role_id).first()  
  182. +        if role_ref is None:  
  183. +            raise exception.RoleNotFound(role_id=role_id)  
  184. +        return role_ref  
  185.   
  186.      def list_users(self):  
  187.          session = self.get_session()  
  188. @@ -225,6 +258,8 @@ class Identity(sql.Base, identity.Driver):  
  189.      # These should probably be part of the high-level API  
  190.      def add_user_to_tenant(self, tenant_id, user_id):  
  191.          session = self.get_session()  
  192. +        self.get_tenant(tenant_id)  
  193. +        self.get_user(user_id)  
  194.          q = session.query(UserTenantMembership)\  
  195.                     .filter_by(user_id=user_id)\  
  196.                     .filter_by(tenant_id=tenant_id)  
  197. @@ -239,10 +274,14 @@ class Identity(sql.Base, identity.Driver):  
  198.   
  199.      def remove_user_from_tenant(self, tenant_id, user_id):  
  200.          session = self.get_session()  
  201.   
  202. +        self.get_tenant(tenant_id)  
  203. +        self.get_user(user_id)  
  204.          membership_ref = session.query(UserTenantMembership)\  
  205.                                  .filter_by(user_id=user_id)\  
  206.                                  .filter_by(tenant_id=tenant_id)\  
  207.                                  .first()  
  208. +        if membership_ref is None:  
  209. +            raise exception.NotFound('User not found in tenant')  
  210.          with session.begin():  
  211.              session.delete(membership_ref)  
  212.              session.flush()  
  213. @@ -254,37 +293,50 @@ class Identity(sql.Base, identity.Driver):  
  214.   
  215.      def get_tenants_for_user(self, user_id):  
  216.          session = self.get_session()  
  217. +        self.get_user(user_id)  
  218.          membership_refs = session.query(UserTenantMembership)\  
  219. -                          .filter_by(user_id=user_id)\  
  220. -                          .all()  
  221. +                                 .filter_by(user_id=user_id)\  
  222. +                                 .all()  
  223.          return [x.tenant_id for x in membership_refs]  
  224.   
  225.      def get_roles_for_user_and_tenant(self, user_id, tenant_id):  
  226. -        metadata_ref = self.get_metadata(user_id, tenant_id)  
  227. -        if not metadata_ref:  
  228. +        self.get_user(user_id)  
  229. +        self.get_tenant(tenant_id)  
  230. +        try:  
  231. +            metadata_ref = self.get_metadata(user_id, tenant_id)  
  232. +        except exception.MetadataNotFound:  
  233.              metadata_ref = {}  
  234.          return metadata_ref.get('roles', [])  
  235.   
  236.      def add_role_to_user_and_tenant(self, user_id, tenant_id, role_id):  
  237. -        metadata_ref = self.get_metadata(user_id, tenant_id)  
  238. -        is_new = False  
  239. -        if not metadata_ref:  
  240. -            is_new = True  
  241. +        self.get_user(user_id)  
  242. +        self.get_tenant(tenant_id)  
  243. +        self.get_role(role_id)  
  244. +        try:  
  245. +            metadata_ref = self.get_metadata(user_id, tenant_id)  
  246. +            is_new = False  
  247. +        except exception.MetadataNotFound:  
  248.              metadata_ref = {}  
  249. +            is_new = True  
  250.          roles = set(metadata_ref.get('roles', []))  
  251. +        if role_id in roles:  
  252. +            msg = ('User %s already has role %s in tenant %s'  
  253. +                   % (user_id, role_id, tenant_id))  
  254. +            raise exception.Conflict(type='role grant', details=msg)  
  255.          roles.add(role_id)  
  256.          metadata_ref['roles'] = list(roles)  
  257. -        if not is_new:  
  258. -            self.update_metadata(user_id, tenant_id, metadata_ref)  
  259. -        else:  
  260. +        if is_new:  
  261.              self.create_metadata(user_id, tenant_id, metadata_ref)  
  262. +        else:  
  263. +            self.update_metadata(user_id, tenant_id, metadata_ref)  
  264.   
  265.      def remove_role_from_user_and_tenant(self, user_id, tenant_id, role_id):  
  266. -        metadata_ref = self.get_metadata(user_id, tenant_id)  
  267. -        is_new = False  
  268. -        if not metadata_ref:  
  269. -            is_new = True  
  270. +        try:  
  271. +            metadata_ref = self.get_metadata(user_id, tenant_id)  
  272. +            is_new = False  
  273. +        except exception.MetadataNotFound:  
  274.              metadata_ref = {}  
  275. +            is_new = True  
  276.          roles = set(metadata_ref.get('roles', []))  
  277.          if role_id not in roles:  
  278.              msg = 'Cannot remove role that has not been granted, %s' % role_id  
  279. @@ -292,14 +344,15 @@ class Identity(sql.Base, identity.Driver):  
  280.   
  281.          roles.remove(role_id)  
  282.          metadata_ref['roles'] = list(roles)  
  283. -        if not is_new:  
  284. -            self.update_metadata(user_id, tenant_id, metadata_ref)  
  285. -        else:  
  286. +        if is_new:  
  287.              self.create_metadata(user_id, tenant_id, metadata_ref)  
  288. +        else:  
  289. +            self.update_metadata(user_id, tenant_id, metadata_ref)  
  290.   
  291.      # CRUD  
  292.      @handle_conflicts(type='user')  
  293.      def create_user(self, user_id, user):  
  294. +        user['name'] = clean.user_name(user['name'])  
  295.          user = _ensure_hashed_password(user)  
  296.          session = self.get_session()  
  297.          with session.begin():  
  298. @@ -310,9 +363,15 @@ class Identity(sql.Base, identity.Driver):  
  299.   
  300.      @handle_conflicts(type='user')  
  301.      def update_user(self, user_id, user):  
  302. +        if 'name' in user:  
  303. +            user['name'] = clean.user_name(user['name'])  
  304.          session = self.get_session()  
  305. +        if 'id' in user and user_id != user['id']:  
  306. +            raise exception.ValidationError('Cannot change user ID')  
  307.          with session.begin():  
  308.              user_ref = session.query(User).filter_by(id=user_id).first()  
  309. +            if user_ref is None:  
  310. +                raise exception.UserNotFound(user_id=user_id)  
  311.              old_user_dict = user_ref.to_dict()  
  312.              user = _ensure_hashed_password(user)  
  313.              for k in user:  
  314. @@ -326,21 +385,17 @@ class Identity(sql.Base, identity.Driver):  
  315.   
  316.      def delete_user(self, user_id):  
  317.          session = self.get_session()  
  318. -        user_ref = session.query(User).filter_by(id=user_id).first()  
  319. -        membership_refs = session.query(UserTenantMembership)\  
  320. -                          .filter_by(user_id=user_id)\  
  321. -                          .all()  
  322. -  
  323.          with session.begin():  
  324. -            if membership_refs:  
  325. -                for membership_ref in membership_refs:  
  326. -                    session.delete(membership_ref)  
  327. -  
  328. -            session.delete(user_ref)  
  329. -            session.flush()  
  330. +            session.query(UserTenantMembership)\  
  331. +                   .filter_by(user_id=user_id).delete(False)  
  332. +            session.query(Metadata)\  
  333. +                   .filter_by(user_id=user_id).delete(False)  
  334. +            if not session.query(User).filter_by(id=user_id).delete(False):  
  335. +                raise exception.UserNotFound(user_id=user_id)  
  336.   
  337.      @handle_conflicts(type='tenant')  
  338.      def create_tenant(self, tenant_id, tenant):  
  339. +        tenant['name'] = clean.tenant_name(tenant['name'])  
  340.          session = self.get_session()  
  341.          with session.begin():  
  342.              tenant_ref = Tenant.from_dict(tenant)  
  343. @@ -350,9 +405,13 @@ class Identity(sql.Base, identity.Driver):  
  344.   
  345.      @handle_conflicts(type='tenant')  
  346.      def update_tenant(self, tenant_id, tenant):  
  347. +        if 'name' in tenant:  
  348. +            tenant['name'] = clean.tenant_name(tenant['name'])  
  349.          session = self.get_session()  
  350.          with session.begin():  
  351.              tenant_ref = session.query(Tenant).filter_by(id=tenant_id).first()  
  352. +            if tenant_ref is None:  
  353. +                raise exception.TenantNotFound(tenant_id=tenant_id)  
  354.              old_tenant_dict = tenant_ref.to_dict()  
  355.              for k in tenant:  
  356.                  old_tenant_dict[k] = tenant[k]  
  357. @@ -365,10 +424,13 @@ class Identity(sql.Base, identity.Driver):  
  358.   
  359.      def delete_tenant(self, tenant_id):  
  360.          session = self.get_session()  
  361. -        tenant_ref = session.query(Tenant).filter_by(id=tenant_id).first()  
  362.          with session.begin():  
  363. -            session.delete(tenant_ref)  
  364. -            session.flush()  
  365. +            session.query(UserTenantMembership)\  
  366. +                   .filter_by(tenant_id=tenant_id).delete(False)  
  367. +            session.query(Metadata)\  
  368. +                   .filter_by(tenant_id=tenant_id).delete(False)  
  369. +            if not session.query(Tenant).filter_by(id=tenant_id).delete(False):  
  370. +                raise exception.TenantNotFound(tenant_id=tenant_id)  
  371.   
  372.      @handle_conflicts(type='metadata')  
  373.      def create_metadata(self, user_id, tenant_id, metadata):  
  374. @@ -412,6 +474,8 @@ class Identity(sql.Base, identity.Driver):  
  375.          session = self.get_session()  
  376.          with session.begin():  
  377.              role_ref = session.query(Role).filter_by(id=role_id).first()  
  378. +            if role_ref is None:  
  379. +                raise exception.RoleNotFound(role_id=role_id)  
  380.              for k in role:  
  381.                  role_ref[k] = role[k]  
  382.              session.flush()  
  383. @@ -419,6 +483,7 @@ class Identity(sql.Base, identity.Driver):  
  384.   
  385.      def delete_role(self, role_id):  
  386.          session = self.get_session()  
  387. -        role_ref = session.query(Role).filter_by(id=role_id).first()  
  388.          with session.begin():  
  389. -            session.delete(role_ref)  
  390. +            if not session.query(Role).filter_by(id=role_id).delete():  
  391. +                raise exception.RoleNotFound(role_id=role_id)  
  392. +            session.flush()  
  393.       

ec2_credential表比较

该表在源文件keystone/contrib/ec2/backends/sql.py文件中定义,从下面的输出来看该表没有变化。

[plain] view plaincopy
  1. [ugyn@localhost keystone]$ git diff 2012.1:keystone/contrib/ec2/backends/sql.py  2012.2:keystone/contrib/ec2/backends/sql.py   > tables_diff  
  2. [ugyn@localhost keystone]$ cat tables_diff  
  3. diff --git a/2012.1:keystone/contrib/ec2/backends/sql.py b/2012.2:keystone/contrib/ec2/backends/sql.py  
  4. index d84c381..c3af464 100644  
  5. --- a/2012.1:keystone/contrib/ec2/backends/sql.py  
  6. +++ b/2012.2:keystone/contrib/ec2/backends/sql.py  
  7. @@ -15,7 +15,6 @@  
  8.  # under the License.  
  9.   
  10.  from keystone.common import sql  
  11. -from keystone.common.sql import migration  
  12.   
  13.   
  14.  class Ec2Credential(sql.ModelBase, sql.DictBase):  

token表的差异

该表在源文件keystone/token/backends/sql.py中定义,从下面的输出来看F版中该表添加了一个新的布尔列valid默认值为True。

[plain] view plaincopy
  1. [ugyn@localhost keystone]$ git diff 2012.1:keystone/token/backends/sql.py  2012.2:keystone/token/backends/sql.py   > tables_diff  
  2. [ugyn@localhost keystone]$ cat tables_diff  
  3. diff --git a/2012.1:keystone/token/backends/sql.py b/2012.2:keystone/token/backends/sql.py  
  4. index 7a9a551..02e8947 100644  
  5. --- a/2012.1:keystone/token/backends/sql.py  
  6. +++ b/2012.2:keystone/token/backends/sql.py  
  7. @@ -16,9 +16,12 @@  
  8.   
  9.  import copy  
  10.  import datetime  
  11. +import hashlib  
  12.   
  13. +from keystone.common import cms  
  14.  from keystone.common import sql  
  15.  from keystone import exception  
  16. +from keystone.openstack.common import timeutils  
  17.  from keystone import token  
  18.   
  19.   
  20. @@ -27,6 +30,7 @@ class TokenModel(sql.ModelBase, sql.DictBase):  
  21.      id = sql.Column(sql.String(64), primary_key=True)  
  22.      expires = sql.Column(sql.DateTime(), default=None)  
  23.      extra = sql.Column(sql.JsonBlob())  
  24. +    valid = sql.Column(sql.Boolean(), default=True)  
  25.   
  26.      @classmethod  
  27.      def from_dict(cls, token_dict):  
  28. @@ -49,21 +53,31 @@ class Token(sql.Base, token.Driver):  
  29.      # Public interface  
  30.      def get_token(self, token_id):  
  31.          session = self.get_session()  
  32. -        token_ref = session.query(TokenModel).filter_by(id=token_id).first()  
  33. +        token_ref = session.query(TokenModel)\  
  34. +            .filter_by(id=self.token_to_key(token_id),  
  35. +                       valid=True).first()  
  36.          now = datetime.datetime.utcnow()  
  37.          if token_ref and (not token_ref.expires or now < token_ref.expires):  
  38.              return token_ref.to_dict()  
  39.          else:  
  40.              raise exception.TokenNotFound(token_id=token_id)  
  41.   
  42. +    def token_to_key(self, token_id):  
  43. +        if len(token_id) > cms.UUID_TOKEN_LENGTH:  
  44. +            hash = hashlib.md5()  
  45. +            hash.update(token_id)  
  46. +            return hash.hexdigest()  
  47. +        else:  
  48. +            return token_id  
  49. +  
  50.      def create_token(self, token_id, data):  
  51.          data_copy = copy.deepcopy(data)  
  52.          if 'expires' not in data_copy:  
  53.              data_copy['expires'] = self._get_default_expire_time()  
  54.   
  55.          token_ref = TokenModel.from_dict(data_copy)  
  56. -        token_ref.id = token_id  
  57. -  
  58. +        token_ref.id = self.token_to_key(token_id)  
  59. +        token_ref.valid = True  
  60.          session = self.get_session()  
  61.          with session.begin():  
  62.              session.add(token_ref)  
  63. @@ -72,12 +86,45 @@ class Token(sql.Base, token.Driver):  
  64.   
  65.      def delete_token(self, token_id):  
  66.          session = self.get_session()  
  67. -        token_ref = session.query(TokenModel)\  
  68. -                                .filter_by(id=token_id)\  
  69. -                                .first()  
  70. -        if not token_ref:  
  71. -            raise exception.TokenNotFound(token_id=token_id)  
  72. -  
  73. +        key = self.token_to_key(token_id)  
  74.          with session.begin():  
  75. -            session.delete(token_ref)  
  76. +            token_ref = session.query(TokenModel).filter_by(id=key,  
  77. +                                                            valid=True).first()  
  78. +            if not token_ref:  
  79. +                raise exception.TokenNotFound(token_id=token_id)  
  80. +            token_ref.valid = False  
  81.              session.flush()  
  82. +  
  83. +    def list_tokens(self, user_id, tenant_id=None):  
  84. +        session = self.get_session()  
  85. +        tokens = []  
  86. +        now = timeutils.utcnow()  
  87. +        for token_ref in session.query(TokenModel)\  
  88. +                                .filter(TokenModel.expires > now)\  
  89. +                                .filter_by(valid=True):  
  90. +            token_ref_dict = token_ref.to_dict()  
  91. +            if 'user' not in token_ref_dict:  
  92. +                continue  
  93. +            if token_ref_dict['user'].get('id') != user_id:  
  94. +                continue  
  95. +            if tenant_id is not None:  
  96. +                if 'tenant' not in token_ref_dict:  
  97. +                    continue  
  98. +                if token_ref_dict['tenant'].get('id') != tenant_id:  
  99. +                    continue  
  100. +            tokens.append(token_ref['id'])  
  101. +        return tokens  
  102. +  
  103. +    def list_revoked_tokens(self):  
  104. +        session = self.get_session()  
  105. +        tokens = []  
  106. +        now = timeutils.utcnow()  
  107. +        for token_ref in session.query(TokenModel)\  
  108. +                                .filter(TokenModel.expires > now)\  
  109. +                                .filter_by(valid=False):  
  110. +            record = {  
  111. +                'id': token_ref['id'],  
  112. +                'expires': token_ref['expires'],  
  113. +            }  
  114. +            tokens.append(record)  
  115. +        return tokens  
  116.       

migrate_version表的差异

该表似乎跟Keystone进行数据库升级有关,具体差异不清楚。

配置差异

从下面的示例配置文件的比较来看主要是增加了几个filter中间件

[plain] view plaincopy
  1. [ugyn@localhost keystone]$ git diff 2012.1:etc/keystone.conf 2012.2:etc/keystone.conf.sample    > tables_diff  
  2. [ugyn@localhost keystone]$ cat tables_diff  
  3. diff --git a/2012.1:etc/keystone.conf b/2012.2:etc/keystone.conf.sample  
  4. index 3ecf641..1d48676 100644  
  5. --- a/2012.1:etc/keystone.conf  
  6. +++ b/2012.2:etc/keystone.conf.sample  
  7. @@ -1,53 +1,128 @@  
  8.  [DEFAULT]  
  9. -#bind_host = 0.0.0.0  
  10. -public_port = 5000  
  11. -admin_port = 35357  
  12. -admin_token = ADMIN  
  13. -compute_port = 8774  
  14. -verbose = True  
  15. -debug = True  
  16. -#log_config = ./etc/logging.conf.sample  
  17. -  
  18. -# ================= Syslog Options ============================  
  19. -# Send logs to syslog (/dev/log) instead of to file specified  
  20. -# by `log-file`  
  21. -use_syslog = False  
  22. -  
  23. -# Facility to use. If unset defaults to LOG_USER.  
  24. -# syslog_log_facility = LOG_LOCAL0  
  25. +# A "shared secret" between keystone and other openstack services  
  26. +# admin_token = ADMIN  
  27. +  
  28. +# The IP address of the network interface to listen on  
  29. +# bind_host = 0.0.0.0  
  30. +  
  31. +# The port number which the public service listens on  
  32. +# public_port = 5000  
  33. +  
  34. +# The port number which the public admin listens on  
  35. +# admin_port = 35357  
  36. +  
  37. +# The port number which the OpenStack Compute service listens on  
  38. +# compute_port = 8774  
  39. +  
  40. +# === Logging Options ===  
  41. +# Print debugging output  
  42. +# verbose = False  
  43. +  
  44. +# Print more verbose output  
  45. +# (includes plaintext request logging, potentially including passwords)  
  46. +# debug = False  
  47. +  
  48. +# Name of log file to output to. If not set, logging will go to stdout.  
  49. +# log_file = keystone.log  
  50. +  
  51. +# The directory to keep log files in (will be prepended to --logfile)  
  52. +# log_dir = /var/log/keystone  
  53. +  
  54. +# Use syslog for logging.  
  55. +# use_syslog = False  
  56. +  
  57. +# syslog facility to receive log lines  
  58. +# syslog_log_facility = LOG_USER  
  59. +  
  60. +# If this option is specified, the logging configuration file specified is  
  61. +# used and overrides any other logging options specified. Please see the  
  62. +# Python logging module documentation for details on logging configuration  
  63. +# files.  
  64. +# log_config = logging.conf  
  65. +  
  66. +# A logging.Formatter log message format string which may use any of the  
  67. +# available logging.LogRecord attributes.  
  68. +# log_format = %(asctime)s %(levelname)8s [%(name)s] %(message)s  
  69. +  
  70. +# Format string for %(asctime)s in log records.  
  71. +# log_date_format = %Y-%m-%d %H:%M:%S  
  72. +  
  73. +# onready allows you to send a notification when the process is ready to serve  
  74. +# For example, to have it notify using systemd, one could set shell command:  
  75. +# onready = systemd-notify --ready  
  76. +# or a module with notify() method:  
  77. +# onready = keystone.common.systemd  
  78.   
  79.  [sql]  
  80. -connection = sqlite:///keystone.db  
  81. -idle_timeout = 200  
  82. +# The SQLAlchemy connection string used to connect to the database  
  83. +# connection = sqlite:///keystone.db  
  84.   
  85. -[ldap]  
  86. -#url = ldap://localhost  
  87. -#tree_dn = dc=example,dc=com  
  88. -#user_tree_dn = ou=Users,dc=example,dc=com  
  89. -#role_tree_dn = ou=Roles,dc=example,dc=com  
  90. -#tenant_tree_dn = ou=Groups,dc=example,dc=com  
  91. -#user = dc=Manager,dc=example,dc=com  
  92. -#password = freeipa4all  
  93. -#suffix = cn=example,cn=com  
  94. +# the timeout before idle sql connections are reaped  
  95. +# idle_timeout = 200  
  96.   
  97.  [identity]  
  98. -driver = keystone.identity.backends.sql.Identity  
  99. +# driver = keystone.identity.backends.sql.Identity  
  100.   
  101.  [catalog]  
  102. -driver = keystone.catalog.backends.templated.TemplatedCatalog  
  103. -template_file = ./etc/default_catalog.templates  
  104. +# dynamic, sql-based backend (supports API/CLI-based management commands)  
  105. +# driver = keystone.catalog.backends.sql.Catalog  
  106. +  
  107. +# static, file-based backend (does *NOT* support any management commands)  
  108. +# driver = keystone.catalog.backends.templated.TemplatedCatalog  
  109. +  
  110. +# template_file = default_catalog.templates  
  111.   
  112.  [token]  
  113. -driver = keystone.token.backends.kvs.Token  
  114. +# driver = keystone.token.backends.kvs.Token  
  115.   
  116.  # Amount of time a token should remain valid (in seconds)  
  117. -expiration = 86400  
  118. +# expiration = 86400  
  119.   
  120.  [policy]  
  121. -driver = keystone.policy.backends.rules.Policy  
  122. +# driver = keystone.policy.backends.rules.Policy  
  123.   
  124.  [ec2]  
  125. -driver = keystone.contrib.ec2.backends.kvs.Ec2  
  126. +# driver = keystone.contrib.ec2.backends.kvs.Ec2  
  127. +  
  128. +[ssl]  
  129. +#enable = True  
  130. +#certfile = /etc/keystone/ssl/certs/keystone.pem  
  131. +#keyfile = /etc/keystone/ssl/private/keystonekey.pem  
  132. +#ca_certs = /etc/keystone/ssl/certs/ca.pem  
  133. +#cert_required = True  
  134. +  
  135. +[signing]  
  136. +#token_format = UUID  
  137. +#certfile = /etc/keystone/ssl/certs/signing_cert.pem  
  138. +#keyfile = /etc/keystone/ssl/private/signing_key.pem  
  139. +#ca_certs = /etc/keystone/ssl/certs/ca.pem  
  140. +#key_size = 1024  
  141. +#valid_days = 3650  
  142. +#ca_password = None  
  143. +#token_format = PKI  
  144. +  
  145. +[ldap]  
  146. +# url = ldap://localhost  
  147. +# user = dc=Manager,dc=example,dc=com  
  148. +# password = None  
  149. +# suffix = cn=example,cn=com  
  150. +# use_dumb_member = False  
  151. +  
  152. +# user_tree_dn = ou=Users,dc=example,dc=com  
  153. +# user_objectclass = inetOrgPerson  
  154. +# user_id_attribute = cn  
  155. +# user_name_attribute = sn  
  156. +  
  157. +# tenant_tree_dn = ou=Groups,dc=example,dc=com  
  158. +# tenant_objectclass = groupOfNames  
  159. +# tenant_id_attribute = cn  
  160. +# tenant_member_attribute = member  
  161. +# tenant_name_attribute = ou  
  162. +  
  163. +# role_tree_dn = ou=Roles,dc=example,dc=com  
  164. +# role_objectclass = organizationalRole  
  165. +# role_id_attribute = cn  
  166. +# role_member_attribute = roleOccupant  
  167.   
  168.  [filter:debug]  
  169.  paste.filter_factory = keystone.common.wsgi:Debug.factory  
  170. @@ -64,12 +139,27 @@ paste.filter_factory = keystone.middleware:XmlBodyMiddleware.factory  
  171.  [filter:json_body]  
  172.  paste.filter_factory = keystone.middleware:JsonBodyMiddleware.factory  
  173.   
  174. +[filter:user_crud_extension]  
  175. +paste.filter_factory = keystone.contrib.user_crud:CrudExtension.factory  
  176. +  
  177.  [filter:crud_extension]  
  178.  paste.filter_factory = keystone.contrib.admin_crud:CrudExtension.factory  
  179.   
  180.  [filter:ec2_extension]  
  181.  paste.filter_factory = keystone.contrib.ec2:Ec2Extension.factory  
  182.   
  183. +[filter:s3_extension]  
  184. +paste.filter_factory = keystone.contrib.s3:S3Extension.factory  
  185. +  
  186. +[filter:url_normalize]  
  187. +paste.filter_factory = keystone.middleware:NormalizingFilter.factory  
  188. +  
  189. +[filter:stats_monitoring]  
  190. +paste.filter_factory = keystone.contrib.stats:StatsMiddleware.factory  
  191. +  
  192. +[filter:stats_reporting]  
  193. +paste.filter_factory = keystone.contrib.stats:StatsExtension.factory  
  194. +  
  195.  [app:public_service]  
  196.  paste.app_factory = keystone.service:public_app_factory  
  197.   
  198. @@ -77,10 +167,10 @@ paste.app_factory = keystone.service:public_app_factory  
  199.  paste.app_factory = keystone.service:admin_app_factory  
  200.   
  201.  [pipeline:public_api]  
  202. -pipeline = token_auth admin_token_auth xml_body json_body debug ec2_extension public_service  
  203. +pipeline = stats_monitoring url_normalize token_auth admin_token_auth xml_body json_body debug ec2_extension user_crud_extension public_service  
  204.   
  205.  [pipeline:admin_api]  
  206. -pipeline = token_auth admin_token_auth xml_body json_body debug ec2_extension crud_extension admin_service  
  207. +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  
  208.   
  209.  [app:public_version_service]  
  210.  paste.app_factory = keystone.service:public_version_app_factory  
  211. @@ -89,10 +179,10 @@ paste.app_factory = keystone.service:public_version_app_factory  
  212.  paste.app_factory = keystone.service:admin_version_app_factory  
  213.   
  214.  [pipeline:public_version_api]  
  215. -pipeline = xml_body public_version_service  
  216. +pipeline = stats_monitoring url_normalize xml_body public_version_service  
  217.   
  218.  [pipeline:admin_version_api]  
  219. -pipeline = xml_body admin_version_service  
  220. +pipeline = stats_monitoring url_normalize xml_body admin_version_service  
  221.   
  222.  [composite:main]  
  223.  use = egg:Paste#urlmap