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:
parent
16111bd236
commit
e37279b459
@ -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.
|
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]``
|
``nova-manage db null_instance_uuid_scan [--delete]``
|
||||||
|
|
||||||
|
@ -940,7 +940,9 @@ class DbCommands(object):
|
|||||||
|
|
||||||
@args('--max_rows', metavar='<number>',
|
@args('--max_rows', metavar='<number>',
|
||||||
help='Maximum number of deleted rows to archive')
|
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
|
"""Move up to max_rows deleted rows from production tables to shadow
|
||||||
tables.
|
tables.
|
||||||
"""
|
"""
|
||||||
@ -949,7 +951,13 @@ class DbCommands(object):
|
|||||||
if max_rows < 0:
|
if max_rows < 0:
|
||||||
print(_("Must supply a positive value for max_rows"))
|
print(_("Must supply a positive value for max_rows"))
|
||||||
return(1)
|
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',
|
@args('--delete', action='store_true', dest='delete',
|
||||||
help='If specified, automatically delete any records found where '
|
help='If specified, automatically delete any records found where '
|
||||||
|
@ -1899,7 +1899,17 @@ def archive_deleted_rows(max_rows=None):
|
|||||||
"""Move up to max_rows rows from production tables to corresponding shadow
|
"""Move up to max_rows rows from production tables to corresponding shadow
|
||||||
tables.
|
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)
|
return IMPL.archive_deleted_rows(max_rows=max_rows)
|
||||||
|
|
||||||
|
@ -6035,19 +6035,34 @@ def archive_deleted_rows(max_rows=None):
|
|||||||
"""Move up to max_rows rows from production tables to the corresponding
|
"""Move up to max_rows rows from production tables to the corresponding
|
||||||
shadow tables.
|
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 = []
|
tablenames = []
|
||||||
for model_class in six.itervalues(models.__dict__):
|
for model_class in six.itervalues(models.__dict__):
|
||||||
if hasattr(model_class, "__tablename__"):
|
if hasattr(model_class, "__tablename__"):
|
||||||
tablenames.append(model_class.__tablename__)
|
tablenames.append(model_class.__tablename__)
|
||||||
rows_archived = 0
|
total_rows_archived = 0
|
||||||
for tablename in tablenames:
|
for tablename in tablenames:
|
||||||
rows_archived += _archive_deleted_rows_for_table(tablename,
|
rows_archived = _archive_deleted_rows_for_table(
|
||||||
max_rows=max_rows - rows_archived)
|
tablename, max_rows=max_rows - total_rows_archived)
|
||||||
if rows_archived >= max_rows:
|
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
|
break
|
||||||
return rows_archived
|
return table_to_rows_archived
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -8022,7 +8022,7 @@ class Ec2TestCase(test.TestCase):
|
|||||||
self.ctxt, 100500)
|
self.ctxt, 100500)
|
||||||
|
|
||||||
|
|
||||||
class ArchiveTestCase(test.TestCase):
|
class ArchiveTestCase(test.TestCase, ModelsObjectComparatorMixin):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(ArchiveTestCase, self).setUp()
|
super(ArchiveTestCase, self).setUp()
|
||||||
@ -8107,7 +8107,9 @@ class ArchiveTestCase(test.TestCase):
|
|||||||
# Verify we have 0 in shadow
|
# Verify we have 0 in shadow
|
||||||
self.assertEqual(len(rows), 0)
|
self.assertEqual(len(rows), 0)
|
||||||
# Archive 2 rows
|
# 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()
|
rows = self.conn.execute(qiim).fetchall()
|
||||||
# Verify we have 4 left in main
|
# Verify we have 4 left in main
|
||||||
self.assertEqual(len(rows), 4)
|
self.assertEqual(len(rows), 4)
|
||||||
@ -8115,7 +8117,9 @@ class ArchiveTestCase(test.TestCase):
|
|||||||
# Verify we have 2 in shadow
|
# Verify we have 2 in shadow
|
||||||
self.assertEqual(len(rows), 2)
|
self.assertEqual(len(rows), 2)
|
||||||
# Archive 2 more rows
|
# 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()
|
rows = self.conn.execute(qiim).fetchall()
|
||||||
# Verify we have 2 left in main
|
# Verify we have 2 left in main
|
||||||
self.assertEqual(len(rows), 2)
|
self.assertEqual(len(rows), 2)
|
||||||
@ -8123,7 +8127,9 @@ class ArchiveTestCase(test.TestCase):
|
|||||||
# Verify we have 4 in shadow
|
# Verify we have 4 in shadow
|
||||||
self.assertEqual(len(rows), 4)
|
self.assertEqual(len(rows), 4)
|
||||||
# Try to archive more, but there are no deleted rows left.
|
# 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()
|
rows = self.conn.execute(qiim).fetchall()
|
||||||
# Verify we still have 2 left in main
|
# Verify we still have 2 left in main
|
||||||
self.assertEqual(len(rows), 2)
|
self.assertEqual(len(rows), 2)
|
||||||
|
@ -375,7 +375,7 @@ class VmCommandsTestCase(test.TestCase):
|
|||||||
self.assertIn('fake-host', result)
|
self.assertIn('fake-host', result)
|
||||||
|
|
||||||
|
|
||||||
class DBCommandsTestCase(test.TestCase):
|
class DBCommandsTestCase(test.NoDBTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(DBCommandsTestCase, self).setUp()
|
super(DBCommandsTestCase, self).setUp()
|
||||||
self.commands = manage.DbCommands()
|
self.commands = manage.DbCommands()
|
||||||
@ -383,6 +383,42 @@ class DBCommandsTestCase(test.TestCase):
|
|||||||
def test_archive_deleted_rows_negative(self):
|
def test_archive_deleted_rows_negative(self):
|
||||||
self.assertEqual(1, self.commands.archive_deleted_rows(-1))
|
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',
|
@mock.patch.object(migration, 'db_null_instance_uuid_scan',
|
||||||
return_value={'foo': 0})
|
return_value={'foo': 0})
|
||||||
def test_null_instance_uuid_scan_no_records_found(self, mock_scan):
|
def test_null_instance_uuid_scan_no_records_found(self, mock_scan):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user