Commit 6e2e3a42 authored by Stan Hu's avatar Stan Hu

Merge branch 'fj-refactor-destroy-project-service' into 'master'

Refactor project destroy services

See merge request gitlab-org/gitlab!22908
parents 09c33735 fcc98762
# frozen_string_literal: true
module Projects
class DestroyRollbackService < BaseService
include Gitlab::ShellAdapter
def execute
return unless project
Projects::ForksCountService.new(project).delete_cache
unless rollback_repository(project.repository)
raise_error(s_('DeleteProject|Failed to restore project repository. Please contact the administrator.'))
end
unless rollback_repository(project.wiki.repository)
raise_error(s_('DeleteProject|Failed to restore wiki repository. Please contact the administrator.'))
end
end
private
def rollback_repository(repository)
return true unless repository
result = Repositories::DestroyRollbackService.new(repository).execute
result[:status] == :success
end
end
end
...@@ -6,9 +6,6 @@ module Projects ...@@ -6,9 +6,6 @@ module Projects
DestroyError = Class.new(StandardError) DestroyError = Class.new(StandardError)
DELETED_FLAG = '+deleted'
REPO_REMOVAL_DELAY = 5.minutes.to_i
def async_execute def async_execute
project.update_attribute(:pending_delete, true) project.update_attribute(:pending_delete, true)
...@@ -18,7 +15,7 @@ module Projects ...@@ -18,7 +15,7 @@ module Projects
schedule_stale_repos_removal schedule_stale_repos_removal
job_id = ProjectDestroyWorker.perform_async(project.id, current_user.id, params) job_id = ProjectDestroyWorker.perform_async(project.id, current_user.id, params)
Rails.logger.info("User #{current_user.id} scheduled destruction of project #{project.full_path} with job ID #{job_id}") # rubocop:disable Gitlab/RailsLogger log_info("User #{current_user.id} scheduled destruction of project #{project.full_path} with job ID #{job_id}")
end end
def execute def execute
...@@ -48,82 +45,34 @@ module Projects ...@@ -48,82 +45,34 @@ module Projects
raise raise
end end
def attempt_repositories_rollback
return unless @project
flush_caches(@project)
unless rollback_repository(removal_path(repo_path), repo_path)
raise_error(s_('DeleteProject|Failed to restore project repository. Please contact the administrator.'))
end
unless rollback_repository(removal_path(wiki_path), wiki_path)
raise_error(s_('DeleteProject|Failed to restore wiki repository. Please contact the administrator.'))
end
end
private private
def repo_path
project.disk_path
end
def wiki_path
project.wiki.disk_path
end
def trash_repositories! def trash_repositories!
unless remove_repository(repo_path) unless remove_repository(project.repository)
raise_error(s_('DeleteProject|Failed to remove project repository. Please try again or contact administrator.')) raise_error(s_('DeleteProject|Failed to remove project repository. Please try again or contact administrator.'))
end end
unless remove_repository(wiki_path) unless remove_repository(project.wiki.repository)
raise_error(s_('DeleteProject|Failed to remove wiki repository. Please try again or contact administrator.')) raise_error(s_('DeleteProject|Failed to remove wiki repository. Please try again or contact administrator.'))
end end
end end
def remove_repository(path) def remove_repository(repository)
# There is a possibility project does not have repository or wiki return true unless repository
return true unless repo_exists?(path)
new_path = removal_path(path) result = Repositories::DestroyService.new(repository).execute
if mv_repository(path, new_path) result[:status] == :success
log_info(%Q{Repository "#{path}" moved to "#{new_path}" for project "#{project.full_path}"})
project.run_after_commit do
GitlabShellWorker.perform_in(REPO_REMOVAL_DELAY, :remove_repository, self.repository_storage, new_path)
end
else
false
end
end end
def schedule_stale_repos_removal def schedule_stale_repos_removal
repo_paths = [removal_path(repo_path), removal_path(wiki_path)] repos = [project.repository, project.wiki.repository]
# Ideally it should wait until the regular removal phase finishes, repos.each do |repository|
# so let's delay it a bit further. next unless repository
repo_paths.each do |path|
GitlabShellWorker.perform_in(REPO_REMOVAL_DELAY * 2, :remove_repository, project.repository_storage, path)
end
end
def rollback_repository(old_path, new_path)
# There is a possibility project does not have repository or wiki
return true unless repo_exists?(old_path)
mv_repository(old_path, new_path)
end
def repo_exists?(path) Repositories::ShellDestroyService.new(repository).execute(Repositories::ShellDestroyService::STALE_REMOVAL_DELAY)
gitlab_shell.repository_exists?(project.repository_storage, path + '.git')
end end
def mv_repository(from_path, to_path)
return true unless repo_exists?(from_path)
gitlab_shell.mv_repository(project.repository_storage, from_path, to_path)
end end
def attempt_rollback(project, message) def attempt_rollback(project, message)
...@@ -191,32 +140,9 @@ module Projects ...@@ -191,32 +140,9 @@ module Projects
raise DestroyError.new(message) raise DestroyError.new(message)
end end
# Build a path for removing repositories
# We use `+` because its not allowed by GitLab so user can not create
# project with name cookies+119+deleted and capture someone stalled repository
#
# gitlab/cookies.git -> gitlab/cookies+119+deleted.git
#
def removal_path(path)
"#{path}+#{project.id}#{DELETED_FLAG}"
end
def flush_caches(project) def flush_caches(project)
ignore_git_errors(repo_path) { project.repository.before_delete }
ignore_git_errors(wiki_path) { Repository.new(wiki_path, project, disk_path: repo_path).before_delete }
Projects::ForksCountService.new(project).delete_cache Projects::ForksCountService.new(project).delete_cache
end end
# If we get a Gitaly error, the repository may be corrupted. We can
# ignore these errors since we're going to trash the repositories
# anyway.
def ignore_git_errors(disk_path, &block)
yield
rescue Gitlab::Git::CommandError => e
Gitlab::GitLogger.warn(class: self.class.name, project_id: project.id, disk_path: disk_path, message: e.to_s)
end
end end
end end
......
...@@ -55,7 +55,7 @@ module Projects ...@@ -55,7 +55,7 @@ module Projects
end end
def attempt_restore_repositories(project) def attempt_restore_repositories(project)
::Projects::DestroyService.new(project, @current_user).attempt_repositories_rollback ::Projects::DestroyRollbackService.new(project, @current_user).execute
end end
def add_source_project_to_fork_network(source_project) def add_source_project_to_fork_network(source_project)
......
# frozen_string_literal: true
class Repositories::BaseService < BaseService
include Gitlab::ShellAdapter
DELETED_FLAG = '+deleted'
attr_reader :repository
delegate :project, :disk_path, :full_path, to: :repository
delegate :repository_storage, to: :project
def initialize(repository)
@repository = repository
end
def repo_exists?(path)
gitlab_shell.repository_exists?(repository_storage, path + '.git')
end
def mv_repository(from_path, to_path)
return true unless repo_exists?(from_path)
gitlab_shell.mv_repository(repository_storage, from_path, to_path)
end
# Build a path for removing repositories
# We use `+` because its not allowed by GitLab so user can not create
# project with name cookies+119+deleted and capture someone stalled repository
#
# gitlab/cookies.git -> gitlab/cookies+119+deleted.git
#
def removal_path
"#{disk_path}+#{project.id}#{DELETED_FLAG}"
end
# If we get a Gitaly error, the repository may be corrupted. We can
# ignore these errors since we're going to trash the repositories
# anyway.
def ignore_git_errors(&block)
yield
rescue Gitlab::Git::CommandError => e
Gitlab::GitLogger.warn(class: self.class.name, project_id: project.id, disk_path: disk_path, message: e.to_s)
end
def move_error(path)
error = %Q{Repository "#{path}" could not be moved}
log_error(error)
error(error)
end
end
# frozen_string_literal: true
class Repositories::DestroyRollbackService < Repositories::BaseService
def execute
# There is a possibility project does not have repository or wiki
return success unless repo_exists?(removal_path)
# Flush the cache for both repositories.
ignore_git_errors { repository.before_delete }
if mv_repository(removal_path, disk_path)
log_info(%Q{Repository "#{removal_path}" moved to "#{disk_path}" for repository "#{full_path}"})
success
else
move_error(removal_path)
end
end
end
# frozen_string_literal: true
class Repositories::DestroyService < Repositories::BaseService
def execute
return success unless repository
return success unless repo_exists?(disk_path)
# Flush the cache for both repositories. This has to be done _before_
# removing the physical repositories as some expiration code depends on
# Git data (e.g. a list of branch names).
ignore_git_errors { repository.before_delete }
if mv_repository(disk_path, removal_path)
log_info(%Q{Repository "#{disk_path}" moved to "#{removal_path}" for repository "#{full_path}"})
current_repository = repository
project.run_after_commit do
Repositories::ShellDestroyService.new(current_repository).execute
end
log_info("Project \"#{project.full_path}\" was removed")
success
else
move_error(disk_path)
end
end
end
# frozen_string_literal: true
class Repositories::ShellDestroyService < Repositories::BaseService
REPO_REMOVAL_DELAY = 5.minutes.to_i
STALE_REMOVAL_DELAY = REPO_REMOVAL_DELAY * 2
def execute(delay = REPO_REMOVAL_DELAY)
return success unless repository
GitlabShellWorker.perform_in(delay,
:remove_repository,
repository_storage,
removal_path)
end
end
...@@ -34,8 +34,8 @@ module EE ...@@ -34,8 +34,8 @@ module EE
def log_geo_event(project) def log_geo_event(project)
::Geo::RepositoryDeletedEventStore.new( ::Geo::RepositoryDeletedEventStore.new(
project, project,
repo_path: repo_path, repo_path: project.disk_path,
wiki_path: wiki_path wiki_path: project.wiki.disk_path
).create! ).create!
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe Projects::DestroyRollbackService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, namespace: user.namespace) }
let(:repository) { project.repository }
let(:repository_storage) { project.repository_storage }
subject { described_class.new(project, user, {}).execute }
describe '#execute' do
let(:path) { repository.disk_path + '.git' }
let(:removal_path) { "#{repository.disk_path}+#{project.id}#{Repositories::DestroyService::DELETED_FLAG}.git" }
before do
aggregate_failures do
expect(TestEnv.storage_dir_exists?(repository_storage, path)).to be_truthy
expect(TestEnv.storage_dir_exists?(repository_storage, removal_path)).to be_falsey
end
# Don't run sidekiq to check if renamed repository exists
Sidekiq::Testing.fake! { destroy_project(project, user, {}) }
aggregate_failures do
expect(TestEnv.storage_dir_exists?(repository_storage, path)).to be_falsey
expect(TestEnv.storage_dir_exists?(repository_storage, removal_path)).to be_truthy
end
end
it 'restores the repositories' do
Sidekiq::Testing.fake! { subject }
aggregate_failures do
expect(TestEnv.storage_dir_exists?(repository_storage, path)).to be_truthy
expect(TestEnv.storage_dir_exists?(repository_storage, removal_path)).to be_falsey
end
end
end
def destroy_project(project, user, params = {})
Projects::DestroyService.new(project, user, params).execute
end
end
...@@ -5,15 +5,11 @@ require 'spec_helper' ...@@ -5,15 +5,11 @@ require 'spec_helper'
describe Projects::DestroyService do describe Projects::DestroyService do
include ProjectForksHelper include ProjectForksHelper
let!(:user) { create(:user) } let_it_be(:user) { create(:user) }
let!(:project) { create(:project, :repository, namespace: user.namespace) } let!(:project) { create(:project, :repository, namespace: user.namespace) }
let!(:path) do let(:path) { project.repository.disk_path }
Gitlab::GitalyClient::StorageSettings.allow_disk_access do let(:remove_path) { removal_path(path) }
project.repository.path_to_repo let(:async) { false } # execute or async_execute
end
end
let!(:remove_path) { path.sub(/\.git\Z/, "+#{project.id}+deleted.git") }
let!(:async) { false } # execute or async_execute
before do before do
stub_container_registry_config(enabled: true) stub_container_registry_config(enabled: true)
...@@ -21,7 +17,12 @@ describe Projects::DestroyService do ...@@ -21,7 +17,12 @@ describe Projects::DestroyService do
end end
shared_examples 'deleting the project' do shared_examples 'deleting the project' do
it 'deletes the project' do before do
# Run sidekiq immediately to check that renamed repository will be removed
destroy_project(project, user, {})
end
it 'deletes the project', :sidekiq_inline do
expect(Project.unscoped.all).not_to include(project) expect(Project.unscoped.all).not_to include(project)
expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_falsey expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_falsey
...@@ -30,16 +31,10 @@ describe Projects::DestroyService do ...@@ -30,16 +31,10 @@ describe Projects::DestroyService do
end end
shared_examples 'deleting the project with pipeline and build' do shared_examples 'deleting the project with pipeline and build' do
context 'with pipeline and build' do # which has optimistic locking context 'with pipeline and build', :sidekiq_inline do # which has optimistic locking
let!(:pipeline) { create(:ci_pipeline, project: project) } let!(:pipeline) { create(:ci_pipeline, project: project) }
let!(:build) { create(:ci_build, :artifacts, pipeline: pipeline) } let!(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
before do
perform_enqueued_jobs do
destroy_project(project, user, {})
end
end
it_behaves_like 'deleting the project' it_behaves_like 'deleting the project'
end end
end end
...@@ -47,49 +42,46 @@ describe Projects::DestroyService do ...@@ -47,49 +42,46 @@ describe Projects::DestroyService do
shared_examples 'handles errors thrown during async destroy' do |error_message| shared_examples 'handles errors thrown during async destroy' do |error_message|
it 'does not allow the error to bubble up' do it 'does not allow the error to bubble up' do
expect do expect do
perform_enqueued_jobs { destroy_project(project, user, {}) } destroy_project(project, user, {})
end.not_to raise_error end.not_to raise_error
end end
it 'unmarks the project as "pending deletion"' do it 'unmarks the project as "pending deletion"' do
perform_enqueued_jobs { destroy_project(project, user, {}) } destroy_project(project, user, {})
expect(project.reload.pending_delete).to be(false) expect(project.reload.pending_delete).to be(false)
end end
it 'stores an error message in `projects.delete_error`' do it 'stores an error message in `projects.delete_error`' do
perform_enqueued_jobs { destroy_project(project, user, {}) } destroy_project(project, user, {})
expect(project.reload.delete_error).to be_present expect(project.reload.delete_error).to be_present
expect(project.delete_error).to include(error_message) expect(project.delete_error).to include(error_message)
end end
end end
context 'Sidekiq inline' do
before do
# Run sidekiq immediately to check that renamed repository will be removed
perform_enqueued_jobs { destroy_project(project, user, {}) }
end
it_behaves_like 'deleting the project' it_behaves_like 'deleting the project'
context 'when has remote mirrors' do it 'invalidates personal_project_count cache' do
expect(user).to receive(:invalidate_personal_projects_count)
destroy_project(project, user, {})
end
context 'when project has remote mirrors' do
let!(:project) do let!(:project) do
create(:project, :repository, namespace: user.namespace).tap do |project| create(:project, :repository, namespace: user.namespace).tap do |project|
project.remote_mirrors.create(url: 'http://test.com') project.remote_mirrors.create(url: 'http://test.com')
end end
end end
let!(:async) { true }
it 'destroys them', :sidekiq_might_not_need_inline do it 'destroys them' do
expect(RemoteMirror.count).to eq(0) expect(RemoteMirror.count).to eq(1)
end
end
it 'invalidates personal_project_count cache' do destroy_project(project, user, {})
expect(user).to receive(:invalidate_personal_projects_count)
destroy_project(project, user) expect(RemoteMirror.count).to eq(0)
end
end end
context 'when project has exports' do context 'when project has exports' do
...@@ -100,15 +92,15 @@ describe Projects::DestroyService do ...@@ -100,15 +92,15 @@ describe Projects::DestroyService do
export_file: fixture_file_upload('spec/fixtures/project_export.tar.gz')) export_file: fixture_file_upload('spec/fixtures/project_export.tar.gz'))
end end
end end
let!(:async) { true }
it 'destroys project and export', :sidekiq_might_not_need_inline do it 'destroys project and export' do
expect { destroy_project(project_with_export, user) }.to change(ImportExportUpload, :count).by(-1) expect do
destroy_project(project_with_export, user, {})
end.to change(ImportExportUpload, :count).by(-1)
expect(Project.all).not_to include(project_with_export) expect(Project.all).not_to include(project_with_export)
end end
end end
end
context 'Sidekiq fake' do context 'Sidekiq fake' do
before do before do
...@@ -117,20 +109,24 @@ describe Projects::DestroyService do ...@@ -117,20 +109,24 @@ describe Projects::DestroyService do
end end
it { expect(Project.all).not_to include(project) } it { expect(Project.all).not_to include(project) }
it { expect(Dir.exist?(path)).to be_falsey }
it { expect(Dir.exist?(remove_path)).to be_truthy } it do
expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_falsey
end
it do
expect(project.gitlab_shell.repository_exists?(project.repository_storage, remove_path + '.git')).to be_truthy
end
end end
context 'when flushing caches fail due to Git errors' do context 'when flushing caches fail due to Git errors' do
before do before do
allow(project.repository).to receive(:before_delete).and_raise(::Gitlab::Git::CommandError) allow(project.repository).to receive(:before_delete).and_raise(::Gitlab::Git::CommandError)
allow(Gitlab::GitLogger).to receive(:warn).with( allow(Gitlab::GitLogger).to receive(:warn).with(
class: described_class.name, class: Repositories::DestroyService.name,
project_id: project.id, project_id: project.id,
disk_path: project.disk_path, disk_path: project.disk_path,
message: 'Gitlab::Git::CommandError').and_call_original message: 'Gitlab::Git::CommandError').and_call_original
perform_enqueued_jobs { destroy_project(project, user, {}) }
end end
it_behaves_like 'deleting the project' it_behaves_like 'deleting the project'
...@@ -153,14 +149,12 @@ describe Projects::DestroyService do ...@@ -153,14 +149,12 @@ describe Projects::DestroyService do
end end
end end
context 'with async_execute', :sidekiq_might_not_need_inline do context 'with async_execute', :sidekiq_inline do
let(:async) { true } let(:async) { true }
context 'async delete of project with private issue visibility' do context 'async delete of project with private issue visibility' do
before do before do
project.project_feature.update_attribute("issues_access_level", ProjectFeature::PRIVATE) project.project_feature.update_attribute("issues_access_level", ProjectFeature::PRIVATE)
# Run sidekiq immediately to check that renamed repository will be removed
perform_enqueued_jobs { destroy_project(project, user, {}) }
end end
it_behaves_like 'deleting the project' it_behaves_like 'deleting the project'
...@@ -204,7 +198,7 @@ describe Projects::DestroyService do ...@@ -204,7 +198,7 @@ describe Projects::DestroyService do
it 'allows error to bubble up and rolls back project deletion' do it 'allows error to bubble up and rolls back project deletion' do
expect do expect do
perform_enqueued_jobs { destroy_project(project, user, {}) } destroy_project(project, user, {})
end.to raise_error(Exception, 'Other error message') end.to raise_error(Exception, 'Other error message')
expect(project.reload.pending_delete).to be(false) expect(project.reload.pending_delete).to be(false)
...@@ -312,15 +306,12 @@ describe Projects::DestroyService do ...@@ -312,15 +306,12 @@ describe Projects::DestroyService do
end end
context 'repository +deleted path removal' do context 'repository +deleted path removal' do
def removal_path(path)
"#{path}+#{project.id}#{described_class::DELETED_FLAG}"
end
context 'regular phase' do context 'regular phase' do
it 'schedules +deleted removal of existing repos' do it 'schedules +deleted removal of existing repos' do
service = described_class.new(project, user, {}) service = described_class.new(project, user, {})
allow(service).to receive(:schedule_stale_repos_removal) allow(service).to receive(:schedule_stale_repos_removal)
expect(Repositories::ShellDestroyService).to receive(:new).and_call_original
expect(GitlabShellWorker).to receive(:perform_in) expect(GitlabShellWorker).to receive(:perform_in)
.with(5.minutes, :remove_repository, project.repository_storage, removal_path(project.disk_path)) .with(5.minutes, :remove_repository, project.repository_storage, removal_path(project.disk_path))
...@@ -329,14 +320,16 @@ describe Projects::DestroyService do ...@@ -329,14 +320,16 @@ describe Projects::DestroyService do
end end
context 'stale cleanup' do context 'stale cleanup' do
let!(:async) { true } let(:async) { true }
it 'schedules +deleted wiki and repo removal' do it 'schedules +deleted wiki and repo removal' do
allow(ProjectDestroyWorker).to receive(:perform_async) allow(ProjectDestroyWorker).to receive(:perform_async)
expect(Repositories::ShellDestroyService).to receive(:new).with(project.repository).and_call_original
expect(GitlabShellWorker).to receive(:perform_in) expect(GitlabShellWorker).to receive(:perform_in)
.with(10.minutes, :remove_repository, project.repository_storage, removal_path(project.disk_path)) .with(10.minutes, :remove_repository, project.repository_storage, removal_path(project.disk_path))
expect(Repositories::ShellDestroyService).to receive(:new).with(project.wiki.repository).and_call_original
expect(GitlabShellWorker).to receive(:perform_in) expect(GitlabShellWorker).to receive(:perform_in)
.with(10.minutes, :remove_repository, project.repository_storage, removal_path(project.wiki.disk_path)) .with(10.minutes, :remove_repository, project.repository_storage, removal_path(project.wiki.disk_path))
...@@ -345,33 +338,11 @@ describe Projects::DestroyService do ...@@ -345,33 +338,11 @@ describe Projects::DestroyService do
end end
end end
context '#attempt_restore_repositories' do
let(:path) { project.disk_path + '.git' }
before do
expect(TestEnv.storage_dir_exists?(project.repository_storage, path)).to be_truthy
expect(TestEnv.storage_dir_exists?(project.repository_storage, remove_path)).to be_falsey
# Dont run sidekiq to check if renamed repository exists
Sidekiq::Testing.fake! { destroy_project(project, user, {}) }
expect(TestEnv.storage_dir_exists?(project.repository_storage, path)).to be_falsey
expect(TestEnv.storage_dir_exists?(project.repository_storage, remove_path)).to be_truthy
end
it 'restores the repositories' do
Sidekiq::Testing.fake! { described_class.new(project, user).attempt_repositories_rollback }
expect(TestEnv.storage_dir_exists?(project.repository_storage, path)).to be_truthy
expect(TestEnv.storage_dir_exists?(project.repository_storage, remove_path)).to be_falsey
end
end
def destroy_project(project, user, params = {}) def destroy_project(project, user, params = {})
if async described_class.new(project, user, params).public_send(async ? :async_execute : :execute)
Projects::DestroyService.new(project, user, params).async_execute
else
Projects::DestroyService.new(project, user, params).execute
end end
def removal_path(path)
"#{path}+#{project.id}#{Repositories::DestroyService::DELETED_FLAG}"
end end
end end
# frozen_string_literal: true
require 'spec_helper'
describe Repositories::DestroyRollbackService do
let_it_be(:user) { create(:user) }
let!(:project) { create(:project, :repository, namespace: user.namespace) }
let(:repository) { project.repository }
let(:path) { repository.disk_path }
let(:remove_path) { "#{path}+#{project.id}#{described_class::DELETED_FLAG}" }
subject { described_class.new(repository).execute }
before do
# Dont run sidekiq to check if renamed repository exists
Sidekiq::Testing.fake! { destroy_project(project, user) }
end
it 'moves the repository from the +deleted folder' do
expect(project.gitlab_shell.repository_exists?(project.repository_storage, remove_path + '.git')).to be_truthy
expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_falsey
subject
expect(project.gitlab_shell.repository_exists?(project.repository_storage, remove_path + '.git')).to be_falsey
expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_truthy
end
it 'logs the successful action' do
expect(Gitlab::AppLogger).to receive(:info)
subject
end
it 'flushes the repository cache' do
expect(repository).to receive(:before_delete)
subject
end
it 'returns success and does not perform any action if repository path does not exist' do
expect(repository).to receive(:disk_path).and_return('foo')
expect(repository).not_to receive(:before_delete)
result = subject
expect(result[:status]).to eq :success
end
context 'when move operation cannot be performed' do
let(:service) { described_class.new(repository) }
before do
allow(service).to receive(:mv_repository).and_return(false)
end
it 'returns error' do
result = service.execute
expect(result[:status]).to eq :error
end
it 'logs the error' do
expect(Gitlab::AppLogger).to receive(:error)
service.execute
end
end
def destroy_project(project, user)
Projects::DestroyService.new(project, user, {}).execute
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Repositories::DestroyService do
let_it_be(:user) { create(:user) }
let!(:project) { create(:project, :repository, namespace: user.namespace) }
let(:repository) { project.repository }
let(:path) { repository.disk_path }
let(:remove_path) { "#{path}+#{project.id}#{described_class::DELETED_FLAG}" }
subject { described_class.new(project.repository).execute }
it 'moves the repository to a +deleted folder' do
expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_truthy
expect(project.gitlab_shell.repository_exists?(project.repository_storage, remove_path + '.git')).to be_falsey
subject
expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_falsey
expect(project.gitlab_shell.repository_exists?(project.repository_storage, remove_path + '.git')).to be_truthy
end
it 'schedules the repository deletion' do
subject
expect(Repositories::ShellDestroyService).to receive(:new).with(repository).and_call_original
expect(GitlabShellWorker).to receive(:perform_in)
.with(Repositories::ShellDestroyService::REPO_REMOVAL_DELAY, :remove_repository, project.repository_storage, remove_path)
# Because GitlabShellWorker is inside a run_after_commit callback we need to
# trigger the callback
project.touch
end
it 'removes the repository', :sidekiq_inline do
subject
project.touch
expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_falsey
expect(project.gitlab_shell.repository_exists?(project.repository_storage, remove_path + '.git')).to be_falsey
end
it 'flushes the repository cache' do
expect(repository).to receive(:before_delete)
subject
end
it 'does not perform any action if repository path does not exist and returns success' do
expect(repository).to receive(:disk_path).and_return('foo')
expect(repository).not_to receive(:before_delete)
result = subject
expect(result[:status]).to eq :success
end
context 'when move operation cannot be performed' do
let(:service) { described_class.new(repository) }
before do
allow(service).to receive(:mv_repository).and_return(false)
end
it 'returns error' do
result = service.execute
expect(result[:status]).to eq :error
end
it 'logs the error' do
expect(Gitlab::AppLogger).to receive(:error)
service.execute
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Repositories::ShellDestroyService do
let_it_be(:user) { create(:user) }
let!(:project) { create(:project, :repository, namespace: user.namespace) }
let(:path) { project.repository.disk_path }
let(:remove_path) { "#{path}+#{project.id}#{described_class::DELETED_FLAG}" }
it 'returns success if the repository is nil' do
expect(GitlabShellWorker).not_to receive(:perform_in)
result = described_class.new(nil).execute
expect(result[:status]).to eq :success
end
it 'schedules the repository deletion' do
expect(GitlabShellWorker).to receive(:perform_in)
.with(described_class::REPO_REMOVAL_DELAY, :remove_repository, project.repository_storage, remove_path)
described_class.new(project.repository).execute
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment