//////////////////////////////////////////////////////////////////////////////// /// 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 //////////////////////////////////////////////////////////////////////////////// #ifndef ARANGOD_CLUSTER_CLUSTER_REPAIR_OPERATIONS_H #define ARANGOD_CLUSTER_CLUSTER_REPAIR_OPERATIONS_H #include #include #include #include #include "ClusterInfo.h" namespace arangodb { namespace velocypack { template class Buffer; } class ClusterInfo; namespace cluster_repairs { using DBServers = std::vector; using VPackBufferPtr = std::shared_ptr>; // Elements are (shardId, protoShardId, dbServers). The dbServers are // the same for both shard and protoShard at this point. using ShardWithProtoAndDbServers = std::tuple; class VersionSort { public: bool operator()(std::string const& a, std::string const& b) const; private: // boost::variant cannot discern between ambiguously convertible types // (unlike std::variant from C++17). So, for compilers where char is unsigned, // using uint64_t results in a compile error (vice versa for signed chars and // int64_t) and therefore this distinct type is needed. class WrappedUInt64 { public: uint64_t value; explicit inline WrappedUInt64(uint64_t value_) : value(value_) {} bool inline operator<(WrappedUInt64 const& other) const { return this->value < other.value; } }; using CharOrInt = boost::variant; std::vector static splitVersion(std::string const& str); }; // "proto collection" always means the collection referred to in the // "distributeShardsLike" attribute of "collection" // All RepairOperations use a constructor with named parameters. This is done to // forbid braced initializer lists and assure that all constructions initialize // every member (defaults really don't make sense here), make constructions more // readable and avoid mixing up arguments of the same type (most are std::string // or typedefs thereof). // The following are used for the named parameters mentioned above. template struct tagged_argument { Type const& value; }; template struct keyword { // NOLINTNEXTLINE(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) struct tagged_argument const operator=(Type const& arg) const { return tagged_argument{arg}; } static keyword const instance; }; template struct keyword const keyword::instance = {}; // Parameters used in Operation-constructors namespace tag { struct database; struct collectionId; struct collectionName; struct protoCollectionId; struct protoCollectionName; struct shard; struct protoShard; struct from; struct to; struct isLeader; struct protoReplicationFactor; struct collectionReplicationFactor; struct replicationFactor; struct renameDistributeShardsLike; struct shards; struct leader; struct followers; struct protoFollowers; struct distributeShardsLike; struct repairingDistributeShardsLike; struct shardsById; struct deleted; struct isSmart; } // namespace tag namespace { keyword _database = decltype(_database)::instance; keyword _collectionId = decltype(_collectionId)::instance; keyword _collectionName = decltype(_collectionName)::instance; keyword _protoCollectionId = decltype(_protoCollectionId)::instance; keyword _protoCollectionName = decltype(_protoCollectionName)::instance; keyword _shard = decltype(_shard)::instance; keyword _protoShard = decltype(_protoShard)::instance; keyword _from = decltype(_from)::instance; keyword _to = decltype(_to)::instance; keyword _isLeader = decltype(_isLeader)::instance; keyword _protoReplicationFactor = decltype(_protoReplicationFactor)::instance; keyword _collectionReplicationFactor = decltype(_collectionReplicationFactor)::instance; keyword _replicationFactor = decltype(_replicationFactor)::instance; keyword _renameDistributeShardsLike = decltype(_renameDistributeShardsLike)::instance; keyword> _shards = decltype(_shards)::instance; keyword _leader = decltype(_leader)::instance; keyword> _followers = decltype(_followers)::instance; keyword> _protoFollowers = decltype(_protoFollowers)::instance; keyword> _distributeShardsLike = decltype(_distributeShardsLike)::instance; keyword> _repairingDistributeShardsLike = decltype(_repairingDistributeShardsLike)::instance; keyword> _shardsById = decltype(_shardsById)::instance; keyword _deleted = decltype(_deleted)::instance; keyword _isSmart = decltype(_isSmart)::instance; } // namespace // Applies the following changes iff renameDistributeShardsLike is true: // * Renames "distributeShardsLike" to "repairingDistributeShardsLike" // * Sets collection.replicationFactor = `protoReplicationFactor` // Asserts the following preconditions: // if renameDistributeShardsLike: // * collection.distributeShardsLike == `protoCollectionId` // * collection.repairingDistributeShardsLike == undefined // * collection.replicationFactor == `collectionReplicationFactor` // * protoCollection.replicationFactor == `protoReplicationFactor` // else: // * collection.repairingDistributeShardsLike == `protoCollectionId` // * collection.distributeShardsLike == undefined // * collection.replicationFactor == `protoReplicationFactor` (sic!) // * protoCollection.replicationFactor == `protoReplicationFactor` // // See RepairOperationToTransactionVisitor for the implementation. struct BeginRepairsOperation { DatabaseID database; CollectionID collectionId; std::string collectionName; CollectionID protoCollectionId; std::string protoCollectionName; uint64_t collectionReplicationFactor; uint64_t protoReplicationFactor; bool renameDistributeShardsLike; BeginRepairsOperation() = delete; // constructor with named parameters BeginRepairsOperation( tagged_argument database_, tagged_argument collectionId_, tagged_argument collectionName_, tagged_argument protoCollectionId_, tagged_argument protoCollectionName_, tagged_argument collectionReplicationFactor_, tagged_argument protoReplicationFactor_, tagged_argument renameDistributeShardsLike_); }; // Applies the following changes: // * Renames "repairingDistributeShardsLike" to "distributeShardsLike" // Asserts the following preconditions: // * collection.repairingDistributeShardsLike == `protoCollectionId` // * collection.distributeShardsLike == undefined // * collection.replicationFactor == `replicationFactor` // * protoCollection.replicationFactor == `replicationFactor` // * shards of both collection and protoCollection match `shards` // // `shards` should contain *all* shards or collection and protoCollection, // so if this transaction succeeds, the collection is guaranteed to be // completely fixed. // // See RepairOperationToTransactionVisitor for the implementation. struct FinishRepairsOperation { DatabaseID database; CollectionID collectionId; std::string collectionName; CollectionID protoCollectionId; std::string protoCollectionName; std::vector shards; uint64_t replicationFactor; FinishRepairsOperation() = delete; // constructor with named parameters FinishRepairsOperation( tagged_argument database_, tagged_argument collectionId_, tagged_argument collectionName_, tagged_argument protoCollectionId_, tagged_argument protoCollectionName_, tagged_argument> shards_, tagged_argument replicationFactor_); }; // Writes a moveShard job in Target/ToDo/ to move // the `shard` from the server `from` to server `to`. // // See RepairOperationToTransactionVisitor for the implementation. struct MoveShardOperation { DatabaseID database; CollectionID collectionId; std::string collectionName; ShardID shard; ServerID from; ServerID to; bool isLeader; MoveShardOperation() = delete; // constructor with named parameters MoveShardOperation(tagged_argument database_, tagged_argument collectionId_, tagged_argument collectionName_, tagged_argument shard_, tagged_argument from_, tagged_argument to_, tagged_argument isLeader_); VPackBufferPtr toVPackTodo(uint64_t jobId, std::chrono::system_clock::time_point jobCreationTimestamp) const; }; // Applies the following changes: // * Sets collection/shards/`shard` to leader :: protoFollowers // Asserts the following preconditions: // * collection/shards/`shard` == leader :: followers // * collection/shards/`shard` == leader :: protoFollowers // // See RepairOperationToTransactionVisitor for the implementation. struct FixServerOrderOperation { DatabaseID database; CollectionID collectionId; std::string collectionName; CollectionID protoCollectionId; std::string protoCollectionName; ShardID shard; ShardID protoShard; ServerID leader; std::vector followers; std::vector protoFollowers; FixServerOrderOperation() = delete; // constructor with named parameters FixServerOrderOperation( tagged_argument database_, tagged_argument collectionId_, tagged_argument collectionName_, tagged_argument protoCollectionId_, tagged_argument protoCollectionName_, tagged_argument shard_, tagged_argument protoShard_, tagged_argument leader_, tagged_argument> followers_, tagged_argument> protoFollowers_); }; bool operator==(BeginRepairsOperation const& left, BeginRepairsOperation const& right); bool operator==(FinishRepairsOperation const& left, FinishRepairsOperation const& right); bool operator==(MoveShardOperation const& left, MoveShardOperation const& right); bool operator==(FixServerOrderOperation const& left, FixServerOrderOperation const& right); std::ostream& operator<<(std::ostream& ostream, BeginRepairsOperation const& operation); std::ostream& operator<<(std::ostream& ostream, FinishRepairsOperation const& operation); std::ostream& operator<<(std::ostream& ostream, MoveShardOperation const& operation); std::ostream& operator<<(std::ostream& ostream, FixServerOrderOperation const& operation); using RepairOperation = boost::variant; std::string getTypeAsString(RepairOperation const& op); std::ostream& operator<<(std::ostream& ostream, RepairOperation const& operation); // Converts any RepairOperation to a Transaction. If its a job (i.e. put in // Target/ToDo/), it returns the corresponding job id as well. class RepairOperationToTransactionVisitor : public boost::static_visitor>> { using ReturnValueT = std::pair>; public: RepairOperationToTransactionVisitor(ClusterInfo&); RepairOperationToTransactionVisitor(std::function getJobId, std::function getJobCreationTimestamp); ReturnValueT operator()(BeginRepairsOperation const& op); ReturnValueT operator()(FinishRepairsOperation const& op); ReturnValueT operator()(MoveShardOperation const& op); ReturnValueT operator()(FixServerOrderOperation const& op); private: std::vector _vpackBufferArray; std::function _getJobId; std::function _getJobCreationTimestamp; std::vector&& steal(); std::string agencyCollectionId(DatabaseID database, CollectionID collection) const; VPackBufferPtr createShardDbServerArray(ServerID const& leader, DBServers const& followers) const; template VPackSlice createSingleValueVPack(T val); }; // Adds any RepairOperation to a VPack as an object, suitable for users to see. // Doesn't contain all data, some members are named differently. // TODO Maybe it would still be good to add all members, at least for the // functional tests? class RepairOperationToVPackVisitor : public boost::static_visitor { public: RepairOperationToVPackVisitor() = delete; explicit RepairOperationToVPackVisitor(VPackBuilder& builder); void operator()(BeginRepairsOperation const& op); void operator()(FinishRepairsOperation const& op); void operator()(MoveShardOperation const& op); void operator()(FixServerOrderOperation const& op); private: VPackBuilder& _builder; VPackBuilder& builder(); }; } // namespace cluster_repairs } // namespace arangodb #endif // ARANGOD_CLUSTER_CLUSTER_REPAIR_OPERATIONS_H