From 939fa2c0ff6527258a9b4e17be8f0f5a765eefce Mon Sep 17 00:00:00 2001 From: Karthik Prabhu Vinod Date: Tue, 28 Feb 2017 00:36:44 +0000 Subject: [PATCH] Make cinder-manage online migrations more verbose This makes the online_data_migrations command for cinder-manage a little more verbose in what it is doing. Each time it is run, it will show you all the migrations that need running and how many records remain for each. Basically, you run this until you see all zeroes. Sample output of $cinder-manage db online_data_migrations +------------+-------+------+-----------+ | Migration | Found | Done | Remaining | +------------+-------+------+-----------+ | mock_mig_1 | 5 | 4 | 1 | | mock_mig_2 | 6 | 6 | 0 | +------------+-------+------+-----------+ Change-Id: I572bf2eb560698766d741bd2fd78c8b1067335d0 --- cinder/cmd/manage.py | 37 +++++++++++++---- cinder/tests/unit/test_cmd.py | 41 +++++++++++++++++++ ...se-online-migrations-94fb7e8a85cdbc10.yaml | 6 +++ 3 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 releasenotes/notes/verbose-online-migrations-94fb7e8a85cdbc10.yaml 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 84da4569138..ec3253de842 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 @@ -224,6 +226,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.