2020-04-08 14:13:33 +05:30
# frozen_string_literal: true
module Projects
2020-05-24 23:13:21 +05:30
class UpdateRepositoryStorageService
2020-04-08 14:13:33 +05:30
Error = Class . new ( StandardError )
2020-04-22 19:07:51 +05:30
SameFilesystemError = Class . new ( Error )
2020-04-08 14:13:33 +05:30
2020-05-24 23:13:21 +05:30
attr_reader :repository_storage_move
delegate :project , :destination_storage_name , to : :repository_storage_move
delegate :repository , to : :project
def initialize ( repository_storage_move )
@repository_storage_move = repository_storage_move
2020-04-08 14:13:33 +05:30
end
2020-05-24 23:13:21 +05:30
def execute
2020-07-28 23:09:34 +05:30
repository_storage_move . with_lock do
return ServiceResponse . success unless repository_storage_move . scheduled? # rubocop:disable Cop/AvoidReturnFromBlocks
repository_storage_move . start!
end
2020-04-22 19:07:51 +05:30
2020-05-24 23:13:21 +05:30
raise SameFilesystemError if same_filesystem? ( repository . storage , destination_storage_name )
2020-04-08 14:13:33 +05:30
2020-05-24 23:13:21 +05:30
mirror_repositories
2020-04-08 14:13:33 +05:30
2020-05-24 23:13:21 +05:30
project . transaction do
mark_old_paths_for_archive
repository_storage_move . finish!
2020-06-23 00:09:42 +05:30
2020-05-24 23:13:21 +05:30
project . leave_pool_repository
project . track_project_repository
end
2020-04-08 14:13:33 +05:30
enqueue_housekeeping
2020-05-24 23:13:21 +05:30
ServiceResponse . success
2020-04-08 14:13:33 +05:30
2020-05-24 23:13:21 +05:30
rescue StandardError = > e
2020-06-23 00:09:42 +05:30
repository_storage_move . do_fail!
2020-04-08 14:13:33 +05:30
Gitlab :: ErrorTracking . track_exception ( e , project_path : project . full_path )
2020-05-24 23:13:21 +05:30
ServiceResponse . error (
message : s_ ( " UpdateRepositoryStorage|Error moving repository storage for %{project_full_path} - %{message} " ) % { project_full_path : project . full_path , message : e . message }
)
2020-04-08 14:13:33 +05:30
end
private
2020-04-22 19:07:51 +05:30
def same_filesystem? ( old_storage , new_storage )
Gitlab :: GitalyClient . filesystem_id ( old_storage ) == Gitlab :: GitalyClient . filesystem_id ( new_storage )
end
2020-05-24 23:13:21 +05:30
def mirror_repositories
mirror_repository
2020-04-08 14:13:33 +05:30
if project . wiki . repository_exists?
2020-05-24 23:13:21 +05:30
mirror_repository ( type : Gitlab :: GlRepository :: WIKI )
end
if project . design_repository . exists?
mirror_repository ( type : :: Gitlab :: GlRepository :: DESIGN )
2020-04-08 14:13:33 +05:30
end
end
2020-05-24 23:13:21 +05:30
def mirror_repository ( type : Gitlab :: GlRepository :: PROJECT )
2020-04-08 14:13:33 +05:30
unless wait_for_pushes ( type )
raise Error , s_ ( 'UpdateRepositoryStorage|Timeout waiting for %{type} repository pushes' ) % { type : type . name }
end
repository = type . repository_for ( project )
full_path = repository . full_path
raw_repository = repository . raw
checksum = repository . checksum
# Initialize a git repository on the target path
new_repository = Gitlab :: Git :: Repository . new (
2020-05-24 23:13:21 +05:30
destination_storage_name ,
2020-04-08 14:13:33 +05:30
raw_repository . relative_path ,
raw_repository . gl_repository ,
full_path
)
new_repository . replicate ( raw_repository )
new_checksum = new_repository . checksum
if checksum != new_checksum
raise Error , s_ ( 'UpdateRepositoryStorage|Failed to verify %{type} repository checksum from %{old} to %{new}' ) % { type : type . name , old : checksum , new : new_checksum }
end
end
def mark_old_paths_for_archive
old_repository_storage = project . repository_storage
new_project_path = moved_path ( project . disk_path )
2020-07-28 23:09:34 +05:30
# Notice that the block passed to `run_after_commit` will run with `repository_storage_move`
2020-04-08 14:13:33 +05:30
# as its context
2020-07-28 23:09:34 +05:30
repository_storage_move . run_after_commit do
2020-04-08 14:13:33 +05:30
GitlabShellWorker . perform_async ( :mv_repository ,
old_repository_storage ,
2020-07-28 23:09:34 +05:30
project . disk_path ,
2020-04-08 14:13:33 +05:30
new_project_path )
2020-07-28 23:09:34 +05:30
if project . wiki . repository_exists?
2020-04-08 14:13:33 +05:30
GitlabShellWorker . perform_async ( :mv_repository ,
old_repository_storage ,
2020-07-28 23:09:34 +05:30
project . wiki . disk_path ,
2020-04-08 14:13:33 +05:30
" #{ new_project_path } .wiki " )
end
2020-05-24 23:13:21 +05:30
2020-07-28 23:09:34 +05:30
if project . design_repository . exists?
2020-05-24 23:13:21 +05:30
GitlabShellWorker . perform_async ( :mv_repository ,
old_repository_storage ,
2020-07-28 23:09:34 +05:30
project . design_repository . disk_path ,
2020-05-24 23:13:21 +05:30
" #{ new_project_path } .design " )
end
2020-04-08 14:13:33 +05:30
end
end
def moved_path ( path )
2020-05-24 23:13:21 +05:30
" #{ path } + #{ project . id } +moved+ #{ Time . current . to_i } "
2020-04-08 14:13:33 +05:30
end
# The underlying FetchInternalRemote call uses a `git fetch` to move data
# to the new repository, which leaves it in a less-well-packed state,
# lacking bitmaps and commit graphs. Housekeeping will boost performance
# significantly.
def enqueue_housekeeping
return unless Gitlab :: CurrentSettings . housekeeping_enabled?
return unless Feature . enabled? ( :repack_after_shard_migration , project )
Projects :: HousekeepingService . new ( project , :gc ) . execute
rescue Projects :: HousekeepingService :: LeaseTaken
# No action required
end
def wait_for_pushes ( type )
reference_counter = project . reference_counter ( type : type )
# Try for 30 seconds, polling every 10
3 . times do
return true if reference_counter . value == 0
sleep 10
end
false
end
end
end