GCC Code Coverage Report
Directory: cvmfs/ Exec Total Coverage
File: cvmfs/nfs_maps_leveldb.cc Lines: 0 174 0.0 %
Date: 2019-02-03 02:48:13 Branches: 0 74 0.0 %

Line Branch Exec Source
1
/**
2
 * This file is part of the CernVM File System.
3
 *
4
 * The NFS maps module maintains inode -- path relations.  An inode that is
5
 * issued once by an NFS exported file system might be asked for
6
 * any time later by clients.
7
 *
8
 * In "NFS mode", cvmfs will issue inodes consequtively and reuse inodes
9
 * based on path name.  The inode --> path and path --> inode maps are
10
 * handled by leveldb.  This workaround is comparable to the Fuse "noforget"
11
 * option, except that the mappings are persistent and thus consistent during
12
 * cvmfs restarts.  Also, leveldb allows for restricting the memory consumption.
13
 *
14
 * The maps are not accounted for by the cache quota.
15
 */
16
17
#include "nfs_maps_leveldb.h"
18
19
#include <unistd.h>
20
21
#include <cassert>
22
#include <cstddef>
23
24
#include "leveldb/cache.h"
25
#include "leveldb/db.h"
26
#include "leveldb/filter_policy.h"
27
#include "logging.h"
28
#include "smalloc.h"
29
#include "statistics.h"
30
#include "util/pointer.h"
31
#include "util/posix.h"
32
#include "util_concurrency.h"
33
34
using namespace std;  // NOLINT
35
36
37
NfsMapsLeveldb::ForkAwareEnv::ForkAwareEnv(NfsMapsLeveldb *maps)
38
  : leveldb::EnvWrapper(leveldb::Env::Default())
39
  , maps_(maps)
40
{
41
  atomic_init32(&num_bg_threads_);
42
}
43
44
45
void NfsMapsLeveldb::ForkAwareEnv::StartThread(void (*f)(void*), void* a) {
46
  if (maps_->spawned_) {
47
    leveldb::Env::Default()->StartThread(f, a);
48
    return;
49
  }
50
  LogCvmfs(kLogNfsMaps, kLogDebug,
51
           "single threaded leveldb::StartThread called");
52
  // Unclear how to handle this because caller assumes that thread is started
53
  abort();
54
}
55
56
57
void NfsMapsLeveldb::ForkAwareEnv::Schedule(void (*function)(void*), void* arg)
58
{
59
  if (maps_->spawned_) {
60
    leveldb::Env::Default()->Schedule(function, arg);
61
    return;
62
  }
63
  LogCvmfs(kLogNfsMaps, kLogDebug,
64
           "single threaded leveldb::Schedule called");
65
  FuncArg *funcarg = new FuncArg();
66
  funcarg->function = function;
67
  funcarg->arg = arg;
68
  funcarg->env = this;
69
  atomic_inc32(&num_bg_threads_);
70
  pthread_t bg_thread;
71
  int retval = pthread_create(&bg_thread, NULL, MainFakeThread, funcarg);
72
  assert(retval == 0);
73
  retval = pthread_detach(bg_thread);
74
  assert(retval == 0);
75
}
76
77
78
void NfsMapsLeveldb::ForkAwareEnv::WaitForBGThreads() {
79
  while (atomic_read32(&num_bg_threads_) > 0)
80
    SafeSleepMs(100);
81
}
82
83
84
/**
85
 * Leveldb's usleep might collide with the ALARM timer
86
 */
87
void NfsMapsLeveldb::ForkAwareEnv::SleepForMicroseconds(int micros) {
88
  SafeSleepMs(micros/1000);
89
}
90
91
92
void *NfsMapsLeveldb::ForkAwareEnv::MainFakeThread(void *data) {
93
  FuncArg *funcarg = reinterpret_cast<FuncArg *>(data);
94
  funcarg->function(funcarg->arg);
95
  atomic_dec32(&(funcarg->env->num_bg_threads_));
96
  delete funcarg;
97
  return NULL;
98
}
99
100
101
//------------------------------------------------------------------------------
102
103
104
NfsMapsLeveldb *NfsMapsLeveldb::Create(
105
    const string &leveldb_dir,
106
    const uint64_t root_inode,
107
    const bool rebuild,
108
    perf::Statistics *statistics)
