2017-05-26 04:48:44 +08:00
|
|
|
/*
|
|
|
|
* MonitorLeader.actor.cpp
|
|
|
|
*
|
|
|
|
* This source file is part of the FoundationDB open source project
|
|
|
|
*
|
|
|
|
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
|
2018-02-22 02:25:11 +08:00
|
|
|
*
|
2017-05-26 04:48:44 +08:00
|
|
|
* 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
|
2018-02-22 02:25:11 +08:00
|
|
|
*
|
2017-05-26 04:48:44 +08:00
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
2018-02-22 02:25:11 +08:00
|
|
|
*
|
2017-05-26 04:48:44 +08:00
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2018-10-20 01:30:13 +08:00
|
|
|
#include "fdbclient/MonitorLeader.h"
|
|
|
|
#include "fdbclient/CoordinationInterface.h"
|
2017-05-26 04:48:44 +08:00
|
|
|
#include "flow/ActorCollection.h"
|
|
|
|
#include "flow/UnitTest.h"
|
|
|
|
#include "fdbrpc/genericactors.actor.h"
|
|
|
|
#include "fdbrpc/Platform.h"
|
2019-02-18 06:55:47 +08:00
|
|
|
#include "flow/actorcompiler.h" // has to be last include
|
2017-05-26 04:48:44 +08:00
|
|
|
|
|
|
|
std::pair< std::string, bool > ClusterConnectionFile::lookupClusterFileName( std::string const& filename ) {
|
|
|
|
if (filename.length())
|
|
|
|
return std::make_pair(filename, false);
|
|
|
|
|
|
|
|
std::string f;
|
|
|
|
bool isDefaultFile = true;
|
|
|
|
if (platform::getEnvironmentVar(CLUSTER_FILE_ENV_VAR_NAME, f)) {
|
|
|
|
// If this is set but points to a file that does not
|
|
|
|
// exist, we will not fallback to any other methods
|
|
|
|
isDefaultFile = false;
|
|
|
|
} else if (fileExists("fdb.cluster"))
|
|
|
|
f = "fdb.cluster";
|
|
|
|
else
|
|
|
|
f = platform::getDefaultClusterFilePath();
|
|
|
|
|
|
|
|
return std::make_pair( f, isDefaultFile );
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string ClusterConnectionFile::getErrorString( std::pair<std::string, bool> const& resolvedClusterFile, Error const& e ) {
|
|
|
|
bool isDefault = resolvedClusterFile.second;
|
|
|
|
if( e.code() == error_code_connection_string_invalid ) {
|
|
|
|
return format("Invalid cluster file `%s': %d %s", resolvedClusterFile.first.c_str(), e.code(), e.what());
|
|
|
|
} else if( e.code() == error_code_no_cluster_file_found ) {
|
|
|
|
if( isDefault )
|
|
|
|
return format("Unable to read cluster file `./fdb.cluster' or `%s' and %s unset: %d %s",
|
|
|
|
platform::getDefaultClusterFilePath().c_str(), CLUSTER_FILE_ENV_VAR_NAME, e.code(), e.what());
|
|
|
|
else
|
|
|
|
return format("Unable to read cluster file `%s': %d %s", resolvedClusterFile.first.c_str(), e.code(), e.what());
|
|
|
|
} else {
|
|
|
|
return format("Unexpected error loading cluster file `%s': %d %s", resolvedClusterFile.first.c_str(), e.code(), e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ClusterConnectionFile::ClusterConnectionFile( std::string const& filename ) {
|
2018-09-22 06:58:14 +08:00
|
|
|
if( !fileExists( filename ) ) {
|
2017-05-26 04:48:44 +08:00
|
|
|
throw no_cluster_file_found();
|
2018-09-22 06:58:14 +08:00
|
|
|
}
|
2017-05-26 04:48:44 +08:00
|
|
|
|
|
|
|
cs = ClusterConnectionString(readFileBytes(filename, MAX_CLUSTER_FILE_BYTES));
|
|
|
|
this->filename = filename;
|
|
|
|
setConn = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ClusterConnectionFile::ClusterConnectionFile(std::string const& filename, ClusterConnectionString const& contents) {
|
|
|
|
this->filename = filename;
|
|
|
|
cs = contents;
|
|
|
|
setConn = true;
|
|
|
|
}
|
|
|
|
|
2018-10-23 08:57:09 +08:00
|
|
|
ClusterConnectionString const& ClusterConnectionFile::getConnectionString() const {
|
2017-05-26 04:48:44 +08:00
|
|
|
return cs;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ClusterConnectionFile::notifyConnected() {
|
|
|
|
if (setConn){
|
2017-09-23 06:06:11 +08:00
|
|
|
this->writeFile();
|
2017-05-26 04:48:44 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ClusterConnectionFile::fileContentsUpToDate() const {
|
|
|
|
ClusterConnectionString temp;
|
|
|
|
return fileContentsUpToDate(temp);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ClusterConnectionFile::fileContentsUpToDate(ClusterConnectionString &fileConnectionString) const {
|
|
|
|
try {
|
|
|
|
// the cluster file hasn't been created yet so there's nothing to check
|
|
|
|
if (setConn)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
ClusterConnectionFile temp( filename );
|
|
|
|
fileConnectionString = temp.getConnectionString();
|
|
|
|
return fileConnectionString.toString() == cs.toString();
|
|
|
|
}
|
|
|
|
catch (Error& e) {
|
2018-08-02 05:30:57 +08:00
|
|
|
TraceEvent(SevWarnAlways, "ClusterFileError").error(e).detail("Filename", filename);
|
2017-05-26 04:48:44 +08:00
|
|
|
return false; // Swallow the error and report that the file is out of date
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ClusterConnectionFile::writeFile() {
|
2017-09-23 06:06:11 +08:00
|
|
|
setConn = false;
|
2017-05-26 04:48:44 +08:00
|
|
|
if(filename.size()) {
|
|
|
|
try {
|
|
|
|
atomicReplace( filename, "# DO NOT EDIT!\n# This file is auto-generated, it is not to be edited by hand\n" + cs.toString().append("\n") );
|
|
|
|
if(!fileContentsUpToDate()) {
|
|
|
|
// This should only happen in rare scenarios where multiple processes are updating the same file to different values simultaneously
|
|
|
|
// In that case, we don't have any guarantees about which file will ultimately be written
|
|
|
|
TraceEvent(SevWarnAlways, "ClusterFileChangedAfterReplace").detail("Filename", filename).detail("ConnStr", cs.toString());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
} catch( Error &e ) {
|
2018-08-02 05:30:57 +08:00
|
|
|
TraceEvent(SevWarnAlways, "UnableToChangeConnectionFile").error(e).detail("Filename", filename).detail("ConnStr", cs.toString());
|
2017-05-26 04:48:44 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ClusterConnectionFile::setConnectionString( ClusterConnectionString const& conn ) {
|
|
|
|
ASSERT( filename.size() );
|
|
|
|
cs = conn;
|
|
|
|
writeFile();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string ClusterConnectionString::getErrorString( std::string const& source, Error const& e ) {
|
|
|
|
if( e.code() == error_code_connection_string_invalid ) {
|
|
|
|
return format("Invalid connection string `%s: %d %s", source.c_str(), e.code(), e.what());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return format("Unexpected error parsing connection string `%s: %d %s", source.c_str(), e.code(), e.what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string trim( std::string const& connectionString ) {
|
|
|
|
// Strip out whitespace
|
|
|
|
// Strip out characters between a # and a newline
|
|
|
|
std::string trimmed;
|
|
|
|
auto end = connectionString.end();
|
|
|
|
for(auto c=connectionString.begin(); c!=end; ++c) {
|
|
|
|
if (*c == '#') {
|
|
|
|
++c;
|
|
|
|
while(c!=end && *c != '\n' && *c != '\r')
|
|
|
|
++c;
|
|
|
|
if(c == end)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (*c != ' ' && *c != '\n' && *c != '\r' && *c != '\t')
|
|
|
|
trimmed += *c;
|
|
|
|
}
|
|
|
|
return trimmed;
|
|
|
|
}
|
|
|
|
|
|
|
|
ClusterConnectionString::ClusterConnectionString( std::string const& connectionString ) {
|
|
|
|
auto trimmed = trim(connectionString);
|
|
|
|
|
|
|
|
// Split on '@' into key@addrs
|
|
|
|
int pAt = trimmed.find_first_of('@');
|
|
|
|
if (pAt == trimmed.npos)
|
|
|
|
throw connection_string_invalid();
|
|
|
|
std::string key = trimmed.substr(0, pAt);
|
|
|
|
std::string addrs = trimmed.substr(pAt+1);
|
|
|
|
|
|
|
|
parseKey(key);
|
|
|
|
|
|
|
|
coord = NetworkAddress::parseList(addrs);
|
|
|
|
ASSERT( coord.size() > 0 ); // parseList() always returns at least one address if it doesn't throw
|
|
|
|
|
|
|
|
std::sort( coord.begin(), coord.end() );
|
|
|
|
// Check that there are no duplicate addresses
|
|
|
|
if ( std::unique( coord.begin(), coord.end() ) != coord.end() )
|
|
|
|
throw connection_string_invalid();
|
|
|
|
}
|
|
|
|
|
2018-10-06 13:09:58 +08:00
|
|
|
TEST_CASE("/fdbclient/MonitorLeader/parseConnectionString/basic") {
|
2017-05-26 04:48:44 +08:00
|
|
|
std::string input;
|
|
|
|
|
|
|
|
{
|
|
|
|
input = "asdf:2345@1.1.1.1:345";
|
|
|
|
ClusterConnectionString cs(input);
|
|
|
|
ASSERT( input == cs.toString() );
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
input = "0xxdeadbeef:100100100@1.1.1.1:34534,5.1.5.3:23443";
|
|
|
|
ClusterConnectionString cs(input);
|
|
|
|
ASSERT( input == cs.toString() );
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
input = "0xxdeadbeef:100100100@1.1.1.1:34534,5.1.5.3:23443";
|
|
|
|
std::string commented("#start of comment\n");
|
|
|
|
commented += input;
|
|
|
|
commented += "\n";
|
|
|
|
commented += "# asdfasdf ##";
|
|
|
|
|
|
|
|
ClusterConnectionString cs(commented);
|
|
|
|
ASSERT( input == cs.toString() );
|
|
|
|
}
|
|
|
|
|
2019-02-27 10:04:03 +08:00
|
|
|
{
|
|
|
|
input = "0xxdeadbeef:100100100@[::1]:1234,[::1]:1235";
|
|
|
|
std::string commented("#start of comment\n");
|
|
|
|
commented += input;
|
|
|
|
commented += "\n";
|
|
|
|
commented += "# asdfasdf ##";
|
|
|
|
|
|
|
|
ClusterConnectionString cs(commented);
|
|
|
|
ASSERT(input == cs.toString());
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
input = "0xxdeadbeef:100100100@[abcd:dcba::1]:1234,[abcd:dcba::abcd:1]:1234";
|
|
|
|
std::string commented("#start of comment\n");
|
|
|
|
commented += input;
|
|
|
|
commented += "\n";
|
|
|
|
commented += "# asdfasdf ##";
|
|
|
|
|
|
|
|
ClusterConnectionString cs(commented);
|
|
|
|
ASSERT(input == cs.toString());
|
|
|
|
}
|
|
|
|
|
2017-05-26 04:48:44 +08:00
|
|
|
return Void();
|
|
|
|
}
|
|
|
|
|
2019-02-01 04:23:12 +08:00
|
|
|
TEST_CASE("/flow/FlatBuffers/LeaderInfo") {
|
2019-02-01 06:45:42 +08:00
|
|
|
{
|
|
|
|
LeaderInfo in;
|
|
|
|
LeaderInfo out;
|
|
|
|
in.forward = g_random->coinflip();
|
|
|
|
in.changeID = g_random->randomUniqueID();
|
|
|
|
{
|
|
|
|
std::string rndString(g_random->randomInt(10, 400), 'x');
|
|
|
|
for (auto& c : rndString) {
|
|
|
|
c = g_random->randomAlphaNumeric();
|
|
|
|
}
|
|
|
|
in.serializedInfo = rndString;
|
|
|
|
}
|
|
|
|
ObjectWriter writer;
|
|
|
|
writer.serialize(in);
|
|
|
|
Standalone<StringRef> copy = writer.toStringRef();
|
|
|
|
ArenaObjectReader reader(copy.arena(), copy);
|
|
|
|
reader.deserialize(out);
|
|
|
|
ASSERT(in.forward == out.forward);
|
|
|
|
ASSERT(in.changeID == out.changeID);
|
|
|
|
ASSERT(in.serializedInfo == out.serializedInfo);
|
|
|
|
}
|
2019-02-01 04:23:12 +08:00
|
|
|
LeaderInfo leaderInfo;
|
|
|
|
leaderInfo.forward = g_random->coinflip();
|
|
|
|
leaderInfo.changeID = g_random->randomUniqueID();
|
|
|
|
{
|
|
|
|
std::string rndString(g_random->randomInt(10, 400), 'x');
|
|
|
|
for (auto& c : rndString) {
|
|
|
|
c = g_random->randomAlphaNumeric();
|
|
|
|
}
|
|
|
|
leaderInfo.serializedInfo = rndString;
|
|
|
|
}
|
2019-02-01 07:07:27 +08:00
|
|
|
ErrorOr<EnsureTable<Optional<LeaderInfo>>> objIn(leaderInfo);
|
|
|
|
ErrorOr<EnsureTable<Optional<LeaderInfo>>> objOut;
|
2019-02-01 04:23:12 +08:00
|
|
|
Standalone<StringRef> copy;
|
2019-02-01 06:45:42 +08:00
|
|
|
ObjectWriter writer;
|
|
|
|
writer.serialize(objIn);
|
|
|
|
copy = writer.toStringRef();
|
2019-02-01 04:23:12 +08:00
|
|
|
ArenaObjectReader reader(copy.arena(), copy);
|
|
|
|
reader.deserialize(objOut);
|
|
|
|
|
|
|
|
ASSERT(!objOut.isError());
|
2019-02-01 07:07:27 +08:00
|
|
|
ASSERT(objOut.get().asUnderlyingType().present());
|
|
|
|
LeaderInfo outLeader = objOut.get().asUnderlyingType().get();
|
2019-02-01 04:23:12 +08:00
|
|
|
ASSERT(outLeader.changeID == leaderInfo.changeID);
|
|
|
|
ASSERT(outLeader.forward == leaderInfo.forward);
|
|
|
|
ASSERT(outLeader.serializedInfo == leaderInfo.serializedInfo);
|
|
|
|
return Void();
|
|
|
|
}
|
|
|
|
|
2018-10-06 13:09:58 +08:00
|
|
|
TEST_CASE("/fdbclient/MonitorLeader/parseConnectionString/fuzz") {
|
2017-05-26 04:48:44 +08:00
|
|
|
// For a static connection string, add in fuzzed comments and whitespace
|
|
|
|
// SOMEDAY: create a series of random connection strings, rather than the one we started with
|
|
|
|
std::string connectionString = "0xxdeadbeef:100100100@1.1.1.1:34534,5.1.5.3:23443";
|
|
|
|
for(int i=0; i<10000; i++)
|
|
|
|
{
|
|
|
|
std::string output("");
|
|
|
|
auto c=connectionString.begin();
|
|
|
|
while(c!=connectionString.end()) {
|
2019-05-11 05:01:52 +08:00
|
|
|
if(deterministicRandom()->random01() < 0.1) // Add whitespace character
|
|
|
|
output += deterministicRandom()->randomChoice(LiteralStringRef(" \t\n\r"));
|
|
|
|
if(deterministicRandom()->random01() < 0.5) { // Add one of the input characters
|
2017-05-26 04:48:44 +08:00
|
|
|
output += *c;
|
|
|
|
++c;
|
|
|
|
}
|
2019-05-11 05:01:52 +08:00
|
|
|
if(deterministicRandom()->random01() < 0.1) { // Add a comment block
|
2017-05-26 04:48:44 +08:00
|
|
|
output += "#";
|
2019-05-11 05:01:52 +08:00
|
|
|
int charCount = deterministicRandom()->randomInt(0, 20);
|
2017-05-26 04:48:44 +08:00
|
|
|
for(int i = 0; i < charCount; i++) {
|
2019-05-11 05:01:52 +08:00
|
|
|
output += deterministicRandom()->randomChoice(LiteralStringRef("asdfzxcv123345:!@#$#$&()<\"\' \t"));
|
2017-05-26 04:48:44 +08:00
|
|
|
}
|
2019-05-11 05:01:52 +08:00
|
|
|
output += deterministicRandom()->randomChoice(LiteralStringRef("\n\r"));
|
2017-05-26 04:48:44 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ClusterConnectionString cs(output);
|
|
|
|
ASSERT( connectionString == cs.toString() );
|
|
|
|
}
|
|
|
|
return Void();
|
|
|
|
}
|
|
|
|
|
|
|
|
ClusterConnectionString::ClusterConnectionString( vector<NetworkAddress> servers, Key key )
|
|
|
|
: coord(servers)
|
|
|
|
{
|
|
|
|
parseKey(key.toString());
|
|
|
|
}
|
|
|
|
|
|
|
|
void ClusterConnectionString::parseKey( std::string const& key ) {
|
|
|
|
// Check the structure of the given key, and fill in this->key and this->keyDesc
|
|
|
|
|
|
|
|
// The key must contain one (and only one) : character
|
|
|
|
int colon = key.find_first_of(':');
|
|
|
|
if (colon == key.npos)
|
|
|
|
throw connection_string_invalid();
|
|
|
|
std::string desc = key.substr(0, colon);
|
|
|
|
std::string id = key.substr(colon+1);
|
|
|
|
|
|
|
|
// Check that description contains only allowed characters (a-z, A-Z, 0-9, _)
|
|
|
|
for(auto c=desc.begin(); c!=desc.end(); ++c)
|
|
|
|
if (!(isalnum(*c) || *c == '_'))
|
|
|
|
throw connection_string_invalid();
|
|
|
|
|
|
|
|
// Check that ID contains only allowed characters (a-z, A-Z, 0-9)
|
|
|
|
for(auto c=id.begin(); c!=id.end(); ++c)
|
|
|
|
if (!isalnum(*c))
|
|
|
|
throw connection_string_invalid();
|
|
|
|
|
|
|
|
this->key = StringRef(key);
|
|
|
|
this->keyDesc = StringRef(desc);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string ClusterConnectionString::toString() const {
|
|
|
|
std::string s = key.toString();
|
|
|
|
s += '@';
|
|
|
|
for(int i=0; i<coord.size(); i++) {
|
|
|
|
if (i) s += ',';
|
|
|
|
s += coord[i].toString();
|
|
|
|
}
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
ClientCoordinators::ClientCoordinators( Reference<ClusterConnectionFile> ccf )
|
|
|
|
: ccf(ccf)
|
|
|
|
{
|
|
|
|
ClusterConnectionString cs = ccf->getConnectionString();
|
|
|
|
for(auto s = cs.coordinators().begin(); s != cs.coordinators().end(); ++s)
|
|
|
|
clientLeaderServers.push_back( ClientLeaderRegInterface( *s ) );
|
|
|
|
clusterKey = cs.clusterKey();
|
|
|
|
}
|
|
|
|
|
|
|
|
UID WLTOKEN_CLIENTLEADERREG_GETLEADER( -1, 2 );
|
|
|
|
|
|
|
|
ClientLeaderRegInterface::ClientLeaderRegInterface( NetworkAddress remote )
|
2018-10-25 05:59:50 +08:00
|
|
|
: getLeader( Endpoint({remote}, WLTOKEN_CLIENTLEADERREG_GETLEADER) )
|
2017-05-26 04:48:44 +08:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ClientLeaderRegInterface::ClientLeaderRegInterface( INetwork* local ) {
|
|
|
|
getLeader.makeWellKnownEndpoint( WLTOKEN_CLIENTLEADERREG_GETLEADER, TaskCoordination );
|
|
|
|
}
|
|
|
|
|
2019-03-06 13:00:19 +08:00
|
|
|
// Nominee is the worker among all workers that are considered as leader by a coordinator
|
|
|
|
// This function contacts a coordinator coord to ask if the worker is considered as a leader (i.e., if the worker
|
|
|
|
// is a nominee)
|
|
|
|
ACTOR Future<Void> monitorNominee( Key key, ClientLeaderRegInterface coord, AsyncTrigger* nomineeChange, Optional<LeaderInfo> *info, int generation, Reference<AsyncVar<int>> connectedCoordinatorsNum ) {
|
2019-03-13 06:08:08 +08:00
|
|
|
state bool hasCounted = false;
|
2017-05-26 04:48:44 +08:00
|
|
|
loop {
|
2017-09-23 06:06:11 +08:00
|
|
|
state Optional<LeaderInfo> li = wait( retryBrokenPromise( coord.getLeader, GetLeaderRequest( key, info->present() ? info->get().changeID : UID() ), TaskCoordinationReply ) );
|
2019-03-13 06:08:08 +08:00
|
|
|
if (li.present() && !hasCounted && connectedCoordinatorsNum.isValid()) {
|
2019-03-06 13:00:19 +08:00
|
|
|
connectedCoordinatorsNum->set(connectedCoordinatorsNum->get() + 1);
|
2019-03-13 06:08:08 +08:00
|
|
|
hasCounted = true;
|
2019-03-06 13:00:19 +08:00
|
|
|
}
|
2018-08-11 04:57:10 +08:00
|
|
|
wait( Future<Void>(Void()) ); // Make sure we weren't cancelled
|
2017-09-23 06:06:11 +08:00
|
|
|
|
2018-10-31 04:44:37 +08:00
|
|
|
TraceEvent("GetLeaderReply").suppressFor(1.0).detail("Coordinator", coord.getLeader.getEndpoint().getPrimaryAddress()).detail("Nominee", li.present() ? li.get().changeID : UID()).detail("Generation", generation);
|
2017-05-26 04:48:44 +08:00
|
|
|
|
2017-09-21 08:42:12 +08:00
|
|
|
if (li != *info) {
|
|
|
|
*info = li;
|
|
|
|
nomineeChange->trigger();
|
2017-05-26 04:48:44 +08:00
|
|
|
|
|
|
|
if( li.present() && li.get().forward )
|
2018-08-11 04:57:10 +08:00
|
|
|
wait( Future<Void>(Never()) );
|
|
|
|
wait( Future<Void>(Void()) );
|
2017-05-26 04:48:44 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Also used in fdbserver/LeaderElection.actor.cpp!
|
2018-05-31 07:48:04 +08:00
|
|
|
// bool represents if the LeaderInfo is a majority answer or not.
|
2018-06-02 09:23:24 +08:00
|
|
|
// This function also masks the first 7 bits of changeId of the nominees and returns the Leader with masked changeId
|
|
|
|
Optional<std::pair<LeaderInfo, bool>> getLeader( const vector<Optional<LeaderInfo>>& nominees ) {
|
|
|
|
vector<LeaderInfo> maskedNominees;
|
|
|
|
maskedNominees.reserve(nominees.size());
|
|
|
|
for (auto &nominee : nominees) {
|
|
|
|
if (nominee.present()) {
|
|
|
|
maskedNominees.push_back(nominee.get());
|
|
|
|
maskedNominees.back().changeID = UID(maskedNominees.back().changeID.first() & LeaderInfo::mask, maskedNominees.back().changeID.second());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-26 04:48:44 +08:00
|
|
|
// If any coordinator says that the quorum is forwarded, then it is
|
2018-06-02 09:23:24 +08:00
|
|
|
for(int i=0; i<maskedNominees.size(); i++)
|
|
|
|
if (maskedNominees[i].forward)
|
|
|
|
return std::pair<LeaderInfo, bool>(maskedNominees[i], true);
|
2017-05-26 04:48:44 +08:00
|
|
|
|
2018-06-02 09:23:24 +08:00
|
|
|
if(!maskedNominees.size())
|
2018-05-31 07:48:04 +08:00
|
|
|
return Optional<std::pair<LeaderInfo, bool>>();
|
|
|
|
|
2018-06-02 09:23:24 +08:00
|
|
|
std::sort(maskedNominees.begin(), maskedNominees.end(),
|
|
|
|
[](const LeaderInfo& l, const LeaderInfo& r) { return l.changeID < r.changeID; });
|
2018-05-31 07:48:04 +08:00
|
|
|
|
2017-11-28 07:08:03 +08:00
|
|
|
int bestCount = 0;
|
2018-06-02 09:23:24 +08:00
|
|
|
LeaderInfo bestNominee;
|
|
|
|
LeaderInfo currentNominee;
|
2018-05-31 07:48:04 +08:00
|
|
|
int curCount = 0;
|
2018-06-02 09:23:24 +08:00
|
|
|
for (int i = 0; i < maskedNominees.size(); i++) {
|
2018-06-02 09:33:29 +08:00
|
|
|
if (currentNominee == maskedNominees[i]) {
|
2018-06-02 09:23:24 +08:00
|
|
|
curCount++;
|
|
|
|
}
|
|
|
|
else {
|
2018-05-31 07:48:04 +08:00
|
|
|
if (curCount > bestCount) {
|
|
|
|
bestNominee = currentNominee;
|
|
|
|
bestCount = curCount;
|
2017-11-28 07:08:03 +08:00
|
|
|
}
|
2018-06-02 09:23:24 +08:00
|
|
|
currentNominee = maskedNominees[i];
|
|
|
|
curCount = 1;
|
2017-11-28 07:08:03 +08:00
|
|
|
}
|
|
|
|
}
|
2018-06-02 09:23:24 +08:00
|
|
|
if (curCount > bestCount) {
|
|
|
|
bestNominee = currentNominee;
|
|
|
|
bestCount = curCount;
|
|
|
|
}
|
2017-11-28 07:08:03 +08:00
|
|
|
|
2018-06-02 09:33:29 +08:00
|
|
|
bool majority = bestCount >= nominees.size() / 2 + 1;
|
|
|
|
return std::pair<LeaderInfo, bool>(bestNominee, majority);
|
2017-05-26 04:48:44 +08:00
|
|
|
}
|
|
|
|
|
2017-09-23 06:06:11 +08:00
|
|
|
struct MonitorLeaderInfo {
|
|
|
|
bool hasConnected;
|
|
|
|
Reference<ClusterConnectionFile> intermediateConnFile;
|
|
|
|
int generation;
|
2017-05-26 04:48:44 +08:00
|
|
|
|
2017-09-23 06:06:11 +08:00
|
|
|
MonitorLeaderInfo() : hasConnected(false), generation(0) {}
|
|
|
|
explicit MonitorLeaderInfo( Reference<ClusterConnectionFile> intermediateConnFile ) : intermediateConnFile(intermediateConnFile), hasConnected(false), generation(0) {}
|
|
|
|
};
|
|
|
|
|
2019-03-06 13:00:19 +08:00
|
|
|
// Leader is the process that will be elected by coordinators as the cluster controller
|
|
|
|
ACTOR Future<MonitorLeaderInfo> monitorLeaderOneGeneration( Reference<ClusterConnectionFile> connFile, Reference<AsyncVar<Value>> outSerializedLeaderInfo, MonitorLeaderInfo info, Reference<AsyncVar<int>> connectedCoordinatorsNum) {
|
2017-09-23 06:06:11 +08:00
|
|
|
state ClientCoordinators coordinators( info.intermediateConnFile );
|
|
|
|
state AsyncTrigger nomineeChange;
|
|
|
|
state std::vector<Optional<LeaderInfo>> nominees;
|
|
|
|
state Future<Void> allActors;
|
|
|
|
|
|
|
|
nominees.resize(coordinators.clientLeaderServers.size());
|
2017-05-26 04:48:44 +08:00
|
|
|
|
2017-09-23 06:06:11 +08:00
|
|
|
std::vector<Future<Void>> actors;
|
2019-03-06 13:00:19 +08:00
|
|
|
// Ask all coordinators if the worker is considered as a leader (leader nominee) by the coordinator.
|
2017-09-23 06:06:11 +08:00
|
|
|
for(int i=0; i<coordinators.clientLeaderServers.size(); i++)
|
2019-03-06 13:00:19 +08:00
|
|
|
actors.push_back( monitorNominee( coordinators.clusterKey, coordinators.clientLeaderServers[i], &nomineeChange, &nominees[i], info.generation, connectedCoordinatorsNum) );
|
2017-09-23 06:06:11 +08:00
|
|
|
allActors = waitForAll(actors);
|
2017-05-26 04:48:44 +08:00
|
|
|
|
2017-09-23 06:06:11 +08:00
|
|
|
loop {
|
2018-05-31 07:48:04 +08:00
|
|
|
Optional<std::pair<LeaderInfo, bool>> leader = getLeader(nominees);
|
|
|
|
TraceEvent("MonitorLeaderChange").detail("NewLeader", leader.present() ? leader.get().first.changeID : UID(1,1));
|
2017-09-23 06:06:11 +08:00
|
|
|
if (leader.present()) {
|
2018-05-31 07:48:04 +08:00
|
|
|
if( leader.get().first.forward ) {
|
|
|
|
TraceEvent("MonitorLeaderForwarding").detail("NewConnStr", leader.get().first.serializedInfo.toString()).detail("OldConnStr", info.intermediateConnFile->getConnectionString().toString());
|
|
|
|
info.intermediateConnFile = Reference<ClusterConnectionFile>(new ClusterConnectionFile(connFile->getFilename(), ClusterConnectionString(leader.get().first.serializedInfo.toString())));
|
2017-09-23 06:06:11 +08:00
|
|
|
return info;
|
|
|
|
}
|
|
|
|
if(connFile != info.intermediateConnFile) {
|
|
|
|
if(!info.hasConnected) {
|
|
|
|
TraceEvent(SevWarnAlways, "IncorrectClusterFileContentsAtConnection").detail("Filename", connFile->getFilename())
|
|
|
|
.detail("ConnectionStringFromFile", connFile->getConnectionString().toString())
|
|
|
|
.detail("CurrentConnectionString", info.intermediateConnFile->getConnectionString().toString());
|
|
|
|
}
|
|
|
|
connFile->setConnectionString(info.intermediateConnFile->getConnectionString());
|
|
|
|
info.intermediateConnFile = connFile;
|
2017-05-26 04:48:44 +08:00
|
|
|
}
|
|
|
|
|
2017-09-23 06:06:11 +08:00
|
|
|
info.hasConnected = true;
|
|
|
|
connFile->notifyConnected();
|
|
|
|
|
2018-05-31 07:48:04 +08:00
|
|
|
outSerializedLeaderInfo->set( leader.get().first.serializedInfo );
|
2017-05-26 04:48:44 +08:00
|
|
|
}
|
2018-08-11 04:57:10 +08:00
|
|
|
wait( nomineeChange.onTrigger() || allActors );
|
2017-09-23 06:06:11 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-06 13:00:19 +08:00
|
|
|
ACTOR Future<Void> monitorLeaderInternal( Reference<ClusterConnectionFile> connFile, Reference<AsyncVar<Value>> outSerializedLeaderInfo, Reference<AsyncVar<int>> connectedCoordinatorsNum ) {
|
2017-09-23 06:06:11 +08:00
|
|
|
state MonitorLeaderInfo info(connFile);
|
|
|
|
loop {
|
2019-03-06 13:00:19 +08:00
|
|
|
// set the AsyncVar to 0
|
2019-03-12 08:10:06 +08:00
|
|
|
if (connectedCoordinatorsNum.isValid()) connectedCoordinatorsNum->set(0);
|
2019-03-06 13:00:19 +08:00
|
|
|
MonitorLeaderInfo _info = wait( monitorLeaderOneGeneration( connFile, outSerializedLeaderInfo, info, connectedCoordinatorsNum) );
|
2017-09-23 06:06:11 +08:00
|
|
|
info = _info;
|
|
|
|
info.generation++;
|
2019-03-06 13:00:19 +08:00
|
|
|
|
2017-05-26 04:48:44 +08:00
|
|
|
}
|
|
|
|
}
|
2019-01-30 03:51:37 +08:00
|
|
|
|
|
|
|
ACTOR Future<Void> asyncDeserializeClusterInterface(Reference<AsyncVar<Value>> serializedInfo,
|
2019-04-12 04:24:00 +08:00
|
|
|
Reference<AsyncVar<Optional<ClusterInterface>>> outKnownLeader) {
|
2019-01-30 03:51:37 +08:00
|
|
|
state Reference<AsyncVar<Optional<ClusterControllerClientInterface>>> knownLeader(
|
2019-04-12 04:24:00 +08:00
|
|
|
new AsyncVar<Optional<ClusterControllerClientInterface>>{});
|
|
|
|
state Future<Void> deserializer = asyncDeserialize(serializedInfo, knownLeader, g_network->useObjectSerializer());
|
2019-01-30 03:51:37 +08:00
|
|
|
loop {
|
|
|
|
choose {
|
|
|
|
when(wait(deserializer)) { UNSTOPPABLE_ASSERT(false); }
|
|
|
|
when(wait(knownLeader->onChange())) {
|
|
|
|
if (knownLeader->get().present()) {
|
|
|
|
outKnownLeader->set(knownLeader->get().get().clientInterface);
|
|
|
|
} else {
|
|
|
|
outKnownLeader->set(Optional<ClusterInterface>{});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|