//////////////////////////////////////////////////////////////////////////////// /// DISCLAIMER /// /// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany /// Copyright 2004-2014 triAGENS 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 Jan Steemann //////////////////////////////////////////////////////////////////////////////// #include "ServerState.h" #include "Basics/ReadLocker.h" #include "Basics/VelocyPackHelper.h" #include "Basics/WriteLocker.h" #include "Logger/Logger.h" #include "Cluster/AgencyComm.h" #include "Cluster/ClusterInfo.h" #include #include using namespace arangodb; using namespace arangodb::basics; //////////////////////////////////////////////////////////////////////////////// /// @brief single instance of ServerState - will live as long as the server is /// running //////////////////////////////////////////////////////////////////////////////// static ServerState Instance; ServerState::ServerState() : _id(), _dataPath(), _logPath(), _arangodPath(), _dbserverConfig(), _coordinatorConfig(), _address(), _lock(), _role(), _idOfPrimary(""), _state(STATE_UNDEFINED), _initialized(false), _clusterEnabled(false), _foxxmaster(""), _foxxmasterQueueupdate(false) { storeRole(ROLE_UNDEFINED); } ServerState::~ServerState() {} //////////////////////////////////////////////////////////////////////////////// /// @brief create the (sole) instance //////////////////////////////////////////////////////////////////////////////// ServerState* ServerState::instance() { return &Instance; } //////////////////////////////////////////////////////////////////////////////// /// @brief get the string representation of a role //////////////////////////////////////////////////////////////////////////////// std::string ServerState::roleToString(ServerState::RoleEnum role) { switch (role) { case ROLE_UNDEFINED: return "UNDEFINED"; case ROLE_SINGLE: return "SINGLE"; case ROLE_PRIMARY: return "PRIMARY"; case ROLE_SECONDARY: return "SECONDARY"; case ROLE_COORDINATOR: return "COORDINATOR"; case ROLE_AGENT: return "AGENT"; } TRI_ASSERT(false); return ""; } //////////////////////////////////////////////////////////////////////////////// /// @brief convert a string to a role //////////////////////////////////////////////////////////////////////////////// ServerState::RoleEnum ServerState::stringToRole(std::string const& value) { if (value == "SINGLE") { return ROLE_SINGLE; } else if (value == "PRIMARY") { return ROLE_PRIMARY; } else if (value == "SECONDARY") { return ROLE_SECONDARY; } else if (value == "COORDINATOR") { return ROLE_COORDINATOR; } return ROLE_UNDEFINED; } //////////////////////////////////////////////////////////////////////////////// /// @brief convert a string representation to a state //////////////////////////////////////////////////////////////////////////////// ServerState::StateEnum ServerState::stringToState(std::string const& value) { if (value == "SHUTDOWN") { return STATE_SHUTDOWN; } // TODO MAX: do we need to understand other states, too? return STATE_UNDEFINED; } //////////////////////////////////////////////////////////////////////////////// /// @brief get the string representation of a state //////////////////////////////////////////////////////////////////////////////// std::string ServerState::stateToString(StateEnum state) { // TODO MAX: cleanup switch (state) { case STATE_UNDEFINED: return "UNDEFINED"; case STATE_STARTUP: return "STARTUP"; case STATE_SERVINGASYNC: return "SERVING"; case STATE_SERVINGSYNC: return "SERVING"; case STATE_STOPPING: return "STOPPING"; case STATE_STOPPED: return "STOPPED"; case STATE_SYNCING: return "SYNCING"; case STATE_INSYNC: return "INSYNC"; case STATE_LOSTPRIMARY: return "LOSTPRIMARY"; case STATE_SERVING: return "SERVING"; case STATE_SHUTDOWN: return "SHUTDOWN"; } TRI_ASSERT(false); return ""; } //////////////////////////////////////////////////////////////////////////////// /// @brief find and set our role //////////////////////////////////////////////////////////////////////////////// void ServerState::findAndSetRoleBlocking() { while (true) { auto role = determineRole(_localInfo, _id); std::string roleString = roleToString(role); LOG_TOPIC(DEBUG, Logger::CLUSTER) << "Found my role: " << roleString; if (storeRole(role)) { break; } sleep(1); } } //////////////////////////////////////////////////////////////////////////////// /// @brief flush the server state (used for testing) //////////////////////////////////////////////////////////////////////////////// void ServerState::flush() { findAndSetRoleBlocking(); } //////////////////////////////////////////////////////////////////////////////// /// @brief get the server role //////////////////////////////////////////////////////////////////////////////// ServerState::RoleEnum ServerState::getRole() { auto role = loadRole(); if (role != ServerState::ROLE_UNDEFINED || !_clusterEnabled) { return role; } TRI_ASSERT(!_id.empty()); findAndSetRoleBlocking(); return loadRole(); } bool ServerState::unregister() { TRI_ASSERT(!getId().empty()); std::string const& id = getId(); std::string localInfoEncoded = StringUtils::urlEncode(_localInfo); AgencyOperation deleteLocalIdMap( "Target/MapLocalToID/" + localInfoEncoded, AgencySimpleOperationType::DELETE_OP); std::vector operations = {deleteLocalIdMap}; auto role = loadRole(); const std::string agencyKey = roleToAgencyKey(role); TRI_ASSERT(isClusterRole(role)); if (role == ROLE_COORDINATOR || role == ROLE_PRIMARY) { operations.push_back( AgencyOperation( "Plan/" + agencyKey + "/" + id, AgencySimpleOperationType::DELETE_OP)); operations.push_back( AgencyOperation( "Current/" + agencyKey + "/" + id, AgencySimpleOperationType::DELETE_OP)); } AgencyWriteTransaction unregisterTransaction(operations); AgencyComm comm; AgencyCommResult result; result = comm.sendTransactionWithFailover(unregisterTransaction); return result.successful(); } //////////////////////////////////////////////////////////////////////////////// /// @brief try to register with a role //////////////////////////////////////////////////////////////////////////////// bool ServerState::registerWithRole(ServerState::RoleEnum role) { if (!getId().empty()) { LOG_TOPIC(INFO, Logger::CLUSTER) << "Registering with role and localinfo. Supplied id is being ignored"; return false; } AgencyComm comm; AgencyCommResult result; std::string localInfoEncoded = StringUtils::urlEncode(_localInfo); result = comm.getValues("Target/MapLocalToID/" + localInfoEncoded); std::string id; bool found = true; if (!result.successful()) { found = false; } else { VPackSlice idSlice = result.slice()[0].get(std::vector( {comm.prefix(), "Target", "MapLocalToID", localInfoEncoded})); if (!idSlice.isString()) { found = false; } else { id = idSlice.copyString(); } } if (!found) { LOG_TOPIC(DEBUG, Logger::CLUSTER) << "Determining id from localinfo failed." << "Continuing with registering ourselves for the first time"; id = createIdForRole(comm, role); } const std::string agencyKey = roleToAgencyKey(role); const std::string planKey = "Plan/" + agencyKey + "/" + id; const std::string currentKey = "Current/" + agencyKey + "/" + id; auto builder = std::make_shared(); result = comm.getValues(planKey); found = true; if (!result.successful()) { found = false; } else { VPackSlice plan = result.slice()[0].get(std::vector( { comm.prefix(), "Plan", agencyKey, id })); if (!plan.isString()) { found = false; } else { builder->add(plan); } } if (!found) { // mop: hmm ... we are registered but not part of the Plan :O // create a plan for ourselves :) builder->add(VPackValue("none")); VPackSlice plan = builder->slice(); comm.setValue(planKey, plan, 0.0); if (!result.successful()) { LOG_TOPIC(ERR, Logger::CLUSTER) << "Couldn't create plan " << result.errorMessage(); return false; } } result = comm.setValue(currentKey, builder->slice(), 0.0); if (!result.successful()) { LOG_TOPIC(ERR, Logger::CLUSTER) << "Could not talk to agency! " << result.errorMessage(); return false; } _id = id; findAndSetRoleBlocking(); LOG_TOPIC(DEBUG, Logger::CLUSTER) << "We successfully announced ourselves as " << roleToString(role) << " and our id is " << id; return true; } ////////////////////////////////////////////////////////////////////////////// /// @brief get the key for a role in the agency ////////////////////////////////////////////////////////////////////////////// std::string ServerState::roleToAgencyKey(ServerState::RoleEnum role) { switch (role) { case ROLE_PRIMARY: return "DBServers"; case ROLE_COORDINATOR: return "Coordinators"; case ROLE_SECONDARY: case ROLE_UNDEFINED: case ROLE_SINGLE: case ROLE_AGENT: {} } return "INVALID_CLUSTER_ROLE"; } ////////////////////////////////////////////////////////////////////////////// /// @brief create an id for a specified role ////////////////////////////////////////////////////////////////////////////// std::string ServerState::createIdForRole( AgencyComm comm, ServerState::RoleEnum role) { std::string const agencyKey = roleToAgencyKey(role); std::string const serverIdPrefix = agencyKey.substr(0, agencyKey.length() - 1); std::string id; VPackBuilder builder; builder.add(VPackValue("none")); VPackSlice idValue = builder.slice(); AgencyCommResult createResult; do { AgencyCommResult result = comm.getValues("Plan/" + agencyKey); if (!result.successful()) { LOG(FATAL) << "Couldn't fetch Plan/" << agencyKey << " from agency. Agency is not initialized?"; FATAL_ERROR_EXIT(); } VPackSlice servers = result.slice()[0].get(std::vector( {comm.prefix(), "Plan", agencyKey})); if (!servers.isObject()) { LOG(FATAL) << "Plan/" << agencyKey << " in agency is no object. " << "Agency not initialized?"; FATAL_ERROR_EXIT(); } // mop: it is not our first run. wait a bit. if (!id.empty()) { sleep(1); } size_t idCounter = 1; VPackSlice entry; do { std::ostringstream idss; idss << std::setw(3) << std::setfill('0') << idCounter++; id = serverIdPrefix + idss.str(); entry = servers.get(id); LOG_TOPIC(TRACE, Logger::STARTUP) << id << " found in existing keys: " << (!entry.isNone()); } while (!entry.isNone()); createResult = comm.casValue("Plan/" + agencyKey + "/" + id, idValue, false, 0.0, 0.0); } while(!createResult.successful()); VPackBuilder localIdBuilder; localIdBuilder.add(VPackValue(id)); VPackSlice localIdValue = localIdBuilder.slice(); AgencyCommResult mapResult = comm.setValue( "Target/MapLocalToID/" + StringUtils::urlEncode(_localInfo), localIdValue, 0.0); if (!mapResult.successful()) { LOG(FATAL) << "Couldn't register Id as localId"; FATAL_ERROR_EXIT(); } return id; } //////////////////////////////////////////////////////////////////////////////// /// @brief set the server role //////////////////////////////////////////////////////////////////////////////// void ServerState::setRole(ServerState::RoleEnum role) { storeRole(role); } //////////////////////////////////////////////////////////////////////////////// /// @brief get the server local info //////////////////////////////////////////////////////////////////////////////// std::string ServerState::getLocalInfo() { READ_LOCKER(readLocker, _lock); return _localInfo; } //////////////////////////////////////////////////////////////////////////////// /// @brief set the server local info //////////////////////////////////////////////////////////////////////////////// void ServerState::setLocalInfo(std::string const& localInfo) { if (localInfo.empty()) { return; } WRITE_LOCKER(writeLocker, _lock); _localInfo = localInfo; } //////////////////////////////////////////////////////////////////////////////// /// @brief get the server id //////////////////////////////////////////////////////////////////////////////// std::string ServerState::getId() { READ_LOCKER(readLocker, _lock); return _id; } //////////////////////////////////////////////////////////////////////////////// /// @brief get the server id //////////////////////////////////////////////////////////////////////////////// std::string ServerState::getPrimaryId() { READ_LOCKER(readLocker, _lock); return _idOfPrimary; } //////////////////////////////////////////////////////////////////////////////// /// @brief set the server id //////////////////////////////////////////////////////////////////////////////// void ServerState::setId(std::string const& id) { if (id.empty()) { return; } WRITE_LOCKER(writeLocker, _lock); _id = id; } //////////////////////////////////////////////////////////////////////////////// /// @brief get the server description //////////////////////////////////////////////////////////////////////////////// std::string ServerState::getDescription() { READ_LOCKER(readLocker, _lock); return _description; } //////////////////////////////////////////////////////////////////////////////// /// @brief set the server description //////////////////////////////////////////////////////////////////////////////// void ServerState::setDescription(std::string const& description) { if (description.empty()) { return; } WRITE_LOCKER(writeLocker, _lock); _description = description; } //////////////////////////////////////////////////////////////////////////////// /// @brief get the server address //////////////////////////////////////////////////////////////////////////////// std::string ServerState::getAddress() { READ_LOCKER(readLocker, _lock); return _address; } //////////////////////////////////////////////////////////////////////////////// /// @brief set the server address //////////////////////////////////////////////////////////////////////////////// void ServerState::setAddress(std::string const& address) { if (address.empty()) { return; } WRITE_LOCKER(writeLocker, _lock); _address = address; } //////////////////////////////////////////////////////////////////////////////// /// @brief get the current state //////////////////////////////////////////////////////////////////////////////// ServerState::StateEnum ServerState::getState() { READ_LOCKER(readLocker, _lock); return _state; } //////////////////////////////////////////////////////////////////////////////// /// @brief set the current state //////////////////////////////////////////////////////////////////////////////// void ServerState::setState(StateEnum state) { bool result = false; auto role = loadRole(); WRITE_LOCKER(writeLocker, _lock); if (state == _state) { return; } if (role == ROLE_PRIMARY) { result = checkPrimaryState(state); } else if (role == ROLE_SECONDARY) { result = checkSecondaryState(state); } else if (role == ROLE_COORDINATOR) { result = checkCoordinatorState(state); } if (result) { LOG_TOPIC(DEBUG, Logger::CLUSTER) << "changing state of " << ServerState::roleToString(role) << " server from " << ServerState::stateToString(_state) << " to " << ServerState::stateToString(state); _state = state; } else { LOG_TOPIC(ERR, Logger::CLUSTER) << "invalid state transition for " << ServerState::roleToString(role) << " server from " << ServerState::stateToString(_state) << " to " << ServerState::stateToString(state); } } //////////////////////////////////////////////////////////////////////////////// /// @brief gets the data path //////////////////////////////////////////////////////////////////////////////// std::string ServerState::getDataPath() { READ_LOCKER(readLocker, _lock); return _dataPath; } //////////////////////////////////////////////////////////////////////////////// /// @brief sets the data path //////////////////////////////////////////////////////////////////////////////// void ServerState::setDataPath(std::string const& value) { WRITE_LOCKER(writeLocker, _lock); _dataPath = value; } //////////////////////////////////////////////////////////////////////////////// /// @brief gets the log path //////////////////////////////////////////////////////////////////////////////// std::string ServerState::getLogPath() { READ_LOCKER(readLocker, _lock); return _logPath; } //////////////////////////////////////////////////////////////////////////////// /// @brief sets the log path //////////////////////////////////////////////////////////////////////////////// void ServerState::setLogPath(std::string const& value) { WRITE_LOCKER(writeLocker, _lock); _logPath = value; } //////////////////////////////////////////////////////////////////////////////// /// @brief gets the arangod path //////////////////////////////////////////////////////////////////////////////// std::string ServerState::getArangodPath() { READ_LOCKER(readLocker, _lock); return _arangodPath; } //////////////////////////////////////////////////////////////////////////////// /// @brief sets the arangod path //////////////////////////////////////////////////////////////////////////////// void ServerState::setArangodPath(std::string const& value) { WRITE_LOCKER(writeLocker, _lock); _arangodPath = value; } //////////////////////////////////////////////////////////////////////////////// /// @brief gets the JavaScript startup path //////////////////////////////////////////////////////////////////////////////// std::string ServerState::getJavaScriptPath() { READ_LOCKER(readLocker, _lock); return _javaScriptStartupPath; } //////////////////////////////////////////////////////////////////////////////// /// @brief sets the arangod path //////////////////////////////////////////////////////////////////////////////// void ServerState::setJavaScriptPath(std::string const& value) { WRITE_LOCKER(writeLocker, _lock); _javaScriptStartupPath = value; } //////////////////////////////////////////////////////////////////////////////// /// @brief gets the DBserver config //////////////////////////////////////////////////////////////////////////////// std::string ServerState::getDBserverConfig() { READ_LOCKER(readLocker, _lock); return _dbserverConfig; } //////////////////////////////////////////////////////////////////////////////// /// @brief sets the DBserver config //////////////////////////////////////////////////////////////////////////////// void ServerState::setDBserverConfig(std::string const& value) { WRITE_LOCKER(writeLocker, _lock); _dbserverConfig = value; } //////////////////////////////////////////////////////////////////////////////// /// @brief gets the coordinator config //////////////////////////////////////////////////////////////////////////////// std::string ServerState::getCoordinatorConfig() { READ_LOCKER(readLocker, _lock); return _coordinatorConfig; } //////////////////////////////////////////////////////////////////////////////// /// @brief sets the coordinator config //////////////////////////////////////////////////////////////////////////////// void ServerState::setCoordinatorConfig(std::string const& value) { WRITE_LOCKER(writeLocker, _lock); _coordinatorConfig = value; } //////////////////////////////////////////////////////////////////////////////// /// @brief redetermine the server role, we do this after a plan change. /// This is needed for automatic failover. This calls determineRole with /// previous values of _info and _id. In particular, the _id will usually /// already be set. If the current role cannot be determined from the /// agency or is not unique, then the system keeps the old role. /// Returns true if there is a change and false otherwise. //////////////////////////////////////////////////////////////////////////////// bool ServerState::redetermineRole() { std::string saveIdOfPrimary = _idOfPrimary; RoleEnum role = determineRole(_localInfo, _id); std::string roleString = roleToString(role); LOG_TOPIC(INFO, Logger::CLUSTER) << "Redetermined role from agency: " << roleString; if (role == ServerState::ROLE_UNDEFINED) { return false; } RoleEnum oldRole = loadRole(); if (role != oldRole) { LOG_TOPIC(INFO, Logger::CLUSTER) << "Changed role to: " << roleString; if (!storeRole(role)) { return false; } return true; } if (_idOfPrimary != saveIdOfPrimary) { LOG_TOPIC(INFO, Logger::CLUSTER) << "The ID of our primary has changed!"; return true; } return false; } //////////////////////////////////////////////////////////////////////////////// /// @brief determine the server role by fetching data from the agency /// Note: this method must be called under the _lock //////////////////////////////////////////////////////////////////////////////// ServerState::RoleEnum ServerState::determineRole(std::string const& info, std::string& id) { if (id.empty()) { int res = lookupLocalInfoToId(info, id); if (res != TRI_ERROR_NO_ERROR) { LOG_TOPIC(ERR, Logger::CLUSTER) << "Could not lookupLocalInfoToId"; return ServerState::ROLE_UNDEFINED; } // When we get here, we have have successfully looked up our id LOG_TOPIC(DEBUG, Logger::CLUSTER) << "Learned my own Id: " << id; setId(id); } ServerState::RoleEnum role = checkCoordinatorsList(id); if (role == ServerState::ROLE_UNDEFINED) { role = checkServersList(id); } // mop: role might still be undefined return role; } //////////////////////////////////////////////////////////////////////////////// /// @brief validate a state transition for a primary server //////////////////////////////////////////////////////////////////////////////// bool ServerState::checkPrimaryState(StateEnum state) { if (state == STATE_STARTUP) { // startup state can only be set once return (_state == STATE_UNDEFINED); } else if (state == STATE_SERVINGASYNC) { return (_state == STATE_STARTUP || _state == STATE_STOPPED); } else if (state == STATE_SERVINGSYNC) { return (_state == STATE_STARTUP || _state == STATE_SERVINGASYNC || _state == STATE_STOPPED); } else if (state == STATE_STOPPING) { return (_state == STATE_SERVINGSYNC || _state == STATE_SERVINGASYNC); } else if (state == STATE_STOPPED) { return (_state == STATE_STOPPING); } else if (state == STATE_SHUTDOWN) { return (_state == STATE_STARTUP || _state == STATE_STOPPED || _state == STATE_SERVINGSYNC || _state == STATE_SERVINGASYNC); } // anything else is invalid return false; } //////////////////////////////////////////////////////////////////////////////// /// @brief validate a state transition for a secondary server //////////////////////////////////////////////////////////////////////////////// bool ServerState::checkSecondaryState(StateEnum state) { if (state == STATE_STARTUP) { // startup state can only be set once return (_state == STATE_UNDEFINED); } else if (state == STATE_SYNCING) { return (_state == STATE_STARTUP || _state == STATE_LOSTPRIMARY); } else if (state == STATE_INSYNC) { return (_state == STATE_SYNCING); } else if (state == STATE_LOSTPRIMARY) { return (_state == STATE_SYNCING || _state == STATE_INSYNC); } else if (state == STATE_SERVING) { return (_state == STATE_STARTUP); } else if (state == STATE_SHUTDOWN) { return (_state == STATE_STARTUP || _state == STATE_SYNCING || _state == STATE_INSYNC || _state == STATE_LOSTPRIMARY); } // anything else is invalid return false; } //////////////////////////////////////////////////////////////////////////////// /// @brief validate a state transition for a coordinator server //////////////////////////////////////////////////////////////////////////////// bool ServerState::checkCoordinatorState(StateEnum state) { if (state == STATE_STARTUP) { // startup state can only be set once return (_state == STATE_UNDEFINED); } else if (state == STATE_SERVING) { return (_state == STATE_STARTUP); } else if (state == STATE_SHUTDOWN) { return (_state == STATE_STARTUP || _state == STATE_SERVING); } // anything else is invalid return false; } //////////////////////////////////////////////////////////////////////////////// /// @brief lookup the server role by scanning Plan/Coordinators for our id //////////////////////////////////////////////////////////////////////////////// ServerState::RoleEnum ServerState::checkCoordinatorsList( std::string const& id) { // fetch value at Plan/Coordinators // we need to do this to determine the server's role std::string const key = "Plan/Coordinators"; AgencyComm comm; AgencyCommResult result = comm.getValues(key); if (!result.successful()) { std::string const endpoints = AgencyComm::getEndpointsString(); LOG_TOPIC(TRACE, Logger::CLUSTER) << "Could not fetch configuration from agency endpoints (" << endpoints << "): got status code " << result._statusCode << ", message: " << result.errorMessage() << ", key: " << key; return ServerState::ROLE_UNDEFINED; } VPackSlice coordinators = result.slice()[0].get(std::vector( {comm.prefix(), "Plan", "Coordinators"})); if (!coordinators.isObject()) { LOG_TOPIC(TRACE, Logger::CLUSTER) << "Got an invalid JSON response for Plan/Coordinators"; return ServerState::ROLE_UNDEFINED; } // check if we can find ourselves in the list returned by the agency VPackSlice me = coordinators.get(id); if (!me.isNone()) { // we are in the list. this means we are a primary server return ServerState::ROLE_COORDINATOR; } return ServerState::ROLE_UNDEFINED; } //////////////////////////////////////////////////////////////////////////////// /// @brief lookup the server id by using the local info //////////////////////////////////////////////////////////////////////////////// int ServerState::lookupLocalInfoToId(std::string const& localInfo, std::string& id) { // fetch value at Plan/DBServers // we need to do this to determine the server's role std::string const key = "Target/MapLocalToID"; int count = 0; while (++count <= 600) { AgencyComm comm; AgencyCommResult result = comm.getValues(key); if (!result.successful()) { std::string const endpoints = AgencyComm::getEndpointsString(); LOG_TOPIC(DEBUG, Logger::STARTUP) << "Could not fetch configuration from agency endpoints (" << endpoints << "): got status code " << result._statusCode << ", message: " << result.errorMessage() << ", key: " << key; } else { VPackSlice slice = result.slice()[0].get(std::vector( {comm.prefix(), "Target", "MapLocalToID"})); if (!slice.isObject()) { LOG_TOPIC(DEBUG, Logger::STARTUP) << "Target/MapLocalToID corrupt: " << "no object."; } else { slice = slice.get(localInfo); if (slice.isObject()) { id = arangodb::basics::VelocyPackHelper::getStringValue(slice, "ID", ""); if (id.empty()) { LOG_TOPIC(ERR, Logger::STARTUP) << "ID not set!"; return TRI_ERROR_CLUSTER_COULD_NOT_DETERMINE_ID; } std::string description = arangodb::basics::VelocyPackHelper::getStringValue( slice, "Description", ""); if (!description.empty()) { setDescription(description); } return TRI_ERROR_NO_ERROR; } } } sleep(1); }; return TRI_ERROR_CLUSTER_COULD_NOT_DETERMINE_ID; } //////////////////////////////////////////////////////////////////////////////// /// @brief lookup the server role by scanning Plan/DBServers for our id //////////////////////////////////////////////////////////////////////////////// ServerState::RoleEnum ServerState::checkServersList(std::string const& id) { // fetch value at Plan/DBServers // we need to do this to determine the server's role std::string const key = "Plan/DBServers"; AgencyComm comm; AgencyCommResult result = comm.getValues(key); if (!result.successful()) { std::string const endpoints = AgencyComm::getEndpointsString(); LOG_TOPIC(TRACE, Logger::CLUSTER) << "Could not fetch configuration from agency endpoints (" << endpoints << "): got status code " << result._statusCode << ", message: " << result.errorMessage() << ", key: " << key; return ServerState::ROLE_UNDEFINED; } ServerState::RoleEnum role = ServerState::ROLE_UNDEFINED; VPackSlice dbservers = result.slice()[0].get(std::vector( {comm.prefix(), "Plan", "DBServers"})); if (!dbservers.isObject()) { LOG_TOPIC(TRACE, Logger::CLUSTER) << "Got an invalid JSON response for Plan/DBServers"; return ServerState::ROLE_UNDEFINED; } // check if we can find ourselves in the list returned by the agency VPackSlice me = dbservers.get(id); if (!me.isNone()) { // we are in the list. this means we are a primary server role = ServerState::ROLE_PRIMARY; } else { // check if we are a secondary... for (auto const& s : VPackObjectIterator(dbservers)) { VPackSlice slice = s.value; std::string name = arangodb::basics::VelocyPackHelper::getStringValue(slice, ""); if (name == id) { role = ServerState::ROLE_SECONDARY; _idOfPrimary = s.key.copyString(); break; } } } return role; } ////////////////////////////////////////////////////////////////////////////// /// @brief store the server role ////////////////////////////////////////////////////////////////////////////// bool ServerState::storeRole(RoleEnum role) { if (isClusterRole(role)) { AgencyComm comm; AgencyCommResult result; if (role == ServerState::ROLE_COORDINATOR) { VPackBuilder builder; try { builder.add(VPackValue("none")); } catch (...) { LOG(FATAL) << "out of memory"; FATAL_ERROR_EXIT(); } // register coordinator AgencyCommResult result = comm.setValue("Current/Coordinators/" + _id, builder.slice(), 0.0); if (!result.successful()) { LOG(FATAL) << "unable to register coordinator in agency"; FATAL_ERROR_EXIT(); } } else if (role == ServerState::ROLE_PRIMARY) { VPackBuilder builder; try { builder.add(VPackValue("none")); } catch (...) { LOG(FATAL) << "out of memory"; FATAL_ERROR_EXIT(); } // register server AgencyCommResult result = comm.setValue("Current/DBServers/" + _id, builder.slice(), 0.0); if (!result.successful()) { LOG(FATAL) << "unable to register db server in agency"; FATAL_ERROR_EXIT(); } } else if (role == ServerState::ROLE_SECONDARY) { std::string keyName = _id; VPackBuilder builder; try { builder.add(VPackValue(keyName)); } catch (...) { LOG(FATAL) << "out of memory"; FATAL_ERROR_EXIT(); } std::string myId ( "Current/DBServers/" + ServerState::instance()->getPrimaryId()); AgencyOperation addMe( myId, AgencyValueOperationType::SET, builder.slice()); AgencyOperation incrementVersion( "Plan/Version", AgencySimpleOperationType::INCREMENT_OP); AgencyPrecondition precondition(myId, AgencyPrecondition::EMPTY, true); AgencyWriteTransaction trx({addMe, incrementVersion}, precondition); // register server AgencyCommResult result = comm.sendTransactionWithFailover(trx, 0.0); if (!result.successful()) { // mop: fail gracefully (allow retry) return false; } } } _role.store(role, std::memory_order_release); return true; } bool ServerState::isFoxxmaster() { return !isRunningInCluster() || _foxxmaster == getId(); } std::string const& ServerState::getFoxxmaster() { return _foxxmaster; } void ServerState::setFoxxmaster(std::string const& foxxmaster) { if (_foxxmaster != foxxmaster) { setFoxxmasterQueueupdate(true); } _foxxmaster = foxxmaster; } bool ServerState::getFoxxmasterQueueupdate() { return _foxxmasterQueueupdate; } void ServerState::setFoxxmasterQueueupdate(bool value) { _foxxmasterQueueupdate = value; }