Mooncake/mooncake-store/tests/segment_test.cpp

443 lines
16 KiB
C++

#include "segment.h"
#include <glog/logging.h>
#include <gtest/gtest.h>
#include <boost/functional/hash.hpp>
namespace mooncake {
// Test fixture for Segment tests
class SegmentTest : public ::testing::Test {
protected:
void SetUp() override {
// Initialize glog for logging
google::InitGoogleLogging("EvictionStrategyTest");
FLAGS_logtostderr = 1; // Output logs to stderr
}
void TearDown() override {
// Cleanup glog
google::ShutdownGoogleLogging();
}
void ValidateMountedSegments(const SegmentManager& segment_manager,
const std::vector<Segment>& segments,
const std::vector<UUID>& client_ids) {
// validate client_segments_ and mounted_segments_
size_t total_num = 0;
for (const auto& it : segment_manager.client_segments_) {
total_num += it.second.size();
}
ASSERT_EQ(total_num, segments.size());
ASSERT_EQ(segment_manager.mounted_segments_.size(), segments.size());
for (size_t i = 0; i < client_ids.size(); i++) {
auto client_it =
segment_manager.client_segments_.find(client_ids[i]);
ASSERT_NE(client_it, segment_manager.client_segments_.end());
auto segment_it =
std::find(client_it->second.begin(), client_it->second.end(),
segments[i].id);
ASSERT_NE(segment_it, client_it->second.end());
ASSERT_EQ(*segment_it, segments[i].id);
ASSERT_NE(segment_manager.mounted_segments_.find(segments[i].id),
segment_manager.mounted_segments_.end());
MountedSegment seg =
segment_manager.mounted_segments_.at(segments[i].id);
ASSERT_EQ(seg.segment.id, segments[i].id);
ASSERT_EQ(seg.segment.name, segments[i].name);
ASSERT_EQ(seg.segment.size, segments[i].size);
ASSERT_EQ(seg.segment.base, segments[i].base);
ASSERT_EQ(seg.status, SegmentStatus::OK);
ASSERT_EQ(seg.buf_allocator->getSegmentName(), segments[i].name);
ASSERT_EQ(seg.buf_allocator->capacity(), segments[i].size);
}
// validate allocators and allocators_by_name
total_num = 0;
for (const auto& it : segment_manager.allocators_by_name_) {
total_num += it.second.size();
}
ASSERT_EQ(total_num, segments.size());
ASSERT_EQ(segment_manager.allocators_.size(), segments.size());
for (const auto& segment : segments) {
MountedSegment mounted_segment =
segment_manager.mounted_segments_.at(segment.id);
auto allocator = mounted_segment.buf_allocator;
// validate allocators_
ASSERT_NE(std::find(segment_manager.allocators_.begin(),
segment_manager.allocators_.end(),
mounted_segment.buf_allocator),
segment_manager.allocators_.end());
// validate allocators_by_name
auto map_it =
segment_manager.allocators_by_name_.find(segment.name);
ASSERT_NE(map_it, segment_manager.allocators_by_name_.end());
auto name_allocator_it = map_it->second.begin();
for (; name_allocator_it != map_it->second.end();
name_allocator_it++) {
if (*name_allocator_it == allocator) {
break;
}
}
ASSERT_NE(name_allocator_it, map_it->second.end());
}
}
void ValidateMountedSegment(const SegmentManager& segment_manager,
const Segment segment, const UUID& client_id) {
std::vector<Segment> segments;
segments.push_back(segment);
std::vector<UUID> client_ids;
client_ids.push_back(client_id);
ValidateMountedSegments(segment_manager, segments, client_ids);
}
};
// Mount Segment Operations Tests:
TEST_F(SegmentTest, MountSegmentSuccess) {
SegmentManager segment_manager;
// Create a valid segment and client ID
Segment segment;
segment.id = generate_uuid();
segment.name = "test_segment";
segment.size = 1024 * 1024 * 16;
segment.base = 0x100000000;
UUID client_id = generate_uuid();
// Get segment access and attempt to mount
auto segment_access = segment_manager.getSegmentAccess();
ASSERT_EQ(segment_access.MountSegment(segment, client_id), ErrorCode::OK);
// Verify segment is properly mounted
ValidateMountedSegment(segment_manager, segment, client_id);
}
// MountSegmentDuplicate Tests:
// 1. MountSegment with the same segment id. The second mount operation return
// SEGMENT_ALREADY_EXISTS.
// 2. MountSegment with different segment id and the same segment name should be
// considered as different segments. Validate the status of SegmentManager use
// ValidateMountedSegments function.
TEST_F(SegmentTest, MountSegmentDuplicate) {
SegmentManager segment_manager;
// Create a valid segment and client ID
Segment segment;
segment.id = generate_uuid();
segment.name = "test_segment";
segment.size = 1024 * 1024 * 16;
segment.base = 0x100000000;
UUID client_id = generate_uuid();
// Get segment access and mount first time
auto segment_access = segment_manager.getSegmentAccess();
ASSERT_EQ(segment_access.MountSegment(segment, client_id), ErrorCode::OK);
// Verify first mount
ValidateMountedSegment(segment_manager, segment, client_id);
// Test duplicate mount - mount the same segment again
ASSERT_EQ(segment_access.MountSegment(segment, client_id),
ErrorCode::SEGMENT_ALREADY_EXISTS);
// Verify state remains the same after duplicate mount
ValidateMountedSegment(segment_manager, segment, client_id);
// Create a new segment with same name but different ID
Segment segment2;
segment2.id = generate_uuid(); // Different ID
segment2.name = segment.name; // Same name
segment2.size = segment.size * 2;
segment2.base = segment.base + segment.size;
// Mount the second segment
ASSERT_EQ(segment_access.MountSegment(segment2, client_id), ErrorCode::OK);
// Verify both segments are mounted correctly
std::vector<Segment> segments = {segment, segment2};
std::vector<UUID> client_ids = {client_id, client_id};
ValidateMountedSegments(segment_manager, segments, client_ids);
}
// UnmountSegmentSuccess:
// 1. Mount a segment and then unmount it. Unmount operation return success.
// 2. Use ValidateMountedSegments function to validate the status of
// SegmentManager.
TEST_F(SegmentTest, UnmountSegmentSuccess) {
SegmentManager segment_manager;
// Create and mount a segment
Segment segment;
segment.id = generate_uuid();
segment.name = "test_segment";
segment.size = 1024 * 1024 * 16;
segment.base = 0x100000000;
UUID client_id = generate_uuid();
// Get segment access and mount
auto segment_access = segment_manager.getSegmentAccess();
ASSERT_EQ(segment_access.MountSegment(segment, client_id), ErrorCode::OK);
// Verify segment is mounted correctly
ValidateMountedSegment(segment_manager, segment, client_id);
// Prepare unmount
size_t metrics_dec_capacity = 0;
ASSERT_EQ(
segment_access.PrepareUnmountSegment(segment.id, metrics_dec_capacity),
ErrorCode::OK);
ASSERT_EQ(metrics_dec_capacity, segment.size);
// Commit unmount
ASSERT_EQ(segment_access.CommitUnmountSegment(segment.id, client_id,
metrics_dec_capacity),
ErrorCode::OK);
// Verify segment is unmounted correctly
std::vector<Segment> empty_segment_vec;
std::vector<UUID> empty_client_ids_vec;
ValidateMountedSegments(segment_manager, empty_segment_vec,
empty_client_ids_vec);
}
// UnmountSegmentDuplicate:
// 1. Mount a segment and then unmount it twice. The second unmount operation
// returns SEGMENT_NOT_FOUND.
// 2. Only use ValidateMountedSegments function to validate the status of
// SegmentManager. Do not use other interfaces for validation.
TEST_F(SegmentTest, UnmountSegmentDuplicate) {
SegmentManager segment_manager;
// Create and mount a segment
Segment segment;
segment.id = generate_uuid();
segment.name = "test_segment";
segment.size = 1024 * 1024 * 16;
segment.base = 0x100000000;
UUID client_id = generate_uuid();
// Get segment access and mount
auto segment_access = segment_manager.getSegmentAccess();
ASSERT_EQ(segment_access.MountSegment(segment, client_id), ErrorCode::OK);
// Verify initial mounted state
ValidateMountedSegment(segment_manager, segment, client_id);
// First unmount
size_t metrics_dec_capacity = 0;
ASSERT_EQ(
segment_access.PrepareUnmountSegment(segment.id, metrics_dec_capacity),
ErrorCode::OK);
ASSERT_EQ(segment_access.CommitUnmountSegment(segment.id, client_id,
metrics_dec_capacity),
ErrorCode::OK);
// Verify segment is unmounted after first unmount
std::vector<Segment> empty_segment_vec;
std::vector<UUID> empty_client_ids_vec;
ValidateMountedSegments(segment_manager, empty_segment_vec,
empty_client_ids_vec);
// Second unmount attempt
metrics_dec_capacity = 0;
ASSERT_EQ(
segment_access.PrepareUnmountSegment(segment.id, metrics_dec_capacity),
ErrorCode::SEGMENT_NOT_FOUND);
// Verify segment remains unmounted after second unmount
ValidateMountedSegments(segment_manager, empty_segment_vec,
empty_client_ids_vec);
}
// ReMountSegmentSuccess:
// 1. Mount a segment A;
// 2. Remount two segments: A and B where A is already mounted and B is a new
// segment. The remount operation return success.
// 3. Only use ValidateMountedSegments function to validate the status of
// SegmentManager. Do not use other interfaces for validation.
TEST_F(SegmentTest, ReMountSegmentSuccess) {
SegmentManager segment_manager;
// Create and mount segment A
Segment segment_a;
segment_a.id = generate_uuid();
segment_a.name = "test_segment_a";
segment_a.size = 1024 * 1024 * 16;
segment_a.base = 0x100000000;
UUID client_id = generate_uuid();
// Get segment access and mount segment A
auto segment_access = segment_manager.getSegmentAccess();
ASSERT_EQ(segment_access.MountSegment(segment_a, client_id), ErrorCode::OK);
// Verify segment A is mounted correctly
ValidateMountedSegment(segment_manager, segment_a, client_id);
// Create segment B
Segment segment_b;
segment_b.id = generate_uuid();
segment_b.name = "test_segment_b";
segment_b.size = 1024 * 1024 * 32;
segment_b.base = 0x200000000;
// Remount both segments A and B
std::vector<Segment> segments_to_remount = {segment_a, segment_b};
ASSERT_EQ(segment_access.ReMountSegment(segments_to_remount, client_id),
ErrorCode::OK);
// Verify both segments are mounted correctly
std::vector<UUID> client_ids = {client_id, client_id};
ValidateMountedSegments(segment_manager, segments_to_remount, client_ids);
}
// ReMountUnmountingSegment:
// 1. Mount a segment A;
// 2. PrepareUnmount segment A;
// 3. Remount segment A. The remount operation return
// UNAVAILABLE_IN_CURRENT_STATUS.
// 4. CommitUnmount segment A;
// 5. Only use ValidateMountedSegments function to validate the status of
// SegmentManager. Do not use other interfaces for validation.
TEST_F(SegmentTest, ReMountUnmountingSegment) {
SegmentManager segment_manager;
// Create and mount segment A
Segment segment_a;
segment_a.id = generate_uuid();
segment_a.name = "test_segment_a";
segment_a.size = 1024 * 1024 * 16;
segment_a.base = 0x100000000;
UUID client_id = generate_uuid();
// Get segment access and mount segment A
auto segment_access = segment_manager.getSegmentAccess();
ASSERT_EQ(segment_access.MountSegment(segment_a, client_id), ErrorCode::OK);
// Verify segment A is mounted correctly
ValidateMountedSegment(segment_manager, segment_a, client_id);
// Prepare unmount segment A
size_t metrics_dec_capacity = 0;
ASSERT_EQ(segment_access.PrepareUnmountSegment(segment_a.id,
metrics_dec_capacity),
ErrorCode::OK);
// Attempt to remount segment A while it's in UNMOUNTING state
std::vector<Segment> segments_to_remount = {segment_a};
ASSERT_EQ(segment_access.ReMountSegment(segments_to_remount, client_id),
ErrorCode::UNAVAILABLE_IN_CURRENT_STATUS);
// Complete the unmount process
ASSERT_EQ(segment_access.CommitUnmountSegment(segment_a.id, client_id,
metrics_dec_capacity),
ErrorCode::OK);
// Verify segment is completely unmounted
std::vector<Segment> empty_segment_vec;
std::vector<UUID> empty_client_ids_vec;
ValidateMountedSegments(segment_manager, empty_segment_vec,
empty_client_ids_vec);
}
// QuerySegments:
// 1. Create and mount 10 different segments with different names and different
// client ids;
// 2. Test GetClientSegments, verify the return value is correct.
// 3. Test GetAllSegments, verify the return value is correct.
// 4. Test QuerySegments, verify the return value is correct.
TEST_F(SegmentTest, QuerySegments) {
SegmentManager segment_manager;
auto segment_access = segment_manager.getSegmentAccess();
// Create 10 different segments with different names and client IDs
std::vector<Segment> segments;
std::vector<UUID> client_ids;
std::unordered_map<UUID, UUID, boost::hash<UUID>> expected_client_segments;
for (int i = 0; i < 10; i++) {
// Create segment
Segment segment;
segment.id = generate_uuid();
segment.name = "test_segment_" + std::to_string(i);
segment.size = 1024 * 1024 * 16;
segment.base =
0x100000000 + (i * 0x100000000); // Different base addresses
// Create client ID
UUID client_id = generate_uuid();
// Mount segment
ASSERT_EQ(segment_access.MountSegment(segment, client_id),
ErrorCode::OK);
// Store for verification
segments.push_back(segment);
client_ids.push_back(client_id);
expected_client_segments[client_id] = segment.id;
}
// Verify all segments are mounted correctly
ValidateMountedSegments(segment_manager, segments, client_ids);
// Test GetClientSegments for each client
for (size_t i = 0; i < client_ids.size(); i++) {
std::vector<Segment> client_segments;
ASSERT_EQ(
segment_access.GetClientSegments(client_ids[i], client_segments),
ErrorCode::OK);
// Verify correct number of segments
ASSERT_EQ(client_segments.size(), 1);
// Verify all expected segments are present
ASSERT_EQ(client_segments[0].id,
expected_client_segments[client_ids[i]]);
}
// Test GetAllSegments
std::vector<std::string> all_segments;
ASSERT_EQ(segment_access.GetAllSegments(all_segments), ErrorCode::OK);
// Verify correct number of segments
ASSERT_EQ(all_segments.size(), segments.size());
// Verify all segment names are present
for (const auto& segment : segments) {
ASSERT_NE(
std::find(all_segments.begin(), all_segments.end(), segment.name),
all_segments.end());
}
// Test QuerySegments for each segment
for (const auto& segment : segments) {
size_t used = 0, capacity = 0;
ASSERT_EQ(segment_access.QuerySegments(segment.name, used, capacity),
ErrorCode::OK);
// Verify capacity matches segment size
ASSERT_EQ(capacity, segment.size);
// Verify used space is 0 for newly mounted segments
ASSERT_EQ(used, 0);
}
// Test QuerySegments for non-existent segment
size_t used = 0, capacity = 0;
ASSERT_EQ(
segment_access.QuerySegments("non_existent_segment", used, capacity),
ErrorCode::SEGMENT_NOT_FOUND);
ASSERT_EQ(used, 0);
ASSERT_EQ(capacity, 0);
}
} // namespace mooncake