293 lines
8.3 KiB
C++
293 lines
8.3 KiB
C++
|
/*
|
||
|
* Copyright (c) 2013 by Felix Hupfeld.
|
||
|
*
|
||
|
* Licensed under the BSD License, see LICENSE file for details.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include <gtest/gtest.h>
|
||
|
|
||
|
#include <stdint.h>
|
||
|
|
||
|
#include <boost/lexical_cast.hpp>
|
||
|
#include <boost/scoped_ptr.hpp>
|
||
|
#include <string>
|
||
|
|
||
|
#include "common/test_environment.h"
|
||
|
#include "common/test_rpc_server_dir.h"
|
||
|
#include "common/test_rpc_server_mrc.h"
|
||
|
#include "common/test_rpc_server_osd.h"
|
||
|
#include "libxtreemfs/client.h"
|
||
|
#include "libxtreemfs/file_handle.h"
|
||
|
#include "libxtreemfs/helper.h"
|
||
|
#include "libxtreemfs/object_cache.h"
|
||
|
#include "libxtreemfs/options.h"
|
||
|
#include "libxtreemfs/volume.h"
|
||
|
#include "libxtreemfs/xtreemfs_exception.h"
|
||
|
#include "rpc/client.h"
|
||
|
#include "rpc/sync_callback.h"
|
||
|
#include "util/logging.h"
|
||
|
#include "xtreemfs/OSDServiceConstants.h"
|
||
|
|
||
|
namespace xtreemfs {
|
||
|
|
||
|
const int kObjectSize = 10;
|
||
|
|
||
|
class FakeOsdFile {
|
||
|
public:
|
||
|
FakeOsdFile() : size_(0), reads_(0), writes_(0) {
|
||
|
data_.reset(new char[500]);
|
||
|
}
|
||
|
int Read(int object_no, char* buffer) {
|
||
|
reads_++;
|
||
|
const int offset = object_no * kObjectSize;
|
||
|
const int bytes_to_read = std::min(kObjectSize, size_ - offset);
|
||
|
if (bytes_to_read <= 0) {
|
||
|
return 0;
|
||
|
}
|
||
|
memcpy(buffer, &data_[offset], bytes_to_read);
|
||
|
return bytes_to_read;
|
||
|
}
|
||
|
void Write(int object_no, const char* data, int bytes_to_write) {
|
||
|
writes_++;
|
||
|
const int offset = object_no * kObjectSize;
|
||
|
memcpy(&data_[offset], data, bytes_to_write);
|
||
|
size_ = std::max(size_, offset + bytes_to_write);
|
||
|
}
|
||
|
void Truncate(int new_size) {
|
||
|
size_ = new_size;
|
||
|
}
|
||
|
boost::scoped_array<char> data_;
|
||
|
int size_;
|
||
|
int reads_;
|
||
|
int writes_;
|
||
|
};
|
||
|
|
||
|
class ObjectCacheTest : public ::testing::Test {
|
||
|
protected:
|
||
|
virtual void SetUp() {
|
||
|
util::initialize_logger(util::LEVEL_WARN);
|
||
|
cache_.reset(new ObjectCache(2, kObjectSize));
|
||
|
reader_ = boost::bind(&FakeOsdFile::Read, &osd_file_, _1, _2);
|
||
|
writer_ = boost::bind(&FakeOsdFile::Write, &osd_file_, _1, _2, _3);
|
||
|
}
|
||
|
|
||
|
virtual void TearDown() {
|
||
|
google::protobuf::ShutdownProtobufLibrary();
|
||
|
util::shutdown_logger();
|
||
|
}
|
||
|
|
||
|
FakeOsdFile osd_file_;
|
||
|
boost::scoped_ptr<ObjectCache> cache_;
|
||
|
ObjectReaderFunction reader_;
|
||
|
ObjectWriterFunction writer_;
|
||
|
};
|
||
|
|
||
|
TEST_F(ObjectCacheTest, BasicUse) {
|
||
|
cache_->Write(0, 0, "TestData", 9, reader_, writer_);
|
||
|
EXPECT_EQ(1, osd_file_.reads_);
|
||
|
EXPECT_EQ(0, osd_file_.writes_);
|
||
|
EXPECT_EQ(0, osd_file_.size_);
|
||
|
|
||
|
char buffer[10];
|
||
|
EXPECT_EQ(9, cache_->Read(0, 0, buffer, 10, reader_, writer_));
|
||
|
EXPECT_EQ(0, strcmp(buffer, "TestData"));
|
||
|
EXPECT_EQ(1, osd_file_.reads_);
|
||
|
EXPECT_EQ(0, osd_file_.writes_);
|
||
|
EXPECT_EQ(0, osd_file_.size_);
|
||
|
|
||
|
cache_->Flush(writer_);
|
||
|
EXPECT_EQ(1, osd_file_.reads_);
|
||
|
EXPECT_EQ(1, osd_file_.writes_);
|
||
|
EXPECT_EQ(9, osd_file_.size_);
|
||
|
|
||
|
cache_->Flush(writer_);
|
||
|
EXPECT_EQ(1, osd_file_.reads_);
|
||
|
EXPECT_EQ(1, osd_file_.writes_);
|
||
|
EXPECT_EQ(9, osd_file_.size_);
|
||
|
}
|
||
|
|
||
|
TEST_F(ObjectCacheTest, Truncate) {
|
||
|
cache_->Write(0, 0, "TestDataTe", 10, reader_, writer_);
|
||
|
cache_->Write(1, 0, "stData", 6, reader_, writer_);
|
||
|
EXPECT_EQ(2, osd_file_.reads_);
|
||
|
EXPECT_EQ(0, osd_file_.writes_);
|
||
|
EXPECT_EQ(0, osd_file_.size_);
|
||
|
|
||
|
// Shrink
|
||
|
cache_->Truncate(12);
|
||
|
osd_file_.Truncate(12);
|
||
|
EXPECT_EQ(2, osd_file_.reads_);
|
||
|
EXPECT_EQ(0, osd_file_.writes_);
|
||
|
EXPECT_EQ(12, osd_file_.size_);
|
||
|
|
||
|
char buffer[17];
|
||
|
EXPECT_EQ(2, cache_->Read(1, 0, buffer, 10, reader_, writer_));
|
||
|
EXPECT_EQ(0, strncmp(buffer, "st", 2));
|
||
|
EXPECT_EQ(2, osd_file_.reads_);
|
||
|
EXPECT_EQ(0, osd_file_.writes_);
|
||
|
EXPECT_EQ(12, osd_file_.size_);
|
||
|
|
||
|
cache_->Flush(writer_);
|
||
|
EXPECT_EQ(2, osd_file_.reads_);
|
||
|
EXPECT_EQ(2, osd_file_.writes_);
|
||
|
EXPECT_EQ(12, osd_file_.size_);
|
||
|
|
||
|
// Extend
|
||
|
cache_->Truncate(20);
|
||
|
osd_file_.Truncate(20);
|
||
|
EXPECT_EQ(2, osd_file_.reads_);
|
||
|
EXPECT_EQ(2, osd_file_.writes_);
|
||
|
EXPECT_EQ(20, osd_file_.size_);
|
||
|
|
||
|
char buffer2[10];
|
||
|
EXPECT_EQ(10, cache_->Read(1, 0, buffer2, 10, reader_, writer_));
|
||
|
EXPECT_EQ(0, strncmp(buffer2, "st\0\0\0\0\0\0", 10));
|
||
|
EXPECT_EQ(2, osd_file_.reads_);
|
||
|
EXPECT_EQ(2, osd_file_.writes_);
|
||
|
EXPECT_EQ(20, osd_file_.size_);
|
||
|
|
||
|
cache_->Flush(writer_);
|
||
|
EXPECT_EQ(2, osd_file_.reads_);
|
||
|
// We need not to flush out shrunk objects as the layer above us will
|
||
|
// take care of it by sending a truncate to the OSD.
|
||
|
EXPECT_EQ(2, osd_file_.writes_);
|
||
|
EXPECT_EQ(20, osd_file_.size_);
|
||
|
}
|
||
|
|
||
|
TEST_F(ObjectCacheTest, WriteBack) {
|
||
|
cache_->Write(0, 0, "TestData", 9, reader_, writer_);
|
||
|
EXPECT_EQ(1, osd_file_.reads_);
|
||
|
EXPECT_EQ(0, osd_file_.writes_);
|
||
|
EXPECT_EQ(0, osd_file_.size_);
|
||
|
|
||
|
cache_->Write(1, 0, "TestData", 9, reader_, writer_);
|
||
|
EXPECT_EQ(2, osd_file_.reads_);
|
||
|
EXPECT_EQ(0, osd_file_.writes_);
|
||
|
EXPECT_EQ(0, osd_file_.size_);
|
||
|
|
||
|
cache_->Write(2, 0, "TestData", 9, reader_, writer_);
|
||
|
EXPECT_EQ(3, osd_file_.reads_);
|
||
|
EXPECT_EQ(1, osd_file_.writes_);
|
||
|
EXPECT_EQ(9, osd_file_.size_);
|
||
|
|
||
|
cache_->Flush(writer_);
|
||
|
EXPECT_EQ(3, osd_file_.reads_);
|
||
|
EXPECT_EQ(3, osd_file_.writes_);
|
||
|
EXPECT_EQ(29, osd_file_.size_);
|
||
|
|
||
|
// Read back in
|
||
|
char buffer[10];
|
||
|
EXPECT_EQ(10, cache_->Read(0, 0, buffer, 10, reader_, writer_));
|
||
|
EXPECT_EQ(0, strncmp(buffer, "TestData\0\0", 10));
|
||
|
EXPECT_EQ(4, osd_file_.reads_);
|
||
|
EXPECT_EQ(3, osd_file_.writes_);
|
||
|
EXPECT_EQ(29, osd_file_.size_);
|
||
|
}
|
||
|
|
||
|
class ObjectCacheEndToEndTest : public ::testing::Test {
|
||
|
protected:
|
||
|
static const int kBlockSize = 1024 * 128;
|
||
|
|
||
|
virtual void SetUp() {
|
||
|
util::initialize_logger(util::LEVEL_WARN);
|
||
|
test_env.options.connect_timeout_s = 15;
|
||
|
test_env.options.request_timeout_s = 5;
|
||
|
test_env.options.retry_delay_s = 5;
|
||
|
test_env.options.object_cache_size = 2;
|
||
|
|
||
|
test_env.options.periodic_xcap_renewal_interval_s = 2;
|
||
|
ASSERT_TRUE(test_env.Start());
|
||
|
|
||
|
// Open a volume
|
||
|
volume = test_env.client->OpenVolume(
|
||
|
test_env.volume_name_,
|
||
|
NULL, // No SSL options.
|
||
|
test_env.options);
|
||
|
|
||
|
// Open a file.
|
||
|
file = volume->OpenFile(
|
||
|
test_env.user_credentials,
|
||
|
"/test_file",
|
||
|
static_cast<xtreemfs::pbrpc::SYSTEM_V_FCNTL>(
|
||
|
xtreemfs::pbrpc::SYSTEM_V_FCNTL_H_O_CREAT |
|
||
|
xtreemfs::pbrpc::SYSTEM_V_FCNTL_H_O_TRUNC |
|
||
|
xtreemfs::pbrpc::SYSTEM_V_FCNTL_H_O_RDWR));
|
||
|
}
|
||
|
|
||
|
virtual void TearDown() {
|
||
|
//volume->Close();
|
||
|
test_env.Stop();
|
||
|
}
|
||
|
|
||
|
void CheckData(char* data, int buffer_size) {
|
||
|
for (int i = 0; i < buffer_size; ++i) {
|
||
|
ASSERT_EQ('0' + (i % 3), data[i]);
|
||
|
}
|
||
|
}
|
||
|
void CheckDataIsNull(char* data, int buffer_size) {
|
||
|
for (int i = 0; i < buffer_size; ++i) {
|
||
|
ASSERT_EQ(0, data[i]);
|
||
|
}
|
||
|
}
|
||
|
void FillData(char* data, int buffer_size) {
|
||
|
for (int i = 0; i < buffer_size; ++i) {
|
||
|
data[i] = '0' + (i % 3);
|
||
|
}
|
||
|
}
|
||
|
TestEnvironment test_env;
|
||
|
Volume* volume;
|
||
|
FileHandle* file;
|
||
|
};
|
||
|
|
||
|
TEST_F(ObjectCacheEndToEndTest, Persistence) {
|
||
|
const size_t blocks = 5;
|
||
|
const size_t buffer_size = kBlockSize * blocks;
|
||
|
boost::scoped_array<char> write_buf(new char[buffer_size]);
|
||
|
FillData(write_buf.get(), buffer_size);
|
||
|
|
||
|
EXPECT_EQ(buffer_size, file->Write(write_buf.get(), buffer_size, 0));
|
||
|
ASSERT_NO_THROW(file->Close());
|
||
|
|
||
|
file = volume->OpenFile(
|
||
|
test_env.user_credentials,
|
||
|
"/test_file",
|
||
|
static_cast<xtreemfs::pbrpc::SYSTEM_V_FCNTL>(
|
||
|
xtreemfs::pbrpc::SYSTEM_V_FCNTL_H_O_CREAT |
|
||
|
xtreemfs::pbrpc::SYSTEM_V_FCNTL_H_O_RDONLY));
|
||
|
memset(write_buf.get(), 0, buffer_size);
|
||
|
EXPECT_EQ(buffer_size, file->Read(write_buf.get(), buffer_size, 0));
|
||
|
CheckData(write_buf.get(), buffer_size);
|
||
|
ASSERT_NO_THROW(file->Close());
|
||
|
}
|
||
|
|
||
|
TEST_F(ObjectCacheEndToEndTest, NormalWrite) {
|
||
|
const size_t blocks = 5;
|
||
|
const size_t buffer_size = kBlockSize * blocks;
|
||
|
boost::scoped_array<char> write_buf(new char[buffer_size]);
|
||
|
FillData(write_buf.get(), buffer_size);
|
||
|
|
||
|
EXPECT_EQ(0, file->Read(write_buf.get(), buffer_size, 0));
|
||
|
|
||
|
EXPECT_EQ(buffer_size, file->Write(write_buf.get(), buffer_size, 0));
|
||
|
|
||
|
EXPECT_EQ(buffer_size, file->Read(write_buf.get(), buffer_size + 5, 0));
|
||
|
CheckData(write_buf.get(), buffer_size);
|
||
|
|
||
|
ASSERT_NO_THROW(file->Flush());
|
||
|
|
||
|
ASSERT_NO_THROW(file->Truncate(test_env.user_credentials, 0));
|
||
|
EXPECT_EQ(0, file->Read(write_buf.get(), buffer_size, 0));
|
||
|
|
||
|
ASSERT_NO_THROW(file->Truncate(test_env.user_credentials, buffer_size));
|
||
|
EXPECT_EQ(buffer_size, file->Read(write_buf.get(), buffer_size, 0));
|
||
|
CheckDataIsNull(write_buf.get(), buffer_size);
|
||
|
|
||
|
file->Write(write_buf.get(), buffer_size, 0);
|
||
|
ASSERT_NO_THROW(file->Close());
|
||
|
|
||
|
EXPECT_EQ(5 + 5, test_env.osds[0]->GetReceivedWrites().size());
|
||
|
}
|
||
|
|
||
|
} // namespace xtreemfs
|