mirror of
https://github.com/XTXMarkets/ternfs.git
synced 2026-04-30 22:59:45 -05:00
registry: add unit tests
This commit is contained in:
committed by
Miroslav Crnic
parent
a9b0baa60c
commit
239b623e56
@@ -66,6 +66,7 @@ public:
|
||||
BincodeBytes(): _data(0) {}
|
||||
BincodeBytes(const char* data, size_t length): _data(0) { copy(data, length); }
|
||||
BincodeBytes(const rocksdb::Slice& slice): BincodeBytes(slice.data(), slice.size()) {}
|
||||
BincodeBytes(const std::string& str): BincodeBytes(str.data(), str.size()) {}
|
||||
BincodeBytes(const BincodeBytesRef& ref): BincodeBytes(ref.data(), ref.size()) {}
|
||||
BincodeBytes(const char* str): BincodeBytes(str, strlen(str)) {}
|
||||
|
||||
@@ -195,10 +196,20 @@ struct BincodeFixedBytes {
|
||||
static constexpr uint16_t STATIC_SIZE = SZ;
|
||||
|
||||
BincodeFixedBytes() { clear(); }
|
||||
BincodeFixedBytes(const char* data, size_t length) { copy(data, length); }
|
||||
BincodeFixedBytes(const rocksdb::Slice& slice): BincodeFixedBytes(slice.data(), slice.size()) {}
|
||||
BincodeFixedBytes(const BincodeBytesRef& ref): BincodeFixedBytes(ref.data(), ref.size()) {}
|
||||
BincodeFixedBytes(const char* str): BincodeFixedBytes(str, strlen(str)) {}
|
||||
BincodeFixedBytes(const std::array<uint8_t, SZ>& data_): data(data_) {}
|
||||
|
||||
void clear() { memset(data.data(), 0, SZ); }
|
||||
|
||||
void copy(const char* data_, size_t length) {
|
||||
clear();
|
||||
ALWAYS_ASSERT(length < SZ);
|
||||
memcpy(data.data(), data_, length);
|
||||
}
|
||||
|
||||
constexpr size_t packedSize() const {
|
||||
return SZ;
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ void RegistryDB::processLogEntries(std::vector<LogsDBLogEntry>& logEntries, std:
|
||||
switch (res.kind) {
|
||||
case RegistryMessageKind::CREATE_LOCATION: {
|
||||
auto& req = reqContainer.getCreateLocation();
|
||||
StaticValue<ShardInfoKey> key;
|
||||
StaticValue<LocationInfoKey> key;
|
||||
std::string value;
|
||||
key().setLocationId(req.id);
|
||||
auto status = _db->Get({}, _locationsCf, key.toSlice(), &value);
|
||||
@@ -119,17 +119,19 @@ void RegistryDB::processLogEntries(std::vector<LogsDBLogEntry>& logEntries, std:
|
||||
newLocation.id = req.id;
|
||||
newLocation.name = req.name;
|
||||
writeLocationInfo(writeBatch, _locationsCf, newLocation);
|
||||
initializeShardsForLocation(writeBatch, _shardsCf, req.id);
|
||||
initializeCdcForLocation(writeBatch, _cdcCf, req.id);
|
||||
toReload.locations = toReload.registry = toReload.shards = toReload.cdc = true;
|
||||
break;
|
||||
}
|
||||
case RegistryMessageKind::RENAME_LOCATION: {
|
||||
auto& req = reqContainer.getRenameLocation();
|
||||
StaticValue<ShardInfoKey> key;
|
||||
StaticValue<LocationInfoKey> key;
|
||||
std::string value;
|
||||
key().setLocationId(req.id);
|
||||
auto status = _db->Get({}, _locationsCf, key.toSlice(), &value);
|
||||
if (status == rocksdb::Status::NotFound()) {
|
||||
LOG_ERROR(_env, "unknown location in req %s", req);
|
||||
LOG_DEBUG(_env, "unknown location in req %s", req);
|
||||
res.err = TernError::LOCATION_NOT_FOUND;
|
||||
break;
|
||||
}
|
||||
@@ -150,7 +152,7 @@ void RegistryDB::processLogEntries(std::vector<LogsDBLogEntry>& logEntries, std:
|
||||
key().setShardId(req.shrid.shardId().u8);
|
||||
auto status = _db->Get({}, _shardsCf, key.toSlice(), &value);
|
||||
if (status == rocksdb::Status::NotFound()) {
|
||||
LOG_ERROR(_env, "unknown shard replica in req %s", req);
|
||||
LOG_DEBUG(_env, "unknown shard replica in req %s", req);
|
||||
res.err = TernError::INVALID_REPLICA;
|
||||
break;
|
||||
}
|
||||
@@ -184,7 +186,7 @@ void RegistryDB::processLogEntries(std::vector<LogsDBLogEntry>& logEntries, std:
|
||||
key().setReplicaId(req.replica);
|
||||
auto status = _db->Get({}, _cdcCf, key.toSlice(), &value);
|
||||
if (status == rocksdb::Status::NotFound()) {
|
||||
LOG_ERROR(_env, "unknown cdc replica in req %s", req);
|
||||
LOG_DEBUG(_env, "unknown cdc replica in req %s", req);
|
||||
res.err = TernError::INVALID_REPLICA;
|
||||
break;
|
||||
}
|
||||
@@ -220,7 +222,7 @@ void RegistryDB::processLogEntries(std::vector<LogsDBLogEntry>& logEntries, std:
|
||||
std::string value;
|
||||
auto status = _db->Get({}, _blockServicesCf, key.toSlice(), &value);
|
||||
if (status == rocksdb::Status::NotFound()) {
|
||||
LOG_ERROR(_env, "unknown block service in req %s", req);
|
||||
LOG_DEBUG(_env, "unknown block service in req %s", req);
|
||||
res.err = TernError::BLOCK_SERVICE_NOT_FOUND;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ public:
|
||||
|
||||
void processLogEntries(std::vector<LogsDBLogEntry>& logEntries, std::vector<RegistryDBWriteResult>& writeResults);
|
||||
|
||||
void flush(bool sync = true) { _db->FlushWAL(sync); }
|
||||
|
||||
private:
|
||||
void _initDb();
|
||||
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
include_directories(${ternfs_SOURCE_DIR}/core ${ternfs_SOURCE_DIR}/shard)
|
||||
include_directories(${ternfs_SOURCE_DIR}/core ${ternfs_SOURCE_DIR}/shard ${ternfs_SOURCE_DIR}/registry)
|
||||
|
||||
add_executable(tests tests.cpp doctest.h)
|
||||
target_link_libraries(tests PRIVATE core shard cdc)
|
||||
|
||||
add_executable(logsdbtests logsdbtests.cpp doctest.h)
|
||||
target_link_libraries(logsdbtests PRIVATE core)
|
||||
|
||||
add_executable(registrydbtests registrydbtests.cpp doctest.h)
|
||||
target_link_libraries(registrydbtests PRIVATE core registry)
|
||||
|
||||
@@ -0,0 +1,796 @@
|
||||
// Copyright 2025 XTX Markets Technologies Limited
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <iostream>
|
||||
#include <ostream>
|
||||
#include <resolv.h>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "Bincode.hpp"
|
||||
#include "Msgs.hpp"
|
||||
#include "MsgsGen.hpp"
|
||||
#include "Time.hpp"
|
||||
#include "utils/TempRegistryDB.hpp"
|
||||
|
||||
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
||||
#include "doctest.h"
|
||||
|
||||
REGISTER_EXCEPTION_TRANSLATOR(AbstractException& ex) {
|
||||
std::stringstream ss;
|
||||
ss << std::endl << ex.what() << std::endl;
|
||||
return doctest::String(ss.str().c_str());
|
||||
}
|
||||
|
||||
TEST_CASE("CreateReopen") {
|
||||
RegistryOptions options;
|
||||
TempRegistryDB db(LogLevel::LOG_ERROR);
|
||||
|
||||
db.open(options);
|
||||
db.close();
|
||||
db.open(options);
|
||||
}
|
||||
|
||||
TEST_CASE("Initialization") {
|
||||
RegistryOptions options;
|
||||
TempRegistryDB db(LogLevel::LOG_ERROR);
|
||||
|
||||
db.open(options);
|
||||
|
||||
std::vector<LocationInfo> locations;
|
||||
db->locations(locations);
|
||||
REQUIRE(locations.size() == 1);
|
||||
CHECK(locations[0].id == DEFAULT_LOCATION);
|
||||
CHECK(locations[0].name == "default");
|
||||
|
||||
std::vector<FullRegistryInfo> registries;
|
||||
db->registries(registries);
|
||||
CHECK(registries.size() == LogsDB::REPLICA_COUNT);
|
||||
|
||||
std::vector<FullShardInfo> shards;
|
||||
db->shards(shards);
|
||||
CHECK(!shards.empty());
|
||||
CHECK(shards.size() == LogsDB::REPLICA_COUNT * ShardId::SHARD_COUNT);
|
||||
|
||||
std::vector<CdcInfo> cdcs;
|
||||
db->cdcs(cdcs);
|
||||
CHECK(!cdcs.empty());
|
||||
CHECK(cdcs.size() == LogsDB::REPLICA_COUNT);
|
||||
}
|
||||
|
||||
TEST_CASE("CreateLocation") {
|
||||
RegistryOptions options;
|
||||
TempRegistryDB db(LogLevel::LOG_ERROR);
|
||||
db.open(options);
|
||||
|
||||
std::vector<LogsDBLogEntry> logEntries;
|
||||
std::vector<RegistryDBWriteResult> writeResults;
|
||||
|
||||
auto& entry = logEntries.emplace_back();
|
||||
entry.idx = db->lastAppliedLogEntry() + 1;
|
||||
RegistryReqContainer reqContainer;
|
||||
auto& createReq = reqContainer.setCreateLocation();
|
||||
createReq.id = DEFAULT_LOCATION;
|
||||
createReq.name = "test-location";
|
||||
|
||||
SUBCASE("failureOnExists") {
|
||||
entry.value.resize(sizeof(uint64_t) + reqContainer.packedSize());
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::LOCATION_EXISTS);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::CREATE_LOCATION);
|
||||
}
|
||||
|
||||
SUBCASE("createSucceeded") {
|
||||
createReq.id = LocationId(42);
|
||||
|
||||
entry.value.resize(sizeof(uint64_t) + reqContainer.packedSize());
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::NO_ERROR);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::CREATE_LOCATION);
|
||||
|
||||
std::vector<LocationInfo> locations;
|
||||
db->locations(locations);
|
||||
REQUIRE(locations.size() == 2);
|
||||
|
||||
bool found = false;
|
||||
for (const auto& loc : locations) {
|
||||
if (loc.id == LocationId(42)) {
|
||||
CHECK(loc.name == "test-location");
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CHECK(found);
|
||||
std::vector<FullRegistryInfo> registries;
|
||||
db->registries(registries);
|
||||
CHECK(registries.size() == LogsDB::REPLICA_COUNT);
|
||||
|
||||
std::vector<FullShardInfo> shards;
|
||||
db->shards(shards);
|
||||
CHECK(!shards.empty());
|
||||
CHECK(shards.size() == LogsDB::REPLICA_COUNT * ShardId::SHARD_COUNT * locations.size());
|
||||
|
||||
std::vector<CdcInfo> cdcs;
|
||||
db->cdcs(cdcs);
|
||||
CHECK(!cdcs.empty());
|
||||
CHECK(cdcs.size() == LogsDB::REPLICA_COUNT * locations.size());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("RenameLocation") {
|
||||
RegistryOptions options;
|
||||
TempRegistryDB db(LogLevel::LOG_ERROR);
|
||||
db.open(options);
|
||||
|
||||
std::vector<LogsDBLogEntry> logEntries;
|
||||
std::vector<RegistryDBWriteResult> writeResults;
|
||||
|
||||
auto& entry = logEntries.emplace_back();
|
||||
entry.idx = db->lastAppliedLogEntry() + 1;
|
||||
|
||||
RegistryReqContainer reqContainer;
|
||||
auto& renameReq = reqContainer.setRenameLocation();
|
||||
renameReq.id = LocationId(10);
|
||||
renameReq.name = "new-name";
|
||||
SUBCASE("failNotFound") {
|
||||
entry.value.resize(sizeof(uint64_t) + reqContainer.packedSize());
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == logEntries.size());
|
||||
CHECK(writeResults[0].err == TernError::LOCATION_NOT_FOUND);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::RENAME_LOCATION);
|
||||
}
|
||||
|
||||
SUBCASE("renameSucceeded") {
|
||||
renameReq.id = DEFAULT_LOCATION;
|
||||
entry.value.resize(sizeof(uint64_t) + reqContainer.packedSize());
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == logEntries.size());
|
||||
CHECK(writeResults[0].err == TernError::NO_ERROR);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::RENAME_LOCATION);
|
||||
std::vector<LocationInfo> locations;
|
||||
db->locations(locations);
|
||||
|
||||
bool found = false;
|
||||
for (const auto& loc : locations) {
|
||||
if (loc.id == LocationId(DEFAULT_LOCATION)) {
|
||||
CHECK(loc.name == "new-name");
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CHECK(found);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("RegisterShard") {
|
||||
RegistryOptions options;
|
||||
TempRegistryDB db(LogLevel::LOG_ERROR);
|
||||
db.open(options);
|
||||
|
||||
std::vector<LogsDBLogEntry> logEntries;
|
||||
std::vector<RegistryDBWriteResult> writeResults;
|
||||
AddrsInfo addrsInfo;
|
||||
parseIpv4Addr("1.2.3.4:8080",addrsInfo.addrs[0]);
|
||||
parseIpv4Addr("5.6.7.8:8081",addrsInfo.addrs[1]);
|
||||
auto& entry = logEntries.emplace_back();
|
||||
entry.idx = db->lastAppliedLogEntry() + 1;
|
||||
|
||||
RegistryReqContainer reqContainer;
|
||||
auto& registerReq = reqContainer.setRegisterShard();
|
||||
registerReq.location = LocationId(10);
|
||||
registerReq.shrid = ShardReplicaId(ShardId(1), ReplicaId(0));
|
||||
registerReq.isLeader = true;
|
||||
registerReq.addrs = addrsInfo;
|
||||
SUBCASE("failInvalidReplica") {
|
||||
entry.value.resize(sizeof(uint64_t) + reqContainer.packedSize());
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::INVALID_REPLICA);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::REGISTER_SHARD);
|
||||
}
|
||||
SUBCASE("registerNew") {
|
||||
registerReq.location = DEFAULT_LOCATION;
|
||||
entry.value.resize(sizeof(uint64_t) + reqContainer.packedSize());
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::NO_ERROR);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::REGISTER_SHARD);
|
||||
|
||||
std::vector<FullShardInfo> shards;
|
||||
db->shards(shards);
|
||||
|
||||
bool found = false;
|
||||
for (const auto& shard : shards) {
|
||||
if (shard.locationId == LocationId(DEFAULT_LOCATION) &&
|
||||
shard.id.replicaId() == ReplicaId(0) &&
|
||||
shard.id.shardId() == ShardId(1)) {
|
||||
CHECK(shard.isLeader == true);
|
||||
CHECK(shard.addrs == addrsInfo);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CHECK(found);
|
||||
}
|
||||
SUBCASE("updateFailLeaderPreempted") {
|
||||
registerReq.location = DEFAULT_LOCATION;
|
||||
entry.value.resize(sizeof(uint64_t) + reqContainer.packedSize());
|
||||
{
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
}
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::NO_ERROR);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::REGISTER_SHARD);
|
||||
db.close();
|
||||
options.enforceStableLeader = true;
|
||||
db.open(options);
|
||||
registerReq.isLeader = false;
|
||||
++entry.idx;
|
||||
{
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
}
|
||||
writeResults.clear();
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::LEADER_PREEMPTED);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::REGISTER_SHARD);
|
||||
|
||||
db.close();
|
||||
options.enforceStableLeader = false;
|
||||
db.open(options);
|
||||
registerReq.isLeader = false;
|
||||
++entry.idx;
|
||||
{
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
}
|
||||
writeResults.clear();
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::NO_ERROR);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::REGISTER_SHARD);
|
||||
}
|
||||
SUBCASE("updateFailDifferentAddressInfo") {
|
||||
registerReq.location = DEFAULT_LOCATION;
|
||||
entry.value.resize(sizeof(uint64_t) + reqContainer.packedSize());
|
||||
{
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
}
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::NO_ERROR);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::REGISTER_SHARD);
|
||||
db.close();
|
||||
options.enforceStableIp = true;
|
||||
db.open(options);
|
||||
|
||||
parseIpv4Addr("1.2.3.4:8080",registerReq.addrs.addrs[0]);
|
||||
parseIpv4Addr("5.6.7.9:8081",registerReq.addrs.addrs[1]);
|
||||
++entry.idx;
|
||||
{
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
}
|
||||
writeResults.clear();
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::NO_ERROR);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::REGISTER_SHARD);
|
||||
|
||||
registerReq.addrs.addrs[0].clear();
|
||||
++entry.idx;
|
||||
{
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
}
|
||||
writeResults.clear();
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::NO_ERROR);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::REGISTER_SHARD);
|
||||
|
||||
parseIpv4Addr("5.4.3.2:8080",registerReq.addrs.addrs[0]);
|
||||
parseIpv4Addr("9.8.7.6:8081",registerReq.addrs.addrs[1]);
|
||||
++entry.idx;
|
||||
{
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
}
|
||||
writeResults.clear();
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::DIFFERENT_ADDRS_INFO);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::REGISTER_SHARD);
|
||||
|
||||
db.close();
|
||||
options.enforceStableIp = false;
|
||||
db.open(options);
|
||||
registerReq.isLeader = false;
|
||||
++entry.idx;
|
||||
{
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
}
|
||||
writeResults.clear();
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::NO_ERROR);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::REGISTER_SHARD);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("RegisterCDC") {
|
||||
RegistryOptions options;
|
||||
TempRegistryDB db(LogLevel::LOG_ERROR);
|
||||
db.open(options);
|
||||
|
||||
std::vector<LogsDBLogEntry> logEntries;
|
||||
std::vector<RegistryDBWriteResult> writeResults;
|
||||
AddrsInfo addrsInfo;
|
||||
parseIpv4Addr("1.2.3.4:8080",addrsInfo.addrs[0]);
|
||||
parseIpv4Addr("5.6.7.8:8081",addrsInfo.addrs[1]);
|
||||
auto& entry = logEntries.emplace_back();
|
||||
entry.idx = db->lastAppliedLogEntry() + 1;
|
||||
|
||||
RegistryReqContainer reqContainer;
|
||||
auto& registerReq = reqContainer.setRegisterCdc();
|
||||
registerReq.location = LocationId(10);
|
||||
registerReq.replica = ReplicaId(0);
|
||||
registerReq.isLeader = true;
|
||||
registerReq.addrs = addrsInfo;
|
||||
SUBCASE("failInvalidReplica") {
|
||||
entry.value.resize(sizeof(uint64_t) + reqContainer.packedSize());
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::INVALID_REPLICA);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::REGISTER_CDC);
|
||||
}
|
||||
SUBCASE("registerNew") {
|
||||
registerReq.location = DEFAULT_LOCATION;
|
||||
entry.value.resize(sizeof(uint64_t) + reqContainer.packedSize());
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::NO_ERROR);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::REGISTER_CDC);
|
||||
|
||||
std::vector<CdcInfo> cdcs;
|
||||
db->cdcs(cdcs);
|
||||
|
||||
bool found = false;
|
||||
for (const auto& cdc : cdcs) {
|
||||
if (cdc.locationId == LocationId(DEFAULT_LOCATION) &&
|
||||
cdc.replicaId == ReplicaId(0)) {
|
||||
CHECK(cdc.isLeader == true);
|
||||
CHECK(cdc.addrs == addrsInfo);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CHECK(found);
|
||||
}
|
||||
SUBCASE("updateFailLeaderPreempted") {
|
||||
registerReq.location = DEFAULT_LOCATION;
|
||||
entry.value.resize(sizeof(uint64_t) + reqContainer.packedSize());
|
||||
{
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
}
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::NO_ERROR);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::REGISTER_CDC);
|
||||
db.close();
|
||||
options.enforceStableLeader = true;
|
||||
db.open(options);
|
||||
registerReq.isLeader = false;
|
||||
++entry.idx;
|
||||
{
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
}
|
||||
writeResults.clear();
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::LEADER_PREEMPTED);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::REGISTER_CDC);
|
||||
|
||||
db.close();
|
||||
options.enforceStableLeader = false;
|
||||
db.open(options);
|
||||
registerReq.isLeader = false;
|
||||
++entry.idx;
|
||||
{
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
}
|
||||
writeResults.clear();
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::NO_ERROR);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::REGISTER_CDC);
|
||||
}
|
||||
SUBCASE("updateFailDifferentAddressInfo") {
|
||||
registerReq.location = DEFAULT_LOCATION;
|
||||
entry.value.resize(sizeof(uint64_t) + reqContainer.packedSize());
|
||||
{
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
}
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::NO_ERROR);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::REGISTER_CDC);
|
||||
db.close();
|
||||
options.enforceStableIp = true;
|
||||
db.open(options);
|
||||
|
||||
parseIpv4Addr("1.2.3.4:8080",registerReq.addrs.addrs[0]);
|
||||
parseIpv4Addr("5.6.7.9:8081",registerReq.addrs.addrs[1]);
|
||||
++entry.idx;
|
||||
{
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
}
|
||||
writeResults.clear();
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::NO_ERROR);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::REGISTER_CDC);
|
||||
|
||||
registerReq.addrs.addrs[0].clear();
|
||||
++entry.idx;
|
||||
{
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
}
|
||||
writeResults.clear();
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::NO_ERROR);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::REGISTER_CDC);
|
||||
|
||||
parseIpv4Addr("5.4.3.2:8080",registerReq.addrs.addrs[0]);
|
||||
parseIpv4Addr("9.8.7.6:8081",registerReq.addrs.addrs[1]);
|
||||
++entry.idx;
|
||||
{
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
}
|
||||
writeResults.clear();
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::DIFFERENT_ADDRS_INFO);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::REGISTER_CDC);
|
||||
|
||||
db.close();
|
||||
options.enforceStableIp = false;
|
||||
db.open(options);
|
||||
registerReq.isLeader = false;
|
||||
++entry.idx;
|
||||
{
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
}
|
||||
writeResults.clear();
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::NO_ERROR);
|
||||
CHECK(writeResults[0].kind == RegistryMessageKind::REGISTER_CDC);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("RegisterBlockServices") {
|
||||
RegistryOptions options;
|
||||
TempRegistryDB db(LogLevel::LOG_ERROR);
|
||||
db.open(options);
|
||||
|
||||
std::vector<LogsDBLogEntry> logEntries;
|
||||
std::vector<RegistryDBWriteResult> writeResults;
|
||||
|
||||
RegisterBlockServiceInfo info;
|
||||
auto now = ternNow();
|
||||
{
|
||||
auto& entry = logEntries.emplace_back();
|
||||
entry.idx = db->lastAppliedLogEntry() + 1;
|
||||
|
||||
RegistryReqContainer reqContainer;
|
||||
auto& registerReq = reqContainer.setRegisterBlockServices();
|
||||
info.id = BlockServiceId(100);
|
||||
info.locationId = DEFAULT_LOCATION;
|
||||
info.storageClass = 1;
|
||||
info.failureDomain.name = "test-fd";
|
||||
info.secretKey = "test-secret-key";
|
||||
info.capacityBytes = 1000000;
|
||||
info.availableBytes = 500000;
|
||||
info.blocks = 100;
|
||||
info.path = "/test/path";
|
||||
|
||||
parseIpv4Addr("1.2.3.4:8080",info.addrs.addrs[0]);
|
||||
parseIpv4Addr("5.6.7.8:8081",info.addrs.addrs[1]);
|
||||
|
||||
registerReq.blockServices.els.emplace_back(info);
|
||||
|
||||
entry.value.resize(sizeof(uint64_t) + reqContainer.packedSize());
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(now.ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
}
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::NO_ERROR);
|
||||
|
||||
SUBCASE("registerNew") {
|
||||
std::vector<FullBlockServiceInfo> services;
|
||||
db->blockServices(services);
|
||||
|
||||
REQUIRE(services.size() >= 1);
|
||||
bool found = false;
|
||||
for (const auto& service : services) {
|
||||
if (service.id == info.id) {
|
||||
CHECK(service.locationId == info.locationId);
|
||||
CHECK(service.storageClass == info.storageClass);
|
||||
CHECK(service.capacityBytes == info.capacityBytes);
|
||||
CHECK(service.availableBytes == info.availableBytes);
|
||||
CHECK(service.blocks == info.blocks);
|
||||
CHECK(service.path == info.path);
|
||||
CHECK(service.lastInfoChange == now);
|
||||
CHECK(service.lastSeen == now);
|
||||
CHECK(service.firstSeen == now);
|
||||
CHECK(service.secretKey == info.secretKey);
|
||||
CHECK(service.flags == info.flags);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CHECK(found);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("ShardBlockServices") {
|
||||
RegistryOptions options;
|
||||
TempRegistryDB db(LogLevel::LOG_ERROR);
|
||||
db.open(options);
|
||||
|
||||
std::vector<LogsDBLogEntry> logEntries;
|
||||
std::vector<RegistryDBWriteResult> writeResults;
|
||||
|
||||
{
|
||||
auto& entry = logEntries.emplace_back();
|
||||
entry.idx = db->lastAppliedLogEntry() + 1;
|
||||
|
||||
RegistryReqContainer reqContainer;
|
||||
auto& registerReq = reqContainer.setRegisterBlockServices();
|
||||
|
||||
auto& service = registerReq.blockServices.els.emplace_back();
|
||||
service.id = BlockServiceId(200);
|
||||
service.locationId = DEFAULT_LOCATION;
|
||||
service.storageClass = 1;
|
||||
service.failureDomain.name = "test-fd";
|
||||
service.secretKey = "test-key";
|
||||
service.capacityBytes = 1000000;
|
||||
service.availableBytes = 500000;
|
||||
service.blocks = 100;
|
||||
service.path = "/test/path";
|
||||
|
||||
entry.value.resize(sizeof(uint64_t) + reqContainer.packedSize());
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
}
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 1);
|
||||
CHECK(writeResults[0].err == TernError::NO_ERROR);
|
||||
|
||||
|
||||
std::vector<BlockServiceInfoShort> shardServices;
|
||||
db->shardBlockServices(ShardId(0), shardServices);
|
||||
|
||||
CHECK(!shardServices.empty());
|
||||
|
||||
bool found = false;
|
||||
for (const auto& service : shardServices) {
|
||||
if (service.id == 200) {
|
||||
CHECK(service.locationId == DEFAULT_LOCATION);
|
||||
CHECK(service.storageClass == 1);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CHECK(found);
|
||||
}
|
||||
|
||||
TEST_CASE("DecommissionBlockService") {
|
||||
RegistryOptions options;
|
||||
TempRegistryDB db(LogLevel::LOG_ERROR);
|
||||
db.open(options);
|
||||
|
||||
std::vector<LogsDBLogEntry> logEntries;
|
||||
std::vector<RegistryDBWriteResult> writeResults;
|
||||
|
||||
// First register a block service
|
||||
{
|
||||
auto& entry = logEntries.emplace_back();
|
||||
entry.idx = db->lastAppliedLogEntry() + 1;
|
||||
|
||||
RegistryReqContainer reqContainer;
|
||||
auto& registerReq = reqContainer.setRegisterBlockServices();
|
||||
|
||||
auto& service = registerReq.blockServices.els.emplace_back();
|
||||
service.id = BlockServiceId(300);
|
||||
service.locationId = DEFAULT_LOCATION;
|
||||
service.storageClass = 1;
|
||||
|
||||
service.failureDomain.name = "test-fd";
|
||||
service.secretKey = "test-key";
|
||||
service.capacityBytes = 1000000;
|
||||
service.availableBytes = 500000;
|
||||
service.blocks = 100;
|
||||
service.path = "/test/path";
|
||||
|
||||
entry.value.resize(sizeof(uint64_t) + reqContainer.packedSize());
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
}
|
||||
|
||||
{
|
||||
auto& entry = logEntries.emplace_back();
|
||||
entry.idx = db->lastAppliedLogEntry() + 2;
|
||||
|
||||
RegistryReqContainer reqContainer;
|
||||
auto& decommissionReq = reqContainer.setDecommissionBlockService();
|
||||
decommissionReq.id = BlockServiceId(300);
|
||||
|
||||
entry.value.resize(sizeof(uint64_t) + reqContainer.packedSize());
|
||||
BincodeBuf buf((char*)entry.value.data(), entry.value.size());
|
||||
buf.packScalar<uint64_t>(ternNow().ns);
|
||||
reqContainer.pack(buf);
|
||||
buf.ensureFinished();
|
||||
}
|
||||
|
||||
db->processLogEntries(logEntries, writeResults);
|
||||
|
||||
REQUIRE(writeResults.size() == 2);
|
||||
CHECK(writeResults[0].err == TernError::NO_ERROR);
|
||||
CHECK(writeResults[1].err == TernError::NO_ERROR);
|
||||
|
||||
std::vector<FullBlockServiceInfo> services;
|
||||
db->blockServices(services);
|
||||
|
||||
bool found = false;
|
||||
for (const auto& service : services) {
|
||||
if (service.id == BlockServiceId(300)) {
|
||||
CHECK(service.flags == BlockServiceFlags::DECOMMISSIONED);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CHECK(found);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright 2025 XTX Markets Technologies Limited
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
|
||||
#include "Env.hpp"
|
||||
#include "RegistryDB.hpp"
|
||||
#include "SharedRocksDB.hpp"
|
||||
|
||||
struct TempRegistryDB {
|
||||
std::string dbDir;
|
||||
Logger logger;
|
||||
std::shared_ptr<XmonAgent> xmon;
|
||||
|
||||
std::unique_ptr<SharedRocksDB> sharedDB;
|
||||
std::unique_ptr<RegistryDB> db;
|
||||
|
||||
TempRegistryDB(
|
||||
LogLevel level): logger(level, STDERR_FILENO, false, false)
|
||||
{
|
||||
dbDir = std::string("temp-registry-db.XXXXXX");
|
||||
if (mkdtemp(dbDir.data()) == nullptr) {
|
||||
throw SYSCALL_EXCEPTION("mkdtemp");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void open(const RegistryOptions& options)
|
||||
{
|
||||
sharedDB = std::make_unique<SharedRocksDB>(logger, xmon, dbDir + "/db", dbDir + "/db-statistics.txt");
|
||||
initSharedDB();
|
||||
db = std::make_unique<RegistryDB>(logger, xmon, options, *sharedDB);
|
||||
}
|
||||
|
||||
void close() {
|
||||
db->close();
|
||||
sharedDB->close();
|
||||
db.reset();
|
||||
sharedDB.reset();
|
||||
}
|
||||
|
||||
~TempRegistryDB() {
|
||||
std::error_code err;
|
||||
if (std::filesystem::remove_all(std::filesystem::path(dbDir), err) < 0) {
|
||||
std::cerr << "Could not remove " << dbDir << ": " << err << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<RegistryDB>& operator->() {
|
||||
return db;
|
||||
}
|
||||
|
||||
void initSharedDB() {
|
||||
sharedDB->registerCFDescriptors({{rocksdb::kDefaultColumnFamilyName, {}}});
|
||||
sharedDB->registerCFDescriptors(RegistryDB::getColumnFamilyDescriptors());
|
||||
rocksdb::Options rocksDBOptions;
|
||||
rocksDBOptions.create_if_missing = true;
|
||||
rocksDBOptions.create_missing_column_families = true;
|
||||
rocksDBOptions.compression = rocksdb::kLZ4Compression;
|
||||
rocksDBOptions.bottommost_compression = rocksdb::kZSTD;
|
||||
// 1000*256 = 256k open files at once, given that we currently run on a
|
||||
// single machine this is appropriate.
|
||||
rocksDBOptions.max_open_files = 1000;
|
||||
// We batch writes and flush manually.
|
||||
rocksDBOptions.manual_wal_flush = true;
|
||||
sharedDB->open(rocksDBOptions);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user