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),
|
||||
)
|
||||
|
||||
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):
|
||||
name = CharField(index=True, unique=True)
|
||||
|
@ -441,7 +459,6 @@ class LogEntry(BaseModel):
|
|||
performer = QuayUserField(allows_robots=True, index=True, null=True,
|
||||
related_name='performer')
|
||||
repository = ForeignKeyField(Repository, index=True, null=True)
|
||||
access_token = ForeignKeyField(AccessToken, null=True)
|
||||
datetime = DateTimeField(default=datetime.now, index=True)
|
||||
ip = CharField(null=True)
|
||||
metadata_json = TextField(default='{}')
|
||||
|
|
|
@ -1774,7 +1774,7 @@ def purge_repository(namespace_name, repository_name):
|
|||
|
||||
# Delete the rest of the repository metadata
|
||||
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):
|
||||
|
@ -2004,7 +2004,7 @@ def log_action(kind_name, user_or_organization_name, performer=None,
|
|||
kind = LogEntryKind.get(LogEntryKind.name == kind_name)
|
||||
account = User.get(User.username == user_or_organization_name)
|
||||
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)
|
||||
|
||||
|
||||
|
|
|
@ -1140,6 +1140,8 @@ class TestChangeRepoVisibility(ApiTestCase):
|
|||
|
||||
class TestDeleteRepository(ApiTestCase):
|
||||
SIMPLE_REPO = ADMIN_ACCESS_USER + '/simple'
|
||||
COMPLEX_REPO = ADMIN_ACCESS_USER + '/complex'
|
||||
|
||||
def test_deleterepo(self):
|
||||
self.login(ADMIN_ACCESS_USER)
|
||||
|
||||
|
@ -1151,6 +1153,64 @@ class TestDeleteRepository(ApiTestCase):
|
|||
params=dict(repository=self.SIMPLE_REPO),
|
||||
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):
|
||||
PUBLIC_REPO = PUBLIC_USER + '/publicrepo'
|
||||
|
|
Reference in a new issue