diff --git a/cinder/cmd/manage.py b/cinder/cmd/manage.py index 5c184bd9d39..e4943631258 100644 --- a/cinder/cmd/manage.py +++ b/cinder/cmd/manage.py @@ -57,6 +57,7 @@ from __future__ import print_function import logging as python_logging import os +import prettytable import sys import time @@ -67,9 +68,6 @@ from oslo_log import log as logging import oslo_messaging as messaging from oslo_utils import timeutils -from cinder import i18n -i18n.enable_lazy() - # Need to register global_opts from cinder.common import config # noqa from cinder.common import constants @@ -246,6 +244,7 @@ class DbCommands(object): def _run_migration(self, ctxt, max_count, ignore_state): ran = 0 + migrations = {} for migration_meth in self.online_migrations: count = max_count - ran try: @@ -255,16 +254,24 @@ class DbCommands(object): {'method': migration_meth.__name__}) found = done = 0 + name = migration_meth.__name__ + remaining = found - done if found: - print(_('%(total)i rows matched query %(meth)s, %(done)i ' - 'migrated') % {'total': found, - 'meth': migration_meth.__name__, - 'done': done}) + print(_('%(found)i rows matched query %(meth)s, %(done)i ' + 'migrated, %(remaining)i remaining') % {'found': found, + 'meth': name, + 'done': done, + 'remaining': + remaining}) + migrations.setdefault(name, (0, 0, 0)) + migrations[name] = (migrations[name][0] + found, + migrations[name][1] + done, + migrations[name][2] + remaining) if max_count is not None: ran += done if ran >= max_count: break - return ran + return migrations @args('--max_count', metavar='', dest='max_count', type=int, help='Maximum number of objects to consider.') @@ -286,11 +293,23 @@ class DbCommands(object): print(_('Running batches of %i until complete.') % max_count) ran = None + migration_info = {} while ran is None or ran != 0: - ran = self._run_migration(ctxt, max_count, ignore_state) + migrations = self._run_migration(ctxt, max_count, ignore_state) + migration_info.update(migrations) + ran = sum([done for found, done, remaining in migrations.values()]) if not unlimited: break + t = prettytable.PrettyTable([_('Migration'), + _('Found'), + _('Done'), + _('Remaining')]) + for name in sorted(migration_info.keys()): + info = migration_info[name] + t.add_row([name, info[0], info[1], info[2]]) + print(t) + sys.exit(1 if ran else 0) diff --git a/cinder/tests/unit/test_cmd.py b/cinder/tests/unit/test_cmd.py index 960c115bcf7..96b5f33dcf2 100644 --- a/cinder/tests/unit/test_cmd.py +++ b/cinder/tests/unit/test_cmd.py @@ -16,10 +16,12 @@ import sys import time import ddt +import fixtures import mock from oslo_config import cfg from oslo_utils import timeutils import six +from six.moves import StringIO try: import rtslib_fb @@ -225,6 +227,45 @@ class TestCinderManageCmd(test.TestCase): cinder_manage.DbCommands.online_migrations[0].assert_has_calls( (mock.call(mock.ANY, 50, False),) * 2) + def _fake_db_command(self, migrations=None): + if migrations is None: + mock_mig_1 = mock.MagicMock(__name__="mock_mig_1") + mock_mig_2 = mock.MagicMock(__name__="mock_mig_2") + mock_mig_1.return_value = (5, 4) + mock_mig_2.return_value = (6, 6) + migrations = (mock_mig_1, mock_mig_2) + + class _CommandSub(cinder_manage.DbCommands): + online_migrations = migrations + + return _CommandSub + + @mock.patch('cinder.context.get_admin_context') + def test_online_migrations(self, mock_get_context): + self.useFixture(fixtures.MonkeyPatch('sys.stdout', StringIO())) + ctxt = mock_get_context.return_value + db_cmds = self._fake_db_command() + command = db_cmds() + exit = self.assertRaises(SystemExit, + command.online_data_migrations, 10) + self.assertEqual(1, exit.code) + expected = """\ +5 rows matched query mock_mig_1, 4 migrated, 1 remaining +6 rows matched query mock_mig_2, 6 migrated, 0 remaining ++------------+-------+------+-----------+ +| Migration | Found | Done | Remaining | ++------------+-------+------+-----------+ +| mock_mig_1 | 5 | 4 | 1 | +| mock_mig_2 | 6 | 6 | 0 | ++------------+-------+------+-----------+ +""" + command.online_migrations[0].assert_has_calls([mock.call(ctxt, + 10, False)]) + command.online_migrations[1].assert_has_calls([mock.call(ctxt, + 6, False)]) + + self.assertEqual(expected, sys.stdout.getvalue()) + @mock.patch('cinder.cmd.manage.DbCommands.online_migrations', (mock.Mock(side_effect=((2, 2), (0, 0)), __name__='foo'),)) def test_db_commands_online_data_migrations_ignore_state_and_max(self): diff --git a/releasenotes/notes/verbose-online-migrations-94fb7e8a85cdbc10.yaml b/releasenotes/notes/verbose-online-migrations-94fb7e8a85cdbc10.yaml new file mode 100644 index 00000000000..0a0f5dfc42e --- /dev/null +++ b/releasenotes/notes/verbose-online-migrations-94fb7e8a85cdbc10.yaml @@ -0,0 +1,6 @@ +--- +features: + - The cinder-manage online_data_migrations command now prints a + tabular summary of completed and remaining records. The goal + here is to get all your numbers to zero. The previous execution + return code behavior is retained for scripting.