109
{
110
  assert(root_inode > 0);
111
  UniquePtr<NfsMapsLeveldb> maps(new NfsMapsLeveldb());
112
  maps->n_db_added_ = statistics->Register(
113
    "nfs.leveldb.n_added", "total number of issued inode");
114
115
  maps->root_inode_ = root_inode;
116
  maps->fork_aware_env_ = new ForkAwareEnv(maps);
117
  leveldb::Status status;
118
  leveldb::Options leveldb_options;
119
  leveldb_options.create_if_missing = true;
120
  leveldb_options.env = maps->fork_aware_env_;
121
122
  // Remove previous database traces
123
  if (rebuild) {
124
    LogCvmfs(kLogNfsMaps, kLogSyslogWarn,
125
             "rebuilding NFS maps, might result in stale entries");
126
    bool retval = RemoveTree(leveldb_dir + "/inode2path") &&
127
                  RemoveTree(leveldb_dir + "/path2inode");
128
    if (!retval) {
129
      LogCvmfs(kLogNfsMaps, kLogDebug, "failed to remove previous databases");
130
      return NULL;
131
    }
132
  }
133
134
  // Open databases
135
  maps->cache_inode2path_ = leveldb::NewLRUCache(32 * 1024*1024);
136
  leveldb_options.block_cache = maps->cache_inode2path_;
137
  maps->filter_inode2path_ = leveldb::NewBloomFilterPolicy(10);
138
  leveldb_options.filter_policy = maps->filter_inode2path_;
139
  status = leveldb::DB::Open(leveldb_options, leveldb_dir + "/inode2path",
140
                             &maps->db_inode2path_);
141
  if (!status.ok()) {
142
    LogCvmfs(kLogNfsMaps, kLogDebug, "failed to create inode2path db: %s",
143
             status.ToString().c_str());
144
    return NULL;
145
  }
146
  LogCvmfs(kLogNfsMaps, kLogDebug, "inode2path opened");
147
148
  // Hashes and inodes, no compression here
149
  leveldb_options.compression = leveldb::kNoCompression;
150
  // Random order, small block size to not trash caches
151
  leveldb_options.block_size = 512;
152
  maps->cache_path2inode_ = leveldb::NewLRUCache(8 * 1024*1024);
153
  leveldb_options.block_cache = maps->cache_path2inode_;
154
  maps->filter_path2inode_ = leveldb::NewBloomFilterPolicy(10);
155
  leveldb_options.filter_policy = maps->filter_path2inode_;
156
  status = leveldb::DB::Open(leveldb_options, leveldb_dir + "/path2inode",
157
                             &maps->db_path2inode_);
158
  if (!status.ok()) {
159
    LogCvmfs(kLogNfsMaps, kLogDebug, "failed to create path2inode db: %s",
160
             status.ToString().c_str());
161
    return NULL;
162
  }
163
  LogCvmfs(kLogNfsMaps, kLogDebug, "path2inode opened");
164
165
  // Fetch highest issued inode
166
  maps->seq_ = maps->FindInode(shash::Md5(shash::AsciiPtr("?seq")));
167
  LogCvmfs(kLogNfsMaps, kLogDebug, "Sequence number is %" PRIu64, maps->seq_);
168
  if (maps->seq_ == 0) {
169
    maps->seq_ = maps->root_inode_;
170
    // Insert root inode
171
    PathString root_path;
172
    maps->GetInode(root_path);
173
  }
174
175
  maps->fork_aware_env_->WaitForBGThreads();
176
177
  return maps.Release();
178
}
179
180
void NfsMapsLeveldb::SetInodeResidue(unsigned residue_class, unsigned remainder)
181
{
182
  MutexLockGuard lock_guard(lock_);
183
  if (residue_class < 2) {
184
    inode_residue_class_ = 1;
185
    inode_remainder_ = 0;
186
  } else {
187
    inode_residue_class_ = residue_class;
188
    inode_remainder_ = remainder % residue_class;
189
    seq_ = ((seq_ / inode_residue_class_) + 1)
190
           * inode_residue_class_ + inode_remainder_;
191
  }
192
}
193
194
195
uint64_t NfsMapsLeveldb::FindInode(const shash::Md5 &path) {
196
  leveldb::Status status;
197
  leveldb::Slice key(reinterpret_cast<const char *>(path.digest),
198
                     path.GetDigestSize());
199
  string result;
200
201
  status = db_path2inode_->Get(leveldb::ReadOptions(), key, &result);
202
  if (!status.ok() && !status.IsNotFound()) {
203
    LogCvmfs(kLogNfsMaps, kLogSyslogErr,
204
             "failed to read from path2inode db (path %s): %s",
205
             path.ToString().c_str(), status.ToString().c_str());
206
    abort();
207
  }
208
209
  if (status.IsNotFound()) {
210
    LogCvmfs(kLogNfsMaps, kLogDebug, "path %s not found",
211
             path.ToString().c_str());
212
    return 0;
213
  } else {
214
    const uint64_t *inode = reinterpret_cast<const uint64_t *>(result.data());
215
    LogCvmfs(kLogNfsMaps, kLogDebug, "path %s maps to inode %" PRIu64,
216
             path.ToString().c_str(), *inode);
217
    return *inode;
218
  }
219
}
220
221
222
uint64_t NfsMapsLeveldb::GetInode(const PathString &path) {
223
  const shash::Md5 md5_path(path.GetChars(), path.GetLength());
224
  uint64_t inode = FindInode(md5_path);
225
  if (inode != 0)
226
    return inode;
227
228
  pthread_mutex_lock(lock_);
229
  // Search again to avoid race
230
  inode = FindInode(md5_path);
231
  if (inode != 0) {
232
    pthread_mutex_unlock(lock_);
233
    return inode;
234
  }
235
236
  // Issue new inode
237
  inode = seq_;
238
  seq_ += inode_residue_class_;
239
  PutPath2Inode(md5_path, inode);
240
  PutInode2Path(inode, path);
241
  pthread_mutex_unlock(lock_);
242
243
  perf::Inc(n_db_added_);
244
  return inode;
245
}
246
247
248
/**
249
 * Finds the path that belongs to an inode.  This must be successful.  The
250
 * inode input comes from the file system, i.e. it must have been issued
251
 * before.
252
 * \return false if not found
253
 */
