]> git.proxmox.com Git - ceph.git/blame - ceph/src/rocksdb/util/fault_injection_test_env.cc
update sources to ceph Nautilus 14.2.1
[ceph.git] / ceph / src / rocksdb / util / fault_injection_test_env.cc
CommitLineData
7c673cae 1// Copyright (c) 2011-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// 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 Env to keep track of the state of a filesystem as of
11// the last "sync". It then checks for data loss errors by purposely dropping
12// file data (or entire files) not protected by a "sync".
13
14#include "util/fault_injection_test_env.h"
15#include <functional>
16#include <utility>
17
18namespace rocksdb {
19
20// Assume a filename, and not a directory name like "/foo/bar/"
21std::string GetDirName(const std::string filename) {
22 size_t found = filename.find_last_of("/\\");
23 if (found == std::string::npos) {
24 return "";
25 } else {
26 return filename.substr(0, found);
27 }
28}
29
30// A basic file truncation function suitable for this test.
31Status Truncate(Env* env, const std::string& filename, uint64_t length) {
32 unique_ptr<SequentialFile> orig_file;
33 const EnvOptions options;
34 Status s = env->NewSequentialFile(filename, &orig_file, options);
35 if (!s.ok()) {
36 fprintf(stderr, "Cannot truncate file %s: %s\n", filename.c_str(),
37 s.ToString().c_str());
38 return s;
39 }
40
41 std::unique_ptr<char[]> scratch(new char[length]);
42 rocksdb::Slice result;
43 s = orig_file->Read(length, &result, scratch.get());
44#ifdef OS_WIN
45 orig_file.reset();
46#endif
47 if (s.ok()) {
48 std::string tmp_name = GetDirName(filename) + "/truncate.tmp";
49 unique_ptr<WritableFile> tmp_file;
50 s = env->NewWritableFile(tmp_name, &tmp_file, options);
51 if (s.ok()) {
52 s = tmp_file->Append(result);
53 if (s.ok()) {
54 s = env->RenameFile(tmp_name, filename);
55 } else {
56 fprintf(stderr, "Cannot rename file %s to %s: %s\n", tmp_name.c_str(),
57 filename.c_str(), s.ToString().c_str());
58 env->DeleteFile(tmp_name);
59 }
60 }
61 }
62 if (!s.ok()) {
63 fprintf(stderr, "Cannot truncate file %s: %s\n", filename.c_str(),
64 s.ToString().c_str());
65 }
66
67 return s;
68}
69
70// Trim the tailing "/" in the end of `str`
71std::string TrimDirname(const std::string& str) {
72 size_t found = str.find_last_not_of("/");
73 if (found == std::string::npos) {
74 return str;
75 }
76 return str.substr(0, found + 1);
77}
78
79// Return pair <parent directory name, file name> of a full path.
80std::pair<std::string, std::string> GetDirAndName(const std::string& name) {
81 std::string dirname = GetDirName(name);
82 std::string fname = name.substr(dirname.size() + 1);
83 return std::make_pair(dirname, fname);
84}
85
86Status FileState::DropUnsyncedData(Env* env) const {
87 ssize_t sync_pos = pos_at_last_sync_ == -1 ? 0 : pos_at_last_sync_;
88 return Truncate(env, filename_, sync_pos);
89}
90
91Status FileState::DropRandomUnsyncedData(Env* env, Random* rand) const {
92 ssize_t sync_pos = pos_at_last_sync_ == -1 ? 0 : pos_at_last_sync_;
93 assert(pos_ >= sync_pos);
94 int range = static_cast<int>(pos_ - sync_pos);
95 uint64_t truncated_size =
96 static_cast<uint64_t>(sync_pos) + rand->Uniform(range);
97 return Truncate(env, filename_, truncated_size);
98}
99
100Status TestDirectory::Fsync() {
101 env_->SyncDir(dirname_);
102 return dir_->Fsync();
103}
104
105TestWritableFile::TestWritableFile(const std::string& fname,
106 unique_ptr<WritableFile>&& f,
107 FaultInjectionTestEnv* env)
108 : state_(fname),
109 target_(std::move(f)),
110 writable_file_opened_(true),
111 env_(env) {
112 assert(target_ != nullptr);
113 state_.pos_ = 0;
114}
115
116TestWritableFile::~TestWritableFile() {
117 if (writable_file_opened_) {
118 Close();
119 }
120}
121
122Status TestWritableFile::Append(const Slice& data) {
123 if (!env_->IsFilesystemActive()) {
11fdf7f2 124 return env_->GetError();
7c673cae
FG
125 }
126 Status s = target_->Append(data);
127 if (s.ok()) {
128 state_.pos_ += data.size();
129 }
130 return s;
131}
132
133Status TestWritableFile::Close() {
134 writable_file_opened_ = false;
135 Status s = target_->Close();
136 if (s.ok()) {
137 env_->WritableFileClosed(state_);
138 }
139 return s;
140}
141
142Status TestWritableFile::Flush() {
143 Status s = target_->Flush();
144 if (s.ok() && env_->IsFilesystemActive()) {
145 state_.pos_at_last_flush_ = state_.pos_;
146 }
147 return s;
148}
149
150Status TestWritableFile::Sync() {
151 if (!env_->IsFilesystemActive()) {
152 return Status::IOError("FaultInjectionTestEnv: not active");
153 }
154 // No need to actual sync.
155 state_.pos_at_last_sync_ = state_.pos_;
156 return Status::OK();
157}
158
159Status FaultInjectionTestEnv::NewDirectory(const std::string& name,
160 unique_ptr<Directory>* result) {
161 unique_ptr<Directory> r;
162 Status s = target()->NewDirectory(name, &r);
163 assert(s.ok());
164 if (!s.ok()) {
165 return s;
166 }
167 result->reset(new TestDirectory(this, TrimDirname(name), r.release()));
168 return Status::OK();
169}
170
171Status FaultInjectionTestEnv::NewWritableFile(const std::string& fname,
172 unique_ptr<WritableFile>* result,
173 const EnvOptions& soptions) {
174 if (!IsFilesystemActive()) {
11fdf7f2 175 return GetError();
7c673cae
FG
176 }
177 // Not allow overwriting files
178 Status s = target()->FileExists(fname);
179 if (s.ok()) {
180 return Status::Corruption("File already exists.");
181 } else if (!s.IsNotFound()) {
182 assert(s.IsIOError());
183 return s;
184 }
185 s = target()->NewWritableFile(fname, result, soptions);
186 if (s.ok()) {
187 result->reset(new TestWritableFile(fname, std::move(*result), this));
188 // WritableFileWriter* file is opened
189 // again then it will be truncated - so forget our saved state.
190 UntrackFile(fname);
191 MutexLock l(&mutex_);
192 open_files_.insert(fname);
193 auto dir_and_name = GetDirAndName(fname);
194 auto& list = dir_to_new_files_since_last_sync_[dir_and_name.first];
195 list.insert(dir_and_name.second);
196 }
197 return s;
198}
199
11fdf7f2
TL
200Status FaultInjectionTestEnv::NewRandomAccessFile(
201 const std::string& fname, std::unique_ptr<RandomAccessFile>* result,
202 const EnvOptions& soptions) {
203 if (!IsFilesystemActive()) {
204 return GetError();
205 }
206 return target()->NewRandomAccessFile(fname, result, soptions);
207}
208
7c673cae
FG
209Status FaultInjectionTestEnv::DeleteFile(const std::string& f) {
210 if (!IsFilesystemActive()) {
11fdf7f2 211 return GetError();
7c673cae
FG
212 }
213 Status s = EnvWrapper::DeleteFile(f);
214 if (!s.ok()) {
215 fprintf(stderr, "Cannot delete file %s: %s\n", f.c_str(),
216 s.ToString().c_str());
217 }
218 assert(s.ok());
219 if (s.ok()) {
220 UntrackFile(f);
221 }
222 return s;
223}
224
225Status FaultInjectionTestEnv::RenameFile(const std::string& s,
226 const std::string& t) {
227 if (!IsFilesystemActive()) {
11fdf7f2 228 return GetError();
7c673cae
FG
229 }
230 Status ret = EnvWrapper::RenameFile(s, t);
231
232 if (ret.ok()) {
233 MutexLock l(&mutex_);
234 if (db_file_state_.find(s) != db_file_state_.end()) {
235 db_file_state_[t] = db_file_state_[s];
236 db_file_state_.erase(s);
237 }
238
239 auto sdn = GetDirAndName(s);
240 auto tdn = GetDirAndName(t);
241 if (dir_to_new_files_since_last_sync_[sdn.first].erase(sdn.second) != 0) {
242 auto& tlist = dir_to_new_files_since_last_sync_[tdn.first];
243 assert(tlist.find(tdn.second) == tlist.end());
244 tlist.insert(tdn.second);
245 }
246 }
247
248 return ret;
249}
250
251void FaultInjectionTestEnv::WritableFileClosed(const FileState& state) {
252 MutexLock l(&mutex_);
253 if (open_files_.find(state.filename_) != open_files_.end()) {
254 db_file_state_[state.filename_] = state;
255 open_files_.erase(state.filename_);
256 }
257}
258
259// For every file that is not fully synced, make a call to `func` with
260// FileState of the file as the parameter.
261Status FaultInjectionTestEnv::DropFileData(
262 std::function<Status(Env*, FileState)> func) {
263 Status s;
264 MutexLock l(&mutex_);
265 for (std::map<std::string, FileState>::const_iterator it =
266 db_file_state_.begin();
267 s.ok() && it != db_file_state_.end(); ++it) {
268 const FileState& state = it->second;
269 if (!state.IsFullySynced()) {
270 s = func(target(), state);
271 }
272 }
273 return s;
274}
275
276Status FaultInjectionTestEnv::DropUnsyncedFileData() {
277 return DropFileData([&](Env* env, const FileState& state) {
278 return state.DropUnsyncedData(env);
279 });
280}
281
282Status FaultInjectionTestEnv::DropRandomUnsyncedFileData(Random* rnd) {
283 return DropFileData([&](Env* env, const FileState& state) {
284 return state.DropRandomUnsyncedData(env, rnd);
285 });
286}
287
288Status FaultInjectionTestEnv::DeleteFilesCreatedAfterLastDirSync() {
289 // Because DeleteFile access this container make a copy to avoid deadlock
290 std::map<std::string, std::set<std::string>> map_copy;
291 {
292 MutexLock l(&mutex_);
293 map_copy.insert(dir_to_new_files_since_last_sync_.begin(),
294 dir_to_new_files_since_last_sync_.end());
295 }
296
297 for (auto& pair : map_copy) {
298 for (std::string name : pair.second) {
299 Status s = DeleteFile(pair.first + "/" + name);
300 if (!s.ok()) {
301 return s;
302 }
303 }
304 }
305 return Status::OK();
306}
307void FaultInjectionTestEnv::ResetState() {
308 MutexLock l(&mutex_);
309 db_file_state_.clear();
310 dir_to_new_files_since_last_sync_.clear();
311 SetFilesystemActiveNoLock(true);
312}
313
314void FaultInjectionTestEnv::UntrackFile(const std::string& f) {
315 MutexLock l(&mutex_);
316 auto dir_and_name = GetDirAndName(f);
317 dir_to_new_files_since_last_sync_[dir_and_name.first].erase(
318 dir_and_name.second);
319 db_file_state_.erase(f);
320 open_files_.erase(f);
321}
322} // namespace rocksdb