From 360dc45680df0c968994d60f87446e1a93e2d792 Mon Sep 17 00:00:00 2001 From: Mitya_Eremeev Date: Wed, 4 May 2022 15:16:29 +0300 Subject: [PATCH] glance-manage can purge all deleted rows When we want to purge all deleted rows we use "glance-manage db purge" or "glance-manage purge_images_table" then we never know how many deleted rows are still in a table. So we need to launch the command many times until the command reports that 0 rows were deleted in every table. It's inconvenient. The patch introduced new valid value for "max_rows". If "max_rows" equals -1 all deleted rows are purged. Closes-Bug: 1702445 Change-Id: Ia9811d93b1c0b67a1b29a9e8ee3e0fc098c57d4a --- doc/source/admin/db.rst | 6 +++-- glance/cmd/manage.py | 10 ++++---- glance/db/sqlalchemy/api.py | 24 ++++++++++++------- glance/tests/unit/test_glance_manage.py | 20 +++++++++++++--- ...rge_all_deleted_rows-7b3b9b767669b1a5.yaml | 4 ++++ 5 files changed, 46 insertions(+), 18 deletions(-) create mode 100644 releasenotes/notes/add_capability_to_purge_all_deleted_rows-7b3b9b767669b1a5.yaml diff --git a/doc/source/admin/db.rst b/doc/source/admin/db.rst index 72865e2153..5cfc4915d7 100644 --- a/doc/source/admin/db.rst +++ b/doc/source/admin/db.rst @@ -184,7 +184,8 @@ This command takes two optional parameters: than *NUM* days. The default is 30 days. --max_rows NUM Purge a maximum of *NUM* rows from each table. - The default is 100. + The default is 100. All deleted rows are purged if equals + -1 Purging the Images Table @@ -212,7 +213,8 @@ It takes two optional parameters: than *NUM* days. The default is 180 days. --max_rows NUM Purge a maximum of *NUM* rows from the **images** table. - The default is 100. + The default is 100. All deleted rows are purged if equals + -1 It is possible for this command to fail with an IntegrityError saying something like "Cannot delete or update a parent row: a foreign key diff --git a/glance/cmd/manage.py b/glance/cmd/manage.py index adbb9c14ff..f42bccb715 100644 --- a/glance/cmd/manage.py +++ b/glance/cmd/manage.py @@ -339,8 +339,8 @@ class DbCommands(object): sys.exit(_("Must supply a non-negative value for age.")) if age_in_days >= (int(time.time()) / 86400): sys.exit(_("Maximal age is count of days since epoch.")) - if max_rows < 1: - sys.exit(_("Minimal rows limit is 1.")) + if max_rows < -1: + sys.exit(_("Minimal rows limit is -1.")) ctx = context.get_admin_context(show_deleted=True) try: if purge_images_only: @@ -357,7 +357,8 @@ class DbCommands(object): @args('--age_in_days', type=int, help='Purge deleted rows older than age in days') @args('--max_rows', type=int, - help='Limit number of records to delete') + help='Limit number of records to delete. All deleted rows will be ' + 'purged if equals -1.') def purge(self, age_in_days=30, max_rows=100): """Purge deleted rows older than a given age from glance tables.""" self._purge(age_in_days, max_rows) @@ -365,7 +366,8 @@ class DbCommands(object): @args('--age_in_days', type=int, help='Purge deleted rows older than age in days') @args('--max_rows', type=int, - help='Limit number of records to delete') + help='Limit number of records to delete. All deleted rows will be ' + 'purged if equals -1.') def purge_images_table(self, age_in_days=180, max_rows=100): """Purge deleted rows older than a given age from images table.""" self._purge(age_in_days, max_rows, purge_images_only=True) diff --git a/glance/db/sqlalchemy/api.py b/glance/db/sqlalchemy/api.py index cdf17d8252..8630fb69e8 100644 --- a/glance/db/sqlalchemy/api.py +++ b/glance/db/sqlalchemy/api.py @@ -1510,9 +1510,12 @@ def purge_deleted_rows(context, age_in_days, max_rows, session=None): t = Table("tasks", metadata, autoload=True) ti = Table("task_info", metadata, autoload=True) joined_rec = ti.join(t, t.c.id == ti.c.task_id) - deleted_task_info = sql.select([ti.c.task_id], - t.c.deleted_at < deleted_age).\ - select_from(joined_rec).order_by(t.c.deleted_at).limit(max_rows) + deleted_task_info = sql.\ + select([ti.c.task_id], t.c.deleted_at < deleted_age).\ + select_from(joined_rec).\ + order_by(t.c.deleted_at) + if max_rows != -1: + deleted_task_info = deleted_task_info.limit(max_rows) delete_statement = DeleteFromSelect(ti, deleted_task_info, ti.c.task_id) LOG.info(_LI('Purging deleted rows older than %(age_in_days)d day(s) ' @@ -1556,9 +1559,10 @@ def purge_deleted_rows(context, age_in_days, max_rows, session=None): column = tab.c.id deleted_at_column = tab.c.deleted_at - query_delete = sql.select( - [column], deleted_at_column < deleted_age).order_by( - deleted_at_column).limit(max_rows) + query_delete = sql.select([column], deleted_at_column < deleted_age).\ + order_by(deleted_at_column) + if max_rows != -1: + query_delete = query_delete.limit(max_rows) delete_statement = DeleteFromSelect(tab, query_delete, column) @@ -1600,9 +1604,11 @@ def purge_deleted_rows_from_images(context, age_in_days, max_rows, column = tab.c.id deleted_at_column = tab.c.deleted_at - query_delete = sql.select( - [column], deleted_at_column < deleted_age).order_by( - deleted_at_column).limit(max_rows) + query_delete = sql.\ + select([column], deleted_at_column < deleted_age).\ + order_by(deleted_at_column) + if max_rows != -1: + query_delete = query_delete.limit(max_rows) delete_statement = DeleteFromSelect(tab, query_delete, column) diff --git a/glance/tests/unit/test_glance_manage.py b/glance/tests/unit/test_glance_manage.py index cc8dc11d0b..a476bb7e67 100644 --- a/glance/tests/unit/test_glance_manage.py +++ b/glance/tests/unit/test_glance_manage.py @@ -41,9 +41,9 @@ class DBCommandsTestCase(test_utils.BaseTestCase): self.commands.purge(0, 100) mock_db_purge.assert_called_once_with(self.context, 0, 100) - def test_purge_command_negative_rows(self): - exit = self.assertRaises(SystemExit, self.commands.purge, 1, -1) - self.assertEqual("Minimal rows limit is 1.", exit.code) + def test_purge_command_rows_less_minus_one(self): + exit = self.assertRaises(SystemExit, self.commands.purge, 1, -2) + self.assertEqual("Minimal rows limit is -1.", exit.code) def test_purge_invalid_age_in_days(self): age_in_days = 'abcd' @@ -86,3 +86,17 @@ class DBCommandsTestCase(test_utils.BaseTestCase): exit = self.assertRaises(SystemExit, self.commands.purge, 10, 100) self.assertEqual("Purge command failed, check glance-manage logs" " for more details.", exit.code) + + @mock.patch.object(db_api, 'purge_deleted_rows') + @mock.patch.object(context, 'get_admin_context') + def test_purge_command_purge_all(self, mock_context, mock_db_purge): + mock_context.return_value = self.context + self.commands.purge(max_rows=-1) + mock_db_purge.assert_called_once_with(self.context, 30, -1) + + @mock.patch.object(db_api, 'purge_deleted_rows_from_images') + @mock.patch.object(context, 'get_admin_context') + def test_purge_images_table_purge_all(self, mock_context, mock_db_purge): + mock_context.return_value = self.context + self.commands.purge_images_table(max_rows=-1) + mock_db_purge.assert_called_once_with(self.context, 180, -1) diff --git a/releasenotes/notes/add_capability_to_purge_all_deleted_rows-7b3b9b767669b1a5.yaml b/releasenotes/notes/add_capability_to_purge_all_deleted_rows-7b3b9b767669b1a5.yaml new file mode 100644 index 0000000000..bee3159013 --- /dev/null +++ b/releasenotes/notes/add_capability_to_purge_all_deleted_rows-7b3b9b767669b1a5.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + "glance-manage" purges all deleted rows if "--max_rows" equals -1