]>
Commit | Line | Data |
---|---|---|
7c673cae | 1 | // Copyright (c) 2016-present, Facebook, Inc. All rights reserved. |
11fdf7f2 TL |
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). | |
7c673cae FG |
5 | |
6 | #ifndef ROCKSDB_LITE | |
7 | ||
8 | #include <algorithm> | |
9 | #include <string> | |
10 | #include <vector> | |
11 | ||
f67539c2 | 12 | #include "db/db_impl/db_impl.h" |
7c673cae | 13 | #include "db/db_test_util.h" |
f67539c2 | 14 | #include "file/file_util.h" |
7c673cae FG |
15 | #include "rocksdb/comparator.h" |
16 | #include "rocksdb/db.h" | |
17 | #include "rocksdb/transaction_log.h" | |
7c673cae FG |
18 | #include "util/string_util.h" |
19 | ||
f67539c2 | 20 | namespace ROCKSDB_NAMESPACE { |
7c673cae FG |
21 | |
22 | #ifndef ROCKSDB_LITE | |
23 | class RepairTest : public DBTestBase { | |
24 | public: | |
20effc67 | 25 | RepairTest() : DBTestBase("/repair_test", /*env_do_fsync=*/true) {} |
7c673cae | 26 | |
20effc67 TL |
27 | Status GetFirstSstPath(std::string* first_sst_path) { |
28 | assert(first_sst_path != nullptr); | |
29 | first_sst_path->clear(); | |
7c673cae FG |
30 | uint64_t manifest_size; |
31 | std::vector<std::string> files; | |
20effc67 TL |
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; | |
7c673cae FG |
44 | } |
45 | }; | |
46 | ||
47 | TEST_F(RepairTest, LostManifest) { | |
48 | // Add a couple SST files, delete the manifest, and verify RepairDB() saves | |
49 | // the day. | |
20effc67 TL |
50 | ASSERT_OK(Put("key", "val")); |
51 | ASSERT_OK(Flush()); | |
52 | ASSERT_OK(Put("key2", "val2")); | |
53 | ASSERT_OK(Flush()); | |
7c673cae FG |
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. | |
20effc67 TL |
71 | ASSERT_OK(Put("key", "val")); |
72 | ASSERT_OK(Flush()); | |
73 | ASSERT_OK(Put("key2", "val2")); | |
74 | ASSERT_OK(Flush()); | |
7c673cae FG |
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)); | |
f67539c2 TL |
82 | |
83 | LegacyFileSystemWrapper fs(env_); | |
20effc67 | 84 | ASSERT_OK(CreateFile(&fs, manifest_path, "blah", false /* use_fsync */)); |
7c673cae FG |
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. | |
20effc67 TL |
95 | ASSERT_OK(Put("key", "val")); |
96 | ASSERT_OK(Flush()); | |
7c673cae FG |
97 | std::string orig_manifest_path = |
98 | DescriptorFileName(dbname_, dbfull()->TEST_Current_Manifest_FileNo()); | |
99 | CopyFile(orig_manifest_path, orig_manifest_path + ".tmp"); | |
20effc67 TL |
100 | ASSERT_OK(Put("key2", "val2")); |
101 | ASSERT_OK(Flush()); | |
7c673cae FG |
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 | ||
11fdf7f2 TL |
118 | TEST_F(RepairTest, PostRepairSstFileNumbering) { |
119 | // Verify after a DB is repaired, new files will be assigned higher numbers | |
120 | // than old files. | |
20effc67 TL |
121 | ASSERT_OK(Put("key", "val")); |
122 | ASSERT_OK(Flush()); | |
123 | ASSERT_OK(Put("key2", "val2")); | |
124 | ASSERT_OK(Flush()); | |
11fdf7f2 TL |
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 | ||
7c673cae FG |
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. | |
20effc67 TL |
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)); | |
7c673cae FG |
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. | |
20effc67 TL |
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)); | |
7c673cae | 164 | ASSERT_FALSE(sst_path.empty()); |
f67539c2 TL |
165 | |
166 | LegacyFileSystemWrapper fs(env_); | |
20effc67 | 167 | ASSERT_OK(CreateFile(&fs, sst_path, "blah", false /* use_fsync */)); |
7c673cae FG |
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. | |
20effc67 | 180 | ASSERT_OK(Put("key", "val")); |
7c673cae FG |
181 | VectorLogPtr wal_files; |
182 | ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files)); | |
183 | ASSERT_EQ(wal_files.size(), 1); | |
20effc67 TL |
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 | } | |
7c673cae FG |
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); | |
20effc67 TL |
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 | } | |
7c673cae FG |
209 | ASSERT_EQ(Get("key"), "val"); |
210 | } | |
211 | ||
11fdf7f2 TL |
212 | TEST_F(RepairTest, SeparateWalDir) { |
213 | do { | |
214 | Options options = CurrentOptions(); | |
215 | DestroyAndReopen(options); | |
20effc67 TL |
216 | ASSERT_OK(Put("key", "val")); |
217 | ASSERT_OK(Put("foo", "bar")); | |
11fdf7f2 TL |
218 | VectorLogPtr wal_files; |
219 | ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files)); | |
220 | ASSERT_EQ(wal_files.size(), 1); | |
20effc67 TL |
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 | } | |
11fdf7f2 TL |
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); | |
20effc67 TL |
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 | } | |
11fdf7f2 TL |
247 | ASSERT_EQ(Get("key"), "val"); |
248 | ASSERT_EQ(Get("foo"), "bar"); | |
249 | ||
250 | } while(ChangeWalOptions()); | |
251 | } | |
252 | ||
7c673cae FG |
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) { | |
20effc67 | 262 | ASSERT_OK(Put(i, "key" + ToString(j), "val" + ToString(j))); |
7c673cae FG |
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 | } | |
20effc67 | 268 | ASSERT_OK(Flush(i)); |
7c673cae FG |
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) { | |
20effc67 | 307 | ASSERT_OK(Put(i, "key" + ToString(j), "val" + ToString(j))); |
7c673cae FG |
308 | if (i == kNumCfs - 1 && j == kEntriesPerCf - 1) { |
309 | // Leave one unflushed so we can verify RepairDB's flush logic | |
310 | continue; | |
311 | } | |
20effc67 | 312 | ASSERT_OK(Flush(i)); |
7c673cae FG |
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; | |
20effc67 | 332 | ASSERT_OK(db_->GetPropertiesOfAllTables(handles_[1], &fname_to_props)); |
7c673cae FG |
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 | } | |
494da23a | 341 | Close(); |
7c673cae FG |
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 | ||
11fdf7f2 TL |
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 | ||
20effc67 TL |
366 | ASSERT_OK(Put("key", "val")); |
367 | ASSERT_OK(Flush()); | |
11fdf7f2 TL |
368 | Close(); |
369 | ||
370 | ASSERT_OK(RepairDB(dbname_ + "/", CurrentOptions())); | |
371 | Reopen(CurrentOptions()); | |
372 | ASSERT_EQ(Get("key"), "val"); | |
373 | } | |
7c673cae | 374 | #endif // ROCKSDB_LITE |
f67539c2 | 375 | } // namespace ROCKSDB_NAMESPACE |
7c673cae FG |
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 | ||
11fdf7f2 | 385 | int main(int /*argc*/, char** /*argv*/) { |
7c673cae FG |
386 | fprintf(stderr, "SKIPPED as RepairDB is not supported in ROCKSDB_LITE\n"); |
387 | return 0; | |
388 | } | |
389 | ||
390 | #endif // ROCKSDB_LITE |