1 // Copyright (c) 2011-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 // Copyright 2014 The LevelDB Authors. All rights reserved.
7 // Use of this source code is governed by a BSD-style license that can be
8 // found in the LICENSE file. See the AUTHORS file for names of contributors.
9
10 // This test uses a custom FileSystem to keep track of the state of a file
11 // system the last "Sync". The data being written is cached in a "buffer".
12 // Only when "Sync" is called, the data will be persistent. It can similate
13 // file data loss (or entire files) not protected by a "Sync". For any of the
14 // FileSystem related operations, by specify the "IOStatus Error", a specific
15 // error can be returned when file system is not activated.
16
17 #include "test_util/fault_injection_test_fs.h"
18 #include <functional>
19 #include <utility>
20
21 namespace ROCKSDB_NAMESPACE {
22
23 // Assume a filename, and not a directory name like "/foo/bar/"
TestFSGetDirName(const std::string filename)24 std::string TestFSGetDirName(const std::string filename) {
25 size_t found = filename.find_last_of("/\\");
26 if (found == std::string::npos) {
27 return "";
28 } else {
29 return filename.substr(0, found);
30 }
31 }
32
33 // Trim the tailing "/" in the end of `str`
TestFSTrimDirname(const std::string & str)34 std::string TestFSTrimDirname(const std::string& str) {
35 size_t found = str.find_last_not_of("/");
36 if (found == std::string::npos) {
37 return str;
38 }
39 return str.substr(0, found + 1);
40 }
41
42 // Return pair <parent directory name, file name> of a full path.
TestFSGetDirAndName(const std::string & name)43 std::pair<std::string, std::string> TestFSGetDirAndName(
44 const std::string& name) {
45 std::string dirname = TestFSGetDirName(name);
46 std::string fname = name.substr(dirname.size() + 1);
47 return std::make_pair(dirname, fname);
48 }
49
DropUnsyncedData()50 IOStatus FSFileState::DropUnsyncedData() {
51 buffer_.resize(0);
52 return IOStatus::OK();
53 }
54
DropRandomUnsyncedData(Random * rand)55 IOStatus FSFileState::DropRandomUnsyncedData(Random* rand) {
56 int range = static_cast<int>(buffer_.size());
57 size_t truncated_size = static_cast<size_t>(rand->Uniform(range));
58 buffer_.resize(truncated_size);
59 return IOStatus::OK();
60 }
61
Fsync(const IOOptions & options,IODebugContext * dbg)62 IOStatus TestFSDirectory::Fsync(const IOOptions& options, IODebugContext* dbg) {
63 if (!fs_->IsFilesystemActive()) {
64 return fs_->GetError();
65 }
66 fs_->SyncDir(dirname_);
67 return dir_->Fsync(options, dbg);
68 }
69
TestFSWritableFile(const std::string & fname,std::unique_ptr<FSWritableFile> && f,FaultInjectionTestFS * fs)70 TestFSWritableFile::TestFSWritableFile(const std::string& fname,
71 std::unique_ptr<FSWritableFile>&& f,
72 FaultInjectionTestFS* fs)
73 : state_(fname),
74 target_(std::move(f)),
75 writable_file_opened_(true),
76 fs_(fs) {
77 assert(target_ != nullptr);
78 state_.pos_ = 0;
79 }
80
~TestFSWritableFile()81 TestFSWritableFile::~TestFSWritableFile() {
82 if (writable_file_opened_) {
83 Close(IOOptions(), nullptr);
84 }
85 }
86
Append(const Slice & data,const IOOptions &,IODebugContext *)87 IOStatus TestFSWritableFile::Append(const Slice& data, const IOOptions&,
88 IODebugContext*) {
89 MutexLock l(&mutex_);
90 if (!fs_->IsFilesystemActive()) {
91 return fs_->GetError();
92 }
93 state_.buffer_.append(data.data(), data.size());
94 state_.pos_ += data.size();
95 fs_->WritableFileAppended(state_);
96 return IOStatus::OK();
97 }
98
Close(const IOOptions & options,IODebugContext * dbg)99 IOStatus TestFSWritableFile::Close(const IOOptions& options,
100 IODebugContext* dbg) {
101 if (!fs_->IsFilesystemActive()) {
102 return fs_->GetError();
103 }
104 writable_file_opened_ = false;
105 IOStatus io_s;
106 io_s = target_->Append(state_.buffer_, options, dbg);
107 if (io_s.ok()) {
108 state_.buffer_.resize(0);
109 target_->Sync(options, dbg);
110 io_s = target_->Close(options, dbg);
111 }
112 if (io_s.ok()) {
113 fs_->WritableFileClosed(state_);
114 }
115 return io_s;
116 }
117
Flush(const IOOptions &,IODebugContext *)118 IOStatus TestFSWritableFile::Flush(const IOOptions&, IODebugContext*) {
119 if (!fs_->IsFilesystemActive()) {
120 return fs_->GetError();
121 }
122 IOStatus io_s;
123 if (io_s.ok() && fs_->IsFilesystemActive()) {
124 state_.pos_at_last_flush_ = state_.pos_;
125 }
126 return io_s;
127 }
128
Sync(const IOOptions & options,IODebugContext * dbg)129 IOStatus TestFSWritableFile::Sync(const IOOptions& options,
130 IODebugContext* dbg) {
131 if (!fs_->IsFilesystemActive()) {
132 return fs_->GetError();
133 }
134 IOStatus io_s = target_->Append(state_.buffer_, options, dbg);
135 state_.buffer_.resize(0);
136 target_->Sync(options, dbg);
137 state_.pos_at_last_sync_ = state_.pos_;
138 fs_->WritableFileSynced(state_);
139 return io_s;
140 }
141
TestFSRandomRWFile(const std::string &,std::unique_ptr<FSRandomRWFile> && f,FaultInjectionTestFS * fs)142 TestFSRandomRWFile::TestFSRandomRWFile(const std::string& /*fname*/,
143 std::unique_ptr<FSRandomRWFile>&& f,
144 FaultInjectionTestFS* fs)
145 : target_(std::move(f)), file_opened_(true), fs_(fs) {
146 assert(target_ != nullptr);
147 }
148
~TestFSRandomRWFile()149 TestFSRandomRWFile::~TestFSRandomRWFile() {
150 if (file_opened_) {
151 Close(IOOptions(), nullptr);
152 }
153 }
154
Write(uint64_t offset,const Slice & data,const IOOptions & options,IODebugContext * dbg)155 IOStatus TestFSRandomRWFile::Write(uint64_t offset, const Slice& data,
156 const IOOptions& options,
157 IODebugContext* dbg) {
158 if (!fs_->IsFilesystemActive()) {
159 return fs_->GetError();
160 }
161 return target_->Write(offset, data, options, dbg);
162 }
163
Read(uint64_t offset,size_t n,const IOOptions & options,Slice * result,char * scratch,IODebugContext * dbg) const164 IOStatus TestFSRandomRWFile::Read(uint64_t offset, size_t n,
165 const IOOptions& options, Slice* result,
166 char* scratch, IODebugContext* dbg) const {
167 if (!fs_->IsFilesystemActive()) {
168 return fs_->GetError();
169 }
170 return target_->Read(offset, n, options, result, scratch, dbg);
171 }
172
Close(const IOOptions & options,IODebugContext * dbg)173 IOStatus TestFSRandomRWFile::Close(const IOOptions& options,
174 IODebugContext* dbg) {
175 if (!fs_->IsFilesystemActive()) {
176 return fs_->GetError();
177 }
178 file_opened_ = false;
179 return target_->Close(options, dbg);
180 }
181
Flush(const IOOptions & options,IODebugContext * dbg)182 IOStatus TestFSRandomRWFile::Flush(const IOOptions& options,
183 IODebugContext* dbg) {
184 if (!fs_->IsFilesystemActive()) {
185 return fs_->GetError();
186 }
187 return target_->Flush(options, dbg);
188 }
189
Sync(const IOOptions & options,IODebugContext * dbg)190 IOStatus TestFSRandomRWFile::Sync(const IOOptions& options,
191 IODebugContext* dbg) {
192 if (!fs_->IsFilesystemActive()) {
193 return fs_->GetError();
194 }
195 return target_->Sync(options, dbg);
196 }
197
NewDirectory(const std::string & name,const IOOptions & options,std::unique_ptr<FSDirectory> * result,IODebugContext * dbg)198 IOStatus FaultInjectionTestFS::NewDirectory(
199 const std::string& name, const IOOptions& options,
200 std::unique_ptr<FSDirectory>* result, IODebugContext* dbg) {
201 std::unique_ptr<FSDirectory> r;
202 IOStatus io_s = target()->NewDirectory(name, options, &r, dbg);
203 assert(io_s.ok());
204 if (!io_s.ok()) {
205 return io_s;
206 }
207 result->reset(
208 new TestFSDirectory(this, TestFSTrimDirname(name), r.release()));
209 return IOStatus::OK();
210 }
211
NewWritableFile(const std::string & fname,const FileOptions & file_opts,std::unique_ptr<FSWritableFile> * result,IODebugContext * dbg)212 IOStatus FaultInjectionTestFS::NewWritableFile(
213 const std::string& fname, const FileOptions& file_opts,
214 std::unique_ptr<FSWritableFile>* result, IODebugContext* dbg) {
215 if (!IsFilesystemActive()) {
216 return GetError();
217 }
218 // Not allow overwriting files
219 IOStatus io_s = target()->FileExists(fname, IOOptions(), dbg);
220 if (io_s.ok()) {
221 return IOStatus::Corruption("File already exists.");
222 } else if (!io_s.IsNotFound()) {
223 assert(io_s.IsIOError());
224 return io_s;
225 }
226 io_s = target()->NewWritableFile(fname, file_opts, result, dbg);
227 if (io_s.ok()) {
228 result->reset(new TestFSWritableFile(fname, std::move(*result), this));
229 // WritableFileWriter* file is opened
230 // again then it will be truncated - so forget our saved state.
231 UntrackFile(fname);
232 MutexLock l(&mutex_);
233 open_files_.insert(fname);
234 auto dir_and_name = TestFSGetDirAndName(fname);
235 auto& list = dir_to_new_files_since_last_sync_[dir_and_name.first];
236 list.insert(dir_and_name.second);
237 }
238 return io_s;
239 }
240
ReopenWritableFile(const std::string & fname,const FileOptions & file_opts,std::unique_ptr<FSWritableFile> * result,IODebugContext * dbg)241 IOStatus FaultInjectionTestFS::ReopenWritableFile(
242 const std::string& fname, const FileOptions& file_opts,
243 std::unique_ptr<FSWritableFile>* result, IODebugContext* dbg) {
244 if (!IsFilesystemActive()) {
245 return GetError();
246 }
247 IOStatus io_s = target()->ReopenWritableFile(fname, file_opts, result, dbg);
248 if (io_s.ok()) {
249 result->reset(new TestFSWritableFile(fname, std::move(*result), this));
250 // WritableFileWriter* file is opened
251 // again then it will be truncated - so forget our saved state.
252 UntrackFile(fname);
253 MutexLock l(&mutex_);
254 open_files_.insert(fname);
255 auto dir_and_name = TestFSGetDirAndName(fname);
256 auto& list = dir_to_new_files_since_last_sync_[dir_and_name.first];
257 list.insert(dir_and_name.second);
258 }
259 return io_s;
260 }
261
NewRandomRWFile(const std::string & fname,const FileOptions & file_opts,std::unique_ptr<FSRandomRWFile> * result,IODebugContext * dbg)262 IOStatus FaultInjectionTestFS::NewRandomRWFile(
263 const std::string& fname, const FileOptions& file_opts,
264 std::unique_ptr<FSRandomRWFile>* result, IODebugContext* dbg) {
265 if (!IsFilesystemActive()) {
266 return GetError();
267 }
268 IOStatus io_s = target()->NewRandomRWFile(fname, file_opts, result, dbg);
269 if (io_s.ok()) {
270 result->reset(new TestFSRandomRWFile(fname, std::move(*result), this));
271 // WritableFileWriter* file is opened
272 // again then it will be truncated - so forget our saved state.
273 UntrackFile(fname);
274 MutexLock l(&mutex_);
275 open_files_.insert(fname);
276 auto dir_and_name = TestFSGetDirAndName(fname);
277 auto& list = dir_to_new_files_since_last_sync_[dir_and_name.first];
278 list.insert(dir_and_name.second);
279 }
280 return io_s;
281 }
282
NewRandomAccessFile(const std::string & fname,const FileOptions & file_opts,std::unique_ptr<FSRandomAccessFile> * result,IODebugContext * dbg)283 IOStatus FaultInjectionTestFS::NewRandomAccessFile(
284 const std::string& fname, const FileOptions& file_opts,
285 std::unique_ptr<FSRandomAccessFile>* result, IODebugContext* dbg) {
286 if (!IsFilesystemActive()) {
287 return GetError();
288 }
289 return target()->NewRandomAccessFile(fname, file_opts, result, dbg);
290 }
291
DeleteFile(const std::string & f,const IOOptions & options,IODebugContext * dbg)292 IOStatus FaultInjectionTestFS::DeleteFile(const std::string& f,
293 const IOOptions& options,
294 IODebugContext* dbg) {
295 if (!IsFilesystemActive()) {
296 return GetError();
297 }
298 IOStatus io_s = FileSystemWrapper::DeleteFile(f, options, dbg);
299 if (!io_s.ok()) {
300 fprintf(stderr, "Cannot delete file %s: %s\n", f.c_str(),
301 io_s.ToString().c_str());
302 }
303 if (io_s.ok()) {
304 UntrackFile(f);
305 }
306 return io_s;
307 }
308
RenameFile(const std::string & s,const std::string & t,const IOOptions & options,IODebugContext * dbg)309 IOStatus FaultInjectionTestFS::RenameFile(const std::string& s,
310 const std::string& t,
311 const IOOptions& options,
312 IODebugContext* dbg) {
313 if (!IsFilesystemActive()) {
314 return GetError();
315 }
316 IOStatus io_s = FileSystemWrapper::RenameFile(s, t, options, dbg);
317
318 if (io_s.ok()) {
319 MutexLock l(&mutex_);
320 if (db_file_state_.find(s) != db_file_state_.end()) {
321 db_file_state_[t] = db_file_state_[s];
322 db_file_state_.erase(s);
323 }
324
325 auto sdn = TestFSGetDirAndName(s);
326 auto tdn = TestFSGetDirAndName(t);
327 if (dir_to_new_files_since_last_sync_[sdn.first].erase(sdn.second) != 0) {
328 auto& tlist = dir_to_new_files_since_last_sync_[tdn.first];
329 assert(tlist.find(tdn.second) == tlist.end());
330 tlist.insert(tdn.second);
331 }
332 }
333
334 return io_s;
335 }
336
WritableFileClosed(const FSFileState & state)337 void FaultInjectionTestFS::WritableFileClosed(const FSFileState& state) {
338 MutexLock l(&mutex_);
339 if (open_files_.find(state.filename_) != open_files_.end()) {
340 db_file_state_[state.filename_] = state;
341 open_files_.erase(state.filename_);
342 }
343 }
344
WritableFileSynced(const FSFileState & state)345 void FaultInjectionTestFS::WritableFileSynced(const FSFileState& state) {
346 MutexLock l(&mutex_);
347 if (open_files_.find(state.filename_) != open_files_.end()) {
348 if (db_file_state_.find(state.filename_) == db_file_state_.end()) {
349 db_file_state_.insert(std::make_pair(state.filename_, state));
350 } else {
351 db_file_state_[state.filename_] = state;
352 }
353 }
354 }
355
WritableFileAppended(const FSFileState & state)356 void FaultInjectionTestFS::WritableFileAppended(const FSFileState& state) {
357 MutexLock l(&mutex_);
358 if (open_files_.find(state.filename_) != open_files_.end()) {
359 if (db_file_state_.find(state.filename_) == db_file_state_.end()) {
360 db_file_state_.insert(std::make_pair(state.filename_, state));
361 } else {
362 db_file_state_[state.filename_] = state;
363 }
364 }
365 }
366
DropUnsyncedFileData()367 IOStatus FaultInjectionTestFS::DropUnsyncedFileData() {
368 IOStatus io_s;
369 MutexLock l(&mutex_);
370 for (std::map<std::string, FSFileState>::iterator it = db_file_state_.begin();
371 io_s.ok() && it != db_file_state_.end(); ++it) {
372 FSFileState& fs_state = it->second;
373 if (!fs_state.IsFullySynced()) {
374 io_s = fs_state.DropUnsyncedData();
375 }
376 }
377 return io_s;
378 }
379
DropRandomUnsyncedFileData(Random * rnd)380 IOStatus FaultInjectionTestFS::DropRandomUnsyncedFileData(Random* rnd) {
381 IOStatus io_s;
382 MutexLock l(&mutex_);
383 for (std::map<std::string, FSFileState>::iterator it = db_file_state_.begin();
384 io_s.ok() && it != db_file_state_.end(); ++it) {
385 FSFileState& fs_state = it->second;
386 if (!fs_state.IsFullySynced()) {
387 io_s = fs_state.DropRandomUnsyncedData(rnd);
388 }
389 }
390 return io_s;
391 }
392
DeleteFilesCreatedAfterLastDirSync(const IOOptions & options,IODebugContext * dbg)393 IOStatus FaultInjectionTestFS::DeleteFilesCreatedAfterLastDirSync(
394 const IOOptions& options, IODebugContext* dbg) {
395 // Because DeleteFile access this container make a copy to avoid deadlock
396 std::map<std::string, std::set<std::string>> map_copy;
397 {
398 MutexLock l(&mutex_);
399 map_copy.insert(dir_to_new_files_since_last_sync_.begin(),
400 dir_to_new_files_since_last_sync_.end());
401 }
402
403 for (auto& pair : map_copy) {
404 for (std::string name : pair.second) {
405 IOStatus io_s = DeleteFile(pair.first + "/" + name, options, dbg);
406 if (!io_s.ok()) {
407 return io_s;
408 }
409 }
410 }
411 return IOStatus::OK();
412 }
413
ResetState()414 void FaultInjectionTestFS::ResetState() {
415 MutexLock l(&mutex_);
416 db_file_state_.clear();
417 dir_to_new_files_since_last_sync_.clear();
418 SetFilesystemActiveNoLock(true);
419 }
420
UntrackFile(const std::string & f)421 void FaultInjectionTestFS::UntrackFile(const std::string& f) {
422 MutexLock l(&mutex_);
423 auto dir_and_name = TestFSGetDirAndName(f);
424 dir_to_new_files_since_last_sync_[dir_and_name.first].erase(
425 dir_and_name.second);
426 db_file_state_.erase(f);
427 open_files_.erase(f);
428 }
429
430 } // namespace ROCKSDB_NAMESPACE
431