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