254
bool NfsMapsLeveldb::GetPath(const uint64_t inode, PathString *path) {
255
  leveldb::Status status;
256
  leveldb::Slice key(reinterpret_cast<const char *>(&inode), sizeof(inode));
257
  string result;
258
259
  status = db_inode2path_->Get(leveldb::ReadOptions(), key, &result);
260
  if (status.IsNotFound()) {
261
    LogCvmfs(kLogNfsMaps, kLogDebug,
262
             "failed to find inode %" PRIu64 " in NFS maps, returning ESTALE",
263
             inode);
264
    return false;
265
  }
266
  if (!status.ok()) {
267
    LogCvmfs(kLogNfsMaps, kLogSyslogErr,
268
             "failed to read from inode2path db inode %" PRIu64 ": %s",
269
             inode, status.ToString().c_str());
270
    abort();
271
  }
272
273
  path->Assign(result.data(), result.length());
274
  LogCvmfs(kLogNfsMaps, kLogDebug, "inode %" PRIu64 " maps to path %s",
275
           inode, path->c_str());
276
  return true;
277
}
278
279
280
string NfsMapsLeveldb::GetStatistics() {
281
  string stats;
282
283
  db_inode2path_->GetProperty(leveldb::Slice("leveldb.stats"), &stats);
284
  stats += "inode --> path database:\n" + stats + "\n";
285
286
  db_path2inode_->GetProperty(leveldb::Slice("leveldb.stats"), &stats);
287
  stats += "path --> inode database:\n" + stats + "\n";
288
289
  return stats;
290
}
291
292
293
NfsMapsLeveldb::NfsMapsLeveldb()
294
  : db_inode2path_(NULL)
