Merge "Allow running db archiving continuously"

This commit is contained in:
Jenkins 2016-10-05 00:56:53 +00:00 committed by Gerrit Code Review
commit 870a77f571
3 changed files with 110 additions and 14 deletions

View File

@ -825,28 +825,53 @@ class DbCommands(object):
"""Print the current database version.""" """Print the current database version."""
print(migration.db_version()) print(migration.db_version())
@args('--max_rows', metavar='<number>', @args('--max_rows', metavar='<number>', default=1000,
help='Maximum number of deleted rows to archive') help='Maximum number of deleted rows to archive')
@args('--verbose', action='store_true', dest='verbose', default=False, @args('--verbose', action='store_true', dest='verbose', default=False,
help='Print how many rows were archived per table.') help='Print how many rows were archived per table.')
def archive_deleted_rows(self, max_rows, verbose=False): @args('--until-complete', action='store_true', dest='until_complete',
"""Move up to max_rows deleted rows from production tables to shadow default=False,
tables. help=('Run continuously until all deleted rows are archived. Use '
'max_rows as a batch size for each iteration.'))
def archive_deleted_rows(self, max_rows, verbose=False,
until_complete=False):
"""Move deleted rows from production tables to shadow tables.
Returns 0 if nothing was archived, 1 if some number of rows were Returns 0 if nothing was archived, 1 if some number of rows were
archived, 2 if max_rows is invalid. If automating, this should be archived, 2 if max_rows is invalid. If automating, this should be
run continuously while the result is 1, stopping at 0. run continuously while the result is 1, stopping at 0.
""" """
if max_rows is not None: max_rows = int(max_rows)
max_rows = int(max_rows) 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(2)
return(2) if max_rows > db.MAX_INT:
if max_rows > db.MAX_INT: print(_('max rows must be <= %(max_value)d') %
print(_('max rows must be <= %(max_value)d') % {'max_value': db.MAX_INT})
{'max_value': db.MAX_INT}) return(2)
return(2)
table_to_rows_archived = db.archive_deleted_rows(max_rows) table_to_rows_archived = {}
if until_complete and verbose:
sys.stdout.write(_('Archiving') + '..') # noqa
while True:
try:
run = db.archive_deleted_rows(max_rows)
except KeyboardInterrupt:
run = {}
if until_complete and verbose:
print('.' + _('stopped')) # noqa
break
for k, v in run.items():
table_to_rows_archived.setdefault(k, 0)
table_to_rows_archived[k] += v
if not until_complete:
break
elif not run:
if verbose:
print('.' + _('complete')) # noqa
break
if verbose:
sys.stdout.write('.')
if verbose: if verbose:
if table_to_rows_archived: if table_to_rows_archived:
utils.print_dict(table_to_rows_archived, _('Table'), utils.print_dict(table_to_rows_archived, _('Table'),

View File

@ -503,6 +503,70 @@ class DBCommandsTestCase(test.NoDBTestCase):
# Tests that we get table output. # Tests that we get table output.
self._test_archive_deleted_rows(verbose=True) self._test_archive_deleted_rows(verbose=True)
@mock.patch.object(db, 'archive_deleted_rows')
def test_archive_deleted_rows_until_complete(self, mock_db_archive,
verbose=False):
mock_db_archive.side_effect = [
{'instances': 10, 'instance_extra': 5},
{'instances': 5, 'instance_faults': 1},
{}]
result = self.commands.archive_deleted_rows(20, verbose=verbose,
until_complete=True)
self.assertEqual(1, result)
if verbose:
expected = """\
Archiving.....complete
+-----------------+-------------------------+
| Table | Number of Rows Archived |
+-----------------+-------------------------+
| instance_extra | 5 |
| instance_faults | 1 |
| instances | 15 |
+-----------------+-------------------------+
"""
else:
expected = ''
self.assertEqual(expected, self.output.getvalue())
mock_db_archive.assert_has_calls([mock.call(20),
mock.call(20),
mock.call(20)])
def test_archive_deleted_rows_until_complete_quiet(self):
self.test_archive_deleted_rows_until_complete(verbose=False)
@mock.patch.object(db, 'archive_deleted_rows')
def test_archive_deleted_rows_until_stopped(self, mock_db_archive,
verbose=True):
mock_db_archive.side_effect = [
{'instances': 10, 'instance_extra': 5},
{'instances': 5, 'instance_faults': 1},
KeyboardInterrupt]
result = self.commands.archive_deleted_rows(20, verbose=verbose,
until_complete=True)
self.assertEqual(1, result)
if verbose:
expected = """\
Archiving.....stopped
+-----------------+-------------------------+
| Table | Number of Rows Archived |
+-----------------+-------------------------+
| instance_extra | 5 |
| instance_faults | 1 |
| instances | 15 |
+-----------------+-------------------------+
"""
else:
expected = ''
self.assertEqual(expected, self.output.getvalue())
mock_db_archive.assert_has_calls([mock.call(20),
mock.call(20),
mock.call(20)])
def test_archive_deleted_rows_until_stopped_quiet(self):
self.test_archive_deleted_rows_until_stopped(verbose=False)
@mock.patch.object(db, 'archive_deleted_rows', return_value={}) @mock.patch.object(db, 'archive_deleted_rows', return_value={})
def test_archive_deleted_rows_verbose_no_results(self, mock_db_archive): def test_archive_deleted_rows_verbose_no_results(self, mock_db_archive):
result = self.commands.archive_deleted_rows(20, verbose=True) result = self.commands.archive_deleted_rows(20, verbose=True)

View File

@ -0,0 +1,7 @@
---
features:
- |
Support for archiving all deleted rows from the database has
been added to the ``nova-manage db archive_deleted_rows``
command. The ``--until-complete`` option will continuously
run the process until no more rows are available for archiving.