1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
|
# frozen_string_literal: true
module Projects
class UpdateRepositoryStorageService
Error = Class.new(StandardError)
SameFilesystemError = Class.new(Error)
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
end
def execute
repository_storage_move.start!
raise SameFilesystemError if same_filesystem?(repository.storage, destination_storage_name)
mirror_repositories
project.transaction do
mark_old_paths_for_archive
repository_storage_move.finish!
project.update!(repository_storage: destination_storage_name, repository_read_only: false)
project.leave_pool_repository
project.track_project_repository
end
enqueue_housekeeping
ServiceResponse.success
rescue StandardError => e
project.transaction do
repository_storage_move.do_fail!
project.update!(repository_read_only: false)
end
Gitlab::ErrorTracking.track_exception(e, project_path: project.full_path)
ServiceResponse.error(
message: s_("UpdateRepositoryStorage|Error moving repository storage for %{project_full_path} - %{message}") % { project_full_path: project.full_path, message: e.message }
)
end
private
def same_filesystem?(old_storage, new_storage)
Gitlab::GitalyClient.filesystem_id(old_storage) == Gitlab::GitalyClient.filesystem_id(new_storage)
end
def mirror_repositories
mirror_repository
if project.wiki.repository_exists?
mirror_repository(type: Gitlab::GlRepository::WIKI)
end
if project.design_repository.exists?
mirror_repository(type: ::Gitlab::GlRepository::DESIGN)
end
end
def mirror_repository(type: Gitlab::GlRepository::PROJECT)
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(
destination_storage_name,
raw_repository.relative_path,
raw_repository.gl_repository,
full_path
)
new_repository.create_repository
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)
# Notice that the block passed to `run_after_commit` will run with `project`
# as its context
project.run_after_commit do
GitlabShellWorker.perform_async(:mv_repository,
old_repository_storage,
disk_path,
new_project_path)
if wiki.repository_exists?
GitlabShellWorker.perform_async(:mv_repository,
old_repository_storage,
wiki.disk_path,
"#{new_project_path}.wiki")
end
if design_repository.exists?
GitlabShellWorker.perform_async(:mv_repository,
old_repository_storage,
design_repository.disk_path,
"#{new_project_path}.design")
end
end
end
def moved_path(path)
"#{path}+#{project.id}+moved+#{Time.current.to_i}"
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
|