2020-03-02 21:08:01 +00:00
# frozen_string_literal: true
module Projects
2020-05-06 06:09:36 +00:00
class UpdateRepositoryStorageService
2020-03-06 15:08:05 +00:00
Error = Class . new ( StandardError )
2020-04-14 00:09:57 +00:00
SameFilesystemError = Class . new ( Error )
2020-03-02 21:08:01 +00:00
2020-05-06 06:09:36 +00:00
attr_reader :repository_storage_move
2020-08-13 12:09:50 +00:00
delegate :project , :source_storage_name , :destination_storage_name , to : :repository_storage_move
2020-05-06 06:09:36 +00:00
def initialize ( repository_storage_move )
@repository_storage_move = repository_storage_move
2020-03-02 21:08:01 +00:00
end
2020-05-06 06:09:36 +00:00
def execute
2020-07-02 00:09:51 +00:00
repository_storage_move . with_lock do
return ServiceResponse . success unless repository_storage_move . scheduled? # rubocop:disable Cop/AvoidReturnFromBlocks
repository_storage_move . start!
end
2020-05-06 06:09:36 +00:00
2020-08-13 12:09:50 +00:00
raise SameFilesystemError if same_filesystem? ( source_storage_name , destination_storage_name )
2020-04-14 00:09:57 +00:00
2020-05-06 06:09:36 +00:00
mirror_repositories
2020-03-02 21:08:01 +00:00
2020-08-13 12:09:50 +00:00
repository_storage_move . transaction do
repository_storage_move . finish_replication!
2020-05-26 21:07:45 +00:00
2020-05-06 06:09:36 +00:00
project . leave_pool_repository
project . track_project_repository
end
2020-03-06 15:08:05 +00:00
2020-08-13 12:09:50 +00:00
remove_old_paths
2020-03-06 15:08:05 +00:00
enqueue_housekeeping
2020-08-13 12:09:50 +00:00
repository_storage_move . finish_cleanup!
2020-05-06 06:09:36 +00:00
ServiceResponse . success
2020-03-06 15:08:05 +00:00
2020-04-22 15:09:27 +00:00
rescue StandardError = > e
2020-05-26 21:07:45 +00:00
repository_storage_move . do_fail!
2020-03-06 15:08:05 +00:00
Gitlab :: ErrorTracking . track_exception ( e , project_path : project . full_path )
2020-05-06 06:09:36 +00:00
ServiceResponse . error (
message : s_ ( " UpdateRepositoryStorage|Error moving repository storage for %{project_full_path} - %{message} " ) % { project_full_path : project . full_path , message : e . message }
)
2020-03-02 21:08:01 +00:00
end
private
2020-04-14 00:09:57 +00:00
def same_filesystem? ( old_storage , new_storage )
Gitlab :: GitalyClient . filesystem_id ( old_storage ) == Gitlab :: GitalyClient . filesystem_id ( new_storage )
end
2020-05-06 06:09:36 +00:00
def mirror_repositories
2020-10-29 15:09:12 +00:00
mirror_repository if project . repository_exists?
2020-03-02 21:08:01 +00:00
if project . wiki . repository_exists?
2020-05-06 06:09:36 +00:00
mirror_repository ( type : Gitlab :: GlRepository :: WIKI )
2020-03-02 21:08:01 +00:00
end
2020-05-13 18:08:47 +00:00
if project . design_repository . exists?
mirror_repository ( type : :: Gitlab :: GlRepository :: DESIGN )
end
2020-03-02 21:08:01 +00:00
end
2020-05-06 06:09:36 +00:00
def mirror_repository ( type : Gitlab :: GlRepository :: PROJECT )
2020-03-06 15:08:05 +00:00
unless wait_for_pushes ( type )
raise Error , s_ ( 'UpdateRepositoryStorage|Timeout waiting for %{type} repository pushes' ) % { type : type . name }
end
2020-03-02 21:08:01 +00:00
repository = type . repository_for ( project )
full_path = repository . full_path
raw_repository = repository . raw
2020-03-06 15:08:05 +00:00
checksum = repository . checksum
2020-03-02 21:08:01 +00:00
# Initialize a git repository on the target path
2020-03-16 12:09:12 +00:00
new_repository = Gitlab :: Git :: Repository . new (
2020-05-06 06:09:36 +00:00
destination_storage_name ,
2020-03-16 12:09:12 +00:00
raw_repository . relative_path ,
raw_repository . gl_repository ,
full_path
)
2020-03-13 00:09:34 +00:00
new_repository . replicate ( raw_repository )
2020-03-06 15:08:05 +00:00
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
2020-03-02 21:08:01 +00:00
end
2020-08-13 12:09:50 +00:00
def remove_old_paths
2020-10-29 15:09:12 +00:00
if project . repository_exists?
Gitlab :: Git :: Repository . new (
source_storage_name ,
" #{ project . disk_path } .git " ,
nil ,
nil
) . remove
end
2020-08-13 12:09:50 +00:00
if project . wiki . repository_exists?
Gitlab :: Git :: Repository . new (
source_storage_name ,
" #{ project . wiki . disk_path } .git " ,
nil ,
nil
) . remove
2020-03-02 21:08:01 +00:00
end
2020-08-13 12:09:50 +00:00
if project . design_repository . exists?
Gitlab :: Git :: Repository . new (
source_storage_name ,
" #{ project . design_repository . disk_path } .git " ,
nil ,
nil
) . remove
end
2020-03-02 21:08:01 +00:00
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