summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorToon Claes <toon@iotcl.com>2017-08-16 14:46:26 +0200
committerToon Claes <toon@iotcl.com>2017-08-22 14:29:54 +0200
commit2074f39a47645b5ea453adfba84247f2fcc4f3c7 (patch)
tree3f2aab56177cf0704faa8d739ce61acc7055a99e
parentce89c425fe51d2317322350bbd8a364c08d97d21 (diff)
downloadgitlab-ce-2074f39a47645b5ea453adfba84247f2fcc4f3c7.tar.gz
Migration to remove pending delete projects with non-existing namespace
There might be some projects where the namespace was removed, but the project wasn't. For these the projects still have a `namespace_id` set. So this adds a post-deploy migration remove all projects that are pending delete, and have a `namespace_id` that is non-existing.
-rw-r--r--app/workers/namespaceless_project_destroy_worker.rb7
-rw-r--r--changelogs/unreleased/tc-remove-nonexisting-namespace-pending-delete-projects.yml5
-rw-r--r--db/post_migrate/20170816102555_cleanup_nonexisting_namespace_pending_delete_projects.rb54
-rw-r--r--spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb32
-rw-r--r--spec/workers/namespaceless_project_destroy_worker_spec.rb14
5 files changed, 111 insertions, 1 deletions
diff --git a/app/workers/namespaceless_project_destroy_worker.rb b/app/workers/namespaceless_project_destroy_worker.rb
index a9073742ff7..b148e7082b3 100644
--- a/app/workers/namespaceless_project_destroy_worker.rb
+++ b/app/workers/namespaceless_project_destroy_worker.rb
@@ -18,7 +18,8 @@ class NamespacelessProjectDestroyWorker
rescue ActiveRecord::RecordNotFound
return
end
- return unless project.namespace_id.nil? # Reject doing anything for projects that *do* have a namespace
+
+ return if namespace?(project) # Reject doing anything for projects that *do* have a namespace
project.team.truncate
@@ -29,6 +30,10 @@ class NamespacelessProjectDestroyWorker
private
+ def namespace?(project)
+ project.namespace_id && Namespace.exists?(project.namespace_id)
+ end
+
def unlink_fork(project)
merge_requests = project.forked_from_project.merge_requests.opened.from_project(project)
diff --git a/changelogs/unreleased/tc-remove-nonexisting-namespace-pending-delete-projects.yml b/changelogs/unreleased/tc-remove-nonexisting-namespace-pending-delete-projects.yml
new file mode 100644
index 00000000000..218336df5d2
--- /dev/null
+++ b/changelogs/unreleased/tc-remove-nonexisting-namespace-pending-delete-projects.yml
@@ -0,0 +1,5 @@
+---
+title: Migration to remove pending delete projects with non-existing namespace
+merge_request: 13598
+author:
+type: other
diff --git a/db/post_migrate/20170816102555_cleanup_nonexisting_namespace_pending_delete_projects.rb b/db/post_migrate/20170816102555_cleanup_nonexisting_namespace_pending_delete_projects.rb
new file mode 100644
index 00000000000..fe88f25827f
--- /dev/null
+++ b/db/post_migrate/20170816102555_cleanup_nonexisting_namespace_pending_delete_projects.rb
@@ -0,0 +1,54 @@
+# Follow up of CleanupNamespacelessPendingDeleteProjects and it cleans
+# all projects with `pending_delete = true` and for which the
+# namespace no longer exists.
+class CleanupNonexistingNamespacePendingDeleteProjects < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ @offset = 0
+
+ loop do
+ ids = pending_delete_batch
+
+ break if ids.empty?
+
+ args = ids.map { |id| Array(id) }
+
+ NamespacelessProjectDestroyWorker.bulk_perform_async(args)
+
+ @offset += 1
+ end
+ end
+
+ def down
+ # noop
+ end
+
+ private
+
+ def pending_delete_batch
+ connection.exec_query(find_batch).map { |row| row['id'].to_i }
+ end
+
+ BATCH_SIZE = 5000
+
+ def find_batch
+ projects = Project.arel_table
+ namespaces = Namespace.arel_table
+
+ namespace_query = namespaces.project(1)
+ .where(namespaces[:id].eq(projects[:namespace_id]))
+ .exists.not
+
+ projects.project(projects[:id])
+ .where(projects[:pending_delete].eq(true))
+ .where(namespace_query)
+ .skip(@offset * BATCH_SIZE)
+ .take(BATCH_SIZE)
+ .to_sql
+ end
+end
diff --git a/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb b/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb
new file mode 100644
index 00000000000..7879105a334
--- /dev/null
+++ b/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170816102555_cleanup_nonexisting_namespace_pending_delete_projects.rb')
+
+describe CleanupNonexistingNamespacePendingDeleteProjects do
+ before do
+ # Stub after_save callbacks that will fail when Project has invalid namespace
+ allow_any_instance_of(Project).to receive(:ensure_storage_path_exist).and_return(nil)
+ allow_any_instance_of(Project).to receive(:update_project_statistics).and_return(nil)
+ end
+
+ describe '#up' do
+ set(:some_project) { create(:project) }
+
+ it 'only cleans up when namespace does not exist' do
+ create(:project, pending_delete: true)
+ project = build(:project, pending_delete: true, namespace: nil, namespace_id: Namespace.maximum(:id).to_i.succ)
+ project.save(validate: false)
+
+ expect(NamespacelessProjectDestroyWorker).to receive(:bulk_perform_async).with([[project.id]])
+
+ described_class.new.up
+ end
+
+ it 'does nothing when no pending delete projects without namespace found' do
+ create(:project, pending_delete: true, namespace: create(:namespace))
+
+ expect(NamespacelessProjectDestroyWorker).not_to receive(:bulk_perform_async)
+
+ described_class.new.up
+ end
+ end
+end
diff --git a/spec/workers/namespaceless_project_destroy_worker_spec.rb b/spec/workers/namespaceless_project_destroy_worker_spec.rb
index 817e103fd9a..20cf580af8a 100644
--- a/spec/workers/namespaceless_project_destroy_worker_spec.rb
+++ b/spec/workers/namespaceless_project_destroy_worker_spec.rb
@@ -75,5 +75,19 @@ describe NamespacelessProjectDestroyWorker do
end
end
end
+
+ context 'project has non-existing namespace' do
+ let!(:project) do
+ project = build(:project, namespace_id: Namespace.maximum(:id).to_i.succ)
+ project.save(validate: false)
+ project
+ end
+
+ it 'deletes the project' do
+ subject.perform(project.id)
+
+ expect(Project.unscoped.all).not_to include(project)
+ end
+ end
end
end