mirror of https://gitee.com/bigwinds/arangodb
958 lines
40 KiB
C++
958 lines
40 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
/// DISCLAIMER
|
|
///
|
|
/// Copyright 2018 ArangoDB GmbH, Cologne, Germany
|
|
///
|
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|
/// you may not use this file except in compliance with the License.
|
|
/// You may obtain a copy of the License at
|
|
///
|
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
|
///
|
|
/// Unless required by applicable law or agreed to in writing, software
|
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
/// See the License for the specific language governing permissions and
|
|
/// limitations under the License.
|
|
///
|
|
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
|
///
|
|
/// @author Tobias Gödderz
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "gtest/gtest.h"
|
|
|
|
#include "Basics/ScopeGuard.h"
|
|
#include "Cluster/ClusterRepairs.h"
|
|
#include "Cluster/ServerState.h"
|
|
|
|
#include "Agency/AddFollower.h"
|
|
#include "Agency/FailedLeader.h"
|
|
#include "Agency/MoveShard.h"
|
|
#include "arangod/Cluster/ResultT.h"
|
|
#include "lib/Random/RandomGenerator.h"
|
|
|
|
#include <boost/core/demangle.hpp>
|
|
#include <boost/date_time.hpp>
|
|
#include <boost/range/combine.hpp>
|
|
|
|
#include <iostream>
|
|
#include <typeinfo>
|
|
|
|
using namespace arangodb;
|
|
using namespace arangodb::consensus;
|
|
using namespace arangodb::cluster_repairs;
|
|
|
|
namespace arangodb {
|
|
|
|
bool operator==(AgencyWriteTransaction const& left, AgencyWriteTransaction const& right) {
|
|
VPackBuilder leftBuilder;
|
|
VPackBuilder rightBuilder;
|
|
|
|
left.toVelocyPack(leftBuilder);
|
|
right.toVelocyPack(rightBuilder);
|
|
|
|
return velocypack::NormalizedCompare::equals(leftBuilder.slice(), rightBuilder.slice());
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& ostream, AgencyWriteTransaction const& trx) {
|
|
Options optPretty = VPackOptions::Defaults;
|
|
optPretty.prettyPrint = true;
|
|
|
|
VPackBuilder builder;
|
|
|
|
trx.toVelocyPack(builder);
|
|
|
|
ostream << builder.slice().toJson(&optPretty);
|
|
|
|
return ostream;
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& ostream, std::list<RepairOperation> const& list) {
|
|
ostream << "std::list<RepairOperation> {\n";
|
|
if (!list.empty()) {
|
|
auto it = list.begin();
|
|
ostream << *it << "\n";
|
|
for (++it; it != list.end(); ++it) {
|
|
ostream << ", " << *it << "\n";
|
|
}
|
|
}
|
|
ostream << "}" << std::endl;
|
|
return ostream;
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& ostream, std::vector<RepairOperation> const& vector) {
|
|
ostream << "std::vector<RepairOperation> {\n";
|
|
if (!vector.empty()) {
|
|
auto it = vector.begin();
|
|
ostream << *it << "\n";
|
|
for (++it; it != vector.end(); ++it) {
|
|
ostream << ", " << *it << "\n";
|
|
}
|
|
}
|
|
ostream << "}" << std::endl;
|
|
return ostream;
|
|
}
|
|
|
|
template <typename K, typename V>
|
|
std::ostream& operator<<(std::ostream& ostream, std::map<K, V> const& map) {
|
|
std::string const typeNameK = boost::core::demangle(typeid(K).name());
|
|
std::string const typeNameV = boost::core::demangle(typeid(V).name());
|
|
ostream << "std::map<" << typeNameK << ", " << typeNameV << "> {\n";
|
|
if (!map.empty()) {
|
|
auto it = map.begin();
|
|
ostream << it->first << " => " << it->second << "\n";
|
|
for (++it; it != map.end(); ++it) {
|
|
ostream << ", " << it->first << " => " << it->second << "\n";
|
|
}
|
|
}
|
|
ostream << "}" << std::endl;
|
|
return ostream;
|
|
}
|
|
|
|
template <typename T>
|
|
std::ostream& operator<<(std::ostream& stream, ResultT<T> const& result) {
|
|
// clang-format off
|
|
std::string const typeName =
|
|
typeid(T) == typeid(RepairOperation) ?
|
|
"RepairOperation" :
|
|
typeid(T) == typeid(std::list<RepairOperation>) ?
|
|
"std::list<RepairOperation>" :
|
|
typeid(T) == typeid(std::vector<RepairOperation>) ?
|
|
"std::vector<RepairOperation>" :
|
|
boost::core::demangle(typeid(T).name());
|
|
// clang-format on
|
|
|
|
if (result.ok()) {
|
|
return stream << "ResultT<" << typeName << "> {"
|
|
<< "val = " << result.get() << " }";
|
|
}
|
|
|
|
return stream << "ResultT<" << typeName << "> {"
|
|
<< "errorNumber = " << result.errorNumber() << ", "
|
|
<< "errorMessage = \"" << result.errorMessage() << "\" }";
|
|
}
|
|
|
|
namespace tests {
|
|
namespace cluster_repairs_test {
|
|
|
|
VPackBufferPtr vpackFromJsonString(char const* c) {
|
|
Options options;
|
|
options.checkAttributeUniqueness = true;
|
|
VPackParser parser(&options);
|
|
parser.parse(c);
|
|
|
|
std::shared_ptr<VPackBuilder> builder = parser.steal();
|
|
|
|
return builder->steal();
|
|
}
|
|
|
|
VPackBufferPtr operator"" _vpack(const char* json, size_t) {
|
|
return vpackFromJsonString(json);
|
|
}
|
|
|
|
class ClusterRepairsTest : public ::testing::Test {
|
|
protected:
|
|
void checkAgainstExpectedOperations(
|
|
VPackBufferPtr const& planCollections, VPackBufferPtr const& supervisionHealth,
|
|
std::map<CollectionID, ResultT<std::vector<RepairOperation>>> expectedRepairOperationsByCollection) {
|
|
ResultT<std::map<CollectionID, ResultT<std::list<RepairOperation>>>> repairOperationsByCollectionResult =
|
|
DistributeShardsLikeRepairer::repairDistributeShardsLike(
|
|
VPackSlice(planCollections->data()), VPackSlice(supervisionHealth->data()));
|
|
|
|
ASSERT_TRUE(repairOperationsByCollectionResult.ok());
|
|
std::map<CollectionID, ResultT<std::list<RepairOperation>>>& repairOperationsByCollection =
|
|
repairOperationsByCollectionResult.get();
|
|
|
|
{
|
|
std::stringstream expectedOperationsStringStream;
|
|
expectedOperationsStringStream << "{" << std::endl;
|
|
for (auto const& it : expectedRepairOperationsByCollection) {
|
|
std::string const& collection = it.first;
|
|
ResultT<std::vector<RepairOperation>> const& expectedRepairResult = it.second;
|
|
|
|
expectedOperationsStringStream << "\"" << collection << "\": \n";
|
|
expectedOperationsStringStream << expectedRepairResult << std::endl;
|
|
}
|
|
expectedOperationsStringStream << "}";
|
|
|
|
std::stringstream repairOperationsStringStream;
|
|
repairOperationsStringStream << "{" << std::endl;
|
|
for (auto const& it : repairOperationsByCollection) {
|
|
std::string const& collection = it.first;
|
|
ResultT<std::list<RepairOperation>> const repairOperationsResult = it.second;
|
|
repairOperationsStringStream << "\"" << collection << "\": \n";
|
|
repairOperationsStringStream << repairOperationsResult << std::endl;
|
|
}
|
|
repairOperationsStringStream << "}";
|
|
|
|
ASSERT_TRUE(repairOperationsByCollection.size() ==
|
|
expectedRepairOperationsByCollection.size());
|
|
for (auto const& it : boost::combine(expectedRepairOperationsByCollection,
|
|
repairOperationsByCollection)) {
|
|
auto const& expectedResult = it.get<0>().second;
|
|
auto const& actualResult = it.get<1>().second;
|
|
|
|
ASSERT_TRUE(expectedResult.ok() == actualResult.ok());
|
|
if (expectedResult.ok()) {
|
|
ASSERT_TRUE(expectedResult.get().size() == actualResult.get().size());
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto const& it : boost::combine(repairOperationsByCollection,
|
|
expectedRepairOperationsByCollection)) {
|
|
std::string const& collection = it.get<0>().first;
|
|
ResultT<std::list<RepairOperation>> const repairResult = it.get<0>().second;
|
|
|
|
std::string const& expectedCollection = it.get<1>().first;
|
|
ResultT<std::vector<RepairOperation>> const& expectedResult = it.get<1>().second;
|
|
|
|
ASSERT_TRUE(collection == expectedCollection);
|
|
ASSERT_TRUE(repairResult.ok() == expectedResult.ok());
|
|
if (expectedResult.ok()) {
|
|
std::list<RepairOperation> const& repairOperations = repairResult.get();
|
|
std::vector<RepairOperation> const& expectedOperations = expectedResult.get();
|
|
|
|
for (auto const& it : boost::combine(repairOperations, expectedOperations)) {
|
|
auto const& repairOpIt = it.get<0>();
|
|
auto const& expectedRepairOpIt = it.get<1>();
|
|
|
|
ASSERT_TRUE(repairOpIt == expectedRepairOpIt);
|
|
}
|
|
} else {
|
|
ASSERT_TRUE(repairResult == expectedResult);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
class ClusterRepairsTestBrokenDistribution : public ClusterRepairsTest {
|
|
protected:
|
|
// save old manager (may be null)
|
|
std::unique_ptr<AgencyCommManager> oldManager;
|
|
|
|
ClusterRepairsTestBrokenDistribution()
|
|
: oldManager(std::move(AgencyCommManager::MANAGER)) {
|
|
// Disable cluster logging
|
|
arangodb::LogTopic::setLogLevel(arangodb::Logger::CLUSTER.name(),
|
|
arangodb::LogLevel::FATAL);
|
|
TRI_DEFER(arangodb::LogTopic::setLogLevel(arangodb::Logger::CLUSTER.name(),
|
|
arangodb::LogLevel::DEFAULT));
|
|
|
|
// get a new manager
|
|
AgencyCommManager::initialize("testArangoAgencyPrefix");
|
|
}
|
|
|
|
~ClusterRepairsTestBrokenDistribution() {
|
|
// restore old manager
|
|
AgencyCommManager::MANAGER = std::move(oldManager);
|
|
}
|
|
};
|
|
|
|
TEST_F(ClusterRepairsTestBrokenDistribution,
|
|
an_agency_where_on_two_shards_the_dbservers_are_swapped_one_unused_dbserver_is_free_to_exchange_the_leader) {
|
|
#include "ClusterRepairsTest.swapWithLeader.cpp"
|
|
|
|
checkAgainstExpectedOperations(planCollections, supervisionHealth3Healthy0Bad,
|
|
expectedResultsWithTwoSwappedDBServers);
|
|
}
|
|
|
|
TEST_F(ClusterRepairsTestBrokenDistribution,
|
|
an_agency_where_on_two_shards_the_dbservers_are_swapped_the_unused_dbserver_is_marked_as_nonhealthy) {
|
|
#include "ClusterRepairsTest.unusedServerUnhealthy.cpp"
|
|
|
|
auto result = DistributeShardsLikeRepairer::repairDistributeShardsLike(
|
|
VPackSlice(planCollections->data()),
|
|
VPackSlice(supervisionHealth2Healthy1Bad->data()));
|
|
|
|
ASSERT_TRUE(result.ok());
|
|
std::map<CollectionID, ResultT<std::list<RepairOperation>>> operationResultByCollectionId =
|
|
result.get();
|
|
ASSERT_TRUE(operationResultByCollectionId.size() == 1);
|
|
ASSERT_TRUE(operationResultByCollectionId.find("11111111") !=
|
|
operationResultByCollectionId.end());
|
|
ResultT<std::list<RepairOperation>> collectionResult =
|
|
operationResultByCollectionId.at("11111111");
|
|
|
|
ASSERT_TRUE(collectionResult.errorNumber() == TRI_ERROR_CLUSTER_REPAIRS_NOT_ENOUGH_HEALTHY);
|
|
ASSERT_TRUE(0 == strcmp(TRI_errno_string(collectionResult.errorNumber()),
|
|
"not enough (healthy) db servers"));
|
|
ASSERT_TRUE(collectionResult.fail());
|
|
}
|
|
|
|
TEST_F(ClusterRepairsTestBrokenDistribution,
|
|
an_agency_where_on_two_shards_the_dbservers_are_swapped_the_replicationfactor_equals_the_number_of_dbservers) {
|
|
#include "ClusterRepairsTest.replicationFactorTooHigh.cpp"
|
|
|
|
auto result = DistributeShardsLikeRepairer::repairDistributeShardsLike(
|
|
VPackSlice(planCollections->data()),
|
|
VPackSlice(supervisionHealth2Healthy0Bad->data()));
|
|
|
|
ASSERT_TRUE(result.ok());
|
|
std::map<CollectionID, ResultT<std::list<RepairOperation>>> operationResultByCollectionId =
|
|
result.get();
|
|
ASSERT_TRUE(operationResultByCollectionId.size() == 1);
|
|
ASSERT_TRUE(operationResultByCollectionId.find("11111111") !=
|
|
operationResultByCollectionId.end());
|
|
ResultT<std::list<RepairOperation>> collectionResult =
|
|
operationResultByCollectionId.at("11111111");
|
|
|
|
ASSERT_TRUE(collectionResult.errorNumber() == TRI_ERROR_CLUSTER_REPAIRS_NOT_ENOUGH_HEALTHY);
|
|
ASSERT_TRUE(0 == strcmp(TRI_errno_string(collectionResult.errorNumber()),
|
|
"not enough (healthy) db servers"));
|
|
ASSERT_TRUE(collectionResult.fail());
|
|
}
|
|
|
|
TEST_F(ClusterRepairsTestBrokenDistribution,
|
|
an_agency_where_differently_ordered_followers_have_to_be_moved) {
|
|
#include "ClusterRepairsTest.moveFollower.cpp"
|
|
// This test should ensure that the (internal) order in the repairer
|
|
// after a shard move resembles the one after a real shard move.
|
|
// i.e., moving a follower puts it to the end of the list, e.g., given
|
|
// [a, b, c, d] (where a is the leader), moving b to e results in
|
|
// [a, c, d, e] rather than [a, e, c, d].
|
|
checkAgainstExpectedOperations(planCollections, supervisionHealth4Healthy0Bad,
|
|
expectedResultsWithFollowerOrder);
|
|
}
|
|
|
|
TEST_F(ClusterRepairsTestBrokenDistribution,
|
|
an_agency_where_a_follower_shard_has_erroneously_ordered_dbservers) {
|
|
#include "ClusterRepairsTest.unorderedFollowers.cpp"
|
|
checkAgainstExpectedOperations(planCollections, supervisionHealth4Healthy0Bad,
|
|
expectedResultsWithWronglyOrderedFollowers);
|
|
}
|
|
|
|
TEST_F(ClusterRepairsTestBrokenDistribution,
|
|
an_agency_where_a_collection_has_repairing_distributshardslike_but_nothing_else_is_broken) {
|
|
#include "ClusterRepairsTest.repairingDistributeShardsLike.cpp"
|
|
checkAgainstExpectedOperations(planCollections, supervisionHealth4Healthy0Bad,
|
|
expectedResultsWithRepairingDistributeShardsLike);
|
|
}
|
|
|
|
TEST_F(ClusterRepairsTestBrokenDistribution,
|
|
an_agency_where_a_collection_has_repairing_distributshardslike_but_the_replicationfactor_differs) {
|
|
#include "ClusterRepairsTest.repairingDslChangedRf.cpp"
|
|
checkAgainstExpectedOperations(planCollections, supervisionHealth4Healthy0Bad,
|
|
expectedResultsWithRepairingDistributeShardsLike);
|
|
}
|
|
|
|
TEST_F(ClusterRepairsTestBrokenDistribution, an_agency_with_multiple_collections) {
|
|
#include "ClusterRepairsTest.multipleCollections.cpp"
|
|
checkAgainstExpectedOperations(planCollections, supervisionHealth4Healthy0Bad,
|
|
expectedResultsWithMultipleCollections);
|
|
}
|
|
|
|
TEST_F(ClusterRepairsTestBrokenDistribution, a_collection_with_multiple_shards) {
|
|
#include "ClusterRepairsTest.multipleShards.cpp"
|
|
checkAgainstExpectedOperations(planCollections, supervisionHealth3Healthy0Bad,
|
|
expectedResultsWithMultipleShards);
|
|
}
|
|
|
|
TEST_F(ClusterRepairsTestBrokenDistribution,
|
|
a_collection_where_the_replicationfactor_doesnt_conform_with_its_prototype) {
|
|
#include "ClusterRepairsTest.unequalReplicationFactor.cpp"
|
|
checkAgainstExpectedOperations(planCollections, supervisionHealth3Healthy0Bad,
|
|
expectedResultsWithUnequalReplicationFactor);
|
|
}
|
|
|
|
TEST_F(ClusterRepairsTestBrokenDistribution, a_smart_graph_with_some_broken_collections) {
|
|
#include "ClusterRepairsTest.smartCollections.cpp"
|
|
checkAgainstExpectedOperations(planCollections, supervisionHealth3Healthy0Bad,
|
|
expectedResultsWithSmartGraph);
|
|
}
|
|
|
|
TEST_F(ClusterRepairsTestBrokenDistribution, a_satellite_collection) {
|
|
#include "ClusterRepairsTest.satelliteCollection.cpp"
|
|
checkAgainstExpectedOperations(planCollections, supervisionHealth3Healthy0Bad,
|
|
expectedResultsWithSatelliteCollection);
|
|
}
|
|
|
|
TEST_F(ClusterRepairsTestBrokenDistribution,
|
|
a_collection_that_should_usually_be_fixed_but_is_deleted) {
|
|
#include "ClusterRepairsTest.deletedCollection.cpp"
|
|
checkAgainstExpectedOperations(planCollections, supervisionHealth3Healthy0Bad,
|
|
expectedResultsWithDeletedCollection);
|
|
}
|
|
|
|
#ifdef ARANGODB_ENABLE_FAILURE_TESTS
|
|
TEST_F(ClusterRepairsTestBrokenDistribution, collections_with_triggered_failures) {
|
|
// NOTE: Some of the collection names used in in the following file would
|
|
// usually be invalid because they are too long.
|
|
#include "ClusterRepairsTest.triggeredFailures.cpp"
|
|
TRI_AddFailurePointDebugging(
|
|
"DistributeShardsLikeRepairer::createFixServerOrderOperation/"
|
|
"TRI_ERROR_CLUSTER_REPAIRS_MISMATCHING_LEADERS");
|
|
TRI_AddFailurePointDebugging(
|
|
"DistributeShardsLikeRepairer::createFixServerOrderOperation/"
|
|
"TRI_ERROR_CLUSTER_REPAIRS_MISMATCHING_FOLLOWERS");
|
|
TRI_AddFailurePointDebugging(
|
|
"DistributeShardsLikeRepairer::repairDistributeShardsLike/"
|
|
"TRI_ERROR_CLUSTER_REPAIRS_INCONSISTENT_ATTRIBUTES");
|
|
TRI_AddFailurePointDebugging(
|
|
"DistributeShardsLikeRepairer::createBeginRepairsOperation/"
|
|
"TRI_ERROR_CLUSTER_REPAIRS_INCONSISTENT_ATTRIBUTES");
|
|
TRI_AddFailurePointDebugging(
|
|
"DistributeShardsLikeRepairer::createFinishRepairsOperation/"
|
|
"TRI_ERROR_CLUSTER_REPAIRS_INCONSISTENT_ATTRIBUTES");
|
|
TRI_AddFailurePointDebugging(
|
|
"DistributeShardsLikeRepairer::repairDistributeShardsLike/"
|
|
"TRI_ERROR_CLUSTER_REPAIRS_NO_DBSERVERS");
|
|
try {
|
|
checkAgainstExpectedOperations(planCollections, supervisionHealth2Healthy0Bad,
|
|
expectedResultsWithTriggeredFailures);
|
|
TRI_ClearFailurePointsDebugging();
|
|
} catch (...) {
|
|
TRI_ClearFailurePointsDebugging();
|
|
throw;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
TEST(ClusterRepairsTestVersionSort, different_version_strings) {
|
|
// Disable cluster logging
|
|
arangodb::LogTopic::setLogLevel(arangodb::Logger::CLUSTER.name(), arangodb::LogLevel::FATAL);
|
|
TRI_DEFER(arangodb::LogTopic::setLogLevel(arangodb::Logger::CLUSTER.name(),
|
|
arangodb::LogLevel::DEFAULT));
|
|
|
|
// General functionality check
|
|
EXPECT_TRUE(VersionSort()("s2", "s10"));
|
|
EXPECT_TRUE(!VersionSort()("s10", "s2"));
|
|
|
|
EXPECT_TRUE(VersionSort()("s5", "s7"));
|
|
EXPECT_TRUE(!VersionSort()("s7", "s5"));
|
|
|
|
// Make sure sorting by the last char works
|
|
EXPECT_TRUE(VersionSort()("s100a", "s0100b"));
|
|
EXPECT_TRUE(!VersionSort()("s0100b", "s100a"));
|
|
|
|
// Make sure the ints aren't casted into signed chars and overflow
|
|
EXPECT_TRUE(VersionSort()("s126", "s129"));
|
|
EXPECT_TRUE(!VersionSort()("s129", "s126"));
|
|
|
|
// Make sure the ints aren't casted into unsigned chars and overflow
|
|
EXPECT_TRUE(VersionSort()("s254", "s257"));
|
|
EXPECT_TRUE(!VersionSort()("s257", "s254"));
|
|
|
|
// Regression test
|
|
EXPECT_TRUE(VersionSort()("s1000057", "s1000065"));
|
|
EXPECT_TRUE(!VersionSort()("s1000065", "s1000057"));
|
|
|
|
EXPECT_TRUE(VersionSort()("s1000050", "s1000064"));
|
|
EXPECT_TRUE(!VersionSort()("s1000064", "s1000050"));
|
|
}
|
|
|
|
class ClusterRepairsTestOperations : public ClusterRepairsTest {
|
|
protected:
|
|
std::unique_ptr<AgencyCommManager> oldManager;
|
|
std::string const oldServerId;
|
|
RepairOperationToTransactionVisitor conversionVisitor;
|
|
|
|
static uint64_t mockJobIdGenerator() {
|
|
EXPECT_TRUE(false);
|
|
return 0ul;
|
|
}
|
|
|
|
static std::chrono::system_clock::time_point mockJobCreationTimestampGenerator() {
|
|
EXPECT_TRUE(false);
|
|
return std::chrono::system_clock::now();
|
|
}
|
|
|
|
ClusterRepairsTestOperations()
|
|
: oldManager(std::move(AgencyCommManager::MANAGER)),
|
|
oldServerId(ServerState::instance()->getId()),
|
|
conversionVisitor(mockJobIdGenerator, mockJobCreationTimestampGenerator) {
|
|
// Disable cluster logging
|
|
arangodb::LogTopic::setLogLevel(arangodb::Logger::CLUSTER.name(),
|
|
arangodb::LogLevel::FATAL);
|
|
TRI_DEFER(arangodb::LogTopic::setLogLevel(arangodb::Logger::CLUSTER.name(),
|
|
arangodb::LogLevel::DEFAULT));
|
|
|
|
// get a new manager
|
|
AgencyCommManager::initialize("testArangoAgencyPrefix");
|
|
}
|
|
|
|
~ClusterRepairsTestOperations() {
|
|
// restore old manager
|
|
AgencyCommManager::MANAGER = std::move(oldManager);
|
|
}
|
|
};
|
|
|
|
TEST_F(ClusterRepairsTestOperations,
|
|
a_beginrepairsoperation_with_equal_replicationfactors_and_rename_true_converted_into_an_agencytransaction) {
|
|
BeginRepairsOperation operation{_database = "myDbName",
|
|
_collectionId = "123456",
|
|
_collectionName = "myCollection",
|
|
_protoCollectionId = "789876",
|
|
_protoCollectionName = "myProtoCollection",
|
|
_collectionReplicationFactor = 3,
|
|
_protoReplicationFactor = 3,
|
|
_renameDistributeShardsLike = true};
|
|
AgencyWriteTransaction trx;
|
|
boost::optional<uint64_t> jobid;
|
|
std::tie(trx, jobid) = conversionVisitor(operation);
|
|
|
|
ASSERT_FALSE(jobid.is_initialized());
|
|
|
|
VPackBufferPtr protoCollIdVPack = R"=("789876")="_vpack;
|
|
Slice protoCollIdSlice = Slice(protoCollIdVPack->data());
|
|
VPackBufferPtr replicationFactorVPack = R"=(3)="_vpack;
|
|
Slice replicationFactorSlice = Slice(replicationFactorVPack->data());
|
|
|
|
AgencyWriteTransaction expectedTrx{
|
|
std::vector<AgencyOperation>{
|
|
AgencyOperation{
|
|
"Plan/Collections/myDbName/123456/distributeShardsLike",
|
|
AgencySimpleOperationType::DELETE_OP},
|
|
AgencyOperation{"Plan/Collections/myDbName/123456/"
|
|
"repairingDistributeShardsLike",
|
|
AgencyValueOperationType::SET, protoCollIdSlice},
|
|
AgencyOperation{"Plan/Collections/myDbName/123456/replicationFactor",
|
|
AgencyValueOperationType::SET, replicationFactorSlice},
|
|
AgencyOperation{"Plan/Version", AgencySimpleOperationType::INCREMENT_OP}},
|
|
std::vector<AgencyPrecondition>{
|
|
AgencyPrecondition{"Plan/Collections/myDbName/123456/"
|
|
"repairingDistributeShardsLike",
|
|
AgencyPrecondition::Type::EMPTY, true},
|
|
AgencyPrecondition{
|
|
"Plan/Collections/myDbName/123456/distributeShardsLike",
|
|
AgencyPrecondition::Type::VALUE, protoCollIdSlice},
|
|
AgencyPrecondition{
|
|
"Plan/Collections/myDbName/123456/replicationFactor",
|
|
AgencyPrecondition::Type::VALUE, replicationFactorSlice},
|
|
AgencyPrecondition{
|
|
"Plan/Collections/myDbName/789876/replicationFactor",
|
|
AgencyPrecondition::Type::VALUE, replicationFactorSlice}}};
|
|
|
|
trx.clientId = expectedTrx.clientId = "dummy-client-id";
|
|
|
|
ASSERT_TRUE(trx == expectedTrx);
|
|
}
|
|
|
|
TEST_F(ClusterRepairsTestOperations,
|
|
a_beginrepairsoperation_with_equal_replicationfactors_and_rename_true_compared_via_eqeq) {
|
|
BeginRepairsOperation operation{_database = "myDbName",
|
|
_collectionId = "123456",
|
|
_collectionName = "myCollection",
|
|
_protoCollectionId = "789876",
|
|
_protoCollectionName = "myProtoCollection",
|
|
_collectionReplicationFactor = 3,
|
|
_protoReplicationFactor = 3,
|
|
_renameDistributeShardsLike = true};
|
|
BeginRepairsOperation other = operation;
|
|
|
|
ASSERT_TRUE(operation == other);
|
|
|
|
(other = operation).database = "differing database";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).collectionId = "differing collectionId";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).collectionName = "differing collectionName";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).protoCollectionId = "differing protoCollectionId";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).protoCollectionName = "differing protoCollectionName";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).collectionReplicationFactor = 42;
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).protoReplicationFactor = 23;
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).renameDistributeShardsLike = !operation.renameDistributeShardsLike;
|
|
ASSERT_FALSE(operation == other);
|
|
}
|
|
|
|
TEST_F(ClusterRepairsTestOperations,
|
|
a_beginrepairsoperation_with_differing_replicationfactors_and_rename_false_converted_into_an_agencytransaction) {
|
|
BeginRepairsOperation operation{_database = "myDbName",
|
|
_collectionId = "123456",
|
|
_collectionName = "myCollection",
|
|
_protoCollectionId = "789876",
|
|
_protoCollectionName = "myProtoCollection",
|
|
_collectionReplicationFactor = 5,
|
|
_protoReplicationFactor = 4,
|
|
_renameDistributeShardsLike = false};
|
|
|
|
AgencyWriteTransaction trx;
|
|
boost::optional<uint64_t> jobid;
|
|
std::tie(trx, jobid) = conversionVisitor(operation);
|
|
|
|
ASSERT_FALSE(jobid.is_initialized());
|
|
|
|
VPackBufferPtr protoCollIdVPack = R"=("789876")="_vpack;
|
|
Slice protoCollIdSlice = Slice(protoCollIdVPack->data());
|
|
VPackBufferPtr replicationFactorVPack = R"=(4)="_vpack;
|
|
Slice replicationFactorSlice = Slice(replicationFactorVPack->data());
|
|
|
|
AgencyWriteTransaction expectedTrx{
|
|
{AgencyOperation{"Plan/Version", AgencySimpleOperationType::INCREMENT_OP}},
|
|
std::vector<AgencyPrecondition>{
|
|
AgencyPrecondition{
|
|
"Plan/Collections/myDbName/123456/distributeShardsLike",
|
|
AgencyPrecondition::Type::EMPTY, true},
|
|
AgencyPrecondition{"Plan/Collections/myDbName/123456/"
|
|
"repairingDistributeShardsLike",
|
|
AgencyPrecondition::Type::VALUE, protoCollIdSlice},
|
|
AgencyPrecondition{
|
|
"Plan/Collections/myDbName/123456/replicationFactor",
|
|
AgencyPrecondition::Type::VALUE, replicationFactorSlice},
|
|
AgencyPrecondition{
|
|
"Plan/Collections/myDbName/789876/replicationFactor",
|
|
AgencyPrecondition::Type::VALUE, replicationFactorSlice}}};
|
|
|
|
trx.clientId = expectedTrx.clientId = "dummy-client-id";
|
|
|
|
ASSERT_TRUE(trx == expectedTrx);
|
|
}
|
|
|
|
TEST_F(ClusterRepairsTestOperations,
|
|
a_beginrepairsoperation_with_differing_replicationfactors_and_rename_true_converted_into_an_agency_transaction) {
|
|
BeginRepairsOperation operation{_database = "myDbName",
|
|
_collectionId = "123456",
|
|
_collectionName = "myCollection",
|
|
_protoCollectionId = "789876",
|
|
_protoCollectionName = "myProtoCollection",
|
|
_collectionReplicationFactor = 2,
|
|
_protoReplicationFactor = 5,
|
|
_renameDistributeShardsLike = true};
|
|
|
|
AgencyWriteTransaction trx;
|
|
boost::optional<uint64_t> jobid;
|
|
std::tie(trx, jobid) = conversionVisitor(operation);
|
|
|
|
ASSERT_FALSE(jobid.is_initialized());
|
|
|
|
VPackBufferPtr protoCollIdVPack = R"=("789876")="_vpack;
|
|
Slice protoCollIdSlice = Slice(protoCollIdVPack->data());
|
|
VPackBufferPtr replicationFactorVPack = R"=(5)="_vpack;
|
|
Slice replicationFactorSlice = Slice(replicationFactorVPack->data());
|
|
VPackBufferPtr prevReplicationFactorVPack = R"=(2)="_vpack;
|
|
Slice prevReplicationFactorSlice = Slice(prevReplicationFactorVPack->data());
|
|
|
|
AgencyWriteTransaction expectedTrx{
|
|
std::vector<AgencyOperation>{
|
|
AgencyOperation{
|
|
"Plan/Collections/myDbName/123456/distributeShardsLike",
|
|
AgencySimpleOperationType::DELETE_OP},
|
|
AgencyOperation{"Plan/Collections/myDbName/123456/"
|
|
"repairingDistributeShardsLike",
|
|
AgencyValueOperationType::SET, protoCollIdSlice},
|
|
AgencyOperation{"Plan/Collections/myDbName/123456/replicationFactor",
|
|
AgencyValueOperationType::SET, replicationFactorSlice},
|
|
AgencyOperation{"Plan/Version", AgencySimpleOperationType::INCREMENT_OP}},
|
|
std::vector<AgencyPrecondition>{
|
|
AgencyPrecondition{"Plan/Collections/myDbName/123456/"
|
|
"repairingDistributeShardsLike",
|
|
AgencyPrecondition::Type::EMPTY, true},
|
|
AgencyPrecondition{
|
|
"Plan/Collections/myDbName/123456/distributeShardsLike",
|
|
AgencyPrecondition::Type::VALUE, protoCollIdSlice},
|
|
AgencyPrecondition{
|
|
"Plan/Collections/myDbName/123456/replicationFactor",
|
|
AgencyPrecondition::Type::VALUE, prevReplicationFactorSlice},
|
|
AgencyPrecondition{
|
|
"Plan/Collections/myDbName/789876/replicationFactor",
|
|
AgencyPrecondition::Type::VALUE, replicationFactorSlice}}};
|
|
|
|
trx.clientId = expectedTrx.clientId = "dummy-client-id";
|
|
|
|
ASSERT_TRUE(trx == expectedTrx);
|
|
}
|
|
|
|
TEST_F(ClusterRepairsTestOperations, a_finishrepairsoperation_converted_into_an_agencytransaction) {
|
|
FinishRepairsOperation operation{
|
|
_database = "myDbName",
|
|
_collectionId = "123456",
|
|
_collectionName = "myCollection",
|
|
_protoCollectionId = "789876",
|
|
_protoCollectionName = "myProtoCollection",
|
|
_shards =
|
|
std::vector<ShardWithProtoAndDbServers>{
|
|
std::make_tuple<ShardID, ShardID, DBServers>(
|
|
"shard1", "protoShard1", {"dbServer1", "dbServer2"}),
|
|
std::make_tuple<ShardID, ShardID, DBServers>(
|
|
"shard2", "protoShard2", {"dbServer2", "dbServer3"})},
|
|
_replicationFactor = 3};
|
|
|
|
AgencyWriteTransaction trx;
|
|
boost::optional<uint64_t> jobid;
|
|
std::tie(trx, jobid) = conversionVisitor(operation);
|
|
|
|
ASSERT_FALSE(jobid.is_initialized());
|
|
|
|
VPackBufferPtr protoIdVPack = R"=("789876")="_vpack;
|
|
Slice protoIdSlice = Slice(protoIdVPack->data());
|
|
VPackBufferPtr replicationFactorVPack = R"=(3)="_vpack;
|
|
Slice replicationFactorSlice = Slice(replicationFactorVPack->data());
|
|
|
|
VPackBufferPtr serverOrderVPack1 = R"=(["dbServer1", "dbServer2"])="_vpack;
|
|
VPackBufferPtr serverOrderVPack2 = R"=(["dbServer2", "dbServer3"])="_vpack;
|
|
Slice serverOrderSlice1 = Slice(serverOrderVPack1->data());
|
|
Slice serverOrderSlice2 = Slice(serverOrderVPack2->data());
|
|
|
|
AgencyWriteTransaction expectedTrx{
|
|
std::vector<AgencyOperation>{
|
|
AgencyOperation{"Plan/Collections/myDbName/123456/"
|
|
"repairingDistributeShardsLike",
|
|
AgencySimpleOperationType::DELETE_OP},
|
|
AgencyOperation{
|
|
"Plan/Collections/myDbName/123456/distributeShardsLike",
|
|
AgencyValueOperationType::SET, protoIdSlice},
|
|
AgencyOperation{"Plan/Version", AgencySimpleOperationType::INCREMENT_OP}},
|
|
std::vector<AgencyPrecondition>{
|
|
AgencyPrecondition{
|
|
"Plan/Collections/myDbName/123456/distributeShardsLike",
|
|
AgencyPrecondition::Type::EMPTY, true},
|
|
AgencyPrecondition{"Plan/Collections/myDbName/123456/"
|
|
"repairingDistributeShardsLike",
|
|
AgencyPrecondition::Type::VALUE, protoIdSlice},
|
|
AgencyPrecondition{
|
|
"Plan/Collections/myDbName/123456/replicationFactor",
|
|
AgencyPrecondition::Type::VALUE, replicationFactorSlice},
|
|
AgencyPrecondition{
|
|
"Plan/Collections/myDbName/789876/replicationFactor",
|
|
AgencyPrecondition::Type::VALUE, replicationFactorSlice},
|
|
AgencyPrecondition{"Plan/Collections/myDbName/123456/shards/shard1",
|
|
AgencyPrecondition::Type::VALUE, serverOrderSlice1},
|
|
AgencyPrecondition{
|
|
"Plan/Collections/myDbName/789876/shards/protoShard1",
|
|
AgencyPrecondition::Type::VALUE, serverOrderSlice1},
|
|
AgencyPrecondition{"Plan/Collections/myDbName/123456/shards/shard2",
|
|
AgencyPrecondition::Type::VALUE, serverOrderSlice2},
|
|
AgencyPrecondition{
|
|
"Plan/Collections/myDbName/789876/shards/protoShard2",
|
|
AgencyPrecondition::Type::VALUE, serverOrderSlice2}}};
|
|
|
|
trx.clientId = expectedTrx.clientId = "dummy-client-id";
|
|
|
|
ASSERT_TRUE(trx == expectedTrx);
|
|
}
|
|
|
|
TEST_F(ClusterRepairsTestOperations, a_finishrepairsoperation_compared_via_eqeq) {
|
|
FinishRepairsOperation operation{
|
|
_database = "myDbName",
|
|
_collectionId = "123456",
|
|
_collectionName = "myCollection",
|
|
_protoCollectionId = "789876",
|
|
_protoCollectionName = "myProtoCollection",
|
|
_shards =
|
|
std::vector<ShardWithProtoAndDbServers>{
|
|
std::make_tuple<ShardID, ShardID, DBServers>(
|
|
"shard1", "protoShard1", {"dbServer1", "dbServer2"}),
|
|
std::make_tuple<ShardID, ShardID, DBServers>(
|
|
"shard2", "protoShard2", {"dbServer2", "dbServer3"})},
|
|
_replicationFactor = 3};
|
|
|
|
FinishRepairsOperation other = operation;
|
|
|
|
ASSERT_TRUE(operation == other);
|
|
|
|
(other = operation).database = "differing database";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).collectionId = "differing collectionId";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).collectionName = "differing collectionName";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).protoCollectionId = "differing protoCollectionId";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).protoCollectionName = "differing protoCollectionName";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).shards = {
|
|
std::make_tuple<ShardID, ShardID, DBServers>("differing", "shards", {"vector"})};
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).replicationFactor = 42;
|
|
ASSERT_FALSE(operation == other);
|
|
}
|
|
|
|
TEST_F(ClusterRepairsTestOperations, a_moveshardoperation_converted_into_an_agencytransaction) {
|
|
ServerState::instance()->setId("CurrentCoordinatorServerId");
|
|
|
|
MoveShardOperation operation{_database = "myDbName",
|
|
_collectionId = "123456",
|
|
_collectionName = "myCollection",
|
|
_shard = "s1",
|
|
_from = "db-from-server",
|
|
_to = "db-to-server",
|
|
_isLeader = true};
|
|
|
|
uint64_t nextJobId = 41;
|
|
auto jobIdGenerator = [&nextJobId]() { return nextJobId++; };
|
|
auto jobCreationTimestampGenerator = []() {
|
|
std::tm tm = {};
|
|
tm.tm_year = 2018 - 1900; // years since 1900
|
|
tm.tm_mon = 3 - 1; // March, counted from january
|
|
tm.tm_mday = 7;
|
|
tm.tm_hour = 15;
|
|
tm.tm_min = 20;
|
|
tm.tm_sec = 1;
|
|
tm.tm_isdst = 0;
|
|
|
|
std::chrono::system_clock::time_point tp =
|
|
std::chrono::system_clock::from_time_t(TRI_timegm(&tm));
|
|
|
|
return tp;
|
|
};
|
|
|
|
conversionVisitor =
|
|
RepairOperationToTransactionVisitor(jobIdGenerator, jobCreationTimestampGenerator);
|
|
|
|
AgencyWriteTransaction trx;
|
|
boost::optional<uint64_t> jobId;
|
|
std::tie(trx, jobId) = conversionVisitor(operation);
|
|
|
|
ASSERT_TRUE(jobId.is_initialized());
|
|
// "timeCreated": "2018-03-07T15:20:01.284Z",
|
|
VPackBufferPtr todoVPack = R"=(
|
|
{
|
|
"type": "moveShard",
|
|
"database": "myDbName",
|
|
"collection": "123456",
|
|
"shard": "s1",
|
|
"fromServer": "db-from-server",
|
|
"toServer": "db-to-server",
|
|
"jobId": "41",
|
|
"timeCreated": "2018-03-07T15:20:01Z",
|
|
"creator": "CurrentCoordinatorServerId",
|
|
"isLeader": true
|
|
}
|
|
)="_vpack;
|
|
Slice todoSlice = Slice(todoVPack->data());
|
|
|
|
AgencyWriteTransaction expectedTrx{
|
|
AgencyOperation{"Target/ToDo/" + std::to_string(jobId.get()),
|
|
AgencyValueOperationType::SET, todoSlice},
|
|
AgencyPrecondition{"Target/ToDo/" + std::to_string(jobId.get()),
|
|
AgencyPrecondition::Type::EMPTY, true}};
|
|
|
|
trx.clientId = expectedTrx.clientId = "dummy-client-id";
|
|
|
|
ASSERT_TRUE(trx == expectedTrx);
|
|
}
|
|
|
|
TEST_F(ClusterRepairsTestOperations, a_moveshardoperation_compared_via_eqeq) {
|
|
ServerState::instance()->setId("CurrentCoordinatorServerId");
|
|
|
|
MoveShardOperation operation{_database = "myDbName",
|
|
_collectionId = "123456",
|
|
_collectionName = "myCollection",
|
|
_shard = "s1",
|
|
_from = "db-from-server",
|
|
_to = "db-to-server",
|
|
_isLeader = true};
|
|
|
|
MoveShardOperation other = operation;
|
|
|
|
ASSERT_TRUE(operation == other);
|
|
|
|
(other = operation).database = "differing database";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).collectionId = "differing collectionId";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).collectionName = "differing collectionName";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).shard = "differing shard";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).from = "differing from";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).to = "differing to";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).isLeader = !operation.isLeader;
|
|
ASSERT_FALSE(operation == other);
|
|
}
|
|
|
|
TEST_F(ClusterRepairsTestOperations,
|
|
a_fixserverorderoperation_converted_into_an_agencytransaction) {
|
|
FixServerOrderOperation operation{
|
|
_database = "myDbName",
|
|
_collectionId = "123456",
|
|
_collectionName = "myCollection",
|
|
_protoCollectionId = "789876",
|
|
_protoCollectionName = "myProtoCollection",
|
|
_shard = "s1",
|
|
_protoShard = "s7",
|
|
_leader = "db-leader-server",
|
|
_followers = {"db-follower-3-server", "db-follower-2-server",
|
|
"db-follower-4-server", "db-follower-1-server"},
|
|
_protoFollowers = {"db-follower-1-server", "db-follower-2-server",
|
|
"db-follower-3-server", "db-follower-4-server"}};
|
|
|
|
VPackBufferPtr previousServerOrderVPack = R"=([
|
|
"db-leader-server",
|
|
"db-follower-3-server",
|
|
"db-follower-2-server",
|
|
"db-follower-4-server",
|
|
"db-follower-1-server"
|
|
])="_vpack;
|
|
VPackBufferPtr correctServerOrderVPack = R"=([
|
|
"db-leader-server",
|
|
"db-follower-1-server",
|
|
"db-follower-2-server",
|
|
"db-follower-3-server",
|
|
"db-follower-4-server"
|
|
])="_vpack;
|
|
Slice previousServerOrderSlice = Slice(previousServerOrderVPack->data());
|
|
Slice correctServerOrderSlice = Slice(correctServerOrderVPack->data());
|
|
|
|
AgencyWriteTransaction trx;
|
|
boost::optional<uint64_t> jobid;
|
|
std::tie(trx, jobid) = conversionVisitor(operation);
|
|
|
|
ASSERT_FALSE(jobid.is_initialized());
|
|
|
|
AgencyWriteTransaction expectedTrx{
|
|
AgencyOperation{"Plan/Collections/myDbName/123456/shards/s1",
|
|
AgencyValueOperationType::SET, correctServerOrderSlice},
|
|
{AgencyPrecondition{"Plan/Collections/myDbName/123456/shards/s1",
|
|
AgencyPrecondition::Type::VALUE, previousServerOrderSlice},
|
|
AgencyPrecondition{"Plan/Collections/myDbName/789876/shards/s7",
|
|
AgencyPrecondition::Type::VALUE, correctServerOrderSlice}}};
|
|
|
|
trx.clientId = expectedTrx.clientId = "dummy-client-id";
|
|
|
|
ASSERT_TRUE(trx == expectedTrx);
|
|
}
|
|
|
|
TEST_F(ClusterRepairsTestOperations, a_fixserverorderoperation_compared_via_eqeq) {
|
|
FixServerOrderOperation operation{
|
|
_database = "myDbName",
|
|
_collectionId = "123456",
|
|
_collectionName = "myCollection",
|
|
_protoCollectionId = "789876",
|
|
_protoCollectionName = "myProtoCollection",
|
|
_shard = "s1",
|
|
_protoShard = "s7",
|
|
_leader = "db-leader-server",
|
|
_followers = {"db-follower-3-server", "db-follower-2-server",
|
|
"db-follower-4-server", "db-follower-1-server"},
|
|
_protoFollowers = {"db-follower-1-server", "db-follower-2-server",
|
|
"db-follower-3-server", "db-follower-4-server"}};
|
|
|
|
FixServerOrderOperation other = operation;
|
|
|
|
ASSERT_TRUE(operation == other);
|
|
|
|
(other = operation).database = "differing database";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).collectionId = "differing collectionId";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).collectionName = "differing collectionName";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).protoCollectionId = "differing protoCollectionId";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).protoCollectionName = "differing protoCollectionName";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).shard = "differing shard";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).protoShard = "differing protoShard";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).leader = "differing leader";
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).followers = {"differing", "followers"};
|
|
ASSERT_FALSE(operation == other);
|
|
(other = operation).protoFollowers = {"differing", "protoFollowers"};
|
|
ASSERT_FALSE(operation == other);
|
|
}
|
|
|
|
} // namespace cluster_repairs_test
|
|
} // namespace tests
|
|
} // namespace arangodb
|