Implementation of db check command

This patch adds a new "glance-manage db check" command
which will check the current state of the users upgrade
repos and relay info back to the user if the user has any
outstanding db upgrades left to run with appropriate exit code.

Co-Authored-By: Bhagyashri Shewale <bhagyashri.shewale@nttdata.com>

Implements: Ie1e2fec2361765ddf23da897abcf0e12e682612e
Change-Id: I1e0b02d615690f65a17b4ccfe4e4a72cc9e15ada
This commit is contained in:
Steve Lewis 2017-04-11 14:41:38 -07:00 committed by bhagyashris
parent 55fa35963a
commit 08d1c7f930
4 changed files with 175 additions and 5 deletions

View File

@ -89,6 +89,39 @@ class DbCommands(object):
'"glance-manage db sync" to place the database under '
'alembic migration control.'))
def check(self):
"""Report any pending database upgrades.
An exit code of 3 indicates db expand is needed, see stdout output.
An exit code of 4 indicates db migrate is needed, see stdout output.
An exit code of 5 indicates db contract is needed, see stdout output.
"""
engine = db_api.get_engine()
self._validate_engine(engine)
curr_heads = alembic_migrations.get_current_alembic_heads()
expand_heads = alembic_migrations.get_alembic_branch_head(
db_migration.EXPAND_BRANCH)
contract_heads = alembic_migrations.get_alembic_branch_head(
db_migration.CONTRACT_BRANCH)
if (contract_heads in curr_heads):
print(_('Database is up to date. No upgrades needed.'))
sys.exit()
elif ((not expand_heads) or (expand_heads not in curr_heads)):
print(_('Your database is not up to date. '
'Your first step is to run `glance-manage db expand`.'))
sys.exit(3)
elif data_migrations.has_pending_migrations(db_api.get_engine()):
print(_('Your database is not up to date. '
'Your next step is to run `glance-manage db migrate`.'))
sys.exit(4)
elif ((not contract_heads) or (contract_heads not in curr_heads)):
print(_('Your database is not up to date. '
'Your next step is to run `glance-manage db contract`.'))
sys.exit(5)
@args('--version', metavar='<version>', help='Database version')
def upgrade(self, version='heads'):
"""Upgrade the database's migration level"""
@ -335,6 +368,9 @@ class DbLegacyCommands(object):
def migrate(self):
self.command_object.migrate()
def check(self):
self.command_object.check()
def load_metadefs(self, path=None, merge=False,
prefer_new=False, overwrite=False):
self.command_object.load_metadefs(CONF.command.path,
@ -384,6 +420,10 @@ def add_legacy_command_parsers(command_object, subparsers):
parser.set_defaults(action_fn=legacy_command_object.migrate)
parser.set_defaults(action='db_migrate')
parser = subparsers.add_parser('db_check')
parser.set_defaults(action_fn=legacy_command_object.check)
parser.set_defaults(action='db_check')
parser = subparsers.add_parser('db_load_metadefs')
parser.set_defaults(action_fn=legacy_command_object.load_metadefs)
parser.add_argument('path', nargs='?')

View File

@ -24,6 +24,8 @@ from oslo_db import options as db_options
from glance.common import utils
from glance.db import migration as db_migration
from glance.db.sqlalchemy import alembic_migrations
from glance.db.sqlalchemy.alembic_migrations import data_migrations
from glance.db.sqlalchemy import api as db_api
from glance.tests import functional
from glance.tests.utils import depends_on_exe
from glance.tests.utils import execute
@ -46,16 +48,28 @@ class TestGlanceManage(functional.FunctionalTest):
db_options.set_defaults(CONF, connection='sqlite:///%s' %
self.db_filepath)
def _sync_db(self):
def _db_command(self, db_method):
with open(self.conf_filepath, 'w') as conf_file:
conf_file.write('[DEFAULT]\n')
conf_file.write(self.connection)
conf_file.flush()
cmd = ('%s -m glance.cmd.manage --config-file %s db sync' %
(sys.executable, self.conf_filepath))
cmd = ('%s -m glance.cmd.manage --config-file %s db %s' %
(sys.executable, self.conf_filepath, db_method))
execute(cmd, raise_error=True)
def _check_db(self, expected_exitcode):
with open(self.conf_filepath, 'w') as conf_file:
conf_file.write('[DEFAULT]\n')
conf_file.write(self.connection)
conf_file.flush()
cmd = ('%s -m glance.cmd.manage --config-file %s db check' %
(sys.executable, self.conf_filepath))
exitcode, out, err = execute(cmd, raise_error=True,
expected_exitcode=expected_exitcode)
return exitcode, out
def _assert_table_exists(self, db_table):
cmd = ("sqlite3 {0} \"SELECT name FROM sqlite_master WHERE "
"type='table' AND name='{1}'\"").format(self.db_filepath,
@ -68,7 +82,7 @@ class TestGlanceManage(functional.FunctionalTest):
@skip_if_disabled
def test_db_creation(self):
"""Test schema creation by db_sync on a fresh DB"""
self._sync_db()
self._db_command(db_method='sync')
for table in ['images', 'image_tags', 'image_locations',
'image_members', 'image_properties']:
@ -78,7 +92,7 @@ class TestGlanceManage(functional.FunctionalTest):
@skip_if_disabled
def test_sync(self):
"""Test DB sync which internally calls EMC"""
self._sync_db()
self._db_command(db_method='sync')
contract_head = alembic_migrations.get_alembic_branch_head(
db_migration.CONTRACT_BRANCH)
@ -86,3 +100,22 @@ class TestGlanceManage(functional.FunctionalTest):
).format(self.db_filepath)
exitcode, out, err = execute(cmd, raise_error=True)
self.assertEqual(contract_head, out.rstrip().decode("utf-8"))
@depends_on_exe('sqlite3')
@skip_if_disabled
def test_check(self):
exitcode, out = self._check_db(3)
self.assertEqual(3, exitcode)
self._db_command(db_method='expand')
if data_migrations.has_pending_migrations(db_api.get_engine()):
exitcode, out = self._check_db(4)
self.assertEqual(4, exitcode)
self._db_command(db_method='migrate')
exitcode, out = self._check_db(5)
self.assertEqual(5, exitcode)
self._db_command(db_method='contract')
exitcode, out = self._check_db(0)
self.assertEqual(0, exitcode)

View File

@ -17,6 +17,7 @@ from __future__ import absolute_import
import fixtures
import mock
from six.moves import StringIO
from glance.cmd import manage
from glance.db.sqlalchemy import api as db_api
@ -158,11 +159,80 @@ class TestLegacyManage(TestManageBase):
class TestManage(TestManageBase):
def setUp(self):
super(TestManage, self).setUp()
self.db = manage.DbCommands()
self.output = StringIO()
self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.output))
@mock.patch('glance.db.sqlalchemy.api.get_engine')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.data_migrations.'
'has_pending_migrations')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_current_alembic_heads')
@mock.patch(
'glance.db.sqlalchemy.alembic_migrations.get_alembic_branch_head')
def test_db_check_result(self, mock_get_alembic_branch_head,
mock_get_current_alembic_heads,
mock_has_pending_migrations,
get_mock_engine):
get_mock_engine.return_value = mock.Mock()
engine = get_mock_engine.return_value
engine.engine.name = 'postgresql'
exit = self.assertRaises(SystemExit, self.db.check)
self.assertIn('Rolling upgrades are currently supported only for '
'MySQL and Sqlite', exit.code)
engine = get_mock_engine.return_value
engine.engine.name = 'mysql'
mock_get_current_alembic_heads.return_value = ['ocata_contract01']
mock_get_alembic_branch_head.return_value = 'pike_expand01'
exit = self.assertRaises(SystemExit, self.db.check)
self.assertEqual(3, exit.code)
self.assertIn('Your database is not up to date. '
'Your first step is to run `glance-manage db expand`.',
self.output.getvalue())
mock_get_current_alembic_heads.return_value = ['pike_expand01']
mock_get_alembic_branch_head.side_effect = ['pike_expand01', None]
mock_has_pending_migrations.return_value = [mock.Mock()]
exit = self.assertRaises(SystemExit, self.db.check)
self.assertEqual(4, exit.code)
self.assertIn('Your database is not up to date. '
'Your next step is to run `glance-manage db migrate`.',
self.output.getvalue())
mock_get_current_alembic_heads.return_value = ['pike_expand01']
mock_get_alembic_branch_head.side_effect = ['pike_expand01',
'pike_contract01']
mock_has_pending_migrations.return_value = None
exit = self.assertRaises(SystemExit, self.db.check)
self.assertEqual(5, exit.code)
self.assertIn('Your database is not up to date. '
'Your next step is to run `glance-manage db contract`.',
self.output.getvalue())
mock_get_current_alembic_heads.return_value = ['pike_contract01']
mock_get_alembic_branch_head.side_effect = ['pike_expand01',
'pike_contract01']
mock_has_pending_migrations.return_value = None
self.assertRaises(SystemExit, self.db.check)
self.assertIn('Database is up to date. No upgrades needed.',
self.output.getvalue())
@mock.patch.object(manage.DbCommands, 'version')
def test_db_version(self, version):
self._main_test_helper(['glance.cmd.manage', 'db', 'version'],
manage.DbCommands.version)
@mock.patch.object(manage.DbCommands, 'check')
def test_db_check(self, check):
self._main_test_helper(['glance.cmd.manage', 'db', 'check'],
manage.DbCommands.check)
@mock.patch.object(manage.DbCommands, 'sync')
def test_db_sync(self, sync):
self._main_test_helper(['glance.cmd.manage', 'db', 'sync'],

View File

@ -0,0 +1,27 @@
---
features:
- |
Added a new command ``glance-manage db check``, the command will
allow a user to check the status of upgrades in the database.
upgrade:
- |
Using db check
In order to check the current state of your database upgrades, you may run the
command ``glance-manage db check``. This will inform you of any
outstanding actions you have left to take.
Here is 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`` upgrades are complete.
- A return code of ``3`` means that an upgrade from your current database
version is available and your first step is to run ``glance-manage db expand``.
- A return code of ``4`` means that the expansion stage is complete, and the
next step is to run ``glance-manage db migrate``.
- A return code of ``5`` means that the expansion and data migration stages are
complete, and the next step is to run ``glance-manage db contract``.