Print number of rows archived per table in db archive_deleted_rows

The `nova-manage db archive_deleted_rows` command doesn't print any
results, not even how many rows were deleted, which is something the
database API returns.

The number of rows deleted also changes depending on the max_rows input
and how many rows are deleted in each table, it's a cumulative effect.

We should keep track of which tables we've deleted rows from along with
how many rows, and print that all out to the CLI console if the user
passed a --verbose option.

Implements blueprint print-table-archived-rows

Change-Id: I5c47cd5633eca056f8ae508753a41e2c1ed9e523
This commit is contained in:
Matt Riedemann 2015-10-08 08:55:55 -07:00
parent 16111bd236
commit e37279b459
6 changed files with 93 additions and 16 deletions

View File

@ -50,9 +50,11 @@ Nova Db
Sync the main database up to the most recent version. This is the standard way to create the db as well.
``nova-manage db archive_deleted_rows [--max_rows <number>]``
``nova-manage db archive_deleted_rows [--max_rows <number>] [--verbose]``
Move deleted rows from production tables to shadow tables.
Move deleted rows from production tables to shadow tables. Specifying
--verbose will print the results of the archive operation for any tables
that were changed.
``nova-manage db null_instance_uuid_scan [--delete]``

View File

@ -940,7 +940,9 @@ class DbCommands(object):
@args('--max_rows', metavar='<number>',
help='Maximum number of deleted rows to archive')
def archive_deleted_rows(self, max_rows):
@args('--verbose', action='store_true', dest='verbose', default=False,
help='Print how many rows were archived per table.')
def archive_deleted_rows(self, max_rows, verbose=False):
"""Move up to max_rows deleted rows from production tables to shadow
tables.
"""
@ -949,7 +951,13 @@ class DbCommands(object):
if max_rows < 0:
print(_("Must supply a positive value for max_rows"))
return(1)
db.archive_deleted_rows(max_rows)
table_to_rows_archived = db.archive_deleted_rows(max_rows)
if verbose:
if table_to_rows_archived:
cliutils.print_dict(table_to_rows_archived, _('Table'),
dict_value=_('Number of Rows Archived'))
else:
print(_('Nothing was archived.'))
@args('--delete', action='store_true', dest='delete',
help='If specified, automatically delete any records found where '

View File

@ -1899,7 +1899,17 @@ def archive_deleted_rows(max_rows=None):
"""Move up to max_rows rows from production tables to corresponding shadow
tables.
:returns: number of rows archived.
:returns: dict that maps table name to number of rows archived from that
table, for example:
::
{
'instances': 5,
'block_device_mapping': 5,
'pci_devices': 2,
}
"""
return IMPL.archive_deleted_rows(max_rows=max_rows)

View File

@ -6035,19 +6035,34 @@ def archive_deleted_rows(max_rows=None):
"""Move up to max_rows rows from production tables to the corresponding
shadow tables.
:returns: Number of rows archived.
:returns: dict that maps table name to number of rows archived from that
table, for example:
::
{
'instances': 5,
'block_device_mapping': 5,
'pci_devices': 2,
}
"""
table_to_rows_archived = {}
tablenames = []
for model_class in six.itervalues(models.__dict__):
if hasattr(model_class, "__tablename__"):
tablenames.append(model_class.__tablename__)
rows_archived = 0
total_rows_archived = 0
for tablename in tablenames:
rows_archived += _archive_deleted_rows_for_table(tablename,
max_rows=max_rows - rows_archived)
if rows_archived >= max_rows:
rows_archived = _archive_deleted_rows_for_table(
tablename, max_rows=max_rows - total_rows_archived)
total_rows_archived += rows_archived
# Only report results for tables that had updates.
if rows_archived:
table_to_rows_archived[tablename] = rows_archived
if total_rows_archived >= max_rows:
break
return rows_archived
return table_to_rows_archived
####################

View File

@ -8022,7 +8022,7 @@ class Ec2TestCase(test.TestCase):
self.ctxt, 100500)
class ArchiveTestCase(test.TestCase):
class ArchiveTestCase(test.TestCase, ModelsObjectComparatorMixin):
def setUp(self):
super(ArchiveTestCase, self).setUp()
@ -8107,7 +8107,9 @@ class ArchiveTestCase(test.TestCase):
# Verify we have 0 in shadow
self.assertEqual(len(rows), 0)
# Archive 2 rows
db.archive_deleted_rows(max_rows=2)
results = db.archive_deleted_rows(max_rows=2)
expected = dict(instance_id_mappings=2)
self._assertEqualObjects(expected, results)
rows = self.conn.execute(qiim).fetchall()
# Verify we have 4 left in main
self.assertEqual(len(rows), 4)
@ -8115,7 +8117,9 @@ class ArchiveTestCase(test.TestCase):
# Verify we have 2 in shadow
self.assertEqual(len(rows), 2)
# Archive 2 more rows
db.archive_deleted_rows(max_rows=2)
results = db.archive_deleted_rows(max_rows=2)
expected = dict(instance_id_mappings=2)
self._assertEqualObjects(expected, results)
rows = self.conn.execute(qiim).fetchall()
# Verify we have 2 left in main
self.assertEqual(len(rows), 2)
@ -8123,7 +8127,9 @@ class ArchiveTestCase(test.TestCase):
# Verify we have 4 in shadow
self.assertEqual(len(rows), 4)
# Try to archive more, but there are no deleted rows left.
db.archive_deleted_rows(max_rows=2)
results = db.archive_deleted_rows(max_rows=2)
expected = dict()
self._assertEqualObjects(expected, results)
rows = self.conn.execute(qiim).fetchall()
# Verify we still have 2 left in main
self.assertEqual(len(rows), 2)

