summaryrefslogtreecommitdiff
path: root/keystone/tests/unit/test_sql_upgrade.py
diff options
context:
space:
mode:
Diffstat (limited to 'keystone/tests/unit/test_sql_upgrade.py')
-rw-r--r--keystone/tests/unit/test_sql_upgrade.py356
1 files changed, 26 insertions, 330 deletions
diff --git a/keystone/tests/unit/test_sql_upgrade.py b/keystone/tests/unit/test_sql_upgrade.py
index 78f644977..55440c955 100644
--- a/keystone/tests/unit/test_sql_upgrade.py
+++ b/keystone/tests/unit/test_sql_upgrade.py
@@ -39,28 +39,23 @@ For further information, see `oslo.db documentation
all data will be lost.
"""
-import glob
-import os
-
import fixtures
-from migrate.versioning import api as migrate_api
from migrate.versioning import script
-from oslo_db import exception as db_exception
+from oslo_db import options as db_options
from oslo_db.sqlalchemy import enginefacade
from oslo_db.sqlalchemy import test_fixtures as db_fixtures
from oslo_log import fixture as log_fixture
from oslo_log import log
-from oslotest import base as test_base
import sqlalchemy.exc
from keystone.cmd import cli
from keystone.common import sql
from keystone.common.sql import upgrades
-from keystone.credential.providers import fernet as credential_fernet
+import keystone.conf
from keystone.tests import unit
from keystone.tests.unit import ksfixtures
-from keystone.tests.unit.ksfixtures import database
+CONF = keystone.conf.CONF
# NOTE(morganfainberg): This should be updated when each DB migration collapse
# is done to mirror the expected structure of the DB in the format of
@@ -229,54 +224,10 @@ INITIAL_TABLE_STRUCTURE = {
}
-class Repository:
-
- def __init__(self, engine, repo_name):
- self.repo_name = repo_name
-
- self.repo_path = upgrades._get_migrate_repo_path(self.repo_name)
- self.min_version = upgrades.INITIAL_VERSION
- self.schema_ = migrate_api.ControlledSchema.create(
- engine, self.repo_path, self.min_version,
- )
- self.max_version = self.schema_.repository.version().version
-
- def upgrade(self, version=None, current_schema=None):
- version = version or self.max_version
- err = ''
- upgrade = True
- version = migrate_api._migrate_version(
- self.schema_, version, upgrade, err,
- )
- upgrades._validate_upgrade_order(
- self.repo_name, target_repo_version=version,
- )
- if not current_schema:
- current_schema = self.schema_
- changeset = current_schema.changeset(version)
- for ver, change in changeset:
- self.schema_.runchange(ver, change, changeset.step)
-
- if self.schema_.version != version:
- raise Exception(
- 'Actual version (%s) of %s does not equal expected '
- 'version (%s)' % (
- self.schema_.version, self.repo_name, version,
- ),
- )
-
- @property
- def version(self):
- with sql.session_for_read() as session:
- return upgrades._migrate_db_version(
- session.get_bind(), self.repo_path, self.min_version,
- )
-
-
class MigrateBase(
db_fixtures.OpportunisticDBTestMixin,
- test_base.BaseTestCase,
):
+ """Test complete orchestration between all database phases."""
def setUp(self):
super().setUp()
@@ -292,10 +243,7 @@ class MigrateBase(
# modules have the same name (001_awesome.py).
self.addCleanup(script.PythonScript.clear)
- # NOTE(dstanek): SQLAlchemy's migrate makes some assumptions in the
- # SQLite driver about the lack of foreign key enforcement.
- database.initialize_sql_session(self.engine.url,
- enforce_sqlite_fks=False)
+ db_options.set_defaults(CONF, connection=self.engine.url)
# Override keystone's context manager to be oslo.db's global context
# manager.
@@ -304,29 +252,13 @@ class MigrateBase(
sql.core, '_TESTING_USE_GLOBAL_CONTEXT_MANAGER', False)
self.addCleanup(sql.cleanup)
- self.repos = {
- upgrades.EXPAND_BRANCH: Repository(
- self.engine, upgrades.EXPAND_BRANCH,
- ),
- upgrades.DATA_MIGRATION_BRANCH: Repository(
- self.engine, upgrades.DATA_MIGRATION_BRANCH,
- ),
- upgrades.CONTRACT_BRANCH: Repository(
- self.engine, upgrades.CONTRACT_BRANCH,
- ),
- }
-
- def expand(self, *args, **kwargs):
+ def expand(self):
"""Expand database schema."""
- self.repos[upgrades.EXPAND_BRANCH].upgrade(*args, **kwargs)
+ upgrades.expand_schema(engine=self.engine)
- def migrate(self, *args, **kwargs):
- """Migrate data."""
- self.repos[upgrades.DATA_MIGRATION_BRANCH].upgrade(*args, **kwargs)
-
- def contract(self, *args, **kwargs):
+ def contract(self):
"""Contract database schema."""
- self.repos[upgrades.CONTRACT_BRANCH].upgrade(*args, **kwargs)
+ upgrades.contract_schema(engine=self.engine)
@property
def metadata(self):
@@ -334,7 +266,9 @@ class MigrateBase(
return sqlalchemy.MetaData(self.engine)
def load_table(self, name):
- table = sqlalchemy.Table(name, self.metadata, autoload=True)
+ table = sqlalchemy.Table(
+ name, self.metadata, autoload_with=self.engine,
+ )
return table
def assertTableDoesNotExist(self, table_name):
@@ -342,7 +276,9 @@ class MigrateBase(
# Switch to a different metadata otherwise you might still
# detect renamed or dropped tables
try:
- sqlalchemy.Table(table_name, self.metadata, autoload=True)
+ sqlalchemy.Table(
+ table_name, self.metadata, autoload_with=self.engine,
+ )
except sqlalchemy.exc.NoSuchTableError:
pass
else:
@@ -357,210 +293,8 @@ class MigrateBase(
self.assertCountEqual(expected_cols, actual_cols,
'%s table' % table_name)
-
-class ExpandSchemaUpgradeTests(MigrateBase):
-
- def test_start_version_db_init_version(self):
- self.assertEqual(
- self.repos[upgrades.EXPAND_BRANCH].min_version,
- self.repos[upgrades.EXPAND_BRANCH].version)
-
- def test_blank_db_to_start(self):
- self.assertTableDoesNotExist('user')
-
- def test_upgrade_add_initial_tables(self):
- self.expand(upgrades.INITIAL_VERSION + 1)
- self.check_initial_table_structure()
-
- def check_initial_table_structure(self):
- for table in INITIAL_TABLE_STRUCTURE:
- self.assertTableColumns(table, INITIAL_TABLE_STRUCTURE[table])
-
-
-class MySQLOpportunisticExpandSchemaUpgradeTestCase(
- ExpandSchemaUpgradeTests,
-):
- FIXTURE = db_fixtures.MySQLOpportunisticFixture
-
-
-class PostgreSQLOpportunisticExpandSchemaUpgradeTestCase(
- ExpandSchemaUpgradeTests,
-):
- FIXTURE = db_fixtures.PostgresqlOpportunisticFixture
-
-
-class DataMigrationUpgradeTests(MigrateBase):
-
- def setUp(self):
- # Make sure the expand repo is fully upgraded, since the data migration
- # phase is only run after this is upgraded
- super().setUp()
- self.expand()
-
- def test_start_version_db_init_version(self):
- self.assertEqual(
- self.repos[upgrades.DATA_MIGRATION_BRANCH].min_version,
- self.repos[upgrades.DATA_MIGRATION_BRANCH].version,
- )
-
-
-class MySQLOpportunisticDataMigrationUpgradeTestCase(
- DataMigrationUpgradeTests,
-):
- FIXTURE = db_fixtures.MySQLOpportunisticFixture
-
-
-class PostgreSQLOpportunisticDataMigrationUpgradeTestCase(
- DataMigrationUpgradeTests,
-):
- FIXTURE = db_fixtures.PostgresqlOpportunisticFixture
-
-
-class ContractSchemaUpgradeTests(MigrateBase, unit.TestCase):
-
- def setUp(self):
- # Make sure the expand and data migration repos are fully
- # upgraded, since the contract phase is only run after these are
- # upgraded.
- super().setUp()
- self.useFixture(
- ksfixtures.KeyRepository(
- self.config_fixture,
- 'credential',
- credential_fernet.MAX_ACTIVE_KEYS
- )
- )
- self.expand()
- self.migrate()
-
- def test_start_version_db_init_version(self):
- self.assertEqual(
- self.repos[upgrades.CONTRACT_BRANCH].min_version,
- self.repos[upgrades.CONTRACT_BRANCH].version,
- )
-
-
-class MySQLOpportunisticContractSchemaUpgradeTestCase(
- ContractSchemaUpgradeTests,
-):
- FIXTURE = db_fixtures.MySQLOpportunisticFixture
-
-
-class PostgreSQLOpportunisticContractSchemaUpgradeTestCase(
- ContractSchemaUpgradeTests,
-):
- FIXTURE = db_fixtures.PostgresqlOpportunisticFixture
-
-
-class VersionTests(MigrateBase):
-
- def test_migrate_repos_stay_in_lockstep(self):
- """Rolling upgrade repositories should always stay in lockstep.
-
- By maintaining a single "latest" version number in each of the three
- migration repositories (expand, data migrate, and contract), we can
- trivially prevent operators from "doing the wrong thing", such as
- running upgrades operations out of order (for example, you should not
- be able to run data migration 5 until schema expansion 5 has been run).
-
- For example, even if your rolling upgrade task *only* involves adding a
- new column with a reasonable default, and doesn't require any triggers,
- data migration, etc, you still need to create "empty" upgrade steps in
- the data migration and contract repositories with the same version
- number as the expansion.
-
- For more information, see "Database Migrations" here:
-
- https://docs.openstack.org/keystone/latest/contributor/database-migrations.html
-
- """
- # Transitive comparison: expand == data migration == contract
- self.assertEqual(
- self.repos[upgrades.EXPAND_BRANCH].max_version,
- self.repos[upgrades.DATA_MIGRATION_BRANCH].max_version,
- )
- self.assertEqual(
- self.repos[upgrades.DATA_MIGRATION_BRANCH].max_version,
- self.repos[upgrades.CONTRACT_BRANCH].max_version,
- )
-
- def test_migrate_repos_file_names_have_prefix(self):
- """Migration files should be unique to avoid caching errors.
-
- This test enforces migration files to include a prefix (expand,
- migrate, contract) in order to keep them unique. Here is the required
- format: [version]_[prefix]_[description]. For example:
- 001_expand_add_created_column.py
-
- """
- versions_path = '/versions'
-
- # test for expand prefix, e.g. 001_expand_new_fk_constraint.py
- repo_path = self.repos[upgrades.EXPAND_BRANCH].repo_path
- expand_list = glob.glob(repo_path + versions_path + '/*.py')
- self.assertRepoFileNamePrefix(expand_list, 'expand')
-
- # test for migrate prefix, e.g. 001_migrate_new_fk_constraint.py
- repo_path = self.repos[upgrades.DATA_MIGRATION_BRANCH].repo_path
- migrate_list = glob.glob(repo_path + versions_path + '/*.py')
- self.assertRepoFileNamePrefix(migrate_list, 'migrate')
-
- # test for contract prefix, e.g. 001_contract_new_fk_constraint.py
- repo_path = self.repos[upgrades.CONTRACT_BRANCH].repo_path
- contract_list = glob.glob(repo_path + versions_path + '/*.py')
- self.assertRepoFileNamePrefix(contract_list, 'contract')
-
- def assertRepoFileNamePrefix(self, repo_list, prefix):
- if len(repo_list) > 1:
- # grab the file name for the max version
- file_name = os.path.basename(sorted(repo_list)[-2])
- # pattern for the prefix standard, ignoring placeholder, init files
- pattern = (
- '^[0-9]{3,}_PREFIX_|^[0-9]{3,}_placeholder.py|^__init__.py')
- pattern = pattern.replace('PREFIX', prefix)
- msg = 'Missing required prefix %s in $file_name' % prefix
- self.assertRegex(file_name, pattern, msg)
-
-
-class MigrationValidation(MigrateBase, unit.TestCase):
- """Test validation of database between database phases."""
-
- def _set_db_sync_command_versions(self):
- self.expand(upgrades.INITIAL_VERSION + 1)
- self.migrate(upgrades.INITIAL_VERSION + 1)
- self.contract(upgrades.INITIAL_VERSION + 1)
- for version in (
- upgrades.get_db_version('expand'),
- upgrades.get_db_version('data_migration'),
- upgrades.get_db_version('contract'),
- ):
- self.assertEqual(upgrades.INITIAL_VERSION + 1, version)
-
- def test_running_db_sync_migrate_ahead_of_expand_fails(self):
- self._set_db_sync_command_versions()
- self.assertRaises(
- db_exception.DBMigrationError,
- self.migrate,
- upgrades.INITIAL_VERSION + 2,
- "You are attempting to upgrade migrate ahead of expand",
- )
-
- def test_running_db_sync_contract_ahead_of_migrate_fails(self):
- self._set_db_sync_command_versions()
- self.assertRaises(
- db_exception.DBMigrationError,
- self.contract,
- upgrades.INITIAL_VERSION + 2,
- "You are attempting to upgrade contract ahead of migrate",
- )
-
-
-class FullMigration(MigrateBase, unit.TestCase):
- """Test complete orchestration between all database phases."""
-
def test_db_sync_check(self):
checker = cli.DbSync()
- latest_version = self.repos[upgrades.EXPAND_BRANCH].max_version
# If the expand repository doesn't exist yet, then we need to make sure
# we advertise that `--expand` must be run first.
@@ -569,25 +303,9 @@ class FullMigration(MigrateBase, unit.TestCase):
self.assertIn("keystone-manage db_sync --expand", log_info.output)
self.assertEqual(status, 2)
- # Assert the correct message is printed when expand is the first step
- # that needs to run
- self.expand(upgrades.INITIAL_VERSION + 1)
- log_info = self.useFixture(fixtures.FakeLogger(level=log.INFO))
- status = checker.check_db_sync_status()
- self.assertIn("keystone-manage db_sync --expand", log_info.output)
- self.assertEqual(status, 2)
-
- # Assert the correct message is printed when expand is farther than
- # migrate
- self.expand(latest_version)
- log_info = self.useFixture(fixtures.FakeLogger(level=log.INFO))
- status = checker.check_db_sync_status()
- self.assertIn("keystone-manage db_sync --migrate", log_info.output)
- self.assertEqual(status, 3)
-
- # Assert the correct message is printed when migrate is farther than
+ # Assert the correct message is printed when migrate is ahead of
# contract
- self.migrate(latest_version)
+ self.expand()
log_info = self.useFixture(fixtures.FakeLogger(level=log.INFO))
status = checker.check_db_sync_status()
self.assertIn("keystone-manage db_sync --contract", log_info.output)
@@ -595,47 +313,25 @@ class FullMigration(MigrateBase, unit.TestCase):
# Assert the correct message gets printed when all commands are on
# the same version
- self.contract(latest_version)
+ self.contract()
log_info = self.useFixture(fixtures.FakeLogger(level=log.INFO))
status = checker.check_db_sync_status()
self.assertIn("All db_sync commands are upgraded", log_info.output)
self.assertEqual(status, 0)
- def test_out_of_sync_db_migration_fails(self):
- # We shouldn't allow for operators to accidentally run migration out of
- # order. This test ensures we fail if we attempt to upgrade the
- # contract repository ahead of the expand or migrate repositories.
- self.expand(upgrades.INITIAL_VERSION + 1)
- self.migrate(upgrades.INITIAL_VERSION + 1)
- self.assertRaises(
- db_exception.DBMigrationError,
- self.contract,
- upgrades.INITIAL_VERSION + 2,
- )
-
- def test_migration_079_expand_update_local_id_limit(self):
- self.expand(78)
- self.migrate(78)
- self.contract(78)
-
- id_mapping_table = sqlalchemy.Table('id_mapping',
- self.metadata, autoload=True)
- # assert local_id column is a string of 64 characters (before)
- self.assertEqual('VARCHAR(64)', str(id_mapping_table.c.local_id.type))
+ def test_upgrade_add_initial_tables(self):
+ self.expand()
+ for table in INITIAL_TABLE_STRUCTURE:
+ self.assertTableColumns(table, INITIAL_TABLE_STRUCTURE[table])
- self.expand(79)
- self.migrate(79)
- self.contract(79)
- id_mapping_table = sqlalchemy.Table('id_mapping',
- self.metadata, autoload=True)
- # assert local_id column is a string of 255 characters (after)
- self.assertEqual('VARCHAR(255)', str(id_mapping_table.c.local_id.type))
+class FullMigrationSQLite(MigrateBase, unit.TestCase):
+ pass
-class MySQLOpportunisticFullMigration(FullMigration):
+class FullMigrationMySQL(MigrateBase, unit.TestCase):
FIXTURE = db_fixtures.MySQLOpportunisticFixture
-class PostgreSQLOpportunisticFullMigration(FullMigration):
+class FullMigrationPostgreSQL(MigrateBase, unit.TestCase):
FIXTURE = db_fixtures.PostgresqlOpportunisticFixture