diff --git a/keystone/common/sql/migrate_repo/versions/091_migrate_data_to_local_user_and_password_tables.py b/keystone/common/sql/migrate_repo/versions/091_migrate_data_to_local_user_and_password_tables.py index 1f41fd8965..7e98b99b79 100644 --- a/keystone/common/sql/migrate_repo/versions/091_migrate_data_to_local_user_and_password_tables.py +++ b/keystone/common/sql/migrate_repo/versions/091_migrate_data_to_local_user_and_password_tables.py @@ -54,11 +54,29 @@ def upgrade(migrate_engine): if password_values: password_table.insert().values(password_values).execute() + # NOTE(gnuoy): the `domain_id` unique constraint is not guaranteed to + # be a fixed name, such as 'ixu_user_name_domain_id`, so we need to + # search for the correct constraint that only affects + # user_table.c.domain_id and drop that constraint. (Fix based on + # morganfainbergs fix in 088_domain_specific_roles.py) + to_drop = None + if migrate_engine.name == 'mysql': + for index in user_table.indexes: + if (index.unique and len(index.columns) == 2 and + 'domain_id' in index.columns and 'name' in index.columns): + to_drop = index + break + else: + for index in user_table.constraints: + if (len(index.columns) == 2 and 'domain_id' in index.columns and + 'name' in index.columns): + to_drop = index + break # remove domain_id and name unique constraint - if migrate_engine.name != 'sqlite': + if migrate_engine.name != 'sqlite' and to_drop is not None: migrate.UniqueConstraint(user_table.c.domain_id, user_table.c.name, - name='ixu_user_name_domain_id').drop() + name=to_drop.name).drop() # drop user columns user_table.c.domain_id.drop() diff --git a/keystone/common/sql/migrate_repo/versions/097_drop_user_name_domainid_constraint.py b/keystone/common/sql/migrate_repo/versions/097_drop_user_name_domainid_constraint.py new file mode 100644 index 0000000000..bb1aea8823 --- /dev/null +++ b/keystone/common/sql/migrate_repo/versions/097_drop_user_name_domainid_constraint.py @@ -0,0 +1,67 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import migrate +import sqlalchemy as sql + +_USER_TABLE_NAME = 'user' +_USER_NAME_COLUMN_NAME = 'name' +_USER_DOMAINID_COLUMN_NAME = 'domain_id' +_USER_PASSWORD_COLUMN_NAME = 'password' + + +def upgrade(migrate_engine): + meta = sql.MetaData() + meta.bind = migrate_engine + + user_table = sql.Table(_USER_TABLE_NAME, meta, autoload=True) + + # NOTE(gnuoy): the `domain_id` unique constraint is not guaranteed to + # be a fixed name, such as 'ixu_user_name_domain_id`, so we need to + # search for the correct constraint that only affects + # user_table.c.domain_id and drop that constraint. (Fix based on + # morganfainbergs fix in 088_domain_specific_roles.py) + # + # This is an idempotent change that reflects the fix to migration + # 91 if the user name & domain_id unique constraint was not named + # consistently and someone manually fixed the migrations / db + # without dropping the old constraint. + to_drop = None + if migrate_engine.name == 'mysql': + for index in user_table.indexes: + if (index.unique and len(index.columns) == 2 and + _USER_DOMAINID_COLUMN_NAME in index.columns and + _USER_NAME_COLUMN_NAME in index.columns): + to_drop = index + break + else: + for index in user_table.constraints: + if (len(index.columns) == 2 and + _USER_DOMAINID_COLUMN_NAME in index.columns and + _USER_NAME_COLUMN_NAME in index.columns): + to_drop = index + break + + # remove domain_id and name unique constraint + if to_drop is not None: + migrate.UniqueConstraint(user_table.c.domain_id, + user_table.c.name, + name=to_drop.name).drop() + + # If migration 91 was aborted due to Bug #1572341 then columns may not + # have been dropped. + if _USER_DOMAINID_COLUMN_NAME in user_table.c: + user_table.c.domain_id.drop() + if _USER_NAME_COLUMN_NAME in user_table.c: + user_table.c.name.drop() + if _USER_PASSWORD_COLUMN_NAME in user_table.c: + user_table.c.password.drop() diff --git a/keystone/tests/unit/test_sql_upgrade.py b/keystone/tests/unit/test_sql_upgrade.py index 5ca12f66fc..7ad34f6305 100644 --- a/keystone/tests/unit/test_sql_upgrade.py +++ b/keystone/tests/unit/test_sql_upgrade.py @@ -1079,6 +1079,31 @@ class SqlUpgradeTests(SqlMigrateBase): migrate.UniqueConstraint(role_table.c.name, name=constraint_name).drop() + def _add_unique_constraint_to_user_name_domainid( + self, + constraint_name='ixu_role_name'): + meta = sqlalchemy.MetaData() + meta.bind = self.engine + user_table = sqlalchemy.Table('user', meta, autoload=True) + migrate.UniqueConstraint(user_table.c.name, user_table.c.domain_id, + name=constraint_name).create() + + def _add_name_domain_id_columns_to_user(self): + meta = sqlalchemy.MetaData() + meta.bind = self.engine + user_table = sqlalchemy.Table('user', meta, autoload=True) + column_name = sqlalchemy.Column('name', sql.String(255)) + column_domain_id = sqlalchemy.Column('domain_id', sql.String(64)) + user_table.create_column(column_name) + user_table.create_column(column_domain_id) + + def _drop_unique_constraint_to_user_name_domainid( + self, + constraint_name='ixu_user_name_domain_id'): + user_table = sqlalchemy.Table('user', self.metadata, autoload=True) + migrate.UniqueConstraint(user_table.c.name, user_table.c.domain_id, + name=constraint_name).drop() + def test_migration_88_drops_unique_constraint(self): self.upgrade(87) if self.engine.name == 'mysql': @@ -1120,6 +1145,58 @@ class SqlUpgradeTests(SqlMigrateBase): self.assertFalse(self.does_constraint_exist('role', 'ixu_role_name')) + def test_migration_91_drops_unique_constraint(self): + self.upgrade(90) + if self.engine.name == 'mysql': + self.assertTrue(self.does_index_exist('user', + 'ixu_user_name_domain_id')) + else: + self.assertTrue(self.does_constraint_exist( + 'user', + 'ixu_user_name_domain_id')) + self.upgrade(91) + if self.engine.name == 'mysql': + self.assertFalse(self.does_index_exist( + 'user', + 'ixu_user_name_domain_id')) + else: + self.assertFalse(self.does_constraint_exist( + 'user', + 'ixu_user_name_domain_id')) + + def test_migration_91_inconsistent_constraint_name(self): + self.upgrade(90) + self._drop_unique_constraint_to_user_name_domainid() + + constraint_name = uuid.uuid4().hex + self._add_unique_constraint_to_user_name_domainid( + constraint_name=constraint_name) + + if self.engine.name == 'mysql': + self.assertTrue(self.does_index_exist('user', constraint_name)) + self.assertFalse(self.does_index_exist( + 'user', + 'ixu_user_name_domain_id')) + else: + self.assertTrue(self.does_constraint_exist('user', + constraint_name)) + self.assertFalse(self.does_constraint_exist( + 'user', + 'ixu_user_name_domain_id')) + + self.upgrade(91) + if self.engine.name == 'mysql': + self.assertFalse(self.does_index_exist('user', constraint_name)) + self.assertFalse(self.does_index_exist( + 'user', + 'ixu_user_name_domain_id')) + else: + self.assertFalse(self.does_constraint_exist('user', + constraint_name)) + self.assertFalse(self.does_constraint_exist( + 'user', + 'ixu_user_name_domain_id')) + def test_migration_96(self): self.upgrade(95) if self.engine.name == 'mysql': @@ -1152,6 +1229,72 @@ class SqlUpgradeTests(SqlMigrateBase): self.assertFalse(self.does_constraint_exist('role', 'ixu_role_name')) + def test_migration_97(self): + self.upgrade(96) + if self.engine.name == 'mysql': + self.assertFalse(self.does_index_exist( + 'user', + 'ixu_user_name_domain_id')) + else: + self.assertFalse(self.does_constraint_exist( + 'user', + 'ixu_user_name_domain_id')) + + self.upgrade(97) + if self.engine.name == 'mysql': + self.assertFalse(self.does_index_exist( + 'user', + 'ixu_user_name_domain_id')) + else: + self.assertFalse(self.does_constraint_exist( + 'user', + 'ixu_user_name_domain_id')) + + def test_migration_97_constraint_exists(self): + self.upgrade(96) + self._add_name_domain_id_columns_to_user() + self._add_unique_constraint_to_user_name_domainid( + constraint_name='ixu_user_name_domain_id') + + if self.engine.name == 'mysql': + self.assertTrue(self.does_index_exist( + 'user', + 'ixu_user_name_domain_id')) + else: + self.assertTrue(self.does_constraint_exist( + 'user', + 'ixu_user_name_domain_id')) + + self.upgrade(97) + if self.engine.name == 'mysql': + self.assertFalse(self.does_index_exist( + 'user', + 'ixu_user_name_domain_id')) + else: + self.assertFalse(self.does_constraint_exist( + 'user', + 'ixu_user_name_domain_id')) + + def test_migration_97_inconsistent_constraint_exists(self): + self.upgrade(96) + constraint_name = uuid.uuid4().hex + self._add_name_domain_id_columns_to_user() + self._add_unique_constraint_to_user_name_domainid( + constraint_name=constraint_name) + + if self.engine.name == 'mysql': + self.assertTrue(self.does_index_exist('user', constraint_name)) + else: + self.assertTrue(self.does_constraint_exist('user', + constraint_name)) + + self.upgrade(97) + if self.engine.name == 'mysql': + self.assertFalse(self.does_index_exist('user', constraint_name)) + else: + self.assertFalse(self.does_constraint_exist('user', + constraint_name)) + class VersionTests(SqlMigrateBase):