1 // Copyright 2014 The LevelDB Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. See the AUTHORS file for names of contributors.
4 
5 // This test uses a custom Env to keep track of the state of a filesystem as of
6 // the last "sync". It then checks for data loss errors by purposely dropping
7 // file data (or entire files) not protected by a "sync".
8 
9 #include "leveldb/db.h"
10 
11 #include <map>
12 #include <set>
13 #include "db/db_impl.h"
14 #include "db/filename.h"
15 #include "db/log_format.h"
16 #include "db/version_set.h"
17 #include "leveldb/cache.h"
18 #include "leveldb/env.h"
19 #include "leveldb/table.h"
20 #include "leveldb/write_batch.h"
21 #include "util/logging.h"
22 #include "util/mutexlock.h"
23 #include "util/testharness.h"
24 #include "util/testutil.h"
25 
26 namespace leveldb {
27 
28 static const int kValueSize = 1000;
29 static const int kMaxNumValues = 2000;
30 static const size_t kNumIterations = 3;
31 
32 class FaultInjectionTestEnv;
33 
34 namespace {
35 
36 // Assume a filename, and not a directory name like "/foo/bar/"
GetDirName(const std::string filename)37 static std::string GetDirName(const std::string filename) {
38   size_t found = filename.find_last_of("/\\");
39   if (found == std::string::npos) {
40     return "";
41   } else {
42     return filename.substr(0, found);
43   }
44 }
45 
SyncDir(const std::string & dir)46 Status SyncDir(const std::string& dir) {
47   // As this is a test it isn't required to *actually* sync this directory.
48   return Status::OK();
49 }
50 
51 // A basic file truncation function suitable for this test.
Truncate(const std::string & filename,uint64_t length)52 Status Truncate(const std::string& filename, uint64_t length) {
53   leveldb::Env* env = leveldb::Env::Default();
54 
55   SequentialFile* orig_file;
56   Status s = env->NewSequentialFile(filename, &orig_file);
57   if (!s.ok())
58     return s;
59 
60   char* scratch = new char[length];
61   leveldb::Slice result;
62   s = orig_file->Read(length, &result, scratch);
63   delete orig_file;
64   if (s.ok()) {
65     std::string tmp_name = GetDirName(filename) + "/truncate.tmp";
66     WritableFile* tmp_file;
67     s = env->NewWritableFile(tmp_name, &tmp_file);
68     if (s.ok()) {
69       s = tmp_file->Append(result);
70       delete tmp_file;
71       if (s.ok()) {
72         s = env->RenameFile(tmp_name, filename);
73       } else {
74         env->DeleteFile(tmp_name);
75       }
76     }
77   }
78 
79   delete[] scratch;
80 
81   return s;
82 }
83 
84 struct FileState {
85   std::string filename_;
86   ssize_t pos_;
87   ssize_t pos_at_last_sync_;
88   ssize_t pos_at_last_flush_;
89 
FileStateleveldb::__anon61981fdc0111::FileState90   FileState(const std::string& filename)
91       : filename_(filename),
92         pos_(-1),
93         pos_at_last_sync_(-1),
94         pos_at_last_flush_(-1) { }
95 
FileStateleveldb::__anon61981fdc0111::FileState96   FileState() : pos_(-1), pos_at_last_sync_(-1), pos_at_last_flush_(-1) {}
97 
IsFullySyncedleveldb::__anon61981fdc0111::FileState98   bool IsFullySynced() const { return pos_ <= 0 || pos_ == pos_at_last_sync_; }
99 
100   Status DropUnsyncedData() const;
101 };
102 
103 }  // anonymous namespace
104 
105 // A wrapper around WritableFile which informs another Env whenever this file
106 // is written to or sync'ed.
107 class TestWritableFile : public WritableFile {
108  public:
109   TestWritableFile(const FileState& state,
110                    WritableFile* f,
111                    FaultInjectionTestEnv* env);
112   virtual ~TestWritableFile();
113   virtual Status Append(const Slice& data);
114   virtual Status Close();
115   virtual Status Flush();
116   virtual Status Sync();
117 
118  private:
119   FileState state_;
120   WritableFile* target_;
121   bool writable_file_opened_;
122   FaultInjectionTestEnv* env_;
123 
124   Status SyncParent();
125 };
126 
127 class FaultInjectionTestEnv : public EnvWrapper {
128  public:
FaultInjectionTestEnv()129   FaultInjectionTestEnv() : EnvWrapper(Env::Default()), filesystem_active_(true) {}
~FaultInjectionTestEnv()130   virtual ~FaultInjectionTestEnv() { }
131   virtual Status NewWritableFile(const std::string& fname,
132                                  WritableFile** result);
133   virtual Status NewAppendableFile(const std::string& fname,
134                                    WritableFile** result);
135   virtual Status DeleteFile(const std::string& f);
136   virtual Status RenameFile(const std::string& s, const std::string& t);
137 
138   void WritableFileClosed(const FileState& state);
139   Status DropUnsyncedFileData();
140   Status DeleteFilesCreatedAfterLastDirSync();
141   void DirWasSynced();
142   bool IsFileCreatedSinceLastDirSync(const std::string& filename);
143   void ResetState();
144   void UntrackFile(const std::string& f);
145   // Setting the filesystem to inactive is the test equivalent to simulating a
146   // system reset. Setting to inactive will freeze our saved filesystem state so
147   // that it will stop being recorded. It can then be reset back to the state at
148   // the time of the reset.
IsFilesystemActive() const149   bool IsFilesystemActive() const { return filesystem_active_; }
SetFilesystemActive(bool active)150   void SetFilesystemActive(bool active) { filesystem_active_ = active; }
151 
152  private:
153   port::Mutex mutex_;
154   std::map<std::string, FileState> db_file_state_;
155   std::set<std::string> new_files_since_last_dir_sync_;
156   bool filesystem_active_;  // Record flushes, syncs, writes
157 };
158 
TestWritableFile(const FileState & state,WritableFile * f,FaultInjectionTestEnv * env)159 TestWritableFile::TestWritableFile(const FileState& state,
160                                    WritableFile* f,
161                                    FaultInjectionTestEnv* env)
162     : state_(state),
163       target_(f),
164       writable_file_opened_(true),
165       env_(env) {
166   assert(f != NULL);
167 }
168 
~TestWritableFile()169 TestWritableFile::~TestWritableFile() {
170   if (writable_file_opened_) {
171     Close();
172   }
173   delete target_;
174 }
175 
Append(const Slice & data)176 Status TestWritableFile::Append(const Slice& data) {
177   Status s = target_->Append(data);
178   if (s.ok() && env_->IsFilesystemActive()) {
179     state_.pos_ += data.size();
180   }
181   return s;
182 }
183 
Close()184 Status TestWritableFile::Close() {
185   writable_file_opened_ = false;
186   Status s = target_->Close();
187   if (s.ok()) {
188     env_->WritableFileClosed(state_);
189   }
190   return s;
191 }
192 
Flush()193 Status TestWritableFile::Flush() {
194   Status s = target_->Flush();
195   if (s.ok() && env_->IsFilesystemActive()) {
196     state_.pos_at_last_flush_ = state_.pos_;
197   }
198   return s;
199 }
200 
SyncParent()201 Status TestWritableFile::SyncParent() {
202   Status s = SyncDir(GetDirName(state_.filename_));
203   if (s.ok()) {
204     env_->DirWasSynced();
205   }
206   return s;
207 }
208 
Sync()209 Status TestWritableFile::Sync() {
210   if (!env_->IsFilesystemActive()) {
211     return Status::OK();
212   }
213   // Ensure new files referred to by the manifest are in the filesystem.
214   Status s = target_->Sync();
215   if (s.ok()) {
216     state_.pos_at_last_sync_ = state_.pos_;
217   }
218   if (env_->IsFileCreatedSinceLastDirSync(state_.filename_)) {
219     Status ps = SyncParent();
220     if (s.ok() && !ps.ok()) {
221       s = ps;
222     }
223   }
224   return s;
225 }
226 
NewWritableFile(const std::string & fname,WritableFile ** result)227 Status FaultInjectionTestEnv::NewWritableFile(const std::string& fname,
228                                               WritableFile** result) {
229   WritableFile* actual_writable_file;
230   Status s = target()->NewWritableFile(fname, &actual_writable_file);
231   if (s.ok()) {
232     FileState state(fname);
233     state.pos_ = 0;
234     *result = new TestWritableFile(state, actual_writable_file, this);
235     // NewWritableFile doesn't append to files, so if the same file is
236     // opened again then it will be truncated - so forget our saved
237     // state.
238     UntrackFile(fname);
239     MutexLock l(&mutex_);
240     new_files_since_last_dir_sync_.insert(fname);
241   }
242   return s;
243 }
244 
NewAppendableFile(const std::string & fname,WritableFile ** result)245 Status FaultInjectionTestEnv::NewAppendableFile(const std::string& fname,
246                                                 WritableFile** result) {
247   WritableFile* actual_writable_file;
248   Status s = target()->NewAppendableFile(fname, &actual_writable_file);
249   if (s.ok()) {
250     FileState state(fname);
251     state.pos_ = 0;
252     {
253       MutexLock l(&mutex_);
254       if (db_file_state_.count(fname) == 0) {
255         new_files_since_last_dir_sync_.insert(fname);
256       } else {
257         state = db_file_state_[fname];
258       }
259     }
260     *result = new TestWritableFile(state, actual_writable_file, this);
261   }
262   return s;
263 }
264 
DropUnsyncedFileData()265 Status FaultInjectionTestEnv::DropUnsyncedFileData() {
266   Status s;
267   MutexLock l(&mutex_);
268   for (std::map<std::string, FileState>::const_iterator it =
269            db_file_state_.begin();
270        s.ok() && it != db_file_state_.end(); ++it) {
271     const FileState& state = it->second;
272     if (!state.IsFullySynced()) {
273       s = state.DropUnsyncedData();
274     }
275   }
276   return s;
277 }
278 
DirWasSynced()279 void FaultInjectionTestEnv::DirWasSynced() {
280   MutexLock l(&mutex_);
281   new_files_since_last_dir_sync_.clear();
282 }
283 
IsFileCreatedSinceLastDirSync(const std::string & filename)284 bool FaultInjectionTestEnv::IsFileCreatedSinceLastDirSync(
285     const std::string& filename) {
286   MutexLock l(&mutex_);
287   return new_files_since_last_dir_sync_.find(filename) !=
288          new_files_since_last_dir_sync_.end();
289 }
290 
UntrackFile(const std::string & f)291 void FaultInjectionTestEnv::UntrackFile(const std::string& f) {
292   MutexLock l(&mutex_);
293   db_file_state_.erase(f);
294   new_files_since_last_dir_sync_.erase(f);
295 }
296 
DeleteFile(const std::string & f)297 Status FaultInjectionTestEnv::DeleteFile(const std::string& f) {
298   Status s = EnvWrapper::DeleteFile(f);
299   ASSERT_OK(s);
300   if (s.ok()) {
301     UntrackFile(f);
302   }
303   return s;
304 }
305 
RenameFile(const std::string & s,const std::string & t)306 Status FaultInjectionTestEnv::RenameFile(const std::string& s,
307                                          const std::string& t) {
308   Status ret = EnvWrapper::RenameFile(s, t);
309 
310   if (ret.ok()) {
311     MutexLock l(&mutex_);
312     if (db_file_state_.find(s) != db_file_state_.end()) {
313       db_file_state_[t] = db_file_state_[s];
314       db_file_state_.erase(s);
315     }
316 
317     if (new_files_since_last_dir_sync_.erase(s) != 0) {
318       assert(new_files_since_last_dir_sync_.find(t) ==
319              new_files_since_last_dir_sync_.end());
320       new_files_since_last_dir_sync_.insert(t);
321     }
322   }
323 
324   return ret;
325 }
326 
ResetState()327 void FaultInjectionTestEnv::ResetState() {
328   // Since we are not destroying the database, the existing files
329   // should keep their recorded synced/flushed state. Therefore
330   // we do not reset db_file_state_ and new_files_since_last_dir_sync_.
331   MutexLock l(&mutex_);
332   SetFilesystemActive(true);
333 }
334 
DeleteFilesCreatedAfterLastDirSync()335 Status FaultInjectionTestEnv::DeleteFilesCreatedAfterLastDirSync() {
336   // Because DeleteFile access this container make a copy to avoid deadlock
337   mutex_.Lock();
338   std::set<std::string> new_files(new_files_since_last_dir_sync_.begin(),
339                                   new_files_since_last_dir_sync_.end());
340   mutex_.Unlock();
341   Status s;
342   std::set<std::string>::const_iterator it;
343   for (it = new_files.begin(); s.ok() && it != new_files.end(); ++it) {
344     s = DeleteFile(*it);
345   }
346   return s;
347 }
348 
WritableFileClosed(const FileState & state)349 void FaultInjectionTestEnv::WritableFileClosed(const FileState& state) {
350   MutexLock l(&mutex_);
351   db_file_state_[state.filename_] = state;
352 }
353 
DropUnsyncedData() const354 Status FileState::DropUnsyncedData() const {
355   ssize_t sync_pos = pos_at_last_sync_ == -1 ? 0 : pos_at_last_sync_;
356   return Truncate(filename_, sync_pos);
357 }
358 
359 class FaultInjectionTest {
360  public:
361   enum ExpectedVerifResult { VAL_EXPECT_NO_ERROR, VAL_EXPECT_ERROR };
362   enum ResetMethod { RESET_DROP_UNSYNCED_DATA, RESET_DELETE_UNSYNCED_FILES };
363 
364   FaultInjectionTestEnv* env_;
365   std::string dbname_;
366   Cache* tiny_cache_;
367   Options options_;
368   DB* db_;
369 
FaultInjectionTest()370   FaultInjectionTest()
371       : env_(new FaultInjectionTestEnv),
372         tiny_cache_(NewLRUCache(100)),
373         db_(NULL) {
374     dbname_ = test::TmpDir() + "/fault_test";
375     DestroyDB(dbname_, Options());  // Destroy any db from earlier run
376     options_.reuse_logs = true;
377     options_.env = env_;
378     options_.paranoid_checks = true;
379     options_.block_cache = tiny_cache_;
380     options_.create_if_missing = true;
381   }
382 
~FaultInjectionTest()383   ~FaultInjectionTest() {
384     CloseDB();
385     DestroyDB(dbname_, Options());
386     delete tiny_cache_;
387     delete env_;
388   }
389 
ReuseLogs(bool reuse)390   void ReuseLogs(bool reuse) {
391     options_.reuse_logs = reuse;
392   }
393 
Build(int start_idx,int num_vals)394   void Build(int start_idx, int num_vals) {
395     std::string key_space, value_space;
396     WriteBatch batch;
397     for (int i = start_idx; i < start_idx + num_vals; i++) {
398       Slice key = Key(i, &key_space);
399       batch.Clear();
400       batch.Put(key, Value(i, &value_space));
401       WriteOptions options;
402       ASSERT_OK(db_->Write(options, &batch));
403     }
404   }
405 
ReadValue(int i,std::string * val) const406   Status ReadValue(int i, std::string* val) const {
407     std::string key_space, value_space;
408     Slice key = Key(i, &key_space);
409     Value(i, &value_space);
410     ReadOptions options;
411     return db_->Get(options, key, val);
412   }
413 
Verify(int start_idx,int num_vals,ExpectedVerifResult expected) const414   Status Verify(int start_idx, int num_vals,
415                 ExpectedVerifResult expected) const {
416     std::string val;
417     std::string value_space;
418     Status s;
419     for (int i = start_idx; i < start_idx + num_vals && s.ok(); i++) {
420       Value(i, &value_space);
421       s = ReadValue(i, &val);
422       if (expected == VAL_EXPECT_NO_ERROR) {
423         if (s.ok()) {
424           ASSERT_EQ(value_space, val);
425         }
426       } else if (s.ok()) {
427         fprintf(stderr, "Expected an error at %d, but was OK\n", i);
428         s = Status::IOError(dbname_, "Expected value error:");
429       } else {
430         s = Status::OK();  // An expected error
431       }
432     }
433     return s;
434   }
435 
436   // Return the ith key
Key(int i,std::string * storage) const437   Slice Key(int i, std::string* storage) const {
438     char buf[100];
439     snprintf(buf, sizeof(buf), "%016d", i);
440     storage->assign(buf, strlen(buf));
441     return Slice(*storage);
442   }
443 
444   // Return the value to associate with the specified key
Value(int k,std::string * storage) const445   Slice Value(int k, std::string* storage) const {
446     Random r(k);
447     return test::RandomString(&r, kValueSize, storage);
448   }
449 
OpenDB()450   Status OpenDB() {
451     delete db_;
452     db_ = NULL;
453     env_->ResetState();
454     return DB::Open(options_, dbname_, &db_);
455   }
456 
CloseDB()457   void CloseDB() {
458     delete db_;
459     db_ = NULL;
460   }
461 
DeleteAllData()462   void DeleteAllData() {
463     Iterator* iter = db_->NewIterator(ReadOptions());
464     WriteOptions options;
465     for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
466       ASSERT_OK(db_->Delete(WriteOptions(), iter->key()));
467     }
468 
469     delete iter;
470   }
471 
ResetDBState(ResetMethod reset_method)472   void ResetDBState(ResetMethod reset_method) {
473     switch (reset_method) {
474       case RESET_DROP_UNSYNCED_DATA:
475         ASSERT_OK(env_->DropUnsyncedFileData());
476         break;
477       case RESET_DELETE_UNSYNCED_FILES:
478         ASSERT_OK(env_->DeleteFilesCreatedAfterLastDirSync());
479         break;
480       default:
481         assert(false);
482     }
483   }
484 
PartialCompactTestPreFault(int num_pre_sync,int num_post_sync)485   void PartialCompactTestPreFault(int num_pre_sync, int num_post_sync) {
486     DeleteAllData();
487     Build(0, num_pre_sync);
488     db_->CompactRange(NULL, NULL);
489     Build(num_pre_sync, num_post_sync);
490   }
491 
PartialCompactTestReopenWithFault(ResetMethod reset_method,int num_pre_sync,int num_post_sync)492   void PartialCompactTestReopenWithFault(ResetMethod reset_method,
493                                          int num_pre_sync,
494                                          int num_post_sync) {
495     env_->SetFilesystemActive(false);
496     CloseDB();
497     ResetDBState(reset_method);
498     ASSERT_OK(OpenDB());
499     ASSERT_OK(Verify(0, num_pre_sync, FaultInjectionTest::VAL_EXPECT_NO_ERROR));
500     ASSERT_OK(Verify(num_pre_sync, num_post_sync, FaultInjectionTest::VAL_EXPECT_ERROR));
501   }
502 
NoWriteTestPreFault()503   void NoWriteTestPreFault() {
504   }
505 
NoWriteTestReopenWithFault(ResetMethod reset_method)506   void NoWriteTestReopenWithFault(ResetMethod reset_method) {
507     CloseDB();
508     ResetDBState(reset_method);
509     ASSERT_OK(OpenDB());
510   }
511 
DoTest()512   void DoTest() {
513     Random rnd(0);
514     ASSERT_OK(OpenDB());
515     for (size_t idx = 0; idx < kNumIterations; idx++) {
516       int num_pre_sync = rnd.Uniform(kMaxNumValues);
517       int num_post_sync = rnd.Uniform(kMaxNumValues);
518 
519       PartialCompactTestPreFault(num_pre_sync, num_post_sync);
520       PartialCompactTestReopenWithFault(RESET_DROP_UNSYNCED_DATA,
521                                         num_pre_sync,
522                                         num_post_sync);
523 
524       NoWriteTestPreFault();
525       NoWriteTestReopenWithFault(RESET_DROP_UNSYNCED_DATA);
526 
527       PartialCompactTestPreFault(num_pre_sync, num_post_sync);
528       // No new files created so we expect all values since no files will be
529       // dropped.
530       PartialCompactTestReopenWithFault(RESET_DELETE_UNSYNCED_FILES,
531                                         num_pre_sync + num_post_sync,
532                                         0);
533 
534       NoWriteTestPreFault();
535       NoWriteTestReopenWithFault(RESET_DELETE_UNSYNCED_FILES);
536     }
537   }
538 };
539 
TEST(FaultInjectionTest,FaultTestNoLogReuse)540 TEST(FaultInjectionTest, FaultTestNoLogReuse) {
541   ReuseLogs(false);
542   DoTest();
543 }
544 
TEST(FaultInjectionTest,FaultTestWithLogReuse)545 TEST(FaultInjectionTest, FaultTestWithLogReuse) {
546   ReuseLogs(true);
547   DoTest();
548 }
549 
550 }  // namespace leveldb
551 
main(int argc,char ** argv)552 int main(int argc, char** argv) {
553   return leveldb::test::RunAllTests();
554 }
555