Commit d596b260 authored by Nick Thomas's avatar Nick Thomas

Merge branch 'storage_move_cleanup' into 'master'

Remove repositories from previous storage when storage move succeeds

See merge request gitlab-org/gitlab!38547
parents dfcda61a 9b761ebb
...@@ -29,12 +29,17 @@ class ProjectRepositoryStorageMove < ApplicationRecord ...@@ -29,12 +29,17 @@ class ProjectRepositoryStorageMove < ApplicationRecord
transition scheduled: :started transition scheduled: :started
end end
event :finish do event :finish_replication do
transition started: :finished transition started: :replicated
end
event :finish_cleanup do
transition replicated: :finished
end end
event :do_fail do event :do_fail do
transition [:initial, :scheduled, :started] => :failed transition [:initial, :scheduled, :started] => :failed
transition replicated: :cleanup_failed
end end
after_transition initial: :scheduled do |storage_move| after_transition initial: :scheduled do |storage_move|
...@@ -49,7 +54,7 @@ class ProjectRepositoryStorageMove < ApplicationRecord ...@@ -49,7 +54,7 @@ class ProjectRepositoryStorageMove < ApplicationRecord
end end
end end
after_transition started: :finished do |storage_move| after_transition started: :replicated do |storage_move|
storage_move.project.update_columns( storage_move.project.update_columns(
repository_read_only: false, repository_read_only: false,
repository_storage: storage_move.destination_storage_name repository_storage: storage_move.destination_storage_name
...@@ -65,6 +70,8 @@ class ProjectRepositoryStorageMove < ApplicationRecord ...@@ -65,6 +70,8 @@ class ProjectRepositoryStorageMove < ApplicationRecord
state :started, value: 3 state :started, value: 3
state :finished, value: 4 state :finished, value: 4
state :failed, value: 5 state :failed, value: 5
state :replicated, value: 6
state :cleanup_failed, value: 7
end end
scope :order_created_at_desc, -> { order(created_at: :desc) } scope :order_created_at_desc, -> { order(created_at: :desc) }
......
...@@ -6,8 +6,7 @@ module Projects ...@@ -6,8 +6,7 @@ module Projects
SameFilesystemError = Class.new(Error) SameFilesystemError = Class.new(Error)
attr_reader :repository_storage_move attr_reader :repository_storage_move
delegate :project, :destination_storage_name, to: :repository_storage_move delegate :project, :source_storage_name, :destination_storage_name, to: :repository_storage_move
delegate :repository, to: :project
def initialize(repository_storage_move) def initialize(repository_storage_move)
@repository_storage_move = repository_storage_move @repository_storage_move = repository_storage_move
...@@ -20,21 +19,22 @@ module Projects ...@@ -20,21 +19,22 @@ module Projects
repository_storage_move.start! repository_storage_move.start!
end end
raise SameFilesystemError if same_filesystem?(repository.storage, destination_storage_name) raise SameFilesystemError if same_filesystem?(source_storage_name, destination_storage_name)
mirror_repositories mirror_repositories
project.transaction do repository_storage_move.transaction do
mark_old_paths_for_archive repository_storage_move.finish_replication!
repository_storage_move.finish!
project.leave_pool_repository project.leave_pool_repository
project.track_project_repository project.track_project_repository
end end
remove_old_paths
enqueue_housekeeping enqueue_housekeeping
repository_storage_move.finish_cleanup!
ServiceResponse.success ServiceResponse.success
rescue StandardError => e rescue StandardError => e
...@@ -91,36 +91,31 @@ module Projects ...@@ -91,36 +91,31 @@ module Projects
end end
end end
def mark_old_paths_for_archive def remove_old_paths
old_repository_storage = project.repository_storage Gitlab::Git::Repository.new(
new_project_path = moved_path(project.disk_path) source_storage_name,
"#{project.disk_path}.git",
# Notice that the block passed to `run_after_commit` will run with `repository_storage_move` nil,
# as its context nil
repository_storage_move.run_after_commit do ).remove
GitlabShellWorker.perform_async(:mv_repository,
old_repository_storage, if project.wiki.repository_exists?
project.disk_path, Gitlab::Git::Repository.new(
new_project_path) source_storage_name,
"#{project.wiki.disk_path}.git",
if project.wiki.repository_exists? nil,
GitlabShellWorker.perform_async(:mv_repository, nil
old_repository_storage, ).remove
project.wiki.disk_path,
"#{new_project_path}.wiki")
end
if project.design_repository.exists?
GitlabShellWorker.perform_async(:mv_repository,
old_repository_storage,
project.design_repository.disk_path,
"#{new_project_path}.design")
end
end end
end
def moved_path(path) if project.design_repository.exists?
"#{path}+#{project.id}+moved+#{Time.current.to_i}" Gitlab::Git::Repository.new(
source_storage_name,
"#{project.design_repository.disk_path}.git",
nil,
nil
).remove
end
end end
# The underlying FetchInternalRemote call uses a `git fetch` to move data # The underlying FetchInternalRemote call uses a `git fetch` to move data
......
---
title: Remove repositories from previous storage when storage move succeeds
merge_request: 38547
author:
type: changed
...@@ -15,6 +15,10 @@ FactoryBot.define do ...@@ -15,6 +15,10 @@ FactoryBot.define do
state { ProjectRepositoryStorageMove.state_machines[:state].states[:started].value } state { ProjectRepositoryStorageMove.state_machines[:state].states[:started].value }
end end
trait :replicated do
state { ProjectRepositoryStorageMove.state_machines[:state].states[:replicated].value }
end
trait :finished do trait :finished do
state { ProjectRepositoryStorageMove.state_machines[:state].states[:finished].value } state { ProjectRepositoryStorageMove.state_machines[:state].states[:finished].value }
end end
......
...@@ -74,9 +74,9 @@ RSpec.describe ProjectRepositoryStorageMove, type: :model do ...@@ -74,9 +74,9 @@ RSpec.describe ProjectRepositoryStorageMove, type: :model do
context 'when started' do context 'when started' do
subject(:storage_move) { create(:project_repository_storage_move, :started, project: project, destination_storage_name: 'test_second_storage') } subject(:storage_move) { create(:project_repository_storage_move, :started, project: project, destination_storage_name: 'test_second_storage') }
context 'and transits to finished' do context 'and transits to replicated' do
it 'sets the repository storage and marks the project as writable' do it 'sets the repository storage and marks the project as writable' do
storage_move.finish! storage_move.finish_replication!
expect(project.repository_storage).to eq('test_second_storage') expect(project.repository_storage).to eq('test_second_storage')
expect(project).not_to be_repository_read_only expect(project).not_to be_repository_read_only
......
...@@ -21,6 +21,7 @@ RSpec.describe Projects::UpdateRepositoryStorageService do ...@@ -21,6 +21,7 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
let(:repository_storage_move) { create(:project_repository_storage_move, :scheduled, project: project, destination_storage_name: destination) } let(:repository_storage_move) { create(:project_repository_storage_move, :scheduled, project: project, destination_storage_name: destination) }
let!(:checksum) { project.repository.checksum } let!(:checksum) { project.repository.checksum }
let(:project_repository_double) { double(:repository) } let(:project_repository_double) { double(:repository) }
let(:original_project_repository_double) { double(:repository) }
before do before do
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
...@@ -29,6 +30,9 @@ RSpec.describe Projects::UpdateRepositoryStorageService do ...@@ -29,6 +30,9 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
allow(Gitlab::Git::Repository).to receive(:new) allow(Gitlab::Git::Repository).to receive(:new)
.with('test_second_storage', project.repository.raw.relative_path, project.repository.gl_repository, project.repository.full_path) .with('test_second_storage', project.repository.raw.relative_path, project.repository.gl_repository, project.repository.full_path)
.and_return(project_repository_double) .and_return(project_repository_double)
allow(Gitlab::Git::Repository).to receive(:new)
.with('default', project.repository.raw.relative_path, nil, nil)
.and_return(original_project_repository_double)
end end
context 'when the move succeeds' do context 'when the move succeeds' do
...@@ -41,8 +45,7 @@ RSpec.describe Projects::UpdateRepositoryStorageService do ...@@ -41,8 +45,7 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
.with(project.repository.raw) .with(project.repository.raw)
expect(project_repository_double).to receive(:checksum) expect(project_repository_double).to receive(:checksum)
.and_return(checksum) .and_return(checksum)
expect(GitlabShellWorker).to receive(:perform_async).with(:mv_repository, 'default', anything, anything) expect(original_project_repository_double).to receive(:remove)
.and_call_original
result = subject.execute result = subject.execute
project.reload project.reload
...@@ -74,13 +77,29 @@ RSpec.describe Projects::UpdateRepositoryStorageService do ...@@ -74,13 +77,29 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
expect(project_repository_double).to receive(:replicate) expect(project_repository_double).to receive(:replicate)
.with(project.repository.raw) .with(project.repository.raw)
.and_raise(Gitlab::Git::CommandError) .and_raise(Gitlab::Git::CommandError)
expect(GitlabShellWorker).not_to receive(:perform_async)
result = subject.execute result = subject.execute
expect(result).to be_error expect(result).to be_error
expect(project).not_to be_repository_read_only expect(project).not_to be_repository_read_only
expect(project.repository_storage).to eq('default') expect(project.repository_storage).to eq('default')
expect(repository_storage_move).to be_failed
end
end
context 'when the cleanup fails' do
it 'sets the correct state' do
expect(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
expect(project_repository_double).to receive(:checksum)
.and_return(checksum)
expect(original_project_repository_double).to receive(:remove)
.and_raise(Gitlab::Git::CommandError)
result = subject.execute
expect(result).to be_error
expect(repository_storage_move).to be_cleanup_failed
end end
end end
...@@ -93,7 +112,6 @@ RSpec.describe Projects::UpdateRepositoryStorageService do ...@@ -93,7 +112,6 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
.with(project.repository.raw) .with(project.repository.raw)
expect(project_repository_double).to receive(:checksum) expect(project_repository_double).to receive(:checksum)
.and_return('not matching checksum') .and_return('not matching checksum')
expect(GitlabShellWorker).not_to receive(:perform_async)
result = subject.execute result = subject.execute
...@@ -114,6 +132,7 @@ RSpec.describe Projects::UpdateRepositoryStorageService do ...@@ -114,6 +132,7 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
.with(project.repository.raw) .with(project.repository.raw)
expect(project_repository_double).to receive(:checksum) expect(project_repository_double).to receive(:checksum)
.and_return(checksum) .and_return(checksum)
expect(original_project_repository_double).to receive(:remove)
result = subject.execute result = subject.execute
project.reload project.reload
......
...@@ -2,9 +2,11 @@ ...@@ -2,9 +2,11 @@
RSpec.shared_examples 'moves repository to another storage' do |repository_type| RSpec.shared_examples 'moves repository to another storage' do |repository_type|
let(:project_repository_double) { double(:repository) } let(:project_repository_double) { double(:repository) }
let(:original_project_repository_double) { double(:repository) }
let!(:project_repository_checksum) { project.repository.checksum } let!(:project_repository_checksum) { project.repository.checksum }
let(:repository_double) { double(:repository) } let(:repository_double) { double(:repository) }
let(:original_repository_double) { double(:repository) }
let(:repository_checksum) { repository.checksum } let(:repository_checksum) { repository.checksum }
before do before do
...@@ -14,10 +16,16 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type| ...@@ -14,10 +16,16 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
allow(Gitlab::Git::Repository).to receive(:new) allow(Gitlab::Git::Repository).to receive(:new)
.with('test_second_storage', project.repository.raw.relative_path, project.repository.gl_repository, project.repository.full_path) .with('test_second_storage', project.repository.raw.relative_path, project.repository.gl_repository, project.repository.full_path)
.and_return(project_repository_double) .and_return(project_repository_double)
allow(Gitlab::Git::Repository).to receive(:new)
.with('default', project.repository.raw.relative_path, nil, nil)
.and_return(original_project_repository_double)
allow(Gitlab::Git::Repository).to receive(:new) allow(Gitlab::Git::Repository).to receive(:new)
.with('test_second_storage', repository.raw.relative_path, repository.gl_repository, repository.full_path) .with('test_second_storage', repository.raw.relative_path, repository.gl_repository, repository.full_path)
.and_return(repository_double) .and_return(repository_double)
allow(Gitlab::Git::Repository).to receive(:new)
.with('default', repository.raw.relative_path, nil, nil)
.and_return(original_repository_double)
end end
context 'when the move succeeds', :clean_gitlab_redis_shared_state do context 'when the move succeeds', :clean_gitlab_redis_shared_state do
...@@ -35,8 +43,8 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type| ...@@ -35,8 +43,8 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
allow(repository_double).to receive(:checksum) allow(repository_double).to receive(:checksum)
.and_return(repository_checksum) .and_return(repository_checksum)
expect(GitlabShellWorker).to receive(:perform_async).with(:mv_repository, 'default', anything, anything) expect(original_project_repository_double).to receive(:remove)
.twice.and_call_original expect(original_repository_double).to receive(:remove)
end end
it "moves the project and its #{repository_type} repository to the new storage and unmarks the repository as read only" do it "moves the project and its #{repository_type} repository to the new storage and unmarks the repository as read only" do
...@@ -110,13 +118,36 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type| ...@@ -110,13 +118,36 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
.with(repository.raw) .with(repository.raw)
.and_raise(Gitlab::Git::CommandError) .and_raise(Gitlab::Git::CommandError)
expect(GitlabShellWorker).not_to receive(:perform_async)
result = subject.execute result = subject.execute
expect(result).to be_error expect(result).to be_error
expect(project).not_to be_repository_read_only expect(project).not_to be_repository_read_only
expect(project.repository_storage).to eq('default') expect(project.repository_storage).to eq('default')
expect(repository_storage_move).to be_failed
end
end
context "when the cleanup of the #{repository_type} repository fails" do
it 'sets the correct state' do
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
allow(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
allow(project_repository_double).to receive(:checksum)
.and_return(project_repository_checksum)
allow(original_project_repository_double).to receive(:remove)
allow(repository_double).to receive(:replicate)
.with(repository.raw)
allow(repository_double).to receive(:checksum)
.and_return(repository_checksum)
expect(original_repository_double).to receive(:remove)
.and_raise(Gitlab::Git::CommandError)
result = subject.execute
expect(result).to be_error
expect(repository_storage_move).to be_cleanup_failed
end end
end end
...@@ -134,8 +165,6 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type| ...@@ -134,8 +165,6 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
allow(repository_double).to receive(:checksum) allow(repository_double).to receive(:checksum)
.and_return('not matching checksum') .and_return('not matching checksum')
expect(GitlabShellWorker).not_to receive(:perform_async)
result = subject.execute result = subject.execute
expect(result).to be_error expect(result).to be_error
......
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