Line |
Branch |
Exec |
Source |
1 |
|
|
/** |
2 |
|
|
* This file is part of the CernVM File System. |
3 |
|
|
* |
4 |
|
|
* Careful: any real schema migration as of now requires taking care of |
5 |
|
|
* hash algorithm |
6 |
|
|
*/ |
7 |
|
|
|
8 |
|
|
#include "swissknife_migrate.h" |
9 |
|
|
|
10 |
|
|
#include <sys/resource.h> |
11 |
|
|
#include <unistd.h> |
12 |
|
|
|
13 |
|
|
#include "catalog_rw.h" |
14 |
|
|
#include "catalog_sql.h" |
15 |
|
|
#include "catalog_virtual.h" |
16 |
|
|
#include "compression/compression.h" |
17 |
|
|
#include "crypto/hash.h" |
18 |
|
|
#include "swissknife_history.h" |
19 |
|
|
#include "util/concurrency.h" |
20 |
|
|
#include "util/logging.h" |
21 |
|
|
|
22 |
|
|
using namespace std; // NOLINT |
23 |
|
|
|
24 |
|
|
namespace swissknife { |
25 |
|
|
|
26 |
|
|
catalog::DirectoryEntry CommandMigrate::nested_catalog_marker_; |
27 |
|
|
|
28 |
|
✗ |
CommandMigrate::CommandMigrate() |
29 |
|
✗ |
: file_descriptor_limit_(8192) |
30 |
|
✗ |
, catalog_count_(0) |
31 |
|
✗ |
, has_committed_new_revision_(false) |
32 |
|
✗ |
, uid_(0) |
33 |
|
✗ |
, gid_(0) |
34 |
|
✗ |
, root_catalog_(NULL) { |
35 |
|
✗ |
atomic_init32(&catalogs_processed_); |
36 |
|
|
} |
37 |
|
|
|
38 |
|
|
|
39 |
|
✗ |
ParameterList CommandMigrate::GetParams() const { |
40 |
|
✗ |
ParameterList r; |
41 |
|
✗ |
r.push_back(Parameter::Mandatory( |
42 |
|
|
'v', |
43 |
|
|
"migration base version ( 2.0.x | 2.1.7 | chown | hardlink | bulkhash | " |
44 |
|
|
"stats)")); |
45 |
|
✗ |
r.push_back(Parameter::Mandatory( |
46 |
|
|
'r', "repository URL (absolute local path or remote URL)")); |
47 |
|
✗ |
r.push_back(Parameter::Mandatory('u', "upstream definition string")); |
48 |
|
✗ |
r.push_back(Parameter::Mandatory('o', "manifest output file")); |
49 |
|
✗ |
r.push_back( |
50 |
|
✗ |
Parameter::Mandatory('t', "temporary directory for catalog decompress")); |
51 |
|
✗ |
r.push_back( |
52 |
|
✗ |
Parameter::Optional('p', "user id to be used for this repository")); |
53 |
|
✗ |
r.push_back( |
54 |
|
✗ |
Parameter::Optional('g', "group id to be used for this repository")); |
55 |
|
✗ |
r.push_back(Parameter::Optional('n', "fully qualified repository name")); |
56 |
|
✗ |
r.push_back(Parameter::Optional('h', "root hash (other than trunk)")); |
57 |
|
✗ |
r.push_back(Parameter::Optional('k', "repository master key(s)")); |
58 |
|
✗ |
r.push_back(Parameter::Optional('i', "UID map for chown")); |
59 |
|
✗ |
r.push_back(Parameter::Optional('j', "GID map for chown")); |
60 |
|
✗ |
r.push_back(Parameter::Optional('@', "proxy url")); |
61 |
|
✗ |
r.push_back(Parameter::Switch('f', "fix nested catalog transition points")); |
62 |
|
✗ |
r.push_back(Parameter::Switch('l', "disable linkcount analysis of files")); |
63 |
|
✗ |
r.push_back( |
64 |
|
✗ |
Parameter::Switch('s', "enable collection of catalog statistics")); |
65 |
|
✗ |
return r; |
66 |
|
|
} |
67 |
|
|
|
68 |
|
|
|
69 |
|
✗ |
static void Error(const std::string &message) { |
70 |
|
✗ |
LogCvmfs(kLogCatalog, kLogStderr, "%s", message.c_str()); |
71 |
|
|
} |
72 |
|
|
|
73 |
|
|
|
74 |
|
✗ |
static void Error(const std::string &message, |
75 |
|
|
const CommandMigrate::PendingCatalog *catalog) { |
76 |
|
|
const std::string err_msg = message |
77 |
|
✗ |
+ "\n" |
78 |
|
|
"Catalog: " |
79 |
|
✗ |
+ catalog->root_path(); |
80 |
|
✗ |
Error(err_msg); |
81 |
|
|
} |
82 |
|
|
|
83 |
|
|
|
84 |
|
✗ |
static void Error(const std::string &message, |
85 |
|
|
const catalog::SqlCatalog &statement, |
86 |
|
|
const CommandMigrate::PendingCatalog *catalog) { |
87 |
|
|
const std::string err_msg = message |
88 |
|
✗ |
+ "\n" |
89 |
|
|
"SQLite: " |
90 |
|
✗ |
+ StringifyInt(statement.GetLastError()) + " - " |
91 |
|
✗ |
+ statement.GetLastErrorMsg(); |
92 |
|
✗ |
Error(err_msg, catalog); |
93 |
|
|
} |
94 |
|
|
|
95 |
|
|
|
96 |
|
✗ |
int CommandMigrate::Main(const ArgumentList &args) { |
97 |
|
✗ |
shash::Any manual_root_hash; |
98 |
|
✗ |
const std::string &migration_base = *args.find('v')->second; |
99 |
|
✗ |
const std::string &repo_url = *args.find('r')->second; |
100 |
|
✗ |
const std::string &spooler = *args.find('u')->second; |
101 |
|
✗ |
const std::string &manifest_path = *args.find('o')->second; |
102 |
|
✗ |
const std::string &tmp_dir = *args.find('t')->second; |
103 |
|
✗ |
const std::string &uid = (args.count('p') > 0) ? *args.find('p')->second : ""; |
104 |
|
✗ |
const std::string &gid = (args.count('g') > 0) ? *args.find('g')->second : ""; |
105 |
|
✗ |
const std::string &repo_name = (args.count('n') > 0) ? *args.find('n')->second |
106 |
|
✗ |
: ""; |
107 |
|
✗ |
const std::string &repo_keys = (args.count('k') > 0) ? *args.find('k')->second |
108 |
|
✗ |
: ""; |
109 |
|
✗ |
const std::string &uid_map_path = (args.count('i') > 0) |
110 |
|
✗ |
? *args.find('i')->second |
111 |
|
✗ |
: ""; |
112 |
|
✗ |
const std::string &gid_map_path = (args.count('j') > 0) |
113 |
|
✗ |
? *args.find('j')->second |
114 |
|
✗ |
: ""; |
115 |
|
✗ |
const bool fix_transition_points = (args.count('f') > 0); |
116 |
|
✗ |
const bool analyze_file_linkcounts = (args.count('l') == 0); |
117 |
|
✗ |
const bool collect_catalog_statistics = (args.count('s') > 0); |
118 |
|
✗ |
if (args.count('h') > 0) { |
119 |
|
✗ |
manual_root_hash = shash::MkFromHexPtr( |
120 |
|
✗ |
shash::HexPtr(*args.find('h')->second), shash::kSuffixCatalog); |
121 |
|
|
} |
122 |
|
|
|
123 |
|
|
// We might need a lot of file descriptors |
124 |
|
✗ |
if (!RaiseFileDescriptorLimit()) { |
125 |
|
✗ |
Error("Failed to raise file descriptor limits"); |
126 |
|
✗ |
return 2; |
127 |
|
|
} |
128 |
|
|
|
129 |
|
|
// Put SQLite into multithreaded mode |
130 |
|
✗ |
if (!ConfigureSQLite()) { |
131 |
|
✗ |
Error("Failed to preconfigure SQLite library"); |
132 |
|
✗ |
return 3; |
133 |
|
|
} |
134 |
|
|
|
135 |
|
|
// Create an upstream spooler |
136 |
|
✗ |
temporary_directory_ = tmp_dir; |
137 |
|
✗ |
const upload::SpoolerDefinition spooler_definition(spooler, shash::kSha1); |
138 |
|
✗ |
spooler_ = upload::Spooler::Construct(spooler_definition); |
139 |
|
✗ |
if (!spooler_.IsValid()) { |
140 |
|
✗ |
Error("Failed to create upstream Spooler."); |
141 |
|
✗ |
return 5; |
142 |
|
|
} |
143 |
|
✗ |
spooler_->RegisterListener(&CommandMigrate::UploadCallback, this); |
144 |
|
|
|
145 |
|
|
// Load the full catalog hierarchy |
146 |
|
✗ |
LogCvmfs(kLogCatalog, kLogStdout, "Loading current catalog tree..."); |
147 |
|
|
|
148 |
|
✗ |
catalog_loading_stopwatch_.Start(); |
149 |
|
✗ |
bool loading_successful = false; |
150 |
|
✗ |
if (IsHttpUrl(repo_url)) { |
151 |
|
|
typedef HttpObjectFetcher<catalog::WritableCatalog> ObjectFetcher; |
152 |
|
|
|
153 |
|
✗ |
const bool follow_redirects = false; |
154 |
|
✗ |
const string proxy = (args.count('@') > 0) ? *args.find('@')->second : ""; |
155 |
|
✗ |
if (!this->InitDownloadManager(follow_redirects, proxy) |
156 |
|
✗ |
|| !this->InitSignatureManager(repo_keys)) { |
157 |
|
✗ |
LogCvmfs(kLogCatalog, kLogStderr, "Failed to init repo connection"); |
158 |
|
✗ |
return 1; |
159 |
|
|
} |
160 |
|
|
|
161 |
|
|
ObjectFetcher fetcher( |
162 |
|
✗ |
repo_name, repo_url, tmp_dir, download_manager(), signature_manager()); |
163 |
|
|
|
164 |
|
✗ |
loading_successful = LoadCatalogs(manual_root_hash, &fetcher); |
165 |
|
✗ |
} else { |
166 |
|
|
typedef LocalObjectFetcher<catalog::WritableCatalog> ObjectFetcher; |
167 |
|
✗ |
ObjectFetcher fetcher(repo_url, tmp_dir); |
168 |
|
✗ |
loading_successful = LoadCatalogs(manual_root_hash, &fetcher); |
169 |
|
|
} |
170 |
|
✗ |
catalog_loading_stopwatch_.Stop(); |
171 |
|
|
|
172 |
|
✗ |
if (!loading_successful) { |
173 |
|
✗ |
Error("Failed to load catalog tree"); |
174 |
|
✗ |
return 4; |
175 |
|
|
} |
176 |
|
|
|
177 |
|
✗ |
LogCvmfs(kLogCatalog, kLogStdout, "Loaded %d catalogs", catalog_count_); |
178 |
|
✗ |
assert(root_catalog_ != NULL); |
179 |
|
|
|
180 |
|
|
// Do the actual migration step |
181 |
|
✗ |
bool migration_succeeded = false; |
182 |
|
✗ |
if (migration_base == "2.0.x") { |
183 |
|
✗ |
if (!ReadPersona(uid, gid)) { |
184 |
|
✗ |
return 1; |
185 |
|
|
} |
186 |
|
|
|
187 |
|
|
// Generate and upload a nested catalog marker |
188 |
|
✗ |
if (!GenerateNestedCatalogMarkerChunk()) { |
189 |
|
✗ |
Error("Failed to create a nested catalog marker."); |
190 |
|
✗ |
return 6; |
191 |
|
|
} |
192 |
|
✗ |
spooler_->WaitForUpload(); |
193 |
|
|
|
194 |
|
|
// Configure the concurrent catalog migration facility |
195 |
|
✗ |
MigrationWorker_20x::worker_context context(temporary_directory_, |
196 |
|
|
collect_catalog_statistics, |
197 |
|
|
fix_transition_points, |
198 |
|
|
analyze_file_linkcounts, |
199 |
|
|
uid_, |
200 |
|
✗ |
gid_); |
201 |
|
✗ |
migration_succeeded = DoMigrationAndCommit<MigrationWorker_20x>( |
202 |
|
|
manifest_path, &context); |
203 |
|
✗ |
} else if (migration_base == "2.1.7") { |
204 |
|
✗ |
MigrationWorker_217::worker_context context(temporary_directory_, |
205 |
|
✗ |
collect_catalog_statistics); |
206 |
|
✗ |
migration_succeeded = DoMigrationAndCommit<MigrationWorker_217>( |
207 |
|
|
manifest_path, &context); |
208 |
|
✗ |
} else if (migration_base == "chown") { |
209 |
|
✗ |
UidMap uid_map; |
210 |
|
✗ |
GidMap gid_map; |
211 |
|
✗ |
if (!ReadPersonaMaps(uid_map_path, gid_map_path, &uid_map, &gid_map)) { |
212 |
|
✗ |
Error("Failed to read UID and/or GID map"); |
213 |
|
✗ |
return 1; |
214 |
|
|
} |
215 |
|
|
ChownMigrationWorker::worker_context context( |
216 |
|
✗ |
temporary_directory_, collect_catalog_statistics, uid_map, gid_map); |
217 |
|
✗ |
migration_succeeded = DoMigrationAndCommit<ChownMigrationWorker>( |
218 |
|
|
manifest_path, &context); |
219 |
|
✗ |
} else if (migration_base == "hardlink") { |
220 |
|
|
HardlinkRemovalMigrationWorker::worker_context context( |
221 |
|
✗ |
temporary_directory_, collect_catalog_statistics); |
222 |
|
✗ |
migration_succeeded = DoMigrationAndCommit<HardlinkRemovalMigrationWorker>( |
223 |
|
|
manifest_path, &context); |
224 |
|
✗ |
} else if (migration_base == "bulkhash") { |
225 |
|
|
BulkhashRemovalMigrationWorker::worker_context context( |
226 |
|
✗ |
temporary_directory_, collect_catalog_statistics); |
227 |
|
✗ |
migration_succeeded = DoMigrationAndCommit<BulkhashRemovalMigrationWorker>( |
228 |
|
|
manifest_path, &context); |
229 |
|
✗ |
} else if (migration_base == "stats") { |
230 |
|
✗ |
StatsMigrationWorker::worker_context context(temporary_directory_, |
231 |
|
✗ |
collect_catalog_statistics); |
232 |
|
✗ |
migration_succeeded = DoMigrationAndCommit<StatsMigrationWorker>( |
233 |
|
|
manifest_path, &context); |
234 |
|
✗ |
} else { |
235 |
|
✗ |
const std::string err_msg = "Unknown migration base: " + migration_base; |
236 |
|
✗ |
Error(err_msg); |
237 |
|
✗ |
return 1; |
238 |
|
|
} |
239 |
|
|
|
240 |
|
|
// Check if everything went well |
241 |
|
✗ |
if (!migration_succeeded) { |
242 |
|
✗ |
Error("Migration failed!"); |
243 |
|
✗ |
return 5; |
244 |
|
|
} |
245 |
|
|
|
246 |
|
|
// Analyze collected statistics |
247 |
|
✗ |
if (collect_catalog_statistics && has_committed_new_revision_) { |
248 |
|
✗ |
LogCvmfs(kLogCatalog, kLogStdout, "\nCollected statistics results:"); |
249 |
|
✗ |
AnalyzeCatalogStatistics(); |
250 |
|
|
} |
251 |
|
|
|
252 |
|
✗ |
LogCvmfs(kLogCatalog, kLogStdout, "\nCatalog Migration succeeded"); |
253 |
|
✗ |
return 0; |
254 |
|
|
} |
255 |
|
|
|
256 |
|
|
|
257 |
|
✗ |
bool CommandMigrate::ReadPersona(const std::string &uid, |
258 |
|
|
const std::string &gid) { |
259 |
|
✗ |
if (uid.empty()) { |
260 |
|
✗ |
Error("Please provide a user ID"); |
261 |
|
✗ |
return false; |
262 |
|
|
} |
263 |
|
✗ |
if (gid.empty()) { |
264 |
|
✗ |
Error("Please provide a group ID"); |
265 |
|
✗ |
return false; |
266 |
|
|
} |
267 |
|
|
|
268 |
|
✗ |
uid_ = String2Int64(uid); |
269 |
|
✗ |
gid_ = String2Int64(gid); |
270 |
|
✗ |
return true; |
271 |
|
|
} |
272 |
|
|
|
273 |
|
|
|
274 |
|
✗ |
bool CommandMigrate::ReadPersonaMaps(const std::string &uid_map_path, |
275 |
|
|
const std::string &gid_map_path, |
276 |
|
|
UidMap *uid_map, |
277 |
|
|
GidMap *gid_map) const { |
278 |
|
✗ |
if (!uid_map->Read(uid_map_path) || !uid_map->IsValid()) { |
279 |
|
✗ |
Error("Failed to read UID map"); |
280 |
|
✗ |
return false; |
281 |
|
|
} |
282 |
|
|
|
283 |
|
✗ |
if (!gid_map->Read(gid_map_path) || !gid_map->IsValid()) { |
284 |
|
✗ |
Error("Failed to read GID map"); |
285 |
|
✗ |
return false; |
286 |
|
|
} |
287 |
|
|
|
288 |
|
✗ |
if (uid_map->RuleCount() == 0 && !uid_map->HasDefault()) { |
289 |
|
✗ |
Error("UID map appears to be empty"); |
290 |
|
✗ |
return false; |
291 |
|
|
} |
292 |
|
|
|
293 |
|
✗ |
if (gid_map->RuleCount() == 0 && !gid_map->HasDefault()) { |
294 |
|
✗ |
Error("GID map appears to be empty"); |
295 |
|
✗ |
return false; |
296 |
|
|
} |
297 |
|
|
|
298 |
|
✗ |
return true; |
299 |
|
|
} |
300 |
|
|
|
301 |
|
|
|
302 |
|
✗ |
void CommandMigrate::UploadHistoryClosure(const upload::SpoolerResult &result, |
303 |
|
|
Future<shash::Any> *hash) { |
304 |
|
✗ |
assert(!result.IsChunked()); |
305 |
|
✗ |
if (result.return_code != 0) { |
306 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStderr, "failed to upload history database (%d)", |
307 |
|
|
result.return_code); |
308 |
|
✗ |
hash->Set(shash::Any()); |
309 |
|
|
} else { |
310 |
|
✗ |
hash->Set(result.content_hash); |
311 |
|
|
} |
312 |
|
|
} |
313 |
|
|
|
314 |
|
|
|
315 |
|
✗ |
bool CommandMigrate::UpdateUndoTags(PendingCatalog *root_catalog, |
316 |
|
|
uint64_t revision, |
317 |
|
|
time_t timestamp, |
318 |
|
|
shash::Any *history_hash) { |
319 |
|
✗ |
const string filename_old = history_upstream_->filename(); |
320 |
|
✗ |
const string filename_new = filename_old + ".new"; |
321 |
|
✗ |
bool retval = CopyPath2Path(filename_old, filename_new); |
322 |
|
✗ |
if (!retval) |
323 |
|
✗ |
return false; |
324 |
|
|
UniquePtr<history::SqliteHistory> history( |
325 |
|
✗ |
history::SqliteHistory::OpenWritable(filename_new)); |
326 |
|
✗ |
history->TakeDatabaseFileOwnership(); |
327 |
|
|
|
328 |
|
✗ |
history::History::Tag tag_trunk; |
329 |
|
✗ |
const bool exists = history->GetByName(CommandTag::kHeadTag, &tag_trunk); |
330 |
|
✗ |
if (exists) { |
331 |
|
✗ |
retval = history->Remove(CommandTag::kHeadTag); |
332 |
|
✗ |
if (!retval) |
333 |
|
✗ |
return false; |
334 |
|
|
|
335 |
|
✗ |
history::History::Tag tag_trunk_previous = tag_trunk; |
336 |
|
✗ |
tag_trunk_previous.name = CommandTag::kPreviousHeadTag; |
337 |
|
✗ |
tag_trunk_previous.description = CommandTag::kPreviousHeadTagDescription; |
338 |
|
✗ |
history->Remove(CommandTag::kPreviousHeadTag); |
339 |
|
|
|
340 |
|
✗ |
tag_trunk.root_hash = root_catalog->new_catalog_hash; |
341 |
|
✗ |
tag_trunk.size = root_catalog->new_catalog_size; |
342 |
|
✗ |
tag_trunk.revision = revision; |
343 |
|
✗ |
tag_trunk.timestamp = timestamp; |
344 |
|
|
|
345 |
|
✗ |
retval = history->Insert(tag_trunk_previous); |
346 |
|
✗ |
if (!retval) |
347 |
|
✗ |
return false; |
348 |
|
✗ |
retval = history->Insert(tag_trunk); |
349 |
|
✗ |
if (!retval) |
350 |
|
✗ |
return false; |
351 |
|
|
} |
352 |
|
|
|
353 |
|
✗ |
history->SetPreviousRevision(manifest_upstream_->history()); |
354 |
|
✗ |
history->DropDatabaseFileOwnership(); |
355 |
|
✗ |
history.Destroy(); |
356 |
|
|
|
357 |
|
✗ |
Future<shash::Any> history_hash_new; |
358 |
|
✗ |
upload::Spooler::CallbackPtr callback = spooler_->RegisterListener( |
359 |
|
|
&CommandMigrate::UploadHistoryClosure, this, &history_hash_new); |
360 |
|
✗ |
spooler_->ProcessHistory(filename_new); |
361 |
|
✗ |
spooler_->WaitForUpload(); |
362 |
|
✗ |
spooler_->UnregisterListener(callback); |
363 |
|
✗ |
unlink(filename_new.c_str()); |
364 |
|
✗ |
*history_hash = history_hash_new.Get(); |
365 |
|
✗ |
if (history_hash->IsNull()) { |
366 |
|
✗ |
Error("failed to upload tag database"); |
367 |
|
✗ |
return false; |
368 |
|
|
} |
369 |
|
|
|
370 |
|
✗ |
return true; |
371 |
|
|
} |
372 |
|
|
|
373 |
|
|
|
374 |
|
|
template<class MigratorT> |
375 |
|
✗ |
bool CommandMigrate::DoMigrationAndCommit( |
376 |
|
|
const std::string &manifest_path, |
377 |
|
|
typename MigratorT::worker_context *context) { |
378 |
|
|
// Create a concurrent migration context for catalog migration |
379 |
|
✗ |
const unsigned int cpus = GetNumberOfCpuCores(); |
380 |
|
✗ |
ConcurrentWorkers<MigratorT> concurrent_migration(cpus, cpus * 10, context); |
381 |
|
|
|
382 |
|
✗ |
if (!concurrent_migration.Initialize()) { |
383 |
|
✗ |
Error("Failed to initialize worker migration system."); |
384 |
|
✗ |
return false; |
385 |
|
|
} |
386 |
|
✗ |
concurrent_migration.RegisterListener(&CommandMigrate::MigrationCallback, |
387 |
|
|
this); |
388 |
|
|
|
389 |
|
|
// Migrate catalogs recursively (starting with the deepest nested catalogs) |
390 |
|
✗ |
LogCvmfs(kLogCatalog, kLogStdout, "\nMigrating catalogs..."); |
391 |
|
✗ |
PendingCatalog *root_catalog = new PendingCatalog(root_catalog_); |
392 |
|
✗ |
migration_stopwatch_.Start(); |
393 |
|
✗ |
ConvertCatalogsRecursively(root_catalog, &concurrent_migration); |
394 |
|
✗ |
concurrent_migration.WaitForEmptyQueue(); |
395 |
|
✗ |
spooler_->WaitForUpload(); |
396 |
|
✗ |
spooler_->UnregisterListeners(); |
397 |
|
✗ |
migration_stopwatch_.Stop(); |
398 |
|
|
|
399 |
|
|
// check for possible errors during the migration process |
400 |
|
✗ |
const unsigned int errors = concurrent_migration.GetNumberOfFailedJobs() |
401 |
|
✗ |
+ spooler_->GetNumberOfErrors(); |
402 |
|
✗ |
LogCvmfs(kLogCatalog, kLogStdout, |
403 |
|
|
"Catalog Migration finished with %d errors.", errors); |
404 |
|
✗ |
if (errors > 0) { |
405 |
|
✗ |
LogCvmfs(kLogCatalog, kLogStdout, |
406 |
|
|
"\nCatalog Migration produced errors\nAborting..."); |
407 |
|
✗ |
return false; |
408 |
|
|
} |
409 |
|
|
|
410 |
|
✗ |
if (root_catalog->was_updated.Get()) { |
411 |
|
✗ |
LogCvmfs(kLogCatalog, kLogStdout, |
412 |
|
|
"\nCommitting migrated repository revision..."); |
413 |
|
✗ |
manifest::Manifest manifest = *manifest_upstream_; |
414 |
|
✗ |
manifest.set_catalog_hash(root_catalog->new_catalog_hash); |
415 |
|
✗ |
manifest.set_catalog_size(root_catalog->new_catalog_size); |
416 |
|
✗ |
manifest.set_root_path(root_catalog->root_path()); |
417 |
|
✗ |
const catalog::Catalog *new_catalog = (root_catalog->HasNew()) |
418 |
|
✗ |
? root_catalog->new_catalog |
419 |
|
|
: root_catalog->old_catalog; |
420 |
|
✗ |
manifest.set_ttl(new_catalog->GetTTL()); |
421 |
|
✗ |
manifest.set_revision(new_catalog->GetRevision()); |
422 |
|
|
|
423 |
|
|
// Commit the new (migrated) repository revision... |
424 |
|
✗ |
if (history_upstream_.IsValid()) { |
425 |
|
✗ |
shash::Any history_hash(manifest_upstream_->history()); |
426 |
|
✗ |
LogCvmfs(kLogCatalog, kLogStdout | kLogNoLinebreak, |
427 |
|
|
"Updating repository tag database... "); |
428 |
|
✗ |
if (!UpdateUndoTags(root_catalog, |
429 |
|
|
new_catalog->GetRevision(), |
430 |
|
✗ |
new_catalog->GetLastModified(), |
431 |
|
|
&history_hash)) { |
432 |
|
✗ |
Error("Updating tag database failed.\nAborting..."); |
433 |
|
✗ |
return false; |
434 |
|
|
} |
435 |
|
✗ |
manifest.set_history(history_hash); |
436 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "%s", history_hash.ToString().c_str()); |
437 |
|
|
} |
438 |
|
|
|
439 |
|
✗ |
if (!manifest.Export(manifest_path)) { |
440 |
|
✗ |
Error("Manifest export failed.\nAborting..."); |
441 |
|
✗ |
return false; |
442 |
|
|
} |
443 |
|
✗ |
has_committed_new_revision_ = true; |
444 |
|
✗ |
} else { |
445 |
|
✗ |
LogCvmfs(kLogCatalog, kLogStdout, |
446 |
|
|
"\nNo catalogs migrated, skipping the commit..."); |
447 |
|
|
} |
448 |
|
|
|
449 |
|
|
// Get rid of the open root catalog |
450 |
|
✗ |
delete root_catalog; |
451 |
|
|
|
452 |
|
✗ |
return true; |
453 |
|
|
} |
454 |
|
|
|
455 |
|
|
|
456 |
|
✗ |
void CommandMigrate::CatalogCallback( |
457 |
|
|
const CatalogTraversalData<catalog::WritableCatalog> &data) { |
458 |
|
✗ |
std::string tree_indent; |
459 |
|
✗ |
std::string hash_string; |
460 |
|
✗ |
std::string path; |
461 |
|
|
|
462 |
|
✗ |
for (unsigned int i = 1; i < data.tree_level; ++i) { |
463 |
|
✗ |
tree_indent += "\u2502 "; |
464 |
|
|
} |
465 |
|
|
|
466 |
|
✗ |
if (data.tree_level > 0) { |
467 |
|
✗ |
tree_indent += "\u251C\u2500 "; |
468 |
|
|
} |
469 |
|
|
|
470 |
|
✗ |
hash_string = data.catalog_hash.ToString(); |
471 |
|
|
|
472 |
|
✗ |
path = data.catalog->mountpoint().ToString(); |
473 |
|
✗ |
if (path.empty()) { |
474 |
|
✗ |
path = "/"; |
475 |
|
✗ |
root_catalog_ = data.catalog; |
476 |
|
|
} |
477 |
|
|
|
478 |
|
✗ |
LogCvmfs(kLogCatalog, kLogStdout, "%s%s %s", tree_indent.c_str(), |
479 |
|
|
hash_string.c_str(), path.c_str()); |
480 |
|
|
|
481 |
|
✗ |
++catalog_count_; |
482 |
|
|
} |
483 |
|
|
|
484 |
|
|
|
485 |
|
✗ |
void CommandMigrate::MigrationCallback(PendingCatalog * const &data) { |
486 |
|
|
// Check if the migration of the catalog was successful |
487 |
|
✗ |
if (!data->success) { |
488 |
|
✗ |
Error("Catalog migration failed! Aborting..."); |
489 |
|
✗ |
exit(1); |
490 |
|
✗ |
return; |
491 |
|
|
} |
492 |
|
|
|
493 |
|
✗ |
if (!data->HasChanges()) { |
494 |
|
✗ |
PrintStatusMessage(data, data->GetOldContentHash(), "preserved"); |
495 |
|
✗ |
data->was_updated.Set(false); |
496 |
|
✗ |
return; |
497 |
|
|
} |
498 |
|
|
|
499 |
|
✗ |
const string &path = (data->HasNew()) ? data->new_catalog->database_path() |
500 |
|
✗ |
: data->old_catalog->database_path(); |
501 |
|
|
|
502 |
|
|
// Save the processed catalog in the pending map |
503 |
|
|
{ |
504 |
|
✗ |
const LockGuard<PendingCatalogMap> guard(&pending_catalogs_); |
505 |
|
✗ |
assert(pending_catalogs_.find(path) == pending_catalogs_.end()); |
506 |
|
✗ |
pending_catalogs_[path] = data; |
507 |
|
|
} |
508 |
|
✗ |
catalog_statistics_list_.Insert(data->statistics); |
509 |
|
|
|
510 |
|
|
// check the size of the uncompressed catalog file |
511 |
|
✗ |
const size_t new_catalog_size = GetFileSize(path); |
512 |
|
✗ |
if (new_catalog_size <= 0) { |
513 |
|
✗ |
Error("Failed to get uncompressed file size of catalog!", data); |
514 |
|
✗ |
exit(2); |
515 |
|
|
return; |
516 |
|
|
} |
517 |
|
✗ |
data->new_catalog_size = new_catalog_size; |
518 |
|
|
|
519 |
|
|
// Schedule the compression and upload of the catalog |
520 |
|
✗ |
spooler_->ProcessCatalog(path); |
521 |
|
|
} |
522 |
|
|
|
523 |
|
|
|
524 |
|
✗ |
void CommandMigrate::UploadCallback(const upload::SpoolerResult &result) { |
525 |
|
✗ |
const string &path = result.local_path; |
526 |
|
|
|
527 |
|
|
// Check if the upload was successful |
528 |
|
✗ |
if (result.return_code != 0) { |
529 |
|
✗ |
Error("Failed to upload file " + path + "\nAborting..."); |
530 |
|
✗ |
exit(2); |
531 |
|
|
return; |
532 |
|
|
} |
533 |
|
✗ |
assert(result.file_chunks.size() == 0); |
534 |
|
|
|
535 |
|
|
// Remove the just uploaded file |
536 |
|
✗ |
unlink(path.c_str()); |
537 |
|
|
|
538 |
|
|
// Uploaded nested catalog marker... generate and cache DirectoryEntry for it |
539 |
|
✗ |
if (path == nested_catalog_marker_tmp_path_) { |
540 |
|
✗ |
CreateNestedCatalogMarkerDirent(result.content_hash); |
541 |
|
✗ |
return; |
542 |
|
|
} else { |
543 |
|
|
// Find the catalog path in the pending catalogs and remove it from the list |
544 |
|
|
PendingCatalog *catalog; |
545 |
|
|
{ |
546 |
|
✗ |
const LockGuard<PendingCatalogMap> guard(&pending_catalogs_); |
547 |
|
✗ |
const PendingCatalogMap::iterator i = pending_catalogs_.find(path); |
548 |
|
✗ |
assert(i != pending_catalogs_.end()); |
549 |
|
✗ |
catalog = const_cast<PendingCatalog *>(i->second); |
550 |
|
✗ |
pending_catalogs_.erase(i); |
551 |
|
|
} |
552 |
|
|
|
553 |
|
✗ |
PrintStatusMessage(catalog, result.content_hash, "migrated and uploaded"); |
554 |
|
|
|
555 |
|
|
// The catalog is completely processed... fill the content_hash to allow the |
556 |
|
|
// processing of parent catalogs (Notified by 'was_updated'-future) |
557 |
|
|
// NOTE: From now on, this PendingCatalog structure could be deleted and |
558 |
|
|
// should not be used anymore! |
559 |
|
✗ |
catalog->new_catalog_hash = result.content_hash; |
560 |
|
✗ |
catalog->was_updated.Set(true); |
561 |
|
|
} |
562 |
|
|
} |
563 |
|
|
|
564 |
|
|
|
565 |
|
✗ |
void CommandMigrate::PrintStatusMessage(const PendingCatalog *catalog, |
566 |
|
|
const shash::Any &content_hash, |
567 |
|
|
const std::string &message) { |
568 |
|
✗ |
atomic_inc32(&catalogs_processed_); |
569 |
|
✗ |
const unsigned int processed = (atomic_read32(&catalogs_processed_) * 100) |
570 |
|
✗ |
/ catalog_count_; |
571 |
|
✗ |
LogCvmfs(kLogCatalog, kLogStdout, "[%d%%] %s %sC %s", processed, |
572 |
|
|
message.c_str(), content_hash.ToString().c_str(), |
573 |
|
|
catalog->root_path().c_str()); |
574 |
|
|
} |
575 |
|
|
|
576 |
|
|
|
577 |
|
|
template<class MigratorT> |
578 |
|
✗ |
void CommandMigrate::ConvertCatalogsRecursively(PendingCatalog *catalog, |
579 |
|
|
MigratorT *migrator) { |
580 |
|
|
// First migrate all nested catalogs (depth first traversal) |
581 |
|
✗ |
const catalog::CatalogList nested_catalogs = catalog->old_catalog |
582 |
|
|
->GetChildren(); |
583 |
|
✗ |
catalog::CatalogList::const_iterator i = nested_catalogs.begin(); |
584 |
|
✗ |
const catalog::CatalogList::const_iterator iend = nested_catalogs.end(); |
585 |
|
✗ |
catalog->nested_catalogs.reserve(nested_catalogs.size()); |
586 |
|
✗ |
for (; i != iend; ++i) { |
587 |
|
✗ |
PendingCatalog *new_nested = new PendingCatalog(*i); |
588 |
|
✗ |
catalog->nested_catalogs.push_back(new_nested); |
589 |
|
✗ |
ConvertCatalogsRecursively(new_nested, migrator); |
590 |
|
|
} |
591 |
|
|
|
592 |
|
|
// Migrate this catalog referencing all its (already migrated) children |
593 |
|
✗ |
migrator->Schedule(catalog); |
594 |
|
|
} |
595 |
|
|
|
596 |
|
|
|
597 |
|
✗ |
bool CommandMigrate::RaiseFileDescriptorLimit() const { |
598 |
|
|
struct rlimit rpl; |
599 |
|
✗ |
memset(&rpl, 0, sizeof(rpl)); |
600 |
|
✗ |
getrlimit(RLIMIT_NOFILE, &rpl); |
601 |
|
✗ |
if (rpl.rlim_cur < file_descriptor_limit_) { |
602 |
|
✗ |
if (rpl.rlim_max < file_descriptor_limit_) |
603 |
|
✗ |
rpl.rlim_max = file_descriptor_limit_; |
604 |
|
✗ |
rpl.rlim_cur = file_descriptor_limit_; |
605 |
|
✗ |
const bool retval = setrlimit(RLIMIT_NOFILE, &rpl); |
606 |
|
✗ |
if (retval != 0) { |
607 |
|
✗ |
return false; |
608 |
|
|
} |
609 |
|
|
} |
610 |
|
✗ |
return true; |
611 |
|
|
} |
612 |
|
|
|
613 |
|
|
|
614 |
|
✗ |
bool CommandMigrate::ConfigureSQLite() const { |
615 |
|
✗ |
const int retval = sqlite3_config(SQLITE_CONFIG_MULTITHREAD); |
616 |
|
✗ |
return (retval == SQLITE_OK); |
617 |
|
|
} |
618 |
|
|
|
619 |
|
|
|
620 |
|
✗ |
void CommandMigrate::AnalyzeCatalogStatistics() const { |
621 |
|
✗ |
const unsigned int number_of_catalogs = catalog_statistics_list_.size(); |
622 |
|
✗ |
unsigned int aggregated_entry_count = 0; |
623 |
|
✗ |
unsigned int aggregated_max_row_id = 0; |
624 |
|
✗ |
unsigned int aggregated_hardlink_count = 0; |
625 |
|
✗ |
unsigned int aggregated_linkcounts = 0; |
626 |
|
✗ |
double aggregated_migration_time = 0.0; |
627 |
|
|
|
628 |
|
✗ |
CatalogStatisticsList::const_iterator i = catalog_statistics_list_.begin(); |
629 |
|
|
const CatalogStatisticsList::const_iterator iend = |
630 |
|
✗ |
catalog_statistics_list_.end(); |
631 |
|
✗ |
for (; i != iend; ++i) { |
632 |
|
✗ |
aggregated_entry_count += i->entry_count; |
633 |
|
✗ |
aggregated_max_row_id += i->max_row_id; |
634 |
|
✗ |
aggregated_hardlink_count += i->hardlink_group_count; |
635 |
|
✗ |
aggregated_linkcounts += i->aggregated_linkcounts; |
636 |
|
✗ |
aggregated_migration_time += i->migration_time; |
637 |
|
|
} |
638 |
|
|
|
639 |
|
|
// Inode quantization |
640 |
|
✗ |
assert(aggregated_max_row_id > 0); |
641 |
|
✗ |
const unsigned int unused_inodes = aggregated_max_row_id |
642 |
|
|
- aggregated_entry_count; |
643 |
|
✗ |
const float ratio = (static_cast<float>(unused_inodes) |
644 |
|
✗ |
/ static_cast<float>(aggregated_max_row_id)) |
645 |
|
|
* 100.0f; |
646 |
|
✗ |
LogCvmfs(kLogCatalog, kLogStdout, |
647 |
|
|
"Actual Entries: %d\n" |
648 |
|
|
"Allocated Inodes: %d\n" |
649 |
|
|
" Unused Inodes: %d\n" |
650 |
|
|
" Percentage of wasted Inodes: %.1f%%\n", |
651 |
|
|
aggregated_entry_count, aggregated_max_row_id, unused_inodes, ratio); |
652 |
|
|
|
653 |
|
|
// Hardlink statistics |
654 |
|
✗ |
const float average_linkcount = (aggregated_hardlink_count > 0) |
655 |
|
✗ |
? aggregated_linkcounts |
656 |
|
✗ |
/ aggregated_hardlink_count |
657 |
|
|
: 0.0f; |
658 |
|
✗ |
LogCvmfs(kLogCatalog, kLogStdout, |
659 |
|
|
"Generated Hardlink Groups: %d\n" |
660 |
|
|
"Average Linkcount per Group: %.1f\n", |
661 |
|
|
aggregated_hardlink_count, average_linkcount); |
662 |
|
|
|
663 |
|
|
// Performance measures |
664 |
|
✗ |
const double average_migration_time = aggregated_migration_time |
665 |
|
✗ |
/ static_cast<double>( |
666 |
|
|
number_of_catalogs); |
667 |
|
✗ |
LogCvmfs(kLogCatalog, kLogStdout, |
668 |
|
|
"Catalog Loading Time: %.2fs\n" |
669 |
|
|
"Average Migration Time: %.2fs\n" |
670 |
|
|
"Overall Migration Time: %.2fs\n" |
671 |
|
|
"Aggregated Migration Time: %.2fs\n", |
672 |
|
|
catalog_loading_stopwatch_.GetTime(), average_migration_time, |
673 |
|
|
migration_stopwatch_.GetTime(), aggregated_migration_time); |
674 |
|
|
} |
675 |
|
|
|
676 |
|
|
|
677 |
|
✗ |
CommandMigrate::PendingCatalog::~PendingCatalog() { |
678 |
|
✗ |
delete old_catalog; |
679 |
|
✗ |
old_catalog = NULL; |
680 |
|
|
|
681 |
|
✗ |
if (new_catalog != NULL) { |
682 |
|
✗ |
delete new_catalog; |
683 |
|
✗ |
new_catalog = NULL; |
684 |
|
|
} |
685 |
|
|
} |
686 |
|
|
|
687 |
|
|
|
688 |
|
|
template<class DerivedT> |
689 |
|
✗ |
CommandMigrate::AbstractMigrationWorker<DerivedT>::AbstractMigrationWorker( |
690 |
|
|
const worker_context *context) |
691 |
|
✗ |
: temporary_directory_(context->temporary_directory) |
692 |
|
✗ |
, collect_catalog_statistics_(context->collect_catalog_statistics) { } |
693 |
|
|
|
694 |
|
|
|
695 |
|
|
template<class DerivedT> |
696 |
|
✗ |
CommandMigrate::AbstractMigrationWorker<DerivedT>::~AbstractMigrationWorker() { |
697 |
|
|
} |
698 |
|
|
|
699 |
|
|
|
700 |
|
|
template<class DerivedT> |
701 |
|
✗ |
void CommandMigrate::AbstractMigrationWorker<DerivedT>::operator()( |
702 |
|
|
const expected_data &data) { |
703 |
|
✗ |
migration_stopwatch_.Start(); |
704 |
|
✗ |
const bool success = static_cast<DerivedT *>(this)->RunMigration(data) |
705 |
|
✗ |
&& UpdateNestedCatalogReferences(data) |
706 |
|
✗ |
&& UpdateCatalogMetadata(data) |
707 |
|
✗ |
&& CollectAndAggregateStatistics(data) |
708 |
|
✗ |
&& CleanupNestedCatalogs(data); |
709 |
|
✗ |
data->success = success; |
710 |
|
✗ |
migration_stopwatch_.Stop(); |
711 |
|
|
|
712 |
|
✗ |
data->statistics.migration_time = migration_stopwatch_.GetTime(); |
713 |
|
✗ |
migration_stopwatch_.Reset(); |
714 |
|
|
|
715 |
|
|
// Note: MigrationCallback() will take care of the result... |
716 |
|
✗ |
if (success) { |
717 |
|
✗ |
ConcurrentWorker<DerivedT>::master()->JobSuccessful(data); |
718 |
|
|
} else { |
719 |
|
✗ |
ConcurrentWorker<DerivedT>::master()->JobFailed(data); |
720 |
|
|
} |
721 |
|
|
} |
722 |
|
|
|
723 |
|
|
|
724 |
|
|
template<class DerivedT> |
725 |
|
✗ |
bool CommandMigrate::AbstractMigrationWorker< |
726 |
|
|
DerivedT>::UpdateNestedCatalogReferences(PendingCatalog *data) const { |
727 |
|
✗ |
const catalog::Catalog *new_catalog = (data->HasNew()) ? data->new_catalog |
728 |
|
|
: data->old_catalog; |
729 |
|
✗ |
const catalog::CatalogDatabase &writable = new_catalog->database(); |
730 |
|
|
|
731 |
|
✗ |
catalog::SqlCatalog add_nested_catalog( |
732 |
|
|
writable, |
733 |
|
|
"INSERT OR REPLACE INTO nested_catalogs (path, sha1, size) " |
734 |
|
|
" VALUES (:path, :sha1, :size);"); |
735 |
|
|
|
736 |
|
|
// go through all nested catalogs and update their references (we are |
737 |
|
|
// currently in their parent catalog) |
738 |
|
|
// Note: we might need to wait for the nested catalog to be fully processed. |
739 |
|
✗ |
PendingCatalogList::const_iterator i = data->nested_catalogs.begin(); |
740 |
|
✗ |
const PendingCatalogList::const_iterator iend = data->nested_catalogs.end(); |
741 |
|
✗ |
for (; i != iend; ++i) { |
742 |
|
✗ |
PendingCatalog *nested_catalog = *i; |
743 |
|
|
|
744 |
|
✗ |
if (!nested_catalog->was_updated.Get()) { |
745 |
|
✗ |
continue; |
746 |
|
|
} |
747 |
|
|
|
748 |
|
✗ |
const std::string &root_path = nested_catalog->root_path(); |
749 |
|
✗ |
const shash::Any catalog_hash = nested_catalog->new_catalog_hash; |
750 |
|
✗ |
const size_t catalog_size = nested_catalog->new_catalog_size; |
751 |
|
|
|
752 |
|
|
// insert the updated nested catalog reference into the new catalog |
753 |
|
✗ |
const bool retval = add_nested_catalog.BindText(1, root_path) |
754 |
|
✗ |
&& add_nested_catalog.BindText(2, |
755 |
|
|
catalog_hash.ToString()) |
756 |
|
✗ |
&& add_nested_catalog.BindInt64(3, catalog_size) |
757 |
|
✗ |
&& add_nested_catalog.Execute(); |
758 |
|
✗ |
if (!retval) { |
759 |
|
✗ |
Error("Failed to add nested catalog link", add_nested_catalog, data); |
760 |
|
✗ |
return false; |
761 |
|
|
} |
762 |
|
✗ |
add_nested_catalog.Reset(); |
763 |
|
|
} |
764 |
|
|
|
765 |
|
✗ |
return true; |
766 |
|
|
} |
767 |
|
|
|
768 |
|
|
|
769 |
|
|
template<class DerivedT> |
770 |
|
✗ |
bool CommandMigrate::AbstractMigrationWorker<DerivedT>::UpdateCatalogMetadata( |
771 |
|
|
PendingCatalog *data) const { |
772 |
|
✗ |
if (!data->HasChanges()) { |
773 |
|
✗ |
return true; |
774 |
|
|
} |
775 |
|
|
|
776 |
|
✗ |
catalog::WritableCatalog *catalog = (data->HasNew()) |
777 |
|
✗ |
? data->new_catalog |
778 |
|
✗ |
: GetWritable(data->old_catalog); |
779 |
|
|
|
780 |
|
|
// Set the previous revision hash in the new catalog to the old catalog |
781 |
|
|
// we are doing the whole migration as a new snapshot that does not change |
782 |
|
|
// any files, but just applies the necessary data schema migrations |
783 |
|
✗ |
catalog->SetPreviousRevision(data->old_catalog->hash()); |
784 |
|
✗ |
catalog->IncrementRevision(); |
785 |
|
✗ |
catalog->UpdateLastModified(); |
786 |
|
|
|
787 |
|
✗ |
return true; |
788 |
|
|
} |
789 |
|
|
|
790 |
|
|
|
791 |
|
|
template<class DerivedT> |
792 |
|
✗ |
bool CommandMigrate::AbstractMigrationWorker< |
793 |
|
|
DerivedT>::CollectAndAggregateStatistics(PendingCatalog *data) const { |
794 |
|
✗ |
if (!collect_catalog_statistics_) { |
795 |
|
✗ |
return true; |
796 |
|
|
} |
797 |
|
|
|
798 |
|
✗ |
const catalog::Catalog *new_catalog = (data->HasNew()) ? data->new_catalog |
799 |
|
|
: data->old_catalog; |
800 |
|
✗ |
const catalog::CatalogDatabase &writable = new_catalog->database(); |
801 |
|
|
bool retval; |
802 |
|
|
|
803 |
|
|
// Find out the discrepancy between MAX(rowid) and COUNT(*) |
804 |
|
✗ |
catalog::SqlCatalog wasted_inodes( |
805 |
|
|
writable, "SELECT COUNT(*), MAX(rowid) FROM catalog;"); |
806 |
|
✗ |
retval = wasted_inodes.FetchRow(); |
807 |
|
✗ |
if (!retval) { |
808 |
|
✗ |
Error("Failed to count entries in catalog", wasted_inodes, data); |
809 |
|
✗ |
return false; |
810 |
|
|
} |
811 |
|
✗ |
const unsigned int entry_count = wasted_inodes.RetrieveInt64(0); |
812 |
|
✗ |
const unsigned int max_row_id = wasted_inodes.RetrieveInt64(1); |
813 |
|
|
|
814 |
|
|
// Save collected information into the central statistics aggregator |
815 |
|
✗ |
data->statistics.root_path = data->root_path(); |
816 |
|
✗ |
data->statistics.max_row_id = max_row_id; |
817 |
|
✗ |
data->statistics.entry_count = entry_count; |
818 |
|
|
|
819 |
|
✗ |
return true; |
820 |
|
|
} |
821 |
|
|
|
822 |
|
|
|
823 |
|
|
template<class DerivedT> |
824 |
|
✗ |
bool CommandMigrate::AbstractMigrationWorker<DerivedT>::CleanupNestedCatalogs( |
825 |
|
|
PendingCatalog *data) const { |
826 |
|
|
// All nested catalogs of PendingCatalog 'data' are fully processed and |
827 |
|
|
// accounted. It is safe to get rid of their data structures here! |
828 |
|
✗ |
PendingCatalogList::const_iterator i = data->nested_catalogs.begin(); |
829 |
|
✗ |
const PendingCatalogList::const_iterator iend = data->nested_catalogs.end(); |
830 |
|
✗ |
for (; i != iend; ++i) { |
831 |
|
✗ |
delete *i; |
832 |
|
|
} |
833 |
|
|
|
834 |
|
✗ |
data->nested_catalogs.clear(); |
835 |
|
✗ |
return true; |
836 |
|
|
} |
837 |
|
|
|
838 |
|
|
|
839 |
|
|
/** |
840 |
|
|
* Those values _must_ reflect the schema version in catalog_sql.h so that a |
841 |
|
|
* legacy catalog migration generates always the latest catalog revision. |
842 |
|
|
* This is a deliberately duplicated piece of information to ensure that always |
843 |
|
|
* both the catalog management and migration classes get updated. |
844 |
|
|
*/ |
845 |
|
|
const float CommandMigrate::MigrationWorker_20x::kSchema = 2.5; |
846 |
|
|
const unsigned CommandMigrate::MigrationWorker_20x::kSchemaRevision = 7; |
847 |
|
|
|
848 |
|
|
|
849 |
|
|
template<class DerivedT> |
850 |
|
|
catalog::WritableCatalog * |
851 |
|
✗ |
CommandMigrate::AbstractMigrationWorker<DerivedT>::GetWritable( |
852 |
|
|
const catalog::Catalog *catalog) const { |
853 |
|
✗ |
return dynamic_cast<catalog::WritableCatalog *>( |
854 |
|
✗ |
const_cast<catalog::Catalog *>(catalog)); |
855 |
|
|
} |
856 |
|
|
|
857 |
|
|
|
858 |
|
|
//------------------------------------------------------------------------------ |
859 |
|
|
|
860 |
|
|
|
861 |
|
✗ |
CommandMigrate::MigrationWorker_20x::MigrationWorker_20x( |
862 |
|
✗ |
const worker_context *context) |
863 |
|
|
: AbstractMigrationWorker<MigrationWorker_20x>(context) |
864 |
|
✗ |
, fix_nested_catalog_transitions_(context->fix_nested_catalog_transitions) |
865 |
|
✗ |
, analyze_file_linkcounts_(context->analyze_file_linkcounts) |
866 |
|
✗ |
, uid_(context->uid) |
867 |
|
✗ |
, gid_(context->gid) { } |
868 |
|
|
|
869 |
|
|
|
870 |
|
✗ |
bool CommandMigrate::MigrationWorker_20x::RunMigration( |
871 |
|
|
PendingCatalog *data) const { |
872 |
|
|
// double-check that we are generating compatible catalogs to the actual |
873 |
|
|
// catalog management classes |
874 |
|
✗ |
assert(kSchema == catalog::CatalogDatabase::kLatestSupportedSchema); |
875 |
|
✗ |
assert(kSchemaRevision == catalog::CatalogDatabase::kLatestSchemaRevision); |
876 |
|
|
|
877 |
|
✗ |
return CreateNewEmptyCatalog(data) && CheckDatabaseSchemaCompatibility(data) |
878 |
|
✗ |
&& AttachOldCatalogDatabase(data) && StartDatabaseTransaction(data) |
879 |
|
✗ |
&& MigrateFileMetadata(data) && MigrateNestedCatalogMountPoints(data) |
880 |
|
✗ |
&& FixNestedCatalogTransitionPoints(data) |
881 |
|
✗ |
&& RemoveDanglingNestedMountpoints(data) |
882 |
|
✗ |
&& GenerateCatalogStatistics(data) && FindRootEntryInformation(data) |
883 |
|
✗ |
&& CommitDatabaseTransaction(data) && DetachOldCatalogDatabase(data); |
884 |
|
|
} |
885 |
|
|
|
886 |
|
✗ |
bool CommandMigrate::MigrationWorker_20x::CreateNewEmptyCatalog( |
887 |
|
|
PendingCatalog *data) const { |
888 |
|
✗ |
const string root_path = data->root_path(); |
889 |
|
|
|
890 |
|
|
// create a new catalog database schema |
891 |
|
✗ |
const string clg_db_path = CreateTempPath(temporary_directory_ + "/catalog", |
892 |
|
✗ |
0666); |
893 |
|
✗ |
if (clg_db_path.empty()) { |
894 |
|
✗ |
Error("Failed to create temporary file for the new catalog database."); |
895 |
|
✗ |
return false; |
896 |
|
|
} |
897 |
|
✗ |
const bool volatile_content = false; |
898 |
|
|
|
899 |
|
|
{ |
900 |
|
|
// TODO(rmeusel): Attach catalog should work with an open catalog database |
901 |
|
|
// as well, to remove this inefficiency |
902 |
|
|
const UniquePtr<catalog::CatalogDatabase> new_clg_db( |
903 |
|
✗ |
catalog::CatalogDatabase::Create(clg_db_path)); |
904 |
|
✗ |
if (!new_clg_db.IsValid() |
905 |
|
✗ |
|| !new_clg_db->InsertInitialValues(root_path, volatile_content, "")) { |
906 |
|
✗ |
Error("Failed to create database for new catalog"); |
907 |
|
✗ |
unlink(clg_db_path.c_str()); |
908 |
|
✗ |
return false; |
909 |
|
|
} |
910 |
|
|
} |
911 |
|
|
|
912 |
|
|
// Attach the just created nested catalog database |
913 |
|
|
catalog::WritableCatalog |
914 |
|
✗ |
*writable_catalog = catalog::WritableCatalog::AttachFreely( |
915 |
|
✗ |
root_path, clg_db_path, shash::Any(shash::kSha1)); |
916 |
|
✗ |
if (writable_catalog == NULL) { |
917 |
|
✗ |
Error("Failed to open database for new catalog"); |
918 |
|
✗ |
unlink(clg_db_path.c_str()); |
919 |
|
✗ |
return false; |
920 |
|
|
} |
921 |
|
|
|
922 |
|
✗ |
data->new_catalog = writable_catalog; |
923 |
|
✗ |
return true; |
924 |
|
|
} |
925 |
|
|
|
926 |
|
|
|
927 |
|
✗ |
bool CommandMigrate::MigrationWorker_20x::CheckDatabaseSchemaCompatibility( |
928 |
|
|
PendingCatalog *data) const { |
929 |
|
✗ |
const catalog::CatalogDatabase &old_catalog = data->old_catalog->database(); |
930 |
|
✗ |
const catalog::CatalogDatabase &new_catalog = data->new_catalog->database(); |
931 |
|
|
|
932 |
|
✗ |
if ((new_catalog.schema_version() |
933 |
|
✗ |
< catalog::CatalogDatabase::kLatestSupportedSchema |
934 |
|
✗ |
- catalog::CatalogDatabase::kSchemaEpsilon |
935 |
|
✗ |
|| new_catalog.schema_version() |
936 |
|
✗ |
> catalog::CatalogDatabase::kLatestSupportedSchema |
937 |
|
✗ |
+ catalog::CatalogDatabase::kSchemaEpsilon) |
938 |
|
✗ |
|| (old_catalog.schema_version() |
939 |
|
|
> 2.1 + catalog::CatalogDatabase::kSchemaEpsilon)) { |
940 |
|
✗ |
Error("Failed to meet database requirements for migration.", data); |
941 |
|
✗ |
return false; |
942 |
|
|
} |
943 |
|
✗ |
return true; |
944 |
|
|
} |
945 |
|
|
|
946 |
|
|
|
947 |
|
✗ |
bool CommandMigrate::MigrationWorker_20x::AttachOldCatalogDatabase( |
948 |
|
|
PendingCatalog *data) const { |
949 |
|
✗ |
const catalog::CatalogDatabase &old_catalog = data->old_catalog->database(); |
950 |
|
✗ |
const catalog::CatalogDatabase &new_catalog = data->new_catalog->database(); |
951 |
|
|
|
952 |
|
|
catalog::SqlCatalog sql_attach_new( |
953 |
|
✗ |
new_catalog, "ATTACH '" + old_catalog.filename() + "' AS old;"); |
954 |
|
✗ |
const bool retval = sql_attach_new.Execute(); |
955 |
|
|
|
956 |
|
|
// remove the hardlink to the old database file (temporary file), it will not |
957 |
|
|
// be needed anymore... data will get deleted when the database is closed |
958 |
|
✗ |
unlink(data->old_catalog->database().filename().c_str()); |
959 |
|
|
|
960 |
|
✗ |
if (!retval) { |
961 |
|
✗ |
Error("Failed to attach database of old catalog", sql_attach_new, data); |
962 |
|
✗ |
return false; |
963 |
|
|
} |
964 |
|
✗ |
return true; |
965 |
|
|
} |
966 |
|
|
|
967 |
|
|
|
968 |
|
✗ |
bool CommandMigrate::MigrationWorker_20x::StartDatabaseTransaction( |
969 |
|
|
PendingCatalog *data) const { |
970 |
|
✗ |
assert(data->HasNew()); |
971 |
|
✗ |
data->new_catalog->Transaction(); |
972 |
|
✗ |
return true; |
973 |
|
|
} |
974 |
|
|
|
975 |
|
|
|
976 |
|
✗ |
bool CommandMigrate::MigrationWorker_20x::MigrateFileMetadata( |
977 |
|
|
PendingCatalog *data) const { |
978 |
|
✗ |
assert(!data->new_catalog->IsDirty()); |
979 |
|
✗ |
assert(data->HasNew()); |
980 |
|
|
bool retval; |
981 |
|
✗ |
const catalog::CatalogDatabase &writable = data->new_catalog->database(); |
982 |
|
|
|
983 |
|
|
// Hardlinks scratch space. |
984 |
|
|
// This temporary table is used for the hardlink analysis results. |
985 |
|
|
// The old catalog format did not have a direct notion of hardlinks and their |
986 |
|
|
// linkcounts, but this information can be partly retrieved from the under- |
987 |
|
|
// lying file system semantics. |
988 |
|
|
// |
989 |
|
|
// Hardlinks: |
990 |
|
|
// groupid : this group id can be used for the new catalog schema |
991 |
|
|
// inode : the inodes that were part of a hardlink group before |
992 |
|
|
// linkcount : the linkcount for hardlink group id members |
993 |
|
|
catalog::SqlCatalog sql_create_hardlinks_table( |
994 |
|
|
writable, |
995 |
|
|
"CREATE TEMPORARY TABLE hardlinks " |
996 |
|
|
" ( hardlink_group_id INTEGER PRIMARY KEY AUTOINCREMENT, " |
997 |
|
|
" inode INTEGER, " |
998 |
|
|
" linkcount INTEGER, " |
999 |
|
✗ |
" CONSTRAINT unique_inode UNIQUE (inode) );"); |
1000 |
|
✗ |
retval = sql_create_hardlinks_table.Execute(); |
1001 |
|
✗ |
if (!retval) { |
1002 |
|
✗ |
Error("Failed to create temporary hardlink analysis table", |
1003 |
|
|
sql_create_hardlinks_table, data); |
1004 |
|
✗ |
return false; |
1005 |
|
|
} |
1006 |
|
|
|
1007 |
|
|
// Directory Linkcount scratch space. |
1008 |
|
|
// Directory linkcounts can be obtained from the directory hierarchy reflected |
1009 |
|
|
// in the old style catalogs. The new catalog schema asks for this specific |
1010 |
|
|
// linkcount. Directory linkcount analysis results will be put into this |
1011 |
|
|
// temporary table |
1012 |
|
|
catalog::SqlCatalog sql_create_linkcounts_table( |
1013 |
|
|
writable, |
1014 |
|
|
"CREATE TEMPORARY TABLE dir_linkcounts " |
1015 |
|
|
" ( inode INTEGER PRIMARY KEY, " |
1016 |
|
✗ |
" linkcount INTEGER );"); |
1017 |
|
✗ |
retval = sql_create_linkcounts_table.Execute(); |
1018 |
|
✗ |
if (!retval) { |
1019 |
|
✗ |
Error("Failed to create tmeporary directory linkcount analysis table", |
1020 |
|
|
sql_create_linkcounts_table, data); |
1021 |
|
|
} |
1022 |
|
|
|
1023 |
|
|
// It is possible to skip this step. |
1024 |
|
|
// In that case all hardlink inodes with a (potential) linkcount > 1 will get |
1025 |
|
|
// degraded to files containing the same content |
1026 |
|
✗ |
if (analyze_file_linkcounts_) { |
1027 |
|
✗ |
retval = AnalyzeFileLinkcounts(data); |
1028 |
|
✗ |
if (!retval) { |
1029 |
|
✗ |
return false; |
1030 |
|
|
} |
1031 |
|
|
} |
1032 |
|
|
|
1033 |
|
|
// Analyze the linkcounts of directories |
1034 |
|
|
// - each directory has a linkcount of at least 2 (empty directory) |
1035 |
|
|
// (link in parent directory and self reference (cd .) ) |
1036 |
|
|
// - for each child directory, the parent's link count is incremented by 1 |
1037 |
|
|
// (parent reference in child (cd ..) ) |
1038 |
|
|
// |
1039 |
|
|
// Note: nested catalog mountpoints will be miscalculated here, since we can't |
1040 |
|
|
// check the number of containing directories. They are defined in a the |
1041 |
|
|
// linked nested catalog and need to be added later on. |
1042 |
|
|
// (see: MigrateNestedCatalogMountPoints() for details) |
1043 |
|
|
catalog::SqlCatalog sql_dir_linkcounts( |
1044 |
|
|
writable, |
1045 |
|
|
"INSERT INTO dir_linkcounts " |
1046 |
|
|
" SELECT c1.inode as inode, " |
1047 |
|
|
" SUM(IFNULL(MIN(c2.inode,1),0)) + 2 as linkcount " |
1048 |
|
|
" FROM old.catalog as c1 " |
1049 |
|
|
" LEFT JOIN old.catalog as c2 " |
1050 |
|
|
" ON c2.parent_1 = c1.md5path_1 AND " |
1051 |
|
|
" c2.parent_2 = c1.md5path_2 AND " |
1052 |
|
|
" c2.flags & :flag_dir_1 " |
1053 |
|
|
" WHERE c1.flags & :flag_dir_2 " |
1054 |
|
✗ |
" GROUP BY c1.inode;"); |
1055 |
|
✗ |
retval = sql_dir_linkcounts.BindInt64(1, catalog::SqlDirent::kFlagDir) |
1056 |
|
✗ |
&& sql_dir_linkcounts.BindInt64(2, catalog::SqlDirent::kFlagDir) |
1057 |
|
✗ |
&& sql_dir_linkcounts.Execute(); |
1058 |
|
✗ |
if (!retval) { |
1059 |
|
✗ |
Error("Failed to analyze directory specific linkcounts", sql_dir_linkcounts, |
1060 |
|
|
data); |
1061 |
|
✗ |
if (sql_dir_linkcounts.GetLastError() == SQLITE_CONSTRAINT) { |
1062 |
|
✗ |
Error("Obviously your catalogs are corrupted, since we found a directory" |
1063 |
|
|
"inode that is a file inode at the same time!"); |
1064 |
|
|
} |
1065 |
|
✗ |
return false; |
1066 |
|
|
} |
1067 |
|
|
|
1068 |
|
|
// Copy the old file meta information into the new catalog schema |
1069 |
|
|
// here we also add the previously analyzed hardlink/linkcount information |
1070 |
|
|
// from both temporary tables "hardlinks" and "dir_linkcounts". |
1071 |
|
|
// |
1072 |
|
|
// Note: nested catalog mountpoints still need to be treated separately |
1073 |
|
|
// (see MigrateNestedCatalogMountPoints() for details) |
1074 |
|
|
catalog::SqlCatalog migrate_file_meta_data( |
1075 |
|
|
writable, |
1076 |
|
|
"INSERT INTO catalog " |
1077 |
|
|
" SELECT md5path_1, md5path_2, " |
1078 |
|
|
" parent_1, parent_2, " |
1079 |
|
|
" IFNULL(hardlink_group_id, 0) << 32 | " |
1080 |
|
|
" COALESCE(hardlinks.linkcount, dir_linkcounts.linkcount, 1) " |
1081 |
|
|
" AS hardlinks, " |
1082 |
|
|
" hash, size, mode, mtime, NULL, " // set empty mtimens |
1083 |
|
|
" flags, name, symlink, " |
1084 |
|
|
" :uid, " |
1085 |
|
|
" :gid, " |
1086 |
|
|
" NULL " // set empty xattr BLOB (default) |
1087 |
|
|
" FROM old.catalog " |
1088 |
|
|
" LEFT JOIN hardlinks " |
1089 |
|
|
" ON catalog.inode = hardlinks.inode " |
1090 |
|
|
" LEFT JOIN dir_linkcounts " |
1091 |
|
✗ |
" ON catalog.inode = dir_linkcounts.inode;"); |
1092 |
|
✗ |
retval = migrate_file_meta_data.BindInt64(1, uid_) |
1093 |
|
✗ |
&& migrate_file_meta_data.BindInt64(2, gid_) |
1094 |
|
✗ |
&& migrate_file_meta_data.Execute(); |
1095 |
|
✗ |
if (!retval) { |
1096 |
|
✗ |
Error("Failed to migrate the file system meta data", migrate_file_meta_data, |
1097 |
|
|
data); |
1098 |
|
✗ |
return false; |
1099 |
|
|
} |
1100 |
|
|
|
1101 |
|
|
// If we deal with a nested catalog, we need to add a .cvmfscatalog entry |
1102 |
|
|
// since it was not present in the old repository specification but is needed |
1103 |
|
|
// now! |
1104 |
|
✗ |
if (!data->IsRoot()) { |
1105 |
|
|
const catalog::DirectoryEntry |
1106 |
|
✗ |
&nested_marker = CommandMigrate::GetNestedCatalogMarkerDirent(); |
1107 |
|
✗ |
catalog::SqlDirentInsert insert_nested_marker(writable); |
1108 |
|
✗ |
const std::string root_path = data->root_path(); |
1109 |
|
✗ |
const std::string file_path = root_path + "/" |
1110 |
|
✗ |
+ nested_marker.name().ToString(); |
1111 |
|
|
const shash::Md5 &path_hash = shash::Md5(file_path.data(), |
1112 |
|
✗ |
file_path.size()); |
1113 |
|
|
const shash::Md5 &parent_hash = shash::Md5(root_path.data(), |
1114 |
|
✗ |
root_path.size()); |
1115 |
|
✗ |
retval = insert_nested_marker.BindPathHash(path_hash) |
1116 |
|
✗ |
&& insert_nested_marker.BindParentPathHash(parent_hash) |
1117 |
|
✗ |
&& insert_nested_marker.BindDirent(nested_marker) |
1118 |
|
✗ |
&& insert_nested_marker.BindXattrEmpty() |
1119 |
|
✗ |
&& insert_nested_marker.Execute(); |
1120 |
|
✗ |
if (!retval) { |
1121 |
|
✗ |
Error("Failed to insert nested catalog marker into new nested catalog.", |
1122 |
|
|
insert_nested_marker, data); |
1123 |
|
✗ |
return false; |
1124 |
|
|
} |
1125 |
|
|
} |
1126 |
|
|
|
1127 |
|
|
// Copy (and update) the properties fields |
1128 |
|
|
// |
1129 |
|
|
// Note: The 'schema' is explicitly not copied to the new catalog. |
1130 |
|
|
// Each catalog contains a revision, which is also copied here and that |
1131 |
|
|
// is later updated by calling catalog->IncrementRevision() |
1132 |
|
|
catalog::SqlCatalog copy_properties(writable, |
1133 |
|
|
"INSERT OR REPLACE INTO properties " |
1134 |
|
|
" SELECT key, value " |
1135 |
|
|
" FROM old.properties " |
1136 |
|
✗ |
" WHERE key != 'schema';"); |
1137 |
|
✗ |
retval = copy_properties.Execute(); |
1138 |
|
✗ |
if (!retval) { |
1139 |
|
✗ |
Error("Failed to migrate the properties table.", copy_properties, data); |
1140 |
|
✗ |
return false; |
1141 |
|
|
} |
1142 |
|
|
|
1143 |
|
✗ |
return true; |
1144 |
|
|
} |
1145 |
|
|
|
1146 |
|
|
|
1147 |
|
✗ |
bool CommandMigrate::MigrationWorker_20x::AnalyzeFileLinkcounts( |
1148 |
|
|
PendingCatalog *data) const { |
1149 |
|
✗ |
assert(data->HasNew()); |
1150 |
|
✗ |
const catalog::CatalogDatabase &writable = data->new_catalog->database(); |
1151 |
|
|
bool retval; |
1152 |
|
|
|
1153 |
|
|
// Analyze the hardlink relationships in the old catalog |
1154 |
|
|
// inodes used to be assigned at publishing time, implicitly constituating |
1155 |
|
|
// those relationships. We now need them explicitly in the file catalogs |
1156 |
|
|
// This looks for directory entries with matching inodes but differing path- |
1157 |
|
|
// hashes and saves the results in a temporary table called 'hl_scratch' |
1158 |
|
|
// |
1159 |
|
|
// Note: We only support hardlink groups that reside in the same directory! |
1160 |
|
|
// Therefore we first need to figure out hardlink candidates (which |
1161 |
|
|
// might still contain hardlink groups spanning more than one directory) |
1162 |
|
|
// In a second step these candidates will be analyzed to kick out un- |
1163 |
|
|
// supported hardlink groups. |
1164 |
|
|
// Unsupported hardlink groups will be be treated as normal files with |
1165 |
|
|
// the same content |
1166 |
|
|
catalog::SqlCatalog sql_create_hardlinks_scratch_table( |
1167 |
|
|
writable, |
1168 |
|
|
"CREATE TEMPORARY TABLE hl_scratch AS " |
1169 |
|
|
" SELECT c1.inode AS inode, c1.md5path_1, c1.md5path_2, " |
1170 |
|
|
" c1.parent_1 as c1p1, c1.parent_2 as c1p2, " |
1171 |
|
|
" c2.parent_1 as c2p1, c2.parent_2 as c2p2 " |
1172 |
|
|
" FROM old.catalog AS c1 " |
1173 |
|
|
" INNER JOIN old.catalog AS c2 " |
1174 |
|
|
" ON c1.inode == c2.inode AND " |
1175 |
|
|
" (c1.md5path_1 != c2.md5path_1 OR " |
1176 |
|
✗ |
" c1.md5path_2 != c2.md5path_2);"); |
1177 |
|
✗ |
retval = sql_create_hardlinks_scratch_table.Execute(); |
1178 |
|
✗ |
if (!retval) { |
1179 |
|
✗ |
Error("Failed to create temporary scratch table for hardlink analysis", |
1180 |
|
|
sql_create_hardlinks_scratch_table, data); |
1181 |
|
✗ |
return false; |
1182 |
|
|
} |
1183 |
|
|
|
1184 |
|
|
// Figures out which hardlink candidates are supported by CVMFS and can be |
1185 |
|
|
// transferred into the new catalog as so called hardlink groups. Unsupported |
1186 |
|
|
// hardlinks need to be discarded and treated as normal files containing the |
1187 |
|
|
// exact same data |
1188 |
|
|
catalog::SqlCatalog fill_linkcount_table_for_files( |
1189 |
|
|
writable, |
1190 |
|
|
"INSERT INTO hardlinks (inode, linkcount)" |
1191 |
|
|
" SELECT inode, count(*) as linkcount " |
1192 |
|
|
" FROM ( " |
1193 |
|
|
// recombine supported hardlink inodes with their actual manifested |
1194 |
|
|
// hard-links in the catalog. |
1195 |
|
|
// Note: for each directory entry pointing to the same supported |
1196 |
|
|
// hardlink inode we have a distinct MD5 path hash |
1197 |
|
|
" SELECT DISTINCT hl.inode, hl.md5path_1, hl.md5path_2 " |
1198 |
|
|
" FROM ( " |
1199 |
|
|
// sort out supported hardlink inodes from unsupported ones by |
1200 |
|
|
// locality |
1201 |
|
|
// Note: see the next comment for the nested SELECT |
1202 |
|
|
" SELECT inode " |
1203 |
|
|
" FROM ( " |
1204 |
|
|
" SELECT inode, count(*) AS cnt " |
1205 |
|
|
" FROM ( " |
1206 |
|
|
// go through the potential hardlinks and collect location infor- |
1207 |
|
|
// mation about them. |
1208 |
|
|
// Note: we only support hardlinks that all reside in the same |
1209 |
|
|
// directory, thus having the same parent (c1p* == c2p*) |
1210 |
|
|
// --> For supported hardlink candidates the SELECT DISTINCT |
1211 |
|
|
// will produce only a single row, whereas others produce more |
1212 |
|
|
" SELECT DISTINCT inode,c1p1,c1p1,c2p1,c2p2 " |
1213 |
|
|
" FROM hl_scratch AS hl " |
1214 |
|
|
" ) " |
1215 |
|
|
" GROUP BY inode " |
1216 |
|
|
" ) " |
1217 |
|
|
" WHERE cnt = 1 " |
1218 |
|
|
" ) AS supported_hardlinks " |
1219 |
|
|
" LEFT JOIN hl_scratch AS hl " |
1220 |
|
|
" ON supported_hardlinks.inode = hl.inode " |
1221 |
|
|
" ) " |
1222 |
|
✗ |
" GROUP BY inode;"); |
1223 |
|
✗ |
retval = fill_linkcount_table_for_files.Execute(); |
1224 |
|
✗ |
if (!retval) { |
1225 |
|
✗ |
Error("Failed to analyze hardlink relationships for files.", |
1226 |
|
|
fill_linkcount_table_for_files, data); |
1227 |
|
✗ |
return false; |
1228 |
|
|
} |
1229 |
|
|
|
1230 |
|
|
// The file linkcount and hardlink analysis is finished and the scratch table |
1231 |
|
|
// can be deleted... |
1232 |
|
|
catalog::SqlCatalog drop_hardlink_scratch_space(writable, |
1233 |
|
✗ |
"DROP TABLE hl_scratch;"); |
1234 |
|
✗ |
retval = drop_hardlink_scratch_space.Execute(); |
1235 |
|
✗ |
if (!retval) { |
1236 |
|
✗ |
Error("Failed to remove file linkcount analysis scratch table", |
1237 |
|
|
drop_hardlink_scratch_space, data); |
1238 |
|
✗ |
return false; |
1239 |
|
|
} |
1240 |
|
|
|
1241 |
|
|
// Do some statistics if asked for... |
1242 |
|
✗ |
if (collect_catalog_statistics_) { |
1243 |
|
|
catalog::SqlCatalog count_hardlinks( |
1244 |
|
✗ |
writable, "SELECT count(*), sum(linkcount) FROM hardlinks;"); |
1245 |
|
✗ |
retval = count_hardlinks.FetchRow(); |
1246 |
|
✗ |
if (!retval) { |
1247 |
|
✗ |
Error("Failed to count the generated file hardlinks for statistics", |
1248 |
|
|
count_hardlinks, data); |
1249 |
|
✗ |
return false; |
1250 |
|
|
} |
1251 |
|
|
|
1252 |
|
✗ |
data->statistics.hardlink_group_count += count_hardlinks.RetrieveInt64(0); |
1253 |
|
✗ |
data->statistics.aggregated_linkcounts += count_hardlinks.RetrieveInt64(1); |
1254 |
|
|
} |
1255 |
|
|
|
1256 |
|
✗ |
return true; |
1257 |
|
|
} |
1258 |
|
|
|
1259 |
|
|
|
1260 |
|
✗ |
bool CommandMigrate::MigrationWorker_20x::MigrateNestedCatalogMountPoints( |
1261 |
|
|
PendingCatalog *data) const { |
1262 |
|
✗ |
assert(data->HasNew()); |
1263 |
|
✗ |
const catalog::CatalogDatabase &writable = data->new_catalog->database(); |
1264 |
|
|
bool retval; |
1265 |
|
|
|
1266 |
|
|
// preparing the SQL statement for nested catalog mountpoint update |
1267 |
|
|
catalog::SqlCatalog update_mntpnt_linkcount( |
1268 |
|
|
writable, |
1269 |
|
|
"UPDATE catalog " |
1270 |
|
|
"SET hardlinks = :linkcount " |
1271 |
|
✗ |
"WHERE md5path_1 = :md5_1 AND md5path_2 = :md5_2;"); |
1272 |
|
|
|
1273 |
|
|
// update all nested catalog mountpoints |
1274 |
|
|
// (Note: we might need to wait for the nested catalog to be processed) |
1275 |
|
✗ |
PendingCatalogList::const_iterator i = data->nested_catalogs.begin(); |
1276 |
|
✗ |
const PendingCatalogList::const_iterator iend = data->nested_catalogs.end(); |
1277 |
|
✗ |
for (; i != iend; ++i) { |
1278 |
|
|
// collect information about the nested catalog |
1279 |
|
✗ |
PendingCatalog *nested_catalog = *i; |
1280 |
|
✗ |
const catalog::DirectoryEntry root_entry = nested_catalog->root_entry.Get(); |
1281 |
|
✗ |
const string &root_path = nested_catalog->root_path(); |
1282 |
|
|
|
1283 |
|
|
// update the nested catalog mountpoint directory entry with the correct |
1284 |
|
|
// linkcount that was determined while processing the nested catalog |
1285 |
|
|
const shash::Md5 mountpoint_hash = shash::Md5(root_path.data(), |
1286 |
|
✗ |
root_path.size()); |
1287 |
|
✗ |
retval = update_mntpnt_linkcount.BindInt64(1, root_entry.linkcount()) |
1288 |
|
✗ |
&& update_mntpnt_linkcount.BindMd5(2, 3, mountpoint_hash) |
1289 |
|
✗ |
&& update_mntpnt_linkcount.Execute(); |
1290 |
|
✗ |
if (!retval) { |
1291 |
|
✗ |
Error("Failed to update linkcount of nested catalog mountpoint", |
1292 |
|
|
update_mntpnt_linkcount, data); |
1293 |
|
✗ |
return false; |
1294 |
|
|
} |
1295 |
|
✗ |
update_mntpnt_linkcount.Reset(); |
1296 |
|
|
} |
1297 |
|
|
|
1298 |
|
✗ |
return true; |
1299 |
|
|
} |
1300 |
|
|
|
1301 |
|
|
|
1302 |
|
✗ |
bool CommandMigrate::MigrationWorker_20x::FixNestedCatalogTransitionPoints( |
1303 |
|
|
PendingCatalog *data) const { |
1304 |
|
✗ |
assert(data->HasNew()); |
1305 |
|
✗ |
if (!fix_nested_catalog_transitions_) { |
1306 |
|
|
// Fixing transition point mismatches is not enabled... |
1307 |
|
✗ |
return true; |
1308 |
|
|
} |
1309 |
|
|
|
1310 |
|
|
typedef catalog::DirectoryEntry::Difference Difference; |
1311 |
|
|
|
1312 |
|
✗ |
const catalog::CatalogDatabase &writable = data->new_catalog->database(); |
1313 |
|
|
bool retval; |
1314 |
|
|
|
1315 |
|
✗ |
catalog::SqlLookupPathHash lookup_mountpoint(writable); |
1316 |
|
✗ |
catalog::SqlDirentUpdate update_directory_entry(writable); |
1317 |
|
|
|
1318 |
|
|
// Unbox the nested catalogs (possibly waiting for migration of them first) |
1319 |
|
✗ |
PendingCatalogList::const_iterator i = data->nested_catalogs.begin(); |
1320 |
|
✗ |
const PendingCatalogList::const_iterator iend = data->nested_catalogs.end(); |
1321 |
|
✗ |
for (; i != iend; ++i) { |
1322 |
|
|
// Collect information about the nested catalog |
1323 |
|
✗ |
PendingCatalog *nested_catalog = *i; |
1324 |
|
|
const catalog::DirectoryEntry nested_root_entry = nested_catalog->root_entry |
1325 |
|
✗ |
.Get(); |
1326 |
|
✗ |
const string &nested_root_path = nested_catalog->root_path(); |
1327 |
|
|
const shash::Md5 mountpoint_path_hash = shash::Md5(nested_root_path.data(), |
1328 |
|
✗ |
nested_root_path.size()); |
1329 |
|
|
|
1330 |
|
|
// Retrieve the nested catalog mountpoint from the current catalog |
1331 |
|
✗ |
retval = lookup_mountpoint.BindPathHash(mountpoint_path_hash) |
1332 |
|
✗ |
&& lookup_mountpoint.FetchRow(); |
1333 |
|
✗ |
if (!retval) { |
1334 |
|
✗ |
Error("Failed to fetch nested catalog mountpoint to check for compatible" |
1335 |
|
|
"transition points", |
1336 |
|
|
lookup_mountpoint, data); |
1337 |
|
✗ |
return false; |
1338 |
|
|
} |
1339 |
|
|
|
1340 |
|
|
catalog::DirectoryEntry mountpoint_entry = lookup_mountpoint.GetDirent( |
1341 |
|
✗ |
data->new_catalog); |
1342 |
|
✗ |
lookup_mountpoint.Reset(); |
1343 |
|
|
|
1344 |
|
|
// Compare nested catalog mountpoint and nested catalog root entries |
1345 |
|
|
const catalog::DirectoryEntry::Differences diffs = |
1346 |
|
✗ |
mountpoint_entry.CompareTo(nested_root_entry); |
1347 |
|
|
|
1348 |
|
|
// We MUST deal with two directory entries that are a pair of nested cata- |
1349 |
|
|
// log mountpoint and root entry! Thus we expect their transition flags to |
1350 |
|
|
// differ and their name to be the same. |
1351 |
|
✗ |
assert(diffs & Difference::kNestedCatalogTransitionFlags); |
1352 |
|
✗ |
assert((diffs & Difference::kName) == 0); |
1353 |
|
|
|
1354 |
|
|
// Check if there are other differences except the nested catalog transition |
1355 |
|
|
// flags and fix them... |
1356 |
|
✗ |
if ((diffs ^ Difference::kNestedCatalogTransitionFlags) != 0) { |
1357 |
|
|
// If we found differences, we still assume a couple of directory entry |
1358 |
|
|
// fields to be the same, otherwise some severe stuff would be wrong... |
1359 |
|
✗ |
if ((diffs & Difference::kChecksum) || (diffs & Difference::kLinkcount) |
1360 |
|
✗ |
|| (diffs & Difference::kSymlink) |
1361 |
|
✗ |
|| (diffs & Difference::kChunkedFileFlag)) { |
1362 |
|
✗ |
Error("Found an irreparable mismatch in a nested catalog transition " |
1363 |
|
|
"point at '" |
1364 |
|
✗ |
+ nested_root_path + "'\nAborting...\n"); |
1365 |
|
|
} |
1366 |
|
|
|
1367 |
|
|
// Copy the properties from the nested catalog root entry into the mount- |
1368 |
|
|
// point entry to bring them in sync again |
1369 |
|
✗ |
CommandMigrate::FixNestedCatalogTransitionPoint(nested_root_entry, |
1370 |
|
|
&mountpoint_entry); |
1371 |
|
|
|
1372 |
|
|
// save the nested catalog mountpoint entry into the catalog |
1373 |
|
✗ |
retval = update_directory_entry.BindPathHash(mountpoint_path_hash) |
1374 |
|
✗ |
&& update_directory_entry.BindDirent(mountpoint_entry) |
1375 |
|
✗ |
&& update_directory_entry.Execute(); |
1376 |
|
✗ |
if (!retval) { |
1377 |
|
✗ |
Error("Failed to save resynchronized nested catalog mountpoint into " |
1378 |
|
|
"catalog database", |
1379 |
|
|
update_directory_entry, data); |
1380 |
|
✗ |
return false; |
1381 |
|
|
} |
1382 |
|
✗ |
update_directory_entry.Reset(); |
1383 |
|
|
|
1384 |
|
|
// Fixing of this mountpoint went well... inform the user that this minor |
1385 |
|
|
// issue occurred |
1386 |
|
✗ |
LogCvmfs(kLogCatalog, kLogStdout, |
1387 |
|
|
"NOTE: fixed incompatible nested catalog transition point at: " |
1388 |
|
|
"'%s' ", |
1389 |
|
|
nested_root_path.c_str()); |
1390 |
|
|
} |
1391 |
|
|
} |
1392 |
|
|
|
1393 |
|
✗ |
return true; |
1394 |
|
|
} |
1395 |
|
|
|
1396 |
|
|
|
1397 |
|
✗ |
void CommandMigrate::FixNestedCatalogTransitionPoint( |
1398 |
|
|
const catalog::DirectoryEntry &nested_root, |
1399 |
|
|
catalog::DirectoryEntry *mountpoint) { |
1400 |
|
|
// Replace some file system parameters in the mountpoint to resync it with |
1401 |
|
|
// the nested root of the corresponding nested catalog |
1402 |
|
|
// |
1403 |
|
|
// Note: this method relies on CommandMigrate being a friend of DirectoryEntry |
1404 |
|
✗ |
mountpoint->mode_ = nested_root.mode_; |
1405 |
|
✗ |
mountpoint->uid_ = nested_root.uid_; |
1406 |
|
✗ |
mountpoint->gid_ = nested_root.gid_; |
1407 |
|
✗ |
mountpoint->size_ = nested_root.size_; |
1408 |
|
✗ |
mountpoint->mtime_ = nested_root.mtime_; |
1409 |
|
|
} |
1410 |
|
|
|
1411 |
|
|
|
1412 |
|
✗ |
bool CommandMigrate::MigrationWorker_20x::RemoveDanglingNestedMountpoints( |
1413 |
|
|
PendingCatalog *data) const { |
1414 |
|
✗ |
assert(data->HasNew()); |
1415 |
|
✗ |
const catalog::CatalogDatabase &writable = data->new_catalog->database(); |
1416 |
|
✗ |
bool retval = false; |
1417 |
|
|
|
1418 |
|
|
// build a set of registered nested catalog path hashes |
1419 |
|
|
typedef catalog::Catalog::NestedCatalogList NestedCatalogList; |
1420 |
|
|
typedef std::map<shash::Md5, catalog::Catalog::NestedCatalog> |
1421 |
|
|
NestedCatalogMap; |
1422 |
|
✗ |
const NestedCatalogList &nested_clgs = data->old_catalog |
1423 |
|
✗ |
->ListNestedCatalogs(); |
1424 |
|
✗ |
NestedCatalogList::const_iterator i = nested_clgs.begin(); |
1425 |
|
✗ |
const NestedCatalogList::const_iterator iend = nested_clgs.end(); |
1426 |
|
✗ |
NestedCatalogMap nested_catalog_path_hashes; |
1427 |
|
✗ |
for (; i != iend; ++i) { |
1428 |
|
✗ |
const PathString &path = i->mountpoint; |
1429 |
|
✗ |
const shash::Md5 hash(path.GetChars(), path.GetLength()); |
1430 |
|
✗ |
nested_catalog_path_hashes[hash] = *i; |
1431 |
|
|
} |
1432 |
|
|
|
1433 |
|
|
// Retrieve nested catalog mountpoints that have child entries directly inside |
1434 |
|
|
// the current catalog (which is a malformed state) |
1435 |
|
✗ |
catalog::SqlLookupDanglingMountpoints sql_dangling_mountpoints(writable); |
1436 |
|
✗ |
catalog::SqlDirentUpdate save_updated_mountpoint(writable); |
1437 |
|
|
|
1438 |
|
✗ |
std::vector<catalog::DirectoryEntry> todo_dirent; |
1439 |
|
✗ |
std::vector<shash::Md5> todo_hash; |
1440 |
|
|
|
1441 |
|
|
// go through the list of dangling nested catalog mountpoints and fix them |
1442 |
|
|
// where needed (check if there is no nested catalog registered for them) |
1443 |
|
✗ |
while (sql_dangling_mountpoints.FetchRow()) { |
1444 |
|
|
catalog::DirectoryEntry dangling_mountpoint = sql_dangling_mountpoints |
1445 |
|
|
.GetDirent( |
1446 |
|
✗ |
data->new_catalog); |
1447 |
|
✗ |
const shash::Md5 path_hash = sql_dangling_mountpoints.GetPathHash(); |
1448 |
|
✗ |
assert(dangling_mountpoint.IsNestedCatalogMountpoint()); |
1449 |
|
|
|
1450 |
|
|
// check if the nested catalog mountpoint is registered in the nested cata- |
1451 |
|
|
// log list of the currently migrated catalog |
1452 |
|
|
const NestedCatalogMap::const_iterator |
1453 |
|
✗ |
nested_catalog = nested_catalog_path_hashes.find(path_hash); |
1454 |
|
✗ |
if (nested_catalog != nested_catalog_path_hashes.end()) { |
1455 |
|
✗ |
LogCvmfs(kLogCatalog, kLogStderr, |
1456 |
|
|
"WARNING: found a non-empty nested catalog mountpoint under " |
1457 |
|
|
"'%s'", |
1458 |
|
|
nested_catalog->second.mountpoint.c_str()); |
1459 |
|
✗ |
continue; |
1460 |
|
|
} |
1461 |
|
|
|
1462 |
|
|
// the mountpoint was confirmed to be dangling and needs to be removed |
1463 |
|
✗ |
dangling_mountpoint.set_is_nested_catalog_mountpoint(false); |
1464 |
|
✗ |
todo_dirent.push_back(dangling_mountpoint); |
1465 |
|
✗ |
todo_hash.push_back(path_hash); |
1466 |
|
|
} |
1467 |
|
|
|
1468 |
|
✗ |
for (unsigned i = 0; i < todo_dirent.size(); ++i) { |
1469 |
|
✗ |
retval = save_updated_mountpoint.BindPathHash(todo_hash[i]) |
1470 |
|
✗ |
&& save_updated_mountpoint.BindDirent(todo_dirent[i]) |
1471 |
|
✗ |
&& save_updated_mountpoint.Execute() |
1472 |
|
✗ |
&& save_updated_mountpoint.Reset(); |
1473 |
|
✗ |
if (!retval) { |
1474 |
|
✗ |
Error("Failed to remove dangling nested catalog mountpoint entry in " |
1475 |
|
|
"catalog", |
1476 |
|
|
save_updated_mountpoint, data); |
1477 |
|
✗ |
return false; |
1478 |
|
|
} |
1479 |
|
|
|
1480 |
|
|
// tell the user that this intervention has been taken place |
1481 |
|
✗ |
LogCvmfs(kLogCatalog, kLogStdout, |
1482 |
|
|
"NOTE: fixed dangling nested catalog " |
1483 |
|
|
"mountpoint entry called: '%s' ", |
1484 |
|
|
todo_dirent[i].name().c_str()); |
1485 |
|
|
} |
1486 |
|
|
|
1487 |
|
✗ |
return true; |
1488 |
|
|
} |
1489 |
|
|
|
1490 |
|
|
|
1491 |
|
✗ |
const catalog::DirectoryEntry &CommandMigrate::GetNestedCatalogMarkerDirent() { |
1492 |
|
|
// This is pre-initialized singleton... it MUST be already there... |
1493 |
|
✗ |
assert(nested_catalog_marker_.name_.ToString() == ".cvmfscatalog"); |
1494 |
|
✗ |
return nested_catalog_marker_; |
1495 |
|
|
} |
1496 |
|
|
|
1497 |
|
✗ |
bool CommandMigrate::GenerateNestedCatalogMarkerChunk() { |
1498 |
|
|
// Create an empty nested catalog marker file |
1499 |
|
✗ |
nested_catalog_marker_tmp_path_ = CreateTempPath( |
1500 |
|
✗ |
temporary_directory_ + "/.cvmfscatalog", 0644); |
1501 |
|
✗ |
if (nested_catalog_marker_tmp_path_.empty()) { |
1502 |
|
✗ |
Error("Failed to create temp file for nested catalog marker dummy."); |
1503 |
|
✗ |
return false; |
1504 |
|
|
} |
1505 |
|
|
|
1506 |
|
|
// Process and upload it to the backend storage |
1507 |
|
|
IngestionSource *source = new FileIngestionSource( |
1508 |
|
✗ |
nested_catalog_marker_tmp_path_); |
1509 |
|
✗ |
spooler_->Process(source); |
1510 |
|
✗ |
return true; |
1511 |
|
|
} |
1512 |
|
|
|
1513 |
|
✗ |
void CommandMigrate::CreateNestedCatalogMarkerDirent( |
1514 |
|
|
const shash::Any &content_hash) { |
1515 |
|
|
// Generate it only once |
1516 |
|
✗ |
assert(nested_catalog_marker_.name_.ToString() != ".cvmfscatalog"); |
1517 |
|
|
|
1518 |
|
|
// Fill the DirectoryEntry structure will all needed information |
1519 |
|
✗ |
nested_catalog_marker_.name_.Assign(".cvmfscatalog", strlen(".cvmfscatalog")); |
1520 |
|
✗ |
nested_catalog_marker_.mode_ = 33188; |
1521 |
|
✗ |
nested_catalog_marker_.uid_ = uid_; |
1522 |
|
✗ |
nested_catalog_marker_.gid_ = gid_; |
1523 |
|
✗ |
nested_catalog_marker_.size_ = 0; |
1524 |
|
✗ |
nested_catalog_marker_.mtime_ = time(NULL); |
1525 |
|
✗ |
nested_catalog_marker_.linkcount_ = 1; |
1526 |
|
✗ |
nested_catalog_marker_.checksum_ = content_hash; |
1527 |
|
|
} |
1528 |
|
|
|
1529 |
|
|
|
1530 |
|
✗ |
bool CommandMigrate::MigrationWorker_20x::GenerateCatalogStatistics( |
1531 |
|
|
PendingCatalog *data) const { |
1532 |
|
✗ |
assert(data->HasNew()); |
1533 |
|
✗ |
bool retval = false; |
1534 |
|
✗ |
const catalog::CatalogDatabase &writable = data->new_catalog->database(); |
1535 |
|
|
|
1536 |
|
|
// Aggregated the statistics counters of all nested catalogs |
1537 |
|
|
// Note: we might need to wait until nested catalogs are successfully |
1538 |
|
|
// processed |
1539 |
|
✗ |
catalog::DeltaCounters stats_counters; |
1540 |
|
✗ |
PendingCatalogList::const_iterator i = data->nested_catalogs.begin(); |
1541 |
|
✗ |
const PendingCatalogList::const_iterator iend = data->nested_catalogs.end(); |
1542 |
|
✗ |
for (; i != iend; ++i) { |
1543 |
|
✗ |
const PendingCatalog *nested_catalog = *i; |
1544 |
|
✗ |
const catalog::DeltaCounters &s = nested_catalog->nested_statistics.Get(); |
1545 |
|
✗ |
s.PopulateToParent(&stats_counters); |
1546 |
|
|
} |
1547 |
|
|
|
1548 |
|
|
// Count various directory entry types in the catalog to fill up the catalog |
1549 |
|
|
// statistics counters introduced in the current catalog schema |
1550 |
|
|
catalog::SqlCatalog count_regular_files( |
1551 |
|
|
writable, |
1552 |
|
|
"SELECT count(*) FROM catalog " |
1553 |
|
|
" WHERE flags & :flag_file " |
1554 |
|
✗ |
" AND NOT flags & :flag_link;"); |
1555 |
|
|
catalog::SqlCatalog count_symlinks( |
1556 |
|
✗ |
writable, "SELECT count(*) FROM catalog WHERE flags & :flag_link;"); |
1557 |
|
|
catalog::SqlCatalog count_directories( |
1558 |
|
✗ |
writable, "SELECT count(*) FROM catalog WHERE flags & :flag_dir;"); |
1559 |
|
|
catalog::SqlCatalog aggregate_file_size( |
1560 |
|
|
writable, |
1561 |
|
|
"SELECT sum(size) FROM catalog WHERE flags & :flag_file " |
1562 |
|
✗ |
" AND NOT flags & :flag_link"); |
1563 |
|
|
|
1564 |
|
|
// Run the actual counting queries |
1565 |
|
✗ |
retval = count_regular_files.BindInt64(1, catalog::SqlDirent::kFlagFile) |
1566 |
|
✗ |
&& count_regular_files.BindInt64(2, catalog::SqlDirent::kFlagLink) |
1567 |
|
✗ |
&& count_regular_files.FetchRow(); |
1568 |
|
✗ |
if (!retval) { |
1569 |
|
✗ |
Error("Failed to count regular files.", count_regular_files, data); |
1570 |
|
✗ |
return false; |
1571 |
|
|
} |
1572 |
|
✗ |
retval = count_symlinks.BindInt64(1, catalog::SqlDirent::kFlagLink) |
1573 |
|
✗ |
&& count_symlinks.FetchRow(); |
1574 |
|
✗ |
if (!retval) { |
1575 |
|
✗ |
Error("Failed to count symlinks.", count_symlinks, data); |
1576 |
|
✗ |
return false; |
1577 |
|
|
} |
1578 |
|
✗ |
retval = count_directories.BindInt64(1, catalog::SqlDirent::kFlagDir) |
1579 |
|
✗ |
&& count_directories.FetchRow(); |
1580 |
|
✗ |
if (!retval) { |
1581 |
|
✗ |
Error("Failed to count directories.", count_directories, data); |
1582 |
|
✗ |
return false; |
1583 |
|
|
} |
1584 |
|
✗ |
retval = aggregate_file_size.BindInt64(1, catalog::SqlDirent::kFlagFile) |
1585 |
|
✗ |
&& aggregate_file_size.BindInt64(2, catalog::SqlDirent::kFlagLink) |
1586 |
|
✗ |
&& aggregate_file_size.FetchRow(); |
1587 |
|
✗ |
if (!retval) { |
1588 |
|
✗ |
Error("Failed to aggregate the file sizes.", aggregate_file_size, data); |
1589 |
|
✗ |
return false; |
1590 |
|
|
} |
1591 |
|
|
|
1592 |
|
|
// Insert the counted statistics into the DeltaCounters data structure |
1593 |
|
✗ |
stats_counters.self.regular_files = count_regular_files.RetrieveInt64(0); |
1594 |
|
✗ |
stats_counters.self.symlinks = count_symlinks.RetrieveInt64(0); |
1595 |
|
✗ |
stats_counters.self.directories = count_directories.RetrieveInt64(0); |
1596 |
|
✗ |
stats_counters.self.nested_catalogs = data->nested_catalogs.size(); |
1597 |
|
✗ |
stats_counters.self.file_size = aggregate_file_size.RetrieveInt64(0); |
1598 |
|
|
|
1599 |
|
|
// Write back the generated statistics counters into the catalog database |
1600 |
|
✗ |
stats_counters.WriteToDatabase(writable); |
1601 |
|
|
|
1602 |
|
|
// Push the generated statistics counters up to the parent catalog |
1603 |
|
✗ |
data->nested_statistics.Set(stats_counters); |
1604 |
|
|
|
1605 |
|
✗ |
return true; |
1606 |
|
|
} |
1607 |
|
|
|
1608 |
|
|
|
1609 |
|
✗ |
bool CommandMigrate::MigrationWorker_20x::FindRootEntryInformation( |
1610 |
|
|
PendingCatalog *data) const { |
1611 |
|
✗ |
const catalog::CatalogDatabase &writable = data->new_catalog->database(); |
1612 |
|
|
bool retval; |
1613 |
|
|
|
1614 |
|
✗ |
std::string root_path = data->root_path(); |
1615 |
|
|
const shash::Md5 root_path_hash = |
1616 |
|
✗ |
shash::Md5(root_path.data(), root_path.size()); |
1617 |
|
|
|
1618 |
|
✗ |
catalog::SqlLookupPathHash lookup_root_entry(writable); |
1619 |
|
✗ |
retval = lookup_root_entry.BindPathHash(root_path_hash) |
1620 |
|
✗ |
&& lookup_root_entry.FetchRow(); |
1621 |
|
✗ |
if (!retval) { |
1622 |
|
✗ |
Error("Failed to retrieve root directory entry of migrated catalog", |
1623 |
|
|
lookup_root_entry, data); |
1624 |
|
✗ |
return false; |
1625 |
|
|
} |
1626 |
|
|
|
1627 |
|
|
const catalog::DirectoryEntry entry = |
1628 |
|
✗ |
lookup_root_entry.GetDirent(data->new_catalog); |
1629 |
|
✗ |
if (entry.linkcount() < 2 || entry.hardlink_group() > 0) { |
1630 |
|
✗ |
Error("Retrieved linkcount of catalog root entry is not sane.", data); |
1631 |
|
✗ |
return false; |
1632 |
|
|
} |
1633 |
|
|
|
1634 |
|
✗ |
data->root_entry.Set(entry); |
1635 |
|
✗ |
return true; |
1636 |
|
|
} |
1637 |
|
|
|
1638 |
|
|
|
1639 |
|
✗ |
bool CommandMigrate::MigrationWorker_20x::CommitDatabaseTransaction( |
1640 |
|
|
PendingCatalog *data) const { |
1641 |
|
✗ |
assert(data->HasNew()); |
1642 |
|
✗ |
data->new_catalog->Commit(); |
1643 |
|
✗ |
return true; |
1644 |
|
|
} |
1645 |
|
|
|
1646 |
|
|
|
1647 |
|
✗ |
bool CommandMigrate::MigrationWorker_20x::DetachOldCatalogDatabase( |
1648 |
|
|
PendingCatalog *data) const { |
1649 |
|
✗ |
assert(data->HasNew()); |
1650 |
|
✗ |
const catalog::CatalogDatabase &writable = data->new_catalog->database(); |
1651 |
|
✗ |
catalog::SqlCatalog detach_old_catalog(writable, "DETACH old;"); |
1652 |
|
✗ |
const bool retval = detach_old_catalog.Execute(); |
1653 |
|
✗ |
if (!retval) { |
1654 |
|
✗ |
Error("Failed to detach old catalog database.", detach_old_catalog, data); |
1655 |
|
✗ |
return false; |
1656 |
|
|
} |
1657 |
|
✗ |
return true; |
1658 |
|
|
} |
1659 |
|
|
|
1660 |
|
|
|
1661 |
|
|
//------------------------------------------------------------------------------ |
1662 |
|
|
|
1663 |
|
|
|
1664 |
|
✗ |
CommandMigrate::MigrationWorker_217::MigrationWorker_217( |
1665 |
|
✗ |
const worker_context *context) |
1666 |
|
✗ |
: AbstractMigrationWorker<MigrationWorker_217>(context) { } |
1667 |
|
|
|
1668 |
|
|
|
1669 |
|
✗ |
bool CommandMigrate::MigrationWorker_217::RunMigration( |
1670 |
|
|
PendingCatalog *data) const { |
1671 |
|
✗ |
return CheckDatabaseSchemaCompatibility(data) |
1672 |
|
✗ |
&& StartDatabaseTransaction(data) |
1673 |
|
✗ |
&& GenerateNewStatisticsCounters(data) && UpdateCatalogSchema(data) |
1674 |
|
✗ |
&& CommitDatabaseTransaction(data); |
1675 |
|
|
} |
1676 |
|
|
|
1677 |
|
|
|
1678 |
|
✗ |
bool CommandMigrate::MigrationWorker_217::CheckDatabaseSchemaCompatibility( |
1679 |
|
|
PendingCatalog *data) const { |
1680 |
|
✗ |
assert(!data->HasNew()); |
1681 |
|
✗ |
const catalog::CatalogDatabase &old_catalog = data->old_catalog->database(); |
1682 |
|
|
|
1683 |
|
✗ |
if ((old_catalog.schema_version() |
1684 |
|
|
< 2.4 - catalog::CatalogDatabase::kSchemaEpsilon) |
1685 |
|
✗ |
|| (old_catalog.schema_version() |
1686 |
|
|
> 2.4 + catalog::CatalogDatabase::kSchemaEpsilon)) { |
1687 |
|
✗ |
Error("Given Catalog is not Schema 2.4.", data); |
1688 |
|
✗ |
return false; |
1689 |
|
|
} |
1690 |
|
|
|
1691 |
|
✗ |
return true; |
1692 |
|
|
} |
1693 |
|
|
|
1694 |
|
|
|
1695 |
|
✗ |
bool CommandMigrate::MigrationWorker_217::StartDatabaseTransaction( |
1696 |
|
|
PendingCatalog *data) const { |
1697 |
|
✗ |
assert(!data->HasNew()); |
1698 |
|
✗ |
GetWritable(data->old_catalog)->Transaction(); |
1699 |
|
✗ |
return true; |
1700 |
|
|
} |
1701 |
|
|
|
1702 |
|
|
|
1703 |
|
✗ |
bool CommandMigrate::MigrationWorker_217::GenerateNewStatisticsCounters( |
1704 |
|
|
PendingCatalog *data) const { |
1705 |
|
✗ |
assert(!data->HasNew()); |
1706 |
|
✗ |
bool retval = false; |
1707 |
|
✗ |
const catalog::CatalogDatabase &writable = GetWritable(data->old_catalog) |
1708 |
|
✗ |
->database(); |
1709 |
|
|
|
1710 |
|
|
// Aggregated the statistics counters of all nested catalogs |
1711 |
|
|
// Note: we might need to wait until nested catalogs are successfully |
1712 |
|
|
// processed |
1713 |
|
✗ |
catalog::DeltaCounters stats_counters; |
1714 |
|
✗ |
PendingCatalogList::const_iterator i = data->nested_catalogs.begin(); |
1715 |
|
✗ |
const PendingCatalogList::const_iterator iend = data->nested_catalogs.end(); |
1716 |
|
✗ |
for (; i != iend; ++i) { |
1717 |
|
✗ |
const PendingCatalog *nested_catalog = *i; |
1718 |
|
✗ |
const catalog::DeltaCounters &s = nested_catalog->nested_statistics.Get(); |
1719 |
|
✗ |
s.PopulateToParent(&stats_counters); |
1720 |
|
|
} |
1721 |
|
|
|
1722 |
|
|
// Count various directory entry types in the catalog to fill up the catalog |
1723 |
|
|
// statistics counters introduced in the current catalog schema |
1724 |
|
|
catalog::SqlCatalog count_chunked_files( |
1725 |
|
|
writable, |
1726 |
|
|
"SELECT count(*), sum(size) FROM catalog " |
1727 |
|
✗ |
" WHERE flags & :flag_chunked_file;"); |
1728 |
|
|
catalog::SqlCatalog count_file_chunks(writable, |
1729 |
|
✗ |
"SELECT count(*) FROM chunks;"); |
1730 |
|
|
catalog::SqlCatalog aggregate_file_size( |
1731 |
|
|
writable, |
1732 |
|
|
"SELECT sum(size) FROM catalog WHERE flags & :flag_file " |
1733 |
|
✗ |
" AND NOT flags & :flag_link;"); |
1734 |
|
|
|
1735 |
|
|
// Run the actual counting queries |
1736 |
|
✗ |
retval = count_chunked_files.BindInt64(1, catalog::SqlDirent::kFlagFileChunk) |
1737 |
|
✗ |
&& count_chunked_files.FetchRow(); |
1738 |
|
✗ |
if (!retval) { |
1739 |
|
✗ |
Error("Failed to count chunked files.", count_chunked_files, data); |
1740 |
|
✗ |
return false; |
1741 |
|
|
} |
1742 |
|
✗ |
retval = count_file_chunks.FetchRow(); |
1743 |
|
✗ |
if (!retval) { |
1744 |
|
✗ |
Error("Failed to count file chunks", count_file_chunks, data); |
1745 |
|
✗ |
return false; |
1746 |
|
|
} |
1747 |
|
✗ |
retval = aggregate_file_size.BindInt64(1, catalog::SqlDirent::kFlagFile) |
1748 |
|
✗ |
&& aggregate_file_size.BindInt64(2, catalog::SqlDirent::kFlagLink) |
1749 |
|
✗ |
&& aggregate_file_size.FetchRow(); |
1750 |
|
✗ |
if (!retval) { |
1751 |
|
✗ |
Error("Failed to aggregate the file sizes.", aggregate_file_size, data); |
1752 |
|
✗ |
return false; |
1753 |
|
|
} |
1754 |
|
|
|
1755 |
|
|
// Insert the counted statistics into the DeltaCounters data structure |
1756 |
|
✗ |
stats_counters.self.chunked_files = count_chunked_files.RetrieveInt64(0); |
1757 |
|
✗ |
stats_counters.self.chunked_file_size = count_chunked_files.RetrieveInt64(1); |
1758 |
|
✗ |
stats_counters.self.file_chunks = count_file_chunks.RetrieveInt64(0); |
1759 |
|
✗ |
stats_counters.self.file_size = aggregate_file_size.RetrieveInt64(0); |
1760 |
|
|
|
1761 |
|
|
// Write back the generated statistics counters into the catalog database |
1762 |
|
✗ |
catalog::Counters counters; |
1763 |
|
✗ |
retval = counters.ReadFromDatabase(writable, catalog::LegacyMode::kLegacy); |
1764 |
|
✗ |
if (!retval) { |
1765 |
|
✗ |
Error("Failed to read old catalog statistics counters", data); |
1766 |
|
✗ |
return false; |
1767 |
|
|
} |
1768 |
|
✗ |
counters.ApplyDelta(stats_counters); |
1769 |
|
✗ |
retval = counters.InsertIntoDatabase(writable); |
1770 |
|
✗ |
if (!retval) { |
1771 |
|
✗ |
Error("Failed to write new statistics counters to database", data); |
1772 |
|
✗ |
return false; |
1773 |
|
|
} |
1774 |
|
|
|
1775 |
|
|
// Push the generated statistics counters up to the parent catalog |
1776 |
|
✗ |
data->nested_statistics.Set(stats_counters); |
1777 |
|
|
|
1778 |
|
✗ |
return true; |
1779 |
|
|
} |
1780 |
|
|
|
1781 |
|
|
|
1782 |
|
✗ |
bool CommandMigrate::MigrationWorker_217::UpdateCatalogSchema( |
1783 |
|
|
PendingCatalog *data) const { |
1784 |
|
✗ |
assert(!data->HasNew()); |
1785 |
|
✗ |
const catalog::CatalogDatabase &writable = GetWritable(data->old_catalog) |
1786 |
|
✗ |
->database(); |
1787 |
|
|
catalog::SqlCatalog update_schema_version( |
1788 |
|
|
writable, |
1789 |
|
✗ |
"UPDATE properties SET value = :schema_version WHERE key = 'schema';"); |
1790 |
|
|
|
1791 |
|
✗ |
const bool retval = update_schema_version.BindDouble(1, 2.5) |
1792 |
|
✗ |
&& update_schema_version.Execute(); |
1793 |
|
✗ |
if (!retval) { |
1794 |
|
✗ |
Error( |
1795 |
|
|
"Failed to update catalog schema version", update_schema_version, data); |
1796 |
|
✗ |
return false; |
1797 |
|
|
} |
1798 |
|
|
|
1799 |
|
✗ |
return true; |
1800 |
|
|
} |
1801 |
|
|
|
1802 |
|
|
|
1803 |
|
✗ |
bool CommandMigrate::MigrationWorker_217::CommitDatabaseTransaction( |
1804 |
|
|
PendingCatalog *data) const { |
1805 |
|
✗ |
assert(!data->HasNew()); |
1806 |
|
✗ |
GetWritable(data->old_catalog)->Commit(); |
1807 |
|
✗ |
return true; |
1808 |
|
|
} |
1809 |
|
|
|
1810 |
|
|
|
1811 |
|
|
//------------------------------------------------------------------------------ |
1812 |
|
|
|
1813 |
|
|
|
1814 |
|
✗ |
CommandMigrate::ChownMigrationWorker::ChownMigrationWorker( |
1815 |
|
✗ |
const worker_context *context) |
1816 |
|
|
: AbstractMigrationWorker<ChownMigrationWorker>(context) |
1817 |
|
✗ |
, uid_map_statement_(GenerateMappingStatement(context->uid_map, "uid")) |
1818 |
|
✗ |
, gid_map_statement_(GenerateMappingStatement(context->gid_map, "gid")) { } |
1819 |
|
|
|
1820 |
|
✗ |
bool CommandMigrate::ChownMigrationWorker::RunMigration( |
1821 |
|
|
PendingCatalog *data) const { |
1822 |
|
✗ |
return ApplyPersonaMappings(data); |
1823 |
|
|
} |
1824 |
|
|
|
1825 |
|
|
|
1826 |
|
✗ |
bool CommandMigrate::ChownMigrationWorker::ApplyPersonaMappings( |
1827 |
|
|
PendingCatalog *data) const { |
1828 |
|
✗ |
assert(data->old_catalog != NULL); |
1829 |
|
✗ |
assert(data->new_catalog == NULL); |
1830 |
|
|
|
1831 |
|
✗ |
if (data->old_catalog->mountpoint() |
1832 |
|
✗ |
== PathString("/" + string(catalog::VirtualCatalog::kVirtualPath))) { |
1833 |
|
|
// skipping virtual catalog |
1834 |
|
✗ |
return true; |
1835 |
|
|
} |
1836 |
|
|
|
1837 |
|
✗ |
const catalog::CatalogDatabase &db = GetWritable(data->old_catalog) |
1838 |
|
✗ |
->database(); |
1839 |
|
|
|
1840 |
|
✗ |
if (!db.BeginTransaction()) { |
1841 |
|
✗ |
return false; |
1842 |
|
|
} |
1843 |
|
|
|
1844 |
|
✗ |
catalog::SqlCatalog uid_sql(db, uid_map_statement_); |
1845 |
|
✗ |
if (!uid_sql.Execute()) { |
1846 |
|
✗ |
Error("Failed to update UIDs", uid_sql, data); |
1847 |
|
✗ |
return false; |
1848 |
|
|
} |
1849 |
|
|
|
1850 |
|
✗ |
catalog::SqlCatalog gid_sql(db, gid_map_statement_); |
1851 |
|
✗ |
if (!gid_sql.Execute()) { |
1852 |
|
✗ |
Error("Failed to update GIDs", gid_sql, data); |
1853 |
|
✗ |
return false; |
1854 |
|
|
} |
1855 |
|
|
|
1856 |
|
✗ |
return db.CommitTransaction(); |
1857 |
|
|
} |
1858 |
|
|
|
1859 |
|
|
|
1860 |
|
|
template<class MapT> |
1861 |
|
✗ |
std::string CommandMigrate::ChownMigrationWorker::GenerateMappingStatement( |
1862 |
|
|
const MapT &map, const std::string &column) const { |
1863 |
|
✗ |
assert(map.RuleCount() > 0 || map.HasDefault()); |
1864 |
|
|
|
1865 |
|
✗ |
std::string stmt = "UPDATE OR ABORT catalog SET " + column + " = "; |
1866 |
|
|
|
1867 |
|
✗ |
if (map.RuleCount() == 0) { |
1868 |
|
|
// map everything to the same value (just a simple UPDATE clause) |
1869 |
|
✗ |
stmt += StringifyInt(map.GetDefault()); |
1870 |
|
|
} else { |
1871 |
|
|
// apply multiple ID mappings (UPDATE clause with CASE statement) |
1872 |
|
✗ |
stmt += "CASE " + column + " "; |
1873 |
|
|
typedef typename MapT::map_type::const_iterator map_iterator; |
1874 |
|
✗ |
map_iterator i = map.GetRuleMap().begin(); |
1875 |
|
✗ |
const map_iterator iend = map.GetRuleMap().end(); |
1876 |
|
✗ |
for (; i != iend; ++i) { |
1877 |
|
✗ |
stmt += "WHEN " + StringifyInt(i->first) + " THEN " |
1878 |
|
✗ |
+ StringifyInt(i->second) + " "; |
1879 |
|
|
} |
1880 |
|
|
|
1881 |
|
|
// add a default (if provided) or leave unchanged if no mapping fits |
1882 |
|
✗ |
stmt += (map.HasDefault()) ? "ELSE " + StringifyInt(map.GetDefault()) + " " |
1883 |
|
|
: "ELSE " + column + " "; |
1884 |
|
✗ |
stmt += "END"; |
1885 |
|
|
} |
1886 |
|
|
|
1887 |
|
✗ |
stmt += ";"; |
1888 |
|
✗ |
return stmt; |
1889 |
|
|
} |
1890 |
|
|
|
1891 |
|
|
|
1892 |
|
|
//------------------------------------------------------------------------------ |
1893 |
|
|
|
1894 |
|
|
|
1895 |
|
✗ |
bool CommandMigrate::HardlinkRemovalMigrationWorker::RunMigration( |
1896 |
|
|
PendingCatalog *data) const { |
1897 |
|
✗ |
return CheckDatabaseSchemaCompatibility(data) && BreakUpHardlinks(data); |
1898 |
|
|
} |
1899 |
|
|
|
1900 |
|
|
|
1901 |
|
✗ |
bool CommandMigrate::HardlinkRemovalMigrationWorker:: |
1902 |
|
|
CheckDatabaseSchemaCompatibility(PendingCatalog *data) const { |
1903 |
|
✗ |
assert(data->old_catalog != NULL); |
1904 |
|
✗ |
assert(data->new_catalog == NULL); |
1905 |
|
|
|
1906 |
|
✗ |
const catalog::CatalogDatabase &clg = data->old_catalog->database(); |
1907 |
|
✗ |
return clg.schema_version() >= 2.4 - catalog::CatalogDatabase::kSchemaEpsilon; |
1908 |
|
|
} |
1909 |
|
|
|
1910 |
|
|
|
1911 |
|
✗ |
bool CommandMigrate::HardlinkRemovalMigrationWorker::BreakUpHardlinks( |
1912 |
|
|
PendingCatalog *data) const { |
1913 |
|
✗ |
assert(data->old_catalog != NULL); |
1914 |
|
✗ |
assert(data->new_catalog == NULL); |
1915 |
|
|
|
1916 |
|
✗ |
const catalog::CatalogDatabase &db = GetWritable(data->old_catalog) |
1917 |
|
✗ |
->database(); |
1918 |
|
|
|
1919 |
|
✗ |
if (!db.BeginTransaction()) { |
1920 |
|
✗ |
return false; |
1921 |
|
|
} |
1922 |
|
|
|
1923 |
|
|
// CernVM-FS catalogs do not contain inodes directly but they are assigned by |
1924 |
|
|
// the CVMFS catalog at runtime. Hardlinks are treated with so-called hardlink |
1925 |
|
|
// group IDs to indicate hardlink relationships that need to be respected at |
1926 |
|
|
// runtime by assigning identical inodes accordingly. |
1927 |
|
|
// |
1928 |
|
|
// This updates all directory entries of a given catalog that have a linkcount |
1929 |
|
|
// greater than 1 and are flagged as a 'file'. Note: Symlinks are flagged both |
1930 |
|
|
// as 'file' and as 'symlink', hence they are updated implicitly as well. |
1931 |
|
|
// |
1932 |
|
|
// The 'hardlinks' field in the catalog contains two 32 bit integers: |
1933 |
|
|
// * the linkcount in the lower 32 bits |
1934 |
|
|
// * the (so called) hardlink group ID in the higher 32 bits |
1935 |
|
|
// |
1936 |
|
|
// Files that have a linkcount of exactly 1 do not have any hardlinks and have |
1937 |
|
|
// the (implicit) hardlink group ID '0'. Hence, 'hardlinks == 1' means that a |
1938 |
|
|
// file doesn't have any hardlinks (linkcount = 1) and doesn't need treatment |
1939 |
|
|
// here. |
1940 |
|
|
// |
1941 |
|
|
// Files that have hardlinks (linkcount > 1) will have a very large integer in |
1942 |
|
|
// their 'hardlinks' field (hardlink group ID > 0 in higher 32 bits). Those |
1943 |
|
|
// files will be treated by setting their 'hardlinks' field to 1, effectively |
1944 |
|
|
// clearing all hardlink information from the directory entry. |
1945 |
|
|
const std::string stmt = "UPDATE OR ABORT catalog " |
1946 |
|
|
"SET hardlinks = 1 " |
1947 |
|
|
"WHERE flags & :file_flag " |
1948 |
|
✗ |
" AND hardlinks > 1;"; |
1949 |
|
✗ |
catalog::SqlCatalog hardlink_removal_sql(db, stmt); |
1950 |
|
✗ |
hardlink_removal_sql.BindInt64(1, catalog::SqlDirent::kFlagFile); |
1951 |
|
✗ |
hardlink_removal_sql.Execute(); |
1952 |
|
|
|
1953 |
|
✗ |
return db.CommitTransaction(); |
1954 |
|
|
} |
1955 |
|
|
|
1956 |
|
|
//------------------------------------------------------------------------------ |
1957 |
|
|
|
1958 |
|
|
|
1959 |
|
✗ |
bool CommandMigrate::BulkhashRemovalMigrationWorker::RunMigration( |
1960 |
|
|
PendingCatalog *data) const { |
1961 |
|
✗ |
return CheckDatabaseSchemaCompatibility(data) |
1962 |
|
✗ |
&& RemoveRedundantBulkHashes(data); |
1963 |
|
|
} |
1964 |
|
|
|
1965 |
|
|
|
1966 |
|
✗ |
bool CommandMigrate::BulkhashRemovalMigrationWorker:: |
1967 |
|
|
CheckDatabaseSchemaCompatibility(PendingCatalog *data) const { |
1968 |
|
✗ |
assert(data->old_catalog != NULL); |
1969 |
|
✗ |
assert(data->new_catalog == NULL); |
1970 |
|
|
|
1971 |
|
✗ |
const catalog::CatalogDatabase &clg = data->old_catalog->database(); |
1972 |
|
✗ |
return clg.schema_version() >= 2.4 - catalog::CatalogDatabase::kSchemaEpsilon; |
1973 |
|
|
} |
1974 |
|
|
|
1975 |
|
|
|
1976 |
|
✗ |
bool CommandMigrate::BulkhashRemovalMigrationWorker::RemoveRedundantBulkHashes( |
1977 |
|
|
PendingCatalog *data) const { |
1978 |
|
✗ |
assert(data->old_catalog != NULL); |
1979 |
|
✗ |
assert(data->new_catalog == NULL); |
1980 |
|
|
|
1981 |
|
✗ |
const catalog::CatalogDatabase &db = GetWritable(data->old_catalog) |
1982 |
|
✗ |
->database(); |
1983 |
|
|
|
1984 |
|
✗ |
if (!db.BeginTransaction()) { |
1985 |
|
✗ |
return false; |
1986 |
|
|
} |
1987 |
|
|
|
1988 |
|
|
// Regular files with both bulk hashes and chunked hashes can drop the bulk |
1989 |
|
|
// hash since modern clients >= 2.1.7 won't require them |
1990 |
|
|
const std::string stmt = "UPDATE OR ABORT catalog " |
1991 |
|
|
"SET hash = NULL " |
1992 |
|
✗ |
"WHERE flags & :file_chunked_flag;"; |
1993 |
|
✗ |
catalog::SqlCatalog bulkhash_removal_sql(db, stmt); |
1994 |
|
✗ |
bulkhash_removal_sql.BindInt64(1, catalog::SqlDirent::kFlagFileChunk); |
1995 |
|
✗ |
bulkhash_removal_sql.Execute(); |
1996 |
|
|
|
1997 |
|
✗ |
return db.CommitTransaction(); |
1998 |
|
|
} |
1999 |
|
|
|
2000 |
|
|
|
2001 |
|
|
//------------------------------------------------------------------------------ |
2002 |
|
|
|
2003 |
|
|
|
2004 |
|
✗ |
CommandMigrate::StatsMigrationWorker::StatsMigrationWorker( |
2005 |
|
✗ |
const worker_context *context) |
2006 |
|
✗ |
: AbstractMigrationWorker<StatsMigrationWorker>(context) { } |
2007 |
|
|
|
2008 |
|
|
|
2009 |
|
✗ |
bool CommandMigrate::StatsMigrationWorker::RunMigration( |
2010 |
|
|
PendingCatalog *data) const { |
2011 |
|
✗ |
return CheckDatabaseSchemaCompatibility(data) |
2012 |
|
✗ |
&& StartDatabaseTransaction(data) && RepairStatisticsCounters(data) |
2013 |
|
✗ |
&& CommitDatabaseTransaction(data); |
2014 |
|
|
} |
2015 |
|
|
|
2016 |
|
|
|
2017 |
|
✗ |
bool CommandMigrate::StatsMigrationWorker::CheckDatabaseSchemaCompatibility( |
2018 |
|
|
PendingCatalog *data) const { |
2019 |
|
✗ |
assert(data->old_catalog != NULL); |
2020 |
|
✗ |
assert(data->new_catalog == NULL); |
2021 |
|
|
|
2022 |
|
✗ |
const catalog::CatalogDatabase &clg = data->old_catalog->database(); |
2023 |
|
✗ |
if (clg.schema_version() < 2.5 - catalog::CatalogDatabase::kSchemaEpsilon) { |
2024 |
|
✗ |
Error("Given catalog schema is < 2.5.", data); |
2025 |
|
✗ |
return false; |
2026 |
|
|
} |
2027 |
|
|
|
2028 |
|
✗ |
if (clg.schema_revision() < 5) { |
2029 |
|
✗ |
Error("Given catalog revision is < 5", data); |
2030 |
|
✗ |
return false; |
2031 |
|
|
} |
2032 |
|
|
|
2033 |
|
✗ |
return true; |
2034 |
|
|
} |
2035 |
|
|
|
2036 |
|
|
|
2037 |
|
✗ |
bool CommandMigrate::StatsMigrationWorker::StartDatabaseTransaction( |
2038 |
|
|
PendingCatalog *data) const { |
2039 |
|
✗ |
assert(!data->HasNew()); |
2040 |
|
✗ |
GetWritable(data->old_catalog)->Transaction(); |
2041 |
|
✗ |
return true; |
2042 |
|
|
} |
2043 |
|
|
|
2044 |
|
|
|
2045 |
|
✗ |
bool CommandMigrate::StatsMigrationWorker::RepairStatisticsCounters( |
2046 |
|
|
PendingCatalog *data) const { |
2047 |
|
✗ |
assert(!data->HasNew()); |
2048 |
|
✗ |
bool retval = false; |
2049 |
|
✗ |
const catalog::CatalogDatabase &writable = GetWritable(data->old_catalog) |
2050 |
|
✗ |
->database(); |
2051 |
|
|
|
2052 |
|
|
// Aggregated the statistics counters of all nested catalogs |
2053 |
|
|
// Note: we might need to wait until nested catalogs are successfully |
2054 |
|
|
// processed |
2055 |
|
✗ |
catalog::DeltaCounters stats_counters; |
2056 |
|
✗ |
PendingCatalogList::const_iterator i = data->nested_catalogs.begin(); |
2057 |
|
✗ |
const PendingCatalogList::const_iterator iend = data->nested_catalogs.end(); |
2058 |
|
✗ |
for (; i != iend; ++i) { |
2059 |
|
✗ |
const PendingCatalog *nested_catalog = *i; |
2060 |
|
✗ |
const catalog::DeltaCounters &s = nested_catalog->nested_statistics.Get(); |
2061 |
|
✗ |
s.PopulateToParent(&stats_counters); |
2062 |
|
|
} |
2063 |
|
|
|
2064 |
|
|
// Count various directory entry types in the catalog to fill up the catalog |
2065 |
|
|
// statistics counters introduced in the current catalog schema |
2066 |
|
|
catalog::SqlCatalog count_regular( |
2067 |
|
|
writable, |
2068 |
|
✗ |
std::string("SELECT count(*), sum(size) FROM catalog ") + "WHERE flags & " |
2069 |
|
✗ |
+ StringifyInt(catalog::SqlDirent::kFlagFile) + " AND NOT flags & " |
2070 |
|
✗ |
+ StringifyInt(catalog::SqlDirent::kFlagLink) + " AND NOT flags & " |
2071 |
|
✗ |
+ StringifyInt(catalog::SqlDirent::kFlagFileSpecial) + ";"); |
2072 |
|
|
catalog::SqlCatalog count_external( |
2073 |
|
|
writable, |
2074 |
|
✗ |
std::string("SELECT count(*), sum(size) FROM catalog ") + "WHERE flags & " |
2075 |
|
✗ |
+ StringifyInt(catalog::SqlDirent::kFlagFileExternal) + ";"); |
2076 |
|
|
catalog::SqlCatalog count_symlink( |
2077 |
|
|
writable, |
2078 |
|
✗ |
std::string("SELECT count(*) FROM catalog ") + "WHERE flags & " |
2079 |
|
✗ |
+ StringifyInt(catalog::SqlDirent::kFlagLink) + ";"); |
2080 |
|
|
catalog::SqlCatalog count_special( |
2081 |
|
|
writable, |
2082 |
|
✗ |
std::string("SELECT count(*) FROM catalog ") + "WHERE flags & " |
2083 |
|
✗ |
+ StringifyInt(catalog::SqlDirent::kFlagFileSpecial) + ";"); |
2084 |
|
|
catalog::SqlCatalog count_xattr(writable, |
2085 |
|
✗ |
std::string("SELECT count(*) FROM catalog ") |
2086 |
|
✗ |
+ "WHERE xattr IS NOT NULL;"); |
2087 |
|
|
catalog::SqlCatalog count_chunk( |
2088 |
|
|
writable, |
2089 |
|
✗ |
std::string("SELECT count(*), sum(size) FROM catalog ") + "WHERE flags & " |
2090 |
|
✗ |
+ StringifyInt(catalog::SqlDirent::kFlagFileChunk) + ";"); |
2091 |
|
|
catalog::SqlCatalog count_dir( |
2092 |
|
|
writable, |
2093 |
|
✗ |
std::string("SELECT count(*) FROM catalog ") + "WHERE flags & " |
2094 |
|
✗ |
+ StringifyInt(catalog::SqlDirent::kFlagDir) + ";"); |
2095 |
|
|
catalog::SqlCatalog count_chunk_blobs(writable, |
2096 |
|
✗ |
"SELECT count(*) FROM chunks;"); |
2097 |
|
|
|
2098 |
|
✗ |
retval = count_regular.FetchRow() && count_external.FetchRow() |
2099 |
|
✗ |
&& count_symlink.FetchRow() && count_special.FetchRow() |
2100 |
|
✗ |
&& count_xattr.FetchRow() && count_chunk.FetchRow() |
2101 |
|
✗ |
&& count_dir.FetchRow() && count_chunk_blobs.FetchRow(); |
2102 |
|
✗ |
if (!retval) { |
2103 |
|
✗ |
Error("Failed to collect catalog statistics", data); |
2104 |
|
✗ |
return false; |
2105 |
|
|
} |
2106 |
|
|
|
2107 |
|
✗ |
stats_counters.self.regular_files = count_regular.RetrieveInt64(0); |
2108 |
|
✗ |
stats_counters.self.symlinks = count_symlink.RetrieveInt64(0); |
2109 |
|
✗ |
stats_counters.self.specials = count_special.RetrieveInt64(0); |
2110 |
|
✗ |
stats_counters.self.directories = count_dir.RetrieveInt64(0); |
2111 |
|
✗ |
stats_counters.self.nested_catalogs = data->nested_catalogs.size(); |
2112 |
|
✗ |
stats_counters.self.chunked_files = count_chunk.RetrieveInt64(0); |
2113 |
|
✗ |
stats_counters.self.file_chunks = count_chunk_blobs.RetrieveInt64(0); |
2114 |
|
✗ |
stats_counters.self.file_size = count_regular.RetrieveInt64(1); |
2115 |
|
✗ |
stats_counters.self.chunked_file_size = count_chunk.RetrieveInt64(1); |
2116 |
|
✗ |
stats_counters.self.xattrs = count_xattr.RetrieveInt64(0); |
2117 |
|
✗ |
stats_counters.self.externals = count_external.RetrieveInt64(0); |
2118 |
|
✗ |
stats_counters.self.external_file_size = count_external.RetrieveInt64(1); |
2119 |
|
|
|
2120 |
|
|
// Write back the generated statistics counters into the catalog database |
2121 |
|
✗ |
catalog::Counters counters; |
2122 |
|
✗ |
counters.ApplyDelta(stats_counters); |
2123 |
|
✗ |
retval = counters.InsertIntoDatabase(writable); |
2124 |
|
✗ |
if (!retval) { |
2125 |
|
✗ |
Error("Failed to write new statistics counters to database", data); |
2126 |
|
✗ |
return false; |
2127 |
|
|
} |
2128 |
|
|
|
2129 |
|
|
// Push the generated statistics counters up to the parent catalog |
2130 |
|
✗ |
data->nested_statistics.Set(stats_counters); |
2131 |
|
|
|
2132 |
|
✗ |
return true; |
2133 |
|
|
} |
2134 |
|
|
|
2135 |
|
|
|
2136 |
|
✗ |
bool CommandMigrate::StatsMigrationWorker::CommitDatabaseTransaction( |
2137 |
|
|
PendingCatalog *data) const { |
2138 |
|
✗ |
assert(!data->HasNew()); |
2139 |
|
✗ |
GetWritable(data->old_catalog)->Commit(); |
2140 |
|
✗ |
return true; |
2141 |
|
|
} |
2142 |
|
|
|
2143 |
|
|
} // namespace swissknife |
2144 |
|
|
|