View File

@ -375,7 +375,7 @@ class VmCommandsTestCase(test.TestCase):
self.assertIn('fake-host', result)
class DBCommandsTestCase(test.TestCase):
class DBCommandsTestCase(test.NoDBTestCase):
def setUp(self):
super(DBCommandsTestCase, self).setUp()
self.commands = manage.DbCommands()
@ -383,6 +383,42 @@ class DBCommandsTestCase(test.TestCase):
def test_archive_deleted_rows_negative(self):
self.assertEqual(1, self.commands.archive_deleted_rows(-1))
@mock.patch.object(db, 'archive_deleted_rows',
return_value=dict(instances=10, consoles=5))
def _test_archive_deleted_rows(self, mock_db_archive, verbose=False):
self.useFixture(fixtures.MonkeyPatch('sys.stdout', StringIO()))
self.commands.archive_deleted_rows(20, verbose=verbose)
mock_db_archive.assert_called_once_with(20)
output = sys.stdout.getvalue()
if verbose:
expected = '''\
+-----------+-------------------------+
| Table | Number of Rows Archived |
+-----------+-------------------------+
| consoles | 5 |
| instances | 10 |
+-----------+-------------------------+
'''
self.assertEqual(expected, output)
else:
self.assertEqual(0, len(output))
def test_archive_deleted_rows(self):
# Tests that we don't show any table output (not verbose).
self._test_archive_deleted_rows()
def test_archive_deleted_rows_verbose(self):
# Tests that we get table output.
self._test_archive_deleted_rows(verbose=True)
@mock.patch.object(db, 'archive_deleted_rows', return_value={})
def test_archive_deleted_rows_verbose_no_results(self, mock_db_archive):
self.useFixture(fixtures.MonkeyPatch('sys.stdout', StringIO()))
self.commands.archive_deleted_rows(20, verbose=True)
mock_db_archive.assert_called_once_with(20)
output = sys.stdout.getvalue()
self.assertIn('Nothing was archived.', output)
@mock.patch.object(migration, 'db_null_instance_uuid_scan',
return_value={'foo': 0})
def test_null_instance_uuid_scan_no_records_found(self, mock_scan):