From d5ce8ea0ed6a3b9f748427d1b552fb88ad738545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CRichard?= Date: Tue, 3 Jan 2017 23:48:21 +0000 Subject: [PATCH] Add --check to keystone-manage db_sync command This patch adds a new command to the db_sync upgrade commands. --check will check the current state of the users upgrade repos and relay info back to the user based on what version each command is currently at and if the user has any outstanding db_sync commands left to run. It will also notify the user if the db_sync commands were not upgraded in order Closes-Bug: 1642212 Change-Id: I79d3640a780d624f14059fe311fafa0542c03357 --- doc/source/upgrading.rst | 24 ++++++++ keystone/cmd/cli.py | 56 ++++++++++++++++++- keystone/tests/unit/test_cli.py | 2 + keystone/tests/unit/test_sql_upgrade.py | 50 +++++++++++++++++ .../notes/bug-1642212-9964dfd3af0184bd.yaml | 4 ++ 5 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-1642212-9964dfd3af0184bd.yaml diff --git a/doc/source/upgrading.rst b/doc/source/upgrading.rst index f65749154d..a6a3d61fd2 100644 --- a/doc/source/upgrading.rst +++ b/doc/source/upgrading.rst @@ -223,3 +223,27 @@ authenticate requests normally. When this process completes, the database will no longer be able to support the previous release. + +Using db_sync check +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to check the current state of your rolling upgrades, you may run the +command ``keystone-manage db_sync --check``. This will inform you of any +outstanding actions you have left to take as well as any possible upgrades you +can make from your current version. Here are a list of possible return codes. + +* A return code of ``0`` means you are currently up to date with the latest + migration script version and all ``db_sync`` commands are complete. + +* A return code of ``1`` generally means something serious is wrong with your + database and operator intervention will be required. + +* A return code of ``2`` means that an upgrade from your current database + version is available and your first step is to run ``keystone-manage + db_sync --expand``. + +* A return code of ``3`` means that the expansion stage is complete, and the + next step is to run ``keystone-manage db_sync --migrate``. + +* A return code of ``4`` means that the expansion and data migration stages are + complete, and the next step is to run ``keystone-manage db_sync --contract``. diff --git a/keystone/cmd/cli.py b/keystone/cmd/cli.py index 9161d70b8e..bfd04e8281 100644 --- a/keystone/cmd/cli.py +++ b/keystone/cmd/cli.py @@ -19,6 +19,7 @@ import os import sys import uuid +import migrate from oslo_config import cfg from oslo_log import log from oslo_log import versionutils @@ -445,15 +446,66 @@ class DbSync(BaseApp): 'that are no longer required. This command ' 'should be run after all keystone nodes are ' 'running the new release.')) + + group.add_argument('--check', default=False, action='store_true', + help=('Check for outstanding database actions that ' + 'still need to be executed. This command can ' + 'be used to verify the condition of the ' + 'current database state.')) return parser + @classmethod + def check_db_sync_status(self): + status = 0 + expand_version = upgrades.get_db_version(repo='expand_repo') + migrate_version = upgrades.get_db_version( + repo='data_migration_repo') + contract_version = upgrades.get_db_version(repo='contract_repo') + + repo = migrate.versioning.repository.Repository( + upgrades.find_repo('expand_repo')) + migration_script_version = int(max(repo.versions.versions)) + + if (contract_version > migrate_version or migrate_version > + expand_version): + LOG.info(_LI('Your database is out of sync. For more information ' + 'refer to https://docs.openstack.org/developer/' + 'keystone/upgrading.html')) + status = 1 + elif migration_script_version > expand_version: + LOG.info(_LI('Your database is not up to date. Your first step is ' + 'to run `keystone-manage db_sync --expand`.')) + status = 2 + elif expand_version > migrate_version: + LOG.info(_LI('Expand version is ahead of migrate. Your next step ' + 'is to run `keystone-manage db_sync --migrate`.')) + status = 3 + elif migrate_version > contract_version: + LOG.info(_LI('Migrate version is ahead of contract. Your next ' + 'step is to run `keystone-manage db_sync --contract`.' + )) + status = 4 + elif (migration_script_version == expand_version == migrate_version == + contract_version): + LOG.info(_LI('All db_sync commands are upgraded to the same ' + 'version and up-to-date.')) + LOG.info(_LI('The latest installed migration script version is: ' + '%(script)d.\nCurrent repository versions:\nExpand: ' + '%(expand)d \nMigrate: %(migrate)d\nContract: ' + '%(contract)d') % {'script': migration_script_version, + 'expand': expand_version, + 'migrate': migrate_version, + 'contract': contract_version}) + return status + @staticmethod def main(): assert_not_extension(CONF.command.extension) - # It is possible to run expand and migrate at the same time, # expand needs to run first however. - if CONF.command.expand and CONF.command.migrate: + if CONF.command.check: + sys.exit(DbSync.check_db_sync_status()) + elif CONF.command.expand and CONF.command.migrate: upgrades.expand_schema() upgrades.migrate_data() elif CONF.command.expand: diff --git a/keystone/tests/unit/test_cli.py b/keystone/tests/unit/test_cli.py index 67b39176a8..8dcfa2db36 100644 --- a/keystone/tests/unit/test_cli.py +++ b/keystone/tests/unit/test_cli.py @@ -623,6 +623,7 @@ class CliDBSyncTestCase(unit.BaseTestCase): class FakeConfCommand(object): def __init__(self, parent): self.extension = False + self.check = parent.command_check self.expand = parent.command_expand self.migrate = parent.command_migrate self.contract = parent.command_contract @@ -636,6 +637,7 @@ class CliDBSyncTestCase(unit.BaseTestCase): upgrades.expand_schema = mock.Mock() upgrades.migrate_data = mock.Mock() upgrades.contract_schema = mock.Mock() + self.command_check = False self.command_expand = False self.command_migrate = False self.command_contract = False diff --git a/keystone/tests/unit/test_sql_upgrade.py b/keystone/tests/unit/test_sql_upgrade.py index 5537289be4..1433560b1c 100644 --- a/keystone/tests/unit/test_sql_upgrade.py +++ b/keystone/tests/unit/test_sql_upgrade.py @@ -41,16 +41,19 @@ WARNING:: import json import uuid +import fixtures import migrate from migrate.versioning import repository from migrate.versioning import script import mock from oslo_db import exception as db_exception from oslo_db.sqlalchemy import test_base +from oslo_log import log from sqlalchemy.engine import reflection import sqlalchemy.exc from testtools import matchers +from keystone.cmd import cli from keystone.common import sql from keystone.common.sql import upgrades import keystone.conf @@ -1671,6 +1674,53 @@ class FullMigration(SqlMigrateBase, unit.TestCase): self.assertTrue(1, expand) self.assertTrue(1, migrate) + def test_db_sync_check(self): + checker = cli.DbSync() + latest_version = self.repos[EXPAND_REPO].max_version + + # Assert the correct message is printed when expand is the first step + # that needs to run + self.expand(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 + # contract + self.migrate(latest_version) + 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) + self.assertEqual(status, 4) + + # Assert the correct message gets printed when all commands are on + # the same version + self.contract(latest_version) + 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_db_sync_check_out_of_sync(self): + checker = cli.DbSync() + # Assert we alert operator upgrades are out of sync + self.expand(3) + self.migrate(3) + self.contract(4) + log_info = self.useFixture(fixtures.FakeLogger(level=log.INFO)) + status = checker.check_db_sync_status() + self.assertIn("Your database is out of sync", log_info.output) + self.assertEqual(status, 1) + def test_migration_002_password_created_at_not_nullable(self): # upgrade each repository to 001 self.expand(1) diff --git a/releasenotes/notes/bug-1642212-9964dfd3af0184bd.yaml b/releasenotes/notes/bug-1642212-9964dfd3af0184bd.yaml new file mode 100644 index 0000000000..10f0c79cef --- /dev/null +++ b/releasenotes/notes/bug-1642212-9964dfd3af0184bd.yaml @@ -0,0 +1,4 @@ +--- +features: + - Added an option ``--check`` to ``keystone-manage db_sync``, the option will + allow a user to check the status of rolling upgrades in the database.