summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2019-09-10 15:36:25 +0000
committerGerrit Code Review <review@openstack.org>2019-09-10 15:36:25 +0000
commitcc4f74d112bb93cec0e51a37eea1d509b5ad1776 (patch)
tree2b7c28f3e790da4158ac613d233a0369cfa17a1d
parent37c7c0022c9b41c949a9f0d938e0c1ded6d41d50 (diff)
parent9fdabcabf31b90e703bf9a68fa16c75731685a21 (diff)
downloadglance-stable/queens.tar.gz
Merge "Mitigate OSSN-0075" into stable/queensqueens-eolstable/queens
-rw-r--r--glance/cmd/manage.py32
-rw-r--r--glance/db/sqlalchemy/api.py49
-rw-r--r--glance/tests/functional/db/base.py20
3 files changed, 89 insertions, 12 deletions
diff --git a/glance/cmd/manage.py b/glance/cmd/manage.py
index cefa89275..550f462bd 100644
--- a/glance/cmd/manage.py
+++ b/glance/cmd/manage.py
@@ -329,24 +329,17 @@ class DbCommands(object):
metadata.db_export_metadefs(db_api.get_engine(),
path)
- @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')
- def purge(self, age_in_days=30, max_rows=100):
- """Purge deleted rows older than a given age from glance tables."""
+ def _purge(self, age_in_days, max_rows, purge_images_only=False):
try:
age_in_days = int(age_in_days)
except ValueError:
sys.exit(_("Invalid int value for age_in_days: "
"%(age_in_days)s") % {'age_in_days': age_in_days})
-
try:
max_rows = int(max_rows)
except ValueError:
sys.exit(_("Invalid int value for max_rows: "
"%(max_rows)s") % {'max_rows': max_rows})
-
if age_in_days < 0:
sys.exit(_("Must supply a non-negative value for age."))
if age_in_days >= (int(time.time()) / 86400):
@@ -354,15 +347,34 @@ class DbCommands(object):
if max_rows < 1:
sys.exit(_("Minimal rows limit is 1."))
ctx = context.get_admin_context(show_deleted=True)
-
try:
- db_api.purge_deleted_rows(ctx, age_in_days, max_rows)
+ if purge_images_only:
+ db_api.purge_deleted_rows_from_images(ctx, age_in_days,
+ max_rows)
+ else:
+ db_api.purge_deleted_rows(ctx, age_in_days, max_rows)
except exception.Invalid as exc:
sys.exit(exc.msg)
except db_exc.DBReferenceError:
sys.exit(_("Purge command failed, check glance-manage"
" logs for more details."))
+ @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')
+ 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)
+
+ @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')
+ def purge_images_table(self, age_in_days=30, 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)
+
class DbLegacyCommands(object):
"""Class for managing the db using legacy commands"""
diff --git a/glance/db/sqlalchemy/api.py b/glance/db/sqlalchemy/api.py
index 57f46c00f..ef90a78bd 100644
--- a/glance/db/sqlalchemy/api.py
+++ b/glance/db/sqlalchemy/api.py
@@ -1307,6 +1307,11 @@ def purge_deleted_rows(context, age_in_days, max_rows, session=None):
LOG.warning(_LW('Expected table %(tbl)s was not found in DB.'),
{'tbl': tbl})
else:
+ # NOTE(abhishekk): To mitigate OSSN-0075 images records should be
+ # purged with new ``purge-images-table`` command.
+ if tbl == 'images':
+ continue
+
tables.append(tbl)
for tbl in tables:
@@ -1339,6 +1344,50 @@ def purge_deleted_rows(context, age_in_days, max_rows, session=None):
{'rows': rows, 'tbl': tbl})
+def purge_deleted_rows_from_images(context, age_in_days, max_rows,
+ session=None):
+ """Purges soft deleted rows
+
+ Deletes rows of table images table according to given age for
+ relevant models.
+ """
+ # check max_rows for its maximum limit
+ _validate_db_int(max_rows=max_rows)
+
+ session = session or get_session()
+ metadata = MetaData(get_engine())
+ deleted_age = timeutils.utcnow() - datetime.timedelta(days=age_in_days)
+
+ tbl = 'images'
+ tab = Table(tbl, metadata, autoload=True)
+ LOG.info(
+ _LI('Purging deleted rows older than %(age_in_days)d day(s) '
+ 'from table %(tbl)s'),
+ {'age_in_days': age_in_days, 'tbl': tbl})
+
+ 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)
+
+ delete_statement = DeleteFromSelect(tab, query_delete, column)
+
+ try:
+ with session.begin():
+ result = session.execute(delete_statement)
+ except db_exception.DBReferenceError as ex:
+ with excutils.save_and_reraise_exception():
+ LOG.error(_LE('DBError detected when purging from '
+ "%(tablename)s: %(error)s"),
+ {'tablename': tbl, 'error': six.text_type(ex)})
+
+ rows = result.rowcount
+ LOG.info(_LI('Deleted %(rows)d row(s) from table %(tbl)s'),
+ {'rows': rows, 'tbl': tbl})
+
+
def user_get_storage_usage(context, owner_id, image_id=None, session=None):
_check_image_id(image_id)
session = session or get_session()
diff --git a/glance/tests/functional/db/base.py b/glance/tests/functional/db/base.py
index 803025278..0b08f8370 100644
--- a/glance/tests/functional/db/base.py
+++ b/glance/tests/functional/db/base.py
@@ -1990,11 +1990,27 @@ class DBPurgeTests(test_utils.BaseTestCase):
def test_db_purge(self):
self.db_api.purge_deleted_rows(self.adm_context, 1, 5)
images = self.db_api.image_get_all(self.adm_context)
+
+ # Verify that no records from images have been deleted
+ # as images table will be purged using 'purge_images_table'
+ # command.
+ self.assertEqual(len(images), 3)
+ tasks = self.db_api.task_get_all(self.adm_context)
+ self.assertEqual(len(tasks), 2)
+
+ def test_db_purge_images_table(self):
+ # purge records from images_tags table
+ self.db_api.purge_deleted_rows(self.adm_context, 1, 5)
+
+ # purge records from images table
+ self.db_api.purge_deleted_rows_from_images(self.adm_context, 1, 5)
+ images = self.db_api.image_get_all(self.adm_context)
+
self.assertEqual(len(images), 2)
tasks = self.db_api.task_get_all(self.adm_context)
self.assertEqual(len(tasks), 2)
- def test_purge_fk_constraint_failure(self):
+ def test_purge_images_table_fk_constraint_failure(self):
"""Test foreign key constraint failure
Test whether foreign key constraint failure during purge
@@ -2053,7 +2069,7 @@ class DBPurgeTests(test_utils.BaseTestCase):
# Purge all records deleted at least 10 days ago
self.assertRaises(db_exception.DBReferenceError,
- db_api.purge_deleted_rows,
+ db_api.purge_deleted_rows_from_images,
self.adm_context,
age_in_days=10,
max_rows=50)