1
0
mirror of https://github.com/quay/quay.git synced 2025-07-30 07:43:13 +03:00

Add additional detection for transitive deletes and fix those found (#281)

We were not testing for transitive deletes when performing *User*
deletion, which led us to have a few
This commit is contained in:
Joseph Schorr
2020-03-25 15:58:12 -04:00
committed by GitHub
parent 3d2aadf91c
commit 59bc23313c
5 changed files with 61 additions and 42 deletions

View File

@ -651,9 +651,11 @@ class User(BaseModel):
TeamSync,
RepositorySearchScore,
DeletedNamespace,
DeletedRepository,
RepoMirrorRule,
NamespaceGeoRestriction,
ManifestSecurityStatus,
RepoMirrorConfig,
}
| appr_classes
| v22_classes

View File

@ -38,6 +38,7 @@ from image.docker.schema2.manifest import DockerSchema2ManifestBuilder
from image.shared.schemas import parse_manifest_from_bytes
from util.bytes import Bytes
from test.helpers import check_transitive_modifications
from test.fixtures import *
@ -203,6 +204,7 @@ def assert_gc_integrity(expect_storage_removed=True):
existing_manifest_count = _get_dangling_manifest_count()
# Yield to the GC test.
with check_transitive_modifications():
yield
# Ensure the number of dangling storages, manifests and labels has not changed.

View File

@ -20,6 +20,7 @@ from data.queue import WorkQueue
from util.timedeltastring import convert_to_timedelta
from util.timedeltastring import convert_to_timedelta
from util.security.token import encode_public_private_token
from test.helpers import check_transitive_modifications
from test.fixtures import *
@ -118,6 +119,7 @@ def test_delete_namespace_via_marker(initialized_db):
marker_id = mark_namespace_for_deletion(user, [], queue)
# Delete the user.
with check_transitive_modifications():
delete_namespace_via_marker(marker_id, [])
# Ensure the user was actually deleted.

View File

@ -1,8 +1,13 @@
import multiprocessing
import time
import socket
import logging
import re
from contextlib import contextmanager
from playhouse.test_utils import assert_query_count, _QueryLogHandler
from data.database import LogEntryKind, LogEntry3
@ -32,6 +37,53 @@ class assert_action_logged(object):
assert self.existing_count == (updated_count - 1), error_msg
class log_queries(object):
""" Logs all queries that occur under the context. """
def __init__(self, query_filters=None):
self.filters = query_filters
def get_queries(self):
queries = [q.msg[0] for q in self._handler.queries]
if not self.filters:
return queries
filtered_queries = []
for query_filter in self.filters:
filtered_queries.extend([q for q in queries if re.match(query_filter, q)])
return filtered_queries
def __enter__(self):
logger = logging.getLogger("peewee")
self._handler = _QueryLogHandler()
logger.setLevel(logging.DEBUG)
logger.addHandler(self._handler)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
logger = logging.getLogger("peewee")
logger.removeHandler(self._handler)
class check_transitive_modifications(log_queries):
""" Checks for Peewee-generated transition deletion queries and fails if any are found.
These kinds of queries (which use subqueries) can lock massively on MySQL, so we detect
them and fail.
"""
def __init__(self):
filters = [r"^DELETE.+IN \(SELECT.+$", r"^UPDATE.+IN \(SELECT.+$"]
super(check_transitive_modifications, self).__init__(query_filters=filters)
def __exit__(self, exc_type, exc_val, exc_tb):
super(check_transitive_modifications, self).__exit__(exc_type, exc_val, exc_tb)
queries = self.get_queries()
if queries:
raise Exception("Detected transitive deletion or update in queries: %s" % queries)
_LIVESERVER_TIMEOUT = 5

View File

@ -37,7 +37,7 @@ from data.appr_model.models import NEW_MODELS
from data.database import RepositoryActionCount, Repository as RepositoryTable
from data.logs_model import logs_model
from data.registry_model import registry_model
from test.helpers import assert_action_logged
from test.helpers import assert_action_logged, log_queries, check_transitive_modifications
from util.secscan.fake import fake_security_scanner
from endpoints.api.team import (
@ -2389,45 +2389,6 @@ class TestChangeRepoVisibility(ApiTestCase):
self.assertEquals(False, json["is_public"])
class log_queries(object):
def __init__(self, query_filters=None):
self.filters = query_filters
def get_queries(self):
queries = [q.msg[0] for q in self._handler.queries]
if not self.filters:
return queries
filtered_queries = []
for query_filter in self.filters:
filtered_queries.extend([q for q in queries if re.match(query_filter, q)])
return filtered_queries
def __enter__(self):
logger = logging.getLogger("peewee")
self._handler = _QueryLogHandler()
logger.setLevel(logging.DEBUG)
logger.addHandler(self._handler)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
logger = logging.getLogger("peewee")
logger.removeHandler(self._handler)
class check_transitive_modifications(log_queries):
def __init__(self):
filters = [r"^DELETE.+IN \(SELECT.+$", r"^UPDATE.+IN \(SELECT.+$"]
super(check_transitive_modifications, self).__init__(query_filters=filters)
def __exit__(self, exc_type, exc_val, exc_tb):
super(check_transitive_modifications, self).__exit__(exc_type, exc_val, exc_tb)
queries = self.get_queries()
if queries:
raise Exception("Detected transitive deletion or update in queries: %s" % queries)
class TestDeleteRepository(ApiTestCase):
SIMPLE_REPO = ADMIN_ACCESS_USER + "/simple"
COMPLEX_REPO = ADMIN_ACCESS_USER + "/complex"