aboutsummaryrefslogtreecommitdiffstats
path: root/keystone-moon/keystone/common/sql/migration_helpers.py
diff options
context:
space:
mode:
Diffstat (limited to 'keystone-moon/keystone/common/sql/migration_helpers.py')
-rw-r--r--keystone-moon/keystone/common/sql/migration_helpers.py258
1 files changed, 258 insertions, 0 deletions
diff --git a/keystone-moon/keystone/common/sql/migration_helpers.py b/keystone-moon/keystone/common/sql/migration_helpers.py
new file mode 100644
index 00000000..86932995
--- /dev/null
+++ b/keystone-moon/keystone/common/sql/migration_helpers.py
@@ -0,0 +1,258 @@
+# Copyright 2013 OpenStack Foundation
+# Copyright 2013 Red Hat, Inc.
+# All Rights Reserved.
+#
+# 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 os
+import sys
+
+import migrate
+from migrate import exceptions
+from oslo_config import cfg
+from oslo_db.sqlalchemy import migration
+from oslo_serialization import jsonutils
+from oslo_utils import importutils
+import six
+import sqlalchemy
+
+from keystone.common import sql
+from keystone.common.sql import migrate_repo
+from keystone import contrib
+from keystone import exception
+from keystone.i18n import _
+
+
+CONF = cfg.CONF
+DEFAULT_EXTENSIONS = ['endpoint_filter',
+ 'endpoint_policy',
+ 'federation',
+ 'oauth1',
+ 'revoke',
+ ]
+
+
+def get_default_domain():
+ # Return the reference used for the default domain structure during
+ # sql migrations.
+ return {
+ 'id': CONF.identity.default_domain_id,
+ 'name': 'Default',
+ 'enabled': True,
+ 'extra': jsonutils.dumps({'description': 'Owns users and tenants '
+ '(i.e. projects) available '
+ 'on Identity API v2.'})}
+
+
+# Different RDBMSs use different schemes for naming the Foreign Key
+# Constraints. SQLAlchemy does not yet attempt to determine the name
+# for the constraint, and instead attempts to deduce it from the column.
+# This fails on MySQL.
+def get_constraints_names(table, column_name):
+ fkeys = [fk.name for fk in table.constraints
+ if (isinstance(fk, sqlalchemy.ForeignKeyConstraint) and
+ column_name in fk.columns)]
+ return fkeys
+
+
+# remove_constraints and add_constraints both accept a list of dictionaries
+# that contain:
+# {'table': a sqlalchemy table. The constraint is added to dropped from
+# this table.
+# 'fk_column': the name of a column on the above table, The constraint
+# is added to or dropped from this column
+# 'ref_column':a sqlalchemy column object. This is the reference column
+# for the constraint.
+def remove_constraints(constraints):
+ for constraint_def in constraints:
+ constraint_names = get_constraints_names(constraint_def['table'],
+ constraint_def['fk_column'])
+ for constraint_name in constraint_names:
+ migrate.ForeignKeyConstraint(
+ columns=[getattr(constraint_def['table'].c,
+ constraint_def['fk_column'])],
+ refcolumns=[constraint_def['ref_column']],
+ name=constraint_name).drop()
+
+
+def add_constraints(constraints):
+ for constraint_def in constraints:
+
+ if constraint_def['table'].kwargs.get('mysql_engine') == 'MyISAM':
+ # Don't try to create constraint when using MyISAM because it's
+ # not supported.
+ continue
+
+ ref_col = constraint_def['ref_column']
+ ref_engine = ref_col.table.kwargs.get('mysql_engine')
+ if ref_engine == 'MyISAM':
+ # Don't try to create constraint when using MyISAM because it's
+ # not supported.
+ continue
+
+ migrate.ForeignKeyConstraint(
+ columns=[getattr(constraint_def['table'].c,
+ constraint_def['fk_column'])],
+ refcolumns=[constraint_def['ref_column']]).create()
+
+
+def rename_tables_with_constraints(renames, constraints, engine):
+ """Renames tables with foreign key constraints.
+
+ Tables are renamed after first removing constraints. The constraints are
+ replaced after the rename is complete.
+
+ This works on databases that don't support renaming tables that have
+ constraints on them (DB2).
+
+ `renames` is a dict, mapping {'to_table_name': from_table, ...}
+ """
+
+ if engine.name != 'sqlite':
+ # Sqlite doesn't support constraints, so nothing to remove.
+ remove_constraints(constraints)
+
+ for to_table_name in renames:
+ from_table = renames[to_table_name]
+ from_table.rename(to_table_name)
+
+ if engine != 'sqlite':
+ add_constraints(constraints)
+
+
+def find_migrate_repo(package=None, repo_name='migrate_repo'):
+ package = package or sql
+ path = os.path.abspath(os.path.join(
+ os.path.dirname(package.__file__), repo_name))
+ if os.path.isdir(path):
+ return path
+ raise exception.MigrationNotProvided(package.__name__, path)
+
+
+def _sync_common_repo(version):
+ abs_path = find_migrate_repo()
+ init_version = migrate_repo.DB_INIT_VERSION
+ engine = sql.get_engine()
+ migration.db_sync(engine, abs_path, version=version,
+ init_version=init_version)
+
+
+def _fix_federation_tables(engine):
+ """Fix the identity_provider, federation_protocol and mapping tables
+ to be InnoDB and Charset UTF8.
+
+ This function is to work around bug #1426334. This has occurred because
+ the original migration did not specify InnoDB and charset utf8. Due
+ to the sanity_check, a deployer can get wedged here and require manual
+ database changes to fix.
+ """
+ # NOTE(marco-fargetta) This is a workaround to "fix" that tables only
+ # if we're under MySQL
+ if engine.name == 'mysql':
+ # * Disable any check for the foreign keys because they prevent the
+ # alter table to execute
+ engine.execute("SET foreign_key_checks = 0")
+ # * Make the tables using InnoDB engine
+ engine.execute("ALTER TABLE identity_provider Engine=InnoDB")
+ engine.execute("ALTER TABLE federation_protocol Engine=InnoDB")
+ engine.execute("ALTER TABLE mapping Engine=InnoDB")
+ # * Make the tables using utf8 encoding
+ engine.execute("ALTER TABLE identity_provider "
+ "CONVERT TO CHARACTER SET utf8")
+ engine.execute("ALTER TABLE federation_protocol "
+ "CONVERT TO CHARACTER SET utf8")
+ engine.execute("ALTER TABLE mapping CONVERT TO CHARACTER SET utf8")
+ # * Revert the foreign keys check back
+ engine.execute("SET foreign_key_checks = 1")
+
+
+def _sync_extension_repo(extension, version):
+ init_version = 0
+ engine = sql.get_engine()
+
+ try:
+ package_name = '.'.join((contrib.__name__, extension))
+ package = importutils.import_module(package_name)
+ except ImportError:
+ raise ImportError(_("%s extension does not exist.")
+ % package_name)
+ try:
+ abs_path = find_migrate_repo(package)
+ try:
+ migration.db_version_control(sql.get_engine(), abs_path)
+ # Register the repo with the version control API
+ # If it already knows about the repo, it will throw
+ # an exception that we can safely ignore
+ except exceptions.DatabaseAlreadyControlledError:
+ pass
+ except exception.MigrationNotProvided as e:
+ print(e)
+ sys.exit(1)
+ try:
+ migration.db_sync(engine, abs_path, version=version,
+ init_version=init_version)
+ except ValueError:
+ # NOTE(marco-fargetta): ValueError is raised from the sanity check (
+ # verifies that tables are utf8 under mysql). The federation_protocol,
+ # identity_provider and mapping tables were not initially built with
+ # InnoDB and utf8 as part of the table arguments when the migration
+ # was initially created. Bug #1426334 is a scenario where the deployer
+ # can get wedged, unable to upgrade or downgrade.
+ # This is a workaround to "fix" those tables if we're under MySQL and
+ # the version is before the 6 because before the tables were introduced
+ # before and patched when migration 5 was available
+ if engine.name == 'mysql' and \
+ int(six.text_type(get_db_version(extension))) < 6:
+ _fix_federation_tables(engine)
+ # The migration is applied again after the fix
+ migration.db_sync(engine, abs_path, version=version,
+ init_version=init_version)
+ else:
+ raise
+
+
+def sync_database_to_version(extension=None, version=None):
+ if not extension:
+ _sync_common_repo(version)
+ # If version is greater than 0, it is for the common
+ # repository only, and only that will be synchronized.
+ if version is None:
+ for default_extension in DEFAULT_EXTENSIONS:
+ _sync_extension_repo(default_extension, version)
+ else:
+ _sync_extension_repo(extension, version)
+
+
+def get_db_version(extension=None):
+ if not extension:
+ return migration.db_version(sql.get_engine(), find_migrate_repo(),
+ migrate_repo.DB_INIT_VERSION)
+
+ try:
+ package_name = '.'.join((contrib.__name__, extension))
+ package = importutils.import_module(package_name)
+ except ImportError:
+ raise ImportError(_("%s extension does not exist.")
+ % package_name)
+
+ return migration.db_version(
+ sql.get_engine(), find_migrate_repo(package), 0)
+
+
+def print_db_version(extension=None):
+ try:
+ db_version = get_db_version(extension=extension)
+ print(db_version)
+ except exception.MigrationNotProvided as e:
+ print(e)
+ sys.exit(1)