Merge branch '2016-03-08-cache-writeback' into v0.7-devel
This commit is contained in:
commit
642740e1b6
10
Makefile.in
10
Makefile.in
@ -36,11 +36,15 @@ SOURCE=\
|
||||
base/rolling_hash.cc \
|
||||
base/xml_utils.cc \
|
||||
block-cache/block_cache.cc \
|
||||
block-cache/copier.cc \
|
||||
block-cache/io_engine.cc \
|
||||
block-cache/mem_pool.cc \
|
||||
caching/cache_check.cc \
|
||||
caching/cache_dump.cc \
|
||||
caching/cache_metadata_size.cc \
|
||||
caching/cache_repair.cc \
|
||||
caching/cache_restore.cc \
|
||||
caching/cache_writeback.cc \
|
||||
caching/commands.cc \
|
||||
caching/hint_array.cc \
|
||||
caching/mapping_array.cc \
|
||||
@ -180,8 +184,8 @@ INSTALL_DATA = $(INSTALL) -p -m 644
|
||||
|
||||
ifeq ("@TESTING@", "yes")
|
||||
TEST_INCLUDES=\
|
||||
-Igmock-1.6.0/include \
|
||||
-Igmock-1.6.0/gtest/include
|
||||
-Igoogletest/googlemock/include \
|
||||
-Igoogletest/googletest/include
|
||||
else
|
||||
TEST_INCLUDES=
|
||||
endif
|
||||
@ -234,6 +238,7 @@ install: bin/pdata_tools
|
||||
ln -s -f pdata_tools $(BINDIR)/cache_metadata_size
|
||||
ln -s -f pdata_tools $(BINDIR)/cache_repair
|
||||
ln -s -f pdata_tools $(BINDIR)/cache_restore
|
||||
ln -s -f pdata_tools $(BINDIR)/cache_writeback
|
||||
ln -s -f pdata_tools $(BINDIR)/thin_check
|
||||
ln -s -f pdata_tools $(BINDIR)/thin_delta
|
||||
ln -s -f pdata_tools $(BINDIR)/thin_dump
|
||||
@ -253,6 +258,7 @@ install: bin/pdata_tools
|
||||
$(INSTALL_DATA) man8/cache_dump.8 $(MANPATH)/man8
|
||||
$(INSTALL_DATA) man8/cache_repair.8 $(MANPATH)/man8
|
||||
$(INSTALL_DATA) man8/cache_restore.8 $(MANPATH)/man8
|
||||
$(INSTALL_DATA) man8/cache_writeback.8 $(MANPATH)/man8
|
||||
$(INSTALL_DATA) man8/thin_check.8 $(MANPATH)/man8
|
||||
$(INSTALL_DATA) man8/thin_delta.8 $(MANPATH)/man8
|
||||
$(INSTALL_DATA) man8/thin_dump.8 $(MANPATH)/man8
|
||||
|
67
base/unique_handle.h
Normal file
67
base/unique_handle.h
Normal file
@ -0,0 +1,67 @@
|
||||
#ifndef BASE_UNIQUE_HANDLE_H
|
||||
#define BASE_UNIQUE_HANDLE_H
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <unistd.h>
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
namespace base {
|
||||
template <typename T, T TNul = T()>
|
||||
class unique_handle
|
||||
{
|
||||
public:
|
||||
unique_handle(std::nullptr_t = nullptr)
|
||||
: id_(TNul) {
|
||||
}
|
||||
|
||||
unique_handle(T x)
|
||||
: id_(x) {
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return id_ != TNul;
|
||||
}
|
||||
|
||||
operator T&() {
|
||||
return id_;
|
||||
}
|
||||
|
||||
operator T() const {
|
||||
return id_;
|
||||
}
|
||||
|
||||
T *operator&() {
|
||||
return &id_;
|
||||
}
|
||||
|
||||
const T *operator&() const {
|
||||
return &id_;
|
||||
}
|
||||
|
||||
friend bool operator == (unique_handle a, unique_handle b) { return a.id_ == b.id_; }
|
||||
friend bool operator != (unique_handle a, unique_handle b) { return a.id_ != b.id_; }
|
||||
friend bool operator == (unique_handle a, std::nullptr_t) { return a.id_ == TNul; }
|
||||
friend bool operator != (unique_handle a, std::nullptr_t) { return a.id_ != TNul; }
|
||||
friend bool operator == (std::nullptr_t, unique_handle b) { return TNul == b.id_; }
|
||||
friend bool operator != (std::nullptr_t, unique_handle b) { return TNul != b.id_; }
|
||||
|
||||
private:
|
||||
T id_;
|
||||
};
|
||||
|
||||
//--------------------------------
|
||||
|
||||
struct fd_deleter {
|
||||
typedef unique_handle<int, -1> pointer;
|
||||
void operator()(pointer p) {
|
||||
::close(p);
|
||||
}
|
||||
};
|
||||
typedef std::unique_ptr<int, fd_deleter> unique_fd;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
#endif
|
@ -525,8 +525,11 @@ block_cache::get(block_address index, unsigned flags, validator::ptr v)
|
||||
block *b = lookup_or_read_block(index, flags, v);
|
||||
|
||||
if (b) {
|
||||
if (b->ref_count_ && flags & (GF_DIRTY | GF_ZERO))
|
||||
throw std::runtime_error("attempt to write lock block concurrently");
|
||||
if (b->ref_count_ && (flags & (GF_DIRTY | GF_ZERO))) {
|
||||
std::ostringstream out;
|
||||
out << "attempt to write lock block " << index << " concurrently";
|
||||
throw std::runtime_error(out.str());
|
||||
}
|
||||
|
||||
// FIXME: this gets called even for new blocks
|
||||
hit(*b);
|
||||
|
192
block-cache/copier.cc
Normal file
192
block-cache/copier.cc
Normal file
@ -0,0 +1,192 @@
|
||||
#include "block-cache/copier.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace bcache;
|
||||
using namespace boost;
|
||||
using namespace std;
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
copier::copier(io_engine &engine,
|
||||
string const &src, string const &dest,
|
||||
sector_t block_size, size_t mem)
|
||||
: pool_(block_size * 512, mem, PAGE_SIZE),
|
||||
block_size_(block_size),
|
||||
nr_blocks_(mem / block_size),
|
||||
engine_(engine),
|
||||
src_handle_(engine_.open_file(src, io_engine::M_READ_ONLY)),
|
||||
dest_handle_(engine_.open_file(dest, io_engine::M_READ_WRITE)),
|
||||
genkey_count_(0)
|
||||
{
|
||||
}
|
||||
|
||||
copier::~copier()
|
||||
{
|
||||
engine_.close_file(src_handle_);
|
||||
engine_.close_file(dest_handle_);
|
||||
}
|
||||
|
||||
void
|
||||
copier::issue(copy_op const &op)
|
||||
{
|
||||
void *data;
|
||||
|
||||
while (!(data = pool_.alloc())) {
|
||||
wait_();
|
||||
|
||||
// data may still not be present because the wait_ could
|
||||
// have completed a read and issued the corresponding
|
||||
// write.
|
||||
}
|
||||
|
||||
copy_job job(op, data);
|
||||
job.op.read_complete = job.op.write_complete = false;
|
||||
unsigned key = genkey(); // used as context for the io_engine
|
||||
|
||||
auto r = engine_.issue_io(src_handle_,
|
||||
io_engine::D_READ,
|
||||
to_sector(op.src_b),
|
||||
to_sector(op.src_e),
|
||||
data,
|
||||
key);
|
||||
|
||||
if (r)
|
||||
jobs_.insert(make_pair(key, job));
|
||||
|
||||
else
|
||||
complete(job);
|
||||
}
|
||||
|
||||
unsigned
|
||||
copier::nr_pending() const
|
||||
{
|
||||
return jobs_.size() + complete_.size();
|
||||
}
|
||||
|
||||
boost::optional<copy_op>
|
||||
copier::wait()
|
||||
{
|
||||
if (complete_.empty())
|
||||
wait_();
|
||||
|
||||
return wait_complete();
|
||||
}
|
||||
|
||||
boost::optional<copy_op>
|
||||
copier::wait(unsigned µ)
|
||||
{
|
||||
if (complete_.empty())
|
||||
wait_(micro);
|
||||
return wait_complete();
|
||||
}
|
||||
|
||||
bool
|
||||
copier::pending() const
|
||||
{
|
||||
return !jobs_.empty();
|
||||
}
|
||||
|
||||
boost::optional<copy_op>
|
||||
copier::wait_complete()
|
||||
{
|
||||
if (complete_.empty()) {
|
||||
return optional<copy_op>();
|
||||
|
||||
} else {
|
||||
auto op = complete_.front();
|
||||
complete_.pop_front();
|
||||
return optional<copy_op>(op);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
copier::wait_(unsigned µ)
|
||||
{
|
||||
optional<io_engine::wait_result> mp;
|
||||
|
||||
if (!pending())
|
||||
return;
|
||||
|
||||
|
||||
bool completed = false;
|
||||
while (pending() && !completed) {
|
||||
mp = engine_.wait(micro);
|
||||
if (mp)
|
||||
completed = wait_successful(*mp);
|
||||
|
||||
if (!micro)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
copier::wait_()
|
||||
{
|
||||
bool completed = false;
|
||||
|
||||
while (pending() && !completed) {
|
||||
auto mp = engine_.wait();
|
||||
if (mp)
|
||||
completed = wait_successful(*mp);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
copier::wait_successful(io_engine::wait_result const &p)
|
||||
{
|
||||
auto it = jobs_.find(p.second);
|
||||
if (it == jobs_.end())
|
||||
throw runtime_error("Internal error. Lost track of copy job.");
|
||||
|
||||
copy_job &j = it->second;
|
||||
if (!p.first) {
|
||||
// IO was unsuccessful
|
||||
complete(j);
|
||||
jobs_.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
// IO was successful
|
||||
if (!j.op.read_complete) {
|
||||
j.op.read_complete = true;
|
||||
if (!engine_.issue_io(dest_handle_,
|
||||
io_engine::D_WRITE,
|
||||
to_sector(j.op.dest_b),
|
||||
to_sector(j.op.dest_b + (j.op.src_e - j.op.src_b)),
|
||||
j.data,
|
||||
it->first)) {
|
||||
complete(j);
|
||||
jobs_.erase(it);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
} else {
|
||||
j.op.write_complete = true;
|
||||
complete(j);
|
||||
jobs_.erase(it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
copier::complete(copy_job const &j)
|
||||
{
|
||||
pool_.free(j.data);
|
||||
complete_.push_back(j.op);
|
||||
}
|
||||
|
||||
sector_t
|
||||
copier::to_sector(block_address b) const
|
||||
{
|
||||
return b * block_size_;
|
||||
}
|
||||
|
||||
unsigned
|
||||
copier::genkey()
|
||||
{
|
||||
return genkey_count_++;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
106
block-cache/copier.h
Normal file
106
block-cache/copier.h
Normal file
@ -0,0 +1,106 @@
|
||||
#ifndef BLOCK_CACHE_COPIER_H
|
||||
#define BLOCK_CACHE_COPIER_H
|
||||
|
||||
#include "block-cache/io_engine.h"
|
||||
#include "block-cache/mem_pool.h"
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
namespace bcache {
|
||||
using block_address = uint64_t;
|
||||
|
||||
struct copy_op {
|
||||
copy_op()
|
||||
: src_b(0),
|
||||
src_e(0),
|
||||
dest_b(0),
|
||||
read_complete(false),
|
||||
write_complete(false) {
|
||||
}
|
||||
|
||||
copy_op(block_address src_b_,
|
||||
block_address src_e_,
|
||||
block_address dest_b_)
|
||||
: src_b(src_b_),
|
||||
src_e(src_e_),
|
||||
dest_b(dest_b_),
|
||||
read_complete(false),
|
||||
write_complete(false) {
|
||||
}
|
||||
|
||||
bool operator <(copy_op const &rhs) const {
|
||||
return dest_b < rhs.dest_b;
|
||||
}
|
||||
|
||||
bool success() const {
|
||||
return read_complete && write_complete;
|
||||
}
|
||||
|
||||
block_address src_b, src_e;
|
||||
block_address dest_b;
|
||||
|
||||
bool read_complete;
|
||||
bool write_complete;
|
||||
};
|
||||
|
||||
class copy_job {
|
||||
public:
|
||||
copy_job(copy_op const &op_, void *data_)
|
||||
: op(op_), data(data_) {
|
||||
}
|
||||
|
||||
copy_op op;
|
||||
void *data;
|
||||
};
|
||||
|
||||
class copier {
|
||||
public:
|
||||
copier(io_engine &engine,
|
||||
std::string const &src, std::string const &dest,
|
||||
sector_t block_size, size_t mem);
|
||||
~copier();
|
||||
|
||||
sector_t get_block_size() const {
|
||||
return block_size_;
|
||||
}
|
||||
|
||||
// Blocks if out of memory.
|
||||
void issue(copy_op const &op);
|
||||
|
||||
unsigned nr_pending() const;
|
||||
boost::optional<copy_op> wait();
|
||||
boost::optional<copy_op> wait(unsigned µ);
|
||||
|
||||
private:
|
||||
bool pending() const;
|
||||
bool wait_successful(io_engine::wait_result const &p);
|
||||
boost::optional<copy_op> wait_complete();
|
||||
void wait_(unsigned µ);
|
||||
void wait_();
|
||||
void complete(copy_job const &j);
|
||||
|
||||
sector_t to_sector(block_address b) const;
|
||||
unsigned genkey();
|
||||
|
||||
mempool pool_;
|
||||
sector_t block_size_;
|
||||
unsigned nr_blocks_;
|
||||
io_engine &engine_;
|
||||
io_engine::handle src_handle_;
|
||||
io_engine::handle dest_handle_;
|
||||
unsigned genkey_count_;
|
||||
|
||||
using job_map = std::map<unsigned, copy_job>;
|
||||
using op_list = std::list<copy_op>;
|
||||
job_map jobs_;
|
||||
op_list complete_;
|
||||
};
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
#endif
|
199
block-cache/io_engine.cc
Normal file
199
block-cache/io_engine.cc
Normal file
@ -0,0 +1,199 @@
|
||||
#include "base/container_of.h"
|
||||
#include "block-cache/io_engine.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
using namespace bcache;
|
||||
using namespace boost;
|
||||
using namespace std;
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
control_block_set::control_block_set(unsigned nr)
|
||||
: cbs_(nr)
|
||||
{
|
||||
for (auto i = 0u; i < nr; i++)
|
||||
free_cbs_.insert(i);
|
||||
}
|
||||
|
||||
iocb *
|
||||
control_block_set::alloc(unsigned context)
|
||||
{
|
||||
if (free_cbs_.empty())
|
||||
return nullptr;
|
||||
|
||||
auto it = free_cbs_.begin();
|
||||
|
||||
cblock &cb = cbs_[*it];
|
||||
cb.context = context;
|
||||
free_cbs_.erase(it);
|
||||
|
||||
return &cb.cb;
|
||||
}
|
||||
|
||||
void
|
||||
control_block_set::free(iocb *cb)
|
||||
{
|
||||
cblock *b = base::container_of(cb, &cblock::cb);
|
||||
unsigned index = b - &cbs_[0];
|
||||
free_cbs_.insert(index);
|
||||
}
|
||||
|
||||
unsigned
|
||||
control_block_set::context(iocb *cb) const
|
||||
{
|
||||
cblock *b = base::container_of(cb, &cblock::cb);
|
||||
return b->context;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
aio_engine::aio_engine(unsigned max_io)
|
||||
: aio_context_(0),
|
||||
cbs_(max_io)
|
||||
{
|
||||
int r = io_setup(max_io, &aio_context_);
|
||||
if (r < 0)
|
||||
throw runtime_error("io_setup failed");
|
||||
}
|
||||
|
||||
aio_engine::~aio_engine()
|
||||
{
|
||||
io_destroy(aio_context_);
|
||||
}
|
||||
|
||||
aio_engine::handle
|
||||
aio_engine::open_file(std::string const &path, mode m, sharing s)
|
||||
{
|
||||
int flags = (m == M_READ_ONLY) ? O_RDONLY : O_RDWR;
|
||||
if (s == EXCLUSIVE)
|
||||
flags |= O_EXCL;
|
||||
int fd = ::open(path.c_str(), O_DIRECT | flags);
|
||||
if (fd < 0) {
|
||||
ostringstream out;
|
||||
out << "unable to open '" << path << "'";
|
||||
throw runtime_error(out.str());
|
||||
}
|
||||
|
||||
descriptors_.push_back(base::unique_fd(fd));
|
||||
|
||||
return static_cast<handle>(fd);
|
||||
}
|
||||
|
||||
void
|
||||
aio_engine::close_file(handle h)
|
||||
{
|
||||
for (auto it = descriptors_.begin(); it != descriptors_.end(); ++it) {
|
||||
unsigned it_h = it->get();
|
||||
if (it_h == h) {
|
||||
descriptors_.erase(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ostringstream out;
|
||||
out << "unknown descriptor (" << h << ")";
|
||||
throw runtime_error(out.str());
|
||||
}
|
||||
|
||||
bool
|
||||
aio_engine::issue_io(handle h, dir d, sector_t b, sector_t e, void *data, unsigned context)
|
||||
{
|
||||
if (reinterpret_cast<uint64_t>(data) & (PAGE_SIZE - 1))
|
||||
throw runtime_error("Data passed to issue_io must be page aligned\n");
|
||||
|
||||
iocb *cb;
|
||||
|
||||
cb = cbs_.alloc(context);
|
||||
if (!cb)
|
||||
return false;
|
||||
|
||||
memset(cb, 0, sizeof(*cb));
|
||||
|
||||
cb->aio_fildes = static_cast<int>(h);
|
||||
cb->u.c.buf = data;
|
||||
cb->u.c.offset = b << SECTOR_SHIFT;
|
||||
cb->u.c.nbytes = (e - b) << SECTOR_SHIFT;
|
||||
cb->aio_lio_opcode = (d == D_READ) ? IO_CMD_PREAD : IO_CMD_PWRITE;
|
||||
|
||||
int r = io_submit(aio_context_, 1, &cb);
|
||||
return r == 1;
|
||||
}
|
||||
|
||||
optional<io_engine::wait_result>
|
||||
aio_engine::wait()
|
||||
{
|
||||
return wait_(NULL);
|
||||
}
|
||||
|
||||
optional<io_engine::wait_result>
|
||||
aio_engine::wait(unsigned µsec)
|
||||
{
|
||||
timespec start = micro_to_ts(microsec);
|
||||
timespec stop = start;
|
||||
auto r = wait_(&stop);
|
||||
microsec = ts_to_micro(stop) - microsec;
|
||||
return r;
|
||||
}
|
||||
|
||||
boost::optional<io_engine::wait_result>
|
||||
aio_engine::wait_(timespec *ts)
|
||||
{
|
||||
int r;
|
||||
struct io_event event;
|
||||
|
||||
memset(&event, 0, sizeof(event));
|
||||
r = io_getevents(aio_context_, 1, 1, &event, ts);
|
||||
if (r < 0) {
|
||||
std::ostringstream out;
|
||||
out << "io_getevents failed: " << r;
|
||||
throw std::runtime_error(out.str());
|
||||
}
|
||||
|
||||
if (r == 0) {
|
||||
return optional<wait_result>();
|
||||
}
|
||||
|
||||
iocb *cb = reinterpret_cast<iocb *>(event.obj);
|
||||
unsigned context = cbs_.context(cb);
|
||||
|
||||
if (event.res == cb->u.c.nbytes) {
|
||||
cbs_.free(cb);
|
||||
return optional<wait_result>(make_pair(true, context));
|
||||
|
||||
} else if (static_cast<int>(event.res) < 0) {
|
||||
cbs_.free(cb);
|
||||
return optional<wait_result>(make_pair(false, context));
|
||||
|
||||
} else {
|
||||
cbs_.free(cb);
|
||||
return optional<wait_result>(make_pair(false, context));
|
||||
}
|
||||
|
||||
// shouldn't get here
|
||||
return optional<wait_result>(make_pair(false, 0));
|
||||
}
|
||||
|
||||
struct timespec
|
||||
aio_engine::micro_to_ts(unsigned micro)
|
||||
{
|
||||
timespec ts;
|
||||
ts.tv_sec = micro / 1000000u;
|
||||
ts.tv_nsec = (micro % 1000000) * 1000;
|
||||
return ts;
|
||||
}
|
||||
|
||||
unsigned
|
||||
aio_engine::ts_to_micro(timespec const &ts)
|
||||
{
|
||||
unsigned micro = ts.tv_sec * 1000000;
|
||||
micro += ts.tv_nsec / 1000;
|
||||
return micro;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
117
block-cache/io_engine.h
Normal file
117
block-cache/io_engine.h
Normal file
@ -0,0 +1,117 @@
|
||||
#ifndef BLOCK_CACHE_IO_ENGINE_H
|
||||
#define BLOCK_CACHE_IO_ENGINE_H
|
||||
|
||||
#include "base/unique_handle.h"
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
#include <ctype.h>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <libaio.h>
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
namespace bcache {
|
||||
using sector_t = uint64_t;
|
||||
|
||||
unsigned const SECTOR_SHIFT = 9;
|
||||
unsigned const PAGE_SIZE = 4096;
|
||||
|
||||
// Virtual base class to aid unit testing
|
||||
class io_engine {
|
||||
public:
|
||||
enum mode {
|
||||
M_READ_ONLY,
|
||||
M_READ_WRITE
|
||||
};
|
||||
|
||||
enum dir {
|
||||
D_READ,
|
||||
D_WRITE
|
||||
};
|
||||
|
||||
enum sharing {
|
||||
EXCLUSIVE,
|
||||
SHARED
|
||||
};
|
||||
|
||||
io_engine() {}
|
||||
virtual ~io_engine() {}
|
||||
|
||||
using handle = unsigned;
|
||||
|
||||
virtual handle open_file(std::string const &path, mode m, sharing s = EXCLUSIVE) = 0;
|
||||
virtual void close_file(handle h) = 0;
|
||||
|
||||
// returns false if there are insufficient resources to
|
||||
// queue the IO
|
||||
virtual bool issue_io(handle h, dir d, sector_t b, sector_t e, void *data, unsigned context) = 0;
|
||||
|
||||
// returns (success, context)
|
||||
using wait_result = std::pair<bool, unsigned>;
|
||||
virtual boost::optional<wait_result> wait() = 0;
|
||||
virtual boost::optional<wait_result> wait(unsigned µsec) = 0;
|
||||
|
||||
private:
|
||||
io_engine(io_engine const &) = delete;
|
||||
io_engine &operator =(io_engine const &) = delete;
|
||||
};
|
||||
|
||||
//--------------------------------
|
||||
|
||||
class control_block_set {
|
||||
public:
|
||||
control_block_set(unsigned nr);
|
||||
|
||||
iocb *alloc(unsigned context);
|
||||
void free(iocb *);
|
||||
|
||||
unsigned context(iocb *) const;
|
||||
|
||||
private:
|
||||
struct cblock {
|
||||
unsigned context;
|
||||
struct iocb cb;
|
||||
};
|
||||
|
||||
std::set<unsigned> free_cbs_;
|
||||
std::vector<cblock> cbs_;
|
||||
};
|
||||
|
||||
//----------------
|
||||
|
||||
class aio_engine : public io_engine {
|
||||
public:
|
||||
// max_io is the maximum nr of concurrent ios expected
|
||||
aio_engine(unsigned max_io);
|
||||
~aio_engine();
|
||||
|
||||
using handle = unsigned;
|
||||
|
||||
virtual handle open_file(std::string const &path, mode m, sharing s = EXCLUSIVE);
|
||||
virtual void close_file(handle h);
|
||||
|
||||
// Returns false if queueing the io failed
|
||||
virtual bool issue_io(handle h, dir d, sector_t b, sector_t e, void *data, unsigned context);
|
||||
|
||||
virtual boost::optional<wait_result> wait();
|
||||
virtual boost::optional<wait_result> wait(unsigned µsec);
|
||||
|
||||
private:
|
||||
static struct timespec micro_to_ts(unsigned micro);
|
||||
static unsigned ts_to_micro(timespec const &ts);
|
||||
boost::optional<io_engine::wait_result> wait_(timespec *ts);
|
||||
|
||||
std::list<base::unique_fd> descriptors_;
|
||||
|
||||
io_context_t aio_context_;
|
||||
control_block_set cbs_;
|
||||
|
||||
aio_engine(io_engine const &) = delete;
|
||||
aio_engine &operator =(io_engine const &) = delete;
|
||||
};
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
#endif
|
62
block-cache/mem_pool.cc
Normal file
62
block-cache/mem_pool.cc
Normal file
@ -0,0 +1,62 @@
|
||||
#include "block-cache/mem_pool.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <stdlib.h>
|
||||
|
||||
using namespace bcache;
|
||||
using namespace boost;
|
||||
using namespace mempool_detail;
|
||||
using namespace std;
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
mempool::mempool(size_t block_size, size_t total_mem, size_t alignment)
|
||||
{
|
||||
mem_ = alloc_aligned(total_mem, alignment);
|
||||
|
||||
unsigned nr_blocks = total_mem / block_size;
|
||||
for (auto i = 0u; i < nr_blocks; i++)
|
||||
free(static_cast<unsigned char *>(mem_) + (block_size * i));
|
||||
}
|
||||
|
||||
mempool::~mempool()
|
||||
{
|
||||
free_.clear();
|
||||
::free(mem_);
|
||||
}
|
||||
|
||||
void *
|
||||
mempool::alloc()
|
||||
{
|
||||
if (free_.empty())
|
||||
return nullptr;
|
||||
|
||||
mempool_detail::alloc_block &b = free_.front();
|
||||
free_.pop_front();
|
||||
return reinterpret_cast<void *>(&b);
|
||||
}
|
||||
|
||||
void
|
||||
mempool::free(void *data)
|
||||
{
|
||||
mempool_detail::alloc_block *b = reinterpret_cast<mempool_detail::alloc_block *>(data);
|
||||
free_.push_front(*b);
|
||||
}
|
||||
|
||||
void *
|
||||
mempool::alloc_aligned(size_t len, size_t alignment)
|
||||
{
|
||||
void *result = NULL;
|
||||
int r = posix_memalign(&result, alignment, len);
|
||||
if (r) {
|
||||
ostringstream out;
|
||||
out << "posix_memalign failed: len = " << len << ", alignment = " << alignment << ", r = " << r << "\n";
|
||||
throw runtime_error(out.str());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
46
block-cache/mem_pool.h
Normal file
46
block-cache/mem_pool.h
Normal file
@ -0,0 +1,46 @@
|
||||
#ifndef BLOCK_CACHE_MEM_POOL_H
|
||||
#define BLOCK_CACHE_MEM_POOL_H
|
||||
|
||||
#include <boost/intrusive/list.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <list>
|
||||
|
||||
namespace bi = boost::intrusive;
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
namespace bcache {
|
||||
// FIXME: move to base?
|
||||
|
||||
namespace mempool_detail {
|
||||
struct alloc_block : public bi::list_base_hook<bi::link_mode<bi::normal_link>> {
|
||||
};
|
||||
};
|
||||
|
||||
class mempool {
|
||||
public:
|
||||
// alignment must be a power of 2
|
||||
mempool(size_t block_size, size_t total_mem, size_t alignment = 8);
|
||||
~mempool();
|
||||
|
||||
void *alloc();
|
||||
void free(void *data);
|
||||
|
||||
private:
|
||||
static void *alloc_aligned(size_t len, size_t alignment);
|
||||
|
||||
using block_list = bi::list<mempool_detail::alloc_block>;
|
||||
|
||||
void *mem_;
|
||||
block_list free_;
|
||||
|
||||
//----------------
|
||||
|
||||
mempool(mempool const &) = delete;
|
||||
mempool &operator =(mempool const &) = delete;
|
||||
};
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
#endif
|
459
caching/cache_writeback.cc
Normal file
459
caching/cache_writeback.cc
Normal file
@ -0,0 +1,459 @@
|
||||
#include "base/progress_monitor.h"
|
||||
#include "persistent-data/file_utils.h"
|
||||
#include "block-cache/copier.h"
|
||||
#include "caching/commands.h"
|
||||
#include "caching/mapping_array.h"
|
||||
#include "caching/metadata.h"
|
||||
#include "version.h"
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
#include <getopt.h>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace bcache;
|
||||
using namespace caching;
|
||||
using namespace boost;
|
||||
using namespace std;
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T> T safe_div(T const n, T const d, T const def) {
|
||||
return (d == T()) ? def : (n / d);
|
||||
}
|
||||
|
||||
//--------------------------------
|
||||
|
||||
struct flags {
|
||||
flags()
|
||||
: cache_size(4 * 1024 * 1024),
|
||||
sort_buffers(16 * 1024),
|
||||
list_failed_blocks(false),
|
||||
update_metadata(true) {
|
||||
}
|
||||
|
||||
// The sort buffers have a dramatic effect on the
|
||||
// performance. We give up 10% of the general buffer space
|
||||
// for them.
|
||||
void calc_sort_buffer_size() {
|
||||
size_t sbs = cache_size / 10;
|
||||
cache_size = cache_size - sbs;
|
||||
|
||||
sort_buffers = sbs / sizeof(copy_op);
|
||||
}
|
||||
|
||||
using maybe_string = boost::optional<string>;
|
||||
|
||||
size_t cache_size;
|
||||
unsigned sort_buffers;
|
||||
maybe_string metadata_dev;
|
||||
maybe_string origin_dev;
|
||||
maybe_string fast_dev;
|
||||
bool list_failed_blocks;
|
||||
bool update_metadata;
|
||||
};
|
||||
|
||||
//--------------------------------
|
||||
|
||||
class copy_batch {
|
||||
public:
|
||||
copy_batch(unsigned nr)
|
||||
: max_(nr),
|
||||
count_(0),
|
||||
ops_(nr) {
|
||||
}
|
||||
|
||||
bool space() const {
|
||||
return count_ < max_;
|
||||
}
|
||||
|
||||
void push_op(copy_op const &op) {
|
||||
if (!space())
|
||||
throw runtime_error("copy_batch out of space");
|
||||
|
||||
ops_[count_++] = op;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
count_ = 0;
|
||||
}
|
||||
|
||||
vector<copy_op>::iterator begin() {
|
||||
return ops_.begin();
|
||||
}
|
||||
|
||||
vector<copy_op>::iterator end() {
|
||||
return ops_.begin() + count_;
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned max_;
|
||||
unsigned count_;
|
||||
vector<copy_op> ops_;
|
||||
};
|
||||
|
||||
class copy_visitor : public mapping_visitor {
|
||||
public:
|
||||
copy_visitor(copier &c, unsigned sort_buffer, bool only_dirty,
|
||||
bool list_failed_blocks,
|
||||
progress_monitor &monitor, unsigned cache_blocks)
|
||||
: copier_(c),
|
||||
block_size_(c.get_block_size()),
|
||||
only_dirty_(only_dirty),
|
||||
list_failed_blocks_(list_failed_blocks),
|
||||
batch_(sort_buffer),
|
||||
monitor_(monitor),
|
||||
cache_blocks_(cache_blocks) {
|
||||
}
|
||||
|
||||
virtual void visit(block_address cblock, mapping const &m) {
|
||||
stats_.blocks_scanned = cblock;
|
||||
update_monitor();
|
||||
|
||||
if (!(m.flags_ & M_VALID))
|
||||
return;
|
||||
|
||||
if (only_dirty_ && !(m.flags_ & M_DIRTY))
|
||||
return;
|
||||
|
||||
copy_op cop;
|
||||
cop.src_b = cblock;
|
||||
cop.src_e = cblock + 1ull;
|
||||
cop.dest_b = m.oblock_;
|
||||
|
||||
// blocks
|
||||
stats_.blocks_needed++;
|
||||
batch_.push_op(cop);
|
||||
if (!batch_.space())
|
||||
issue();
|
||||
}
|
||||
|
||||
void issue() {
|
||||
auto compare_dest = [](copy_op const &lhs, copy_op const &rhs) {
|
||||
return lhs.dest_b < rhs.dest_b;
|
||||
};
|
||||
sort(batch_.begin(), batch_.end(), compare_dest);
|
||||
|
||||
auto e = batch_.end();
|
||||
for (auto it = batch_.begin(); it != e; ++it) {
|
||||
copier_.issue(*it);
|
||||
stats_.blocks_issued++;
|
||||
update_monitor();
|
||||
|
||||
check_for_completed_copies();
|
||||
}
|
||||
check_for_completed_copies();
|
||||
|
||||
batch_.reset();
|
||||
}
|
||||
|
||||
void check_for_completed_copies(bool block = false) {
|
||||
optional<copy_op> mop;
|
||||
|
||||
do {
|
||||
if (block)
|
||||
mop = copier_.wait();
|
||||
|
||||
else {
|
||||
unsigned micro = 0;
|
||||
mop = copier_.wait(micro);
|
||||
}
|
||||
|
||||
if (mop) {
|
||||
inc_completed(*mop);
|
||||
if (!mop->success()) {
|
||||
failed_blocks_.insert(*mop);
|
||||
failed_cblocks_.insert(mop->src_b);
|
||||
}
|
||||
}
|
||||
|
||||
} while (mop);
|
||||
}
|
||||
|
||||
void complete() {
|
||||
issue();
|
||||
|
||||
while (copier_.nr_pending())
|
||||
check_for_completed_copies(true);
|
||||
|
||||
monitor_.update_percent(100);
|
||||
cerr << "\n";
|
||||
}
|
||||
|
||||
void inc_completed(copy_op const &op) {
|
||||
stats_.blocks_completed++;
|
||||
update_monitor();
|
||||
}
|
||||
|
||||
void update_monitor() {
|
||||
static unsigned call_count = 0;
|
||||
if (call_count++ % 128)
|
||||
return;
|
||||
|
||||
uint64_t scanned = stats_.blocks_scanned * 100 / cache_blocks_;
|
||||
uint64_t copied = safe_div<block_address>(stats_.blocks_completed * 100,
|
||||
stats_.blocks_needed, 100ull);
|
||||
uint64_t percent = min<uint64_t>(scanned, copied);
|
||||
monitor_.update_percent(percent);
|
||||
}
|
||||
|
||||
struct copy_stats {
|
||||
copy_stats()
|
||||
: blocks_scanned(0),
|
||||
blocks_needed(0),
|
||||
blocks_issued(0),
|
||||
blocks_completed(0),
|
||||
blocks_failed(0) {
|
||||
}
|
||||
|
||||
block_address blocks_scanned;
|
||||
block_address blocks_needed;
|
||||
block_address blocks_issued;
|
||||
block_address blocks_completed;
|
||||
block_address blocks_failed;
|
||||
};
|
||||
|
||||
copy_stats const &get_stats() const {
|
||||
return stats_;
|
||||
}
|
||||
|
||||
set<block_address> failed_writebacks() const {
|
||||
return failed_cblocks_;
|
||||
}
|
||||
|
||||
private:
|
||||
copier &copier_;
|
||||
unsigned block_size_;
|
||||
bool only_dirty_;
|
||||
bool list_failed_blocks_;
|
||||
|
||||
copy_stats stats_;
|
||||
copy_batch batch_;
|
||||
progress_monitor &monitor_;
|
||||
unsigned cache_blocks_;
|
||||
|
||||
set<copy_op> failed_blocks_;
|
||||
set<block_address> failed_cblocks_;
|
||||
};
|
||||
|
||||
//--------------------------------
|
||||
|
||||
using namespace mapping_array_damage;
|
||||
|
||||
class ignore_damage_visitor : public damage_visitor {
|
||||
public:
|
||||
ignore_damage_visitor()
|
||||
: corruption_(false) {
|
||||
}
|
||||
|
||||
void visit(missing_mappings const &d) {
|
||||
cerr << "missing mappings (" << d.keys_.begin_ << ", " << d.keys_.end_ << "]\n";
|
||||
corruption_ = true;
|
||||
}
|
||||
|
||||
void visit(invalid_mapping const &d) {
|
||||
cerr << "invalid mapping cblock = " << d.cblock_ << ", oblock = " << d.m_.oblock_ << "\n";
|
||||
corruption_ = true;
|
||||
}
|
||||
|
||||
bool was_corruption() const {
|
||||
return corruption_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool corruption_;
|
||||
};
|
||||
|
||||
bool clean_shutdown(metadata const &md) {
|
||||
return md.sb_.flags.get_flag(superblock_flags::CLEAN_SHUTDOWN);
|
||||
}
|
||||
|
||||
void update_metadata(metadata &md, set<block_address> const &failed_writebacks) {
|
||||
cout << "Updating metadata ... ";
|
||||
|
||||
cout.flush();
|
||||
|
||||
auto &mappings = md.mappings_;
|
||||
for (block_address cblock = 0; cblock < mappings->get_nr_entries(); cblock++) {
|
||||
auto m = mappings->get(cblock);
|
||||
if (!(m.flags_ & M_VALID))
|
||||
continue;
|
||||
|
||||
if (!(m.flags_ & M_DIRTY))
|
||||
continue;
|
||||
|
||||
if (failed_writebacks.count(cblock))
|
||||
continue;
|
||||
|
||||
m.flags_ &= ~M_DIRTY;
|
||||
cerr << "clearing dirty flag for block " << cblock << "\n";
|
||||
mappings->set(cblock, m);
|
||||
}
|
||||
md.commit(true);
|
||||
cout << "done\n";
|
||||
cout.flush();
|
||||
}
|
||||
|
||||
int writeback_(flags const &f) {
|
||||
block_manager<>::ptr bm = open_bm(*f.metadata_dev, block_manager<>::READ_WRITE);
|
||||
metadata md(bm, metadata::OPEN);
|
||||
|
||||
// FIXME: we're going to have to copy runs to get the through put with small block sizes
|
||||
unsigned max_ios = f.cache_size / (md.sb_.data_block_size << SECTOR_SHIFT);
|
||||
aio_engine engine(max_ios);
|
||||
copier c(engine, *f.fast_dev, *f.origin_dev,
|
||||
md.sb_.data_block_size, f.cache_size);
|
||||
|
||||
auto bar = create_progress_bar("Copying data");
|
||||
copy_visitor cv(c, f.sort_buffers, clean_shutdown(md), f.list_failed_blocks,
|
||||
*bar, md.sb_.cache_blocks);
|
||||
|
||||
ignore_damage_visitor dv;
|
||||
|
||||
walk_mapping_array(*md.mappings_, cv, dv);
|
||||
cv.complete();
|
||||
|
||||
auto stats = cv.get_stats();
|
||||
cout << stats.blocks_issued - stats.blocks_failed << "/"
|
||||
<< stats.blocks_issued << " blocks successfully copied.\n";
|
||||
|
||||
if (stats.blocks_failed)
|
||||
cout << stats.blocks_failed << " blocks were not copied\n";
|
||||
|
||||
if (dv.was_corruption()) {
|
||||
cout << "Metadata corruption was found, some data may not have been copied.\n";
|
||||
if (f.update_metadata)
|
||||
cout << "Unable to update metadata.\n";
|
||||
|
||||
} else if (f.update_metadata)
|
||||
update_metadata(md, cv.failed_writebacks());
|
||||
|
||||
return (stats.blocks_failed || dv.was_corruption()) ? 1 : 0;
|
||||
}
|
||||
|
||||
int writeback(flags const &f) {
|
||||
int r;
|
||||
|
||||
try {
|
||||
r = writeback_(f);
|
||||
|
||||
} catch (std::exception &e) {
|
||||
cerr << e.what() << endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
cache_writeback_cmd::cache_writeback_cmd()
|
||||
: command("cache_writeback")
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
cache_writeback_cmd::usage(std::ostream &out) const
|
||||
{
|
||||
out << "Usage: " << get_name() << " [options]\n"
|
||||
<< "\t\t--metadata-device <dev>\n"
|
||||
<< "\t\t--origin-device <dev>\n"
|
||||
<< "\t\t--fast-device <dev>\n"
|
||||
<< "\t\t--buffer-size-meg <size>\n"
|
||||
<< "\t\t--list-failed-blocks\n"
|
||||
<< "\t\t--no-metadata-update\n"
|
||||
<< "Options:\n"
|
||||
<< " {-h|--help}\n"
|
||||
<< " {-V|--version}" << endl;
|
||||
}
|
||||
|
||||
int
|
||||
cache_writeback_cmd::run(int argc, char **argv)
|
||||
{
|
||||
int c;
|
||||
flags fs;
|
||||
char const *short_opts = "hV";
|
||||
option const long_opts[] = {
|
||||
{ "metadata-device", required_argument, NULL, 0 },
|
||||
{ "origin-device", required_argument, NULL, 1 },
|
||||
{ "fast-device", required_argument, NULL, 2 },
|
||||
{ "buffer-size-meg", required_argument, NULL, 3 },
|
||||
{ "list-failed-blocks", no_argument, NULL, 4 },
|
||||
{ "no-metadata-update", no_argument, NULL, 5 },
|
||||
{ "help", no_argument, NULL, 'h'},
|
||||
{ "version", no_argument, NULL, 'V'},
|
||||
{ NULL, no_argument, NULL, 0 }
|
||||
};
|
||||
|
||||
while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
|
||||
switch(c) {
|
||||
case 0:
|
||||
fs.metadata_dev = optarg;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
fs.origin_dev = optarg;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
fs.fast_dev = optarg;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
fs.cache_size = parse_uint64(optarg, "buffer size") * 1024 * 1024;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
fs.list_failed_blocks = true;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
fs.update_metadata = false;
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
usage(cout);
|
||||
return 0;
|
||||
|
||||
case 'V':
|
||||
cout << THIN_PROVISIONING_TOOLS_VERSION << endl;
|
||||
return 0;
|
||||
|
||||
default:
|
||||
usage(cerr);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
fs.calc_sort_buffer_size();
|
||||
|
||||
if (argc != optind) {
|
||||
usage(cerr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!fs.metadata_dev) {
|
||||
cerr << "No metadata device provided.\n\n";
|
||||
usage(cerr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!fs.origin_dev) {
|
||||
cerr << "No origin device provided.\n\n";
|
||||
usage(cerr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!fs.fast_dev) {
|
||||
cerr << "No fast device provided.\n\n";
|
||||
usage(cerr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return writeback(fs);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
@ -13,6 +13,7 @@ caching::register_cache_commands(application &app)
|
||||
app.add_cmd(command::ptr(new cache_metadata_size_cmd));
|
||||
app.add_cmd(command::ptr(new cache_restore_cmd));
|
||||
app.add_cmd(command::ptr(new cache_repair_cmd));
|
||||
app.add_cmd(command::ptr(new cache_writeback_cmd));
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
@ -63,6 +63,13 @@ namespace caching {
|
||||
virtual int run(int argc, char **argv);
|
||||
};
|
||||
|
||||
class cache_writeback_cmd : public base::command {
|
||||
public:
|
||||
cache_writeback_cmd();
|
||||
virtual void usage(std::ostream &out) const;
|
||||
virtual int run(int argc, char **argv);
|
||||
};
|
||||
|
||||
void register_cache_commands(base::application &app);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,3 @@
|
||||
#!/bin/sh -e
|
||||
|
||||
wget https://googlemock.googlecode.com/files/gmock-1.6.0.zip
|
||||
unzip gmock-1.6.0.zip
|
||||
cd gmock-1.6.0
|
||||
./configure
|
||||
git clone https://github.com/google/googletest
|
||||
|
53
man8/cache_writeback.8
Normal file
53
man8/cache_writeback.8
Normal file
@ -0,0 +1,53 @@
|
||||
.TH CACHE_WRITEBACK 8 "Thin Provisioning Tools" "Red Hat, Inc." \" -*- nroff -*-
|
||||
.SH NAME
|
||||
cache_writeback \- writeback dirty blocks to the origin device.
|
||||
|
||||
.SH SYNOPSIS
|
||||
.B cache_writeback
|
||||
.RB [ options ]
|
||||
.RB --metadata-device
|
||||
.I {device|file}
|
||||
.RB --origin-device
|
||||
.I {device|file}
|
||||
.RB --fast-device
|
||||
.I {device|file}
|
||||
|
||||
.SH DESCRIPTION
|
||||
.B cache_writeback
|
||||
|
||||
An offline tool that writesback dirty data to the data device
|
||||
(origin). Intended for use in recovery scenarios when the SSD is
|
||||
giving IO errors.
|
||||
|
||||
This tool cannot be run on a live cache.
|
||||
|
||||
.SH OPTIONS
|
||||
|
||||
.IP "\fB\\-\-metadata\-device\fP \fI{device|file}\fP"
|
||||
Location of cache metadata.
|
||||
|
||||
.IP "\fB\-\-origin\-device\fP \fI{device|file}\fP"
|
||||
Slow device being cached.
|
||||
|
||||
.IP "\fB\-\-fast\-device\fP \fI{device|file}\fP"
|
||||
Fast device containing the data that needs to be written back.
|
||||
|
||||
.IP "\fB\-\-skip\-metadata\-update\fP"
|
||||
Do not update the metadata to clear the dirty flags for written back
|
||||
data. You may not want to do this if you're decommissioning the
|
||||
cache.
|
||||
|
||||
.IP "\fB\-h, \-\-help\fP"
|
||||
Print help and exit.
|
||||
|
||||
.IP "\fB\-V, \-\-version\fP"
|
||||
Output version information and exit.
|
||||
|
||||
.SH SEE ALSO
|
||||
.B cache_dump(8)
|
||||
.B cache_check(8)
|
||||
.B cache_repair(8)
|
||||
.B cache_restore(8)
|
||||
|
||||
.SH AUTHOR
|
||||
Joe Thornber <ejt@redhat.com>
|
@ -19,6 +19,7 @@
|
||||
#include "block.h"
|
||||
|
||||
#include "base/error_string.h"
|
||||
#include "block-cache/io_engine.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
@ -38,8 +39,6 @@ namespace {
|
||||
using namespace std;
|
||||
|
||||
int const DEFAULT_MODE = 0666;
|
||||
unsigned const SECTOR_SHIFT = 9;
|
||||
|
||||
int const OPEN_FLAGS = O_DIRECT;
|
||||
|
||||
// FIXME: introduce a new exception for this, or at least lift this
|
||||
|
@ -66,6 +66,7 @@ namespace thin_provisioning {
|
||||
class thin_trim_cmd : public base::command {
|
||||
public:
|
||||
thin_trim_cmd();
|
||||
|
||||
virtual void usage(std::ostream &out) const;
|
||||
virtual int run(int argc, char **argv);
|
||||
};
|
||||
@ -74,6 +75,7 @@ namespace thin_provisioning {
|
||||
class thin_ll_dump_cmd : public base::command {
|
||||
public:
|
||||
thin_ll_dump_cmd();
|
||||
|
||||
virtual void usage(std::ostream &out) const;
|
||||
virtual int run(int argc, char **argv);
|
||||
};
|
||||
@ -81,6 +83,7 @@ namespace thin_provisioning {
|
||||
class thin_ll_restore_cmd : public base::command {
|
||||
public:
|
||||
thin_ll_restore_cmd();
|
||||
|
||||
virtual void usage(std::ostream &out) const;
|
||||
virtual int run(int argc, char **argv);
|
||||
};
|
||||
@ -88,6 +91,7 @@ namespace thin_provisioning {
|
||||
class thin_scan_cmd : public base::command {
|
||||
public:
|
||||
thin_scan_cmd();
|
||||
|
||||
virtual void usage(std::ostream &out) const;
|
||||
virtual int run(int argc, char **argv);
|
||||
};
|
||||
|
@ -16,10 +16,10 @@
|
||||
# with thin-provisioning-tools. If not, see
|
||||
# <http://www.gnu.org/licenses/>.
|
||||
|
||||
GMOCK_DIR=gmock-1.6.0/
|
||||
GMOCK_DIR=googletest
|
||||
GMOCK_INCLUDES=\
|
||||
-Igmock-1.6.0/include \
|
||||
-Igmock-1.6.0/gtest/include
|
||||
-I$(GMOCK_DIR)/googlemock/include \
|
||||
-I$(GMOCK_DIR)/googletest/include
|
||||
|
||||
GMOCK_FLAGS=\
|
||||
-Wno-unused-local-typedefs
|
||||
@ -28,16 +28,16 @@ GMOCK_LIBS=\
|
||||
-Llib -lpdata -lgmock -lpthread -laio
|
||||
|
||||
GMOCK_DEPS=\
|
||||
$(wildcard $(GMOCK_DIR)/include/*.h) \
|
||||
$(wildcard $(GMOCK_DIR)/src/*.cc) \
|
||||
$(wildcard $(GMOCK_DIR)/gtest/include/*.h) \
|
||||
$(wildcard $(GMOCK_DIR)/gtest/src/*.cc)
|
||||
$(wildcard $(GMOCK_DIR)/googlemock/include/*.h) \
|
||||
$(wildcard $(GMOCK_DIR)/googlemock/src/*.cc) \
|
||||
$(wildcard $(GMOCK_DIR)/googletest/include/*.h) \
|
||||
$(wildcard $(GMOCK_DIR)/googletest/src/*.cc)
|
||||
|
||||
lib/libgmock.a: $(GMOCK_DEPS)
|
||||
@echo " [CXX] gtest"
|
||||
$(V)g++ $(GMOCK_INCLUDES) -I$(GMOCK_DIR)/gtest -c $(GMOCK_DIR)/gtest/src/gtest-all.cc
|
||||
$(V)g++ $(GMOCK_INCLUDES) -I$(GMOCK_DIR)/googletest -c $(GMOCK_DIR)/googletest/src/gtest-all.cc
|
||||
@echo " [CXX] gmock"
|
||||
$(V)g++ $(GMOCK_INCLUDES) -I$(GMOCK_DIR) -c $(GMOCK_DIR)/src/gmock-all.cc
|
||||
$(V)g++ $(GMOCK_INCLUDES) -I$(GMOCK_DIR)/googlemock -c $(GMOCK_DIR)/googlemock/src/gmock-all.cc
|
||||
@echo " [AR] $<"
|
||||
$(V)ar -rv lib/libgmock.a gtest-all.o gmock-all.o > /dev/null 2>&1
|
||||
|
||||
@ -55,9 +55,12 @@ TEST_SOURCE=\
|
||||
unit-tests/btree_counter_t.cc \
|
||||
unit-tests/btree_damage_visitor_t.cc \
|
||||
unit-tests/cache_superblock_t.cc \
|
||||
unit-tests/copier_t.cc \
|
||||
unit-tests/damage_tracker_t.cc \
|
||||
unit-tests/endian_t.cc \
|
||||
unit-tests/error_state_t.cc \
|
||||
unit-tests/io_engine_t.cc \
|
||||
unit-tests/mem_pool_t.cc \
|
||||
unit-tests/rmap_visitor_t.cc \
|
||||
unit-tests/rolling_hash_t.cc \
|
||||
unit-tests/run_set_t.cc \
|
||||
|
@ -64,6 +64,7 @@ namespace {
|
||||
typedef boost::shared_ptr<validator_mock> ptr;
|
||||
|
||||
MOCK_CONST_METHOD2(check, void(void const *, block_address));
|
||||
MOCK_CONST_METHOD1(check_raw, bool(void const *data));
|
||||
MOCK_CONST_METHOD2(prepare, void(void *, block_address));
|
||||
};
|
||||
|
||||
|
273
unit-tests/copier_t.cc
Normal file
273
unit-tests/copier_t.cc
Normal file
@ -0,0 +1,273 @@
|
||||
// Copyright (C) 2016 Red Hat, Inc. All rights reserved.
|
||||
//
|
||||
// This file is part of the thin-provisioning-tools source.
|
||||
//
|
||||
// thin-provisioning-tools is free software: you can redistribute it
|
||||
// and/or modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
//
|
||||
// thin-provisioning-tools is distributed in the hope that it will be
|
||||
// useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with thin-provisioning-tools. If not, see
|
||||
// <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "block-cache/copier.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
using namespace boost;
|
||||
using namespace std;
|
||||
using namespace test;
|
||||
using namespace testing;
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
unsigned const BLOCK_SIZE = 64u;
|
||||
using wait_result = io_engine::wait_result;
|
||||
|
||||
ostream &operator <<(ostream &out, wait_result const &wr) {
|
||||
out << "wait_result[" << wr.first << ", " << wr.second << "]";
|
||||
return out;
|
||||
}
|
||||
|
||||
ostream &operator <<(ostream &out, optional<wait_result> const &mwr) {
|
||||
if (mwr) {
|
||||
out << "Just[wait_result[" << mwr->first << ", " << mwr->second << "]]";
|
||||
} else
|
||||
out << "Nothing";
|
||||
return out;
|
||||
}
|
||||
|
||||
class io_engine_mock : public io_engine {
|
||||
public:
|
||||
MOCK_METHOD3(open_file, handle(string const &, mode, sharing));
|
||||
MOCK_METHOD1(close_file, void(handle));
|
||||
MOCK_METHOD6(issue_io, bool(handle, dir, sector_t, sector_t, void *, unsigned));
|
||||
|
||||
MOCK_METHOD0(wait, optional<wait_result>());
|
||||
MOCK_METHOD1(wait, optional<wait_result>(unsigned &));
|
||||
};
|
||||
|
||||
class CopierTests : public Test {
|
||||
public:
|
||||
CopierTests()
|
||||
: src_file_("copy_src"),
|
||||
dest_file_("copy_dest") {
|
||||
}
|
||||
|
||||
unique_ptr<copier> make_copier() {
|
||||
EXPECT_CALL(engine_, open_file(src_file_, io_engine::M_READ_ONLY, io_engine::EXCLUSIVE)).
|
||||
WillOnce(Return(SRC_HANDLE));
|
||||
EXPECT_CALL(engine_, open_file(dest_file_, io_engine::M_READ_WRITE, io_engine::EXCLUSIVE)).
|
||||
WillOnce(Return(DEST_HANDLE));
|
||||
|
||||
EXPECT_CALL(engine_, close_file(SRC_HANDLE)).Times(1);
|
||||
EXPECT_CALL(engine_, close_file(DEST_HANDLE)).Times(1);
|
||||
|
||||
return unique_ptr<copier>(new copier(engine_, src_file_,
|
||||
dest_file_,
|
||||
BLOCK_SIZE, 1 * 1024 * 1024));
|
||||
}
|
||||
|
||||
static optional<wait_result> make_wr(bool success, unsigned context) {
|
||||
return optional<wait_result>(wait_result(success, context));
|
||||
}
|
||||
|
||||
void issue_successful_op(copier &c, copy_op &op, unsigned context) {
|
||||
InSequence dummy;
|
||||
|
||||
unsigned nr_pending = c.nr_pending();
|
||||
EXPECT_CALL(engine_, issue_io(SRC_HANDLE, io_engine::D_READ,
|
||||
op.src_b * BLOCK_SIZE,
|
||||
op.src_e * BLOCK_SIZE, _, context)).
|
||||
WillOnce(Return(true));
|
||||
c.issue(op);
|
||||
|
||||
ASSERT_TRUE(c.nr_pending() == nr_pending + 1);
|
||||
|
||||
EXPECT_CALL(engine_, wait()).
|
||||
WillOnce(Return(make_wr(true, context)));
|
||||
|
||||
EXPECT_CALL(engine_, issue_io(DEST_HANDLE, io_engine::D_WRITE,
|
||||
op.dest_b * BLOCK_SIZE,
|
||||
(op.dest_b + (op.src_e - op.src_b)) * BLOCK_SIZE, _, context)).
|
||||
WillOnce(Return(true));
|
||||
|
||||
EXPECT_CALL(engine_, wait()).
|
||||
WillOnce(Return(make_wr(true, context)));
|
||||
|
||||
auto mop = c.wait();
|
||||
ASSERT_EQ(c.nr_pending(), nr_pending);
|
||||
|
||||
ASSERT_TRUE(mop->success());
|
||||
}
|
||||
|
||||
unsigned const SRC_HANDLE = 10;
|
||||
unsigned const DEST_HANDLE = 11;
|
||||
|
||||
string src_file_;
|
||||
string dest_file_;
|
||||
StrictMock<io_engine_mock> engine_;
|
||||
};
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
TEST_F(CopierTests, empty_test)
|
||||
{
|
||||
auto c = make_copier();
|
||||
}
|
||||
|
||||
TEST_F(CopierTests, successful_copy)
|
||||
{
|
||||
// Copy first block
|
||||
copy_op op1(0, 1, 0);
|
||||
|
||||
auto c = make_copier();
|
||||
issue_successful_op(*c, op1, 0);
|
||||
}
|
||||
|
||||
TEST_F(CopierTests, unsuccessful_issue_read)
|
||||
{
|
||||
copy_op op1(0, 1, 0);
|
||||
auto c = make_copier();
|
||||
|
||||
InSequence dummy;
|
||||
EXPECT_CALL(engine_, issue_io(SRC_HANDLE, io_engine::D_READ, 0, BLOCK_SIZE, _, 0)).
|
||||
WillOnce(Return(false));
|
||||
c->issue(op1);
|
||||
|
||||
ASSERT_EQ(c->nr_pending(), 1u);
|
||||
|
||||
auto mop = c->wait();
|
||||
ASSERT_EQ(c->nr_pending(), 0u);
|
||||
ASSERT_FALSE(mop->success());
|
||||
}
|
||||
|
||||
TEST_F(CopierTests, unsuccessful_read)
|
||||
{
|
||||
copy_op op1(0, 1, 0);
|
||||
auto c = make_copier();
|
||||
|
||||
InSequence dummy;
|
||||
EXPECT_CALL(engine_, issue_io(SRC_HANDLE, io_engine::D_READ, 0, BLOCK_SIZE, _, 0)).
|
||||
WillOnce(Return(true));
|
||||
c->issue(op1);
|
||||
|
||||
ASSERT_EQ(c->nr_pending(), 1u);
|
||||
EXPECT_CALL(engine_, wait()).
|
||||
WillOnce(Return(make_wr(false, 0u)));
|
||||
ASSERT_EQ(c->nr_pending(), 1u);
|
||||
|
||||
auto mop = c->wait();
|
||||
ASSERT_EQ(c->nr_pending(), 0u);
|
||||
ASSERT_FALSE(mop->success());
|
||||
}
|
||||
|
||||
TEST_F(CopierTests, unsuccessful_issue_write)
|
||||
{
|
||||
copy_op op1(0, 1, 0);
|
||||
auto c = make_copier();
|
||||
|
||||
InSequence dummy;
|
||||
EXPECT_CALL(engine_, issue_io(SRC_HANDLE, io_engine::D_READ, 0, BLOCK_SIZE, _, 0)).
|
||||
WillOnce(Return(true));
|
||||
c->issue(op1);
|
||||
|
||||
ASSERT_EQ(c->nr_pending(), 1u);
|
||||
|
||||
EXPECT_CALL(engine_, wait()).
|
||||
WillOnce(Return(make_wr(true, 0u)));
|
||||
ASSERT_EQ(c->nr_pending(), 1u);
|
||||
|
||||
EXPECT_CALL(engine_, issue_io(DEST_HANDLE, io_engine::D_WRITE, 0, BLOCK_SIZE, _, 0)).
|
||||
WillOnce(Return(false));
|
||||
|
||||
auto mop = c->wait();
|
||||
ASSERT_EQ(c->nr_pending(), 0u);
|
||||
ASSERT_FALSE(mop->success());
|
||||
}
|
||||
|
||||
TEST_F(CopierTests, unsuccessful_write)
|
||||
{
|
||||
// Copy first block
|
||||
copy_op op1(0, 1, 0);
|
||||
|
||||
auto c = make_copier();
|
||||
|
||||
InSequence dummy;
|
||||
EXPECT_CALL(engine_, issue_io(SRC_HANDLE, io_engine::D_READ, 0, BLOCK_SIZE, _, 0)).
|
||||
WillOnce(Return(true));
|
||||
c->issue(op1);
|
||||
ASSERT_EQ(c->nr_pending(), 1u);
|
||||
|
||||
EXPECT_CALL(engine_, wait()).
|
||||
WillOnce(Return(make_wr(true, 0u)));
|
||||
|
||||
EXPECT_CALL(engine_, issue_io(DEST_HANDLE, io_engine::D_WRITE, 0, BLOCK_SIZE, _, 0)).
|
||||
WillOnce(Return(true));
|
||||
|
||||
EXPECT_CALL(engine_, wait()).
|
||||
WillOnce(Return(make_wr(false, 0u)));
|
||||
|
||||
auto mop = c->wait();
|
||||
ASSERT_EQ(c->nr_pending(), 0u);
|
||||
|
||||
ASSERT_FALSE(mop->success());
|
||||
}
|
||||
|
||||
TEST_F(CopierTests, copy_the_same_block_many_times)
|
||||
{
|
||||
auto c = make_copier();
|
||||
copy_op op1(0, 1, 0);
|
||||
|
||||
for (unsigned i = 0; i < 50000; i++)
|
||||
issue_successful_op(*c, op1, i);
|
||||
}
|
||||
|
||||
TEST_F(CopierTests, copy_different_blocks)
|
||||
{
|
||||
auto c = make_copier();
|
||||
for (unsigned i = 0; i < 5000; i++) {
|
||||
copy_op op(i, i + 1, i);
|
||||
issue_successful_op(*c, op, i);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CopierTests, wait_can_timeout)
|
||||
{
|
||||
copy_op op1(0, 1, 0);
|
||||
auto c = make_copier();
|
||||
|
||||
InSequence dummy;
|
||||
EXPECT_CALL(engine_, issue_io(SRC_HANDLE, io_engine::D_READ, 0, BLOCK_SIZE, _, 0)).
|
||||
WillOnce(Return(true));
|
||||
c->issue(op1);
|
||||
|
||||
ASSERT_EQ(c->nr_pending(), 1u);
|
||||
|
||||
unsigned micro = 10000;
|
||||
EXPECT_CALL(engine_, wait(micro)).
|
||||
WillOnce(Return(make_wr(true, 0u)));
|
||||
ASSERT_EQ(c->nr_pending(), 1u);
|
||||
|
||||
EXPECT_CALL(engine_, issue_io(DEST_HANDLE, io_engine::D_WRITE, 0, BLOCK_SIZE, _, 0)).
|
||||
WillOnce(Return(true));
|
||||
|
||||
EXPECT_CALL(engine_, wait(micro)).
|
||||
WillOnce(DoAll(SetArgReferee<0>(0u), Return(optional<wait_result>())));
|
||||
|
||||
auto mop = c->wait(micro);
|
||||
ASSERT_FALSE(mop);
|
||||
ASSERT_EQ(c->nr_pending(), 1u);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
206
unit-tests/io_engine_t.cc
Normal file
206
unit-tests/io_engine_t.cc
Normal file
@ -0,0 +1,206 @@
|
||||
// Copyright (C) 2016 Red Hat, Inc. All rights reserved.
|
||||
//
|
||||
// This file is part of the thin-provisioning-tools source.
|
||||
//
|
||||
// thin-provisioning-tools is free software: you can redistribute it
|
||||
// and/or modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
//
|
||||
// thin-provisioning-tools is distributed in the hope that it will be
|
||||
// useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with thin-provisioning-tools. If not, see
|
||||
// <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "block-cache/mem_pool.h"
|
||||
#include "block-cache/io_engine.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
using namespace boost;
|
||||
using namespace std;
|
||||
using namespace test;
|
||||
using namespace testing;
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
unsigned const MAX_IO = 64;
|
||||
unsigned const PAGE_SIZE = 4096;
|
||||
|
||||
class IOEngineTests : public Test {
|
||||
public:
|
||||
IOEngineTests()
|
||||
: pool_(64 * 512, 128 * 512, PAGE_SIZE),
|
||||
src_file_("copy_src", 32),
|
||||
dest_file_("copy_dest", 32),
|
||||
engine_(new aio_engine(MAX_IO)) {
|
||||
}
|
||||
|
||||
// in sectors
|
||||
static uint64_t meg(unsigned n) {
|
||||
return 2 * 1024 * n;
|
||||
}
|
||||
|
||||
mempool pool_;
|
||||
temp_file src_file_;
|
||||
temp_file dest_file_;
|
||||
unique_ptr<io_engine> engine_;
|
||||
};
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
TEST_F(IOEngineTests, empty_test)
|
||||
{
|
||||
}
|
||||
|
||||
TEST_F(IOEngineTests, open_and_close)
|
||||
{
|
||||
auto src_handle = engine_->open_file(src_file_.get_path(), io_engine::M_READ_ONLY);
|
||||
auto dest_handle = engine_->open_file(dest_file_.get_path(), io_engine::M_READ_WRITE);
|
||||
ASSERT_TRUE(src_handle != dest_handle);
|
||||
engine_->close_file(src_handle);
|
||||
engine_->close_file(dest_handle);
|
||||
}
|
||||
|
||||
TEST_F(IOEngineTests, you_can_read_a_read_only_handle)
|
||||
{
|
||||
unsigned nr_sectors = 8;
|
||||
auto src_handle = engine_->open_file(src_file_.get_path(), io_engine::M_READ_ONLY);
|
||||
void *data = pool_.alloc();
|
||||
bool r = engine_->issue_io(src_handle,
|
||||
io_engine::D_READ,
|
||||
0, nr_sectors,
|
||||
data,
|
||||
123);
|
||||
ASSERT_TRUE(r);
|
||||
auto wr = engine_->wait();
|
||||
ASSERT_TRUE(wr->first);
|
||||
ASSERT_TRUE(wr->second == 123);
|
||||
|
||||
engine_->close_file(src_handle);
|
||||
pool_.free(data);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(IOEngineTests, you_cannot_write_to_a_read_only_handle)
|
||||
{
|
||||
unsigned nr_sectors = 8;
|
||||
auto src_handle = engine_->open_file(src_file_.get_path(), io_engine::M_READ_ONLY);
|
||||
void *data = pool_.alloc();
|
||||
bool r = engine_->issue_io(src_handle,
|
||||
io_engine::D_WRITE,
|
||||
0, nr_sectors,
|
||||
data,
|
||||
0);
|
||||
ASSERT_FALSE(r);
|
||||
engine_->close_file(src_handle);
|
||||
pool_.free(data);
|
||||
}
|
||||
|
||||
TEST_F(IOEngineTests, you_can_write_to_a_read_write_handle)
|
||||
{
|
||||
unsigned nr_sectors = 8;
|
||||
auto src_handle = engine_->open_file(src_file_.get_path(), io_engine::M_READ_ONLY);
|
||||
void *data = pool_.alloc();
|
||||
bool r = engine_->issue_io(src_handle,
|
||||
io_engine::D_READ,
|
||||
0, nr_sectors,
|
||||
data,
|
||||
123);
|
||||
ASSERT_TRUE(r);
|
||||
auto wr = engine_->wait();
|
||||
ASSERT_TRUE(wr->first);
|
||||
ASSERT_TRUE(wr->second == 123);
|
||||
|
||||
engine_->close_file(src_handle);
|
||||
pool_.free(data);
|
||||
}
|
||||
|
||||
TEST_F(IOEngineTests, final_block_read_succeeds)
|
||||
{
|
||||
unsigned nr_sectors = 8;
|
||||
auto src_handle = engine_->open_file(src_file_.get_path(), io_engine::M_READ_ONLY);
|
||||
void *data = pool_.alloc();
|
||||
bool r = engine_->issue_io(src_handle,
|
||||
io_engine::D_READ,
|
||||
meg(32) - nr_sectors, meg(32),
|
||||
data,
|
||||
123);
|
||||
ASSERT_TRUE(r);
|
||||
auto wr = engine_->wait();
|
||||
ASSERT_TRUE(wr->first);
|
||||
|
||||
engine_->close_file(src_handle);
|
||||
pool_.free(data);
|
||||
|
||||
}
|
||||
|
||||
TEST_F(IOEngineTests, out_of_bounds_read_fails)
|
||||
{
|
||||
unsigned nr_sectors = 8;
|
||||
auto src_handle = engine_->open_file(src_file_.get_path(), io_engine::M_READ_ONLY);
|
||||
void *data = pool_.alloc();
|
||||
bool r = engine_->issue_io(src_handle,
|
||||
io_engine::D_READ,
|
||||
meg(32), meg(32) + nr_sectors,
|
||||
data,
|
||||
123);
|
||||
ASSERT_TRUE(r);
|
||||
auto wr = engine_->wait();
|
||||
ASSERT_FALSE(wr->first);
|
||||
|
||||
engine_->close_file(src_handle);
|
||||
pool_.free(data);
|
||||
|
||||
}
|
||||
|
||||
TEST_F(IOEngineTests, out_of_bounds_write_succeeds)
|
||||
{
|
||||
unsigned nr_sectors = 8;
|
||||
auto handle = engine_->open_file(dest_file_.get_path(), io_engine::M_READ_WRITE);
|
||||
void *data = pool_.alloc();
|
||||
bool r = engine_->issue_io(handle,
|
||||
io_engine::D_WRITE,
|
||||
meg(32), meg(32) + nr_sectors,
|
||||
data,
|
||||
123);
|
||||
ASSERT_TRUE(r);
|
||||
auto wr = engine_->wait();
|
||||
ASSERT_TRUE(wr->first);
|
||||
|
||||
engine_->close_file(handle);
|
||||
pool_.free(data);
|
||||
|
||||
}
|
||||
|
||||
TEST_F(IOEngineTests, succeed_with_timeout)
|
||||
{
|
||||
unsigned nr_sectors = 8;
|
||||
auto src_handle = engine_->open_file(src_file_.get_path(), io_engine::M_READ_ONLY);
|
||||
void *data = pool_.alloc();
|
||||
bool r = engine_->issue_io(src_handle,
|
||||
io_engine::D_READ,
|
||||
0, nr_sectors,
|
||||
data,
|
||||
123);
|
||||
ASSERT_TRUE(r);
|
||||
unsigned micro = 10;
|
||||
auto wr = engine_->wait(micro);
|
||||
ASSERT_TRUE(wr->first);
|
||||
ASSERT_TRUE(wr->second == 123);
|
||||
|
||||
engine_->close_file(src_handle);
|
||||
pool_.free(data);
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------
|
112
unit-tests/mem_pool_t.cc
Normal file
112
unit-tests/mem_pool_t.cc
Normal file
@ -0,0 +1,112 @@
|
||||
// Copyright (C) 2016 Red Hat, Inc. All rights reserved.
|
||||
//
|
||||
// This file is part of the thin-provisioning-tools source.
|
||||
//
|
||||
// thin-provisioning-tools is free software: you can redistribute it
|
||||
// and/or modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
//
|
||||
// thin-provisioning-tools is distributed in the hope that it will be
|
||||
// useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with thin-provisioning-tools. If not, see
|
||||
// <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "block-cache/mem_pool.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
using namespace boost;
|
||||
using namespace std;
|
||||
using namespace test;
|
||||
using namespace testing;
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
class MempoolTests : public Test {
|
||||
public:
|
||||
bool aligned(void *data, size_t alignment) {
|
||||
return (reinterpret_cast<size_t>(data) % alignment) == 0;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
TEST_F(MempoolTests, empty_test)
|
||||
{
|
||||
}
|
||||
|
||||
TEST_F(MempoolTests, create_destroy_cycle)
|
||||
{
|
||||
for (size_t bs = 64; bs <= 512; bs *= 2) {
|
||||
mempool mp(bs, 4 * 1024 * 1024, bs);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(MempoolTests, alignments_observed)
|
||||
{
|
||||
for (size_t bs = 64; bs <= 512; bs *= 2) {
|
||||
mempool mp(bs, 512 * 1024, bs);
|
||||
|
||||
for (unsigned i = 0; i < 100; i++) {
|
||||
auto md = mp.alloc();
|
||||
|
||||
if (!md)
|
||||
throw runtime_error("couldn't alloc");
|
||||
|
||||
ASSERT_TRUE(aligned(md, bs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(MempoolTests, alloc_free_cycle)
|
||||
{
|
||||
mempool mp(512, 512 * 1024, 512);
|
||||
|
||||
for (unsigned i = 0; i < 10000; i++) {
|
||||
auto md = mp.alloc();
|
||||
mp.free(md);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(MempoolTests, exhaust_pool)
|
||||
{
|
||||
mempool mp(512, 100 * 512, 512);
|
||||
|
||||
for (unsigned i = 0; i < 100; i++) {
|
||||
auto md = mp.alloc();
|
||||
ASSERT_NE(md, nullptr);
|
||||
}
|
||||
|
||||
auto md = mp.alloc();
|
||||
ASSERT_EQ(md, nullptr);
|
||||
}
|
||||
|
||||
// Use valgrind
|
||||
TEST_F(MempoolTests, data_can_be_written)
|
||||
{
|
||||
mempool mp(512, 100 * 512, 512);
|
||||
|
||||
for (unsigned i = 0; i < 100; i++) {
|
||||
auto md = mp.alloc();
|
||||
ASSERT_NE(md, nullptr);
|
||||
|
||||
memset(md, 0, 512);
|
||||
}
|
||||
|
||||
auto md = mp.alloc();
|
||||
ASSERT_EQ(md, nullptr);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
@ -3,6 +3,8 @@
|
||||
#include "persistent-data/space-maps/core.h"
|
||||
|
||||
using namespace persistent_data;
|
||||
using namespace std;
|
||||
using namespace test;
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
@ -21,3 +23,35 @@ test::open_temporary_tm(block_manager<>::ptr bm)
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
temp_file::temp_file(string const &name_base, unsigned meg_size)
|
||||
: path_(gen_path(name_base))
|
||||
{
|
||||
int fd = ::open(path_.c_str(), O_CREAT | O_RDWR, 0666);
|
||||
if (fd < 0)
|
||||
throw runtime_error("couldn't open file");
|
||||
|
||||
if (::fallocate(fd, 0, 0, 1024 * 1024 * meg_size))
|
||||
throw runtime_error("couldn't fallocate");
|
||||
|
||||
::close(fd);
|
||||
}
|
||||
|
||||
temp_file::~temp_file()
|
||||
{
|
||||
// ::unlink(path_.c_str());
|
||||
}
|
||||
|
||||
string const &
|
||||
temp_file::get_path() const
|
||||
{
|
||||
return path_;
|
||||
}
|
||||
|
||||
string
|
||||
temp_file::gen_path(string const &base)
|
||||
{
|
||||
return string("./") + base + string(".tmp");
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
@ -114,6 +114,18 @@ namespace test {
|
||||
std::unique_ptr<with_directory> dir_;
|
||||
};
|
||||
|
||||
class temp_file {
|
||||
public:
|
||||
temp_file(std::string const &name_base, unsigned meg_size);
|
||||
~temp_file();
|
||||
std::string const &get_path() const;
|
||||
|
||||
private:
|
||||
static string gen_path(string const &base);
|
||||
|
||||
string path_;
|
||||
};
|
||||
|
||||
//--------------------------------
|
||||
|
||||
template <typename T>
|
||||
|
Loading…
Reference in New Issue
Block a user