]> git.proxmox.com Git - ceph.git/blob - ceph/src/rocksdb/db/repair_test.cc
import quincy beta 17.1.0
[ceph.git] / ceph / src / rocksdb / db / repair_test.cc
1 // Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
2 // This source code is licensed under both the GPLv2 (found in the
3 // COPYING file in the root directory) and Apache 2.0 License
4 // (found in the LICENSE.Apache file in the root directory).
5
6 #ifndef ROCKSDB_LITE
7
8 #include <algorithm>
9 #include <string>
10 #include <vector>
11
12 #include "db/db_impl/db_impl.h"
13 #include "db/db_test_util.h"
14 #include "file/file_util.h"
15 #include "rocksdb/comparator.h"
16 #include "rocksdb/db.h"
17 #include "rocksdb/transaction_log.h"
18 #include "util/string_util.h"
19
20 namespace ROCKSDB_NAMESPACE {
21
22 #ifndef ROCKSDB_LITE
23 class RepairTest : public DBTestBase {
24 public:
25 RepairTest() : DBTestBase("/repair_test", /*env_do_fsync=*/true) {}
26
27 Status GetFirstSstPath(std::string* first_sst_path) {
28 assert(first_sst_path != nullptr);
29 first_sst_path->clear();
30 uint64_t manifest_size;
31 std::vector<std::string> files;
32 Status s = db_->GetLiveFiles(files, &manifest_size);
33 if (s.ok()) {
34 auto sst_iter =
35 std::find_if(files.begin(), files.end(), [](const std::string& file) {
36 uint64_t number;
37 FileType type;
38 bool ok = ParseFileName(file, &number, &type);
39 return ok && type == kTableFile;
40 });
41 *first_sst_path = sst_iter == files.end() ? "" : dbname_ + *sst_iter;
42 }
43 return s;
44 }
45 };
46
47 TEST_F(RepairTest, LostManifest) {
48 // Add a couple SST files, delete the manifest, and verify RepairDB() saves
49 // the day.
50 ASSERT_OK(Put("key", "val"));
51 ASSERT_OK(Flush());
52 ASSERT_OK(Put("key2", "val2"));
53 ASSERT_OK(Flush());
54 // Need to get path before Close() deletes db_, but delete it after Close() to
55 // ensure Close() didn't change the manifest.
56 std::string manifest_path =
57 DescriptorFileName(dbname_, dbfull()->TEST_Current_Manifest_FileNo());
58
59 Close();
60 ASSERT_OK(env_->FileExists(manifest_path));
61 ASSERT_OK(env_->DeleteFile(manifest_path));
62 ASSERT_OK(RepairDB(dbname_, CurrentOptions()));
63 Reopen(CurrentOptions());
64
65 ASSERT_EQ(Get("key"), "val");
66 ASSERT_EQ(Get("key2"), "val2");
67 }
68
69 TEST_F(RepairTest, CorruptManifest) {
70 // Manifest is in an invalid format. Expect a full recovery.
71 ASSERT_OK(Put("key", "val"));
72 ASSERT_OK(Flush());
73 ASSERT_OK(Put("key2", "val2"));
74 ASSERT_OK(Flush());
75 // Need to get path before Close() deletes db_, but overwrite it after Close()
76 // to ensure Close() didn't change the manifest.
77 std::string manifest_path =
78 DescriptorFileName(dbname_, dbfull()->TEST_Current_Manifest_FileNo());
79
80 Close();
81 ASSERT_OK(env_->FileExists(manifest_path));
82
83 LegacyFileSystemWrapper fs(env_);
84 ASSERT_OK(CreateFile(&fs, manifest_path, "blah", false /* use_fsync */));
85 ASSERT_OK(RepairDB(dbname_, CurrentOptions()));
86 Reopen(CurrentOptions());
87
88 ASSERT_EQ(Get("key"), "val");
89 ASSERT_EQ(Get("key2"), "val2");
90 }
91
92 TEST_F(RepairTest, IncompleteManifest) {
93 // In this case, the manifest is valid but does not reference all of the SST
94 // files. Expect a full recovery.
95 ASSERT_OK(Put("key", "val"));
96 ASSERT_OK(Flush());
97 std::string orig_manifest_path =
98 DescriptorFileName(dbname_, dbfull()->TEST_Current_Manifest_FileNo());
99 CopyFile(orig_manifest_path, orig_manifest_path + ".tmp");
100 ASSERT_OK(Put("key2", "val2"));
101 ASSERT_OK(Flush());
102 // Need to get path before Close() deletes db_, but overwrite it after Close()
103 // to ensure Close() didn't change the manifest.
104 std::string new_manifest_path =
105 DescriptorFileName(dbname_, dbfull()->TEST_Current_Manifest_FileNo());
106
107 Close();
108 ASSERT_OK(env_->FileExists(new_manifest_path));
109 // Replace the manifest with one that is only aware of the first SST file.
110 CopyFile(orig_manifest_path + ".tmp", new_manifest_path);
111 ASSERT_OK(RepairDB(dbname_, CurrentOptions()));
112 Reopen(CurrentOptions());
113
114 ASSERT_EQ(Get("key"), "val");
115 ASSERT_EQ(Get("key2"), "val2");
116 }
117
118 TEST_F(RepairTest, PostRepairSstFileNumbering) {
119 // Verify after a DB is repaired, new files will be assigned higher numbers
120 // than old files.
121 ASSERT_OK(Put("key", "val"));
122 ASSERT_OK(Flush());
123 ASSERT_OK(Put("key2", "val2"));
124 ASSERT_OK(Flush());
125 uint64_t pre_repair_file_num = dbfull()->TEST_Current_Next_FileNo();
126 Close();
127
128 ASSERT_OK(RepairDB(dbname_, CurrentOptions()));
129
130 Reopen(CurrentOptions());
131 uint64_t post_repair_file_num = dbfull()->TEST_Current_Next_FileNo();
132 ASSERT_GE(post_repair_file_num, pre_repair_file_num);
133 }
134
135 TEST_F(RepairTest, LostSst) {
136 // Delete one of the SST files but preserve the manifest that refers to it,
137 // then verify the DB is still usable for the intact SST.
138 ASSERT_OK(Put("key", "val"));
139 ASSERT_OK(Flush());
140 ASSERT_OK(Put("key2", "val2"));
141 ASSERT_OK(Flush());
142 std::string sst_path;
143 ASSERT_OK(GetFirstSstPath(&sst_path));
144 ASSERT_FALSE(sst_path.empty());
145 ASSERT_OK(env_->DeleteFile(sst_path));
146
147 Close();
148 ASSERT_OK(RepairDB(dbname_, CurrentOptions()));
149 Reopen(CurrentOptions());
150
151 // Exactly one of the key-value pairs should be in the DB now.
152 ASSERT_TRUE((Get("key") == "val") != (Get("key2") == "val2"));
153 }
154
155 TEST_F(RepairTest, CorruptSst) {
156 // Corrupt one of the SST files but preserve the manifest that refers to it,
157 // then verify the DB is still usable for the intact SST.
158 ASSERT_OK(Put("key", "val"));
159 ASSERT_OK(Flush());
160 ASSERT_OK(Put("key2", "val2"));
161 ASSERT_OK(Flush());
162 std::string sst_path;
163 ASSERT_OK(GetFirstSstPath(&sst_path));
164 ASSERT_FALSE(sst_path.empty());
165
166 LegacyFileSystemWrapper fs(env_);
167 ASSERT_OK(CreateFile(&fs, sst_path, "blah", false /* use_fsync */));
168
169 Close();
170 ASSERT_OK(RepairDB(dbname_, CurrentOptions()));
171 Reopen(CurrentOptions());
172
173 // Exactly one of the key-value pairs should be in the DB now.
174 ASSERT_TRUE((Get("key") == "val") != (Get("key2") == "val2"));
175 }
176
177 TEST_F(RepairTest, UnflushedSst) {
178 // This test case invokes repair while some data is unflushed, then verifies
179 // that data is in the db.
180 ASSERT_OK(Put("key", "val"));
181 VectorLogPtr wal_files;
182 ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files));
183 ASSERT_EQ(wal_files.size(), 1);
184 {
185 uint64_t total_ssts_size;
186 std::unordered_map<std::string, uint64_t> sst_files;
187 ASSERT_OK(GetAllSSTFiles(&sst_files, &total_ssts_size));
188 ASSERT_EQ(total_ssts_size, 0);
189 }
190 // Need to get path before Close() deletes db_, but delete it after Close() to
191 // ensure Close() didn't change the manifest.
192 std::string manifest_path =
193 DescriptorFileName(dbname_, dbfull()->TEST_Current_Manifest_FileNo());
194
195 Close();
196 ASSERT_OK(env_->FileExists(manifest_path));
197 ASSERT_OK(env_->DeleteFile(manifest_path));
198 ASSERT_OK(RepairDB(dbname_, CurrentOptions()));
199 Reopen(CurrentOptions());
200
201 ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files));
202 ASSERT_EQ(wal_files.size(), 0);
203 {
204 uint64_t total_ssts_size;
205 std::unordered_map<std::string, uint64_t> sst_files;
206 ASSERT_OK(GetAllSSTFiles(&sst_files, &total_ssts_size));
207 ASSERT_GT(total_ssts_size, 0);
208 }
209 ASSERT_EQ(Get("key"), "val");
210 }
211
212 TEST_F(RepairTest, SeparateWalDir) {
213 do {
214 Options options = CurrentOptions();
215 DestroyAndReopen(options);
216 ASSERT_OK(Put("key", "val"));
217 ASSERT_OK(Put("foo", "bar"));
218 VectorLogPtr wal_files;
219 ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files));
220 ASSERT_EQ(wal_files.size(), 1);
221 {
222 uint64_t total_ssts_size;
223 std::unordered_map<std::string, uint64_t> sst_files;
224 ASSERT_OK(GetAllSSTFiles(&sst_files, &total_ssts_size));
225 ASSERT_EQ(total_ssts_size, 0);
226 }
227 std::string manifest_path =
228 DescriptorFileName(dbname_, dbfull()->TEST_Current_Manifest_FileNo());
229
230 Close();
231 ASSERT_OK(env_->FileExists(manifest_path));
232 ASSERT_OK(env_->DeleteFile(manifest_path));
233 ASSERT_OK(RepairDB(dbname_, options));
234
235 // make sure that all WALs are converted to SSTables.
236 options.wal_dir = "";
237
238 Reopen(options);
239 ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files));
240 ASSERT_EQ(wal_files.size(), 0);
241 {
242 uint64_t total_ssts_size;
243 std::unordered_map<std::string, uint64_t> sst_files;
244 ASSERT_OK(GetAllSSTFiles(&sst_files, &total_ssts_size));
245 ASSERT_GT(total_ssts_size, 0);
246 }
247 ASSERT_EQ(Get("key"), "val");
248 ASSERT_EQ(Get("foo"), "bar");
249
250 } while(ChangeWalOptions());
251 }
252
253 TEST_F(RepairTest, RepairMultipleColumnFamilies) {
254 // Verify repair logic associates SST files with their original column
255 // families.
256 const int kNumCfs = 3;
257 const int kEntriesPerCf = 2;
258 DestroyAndReopen(CurrentOptions());
259 CreateAndReopenWithCF({"pikachu1", "pikachu2"}, CurrentOptions());
260 for (int i = 0; i < kNumCfs; ++i) {
261 for (int j = 0; j < kEntriesPerCf; ++j) {
262 ASSERT_OK(Put(i, "key" + ToString(j), "val" + ToString(j)));
263 if (j == kEntriesPerCf - 1 && i == kNumCfs - 1) {
264 // Leave one unflushed so we can verify WAL entries are properly
265 // associated with column families.
266 continue;
267 }
268 ASSERT_OK(Flush(i));
269 }
270 }
271
272 // Need to get path before Close() deletes db_, but delete it after Close() to
273 // ensure Close() doesn't re-create the manifest.
274 std::string manifest_path =
275 DescriptorFileName(dbname_, dbfull()->TEST_Current_Manifest_FileNo());
276 Close();
277 ASSERT_OK(env_->FileExists(manifest_path));
278 ASSERT_OK(env_->DeleteFile(manifest_path));
279
280 ASSERT_OK(RepairDB(dbname_, CurrentOptions()));
281
282 ReopenWithColumnFamilies({"default", "pikachu1", "pikachu2"},
283 CurrentOptions());
284 for (int i = 0; i < kNumCfs; ++i) {
285 for (int j = 0; j < kEntriesPerCf; ++j) {
286 ASSERT_EQ(Get(i, "key" + ToString(j)), "val" + ToString(j));
287 }
288 }
289 }
290
291 TEST_F(RepairTest, RepairColumnFamilyOptions) {
292 // Verify repair logic uses correct ColumnFamilyOptions when repairing a
293 // database with different options for column families.
294 const int kNumCfs = 2;
295 const int kEntriesPerCf = 2;
296
297 Options opts(CurrentOptions()), rev_opts(CurrentOptions());
298 opts.comparator = BytewiseComparator();
299 rev_opts.comparator = ReverseBytewiseComparator();
300
301 DestroyAndReopen(opts);
302 CreateColumnFamilies({"reverse"}, rev_opts);
303 ReopenWithColumnFamilies({"default", "reverse"},
304 std::vector<Options>{opts, rev_opts});
305 for (int i = 0; i < kNumCfs; ++i) {
306 for (int j = 0; j < kEntriesPerCf; ++j) {
307 ASSERT_OK(Put(i, "key" + ToString(j), "val" + ToString(j)));
308 if (i == kNumCfs - 1 && j == kEntriesPerCf - 1) {
309 // Leave one unflushed so we can verify RepairDB's flush logic
310 continue;
311 }
312 ASSERT_OK(Flush(i));
313 }
314 }
315 Close();
316
317 // RepairDB() records the comparator in the manifest, and DB::Open would fail
318 // if a different comparator were used.
319 ASSERT_OK(RepairDB(dbname_, opts, {{"default", opts}, {"reverse", rev_opts}},
320 opts /* unknown_cf_opts */));
321 ASSERT_OK(TryReopenWithColumnFamilies({"default", "reverse"},
322 std::vector<Options>{opts, rev_opts}));
323 for (int i = 0; i < kNumCfs; ++i) {
324 for (int j = 0; j < kEntriesPerCf; ++j) {
325 ASSERT_EQ(Get(i, "key" + ToString(j)), "val" + ToString(j));
326 }
327 }
328
329 // Examine table properties to verify RepairDB() used the right options when
330 // converting WAL->SST
331 TablePropertiesCollection fname_to_props;
332 ASSERT_OK(db_->GetPropertiesOfAllTables(handles_[1], &fname_to_props));
333 ASSERT_EQ(fname_to_props.size(), 2U);
334 for (const auto& fname_and_props : fname_to_props) {
335 std::string comparator_name (
336 InternalKeyComparator(rev_opts.comparator).Name());
337 comparator_name = comparator_name.substr(comparator_name.find(':') + 1);
338 ASSERT_EQ(comparator_name,
339 fname_and_props.second->comparator_name);
340 }
341 Close();
342
343 // Also check comparator when it's provided via "unknown" CF options
344 ASSERT_OK(RepairDB(dbname_, opts, {{"default", opts}},
345 rev_opts /* unknown_cf_opts */));
346 ASSERT_OK(TryReopenWithColumnFamilies({"default", "reverse"},
347 std::vector<Options>{opts, rev_opts}));
348 for (int i = 0; i < kNumCfs; ++i) {
349 for (int j = 0; j < kEntriesPerCf; ++j) {
350 ASSERT_EQ(Get(i, "key" + ToString(j)), "val" + ToString(j));
351 }
352 }
353 }
354
355 TEST_F(RepairTest, DbNameContainsTrailingSlash) {
356 {
357 bool tmp;
358 if (env_->AreFilesSame("", "", &tmp).IsNotSupported()) {
359 fprintf(stderr,
360 "skipping RepairTest.DbNameContainsTrailingSlash due to "
361 "unsupported Env::AreFilesSame\n");
362 return;
363 }
364 }
365
366 ASSERT_OK(Put("key", "val"));
367 ASSERT_OK(Flush());
368 Close();
369
370 ASSERT_OK(RepairDB(dbname_ + "/", CurrentOptions()));
371 Reopen(CurrentOptions());
372 ASSERT_EQ(Get("key"), "val");
373 }
374 #endif // ROCKSDB_LITE
375 } // namespace ROCKSDB_NAMESPACE
376
377 int main(int argc, char** argv) {
378 ::testing::InitGoogleTest(&argc, argv);
379 return RUN_ALL_TESTS();
380 }
381
382 #else
383 #include <stdio.h>
384
385 int main(int /*argc*/, char** /*argv*/) {
386 fprintf(stderr, "SKIPPED as RepairDB is not supported in ROCKSDB_LITE\n");
387 return 0;
388 }
389
390 #endif // ROCKSDB_LITE