Commit 585245f2 authored by Georgios Bitzes's avatar Georgios Bitzes
Browse files

Implement recovery command to force-reconfigure a journal

parent 27a49a6b
Pipeline #529843 passed with stages
in 39 minutes and 11 seconds
......@@ -145,6 +145,7 @@ struct cmdMapInit {
redis_cmd_map["recovery_set"] = {RedisCommand::RECOVERY_SET, CommandType::RECOVERY};
redis_cmd_map["recovery_get"] = {RedisCommand::RECOVERY_GET, CommandType::RECOVERY};
redis_cmd_map["recovery_del"] = {RedisCommand::RECOVERY_DEL, CommandType::RECOVERY};
redis_cmd_map["recovery_force_reconfigure_journal"] = {RedisCommand::RECOVERY_FORCE_RECONFIGURE_JOURNAL, CommandType::RECOVERY};
redis_cmd_map["convert_string_to_int"] = {RedisCommand::CONVERT_STRING_TO_INT, CommandType::CONTROL};
redis_cmd_map["convert_int_to_string"] = {RedisCommand::CONVERT_INT_TO_STRING, CommandType::CONTROL};
......
......@@ -151,6 +151,7 @@ enum class RedisCommand {
RECOVERY_SET,
RECOVERY_DEL,
RECOVERY_INFO,
RECOVERY_FORCE_RECONFIGURE_JOURNAL,
CONVERT_STRING_TO_INT,
CONVERT_INT_TO_STRING,
......
......@@ -57,16 +57,27 @@ struct RaftMembers {
RaftMembers(const std::vector<RaftServer> &_nodes, const std::vector<RaftServer> &_obs)
: nodes(_nodes), observers(_obs) {}
RaftMembers(const std::string &serialized) {
bool parse(const std::string &serialized) {
nodes.clear();
observers.clear();
std::vector<std::string> parts = split(serialized, "|");
if(parts.size() != 2) qdb_throw("corruption, unable to parse raft members: " << serialized);
if(parts.size() != 2) return false;
if(!parseServers(parts[0], nodes)) {
qdb_throw("corruption, cannot parse nodes: " << parts[0]);
return false;
}
if(!parts[1].empty() && !parseServers(parts[1], observers)) {
qdb_throw("corruption, cannot parse observers: " << parts[1]);
return false;
}
return true;
}
RaftMembers(const std::string &serialized) {
if(!parse(serialized)) {
qdb_throw("corruption, cannot parse members: " << serialized);
}
}
......
......@@ -24,6 +24,9 @@
#include "RecoveryDispatcher.hh"
#include "../Formatter.hh"
#include "../utils/CommandParsing.hh"
#include "../utils/IntToBinaryString.hh"
#include "../storage/KeyConstants.hh"
#include "../raft/RaftMembers.hh"
using namespace quarkdb;
RecoveryDispatcher::RecoveryDispatcher(RecoveryEditor &ed) : editor(ed) {
......@@ -75,6 +78,36 @@ RedisEncodedResponse RecoveryDispatcher::dispatch(RedisRequest &request) {
if(request.size() != 1) return Formatter::errArgs(request[0]);
return Formatter::vector(editor.retrieveMagicValues());
}
case RedisCommand::RECOVERY_FORCE_RECONFIGURE_JOURNAL: {
if(request.size() != 3) return Formatter::errArgs(request[0]);
RaftMembers members;
if(!members.parse(request[1])) {
return Formatter::err("cannot parse new members");
}
std::string clusterID;
rocksdb::Status st = editor.get(KeyConstants::kJournal_ClusterID, clusterID);
if(!st.ok()) {
return Formatter::err(SSTR("unable to retrieve clusterID, status " << st.ToString() << " - are you sure this is a journal?"));
}
if(clusterID == request[2]) {
return Formatter::err("when force reconfiguring, new clusterID must be different than old one");
}
// All checks are clear, proceed
qdb_assert(editor.set(KeyConstants::kJournal_ClusterID, request[2]).ok());
qdb_assert(editor.set(KeyConstants::kJournal_Members, request[1]).ok());
qdb_assert(editor.set(KeyConstants::kJournal_MembershipEpoch, intToBinaryString(0)).ok());
editor.del(KeyConstants::kJournal_PreviousMembers);
editor.del(KeyConstants::kJournal_PreviousMembershipEpoch);
return Formatter::ok();
}
default: {
qdb_throw("should never reach here");
}
......
......@@ -136,6 +136,9 @@ TEST(Recovery, RemoveJournalEntriesAndChangeClusterID) {
ASSERT_REPLY(qcl.exec("recovery-info"), rep);
ASSERT_REPLY(qcl.exec("recovery-force-reconfigure-journal", "test", "123"), "ERR cannot parse new members");
ASSERT_REPLY(qcl.exec("recovery-force-reconfigure-journal", "example.com:99|", "awesome-cluster-id"), "OK");
// Test integer <-> binary string conversion functions.
redisReplyPtr conv1 = qcl.exec("convert-int-to-string", "999").get();
ASSERT_EQ(qclient::describeRedisReply(conv1), "1) \"As int64_t: \\x00\\x00\\x00\\x00\\x00\\x00\\x03\\xE7\"\n2) \"As uint64_t: \\x00\\x00\\x00\\x00\\x00\\x00\\x03\\xE7\"\n");
......@@ -145,10 +148,37 @@ TEST(Recovery, RemoveJournalEntriesAndChangeClusterID) {
redisReplyPtr conv2 = qcl.exec("convert-string-to-int", unsignedIntToBinaryString(999u)).get();
ASSERT_EQ(qclient::describeRedisReply(conv2), "1) Interpreted as int64_t: 999\n2) Interpreted as uint64_t: 999\n");
rep = {
"RAFT_CURRENT_TERM",
intToBinaryString(4),
"RAFT_LOG_SIZE",
intToBinaryString(2),
"RAFT_LOG_START",
intToBinaryString(0),
"RAFT_CLUSTER_ID",
"awesome-cluster-id",
"RAFT_VOTED_FOR",
"",
"RAFT_COMMIT_INDEX",
intToBinaryString(0),
"RAFT_MEMBERS",
"example.com:99|",
"RAFT_MEMBERSHIP_EPOCH",
intToBinaryString(0),
"RAFT_PREVIOUS_MEMBERS: NotFound: ",
"RAFT_PREVIOUS_MEMBERSHIP_EPOCH: NotFound: ",
"__format: NotFound: ",
"__last-applied: NotFound: ",
"__in-bulkload: NotFound: ",
"__clock: NotFound: "
};
ASSERT_REPLY(qcl.exec("recovery-info"), rep);
}
RaftJournal journal("/tmp/quarkdb-recovery-test");
ASSERT_EQ(journal.getClusterID(), "different-cluster-id");
ASSERT_EQ(journal.getClusterID(), "awesome-cluster-id");
ASSERT_EQ(journal.getLogSize(), 2);
RaftEntry entry;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment