#include "cluster/Backend.hpp" #include "cluster/CacheLoaderDecider.hpp" #include "cluster/ClioNode.hpp" #include "util/MockLedgerCacheLoadingState.hpp" #include #include #include #include #include #include #include #include #include #include #include using namespace cluster; enum class CacheLoaderExpectedAction { AllowLoading, NoAction }; struct CacheLoaderNodeParams { uint8_t uuidValue; bool cacheIsFull = false; bool cacheIsCurrentlyLoading = false; }; struct CacheLoaderDeciderTestParams { std::string testName; uint8_t selfUuidValue; std::vector nodes; CacheLoaderExpectedAction expectedAction; bool useEmptyClusterData = false; }; struct CacheLoaderDeciderTest : testing::TestWithParam { ~CacheLoaderDeciderTest() override { ctx.stop(); ctx.join(); } boost::asio::thread_pool ctx{1}; std::unique_ptr cacheLoadingState = std::make_unique(); MockLedgerCacheLoadingState& cacheLoadingStateRef = *cacheLoadingState; static ClioNode makeNode(boost::uuids::uuid const& uuid, bool cacheIsFull, bool cacheIsCurrentlyLoading) { return ClioNode{ .uuid = std::make_shared(uuid), .updateTime = std::chrono::system_clock::now(), .dbRole = ClioNode::DbRole::NotWriter, .etlStarted = true, .cacheIsFull = cacheIsFull, .cacheIsCurrentlyLoading = cacheIsCurrentlyLoading, }; } static boost::uuids::uuid makeUuid(uint8_t value) { boost::uuids::uuid uuid{}; std::ranges::fill(uuid, value); return uuid; } }; TEST_P(CacheLoaderDeciderTest, CacheLoaderSelection) { auto const& params = GetParam(); auto const selfUuid = makeUuid(params.selfUuidValue); CacheLoaderDecider decider{ctx, std::move(cacheLoadingState)}; auto clonedState = std::make_unique(); switch (params.expectedAction) { case CacheLoaderExpectedAction::AllowLoading: EXPECT_CALL(*clonedState, allowLoading()); EXPECT_CALL(cacheLoadingStateRef, clone()) .WillOnce(testing::Return(testing::ByMove(std::move(clonedState)))); break; case CacheLoaderExpectedAction::NoAction: if (not params.useEmptyClusterData) { EXPECT_CALL(cacheLoadingStateRef, clone()) .WillOnce(testing::Return(testing::ByMove(std::move(clonedState)))); } break; } std::shared_ptr clusterData; ClioNode::CUuid selfIdPtr; if (params.useEmptyClusterData) { clusterData = std::make_shared( std::unexpected(std::string("Communication failed")) ); selfIdPtr = std::make_shared(selfUuid); } else { std::vector nodes; nodes.reserve(params.nodes.size()); for (auto const& nodeParam : params.nodes) { auto node = makeNode( makeUuid(nodeParam.uuidValue), nodeParam.cacheIsFull, nodeParam.cacheIsCurrentlyLoading ); if (nodeParam.uuidValue == params.selfUuidValue) { selfIdPtr = node.uuid; } nodes.push_back(std::move(node)); } clusterData = std::make_shared(std::move(nodes)); } decider.onNewState(selfIdPtr, clusterData); ctx.join(); } INSTANTIATE_TEST_SUITE_P( CacheLoaderDeciderTests, CacheLoaderDeciderTest, testing::Values( CacheLoaderDeciderTestParams{ .testName = "SelfCacheIsFullNoAction", .selfUuidValue = 0x01, .nodes = {{.uuidValue = 0x01, .cacheIsFull = true}, {.uuidValue = 0x02, .cacheIsFull = false}}, .expectedAction = CacheLoaderExpectedAction::NoAction }, CacheLoaderDeciderTestParams{ .testName = "SelfIsFirstNotFullByUuid_AllowLoading", .selfUuidValue = 0x01, .nodes = {{.uuidValue = 0x01, .cacheIsFull = false}, {.uuidValue = 0x02, .cacheIsFull = false}}, .expectedAction = CacheLoaderExpectedAction::AllowLoading }, CacheLoaderDeciderTestParams{ .testName = "OtherNodeIsFirstNotFullByUuid_NoAction", .selfUuidValue = 0x02, .nodes = {{.uuidValue = 0x01, .cacheIsFull = false}, {.uuidValue = 0x02, .cacheIsFull = false}}, .expectedAction = CacheLoaderExpectedAction::NoAction }, CacheLoaderDeciderTestParams{ .testName = "ShuffledNodes_SelfIsFirstNotFull_AllowLoading", .selfUuidValue = 0x02, .nodes = {{.uuidValue = 0x04, .cacheIsFull = false}, {.uuidValue = 0x02, .cacheIsFull = false}, {.uuidValue = 0x03, .cacheIsFull = true}}, .expectedAction = CacheLoaderExpectedAction::AllowLoading }, CacheLoaderDeciderTestParams{ .testName = "FullNodesExcludedFromElection", .selfUuidValue = 0x03, .nodes = {{.uuidValue = 0x01, .cacheIsFull = true}, {.uuidValue = 0x02, .cacheIsFull = true}, {.uuidValue = 0x03, .cacheIsFull = false}}, .expectedAction = CacheLoaderExpectedAction::AllowLoading }, CacheLoaderDeciderTestParams{ .testName = "SomeoneIsCurrentlyLoading_NoAction", .selfUuidValue = 0x01, .nodes = {{.uuidValue = 0x01, .cacheIsFull = false, .cacheIsCurrentlyLoading = false}, {.uuidValue = 0x02, .cacheIsFull = false, .cacheIsCurrentlyLoading = true}}, .expectedAction = CacheLoaderExpectedAction::NoAction }, CacheLoaderDeciderTestParams{ .testName = "SelfIsCurrentlyLoading_NoAction", .selfUuidValue = 0x01, .nodes = {{.uuidValue = 0x01, .cacheIsFull = false, .cacheIsCurrentlyLoading = true}, {.uuidValue = 0x02, .cacheIsFull = false, .cacheIsCurrentlyLoading = false}}, .expectedAction = CacheLoaderExpectedAction::NoAction }, CacheLoaderDeciderTestParams{ .testName = "SingleNodeCluster_SelfAllowLoading", .selfUuidValue = 0x01, .nodes = {{.uuidValue = 0x01, .cacheIsFull = false}}, .expectedAction = CacheLoaderExpectedAction::AllowLoading }, CacheLoaderDeciderTestParams{ .testName = "EmptyClusterData_NoAction", .selfUuidValue = 0x01, .nodes = {}, .expectedAction = CacheLoaderExpectedAction::NoAction, .useEmptyClusterData = true } ), [](testing::TestParamInfo const& info) { return info.param.testName; } );