2013-04-25 20:57:07 +05:30
|
|
|
#include "gmock/gmock.h"
|
|
|
|
|
|
|
|
#include "test_utils.h"
|
|
|
|
|
|
|
|
#include "persistent-data/block.h"
|
2013-04-29 17:54:19 +05:30
|
|
|
#include "thin-provisioning/device_checker.h"
|
2013-04-25 20:57:07 +05:30
|
|
|
#include "thin-provisioning/restore_emitter.h"
|
|
|
|
#include "thin-provisioning/superblock_checker.h"
|
2013-04-29 20:07:53 +05:30
|
|
|
#include "thin-provisioning/superblock_validator.h"
|
2013-04-25 20:57:07 +05:30
|
|
|
|
2013-05-07 18:59:54 +05:30
|
|
|
#include <boost/noncopyable.hpp>
|
2013-05-02 16:52:54 +05:30
|
|
|
#include <stdlib.h>
|
2013-04-25 20:57:07 +05:30
|
|
|
#include <unistd.h>
|
2013-05-02 16:52:54 +05:30
|
|
|
#include <vector>
|
2013-04-25 20:57:07 +05:30
|
|
|
|
|
|
|
using namespace persistent_data;
|
|
|
|
using namespace std;
|
|
|
|
using namespace test;
|
|
|
|
using namespace testing;
|
|
|
|
using namespace thin_provisioning;
|
|
|
|
|
|
|
|
//----------------------------------------------------------------
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
block_address const BLOCK_SIZE = 4096;
|
2013-05-02 16:34:05 +05:30
|
|
|
block_address const NR_BLOCKS = 102400;
|
2013-04-25 20:57:07 +05:30
|
|
|
|
|
|
|
// FIXME: move to utils
|
|
|
|
class with_directory {
|
|
|
|
public:
|
|
|
|
with_directory(std::string const &path)
|
|
|
|
: old_path_(pwd()) {
|
|
|
|
chdir(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
~with_directory() {
|
|
|
|
chdir(old_path_);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
std::string pwd() const {
|
|
|
|
char buffer[PATH_MAX];
|
|
|
|
char *ptr = getcwd(buffer, sizeof(buffer));
|
|
|
|
if (!ptr) {
|
|
|
|
// FIXME: still need a standard syscall failed exception
|
|
|
|
throw std::runtime_error("getcwd failed");
|
|
|
|
}
|
|
|
|
|
|
|
|
return ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void chdir(std::string const &path) {
|
|
|
|
int r = ::chdir(path.c_str());
|
|
|
|
if (r < 0)
|
|
|
|
throw std::runtime_error("chdir failed");
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string old_path_;
|
|
|
|
std::string new_path_;
|
|
|
|
};
|
|
|
|
|
|
|
|
class with_temp_directory {
|
|
|
|
public:
|
|
|
|
with_temp_directory() {
|
|
|
|
std::string name("./tmp");
|
|
|
|
|
|
|
|
rm_rf(name);
|
|
|
|
mkdir(name);
|
|
|
|
|
|
|
|
dir_.reset(new with_directory(name));
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
void rm_rf(std::string const &name) {
|
|
|
|
std::string cmd("rm -rf ");
|
|
|
|
cmd += name;
|
|
|
|
system(cmd);
|
|
|
|
}
|
|
|
|
|
|
|
|
void mkdir(std::string const &name) {
|
|
|
|
std::string cmd("mkdir ");
|
|
|
|
cmd += name;
|
|
|
|
system(cmd);
|
|
|
|
}
|
|
|
|
|
|
|
|
void system(std::string const &cmd) {
|
|
|
|
int r = ::system(cmd.c_str());
|
|
|
|
if (r < 0)
|
|
|
|
throw std::runtime_error("system failed");
|
|
|
|
}
|
|
|
|
|
|
|
|
std::auto_ptr<with_directory> dir_;
|
|
|
|
};
|
|
|
|
|
|
|
|
//--------------------------------
|
|
|
|
|
|
|
|
class metadata_builder {
|
|
|
|
public:
|
|
|
|
metadata_builder(block_manager<>::ptr bm)
|
|
|
|
: bm_(bm) {
|
|
|
|
}
|
|
|
|
|
|
|
|
void build() {
|
|
|
|
metadata::ptr md(new metadata(bm_, metadata::CREATE, 128, 10240));
|
|
|
|
emitter::ptr restorer = create_restore_emitter(md);
|
|
|
|
|
2013-05-01 21:03:42 +05:30
|
|
|
restorer->begin_superblock("test-generated", 0, 0, 128, 10240,
|
|
|
|
boost::optional<uint64_t>());
|
2013-04-29 21:05:03 +05:30
|
|
|
|
|
|
|
list<uint32_t>::const_iterator it, end = devices_.end();
|
|
|
|
for (it = devices_.begin(); it != end; ++it) {
|
|
|
|
restorer->begin_device(*it, 0, 0, 0, 0);
|
|
|
|
restorer->end_device();
|
|
|
|
}
|
|
|
|
|
2013-04-25 20:57:07 +05:30
|
|
|
restorer->end_superblock();
|
|
|
|
}
|
|
|
|
|
2013-04-29 21:05:03 +05:30
|
|
|
void add_device(uint32_t dev) {
|
|
|
|
devices_.push_back(dev);
|
|
|
|
}
|
2013-04-25 20:57:07 +05:30
|
|
|
|
|
|
|
private:
|
|
|
|
block_manager<>::ptr bm_;
|
2013-04-29 21:05:03 +05:30
|
|
|
|
|
|
|
list<uint32_t> devices_;
|
2013-04-25 20:57:07 +05:30
|
|
|
};
|
|
|
|
|
2013-05-01 21:03:42 +05:30
|
|
|
class devices_visitor : public detail_tree::visitor {
|
|
|
|
public:
|
|
|
|
struct node_info {
|
2013-05-07 18:59:54 +05:30
|
|
|
typedef boost::shared_ptr<node_info> ptr;
|
|
|
|
|
2013-05-01 21:03:42 +05:30
|
|
|
bool leaf;
|
2013-05-02 16:34:05 +05:30
|
|
|
unsigned depth;
|
2013-05-01 21:03:42 +05:30
|
|
|
unsigned level;
|
|
|
|
block_address b;
|
|
|
|
range<uint64_t> keys;
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef btree_detail::node_location node_location;
|
|
|
|
typedef boost::shared_ptr<devices_visitor> ptr;
|
|
|
|
|
|
|
|
virtual bool visit_internal(node_location const &loc,
|
|
|
|
detail_tree::internal_node const &n) {
|
2013-05-02 16:34:05 +05:30
|
|
|
record_node(false, loc, n);
|
2013-05-01 21:03:42 +05:30
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual bool visit_internal_leaf(node_location const &loc,
|
|
|
|
detail_tree::internal_node const &n) {
|
2013-05-02 16:34:05 +05:30
|
|
|
record_node(true, loc, n);
|
2013-05-01 21:03:42 +05:30
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
virtual bool visit_leaf(node_location const &loc,
|
|
|
|
detail_tree::leaf_node const &n) {
|
2013-05-02 16:34:05 +05:30
|
|
|
record_node(true, loc, n);
|
2013-05-01 21:03:42 +05:30
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void visit_complete() {
|
|
|
|
}
|
|
|
|
|
2013-05-07 18:59:54 +05:30
|
|
|
vector<node_info::ptr> const &get_nodes() const {
|
2013-05-01 21:03:42 +05:30
|
|
|
return nodes_;
|
|
|
|
}
|
|
|
|
|
2013-05-02 16:52:54 +05:30
|
|
|
node_info const &random_node() const {
|
|
|
|
if (nodes_.empty())
|
|
|
|
throw runtime_error("no nodes in btree");
|
|
|
|
|
2013-05-07 18:59:54 +05:30
|
|
|
return *nodes_[::random() % nodes_.size()];
|
2013-05-02 16:52:54 +05:30
|
|
|
}
|
|
|
|
|
2013-05-01 21:03:42 +05:30
|
|
|
private:
|
2013-05-02 16:34:05 +05:30
|
|
|
// We rely on the visit order being depth first, lowest to highest.
|
2013-05-01 21:03:42 +05:30
|
|
|
template <typename N>
|
2013-05-02 16:34:05 +05:30
|
|
|
void record_node(bool leaf, node_location const &loc, N const &n) {
|
2013-05-07 18:59:54 +05:30
|
|
|
node_info::ptr ni(new node_info);
|
2013-05-01 21:03:42 +05:30
|
|
|
|
2013-05-07 18:59:54 +05:30
|
|
|
ni->leaf = leaf;
|
|
|
|
ni->depth = loc.depth;
|
|
|
|
ni->level = loc.level;
|
|
|
|
ni->b = n.get_location();
|
2013-05-01 21:03:42 +05:30
|
|
|
|
|
|
|
if (n.get_nr_entries())
|
2013-05-07 18:59:54 +05:30
|
|
|
ni->keys = range<uint64_t>(n.key_at(0));
|
2013-05-01 21:03:42 +05:30
|
|
|
else {
|
2013-05-02 16:34:05 +05:30
|
|
|
if (loc.key)
|
2013-05-07 18:59:54 +05:30
|
|
|
ni->keys = range<uint64_t>(*loc.key);
|
2013-05-01 21:03:42 +05:30
|
|
|
else
|
2013-05-07 18:59:54 +05:30
|
|
|
ni->keys = range<uint64_t>();
|
2013-05-01 21:03:42 +05:30
|
|
|
}
|
|
|
|
|
2013-05-02 16:34:05 +05:30
|
|
|
if (last_node_at_depth_.size() > loc.depth) {
|
2013-05-07 18:59:54 +05:30
|
|
|
node_info::ptr &last = last_node_at_depth_[loc.depth];
|
|
|
|
|
|
|
|
last->keys.end_ = ni->keys.begin_;
|
|
|
|
last_node_at_depth_[loc.depth] = ni;
|
2013-05-02 16:34:05 +05:30
|
|
|
} else
|
2013-05-07 18:59:54 +05:30
|
|
|
last_node_at_depth_.push_back(ni);
|
|
|
|
|
|
|
|
nodes_.push_back(ni);
|
2013-05-01 21:03:42 +05:30
|
|
|
}
|
|
|
|
|
2013-05-07 18:59:54 +05:30
|
|
|
vector<node_info::ptr> nodes_;
|
|
|
|
vector<node_info::ptr> last_node_at_depth_;
|
2013-05-01 21:03:42 +05:30
|
|
|
};
|
|
|
|
|
2013-04-29 17:54:19 +05:30
|
|
|
//--------------------------------
|
|
|
|
|
2013-04-29 20:43:18 +05:30
|
|
|
class damage_visitor_mock : public metadata_damage_visitor {
|
|
|
|
public:
|
|
|
|
MOCK_METHOD1(visit, void(super_block_corruption const &));
|
|
|
|
MOCK_METHOD1(visit, void(missing_device_details const &));
|
|
|
|
MOCK_METHOD1(visit, void(missing_devices const &));
|
|
|
|
MOCK_METHOD1(visit, void(missing_mappings const &));
|
|
|
|
MOCK_METHOD1(visit, void(bad_metadata_ref_count const &));
|
|
|
|
MOCK_METHOD1(visit, void(bad_data_ref_count const &));
|
|
|
|
MOCK_METHOD1(visit, void(missing_metadata_ref_counts const &));
|
|
|
|
MOCK_METHOD1(visit, void(missing_data_ref_counts const &));
|
|
|
|
};
|
|
|
|
|
|
|
|
//--------------------------------
|
|
|
|
|
2013-04-29 17:54:19 +05:30
|
|
|
class MetadataCheckerTests : public Test {
|
2013-04-25 20:57:07 +05:30
|
|
|
public:
|
2013-04-29 17:54:19 +05:30
|
|
|
MetadataCheckerTests()
|
2013-05-01 21:03:42 +05:30
|
|
|
: bm_(create_bm<BLOCK_SIZE>(NR_BLOCKS)),
|
2013-04-29 21:05:03 +05:30
|
|
|
builder_(bm_) {
|
|
|
|
}
|
2013-04-25 20:57:07 +05:30
|
|
|
|
2013-04-29 21:05:03 +05:30
|
|
|
metadata_builder &get_builder() {
|
|
|
|
return builder_;
|
2013-04-25 20:57:07 +05:30
|
|
|
}
|
|
|
|
|
2013-04-29 20:07:53 +05:30
|
|
|
superblock read_superblock() {
|
|
|
|
superblock sb;
|
|
|
|
|
|
|
|
block_manager<>::read_ref r = bm_->read_lock(SUPERBLOCK_LOCATION, superblock_validator());
|
|
|
|
superblock_disk const *sbd = reinterpret_cast<superblock_disk const *>(&r.data());
|
|
|
|
superblock_traits::unpack(*sbd, sb);
|
|
|
|
return sb;
|
|
|
|
}
|
|
|
|
|
|
|
|
void zero_block(block_address b) {
|
|
|
|
::test::zero_block(bm_, b);
|
|
|
|
}
|
|
|
|
|
2013-04-29 17:54:19 +05:30
|
|
|
with_temp_directory dir_;
|
|
|
|
block_manager<>::ptr bm_;
|
2013-04-29 21:05:03 +05:30
|
|
|
metadata_builder builder_;
|
2013-04-29 17:54:19 +05:30
|
|
|
};
|
2013-04-29 20:43:18 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------
|
2013-04-29 17:54:19 +05:30
|
|
|
|
2013-04-29 20:43:18 +05:30
|
|
|
namespace {
|
2013-04-29 17:54:19 +05:30
|
|
|
class SuperBlockCheckerTests : public MetadataCheckerTests {
|
|
|
|
public:
|
2013-04-29 21:05:03 +05:30
|
|
|
SuperBlockCheckerTests() {
|
|
|
|
get_builder().build();
|
|
|
|
}
|
|
|
|
|
2013-04-25 20:57:07 +05:30
|
|
|
void corrupt_superblock() {
|
2013-04-29 20:07:53 +05:30
|
|
|
zero_block(SUPERBLOCK_LOCATION);
|
2013-04-25 20:57:07 +05:30
|
|
|
}
|
2013-04-29 17:54:19 +05:30
|
|
|
};
|
2013-04-25 20:57:07 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(SuperBlockCheckerTests, creation_requires_a_block_manager)
|
|
|
|
{
|
|
|
|
superblock_checker sc(bm_);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(SuperBlockCheckerTests, passes_with_good_superblock)
|
|
|
|
{
|
|
|
|
superblock_checker sc(bm_);
|
|
|
|
damage_list_ptr damage = sc.check();
|
|
|
|
ASSERT_THAT(damage->size(), Eq(0U));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(SuperBlockCheckerTests, fails_with_bad_checksum)
|
|
|
|
{
|
|
|
|
corrupt_superblock();
|
|
|
|
|
|
|
|
superblock_checker sc(bm_);
|
|
|
|
damage_list_ptr damage = sc.check();
|
|
|
|
ASSERT_THAT(damage->size(), Eq(1u));
|
|
|
|
|
|
|
|
metadata_damage::ptr d = *damage->begin();
|
|
|
|
ASSERT_THAT(dynamic_cast<super_block_corruption *>(d.get()), NotNull());
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------
|
2013-04-29 17:54:19 +05:30
|
|
|
|
2013-04-29 20:07:53 +05:30
|
|
|
namespace {
|
|
|
|
class DeviceCheckerTests : public MetadataCheckerTests {
|
|
|
|
public:
|
|
|
|
block_address devices_root() {
|
|
|
|
superblock sb = read_superblock();
|
|
|
|
return sb.device_details_root_;
|
|
|
|
}
|
|
|
|
|
2013-05-02 17:13:16 +05:30
|
|
|
device_checker &dev_checker() {
|
|
|
|
if (!dev_checker_.get())
|
|
|
|
dev_checker_.reset(new device_checker(bm_, devices_root()));
|
|
|
|
|
|
|
|
return *dev_checker_;
|
2013-04-29 20:07:53 +05:30
|
|
|
}
|
2013-05-02 17:13:16 +05:30
|
|
|
|
2013-05-02 17:21:55 +05:30
|
|
|
void damage_should_include(damage_list_ptr damage, missing_device_details const &md) {
|
|
|
|
ASSERT_THAT(damage->size(), Gt(0u));
|
|
|
|
|
|
|
|
damage_visitor_mock v;
|
|
|
|
EXPECT_CALL(v, visit(Matcher<missing_device_details const &>(Eq(md))));
|
|
|
|
|
|
|
|
(*damage->begin())->visit(v);
|
|
|
|
}
|
|
|
|
|
2013-05-02 17:13:16 +05:30
|
|
|
private:
|
|
|
|
auto_ptr<device_checker> dev_checker_;
|
2013-04-29 20:07:53 +05:30
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(DeviceCheckerTests, create_require_a_block_manager_and_a_root_block)
|
2013-04-29 17:54:19 +05:30
|
|
|
{
|
2013-04-29 21:05:03 +05:30
|
|
|
get_builder().build();
|
2013-05-02 17:13:16 +05:30
|
|
|
dev_checker();
|
2013-04-29 20:07:53 +05:30
|
|
|
}
|
|
|
|
|
2013-04-29 21:05:03 +05:30
|
|
|
TEST_F(DeviceCheckerTests, passes_with_valid_metadata_containing_zero_devices)
|
2013-04-29 20:07:53 +05:30
|
|
|
{
|
2013-04-29 21:05:03 +05:30
|
|
|
get_builder().build();
|
2013-05-02 17:13:16 +05:30
|
|
|
damage_list_ptr damage = dev_checker().check();
|
2013-04-29 21:05:03 +05:30
|
|
|
ASSERT_THAT(damage->size(), Eq(0u));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(DeviceCheckerTests, passes_with_valid_metadata_containing_some_devices)
|
|
|
|
{
|
|
|
|
metadata_builder &b = get_builder();
|
|
|
|
|
|
|
|
b.add_device(1);
|
|
|
|
b.add_device(5);
|
|
|
|
b.add_device(76);
|
|
|
|
|
|
|
|
b.build();
|
|
|
|
|
2013-05-02 17:13:16 +05:30
|
|
|
damage_list_ptr damage = dev_checker().check();
|
2013-04-29 20:07:53 +05:30
|
|
|
ASSERT_THAT(damage->size(), Eq(0u));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(DeviceCheckerTests, fails_with_corrupt_root)
|
|
|
|
{
|
2013-04-29 21:05:03 +05:30
|
|
|
get_builder().build();
|
2013-04-29 20:07:53 +05:30
|
|
|
zero_block(devices_root());
|
|
|
|
|
2013-05-02 17:13:16 +05:30
|
|
|
damage_list_ptr damage = dev_checker().check();
|
2013-05-02 17:21:55 +05:30
|
|
|
damage_should_include(damage, missing_device_details(range64()));
|
2013-04-29 17:54:19 +05:30
|
|
|
}
|
|
|
|
|
2013-05-01 21:03:42 +05:30
|
|
|
TEST_F(DeviceCheckerTests, damaging_some_btree_nodes_results_in_the_correct_devices_being_flagged_as_missing)
|
|
|
|
{
|
|
|
|
metadata_builder &b = get_builder();
|
|
|
|
|
2013-05-02 17:21:55 +05:30
|
|
|
// FIXME: We should optimise the restorer so it clones the mapping
|
|
|
|
// tree for zero mapping devices, rather than allocating a new one.
|
|
|
|
// It would save allocating a heap of blocks, and more importantly
|
|
|
|
// make these tests run much faster.
|
2013-05-07 18:59:54 +05:30
|
|
|
for (unsigned i = 0; i < 20000; i++)
|
2013-05-01 21:03:42 +05:30
|
|
|
b.add_device(i);
|
|
|
|
|
|
|
|
b.build();
|
|
|
|
|
2013-05-02 17:01:33 +05:30
|
|
|
devices_visitor scanner;
|
2013-05-01 21:03:42 +05:30
|
|
|
transaction_manager::ptr tm = open_temporary_tm(bm_);
|
|
|
|
detail_tree::ptr devices(new detail_tree(tm, devices_root(),
|
|
|
|
device_details_traits::ref_counter()));
|
|
|
|
devices->visit_depth_first(scanner);
|
2013-05-07 18:59:54 +05:30
|
|
|
|
2013-05-02 17:01:33 +05:30
|
|
|
devices_visitor::node_info n = scanner.random_node();
|
2013-05-02 16:52:54 +05:30
|
|
|
zero_block(n.b);
|
2013-05-01 21:03:42 +05:30
|
|
|
|
2013-05-02 17:13:16 +05:30
|
|
|
damage_list_ptr damage = dev_checker().check();
|
2013-05-02 17:21:55 +05:30
|
|
|
damage_should_include(damage, missing_device_details(range64(n.keys)));
|
2013-05-01 21:03:42 +05:30
|
|
|
}
|
|
|
|
|
2013-04-29 17:54:19 +05:30
|
|
|
//----------------------------------------------------------------
|