forked from mooncake-track/Mooncake
2157 lines
86 KiB
C++
2157 lines
86 KiB
C++
#include "master_service.h"
|
|
|
|
#include <glog/logging.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <atomic>
|
|
#include <memory>
|
|
#include <random>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#include "types.h"
|
|
|
|
namespace mooncake::test {
|
|
|
|
class MasterServiceTest : public ::testing::Test {
|
|
protected:
|
|
void SetUp() override {
|
|
google::InitGoogleLogging("MasterServiceTest");
|
|
FLAGS_logtostderr = true;
|
|
}
|
|
|
|
std::vector<Replica::Descriptor> replica_list;
|
|
|
|
void TearDown() override { google::ShutdownGoogleLogging(); }
|
|
};
|
|
|
|
std::string GenerateKeyForSegment(const std::unique_ptr<MasterService>& service,
|
|
const std::string& segment_name) {
|
|
static std::atomic<uint64_t> counter(0);
|
|
|
|
while (true) {
|
|
std::string key = "key_" + std::to_string(counter.fetch_add(1));
|
|
std::vector<Replica::Descriptor> replica_list;
|
|
|
|
// Check if the key already exists.
|
|
auto exist_result = service->ExistKey(key);
|
|
if (exist_result.has_value() && exist_result.value()) {
|
|
continue; // Retry if the key already exists
|
|
}
|
|
|
|
// Attempt to put the key.
|
|
auto put_result = service->PutStart(key, {1024}, {.replica_num = 1});
|
|
if (put_result.has_value()) {
|
|
replica_list = std::move(put_result.value());
|
|
}
|
|
ErrorCode code =
|
|
put_result.has_value() ? ErrorCode::OK : put_result.error();
|
|
|
|
if (code == ErrorCode::OBJECT_ALREADY_EXISTS) {
|
|
continue; // Retry if the key already exists
|
|
}
|
|
if (code != ErrorCode::OK) {
|
|
throw std::runtime_error("PutStart failed with code: " +
|
|
std::to_string(static_cast<int>(code)));
|
|
}
|
|
auto put_end_result = service->PutEnd(key, ReplicaType::MEMORY);
|
|
if (!put_end_result.has_value()) {
|
|
throw std::runtime_error("PutEnd failed");
|
|
}
|
|
if (replica_list[0]
|
|
.get_memory_descriptor()
|
|
.buffer_descriptors[0]
|
|
.segment_name_ == segment_name) {
|
|
return key;
|
|
}
|
|
// Clean up failed attempt
|
|
auto remove_result = service->Remove(key);
|
|
if (!remove_result.has_value()) {
|
|
// Ignore cleanup failure
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, MountUnmountSegmentWithCachelibAllocator) {
|
|
// Create a MasterService instance for testing.
|
|
auto service_config =
|
|
MasterServiceConfig::builder()
|
|
.set_memory_allocator(BufferAllocatorType::CACHELIB)
|
|
.build();
|
|
std::unique_ptr<MasterService> service_(new MasterService(service_config));
|
|
// Define a constant buffer address for the segment.
|
|
constexpr size_t kBufferAddress = 0x300000000;
|
|
// Define the size of the segment (16MB).
|
|
constexpr size_t kSegmentSize = 1024 * 1024 * 16;
|
|
// Define the name of the test segment.
|
|
std::string segment_name = "test_segment";
|
|
Segment segment(generate_uuid(), segment_name, kBufferAddress,
|
|
kSegmentSize);
|
|
UUID client_id = generate_uuid();
|
|
|
|
// Test invalid parameters.
|
|
// Invalid buffer address (0).
|
|
segment.base = 0;
|
|
segment.size = kSegmentSize;
|
|
auto mount_result1 = service_->MountSegment(segment, client_id);
|
|
EXPECT_FALSE(mount_result1.has_value());
|
|
EXPECT_EQ(ErrorCode::INVALID_PARAMS, mount_result1.error());
|
|
|
|
// Invalid segment size (0).
|
|
segment.base = kBufferAddress;
|
|
segment.size = 0;
|
|
auto mount_result2 = service_->MountSegment(segment, client_id);
|
|
EXPECT_FALSE(mount_result2.has_value());
|
|
EXPECT_EQ(ErrorCode::INVALID_PARAMS, mount_result2.error());
|
|
|
|
// Base is not aligned
|
|
segment.base = kBufferAddress + 1;
|
|
segment.size = kSegmentSize;
|
|
auto mount_result3 = service_->MountSegment(segment, client_id);
|
|
EXPECT_FALSE(mount_result3.has_value());
|
|
EXPECT_EQ(ErrorCode::INVALID_PARAMS, mount_result3.error());
|
|
|
|
// Size is not aligned
|
|
segment.base = kBufferAddress;
|
|
segment.size = kSegmentSize + 1;
|
|
auto mount_result4 = service_->MountSegment(segment, client_id);
|
|
EXPECT_FALSE(mount_result4.has_value());
|
|
EXPECT_EQ(ErrorCode::INVALID_PARAMS, mount_result4.error());
|
|
|
|
// Test normal mount operation.
|
|
segment.base = kBufferAddress;
|
|
segment.size = kSegmentSize;
|
|
auto mount_result5 = service_->MountSegment(segment, client_id);
|
|
EXPECT_TRUE(mount_result5.has_value());
|
|
|
|
// Test mounting the same segment again (idempotent request should succeed).
|
|
auto mount_result6 = service_->MountSegment(segment, client_id);
|
|
EXPECT_TRUE(mount_result6.has_value());
|
|
|
|
// Test unmounting the segment.
|
|
auto unmount_result1 = service_->UnmountSegment(segment.id, client_id);
|
|
EXPECT_TRUE(unmount_result1.has_value());
|
|
|
|
// Test unmounting the same segment again (idempotent request should
|
|
// succeed).
|
|
auto unmount_result2 = service_->UnmountSegment(segment.id, client_id);
|
|
EXPECT_TRUE(unmount_result2.has_value());
|
|
|
|
// Test unmounting a non-existent segment (idempotent request should
|
|
// succeed).
|
|
UUID non_existent_id = generate_uuid();
|
|
auto unmount_result3 = service_->UnmountSegment(non_existent_id, client_id);
|
|
EXPECT_TRUE(unmount_result3.has_value());
|
|
|
|
// Test remounting after unmount.
|
|
auto mount_result7 = service_->MountSegment(segment, client_id);
|
|
EXPECT_TRUE(mount_result7.has_value());
|
|
auto unmount_result4 = service_->UnmountSegment(segment.id, client_id);
|
|
EXPECT_TRUE(unmount_result4.has_value());
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, MountUnmountSegmentWithOffsetAllocator) {
|
|
// Create a MasterService instance for testing.
|
|
auto service_config = MasterServiceConfig::builder()
|
|
.set_memory_allocator(BufferAllocatorType::OFFSET)
|
|
.build();
|
|
std::unique_ptr<MasterService> service_(new MasterService(service_config));
|
|
// Define a constant buffer address for the segment.
|
|
constexpr size_t kBufferAddress = 0x300000000;
|
|
// Define the size of the segment (16MB).
|
|
constexpr size_t kSegmentSize = 1024 * 1024 * 16;
|
|
// Define the name of the test segment.
|
|
std::string segment_name = "test_segment";
|
|
Segment segment(generate_uuid(), segment_name, kBufferAddress,
|
|
kSegmentSize);
|
|
UUID client_id = generate_uuid();
|
|
|
|
// Test invalid parameters.
|
|
// Invalid buffer address (0).
|
|
segment.base = 0;
|
|
segment.size = kSegmentSize;
|
|
auto mount_result1 = service_->MountSegment(segment, client_id);
|
|
EXPECT_FALSE(mount_result1.has_value());
|
|
EXPECT_EQ(ErrorCode::INVALID_PARAMS, mount_result1.error());
|
|
|
|
// Invalid segment size (0).
|
|
segment.base = kBufferAddress;
|
|
segment.size = 0;
|
|
auto mount_result2 = service_->MountSegment(segment, client_id);
|
|
EXPECT_FALSE(mount_result2.has_value());
|
|
EXPECT_EQ(ErrorCode::INVALID_PARAMS, mount_result2.error());
|
|
|
|
// Test normal mount operation.
|
|
segment.base = kBufferAddress;
|
|
segment.size = kSegmentSize;
|
|
auto mount_result5 = service_->MountSegment(segment, client_id);
|
|
EXPECT_TRUE(mount_result5.has_value());
|
|
|
|
// Test mounting the same segment again (idempotent request should succeed).
|
|
auto mount_result6 = service_->MountSegment(segment, client_id);
|
|
EXPECT_TRUE(mount_result6.has_value());
|
|
|
|
// Test unmounting the segment.
|
|
auto unmount_result1 = service_->UnmountSegment(segment.id, client_id);
|
|
EXPECT_TRUE(unmount_result1.has_value());
|
|
|
|
// Test unmounting the same segment again (idempotent request should
|
|
// succeed).
|
|
auto unmount_result2 = service_->UnmountSegment(segment.id, client_id);
|
|
EXPECT_TRUE(unmount_result2.has_value());
|
|
|
|
// Test unmounting a non-existent segment (idempotent request should
|
|
// succeed).
|
|
UUID non_existent_id = generate_uuid();
|
|
auto unmount_result3 = service_->UnmountSegment(non_existent_id, client_id);
|
|
EXPECT_TRUE(unmount_result3.has_value());
|
|
|
|
// Test remounting after unmount.
|
|
auto mount_result7 = service_->MountSegment(segment, client_id);
|
|
EXPECT_TRUE(mount_result7.has_value());
|
|
auto unmount_result4 = service_->UnmountSegment(segment.id, client_id);
|
|
EXPECT_TRUE(unmount_result4.has_value());
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, RandomMountUnmountSegment) {
|
|
// Create a MasterService instance for testing.
|
|
std::unique_ptr<MasterService> service_(new MasterService());
|
|
// Define a constant buffer address for the segment.
|
|
constexpr size_t kBufferAddress = 0x300000000;
|
|
// Define the name of the test segment.
|
|
std::string segment_name = "test_random_segment";
|
|
UUID segment_id = generate_uuid();
|
|
UUID client_id = generate_uuid();
|
|
size_t times = 10;
|
|
std::random_device rd;
|
|
std::mt19937 gen(rd());
|
|
std::uniform_int_distribution<> dis(1, 10);
|
|
while (times--) {
|
|
int random_number = dis(gen);
|
|
// Define the size of the segment (16MB).
|
|
size_t kSegmentSize = 1024 * 1024 * 16 * random_number;
|
|
|
|
Segment segment(segment_id, segment_name, kBufferAddress, kSegmentSize);
|
|
|
|
// Test remounting after unmount.
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
EXPECT_TRUE(mount_result.has_value());
|
|
auto unmount_result = service_->UnmountSegment(segment.id, client_id);
|
|
EXPECT_TRUE(unmount_result.has_value());
|
|
}
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, ConcurrentMountUnmount) {
|
|
std::unique_ptr<MasterService> service_(new MasterService());
|
|
constexpr size_t num_threads = 4;
|
|
constexpr size_t iterations = 100;
|
|
std::vector<std::thread> threads;
|
|
std::atomic<int> success_count{0};
|
|
|
|
// Launch multiple threads to mount/unmount segments concurrently
|
|
for (size_t i = 0; i < num_threads; i++) {
|
|
threads.emplace_back([&service_, i, &success_count]() {
|
|
Segment segment;
|
|
segment.name = "segment_" + std::to_string(i);
|
|
segment.id = generate_uuid();
|
|
segment.base = 0x300000000 + i * 0x10000000;
|
|
segment.size = 16 * 1024 * 1024;
|
|
UUID client_id = generate_uuid();
|
|
|
|
for (size_t j = 0; j < iterations; j++) {
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
if (mount_result.has_value()) {
|
|
auto unmount_result =
|
|
service_->UnmountSegment(segment.id, client_id);
|
|
EXPECT_TRUE(unmount_result.has_value());
|
|
success_count++;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Wait for all threads to complete
|
|
for (auto& thread : threads) {
|
|
thread.join();
|
|
}
|
|
|
|
// Verify that some mount/unmount operations succeeded
|
|
EXPECT_GT(success_count, 0);
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, PutStartInvalidParams) {
|
|
std::unique_ptr<MasterService> service_(new MasterService());
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size = 1024 * 1024 * 16;
|
|
std::string segment_name = "test_segment";
|
|
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
|
|
std::string key = "test_key";
|
|
ReplicateConfig config;
|
|
|
|
// Test invalid replica_num
|
|
config.replica_num = 0;
|
|
auto put_result1 = service_->PutStart(key, {1024}, config);
|
|
EXPECT_FALSE(put_result1.has_value());
|
|
EXPECT_EQ(ErrorCode::INVALID_PARAMS, put_result1.error());
|
|
|
|
// Test empty slice_lengths
|
|
config.replica_num = 1;
|
|
std::vector<uint64_t> empty_slices;
|
|
auto put_result2 = service_->PutStart(key, empty_slices, config);
|
|
EXPECT_FALSE(put_result2.has_value());
|
|
EXPECT_EQ(ErrorCode::INVALID_PARAMS, put_result2.error());
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, PutStartEndFlow) {
|
|
std::unique_ptr<MasterService> service_(new MasterService());
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size = 1024 * 1024 * 16;
|
|
std::string segment_name = "test_segment";
|
|
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
|
|
// Test PutStart
|
|
std::string key = "test_key";
|
|
uint64_t value_length = 1024;
|
|
std::vector<uint64_t> slice_lengths = {value_length};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
|
|
auto put_start_result = service_->PutStart(key, slice_lengths, config);
|
|
EXPECT_TRUE(put_start_result.has_value());
|
|
replica_list = put_start_result.value();
|
|
EXPECT_FALSE(replica_list.empty());
|
|
EXPECT_EQ(ReplicaStatus::PROCESSING, replica_list[0].status);
|
|
|
|
// During put, Get/Remove should fail
|
|
auto get_replica_result = service_->GetReplicaList(key);
|
|
EXPECT_FALSE(get_replica_result.has_value());
|
|
EXPECT_EQ(ErrorCode::REPLICA_IS_NOT_READY, get_replica_result.error());
|
|
auto remove_result = service_->Remove(key);
|
|
EXPECT_FALSE(remove_result.has_value());
|
|
EXPECT_EQ(ErrorCode::REPLICA_IS_NOT_READY, remove_result.error());
|
|
|
|
// Test PutEnd
|
|
auto put_end_result = service_->PutEnd(key, ReplicaType::MEMORY);
|
|
EXPECT_TRUE(put_end_result.has_value());
|
|
|
|
// Verify replica list after PutEnd
|
|
auto final_get_result = service_->GetReplicaList(key);
|
|
EXPECT_TRUE(final_get_result.has_value());
|
|
replica_list = final_get_result.value();
|
|
EXPECT_EQ(1, replica_list.size());
|
|
EXPECT_EQ(ReplicaStatus::COMPLETE, replica_list[0].status);
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, RandomPutStartEndFlow) {
|
|
std::unique_ptr<MasterService> service_(new MasterService());
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size = 1024 * 1024 * 16;
|
|
std::string segment_name = "test_segment";
|
|
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
|
|
// Test PutStart
|
|
std::string key = "test_key";
|
|
uint64_t value_length = 1024;
|
|
std::vector<uint64_t> slice_lengths = {value_length};
|
|
ReplicateConfig config;
|
|
std::random_device rd;
|
|
std::mt19937 gen(rd());
|
|
std::uniform_int_distribution<> dis(1, 5);
|
|
int random_number = dis(gen);
|
|
config.replica_num = random_number;
|
|
auto put_start_result = service_->PutStart(key, slice_lengths, config);
|
|
EXPECT_TRUE(put_start_result.has_value());
|
|
replica_list = put_start_result.value();
|
|
EXPECT_FALSE(replica_list.empty());
|
|
EXPECT_EQ(ReplicaStatus::PROCESSING, replica_list[0].status);
|
|
// During put, Get/Remove should fail
|
|
auto get_result = service_->GetReplicaList(key);
|
|
EXPECT_FALSE(get_result.has_value());
|
|
EXPECT_EQ(ErrorCode::REPLICA_IS_NOT_READY, get_result.error());
|
|
auto remove_result = service_->Remove(key);
|
|
EXPECT_FALSE(remove_result.has_value());
|
|
EXPECT_EQ(ErrorCode::REPLICA_IS_NOT_READY, remove_result.error());
|
|
// Test PutEnd
|
|
auto put_end_result = service_->PutEnd(key, ReplicaType::MEMORY);
|
|
EXPECT_TRUE(put_end_result.has_value());
|
|
// Verify replica list after PutEnd
|
|
auto get_result2 = service_->GetReplicaList(key);
|
|
EXPECT_TRUE(get_result2.has_value());
|
|
replica_list = get_result2.value();
|
|
EXPECT_EQ(random_number, replica_list.size());
|
|
for (int i = 0; i < random_number; ++i) {
|
|
EXPECT_EQ(ReplicaStatus::COMPLETE, replica_list[i].status);
|
|
}
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, GetReplicaListByRegex) {
|
|
const uint64_t kv_lease_ttl = 50;
|
|
auto service_config = MasterServiceConfig::builder()
|
|
.set_default_kv_lease_ttl(kv_lease_ttl)
|
|
.build();
|
|
std::unique_ptr<MasterService> service_(new MasterService(service_config));
|
|
// Test getting non-existent key
|
|
auto get_result = service_->GetReplicaList(".*non_existent.*");
|
|
EXPECT_FALSE(get_result.has_value());
|
|
EXPECT_EQ(ErrorCode::OBJECT_NOT_FOUND, get_result.error());
|
|
|
|
// Mount segment and put an object
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size = 1024 * 1024 * 16;
|
|
std::string segment_name = "test_segment";
|
|
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
|
|
int times = 10;
|
|
while (times--) {
|
|
std::string key = "test_key" + std::to_string(times);
|
|
std::vector<uint64_t> slice_lengths = {1024};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
auto put_start_result = service_->PutStart(key, slice_lengths, config);
|
|
ASSERT_TRUE(put_start_result.has_value());
|
|
auto put_end_result = service_->PutEnd(key, ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result.has_value());
|
|
auto exist_result = service_->ExistKey(key);
|
|
ASSERT_TRUE(exist_result.has_value());
|
|
}
|
|
// wait for all the lease to expire
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(kv_lease_ttl));
|
|
|
|
// Test getting existing key
|
|
auto get_result2 = service_->GetReplicaListByRegex("^test_key");
|
|
EXPECT_TRUE(get_result2.has_value());
|
|
auto replica_list_local = get_result2.value();
|
|
EXPECT_EQ(10, replica_list_local.size());
|
|
}
|
|
|
|
// Helper function to put an object, making the test cleaner
|
|
void put_object(MasterService& service, const std::string& key) {
|
|
std::vector<uint64_t> slice_lengths = {1024};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
auto put_start_result = service.PutStart(key, slice_lengths, config);
|
|
ASSERT_TRUE(put_start_result.has_value())
|
|
<< "Failed to PutStart for key: " << key;
|
|
auto put_end_result = service.PutEnd(key, ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result.has_value())
|
|
<< "Failed to PutEnd for key: " << key;
|
|
auto exist_result = service.ExistKey(key);
|
|
ASSERT_TRUE(exist_result.has_value())
|
|
<< "Key does not exist after put: " << key;
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, GetReplicaListByRegexComplex) {
|
|
const uint64_t kv_lease_ttl = 100;
|
|
auto service_config = MasterServiceConfig::builder()
|
|
.set_default_kv_lease_ttl(kv_lease_ttl)
|
|
.build();
|
|
auto service_ = std::make_unique<MasterService>(service_config);
|
|
|
|
// 1. Mount segment
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size = 1024 * 1024 * 16;
|
|
std::string segment_name = "test_segment";
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
|
|
// 2. Prepare a diverse set of keys
|
|
std::vector<std::string> keys_to_put = {
|
|
// Basic keys for prefix matching
|
|
"test_key_01", "test_key_02", "test_key_10",
|
|
// Keys with different prefixes
|
|
"prod_key_alpha", "prod_key_beta",
|
|
// Keys with numbers in the middle
|
|
"data_part_1_chunk_a", "data_part_2_chunk_b",
|
|
// Keys with special characters (if your system supports them)
|
|
"config/user/settings.json", "logs/app-2025-08-13.log",
|
|
// Keys with varying lengths
|
|
"short", "a_very_very_very_long_key_that_tests_length_limits",
|
|
// Keys that look similar but should not match certain regex
|
|
"test-key-extra", "another_key"};
|
|
|
|
for (const auto& key : keys_to_put) {
|
|
put_object(*service_, key);
|
|
}
|
|
|
|
// Wait for all leases to be written to the underlying KV store.
|
|
// In a real system, you might not need this if PutEnd is synchronous.
|
|
// For this test, let's assume it's needed for consistency.
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(kv_lease_ttl));
|
|
|
|
// 3. Run a series of regex tests
|
|
|
|
// Test 3.1: Simple prefix matching
|
|
{
|
|
auto result = service_->GetReplicaListByRegex("^test_key_");
|
|
ASSERT_TRUE(result.has_value());
|
|
EXPECT_EQ(result.value().size(),
|
|
3); // Matches test_key_01, test_key_02, test_key_10
|
|
}
|
|
|
|
// Test 3.2: Matching with a wildcard for any number
|
|
{
|
|
auto result = service_->GetReplicaListByRegex("^test_key_\\d+$");
|
|
ASSERT_TRUE(result.has_value());
|
|
EXPECT_EQ(result.value().size(), 3);
|
|
}
|
|
|
|
// Test 3.3: Matching a specific pattern with wildcards
|
|
{
|
|
// Matches "data_part_1_chunk_a" and "data_part_2_chunk_b"
|
|
auto result =
|
|
service_->GetReplicaListByRegex("^data_part_\\d_chunk_.$");
|
|
ASSERT_TRUE(result.has_value());
|
|
EXPECT_EQ(result.value().size(), 2);
|
|
}
|
|
|
|
// Test 3.4: Matching keys containing a specific substring
|
|
{
|
|
// Matches all keys with "key" in them
|
|
auto result = service_->GetReplicaListByRegex("key");
|
|
ASSERT_TRUE(result.has_value());
|
|
// Expected: test_key_01, test_key_02, test_key_10,
|
|
// prod_key_alpha, prod_key_beta,
|
|
// a_very_very_very_long_key_that_tests_length_limits,
|
|
// test-key-extra, another_key
|
|
EXPECT_EQ(result.value().size(), 8);
|
|
}
|
|
|
|
// Test 3.5: Matching based on file-like paths
|
|
{
|
|
// Match all .log files
|
|
auto result = service_->GetReplicaListByRegex("\\.log$");
|
|
ASSERT_TRUE(result.has_value());
|
|
EXPECT_EQ(result.value().size(), 1);
|
|
EXPECT_EQ(result.value().begin()->first, "logs/app-2025-08-13.log");
|
|
}
|
|
|
|
// Test 3.6: OR condition using |
|
|
{
|
|
// Match keys starting with "prod" OR ending with "json"
|
|
auto result = service_->GetReplicaListByRegex("^prod|\\.json$");
|
|
ASSERT_TRUE(result.has_value());
|
|
// Expected: prod_key_alpha, prod_key_beta, config/user/settings.json
|
|
EXPECT_EQ(result.value().size(), 3);
|
|
}
|
|
|
|
// Test 3.7: Regex that should not match anything
|
|
{
|
|
auto result = service_->GetReplicaListByRegex("^non_existent_prefix_");
|
|
// This should succeed but return an empty map.
|
|
ASSERT_TRUE(result.has_value());
|
|
EXPECT_TRUE(result.value().empty());
|
|
}
|
|
|
|
// Test 3.8: Exact match regex
|
|
{
|
|
auto result = service_->GetReplicaListByRegex("^short$");
|
|
ASSERT_TRUE(result.has_value());
|
|
EXPECT_EQ(result.value().size(), 1);
|
|
EXPECT_EQ(result.value().begin()->first, "short");
|
|
}
|
|
|
|
// Test 3.9: Initial test for non-existent key (as a sanity check)
|
|
{
|
|
auto get_result =
|
|
service_->GetReplicaListByRegex(".*absolutely_non_existent.*");
|
|
// Depending on implementation, this could return an empty map or an
|
|
// error. Let's assume it returns an empty map for a valid regex with no
|
|
// matches.
|
|
ASSERT_TRUE(get_result.has_value());
|
|
EXPECT_TRUE(get_result.value().empty());
|
|
}
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, GetReplicaList) {
|
|
std::unique_ptr<MasterService> service_(new MasterService());
|
|
// Test getting non-existent key
|
|
auto get_result = service_->GetReplicaList("non_existent");
|
|
EXPECT_FALSE(get_result.has_value());
|
|
EXPECT_EQ(ErrorCode::OBJECT_NOT_FOUND, get_result.error());
|
|
|
|
// Mount segment and put an object
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size = 1024 * 1024 * 16;
|
|
std::string segment_name = "test_segment";
|
|
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
|
|
std::string key = "test_key";
|
|
std::vector<uint64_t> slice_lengths = {1024};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
auto put_start_result = service_->PutStart(key, slice_lengths, config);
|
|
ASSERT_TRUE(put_start_result.has_value());
|
|
auto put_end_result = service_->PutEnd(key, ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result.has_value());
|
|
|
|
// Test getting existing key
|
|
auto get_result2 = service_->GetReplicaList(key);
|
|
EXPECT_TRUE(get_result2.has_value());
|
|
auto replica_list_local = get_result2.value();
|
|
EXPECT_FALSE(replica_list_local.empty());
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, RemoveObject) {
|
|
std::unique_ptr<MasterService> service_(new MasterService());
|
|
// Mount segment and put an object
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size = 1024 * 1024 * 16;
|
|
std::string segment_name = "test_segment";
|
|
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
|
|
std::string key = "test_key";
|
|
std::vector<uint64_t> slice_lengths = {1024};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
auto put_start_result = service_->PutStart(key, slice_lengths, config);
|
|
ASSERT_TRUE(put_start_result.has_value());
|
|
auto put_end_result = service_->PutEnd(key, ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result.has_value());
|
|
|
|
// Test removing the object
|
|
auto remove_result = service_->Remove(key);
|
|
EXPECT_TRUE(remove_result.has_value());
|
|
|
|
// Verify object is removed
|
|
auto get_result = service_->GetReplicaList(key);
|
|
EXPECT_FALSE(get_result.has_value());
|
|
EXPECT_EQ(ErrorCode::OBJECT_NOT_FOUND, get_result.error());
|
|
|
|
// Test removing non-existent object
|
|
auto remove_result2 = service_->Remove("non_existent");
|
|
EXPECT_FALSE(remove_result2.has_value());
|
|
EXPECT_EQ(ErrorCode::OBJECT_NOT_FOUND, remove_result2.error());
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, RandomRemoveObject) {
|
|
std::unique_ptr<MasterService> service_(new MasterService());
|
|
// Mount segment and put an object
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size = 1024 * 1024 * 16;
|
|
std::string segment_name = "test_segment";
|
|
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
int times = 10;
|
|
std::random_device rd;
|
|
std::mt19937 gen(rd());
|
|
std::uniform_int_distribution<> dis(1, 1000);
|
|
while (times--) {
|
|
std::string key = "test_key" + std::to_string(dis(gen));
|
|
std::vector<uint64_t> slice_lengths = {1024};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
auto put_start_result = service_->PutStart(key, slice_lengths, config);
|
|
ASSERT_TRUE(put_start_result.has_value());
|
|
auto put_end_result = service_->PutEnd(key, ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result.has_value());
|
|
|
|
// Test removing the object
|
|
auto remove_result = service_->Remove(key);
|
|
EXPECT_TRUE(remove_result.has_value());
|
|
|
|
// Verify object is removed
|
|
auto get_result = service_->GetReplicaList(key);
|
|
EXPECT_FALSE(get_result.has_value());
|
|
EXPECT_EQ(ErrorCode::OBJECT_NOT_FOUND, get_result.error());
|
|
}
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, RemoveByRegex) {
|
|
const uint64_t kv_lease_ttl = 50;
|
|
auto service_config = MasterServiceConfig::builder()
|
|
.set_default_kv_lease_ttl(kv_lease_ttl)
|
|
.build();
|
|
std::unique_ptr<MasterService> service_(new MasterService(service_config));
|
|
// Mount segment and put 10 objects
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size = 1024 * 1024 * 16;
|
|
std::string segment_name = "test_segment";
|
|
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
int times = 10;
|
|
while (times--) {
|
|
std::string key = "test_key" + std::to_string(times);
|
|
std::vector<uint64_t> slice_lengths = {1024};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
auto put_start_result = service_->PutStart(key, slice_lengths, config);
|
|
ASSERT_TRUE(put_start_result.has_value());
|
|
auto put_end_result = service_->PutEnd(key, ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result.has_value());
|
|
auto exist_result = service_->ExistKey(key);
|
|
ASSERT_TRUE(exist_result.has_value());
|
|
}
|
|
// wait for all the lease to expire
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(kv_lease_ttl));
|
|
auto res = service_->RemoveByRegex("^test_key");
|
|
ASSERT_TRUE(res.has_value());
|
|
ASSERT_EQ(10, res.value());
|
|
times = 10;
|
|
while (times--) {
|
|
std::string key = "test_key" + std::to_string(times);
|
|
auto exist_result = service_->ExistKey(key);
|
|
ASSERT_TRUE(exist_result.has_value());
|
|
ASSERT_FALSE(exist_result.value());
|
|
}
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, RemoveByRegexComplex) {
|
|
const uint64_t kv_lease_ttl = 100;
|
|
auto service_config = MasterServiceConfig::builder()
|
|
.set_default_kv_lease_ttl(kv_lease_ttl)
|
|
.build();
|
|
auto service_ = std::make_unique<MasterService>(service_config);
|
|
|
|
// 1. Mount segment
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size = 1024 * 1024 * 16;
|
|
std::string segment_name = "test_segment_remove";
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
|
|
// A helper lambda to repopulate the store for each test case
|
|
auto populate_store = [&]() {
|
|
std::vector<std::string> keys_to_put = {
|
|
"test_key_01",
|
|
"test_key_02",
|
|
"test_key_10",
|
|
"prod_key_alpha",
|
|
"prod_key_beta",
|
|
"data_part_1_chunk_a",
|
|
"data_part_2_chunk_b",
|
|
"config/user/settings.json",
|
|
"logs/app-2025-08-13.log",
|
|
"short",
|
|
"a_very_very_very_long_key_that_tests_length_limits",
|
|
"test-key-extra",
|
|
"another_key"};
|
|
for (const auto& key : keys_to_put) {
|
|
put_object(*service_, key);
|
|
}
|
|
// Wait for potential lease propagation
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(kv_lease_ttl));
|
|
};
|
|
|
|
// --- Test Case 1: Remove a specific subset and verify ---
|
|
{
|
|
SCOPED_TRACE("Test Case 1: Removing keys with prefix 'test_key_'");
|
|
populate_store();
|
|
|
|
// Action: Remove keys starting with "test_key_"
|
|
auto remove_result = service_->RemoveByRegex("^test_key_");
|
|
ASSERT_TRUE(remove_result.has_value());
|
|
EXPECT_EQ(remove_result.value(), 3); // Should remove 3 keys
|
|
|
|
// Verification: Check which keys were deleted and which remain
|
|
std::vector<std::string> deleted_keys = {"test_key_01", "test_key_02",
|
|
"test_key_10"};
|
|
for (const auto& key : deleted_keys) {
|
|
auto exist_result = service_->ExistKey(key);
|
|
ASSERT_TRUE(exist_result.has_value());
|
|
EXPECT_FALSE(exist_result.value())
|
|
<< "Key " << key << " should have been deleted.";
|
|
}
|
|
|
|
std::vector<std::string> remaining_keys = {
|
|
"prod_key_alpha", "short", "test-key-extra"}; // Sample a few
|
|
for (const auto& key : remaining_keys) {
|
|
auto exist_result = service_->ExistKey(key);
|
|
ASSERT_TRUE(exist_result.has_value());
|
|
EXPECT_TRUE(exist_result.value())
|
|
<< "Key " << key << " should NOT have been deleted.";
|
|
}
|
|
}
|
|
|
|
// --- Test Case 2: Remove everything ---
|
|
{
|
|
SCOPED_TRACE("Test Case 2: Removing all keys with '.*'");
|
|
// Store is already populated from the previous (failed) test run, or we
|
|
// can repopulate For isolation, let's assume we start fresh
|
|
auto service_config = MasterServiceConfig::builder()
|
|
.set_default_kv_lease_ttl(kv_lease_ttl)
|
|
.build();
|
|
service_ = std::make_unique<MasterService>(service_config);
|
|
service_->MountSegment(segment, client_id);
|
|
populate_store();
|
|
|
|
size_t total_keys = 13; // Count from the keys_to_put vector
|
|
|
|
// Action: Remove all keys
|
|
auto remove_result = service_->RemoveByRegex(".*");
|
|
ASSERT_TRUE(remove_result.has_value());
|
|
EXPECT_EQ(remove_result.value(), total_keys);
|
|
|
|
// Verification: Check that no keys remain
|
|
auto get_all_result = service_->GetReplicaListByRegex(".*");
|
|
ASSERT_TRUE(get_all_result.has_value());
|
|
EXPECT_TRUE(get_all_result.value().empty());
|
|
}
|
|
|
|
// --- Test Case 3: Attempt to remove with a non-matching pattern ---
|
|
{
|
|
SCOPED_TRACE("Test Case 3: Removing with a non-matching pattern");
|
|
auto service_config = MasterServiceConfig::builder()
|
|
.set_default_kv_lease_ttl(kv_lease_ttl)
|
|
.build();
|
|
service_ = std::make_unique<MasterService>(
|
|
service_config); // Reset the service
|
|
service_->MountSegment(segment, client_id);
|
|
populate_store();
|
|
|
|
size_t total_keys_before_remove = 13;
|
|
|
|
// Action: Attempt to remove using a pattern that matches nothing
|
|
auto remove_result = service_->RemoveByRegex("^nonexistent-pattern-");
|
|
ASSERT_TRUE(remove_result.has_value());
|
|
EXPECT_EQ(remove_result.value(), 0); // Should remove 0 keys
|
|
|
|
// Verification: Check that all keys still exist
|
|
auto get_all_result = service_->GetReplicaListByRegex(".*");
|
|
ASSERT_TRUE(get_all_result.has_value());
|
|
EXPECT_EQ(get_all_result.value().size(), total_keys_before_remove);
|
|
}
|
|
|
|
// --- Test Case 4: Remove based on a complex pattern and verify ---
|
|
{
|
|
SCOPED_TRACE(
|
|
"Test Case 4: Removing based on file paths or containing digits");
|
|
auto service_config = MasterServiceConfig::builder()
|
|
.set_default_kv_lease_ttl(kv_lease_ttl)
|
|
.build();
|
|
service_ = std::make_unique<MasterService>(service_config); // Reset
|
|
service_->MountSegment(segment, client_id);
|
|
populate_store();
|
|
|
|
// Action: Remove all keys that contain a slash '/' OR end with a number
|
|
auto remove_result = service_->RemoveByRegex("/|\\d$");
|
|
ASSERT_TRUE(remove_result.has_value());
|
|
// Matches: "config/user/settings.json", "logs/app-2025-08-13.log",
|
|
// "test_key_01", "test_key_02", "test_key_10"
|
|
// Note: logs/app-2025-08-13.log matches both, but is counted once.
|
|
EXPECT_EQ(remove_result.value(),
|
|
5); // The two paths + test_key_01 and test_key_02.
|
|
// (test_key_10 ends with 0) wait, no, 10 ends with 0.
|
|
// Ah, \d$ matches a single digit at the end. So test_key_01,
|
|
// test_key_02. test_key_10 does NOT match \d$. Let's refine the regex.
|
|
}
|
|
|
|
// --- Test Case 4 ---
|
|
{
|
|
SCOPED_TRACE(
|
|
"Test Case 4 (Corrected): Removing based on complex pattern");
|
|
auto service_config = MasterServiceConfig::builder()
|
|
.set_default_kv_lease_ttl(kv_lease_ttl)
|
|
.build();
|
|
service_ = std::make_unique<MasterService>(service_config); // Reset
|
|
service_->MountSegment(segment, client_id);
|
|
populate_store();
|
|
|
|
// Action: Remove all keys that contain "chunk" OR "config"
|
|
auto remove_result = service_->RemoveByRegex("chunk|config");
|
|
ASSERT_TRUE(remove_result.has_value());
|
|
// Matches: "data_part_1_chunk_a", "data_part_2_chunk_b",
|
|
// "config/user/settings.json"
|
|
EXPECT_EQ(remove_result.value(), 3);
|
|
|
|
// Verification
|
|
auto exist_result_chunk = service_->ExistKey("data_part_1_chunk_a");
|
|
ASSERT_TRUE(exist_result_chunk.has_value());
|
|
EXPECT_FALSE(exist_result_chunk.value());
|
|
|
|
auto exist_result_config =
|
|
service_->ExistKey("config/user/settings.json");
|
|
ASSERT_TRUE(exist_result_config.has_value());
|
|
EXPECT_FALSE(exist_result_config.value());
|
|
|
|
auto exist_result_untouched = service_->ExistKey("prod_key_alpha");
|
|
ASSERT_TRUE(exist_result_untouched.has_value());
|
|
EXPECT_TRUE(exist_result_untouched.value());
|
|
}
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, RemoveAll) {
|
|
const uint64_t kv_lease_ttl = 50;
|
|
auto service_config = MasterServiceConfig::builder()
|
|
.set_default_kv_lease_ttl(kv_lease_ttl)
|
|
.build();
|
|
std::unique_ptr<MasterService> service_(new MasterService(service_config));
|
|
// Mount segment and put 10 objects
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size = 1024 * 1024 * 16;
|
|
std::string segment_name = "test_segment";
|
|
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
int times = 10;
|
|
while (times--) {
|
|
std::string key = "test_key" + std::to_string(times);
|
|
std::vector<uint64_t> slice_lengths = {1024};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
auto put_start_result = service_->PutStart(key, slice_lengths, config);
|
|
ASSERT_TRUE(put_start_result.has_value());
|
|
auto put_end_result = service_->PutEnd(key, ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result.has_value());
|
|
auto exist_result = service_->ExistKey(key);
|
|
ASSERT_TRUE(exist_result.has_value());
|
|
}
|
|
// wait for all the lease to expire
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(kv_lease_ttl));
|
|
ASSERT_EQ(10, service_->RemoveAll());
|
|
times = 10;
|
|
while (times--) {
|
|
std::string key = "test_key" + std::to_string(times);
|
|
auto exist_result = service_->ExistKey(key);
|
|
ASSERT_TRUE(exist_result.has_value());
|
|
ASSERT_FALSE(exist_result.value());
|
|
}
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, MultiSliceMultiReplicaFlow) {
|
|
auto service_config =
|
|
MasterServiceConfig::builder().set_enable_gc(true).build();
|
|
std::unique_ptr<MasterService> service_(new MasterService(service_config));
|
|
|
|
// Mount a segment with sufficient size for multiple replicas
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t segment_size =
|
|
1024 * 1024 * 64; // 64MB to accommodate multiple replicas
|
|
std::string segment_name = "test_segment_multi";
|
|
|
|
Segment segment(generate_uuid(), segment_name, buffer, segment_size);
|
|
UUID client_id = generate_uuid();
|
|
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
|
|
// Test parameters
|
|
std::string key = "multi_slice_object";
|
|
constexpr size_t num_replicas = 3;
|
|
constexpr size_t total_size = 1024 * 1024 * 5; // 5MB total size
|
|
|
|
// Create multiple slices of different sizes
|
|
std::vector<uint64_t> slice_lengths = {
|
|
1024 * 1024 * 2, // 2MB
|
|
1024 * 1024 * 1, // 1MB
|
|
1024 * 1024 * 1, // 1MB
|
|
1024 * 1024 * 1 // 1MB
|
|
};
|
|
|
|
// Verify total size matches sum of slices
|
|
uint64_t sum_slices = 0;
|
|
for (const auto& size : slice_lengths) {
|
|
sum_slices += size;
|
|
}
|
|
ASSERT_EQ(total_size, sum_slices);
|
|
|
|
// Configure replication
|
|
ReplicateConfig config;
|
|
config.replica_num = num_replicas;
|
|
std::vector<Replica::Descriptor> replica_list;
|
|
|
|
// Test PutStart with multiple slices and replicas
|
|
auto put_start_result = service_->PutStart(key, slice_lengths, config);
|
|
ASSERT_TRUE(put_start_result.has_value());
|
|
replica_list = put_start_result.value();
|
|
|
|
// Verify replica list properties
|
|
ASSERT_EQ(num_replicas, replica_list.size());
|
|
for (const auto& replica : replica_list) {
|
|
// Verify replica status
|
|
EXPECT_EQ(ReplicaStatus::PROCESSING, replica.status);
|
|
|
|
// Verify number of handles matches number of slices
|
|
ASSERT_EQ(slice_lengths.size(),
|
|
replica.get_memory_descriptor().buffer_descriptors.size());
|
|
|
|
// Verify each handle's properties
|
|
for (size_t i = 0;
|
|
i < replica.get_memory_descriptor().buffer_descriptors.size();
|
|
i++) {
|
|
const auto& handle =
|
|
replica.get_memory_descriptor().buffer_descriptors[i];
|
|
EXPECT_EQ(BufStatus::INIT, handle.status_);
|
|
|
|
EXPECT_EQ(slice_lengths[i], handle.size_);
|
|
}
|
|
}
|
|
|
|
// Test GetReplicaList during processing (should fail)
|
|
auto get_result = service_->GetReplicaList(key);
|
|
EXPECT_FALSE(get_result.has_value());
|
|
EXPECT_EQ(ErrorCode::REPLICA_IS_NOT_READY, get_result.error());
|
|
|
|
// Complete the put operation
|
|
auto put_end_result = service_->PutEnd(key, ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result.has_value());
|
|
|
|
// Test GetReplicaList after completion
|
|
auto get_result2 = service_->GetReplicaList(key);
|
|
ASSERT_TRUE(get_result2.has_value());
|
|
auto retrieved_replicas = get_result2.value();
|
|
ASSERT_EQ(num_replicas, retrieved_replicas.size());
|
|
|
|
// Verify final state of all replicas
|
|
for (const auto& replica : retrieved_replicas) {
|
|
EXPECT_EQ(ReplicaStatus::COMPLETE, replica.status);
|
|
ASSERT_EQ(slice_lengths.size(),
|
|
replica.get_memory_descriptor().buffer_descriptors.size());
|
|
for (const auto& handle :
|
|
replica.get_memory_descriptor().buffer_descriptors) {
|
|
EXPECT_EQ(BufStatus::COMPLETE, handle.status_);
|
|
}
|
|
}
|
|
|
|
// Sleep for 2 seconds to ensure the object is marked for GC
|
|
std::this_thread::sleep_for(std::chrono::seconds(2));
|
|
auto remove_result = service_->Remove(key);
|
|
EXPECT_FALSE(remove_result.has_value());
|
|
EXPECT_EQ(ErrorCode::OBJECT_NOT_FOUND, remove_result.error());
|
|
|
|
// Verify object is truly removed
|
|
auto get_result3 = service_->GetReplicaList(key);
|
|
EXPECT_FALSE(get_result3.has_value());
|
|
EXPECT_EQ(ErrorCode::OBJECT_NOT_FOUND, get_result3.error());
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, ConcurrentGarbageCollectionTest) {
|
|
auto service_config =
|
|
MasterServiceConfig::builder().set_enable_gc(true).build();
|
|
std::unique_ptr<MasterService> service_(new MasterService(service_config));
|
|
|
|
// Mount segment for testing
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size =
|
|
1024 * 1024 * 256; // Larger segment for concurrent use
|
|
std::string segment_name = "concurrent_gc_segment";
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
|
|
constexpr size_t num_threads = 4;
|
|
constexpr size_t objects_per_thread = 25;
|
|
constexpr size_t total_objects = num_threads * objects_per_thread;
|
|
|
|
// Create a vector to track all created objects
|
|
std::vector<std::string> all_keys;
|
|
all_keys.reserve(total_objects);
|
|
|
|
// Phase 1: Create all objects
|
|
{
|
|
std::vector<std::thread> create_threads;
|
|
std::mutex keys_mutex;
|
|
|
|
for (size_t t = 0; t < num_threads; t++) {
|
|
create_threads.emplace_back([&, t]() {
|
|
for (size_t i = 0; i < objects_per_thread; i++) {
|
|
std::string key = "concurrent_gc_key_" + std::to_string(t) +
|
|
"_" + std::to_string(i);
|
|
std::vector<uint64_t> slice_lengths = {1024};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
std::vector<Replica::Descriptor> replica_list;
|
|
|
|
// Create the object
|
|
auto put_start_result =
|
|
service_->PutStart(key, slice_lengths, config);
|
|
ASSERT_TRUE(put_start_result.has_value());
|
|
auto put_end_result =
|
|
service_->PutEnd(key, ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result.has_value());
|
|
|
|
// Add the key to the tracking list
|
|
{
|
|
std::lock_guard<std::mutex> lock(keys_mutex);
|
|
all_keys.push_back(key);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Wait for all object creation to complete
|
|
for (auto& thread : create_threads) {
|
|
thread.join();
|
|
}
|
|
}
|
|
// Verify all objects were created
|
|
ASSERT_EQ(total_objects, all_keys.size());
|
|
|
|
// Check that all objects exist
|
|
for (const auto& key : all_keys) {
|
|
auto get_result = service_->GetReplicaList(key);
|
|
EXPECT_TRUE(get_result.has_value());
|
|
}
|
|
|
|
// Sleep for 2 seconds to ensure the object is marked for GC
|
|
std::this_thread::sleep_for(std::chrono::seconds(2));
|
|
|
|
// Verify all objects are gone after GC
|
|
size_t found_count = 0;
|
|
for (const auto& key : all_keys) {
|
|
auto get_result = service_->GetReplicaList(key);
|
|
if (get_result.has_value()) {
|
|
found_count++;
|
|
}
|
|
}
|
|
|
|
// All objects should have been garbage collected
|
|
EXPECT_EQ(0, found_count);
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, CleanupStaleHandlesTest) {
|
|
std::unique_ptr<MasterService> service_(new MasterService());
|
|
|
|
// Mount a segment for testing
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size = 1024 * 1024 * 16; // 16MB
|
|
std::string segment_name = "test_segment";
|
|
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
|
|
// Mount the segment
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
|
|
// Create an object that will be stored in the segment
|
|
std::string key = "segment_object";
|
|
std::vector<uint64_t> slice_lengths = {1024 * 1024}; // One 1MB slice
|
|
ReplicateConfig config;
|
|
config.replica_num = 1; // One replica
|
|
|
|
// Create the object
|
|
auto put_start_result = service_->PutStart(key, slice_lengths, config);
|
|
ASSERT_TRUE(put_start_result.has_value());
|
|
auto put_end_result = service_->PutEnd(key, ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result.has_value());
|
|
|
|
// Verify object exists
|
|
auto get_result = service_->GetReplicaList(key);
|
|
ASSERT_TRUE(get_result.has_value());
|
|
auto retrieved_replicas = get_result.value();
|
|
ASSERT_EQ(1, retrieved_replicas.size());
|
|
|
|
// Unmount the segment
|
|
auto unmount_result1 = service_->UnmountSegment(segment.id, client_id);
|
|
ASSERT_TRUE(unmount_result1.has_value());
|
|
|
|
// Try to get the object - it should be automatically removed since the
|
|
// replica is invalid
|
|
auto get_result2 = service_->GetReplicaList(key);
|
|
EXPECT_FALSE(get_result2.has_value());
|
|
EXPECT_EQ(ErrorCode::OBJECT_NOT_FOUND, get_result2.error());
|
|
|
|
// Mount the segment again
|
|
mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
|
|
// Create another object
|
|
std::string key2 = "another_segment_object";
|
|
auto put_start_result2 = service_->PutStart(key2, slice_lengths, config);
|
|
ASSERT_TRUE(put_start_result2.has_value());
|
|
auto put_end_result2 = service_->PutEnd(key2, ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result2.has_value());
|
|
|
|
// Verify we can get it
|
|
auto get_result3 = service_->GetReplicaList(key2);
|
|
ASSERT_TRUE(get_result3.has_value());
|
|
|
|
// Unmount the segment
|
|
auto unmount_result2 = service_->UnmountSegment(segment.id, client_id);
|
|
ASSERT_TRUE(unmount_result2.has_value());
|
|
|
|
// Try to remove the object that should already be cleaned up
|
|
auto remove_result = service_->Remove(key2);
|
|
EXPECT_FALSE(remove_result.has_value());
|
|
EXPECT_EQ(ErrorCode::OBJECT_NOT_FOUND, remove_result.error());
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, ConcurrentWriteAndRemoveAll) {
|
|
std::unique_ptr<MasterService> service_(new MasterService());
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size = 1024 * 1024 * 256; // 256MB for concurrent testing
|
|
std::string segment_name = "concurrent_segment";
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
auto mount_result_concurrent = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result_concurrent.has_value());
|
|
|
|
constexpr int num_threads = 4;
|
|
constexpr int objects_per_thread = 100;
|
|
std::atomic success_writes(0);
|
|
std::atomic remove_all_done(false);
|
|
std::atomic total_removed(0);
|
|
|
|
// Writer threads
|
|
std::vector<std::thread> writers;
|
|
for (int i = 0; i < num_threads; ++i) {
|
|
writers.emplace_back([&, i]() {
|
|
for (int j = 0; j < objects_per_thread; ++j) {
|
|
std::string key =
|
|
"key_" + std::to_string(i) + "_" + std::to_string(j);
|
|
std::vector<uint64_t> slice_lengths = {1024};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
std::vector<Replica::Descriptor> replica_list;
|
|
|
|
auto put_start_result =
|
|
service_->PutStart(key, slice_lengths, config);
|
|
if (put_start_result.has_value()) {
|
|
auto put_end_result =
|
|
service_->PutEnd(key, ReplicaType::MEMORY);
|
|
if (put_end_result.has_value()) {
|
|
success_writes++;
|
|
}
|
|
}
|
|
|
|
// Random sleep to increase concurrency complexity
|
|
std::this_thread::sleep_for(
|
|
std::chrono::milliseconds(rand() % 10));
|
|
}
|
|
});
|
|
}
|
|
|
|
// RemoveAll thread
|
|
std::thread remove_thread([&]() {
|
|
std::this_thread::sleep_for(
|
|
std::chrono::milliseconds(50)); // Let some writes start
|
|
long removed = service_->RemoveAll();
|
|
LOG(INFO) << "Removed " << removed
|
|
<< " objects during concurrent writes";
|
|
ASSERT_GT(removed, 0);
|
|
remove_all_done = true;
|
|
total_removed.fetch_add(removed);
|
|
});
|
|
|
|
// Join all threads
|
|
for (auto& t : writers) {
|
|
t.join();
|
|
}
|
|
remove_thread.join();
|
|
|
|
// Verify results
|
|
EXPECT_GT(success_writes, 0);
|
|
EXPECT_TRUE(remove_all_done);
|
|
|
|
// Final RemoveAll to ensure clean state
|
|
long final_removed = service_->RemoveAll();
|
|
LOG(INFO) << "Final RemoveAll removed " << final_removed << " objects";
|
|
ASSERT_GT(final_removed, 0);
|
|
total_removed.fetch_add(final_removed);
|
|
ASSERT_EQ(total_removed, num_threads * objects_per_thread);
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, ConcurrentReadAndRemoveAll) {
|
|
// set a large kv_lease_ttl so the granted lease will not quickly expire
|
|
const uint64_t kv_lease_ttl = 200;
|
|
auto service_config = MasterServiceConfig::builder()
|
|
.set_default_kv_lease_ttl(kv_lease_ttl)
|
|
.build();
|
|
std::unique_ptr<MasterService> service_(new MasterService(service_config));
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size = 1024 * 1024 * 256; // 256MB for concurrent testing
|
|
std::string segment_name = "concurrent_segment";
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
|
|
// Pre-populate with test data
|
|
constexpr int num_objects = 1000;
|
|
for (int i = 0; i < num_objects; ++i) {
|
|
std::string key = "pre_key_" + std::to_string(i);
|
|
std::vector<uint64_t> slice_lengths = {1024};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
|
|
auto put_start_result = service_->PutStart(key, slice_lengths, config);
|
|
ASSERT_TRUE(put_start_result.has_value());
|
|
auto put_end_result = service_->PutEnd(key, ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result.has_value());
|
|
}
|
|
|
|
std::atomic<int> success_reads(0);
|
|
std::atomic<bool> remove_all_done(false);
|
|
|
|
// Reader threads
|
|
std::vector<std::thread> readers;
|
|
for (int i = 0; i < 4; ++i) {
|
|
readers.emplace_back([&]() {
|
|
for (int j = 0; j < num_objects; ++j) {
|
|
std::string key = "pre_key_" + std::to_string(j);
|
|
auto get_result = service_->GetReplicaList(key);
|
|
if (get_result.has_value()) {
|
|
success_reads++;
|
|
}
|
|
|
|
// Random sleep to increase concurrency complexity
|
|
std::this_thread::sleep_for(
|
|
std::chrono::milliseconds(rand() % 5));
|
|
}
|
|
});
|
|
}
|
|
|
|
// RemoveAll thread
|
|
std::thread remove_thread([&]() {
|
|
std::this_thread::sleep_for(
|
|
std::chrono::milliseconds(10)); // Let some reads start
|
|
long removed = service_->RemoveAll();
|
|
LOG(INFO) << "Removed " << removed
|
|
<< " objects during concurrent reads";
|
|
remove_all_done = true;
|
|
});
|
|
|
|
// Join all threads
|
|
for (auto& t : readers) {
|
|
t.join();
|
|
}
|
|
remove_thread.join();
|
|
|
|
EXPECT_TRUE(remove_all_done);
|
|
// Verify 0 < success_reads < num_objects
|
|
EXPECT_GT(success_reads, 0);
|
|
EXPECT_NE(success_reads, num_objects);
|
|
|
|
// wait for all the lease to expire
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(kv_lease_ttl));
|
|
long removed = service_->RemoveAll();
|
|
LOG(INFO) << "Removed " << removed << " objects after kv lease expired";
|
|
|
|
// Verify all objects were removed
|
|
for (int i = 0; i < num_objects; ++i) {
|
|
std::string key = "pre_key_" + std::to_string(i);
|
|
auto get_result = service_->GetReplicaList(key);
|
|
EXPECT_FALSE(get_result.has_value());
|
|
EXPECT_EQ(ErrorCode::OBJECT_NOT_FOUND, get_result.error());
|
|
}
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, ConcurrentRemoveAllOperations) {
|
|
std::unique_ptr<MasterService> service_(new MasterService());
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size =
|
|
1024 * 1024 * 16 * 100; // 256MB for concurrent testing
|
|
std::string segment_name = "concurrent_segment";
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
|
|
// Pre-populate with test data
|
|
constexpr int num_objects = 1000000;
|
|
for (int i = 0; i < num_objects; ++i) {
|
|
std::string key = "pre_key_" + std::to_string(i);
|
|
std::vector<uint64_t> slice_lengths = {1024};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
|
|
auto put_start_result = service_->PutStart(key, slice_lengths, config);
|
|
ASSERT_TRUE(put_start_result.has_value());
|
|
auto put_end_result = service_->PutEnd(key, ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result.has_value());
|
|
}
|
|
|
|
std::atomic<int> remove_all_count(0);
|
|
|
|
// Two RemoveAll threads
|
|
std::vector<std::thread> remove_threads;
|
|
for (int i = 0; i < 2; ++i) {
|
|
remove_threads.emplace_back([&]() {
|
|
long removed = service_->RemoveAll();
|
|
LOG(INFO) << "RemoveAll removed " << removed << " objects";
|
|
remove_all_count += removed;
|
|
});
|
|
}
|
|
|
|
// Join all threads
|
|
for (auto& t : remove_threads) {
|
|
t.join();
|
|
}
|
|
|
|
// Verify results - one RemoveAll should return num_objects, the other 0
|
|
EXPECT_EQ(num_objects, remove_all_count);
|
|
|
|
// Verify all objects were removed
|
|
for (int i = 0; i < num_objects; ++i) {
|
|
std::string key = "pre_key_" + std::to_string(i);
|
|
auto get_result = service_->GetReplicaList(key);
|
|
EXPECT_FALSE(get_result.has_value());
|
|
EXPECT_EQ(ErrorCode::OBJECT_NOT_FOUND, get_result.error());
|
|
}
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, UnmountSegmentImmediateCleanup) {
|
|
std::unique_ptr<MasterService> service_(new MasterService());
|
|
|
|
// Mount two segments for testing
|
|
constexpr size_t buffer1 = 0x300000000;
|
|
constexpr size_t buffer2 = 0x400000000;
|
|
constexpr size_t size = 1024 * 1024 * 16;
|
|
|
|
Segment segment1(generate_uuid(), "segment1", buffer1, size);
|
|
Segment segment2(generate_uuid(), "segment2", buffer2, size);
|
|
UUID client_id = generate_uuid();
|
|
auto mount_result1 = service_->MountSegment(segment1, client_id);
|
|
ASSERT_TRUE(mount_result1.has_value());
|
|
auto mount_result2 = service_->MountSegment(segment2, client_id);
|
|
ASSERT_TRUE(mount_result2.has_value());
|
|
|
|
// Create two objects in the two segments
|
|
std::string key1 = GenerateKeyForSegment(service_, segment1.name);
|
|
std::string key2 = GenerateKeyForSegment(service_, segment2.name);
|
|
std::vector<uint64_t> slice_lengths = {1024};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
|
|
// Unmount segment1
|
|
auto unmount_result1 = service_->UnmountSegment(segment1.id, client_id);
|
|
ASSERT_TRUE(unmount_result1.has_value());
|
|
// Umount will remove all objects in the segment, include the key1
|
|
ASSERT_EQ(1, service_->GetKeyCount());
|
|
// Verify objects in segment1 is gone
|
|
auto get_result1 = service_->GetReplicaList(key1);
|
|
ASSERT_FALSE(get_result1.has_value());
|
|
ASSERT_EQ(ErrorCode::OBJECT_NOT_FOUND, get_result1.error());
|
|
|
|
// Verify objects in segment2 is still there
|
|
auto get_result2 = service_->GetReplicaList(key2);
|
|
ASSERT_TRUE(get_result2.has_value());
|
|
|
|
// Verify put key1 will put into segment2 rather than segment1
|
|
auto put_start_result = service_->PutStart(key1, slice_lengths, config);
|
|
ASSERT_TRUE(put_start_result.has_value());
|
|
replica_list = put_start_result.value();
|
|
auto put_end_result = service_->PutEnd(key1, ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result.has_value());
|
|
auto get_result3 = service_->GetReplicaList(key1);
|
|
ASSERT_TRUE(get_result3.has_value());
|
|
auto retrieved = get_result3.value();
|
|
ASSERT_EQ(replica_list[0]
|
|
.get_memory_descriptor()
|
|
.buffer_descriptors[0]
|
|
.segment_name_,
|
|
segment2.name);
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, ReadableAfterPartialUnmountWithReplication) {
|
|
std::unique_ptr<MasterService> service_(new MasterService());
|
|
|
|
// TODO: mount two larger segments when replication affinity fixed
|
|
// Mount two segments sized to fit exactly one replica each
|
|
constexpr size_t buffer1 = 0x300000000;
|
|
constexpr size_t buffer2 = 0x400000000;
|
|
constexpr size_t segment_size = 1024 * 1024 * 16;
|
|
constexpr size_t object_size =
|
|
segment_size / 2 + 16; // force at most 1 replica per segment
|
|
|
|
Segment segment1(generate_uuid(), "segment1", buffer1, segment_size);
|
|
Segment segment2(generate_uuid(), "segment2", buffer2, segment_size);
|
|
UUID client_id = generate_uuid();
|
|
auto mount_result1 = service_->MountSegment(segment1, client_id);
|
|
ASSERT_TRUE(mount_result1.has_value());
|
|
auto mount_result2 = service_->MountSegment(segment2, client_id);
|
|
ASSERT_TRUE(mount_result2.has_value());
|
|
|
|
// Put a key with 2 replicas
|
|
std::string key = "replicated_key";
|
|
std::vector<uint64_t> slice_lengths = {object_size};
|
|
ReplicateConfig config;
|
|
config.replica_num = 2;
|
|
|
|
auto put_start_result = service_->PutStart(key, slice_lengths, config);
|
|
ASSERT_TRUE(put_start_result.has_value());
|
|
ASSERT_EQ(2u, put_start_result->size());
|
|
ASSERT_TRUE(service_->PutEnd(key, ReplicaType::MEMORY).has_value());
|
|
|
|
// Verify two replicas exist and they are on distinct segments
|
|
auto get_result = service_->GetReplicaList(key);
|
|
ASSERT_TRUE(get_result.has_value());
|
|
auto replicas = get_result.value();
|
|
ASSERT_EQ(2u, replicas.size());
|
|
std::unordered_set<std::string> seg_names;
|
|
for (const auto& rep : replicas) {
|
|
ASSERT_EQ(ReplicaStatus::COMPLETE, rep.status);
|
|
const auto& mem = rep.get_memory_descriptor();
|
|
ASSERT_EQ(1u, mem.buffer_descriptors.size());
|
|
seg_names.insert(mem.buffer_descriptors[0].segment_name_);
|
|
}
|
|
ASSERT_EQ(2u, seg_names.size())
|
|
<< "Replicas should be on different segments";
|
|
|
|
// Unmount one segment
|
|
ASSERT_TRUE(service_->UnmountSegment(segment1.id, client_id).has_value());
|
|
|
|
// Key should still be readable via the remaining replica
|
|
auto get_after_unmount = service_->GetReplicaList(key);
|
|
ASSERT_TRUE(get_after_unmount.has_value())
|
|
<< "Object should remain accessible with surviving replica";
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, UnmountSegmentPerformance) {
|
|
std::unique_ptr<MasterService> service_(new MasterService());
|
|
constexpr size_t kBufferAddress = 0x300000000;
|
|
constexpr size_t kSegmentSize = 1024 * 1024 * 256; // 256MB
|
|
std::string segment_name = "perf_test_segment";
|
|
Segment segment(generate_uuid(), segment_name, kBufferAddress,
|
|
kSegmentSize);
|
|
UUID client_id = generate_uuid();
|
|
|
|
// Mount a segment for testing
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
|
|
// Create 10000 keys for testing
|
|
constexpr int kNumKeys = 1000;
|
|
std::vector<std::string> keys;
|
|
keys.reserve(kNumKeys);
|
|
|
|
auto start = std::chrono::steady_clock::now();
|
|
|
|
// Create `kNumKeys` keys
|
|
for (int i = 0; i < kNumKeys; ++i) {
|
|
std::string key = GenerateKeyForSegment(service_, segment_name);
|
|
keys.push_back(key);
|
|
}
|
|
|
|
auto create_end = std::chrono::steady_clock::now();
|
|
|
|
// Execute unmount operation and record operation time
|
|
auto unmount_start = std::chrono::steady_clock::now();
|
|
auto unmount_result = service_->UnmountSegment(segment.id, client_id);
|
|
EXPECT_TRUE(unmount_result.has_value());
|
|
auto unmount_end = std::chrono::steady_clock::now();
|
|
|
|
auto unmount_duration =
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(unmount_end -
|
|
unmount_start);
|
|
|
|
// Unmount operation should be very fast, so we set 1s limit
|
|
EXPECT_LE(unmount_duration.count(), 1000)
|
|
<< "Unmount operation took " << unmount_duration.count()
|
|
<< "ms which exceeds 1 second limit";
|
|
|
|
// Verify all keys are gone
|
|
for (const auto& key : keys) {
|
|
auto get_result = service_->GetReplicaList(key);
|
|
EXPECT_FALSE(get_result.has_value());
|
|
EXPECT_EQ(ErrorCode::OBJECT_NOT_FOUND, get_result.error());
|
|
}
|
|
|
|
// Output performance report
|
|
auto total_create_duration =
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(create_end -
|
|
start);
|
|
std::cout << "\nPerformance Metrics:\n"
|
|
<< "Keys created: " << kNumKeys << "\n"
|
|
<< "Creation time: " << total_create_duration.count() << "ms\n"
|
|
<< "Unmount time: " << unmount_duration.count() << "ms\n";
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, RemoveLeasedObject) {
|
|
const uint64_t kv_lease_ttl = 50;
|
|
auto service_config = MasterServiceConfig::builder()
|
|
.set_default_kv_lease_ttl(kv_lease_ttl)
|
|
.build();
|
|
std::unique_ptr<MasterService> service_(new MasterService(service_config));
|
|
// Mount segment and put an object
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size = 1024 * 1024 * 16;
|
|
std::string segment_name = "test_segment";
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
|
|
std::string key = "test_key";
|
|
std::vector<uint64_t> slice_lengths = {1024};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
|
|
// Verify lease is granted on ExistsKey
|
|
auto put_start_result = service_->PutStart(key, slice_lengths, config);
|
|
ASSERT_TRUE(put_start_result.has_value());
|
|
auto put_end_result = service_->PutEnd(key, ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result.has_value());
|
|
auto exist_result = service_->ExistKey(key);
|
|
ASSERT_TRUE(exist_result.has_value());
|
|
auto remove_result = service_->Remove(key);
|
|
EXPECT_FALSE(remove_result.has_value());
|
|
EXPECT_EQ(ErrorCode::OBJECT_HAS_LEASE, remove_result.error());
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(kv_lease_ttl));
|
|
auto remove_result2 = service_->Remove(key);
|
|
EXPECT_TRUE(remove_result2.has_value());
|
|
|
|
// Verify lease is extended on successive ExistsKey
|
|
auto put_start_result2 = service_->PutStart(key, slice_lengths, config);
|
|
ASSERT_TRUE(put_start_result2.has_value());
|
|
auto put_end_result2 = service_->PutEnd(key, ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result2.has_value());
|
|
auto exist_result2 = service_->ExistKey(key);
|
|
ASSERT_TRUE(exist_result2.has_value());
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(kv_lease_ttl));
|
|
auto exist_result3 = service_->ExistKey(key);
|
|
ASSERT_TRUE(exist_result3.has_value());
|
|
auto remove_result3 = service_->Remove(key);
|
|
EXPECT_FALSE(remove_result3.has_value());
|
|
EXPECT_EQ(ErrorCode::OBJECT_HAS_LEASE, remove_result3.error());
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(kv_lease_ttl));
|
|
auto remove_result4 = service_->Remove(key);
|
|
EXPECT_TRUE(remove_result4.has_value());
|
|
|
|
// Verify lease is granted on GetReplicaList
|
|
auto put_start_result3 = service_->PutStart(key, slice_lengths, config);
|
|
ASSERT_TRUE(put_start_result3.has_value());
|
|
auto put_end_result3 = service_->PutEnd(key, ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result3.has_value());
|
|
auto get_result = service_->GetReplicaList(key);
|
|
ASSERT_TRUE(get_result.has_value());
|
|
auto remove_result5 = service_->Remove(key);
|
|
EXPECT_FALSE(remove_result5.has_value());
|
|
EXPECT_EQ(ErrorCode::OBJECT_HAS_LEASE, remove_result5.error());
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(kv_lease_ttl));
|
|
auto remove_result6 = service_->Remove(key);
|
|
EXPECT_TRUE(remove_result6.has_value());
|
|
|
|
// Verify lease is extended on successive GetReplicaList
|
|
auto put_start_result4 = service_->PutStart(key, slice_lengths, config);
|
|
ASSERT_TRUE(put_start_result4.has_value());
|
|
auto put_end_result4 = service_->PutEnd(key, ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result4.has_value());
|
|
auto get_result2 = service_->GetReplicaList(key);
|
|
ASSERT_TRUE(get_result2.has_value());
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(kv_lease_ttl));
|
|
auto get_result3 = service_->GetReplicaList(key);
|
|
ASSERT_TRUE(get_result3.has_value());
|
|
auto remove_result7 = service_->Remove(key);
|
|
EXPECT_FALSE(remove_result7.has_value());
|
|
EXPECT_EQ(ErrorCode::OBJECT_HAS_LEASE, remove_result7.error());
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(kv_lease_ttl));
|
|
auto remove_result8 = service_->Remove(key);
|
|
EXPECT_TRUE(remove_result8.has_value());
|
|
|
|
// Verify object is removed
|
|
auto get_result4 = service_->GetReplicaList(key);
|
|
EXPECT_FALSE(get_result4.has_value());
|
|
EXPECT_EQ(ErrorCode::OBJECT_NOT_FOUND, get_result4.error());
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, RemoveAllLeasedObject) {
|
|
const uint64_t kv_lease_ttl = 50;
|
|
auto service_config = MasterServiceConfig::builder()
|
|
.set_default_kv_lease_ttl(kv_lease_ttl)
|
|
.build();
|
|
std::unique_ptr<MasterService> service_(new MasterService(service_config));
|
|
// Mount segment and put 10 objects, with 5 of them having lease
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size = 1024 * 1024 * 16;
|
|
std::string segment_name = "test_segment";
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
for (int i = 0; i < 10; ++i) {
|
|
std::string key = "test_key" + std::to_string(i);
|
|
std::vector<uint64_t> slice_lengths = {1024};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
auto put_start_result = service_->PutStart(key, slice_lengths, config);
|
|
ASSERT_TRUE(put_start_result.has_value());
|
|
auto put_end_result = service_->PutEnd(key, ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result.has_value());
|
|
if (i >= 5) {
|
|
auto exist_result = service_->ExistKey(key);
|
|
ASSERT_TRUE(exist_result.has_value());
|
|
}
|
|
}
|
|
ASSERT_EQ(5, service_->RemoveAll());
|
|
for (int i = 0; i < 5; ++i) {
|
|
std::string key = "test_key" + std::to_string(i);
|
|
auto exist_result = service_->ExistKey(key);
|
|
ASSERT_FALSE(exist_result.value());
|
|
}
|
|
// wait for all the lease to expire
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(kv_lease_ttl));
|
|
ASSERT_EQ(5, service_->RemoveAll());
|
|
for (int i = 5; i < 10; ++i) {
|
|
std::string key = "test_key" + std::to_string(i);
|
|
auto exist_result = service_->ExistKey(key);
|
|
ASSERT_FALSE(exist_result.value());
|
|
}
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, EvictObject) {
|
|
// set a large kv_lease_ttl so the granted lease will not quickly expire
|
|
const uint64_t kv_lease_ttl = 2000;
|
|
auto service_config = MasterServiceConfig::builder()
|
|
.set_default_kv_lease_ttl(kv_lease_ttl)
|
|
.build();
|
|
std::unique_ptr<MasterService> service_(new MasterService(service_config));
|
|
// Mount a segment that can hold about 1024 * 16 objects.
|
|
// As the eviction is processed separately for each shard,
|
|
// we need to fill each shard with enough objects to thoroughly
|
|
// test the eviction process.
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size = 1024 * 1024 * 16 * 15;
|
|
constexpr size_t object_size = 1024 * 15;
|
|
std::string segment_name = "test_segment";
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
|
|
// Verify if we can put objects more than the segment can hold
|
|
int success_puts = 0;
|
|
for (int i = 0; i < 1024 * 16 + 50; ++i) {
|
|
std::string key = "test_key" + std::to_string(i);
|
|
std::vector<uint64_t> slice_lengths = {object_size};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
auto put_start_result = service_->PutStart(key, slice_lengths, config);
|
|
if (put_start_result.has_value()) {
|
|
auto put_end_result = service_->PutEnd(key, ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result.has_value());
|
|
success_puts++;
|
|
} else {
|
|
// wait for gc thread to work
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
}
|
|
}
|
|
ASSERT_GT(success_puts, 1024 * 16);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(kv_lease_ttl));
|
|
service_->RemoveAll();
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, TryEvictLeasedObject) {
|
|
// set a large kv_lease_ttl so the granted lease will not quickly expire
|
|
const uint64_t kv_lease_ttl = 500;
|
|
auto service_config = MasterServiceConfig::builder()
|
|
.set_default_kv_lease_ttl(kv_lease_ttl)
|
|
.build();
|
|
std::unique_ptr<MasterService> service_(new MasterService(service_config));
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size = 1024 * 1024 * 16;
|
|
constexpr size_t object_size = 1024 * 1024;
|
|
std::string segment_name = "test_segment";
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
|
|
// Verify leased object will not be evicted.
|
|
int success_puts = 0;
|
|
int failed_puts = 0;
|
|
std::vector<std::string> leased_keys;
|
|
for (int i = 0; i < 16 + 10; ++i) {
|
|
std::string key = "test_key" + std::to_string(i);
|
|
std::vector<uint64_t> slice_lengths = {object_size};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
auto put_start_result = service_->PutStart(key, slice_lengths, config);
|
|
if (put_start_result.has_value()) {
|
|
auto put_end_result = service_->PutEnd(key, ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result.has_value());
|
|
// the object is leased
|
|
auto get_result = service_->GetReplicaList(key);
|
|
ASSERT_TRUE(get_result.has_value());
|
|
leased_keys.push_back(key);
|
|
success_puts++;
|
|
} else {
|
|
failed_puts++;
|
|
}
|
|
}
|
|
ASSERT_GT(success_puts, 0);
|
|
ASSERT_GT(failed_puts, 0);
|
|
// wait for gc thread to do eviction
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
// All leased objects should be accessible
|
|
for (const auto& key : leased_keys) {
|
|
auto get_result = service_->GetReplicaList(key);
|
|
ASSERT_TRUE(get_result.has_value());
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(kv_lease_ttl));
|
|
service_->RemoveAll();
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, RemoveSoftPinObject) {
|
|
const uint64_t kv_lease_ttl = 200;
|
|
// set a large soft_pin_ttl so the granted soft pin will not quickly expire
|
|
const uint64_t kv_soft_pin_ttl = 10000;
|
|
const bool allow_evict_soft_pinned_objects = true;
|
|
auto service_config = MasterServiceConfig::builder()
|
|
.set_default_kv_lease_ttl(kv_lease_ttl)
|
|
.set_default_kv_soft_pin_ttl(kv_soft_pin_ttl)
|
|
.set_allow_evict_soft_pinned_objects(
|
|
allow_evict_soft_pinned_objects)
|
|
.build();
|
|
std::unique_ptr<MasterService> service_(new MasterService(service_config));
|
|
// Mount segment and put an object
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size = 1024 * 1024 * 16;
|
|
std::string segment_name = "test_segment";
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
ASSERT_TRUE(service_->MountSegment(segment, client_id).has_value());
|
|
|
|
std::string key = "test_key";
|
|
std::vector<uint64_t> slice_lengths = {1024};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
config.with_soft_pin = true;
|
|
|
|
// Verify soft pin does not block remove
|
|
ASSERT_TRUE(service_->PutStart(key, slice_lengths, config).has_value());
|
|
ASSERT_TRUE(service_->PutEnd(key, ReplicaType::MEMORY).has_value());
|
|
EXPECT_TRUE(service_->Remove(key).has_value());
|
|
|
|
// Verify soft pin does not block RemoveAll
|
|
ASSERT_TRUE(service_->PutStart(key, slice_lengths, config).has_value());
|
|
ASSERT_TRUE(service_->PutEnd(key, ReplicaType::MEMORY).has_value());
|
|
EXPECT_EQ(1, service_->RemoveAll());
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, SoftPinObjectsNotEvictedBeforeOtherObjects) {
|
|
const uint64_t kv_lease_ttl = 200;
|
|
// set a large soft_pin_ttl so the granted soft pin will not quickly expire
|
|
const uint64_t kv_soft_pin_ttl = 10000;
|
|
const double eviction_ratio = 0.5;
|
|
const bool allow_evict_soft_pinned_objects = true;
|
|
auto service_config = MasterServiceConfig::builder()
|
|
.set_default_kv_lease_ttl(kv_lease_ttl)
|
|
.set_default_kv_soft_pin_ttl(kv_soft_pin_ttl)
|
|
.set_allow_evict_soft_pinned_objects(
|
|
allow_evict_soft_pinned_objects)
|
|
.set_eviction_ratio(eviction_ratio)
|
|
.build();
|
|
std::unique_ptr<MasterService> service_(new MasterService(service_config));
|
|
|
|
// Mount segment and put an object
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t segment_size = 1024 * 1024 * 16;
|
|
constexpr size_t value_size = 1024 * 1024;
|
|
std::string segment_name = "test_segment";
|
|
Segment segment(generate_uuid(), segment_name, buffer, segment_size);
|
|
UUID client_id = generate_uuid();
|
|
ASSERT_TRUE(service_->MountSegment(segment, client_id).has_value());
|
|
|
|
// The eviction has random factors, so test 5 times
|
|
for (int test_i = 0; test_i < 5; test_i++) {
|
|
// Put pin_key first
|
|
for (int i = 0; i < 2; i++) {
|
|
std::string pin_key = "pin_key" + std::to_string(i);
|
|
std::vector<uint64_t> slice_lengths = {value_size};
|
|
ReplicateConfig soft_pin_config;
|
|
soft_pin_config.replica_num = 1;
|
|
soft_pin_config.with_soft_pin = true;
|
|
|
|
ASSERT_TRUE(
|
|
service_->PutStart(pin_key, slice_lengths, soft_pin_config)
|
|
.has_value());
|
|
ASSERT_TRUE(
|
|
service_->PutEnd(pin_key, ReplicaType::MEMORY).has_value());
|
|
}
|
|
|
|
// Fill the segment to trigger eviction
|
|
int failed_puts = 0;
|
|
for (int i = 0; i < 20; i++) {
|
|
std::string key = "key" + std::to_string(i);
|
|
std::vector<uint64_t> slice_lengths = {value_size};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
if (service_->PutStart(key, slice_lengths, config).has_value()) {
|
|
ASSERT_TRUE(
|
|
service_->PutEnd(key, ReplicaType::MEMORY).has_value());
|
|
} else {
|
|
failed_puts++;
|
|
}
|
|
}
|
|
ASSERT_GT(failed_puts, 0);
|
|
// wait for gc thread to do eviction
|
|
std::this_thread::sleep_for(
|
|
std::chrono::milliseconds(kv_lease_ttl + 1000));
|
|
// pin_key should still be accessible
|
|
for (int i = 0; i < 2; i++) {
|
|
std::string pin_key = "pin_key" + std::to_string(i);
|
|
ASSERT_TRUE(service_->GetReplicaList(pin_key).has_value());
|
|
}
|
|
|
|
// wait for the lease to expire
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(kv_lease_ttl));
|
|
// remove all objects before the next turn
|
|
service_->RemoveAll();
|
|
}
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, SoftPinObjectsCanBeEvicted) {
|
|
const uint64_t kv_lease_ttl = 200;
|
|
// set a large soft_pin_ttl so the granted soft pin will not quickly expire
|
|
const uint64_t kv_soft_pin_ttl = 10000;
|
|
const bool allow_evict_soft_pinned_objects = true;
|
|
auto service_config = MasterServiceConfig::builder()
|
|
.set_default_kv_lease_ttl(kv_lease_ttl)
|
|
.set_default_kv_soft_pin_ttl(kv_soft_pin_ttl)
|
|
.set_allow_evict_soft_pinned_objects(
|
|
allow_evict_soft_pinned_objects)
|
|
.build();
|
|
std::unique_ptr<MasterService> service_(new MasterService(service_config));
|
|
|
|
// Mount segment and put an object
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t segment_size = 1024 * 1024 * 16;
|
|
constexpr size_t value_size = 1024 * 1024;
|
|
std::string segment_name = "test_segment";
|
|
Segment segment(generate_uuid(), segment_name, buffer, segment_size);
|
|
UUID client_id = generate_uuid();
|
|
ASSERT_TRUE(service_->MountSegment(segment, client_id).has_value());
|
|
|
|
// Verify if we can put objects more than the segment can hold
|
|
int success_puts = 0;
|
|
for (int i = 0; i < 16 + 50; ++i) {
|
|
std::string key = "test_key" + std::to_string(i);
|
|
std::vector<uint64_t> slice_lengths = {value_size};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
config.with_soft_pin = true;
|
|
if (service_->PutStart(key, slice_lengths, config).has_value()) {
|
|
ASSERT_TRUE(service_->PutEnd(key, ReplicaType::MEMORY).has_value());
|
|
success_puts++;
|
|
} else {
|
|
// wait for gc thread to work
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
}
|
|
}
|
|
ASSERT_GT(success_puts, 16);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(kv_lease_ttl));
|
|
service_->RemoveAll();
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, SoftPinExtendedOnGet) {
|
|
const uint64_t kv_lease_ttl = 200;
|
|
// The soft pin ttl shall not be too large, otherwise the test will take too
|
|
// long
|
|
const uint64_t kv_soft_pin_ttl = 1000;
|
|
static_assert(
|
|
kv_soft_pin_ttl > kv_lease_ttl,
|
|
"kv_soft_pin_ttl must be larger than kv_lease_ttl in this test");
|
|
const double eviction_ratio = 0.5;
|
|
const bool allow_evict_soft_pinned_objects = true;
|
|
auto service_config = MasterServiceConfig::builder()
|
|
.set_default_kv_lease_ttl(kv_lease_ttl)
|
|
.set_default_kv_soft_pin_ttl(kv_soft_pin_ttl)
|
|
.set_allow_evict_soft_pinned_objects(
|
|
allow_evict_soft_pinned_objects)
|
|
.set_eviction_ratio(eviction_ratio)
|
|
.build();
|
|
std::unique_ptr<MasterService> service_(new MasterService(service_config));
|
|
|
|
// Mount segment and put an object
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t segment_size = 1024 * 1024 * 16;
|
|
constexpr size_t value_size = 1024 * 1024;
|
|
std::string segment_name = "test_segment";
|
|
Segment segment(generate_uuid(), segment_name, buffer, segment_size);
|
|
UUID client_id = generate_uuid();
|
|
ASSERT_TRUE(service_->MountSegment(segment, client_id).has_value());
|
|
|
|
// The eviction has random factors, so test 3 times
|
|
for (int test_i = 0; test_i < 3; test_i++) {
|
|
// Put pin_key first
|
|
for (int i = 0; i < 2; i++) {
|
|
std::string pin_key = "pin_key" + std::to_string(i);
|
|
std::vector<uint64_t> slice_lengths = {value_size};
|
|
ReplicateConfig soft_pin_config;
|
|
soft_pin_config.replica_num = 1;
|
|
soft_pin_config.with_soft_pin = true;
|
|
|
|
ASSERT_TRUE(
|
|
service_->PutStart(pin_key, slice_lengths, soft_pin_config));
|
|
ASSERT_TRUE(
|
|
service_->PutEnd(pin_key, ReplicaType::MEMORY).has_value());
|
|
}
|
|
|
|
// Wait for the soft pin to expire
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(kv_soft_pin_ttl));
|
|
|
|
// Get the pin_key to extend the soft pin
|
|
for (int i = 0; i < 2; i++) {
|
|
std::string pin_key = "pin_key" + std::to_string(i);
|
|
ASSERT_TRUE(service_->GetReplicaList(pin_key).has_value());
|
|
}
|
|
|
|
// Fill the segment to trigger eviction
|
|
int failed_puts = 0;
|
|
for (int i = 0; i < 16; i++) {
|
|
std::string key = "key" + std::to_string(i);
|
|
std::vector<uint64_t> slice_lengths = {value_size};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
if (service_->PutStart(key, slice_lengths, config).has_value()) {
|
|
ASSERT_TRUE(
|
|
service_->PutEnd(key, ReplicaType::MEMORY).has_value());
|
|
} else {
|
|
failed_puts++;
|
|
}
|
|
}
|
|
ASSERT_GT(failed_puts, 0);
|
|
|
|
// wait for gc thread to do eviction
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(kv_lease_ttl));
|
|
|
|
// pin_key should still be accessible
|
|
for (int i = 0; i < 2; i++) {
|
|
std::string pin_key = "pin_key" + std::to_string(i);
|
|
ASSERT_TRUE(service_->GetReplicaList(pin_key).has_value());
|
|
}
|
|
|
|
// wait for the lease to expire
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(kv_lease_ttl));
|
|
// remove all objects before the next turn
|
|
service_->RemoveAll();
|
|
}
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, SoftPinObjectsNotAllowEvict) {
|
|
const uint64_t kv_lease_ttl = 200;
|
|
// set a large soft_pin_ttl so the granted soft pin will not quickly expire
|
|
const uint64_t kv_soft_pin_ttl = 10000;
|
|
// set allow_evict_soft_pinned_objects to false to disable eviction of soft
|
|
// pinned objects
|
|
const bool allow_evict_soft_pinned_objects = false;
|
|
auto service_config = MasterServiceConfig::builder()
|
|
.set_default_kv_lease_ttl(kv_lease_ttl)
|
|
.set_default_kv_soft_pin_ttl(kv_soft_pin_ttl)
|
|
.set_allow_evict_soft_pinned_objects(
|
|
allow_evict_soft_pinned_objects)
|
|
.build();
|
|
std::unique_ptr<MasterService> service_(new MasterService(service_config));
|
|
|
|
// Mount segment and put an object
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t segment_size = 1024 * 1024 * 16;
|
|
constexpr size_t value_size = 1024 * 1024;
|
|
std::string segment_name = "test_segment";
|
|
Segment segment(generate_uuid(), segment_name, buffer, segment_size);
|
|
UUID client_id = generate_uuid();
|
|
ASSERT_TRUE(service_->MountSegment(segment, client_id).has_value());
|
|
|
|
// Put objects more than the segment can hold
|
|
std::vector<std::string> success_keys;
|
|
for (int i = 0; i < 16 + 50; ++i) {
|
|
std::string key = "test_key" + std::to_string(i);
|
|
std::vector<uint64_t> slice_lengths = {value_size};
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
config.with_soft_pin = true;
|
|
if (service_->PutStart(key, slice_lengths, config).has_value()) {
|
|
ASSERT_TRUE(service_->PutEnd(key, ReplicaType::MEMORY).has_value());
|
|
success_keys.push_back(key);
|
|
} else {
|
|
// wait for gc thread to work
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
}
|
|
}
|
|
ASSERT_LE(success_keys.size(), 17);
|
|
// All soft pinned objects should be accessible
|
|
for (const auto& key : success_keys) {
|
|
ASSERT_TRUE(service_->GetReplicaList(key).has_value());
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(kv_lease_ttl));
|
|
service_->RemoveAll();
|
|
}
|
|
|
|
TEST_F(MasterServiceTest, BatchExistKeyTest) {
|
|
std::unique_ptr<MasterService> service_(new MasterService());
|
|
|
|
// Mount a segment
|
|
constexpr size_t buffer = 0x300000000;
|
|
constexpr size_t size = 1024 * 1024 * 128;
|
|
constexpr size_t value_size = 1024;
|
|
std::string segment_name = "test_segment";
|
|
Segment segment(generate_uuid(), segment_name, buffer, size);
|
|
UUID client_id = generate_uuid();
|
|
auto mount_result = service_->MountSegment(segment, client_id);
|
|
ASSERT_TRUE(mount_result.has_value());
|
|
|
|
int test_object_num = 10;
|
|
std::vector<std::string> test_keys;
|
|
for (int i = 0; i < test_object_num; ++i) {
|
|
test_keys.push_back("test_key" + std::to_string(i));
|
|
ReplicateConfig config;
|
|
config.replica_num = 1;
|
|
std::vector<uint64_t> slice_lengths = {value_size};
|
|
auto put_start_result =
|
|
service_->PutStart(test_keys[i], slice_lengths, config);
|
|
ASSERT_TRUE(put_start_result.has_value());
|
|
auto put_end_result =
|
|
service_->PutEnd(test_keys[i], ReplicaType::MEMORY);
|
|
ASSERT_TRUE(put_end_result.has_value());
|
|
}
|
|
|
|
// Test individual ExistKey calls to verify the underlying functionality
|
|
for (int i = 0; i < test_object_num; ++i) {
|
|
auto exist_result = service_->ExistKey(test_keys[i]);
|
|
EXPECT_TRUE(exist_result.value());
|
|
}
|
|
|
|
// Tets batch
|
|
test_keys.push_back("non_existent_key");
|
|
auto exist_resp = service_->BatchExistKey(test_keys);
|
|
for (int i = 0; i < test_object_num; ++i) {
|
|
ASSERT_TRUE(exist_resp[i].value());
|
|
}
|
|
ASSERT_FALSE(exist_resp[test_object_num].value());
|
|
}
|
|
|
|
} // namespace mooncake::test
|
|
|
|
int main(int argc, char** argv) {
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|