Commit 06df19c9 authored by George Koltsov's avatar George Koltsov

Ensure temp export data is removed if Group/Project export failed

- This MR adds temp data removal from shared/tmp/gitlab_exports
  folder when Group/Project export fails
- Currently if something goes wrong temp data is not being cleaned
  up, and starts to pile up over time
- This MR changes that to ensure it's always removed at the end of
  Export Service execution
parent 6f54d919
...@@ -18,6 +18,8 @@ module Groups ...@@ -18,6 +18,8 @@ module Groups
end end
save! save!
ensure
cleanup
end end
private private
...@@ -28,7 +30,7 @@ module Groups ...@@ -28,7 +30,7 @@ module Groups
if savers.all?(&:save) if savers.all?(&:save)
notify_success notify_success
else else
cleanup_and_notify_error! notify_error!
end end
end end
...@@ -44,14 +46,12 @@ module Groups ...@@ -44,14 +46,12 @@ module Groups
Gitlab::ImportExport::Saver.new(exportable: @group, shared: @shared) Gitlab::ImportExport::Saver.new(exportable: @group, shared: @shared)
end end
def cleanup_and_notify_error def cleanup
FileUtils.rm_rf(shared.export_path) FileUtils.rm_rf(shared.archive_path) if shared&.archive_path
notify_error
end end
def cleanup_and_notify_error! def notify_error!
cleanup_and_notify_error notify_error
raise Gitlab::ImportExport::Error.new(shared.errors.to_sentence) raise Gitlab::ImportExport::Error.new(shared.errors.to_sentence)
end end
......
...@@ -14,6 +14,8 @@ module Projects ...@@ -14,6 +14,8 @@ module Projects
save_all! save_all!
execute_after_export_action(after_export_strategy) execute_after_export_action(after_export_strategy)
ensure
cleanup
end end
private private
...@@ -24,7 +26,7 @@ module Projects ...@@ -24,7 +26,7 @@ module Projects
return unless after_export_strategy return unless after_export_strategy
unless after_export_strategy.execute(current_user, project) unless after_export_strategy.execute(current_user, project)
cleanup_and_notify_error notify_error
end end
end end
...@@ -33,7 +35,7 @@ module Projects ...@@ -33,7 +35,7 @@ module Projects
Gitlab::ImportExport::Saver.save(exportable: project, shared: shared) Gitlab::ImportExport::Saver.save(exportable: project, shared: shared)
notify_success notify_success
else else
cleanup_and_notify_error! notify_error!
end end
end end
...@@ -73,16 +75,12 @@ module Projects ...@@ -73,16 +75,12 @@ module Projects
Gitlab::ImportExport::LfsSaver.new(project: project, shared: shared) Gitlab::ImportExport::LfsSaver.new(project: project, shared: shared)
end end
def cleanup_and_notify_error def cleanup
Rails.logger.error("Import/Export - Project #{project.name} with ID: #{project.id} export error - #{shared.errors.join(', ')}") # rubocop:disable Gitlab/RailsLogger FileUtils.rm_rf(shared.archive_path) if shared&.archive_path
FileUtils.rm_rf(shared.export_path)
notify_error
end end
def cleanup_and_notify_error! def notify_error!
cleanup_and_notify_error notify_error
raise Gitlab::ImportExport::Error.new(shared.errors.to_sentence) raise Gitlab::ImportExport::Error.new(shared.errors.to_sentence)
end end
...@@ -92,6 +90,8 @@ module Projects ...@@ -92,6 +90,8 @@ module Projects
end end
def notify_error def notify_error
Rails.logger.error("Import/Export - Project #{project.name} with ID: #{project.id} export error - #{shared.errors.join(', ')}") # rubocop:disable Gitlab/RailsLogger
notification_service.project_not_exported(project, current_user, shared.errors) notification_service.project_not_exported(project, current_user, shared.errors)
end end
end end
......
---
title: Ensure temp export data is removed if Group/Project export failed
merge_request: 25828
author:
type: fixed
...@@ -7,7 +7,7 @@ describe Groups::ImportExport::ExportService do ...@@ -7,7 +7,7 @@ describe Groups::ImportExport::ExportService do
let!(:user) { create(:user) } let!(:user) { create(:user) }
let(:group) { create(:group) } let(:group) { create(:group) }
let(:shared) { Gitlab::ImportExport::Shared.new(group) } let(:shared) { Gitlab::ImportExport::Shared.new(group) }
let(:export_path) { shared.export_path } let(:archive_path) { shared.archive_path }
let(:service) { described_class.new(group: group, user: user, params: { shared: shared }) } let(:service) { described_class.new(group: group, user: user, params: { shared: shared }) }
before do before do
...@@ -15,7 +15,7 @@ describe Groups::ImportExport::ExportService do ...@@ -15,7 +15,7 @@ describe Groups::ImportExport::ExportService do
end end
after do after do
FileUtils.rm_rf(export_path) FileUtils.rm_rf(archive_path)
end end
it 'saves the models' do it 'saves the models' do
...@@ -29,7 +29,7 @@ describe Groups::ImportExport::ExportService do ...@@ -29,7 +29,7 @@ describe Groups::ImportExport::ExportService do
service.execute service.execute
expect(group.import_export_upload.export_file.file).not_to be_nil expect(group.import_export_upload.export_file.file).not_to be_nil
expect(File.directory?(export_path)).to eq(false) expect(File.directory?(archive_path)).to eq(false)
expect(File.exist?(shared.archive_path)).to eq(false) expect(File.exist?(shared.archive_path)).to eq(false)
end end
end end
...@@ -46,25 +46,42 @@ describe Groups::ImportExport::ExportService do ...@@ -46,25 +46,42 @@ describe Groups::ImportExport::ExportService do
end end
end end
context 'when saving services fail' do context 'when export fails' do
before do context 'when file saver fails' do
allow(service).to receive_message_chain(:tree_exporter, :save).and_return(false) it 'removes the remaining exported data' do
allow_next_instance_of(Gitlab::ImportExport::Saver) do |saver|
allow(saver).to receive(:save).and_return(false)
end
expect { service.execute }.to raise_error(Gitlab::ImportExport::Error)
expect(group.import_export_upload).to be_nil
expect(File.exist?(shared.archive_path)).to eq(false)
end
end end
it 'removes the remaining exported data' do context 'when file compression fails' do
allow_any_instance_of(Gitlab::ImportExport::Saver).to receive(:compress_and_save).and_return(false) before do
allow(service).to receive_message_chain(:tree_exporter, :save).and_return(false)
end
expect { service.execute }.to raise_error(Gitlab::ImportExport::Error) it 'removes the remaining exported data' do
allow_next_instance_of(Gitlab::ImportExport::Saver) do |saver|
allow(saver).to receive(:compress_and_save).and_return(false)
end
expect(group.import_export_upload).to be_nil expect { service.execute }.to raise_error(Gitlab::ImportExport::Error)
expect(File.directory?(export_path)).to eq(false)
expect(File.exist?(shared.archive_path)).to eq(false) expect(group.import_export_upload).to be_nil
end expect(File.exist?(shared.archive_path)).to eq(false)
end
it 'notifies logger' do it 'notifies logger' do
expect_any_instance_of(Gitlab::Import::Logger).to receive(:error) allow(service).to receive_message_chain(:tree_exporter, :save).and_return(false)
expect(shared.logger).to receive(:error)
expect { service.execute }.to raise_error(Gitlab::ImportExport::Error) expect { service.execute }.to raise_error(Gitlab::ImportExport::Error)
end
end end
end end
end end
......
...@@ -91,10 +91,10 @@ describe Projects::ImportExport::ExportService do ...@@ -91,10 +91,10 @@ describe Projects::ImportExport::ExportService do
end end
it 'removes the remaining exported data' do it 'removes the remaining exported data' do
allow(shared).to receive(:export_path).and_return('whatever') allow(shared).to receive(:archive_path).and_return('whatever')
allow(FileUtils).to receive(:rm_rf) allow(FileUtils).to receive(:rm_rf)
expect(FileUtils).to receive(:rm_rf).with(shared.export_path) expect(FileUtils).to receive(:rm_rf).with(shared.archive_path)
end end
it 'notifies the user' do it 'notifies the user' do
...@@ -121,10 +121,10 @@ describe Projects::ImportExport::ExportService do ...@@ -121,10 +121,10 @@ describe Projects::ImportExport::ExportService do
end end
it 'removes the remaining exported data' do it 'removes the remaining exported data' do
allow(shared).to receive(:export_path).and_return('whatever') allow(shared).to receive(:archive_path).and_return('whatever')
allow(FileUtils).to receive(:rm_rf) allow(FileUtils).to receive(:rm_rf)
expect(FileUtils).to receive(:rm_rf).with(shared.export_path) expect(FileUtils).to receive(:rm_rf).with(shared.archive_path)
end end
it 'notifies the user' do it 'notifies the user' do
...@@ -142,6 +142,21 @@ describe Projects::ImportExport::ExportService do ...@@ -142,6 +142,21 @@ describe Projects::ImportExport::ExportService do
end end
end end
context 'when one of the savers fail unexpectedly' do
let(:archive_path) { shared.archive_path }
before do
allow(service).to receive_message_chain(:uploads_saver, :save).and_return(false)
end
it 'removes the remaining exported data' do
expect { service.execute }.to raise_error(Gitlab::ImportExport::Error)
expect(project.import_export_upload).to be_nil
expect(File.exist?(shared.archive_path)).to eq(false)
end
end
context 'when user does not have admin_project permission' do context 'when user does not have admin_project permission' do
let!(:another_user) { create(:user) } let!(:another_user) { create(:user) }
......
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