295
  , db_path2inode_(NULL)
296
  , cache_inode2path_(NULL)
297
  , cache_path2inode_(NULL)
298
  , filter_inode2path_(NULL)
299
  , filter_path2inode_(NULL)
300
  , fork_aware_env_(NULL)
301
  , root_inode_(0)
302
  , seq_(0)
303
  , lock_(NULL)
304
  , spawned_(false)
305
  , inode_residue_class_(1)
306
  , inode_remainder_(0)
307
  , n_db_added_(NULL)
308
{
309
  lock_ = reinterpret_cast<pthread_mutex_t *>(smalloc(sizeof(pthread_mutex_t)));
310
  int retval = pthread_mutex_init(lock_, NULL);
311
  assert(retval == 0);
312
}
313
314
315
NfsMapsLeveldb::~NfsMapsLeveldb() {
316
  PutPath2Inode(shash::Md5(shash::AsciiPtr("?seq")), seq_);
317
318
  delete db_path2inode_;
319
  delete cache_path2inode_;
320
  delete filter_path2inode_;
321
  LogCvmfs(kLogNfsMaps, kLogDebug, "path2inode closed");
322
  delete db_inode2path_;
323
  delete cache_inode2path_;
324
  delete filter_inode2path_;
325
  LogCvmfs(kLogNfsMaps, kLogDebug, "inode2path closed");
326
  delete fork_aware_env_;
327
  pthread_mutex_destroy(lock_);
328
  free(lock_);
329
}
330
331
332
void NfsMapsLeveldb::PutInode2Path(
333
  const uint64_t inode,
334
  const PathString &path)
335
{
336
  leveldb::Status status;
337
  leveldb::Slice key(reinterpret_cast<const char *>(&inode), sizeof(inode));
338
  leveldb::Slice value(path.GetChars(), path.GetLength());
339
340
  status = db_inode2path_->Put(leveldb::WriteOptions(), key, value);
341
  if (!status.ok()) {
342
    LogCvmfs(kLogNfsMaps, kLogSyslogErr,
343
             "failed to write inode2path entry (%" PRIu64 " --> %s): %s",
344
             inode, path.c_str(), status.ToString().c_str());
345
    abort();
346
  }
347
  LogCvmfs(kLogNfsMaps, kLogDebug, "stored inode %" PRIu64 " --> path %s",
348
           inode, path.c_str());
349
}
350
351
352
void NfsMapsLeveldb::PutPath2Inode(
353
  const shash::Md5 &path,
354
  const uint64_t inode)
355
{
356
  leveldb::Status status;
357
  leveldb::Slice key(reinterpret_cast<const char *>(path.digest),
358
                     path.GetDigestSize());
359
  leveldb::Slice value(reinterpret_cast<const char *>(&inode), sizeof(inode));
360
361
  status = db_path2inode_->Put(leveldb::WriteOptions(), key, value);
362
  if (!status.ok()) {
363
    LogCvmfs(kLogNfsMaps, kLogSyslogErr,
364
             "failed to write path2inode entry (%s --> %" PRIu64 "): %s",
365
             path.ToString().c_str(), inode, status.ToString().c_str());
366
    abort();
367
  }
368
  LogCvmfs(kLogNfsMaps, kLogDebug, "stored path %s --> inode %" PRIu64,
369
           path.ToString().c_str(), inode);
370
}