Make repository deletes much faster by adding custom deletion code and have additional tests to verify the deletion code paths
This commit is contained in:
parent
e7cbda86f7
commit
eddcc02ea6
3 changed files with 80 additions and 3 deletions
|
@ -245,6 +245,24 @@ class Repository(BaseModel):
|
||||||
(('namespace_user', 'name'), True),
|
(('namespace_user', 'name'), True),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def delete_instance(self, recursive=False, delete_nullable=False):
|
||||||
|
# Note: peewee generates extra nested deletion statements here that are slow and unnecessary.
|
||||||
|
# Therefore, we define our own deletion order here and use the dependency system to verify it.
|
||||||
|
ordered_dependencies = [RepositoryAuthorizedEmail, RepositoryTag, Image, LogEntry,
|
||||||
|
RepositoryBuild, RepositoryBuildTrigger, RepositoryNotification,
|
||||||
|
RepositoryPermission, AccessToken]
|
||||||
|
|
||||||
|
for query, fk in self.dependencies(search_nullable=True):
|
||||||
|
model = fk.model_class
|
||||||
|
if not model in ordered_dependencies:
|
||||||
|
raise Exception('Missing repository deletion dependency: %s', model)
|
||||||
|
|
||||||
|
for model in ordered_dependencies:
|
||||||
|
model.delete().where(model.repository == self).execute()
|
||||||
|
|
||||||
|
# Delete the repository itself.
|
||||||
|
super(Repository, self).delete_instance(recursive=False, delete_nullable=False)
|
||||||
|
|
||||||
|
|
||||||
class Role(BaseModel):
|
class Role(BaseModel):
|
||||||
name = CharField(index=True, unique=True)
|
name = CharField(index=True, unique=True)
|
||||||
|
@ -441,7 +459,6 @@ class LogEntry(BaseModel):
|
||||||
performer = QuayUserField(allows_robots=True, index=True, null=True,
|
performer = QuayUserField(allows_robots=True, index=True, null=True,
|
||||||
related_name='performer')
|
related_name='performer')
|
||||||
repository = ForeignKeyField(Repository, index=True, null=True)
|
repository = ForeignKeyField(Repository, index=True, null=True)
|
||||||
access_token = ForeignKeyField(AccessToken, null=True)
|
|
||||||
datetime = DateTimeField(default=datetime.now, index=True)
|
datetime = DateTimeField(default=datetime.now, index=True)
|
||||||
ip = CharField(null=True)
|
ip = CharField(null=True)
|
||||||
metadata_json = TextField(default='{}')
|
metadata_json = TextField(default='{}')
|
||||||
|
|
|
@ -1774,7 +1774,7 @@ def purge_repository(namespace_name, repository_name):
|
||||||
|
|
||||||
# Delete the rest of the repository metadata
|
# Delete the rest of the repository metadata
|
||||||
fetched = _get_repository(namespace_name, repository_name)
|
fetched = _get_repository(namespace_name, repository_name)
|
||||||
fetched.delete_instance(recursive=True)
|
fetched.delete_instance(recursive=True, delete_nullable=True)
|
||||||
|
|
||||||
|
|
||||||
def get_private_repo_count(username):
|
def get_private_repo_count(username):
|
||||||
|
@ -2004,7 +2004,7 @@ def log_action(kind_name, user_or_organization_name, performer=None,
|
||||||
kind = LogEntryKind.get(LogEntryKind.name == kind_name)
|
kind = LogEntryKind.get(LogEntryKind.name == kind_name)
|
||||||
account = User.get(User.username == user_or_organization_name)
|
account = User.get(User.username == user_or_organization_name)
|
||||||
LogEntry.create(kind=kind, account=account, performer=performer,
|
LogEntry.create(kind=kind, account=account, performer=performer,
|
||||||
repository=repository, access_token=access_token, ip=ip,
|
repository=repository, ip=ip,
|
||||||
metadata_json=json.dumps(metadata), datetime=timestamp)
|
metadata_json=json.dumps(metadata), datetime=timestamp)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1140,6 +1140,8 @@ class TestChangeRepoVisibility(ApiTestCase):
|
||||||
|
|
||||||
class TestDeleteRepository(ApiTestCase):
|
class TestDeleteRepository(ApiTestCase):
|
||||||
SIMPLE_REPO = ADMIN_ACCESS_USER + '/simple'
|
SIMPLE_REPO = ADMIN_ACCESS_USER + '/simple'
|
||||||
|
COMPLEX_REPO = ADMIN_ACCESS_USER + '/complex'
|
||||||
|
|
||||||
def test_deleterepo(self):
|
def test_deleterepo(self):
|
||||||
self.login(ADMIN_ACCESS_USER)
|
self.login(ADMIN_ACCESS_USER)
|
||||||
|
|
||||||
|
@ -1151,6 +1153,64 @@ class TestDeleteRepository(ApiTestCase):
|
||||||
params=dict(repository=self.SIMPLE_REPO),
|
params=dict(repository=self.SIMPLE_REPO),
|
||||||
expected_code=404)
|
expected_code=404)
|
||||||
|
|
||||||
|
def test_deleterepo2(self):
|
||||||
|
self.login(ADMIN_ACCESS_USER)
|
||||||
|
|
||||||
|
self.deleteResponse(Repository,
|
||||||
|
params=dict(repository=self.COMPLEX_REPO))
|
||||||
|
|
||||||
|
# Verify the repo was deleted.
|
||||||
|
self.getResponse(Repository,
|
||||||
|
params=dict(repository=self.COMPLEX_REPO),
|
||||||
|
expected_code=404)
|
||||||
|
|
||||||
|
def test_populate_and_delete_repo(self):
|
||||||
|
self.login(ADMIN_ACCESS_USER)
|
||||||
|
|
||||||
|
# Make sure the repository has come images and tags.
|
||||||
|
self.assertTrue(len(list(model.get_repository_images(ADMIN_ACCESS_USER, 'complex'))) > 0)
|
||||||
|
self.assertTrue(len(list(model.list_repository_tags(ADMIN_ACCESS_USER, 'complex'))) > 0)
|
||||||
|
|
||||||
|
# Add some data for the repository, in addition to is already existing images and tags.
|
||||||
|
repository = model.get_repository(ADMIN_ACCESS_USER, 'complex')
|
||||||
|
|
||||||
|
# Create some access tokens.
|
||||||
|
access_token = model.create_access_token(repository, 'read')
|
||||||
|
model.create_access_token(repository, 'write')
|
||||||
|
|
||||||
|
delegate_token = model.create_delegate_token(ADMIN_ACCESS_USER, 'complex', 'sometoken', 'read')
|
||||||
|
model.create_delegate_token(ADMIN_ACCESS_USER, 'complex', 'sometoken', 'write')
|
||||||
|
|
||||||
|
# Create some repository builds.
|
||||||
|
model.create_repository_build(repository, access_token, {}, 'someid', 'foobar')
|
||||||
|
model.create_repository_build(repository, delegate_token, {}, 'someid2', 'foobar2')
|
||||||
|
|
||||||
|
# Create some notifications.
|
||||||
|
model.create_repo_notification(repository, 'repo_push', 'hipchat', {})
|
||||||
|
model.create_repo_notification(repository, 'build_queued', 'slack', {})
|
||||||
|
|
||||||
|
# Create some logs.
|
||||||
|
model.log_action('push_repo', ADMIN_ACCESS_USER, repository=repository)
|
||||||
|
model.log_action('push_repo', ADMIN_ACCESS_USER, repository=repository)
|
||||||
|
|
||||||
|
# Create some build triggers.
|
||||||
|
user = model.get_user(ADMIN_ACCESS_USER)
|
||||||
|
model.create_build_trigger(repository, 'github', 'sometoken', user)
|
||||||
|
model.create_build_trigger(repository, 'github', 'anothertoken', user)
|
||||||
|
|
||||||
|
# Create some email authorizations.
|
||||||
|
model.create_email_authorization_for_repo(ADMIN_ACCESS_USER, 'complex', 'a@b.com')
|
||||||
|
model.create_email_authorization_for_repo(ADMIN_ACCESS_USER, 'complex', 'b@c.com')
|
||||||
|
|
||||||
|
# Delete the repository.
|
||||||
|
self.deleteResponse(Repository,
|
||||||
|
params=dict(repository=self.COMPLEX_REPO))
|
||||||
|
|
||||||
|
# Verify the repo was deleted.
|
||||||
|
self.getResponse(Repository,
|
||||||
|
params=dict(repository=self.COMPLEX_REPO),
|
||||||
|
expected_code=404)
|
||||||
|
|
||||||
|
|
||||||
class TestGetRepository(ApiTestCase):
|
class TestGetRepository(ApiTestCase):
|
||||||
PUBLIC_REPO = PUBLIC_USER + '/publicrepo'
|
PUBLIC_REPO = PUBLIC_USER + '/publicrepo'
|
||||||
|
|
Reference in a new issue