Fix bug in deletion of repos with OCI-style linked tags

MySQL does not allow rows in the same table referencing other rows to be deleted in a single statement. We now do a two-pass deletion, and add a test to make sure.

Fixes https://jira.prod.coreos.systems/browse/QS-18
This commit is contained in:
Joseph Schorr 2017-10-18 17:03:27 -04:00
parent 6b217b497a
commit 9f804de23d
2 changed files with 14 additions and 2 deletions

View File

@ -10,7 +10,7 @@ from cachetools import ttl_cache
from data.model import (
config, DataModelException, tag, db_transaction, storage, permission, _basequery)
from data.database import (
Repository, Namespace, RepositoryTag, Star, Image, ImageStorage, User, Visibility,
Repository, Namespace, RepositoryTag, Star, Image, ImageStorage, User, Visibility, Tag,
RepositoryPermission, RepositoryActionCount, Role, RepositoryAuthorizedEmail, TagManifest,
DerivedStorageForImage, Label, TagManifestLabel, db_for_update, get_epoch_timestamp,
db_random_func, db_concat_func, RepositorySearchScore)
@ -81,6 +81,12 @@ def purge_repository(namespace_name, repository_name):
except Repository.DoesNotExist:
return False
# Delete the repository of all OCI-referenced entries.
# Note that new-model Tag's must be deleted in *two* passes, as they can reference parent tags,
# and MySQL is... particular... about such relationships when deleting.
Tag.delete().where(Tag.repository == repo, ~(Tag.linked_tag >> None)).execute()
Tag.delete().where(Tag.repository == repo).execute()
# Delete all tags to allow gc to reclaim storage
previously_referenced = tag.purge_all_tags(repo)
unreferenced_image_q = Image.select(Image.id).where(Image.repository == repo)

View File

@ -26,7 +26,7 @@ from endpoints.webhooks import webhooks
from app import app, config_provider, all_queues, dockerfile_build_queue, notification_queue
from buildtrigger.basehandler import BuildTriggerHandler
from initdb import setup_database_for_testing, finished_database_for_testing
from data import database, model
from data import database, model, oci_model
from data.database import RepositoryActionCount, Repository as RepositoryTable
from test.helpers import assert_action_logged
from util.secscan.fake import fake_security_scanner
@ -2148,6 +2148,12 @@ class TestDeleteRepository(ApiTestCase):
# Add some data for the repository, in addition to is already existing images and tags.
repository = model.repository.get_repository(ADMIN_ACCESS_USER, 'complex')
# Add some new-style tags and linked tags.
base_tag = oci_model.tag.create_or_update_tag(repository, 'somebasetag')
base_tag2 = oci_model.tag.create_or_update_tag(repository, 'somebasetag2')
oci_model.tag.create_or_update_tag(repository, 'somelinkedtag', linked_tag=base_tag)
oci_model.tag.create_or_update_tag(repository, 'somelinkedtag2', linked_tag=base_tag2)
# Create some access tokens.
access_token = model.token.create_access_token(repository, 'read')
model.token.create_access_token(repository, 'write')