1
0
Fork 0

Merge branch 'agency' of https://github.com/arangodb/arangodb into devel

This commit is contained in:
Kaveh Vahedipour 2016-03-23 09:08:37 +00:00
commit 23920c8697
17 changed files with 382 additions and 288 deletions

View File

@ -10,18 +10,8 @@ if (POLICY CMP0037)
cmake_policy(SET CMP0037 NEW) cmake_policy(SET CMP0037 NEW)
endif () endif ()
if (APPLE)
if (NOT DEFINED CMAKE_C_COMPILER)
set(CMAKE_C_COMPILER /usr/bin/clang)
endif ()
if (NOT DEFINED CMAKE_CXX_COMPILER)
set(CMAKE_CXX_COMPILER /usr/bin/clang++)
endif ()
endif ()
option(VERBOSE OFF) option(VERBOSE OFF)
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "deployment target for MacOSX") #set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "deployment target for MacOSX")
project(ArangoDB) project(ArangoDB)

View File

@ -72,13 +72,13 @@ enum AGENT_FAILURE {
template<class T> template<class T>
inline std::ostream& operator<< (std::ostream& l, std::vector<T> const& v) { inline std::ostream& operator<< (std::ostream& l, std::vector<T> const& v) {
for (auto const& i : v) for (auto const& i : v)
l << i << "|"; l << i << ",";
return l; return l;
} }
template<typename T> template<typename T>
inline std::ostream& operator<< (std::ostream& os, std::list<T> const& l) { inline std::ostream& operator<< (std::ostream& os, std::list<T> const& l) {
for (auto const& i : l) for (auto const& i : l)
os << i << "|"; os << i << ",";
return os; return os;
} }
@ -99,19 +99,10 @@ struct AgentConfiguration {
end_point_persist = end_points[id]; end_point_persist = end_points[id];
} }
inline size_t size() const {return end_points.size();} inline size_t size() const {return end_points.size();}
/* inline std::string constituen toString() const { friend std::ostream& operator<<(std::ostream& o, AgentConfiguration const& c) {
std::stringstream out; o << "id(" << c.id << ") min_ping(" << c.min_ping
out << "Configuration\n"; << ") max_ping(" << c.max_ping << ") endpoints(" << c.end_points << ")";
out << " " << "id (" << id << ") min_ping(" << min_ping << ") max_ping(" << max_ping << ")\n"; return o;
out << " " << "endpoints(" << end_points << ")";
return out.str();
}*/
friend std::ostream& operator<< (std::ostream& out, AgentConfiguration const& c) {
out << "Configuration\n";
out << " " << "id (" << c.id << ") min_ping(" << c.min_ping
<< ") max_ping(" << c.max_ping << ")\n";
out << " endpoints(" << c.end_points << ")";
return out;
} }
inline std::string const toString() const { inline std::string const toString() const {
std::stringstream s; std::stringstream s;

View File

@ -35,9 +35,10 @@ using namespace arangodb::velocypack;
namespace arangodb { namespace arangodb {
namespace consensus { namespace consensus {
Agent::Agent () : Thread ("Agent"), _stopping(false) {} Agent::Agent () : Thread ("Agent"), _last_commit_index(0), _stopping(false) {}
Agent::Agent (config_t const& config) : Thread ("Agent"), _config(config) { Agent::Agent (config_t const& config) :
Thread ("Agent"), _config(config), _last_commit_index(0) {
_state.setEndPoint(_config.end_points[this->id()]); _state.setEndPoint(_config.end_points[this->id()]);
_constituent.configure(this); _constituent.configure(this);
_confirmed.resize(size(),0); _confirmed.resize(size(),0);
@ -46,12 +47,20 @@ Agent::Agent (config_t const& config) : Thread ("Agent"), _config(config) {
id_t Agent::id() const { return _config.id;} id_t Agent::id() const { return _config.id;}
Agent::~Agent () { Agent::~Agent () {
// shutdown(); shutdown();
} }
void Agent::start() { State const& Agent::state() const {
return _state;
}
bool Agent::start() {
LOG(INFO) << "AGENCY: Starting constituent thread.";
_constituent.start(); _constituent.start();
_spear_head.start(); LOG(INFO) << "AGENCY: Starting spearhead thread.";
_spearhead.start();
Thread::start();
return true;
} }
term_t Agent::term () const { term_t Agent::term () const {
@ -65,32 +74,24 @@ inline size_t Agent::size() const {
priv_rpc_ret_t Agent::requestVote(term_t t, id_t id, index_t lastLogIndex, priv_rpc_ret_t Agent::requestVote(term_t t, id_t id, index_t lastLogIndex,
index_t lastLogTerm, query_t const& query) { index_t lastLogTerm, query_t const& query) {
if (query != nullptr) { if (query != nullptr) { // record new endpoints
if (query->slice().isArray() || query->slice().isObject()) { if (query->slice().hasKey("endpoints") &&
query->slice().get("endpoints").isArray()) {
size_t j = 0; size_t j = 0;
for (auto const& i : VPackObjectIterator(query->slice())) { for (auto const& i : VPackArrayIterator(query->slice().get("endpoints"))) {
std::string const key(i.key.copyString()); _config.end_points[j++] = i.copyString();
std::string const value(i.value.copyString());
if (key == "endpoint")
_config.end_points[j] = value;
++j;
} }
} }
LOG(WARN) << _config;
} }
return priv_rpc_ret_t( return priv_rpc_ret_t( // vote
_constituent.vote(id, t, lastLogIndex, lastLogTerm), this->term()); _constituent.vote(t, id, lastLogIndex, lastLogTerm), this->term());
} }
config_t const& Agent::config () const { config_t const& Agent::config () const {
return _config; return _config;
} }
void Agent::print (arangodb::LoggerStream& logger) const {
//logger << _config;
}
void Agent::report(status_t status) { void Agent::report(status_t status) {
//_status = status; //_status = status;
} }
@ -99,7 +100,9 @@ id_t Agent::leaderID () const {
return _constituent.leaderID(); return _constituent.leaderID();
} }
void Agent::catchUpReadDB() {}; // TODO bool Agent::leading() const {
return _constituent.leading();
}
bool Agent::waitFor (index_t index, duration_t timeout) { bool Agent::waitFor (index_t index, duration_t timeout) {
@ -121,7 +124,7 @@ bool Agent::waitFor (index_t index, duration_t timeout) {
if (std::chrono::system_clock::now() - start > timeout) { if (std::chrono::system_clock::now() - start > timeout) {
return false; return false;
} }
if (_last_commit_index > index) { if (_last_commit_index >= index) {
return true; return true;
} }
} }
@ -130,51 +133,77 @@ bool Agent::waitFor (index_t index, duration_t timeout) {
} }
void Agent::reportIn (id_t id, index_t index) { void Agent::reportIn (id_t id, index_t index) {
MUTEX_LOCKER(mutexLocker, _confirmedLock); MUTEX_LOCKER(mutexLocker, _ioLock);
if (index > _confirmed[id]) // progress this follower? if (index > _confirmed[id]) // progress this follower?
_confirmed[id] = index; _confirmed[id] = index;
if(index > _last_commit_index) { // progress last commit? if(index > _last_commit_index) { // progress last commit?
size_t n = 0; size_t n = 0;
for (size_t i = 0; i < size(); ++i) { for (size_t i = 0; i < size(); ++i) {
n += (_confirmed[i]>index); n += (_confirmed[i]>=index);
} }
if (n>size()/2) { // enough confirms?
if (n>size()/2) { // catch up read database and commit index
LOG(INFO) << "AGENCY: Critical mass for commiting " << _last_commit_index+1
<< " through " << index << " to read db";
_read_db.apply(_state.slices(_last_commit_index+1, index));
_last_commit_index = index; _last_commit_index = index;
} }
} }
_rest_cv.broadcast(); // wake up REST handlers _rest_cv.broadcast(); // wake up REST handlers
} }
priv_rpc_ret_t Agent::recvAppendEntriesRPC (term_t term, id_t leaderId, index_t prevIndex, bool Agent::recvAppendEntriesRPC (term_t term, id_t leaderId, index_t prevIndex,
term_t prevTerm, index_t leaderCommitIndex, query_t const& queries) { term_t prevTerm, index_t leaderCommitIndex, query_t const& queries) {
//Update commit index //Update commit index
if (queries->slice().type() != VPackValueType::Array) {
LOG(WARN) << "AGENCY: Received malformed entries for appending. Discarting!";
return false;
}
if (queries->slice().length()) {
LOG(INFO) << "AGENCY: Appending "<< queries->slice().length()
<< " entries to state machine.";
} else {
// heart-beat
}
if (_last_commit_index < leaderCommitIndex) {
LOG(INFO) << "Updating last commited index to " << leaderCommitIndex;
}
_last_commit_index = leaderCommitIndex; _last_commit_index = leaderCommitIndex;
// Sanity // Sanity
if (this->term() > term) if (this->term() > term) { // (§5.1)
throw LOWER_TERM_APPEND_ENTRIES_RPC; // (§5.1) LOG(WARN) << "AGENCY: I have a higher term than RPC caller.";
if (!_state.findit(prevIndex, prevTerm)) throw LOWER_TERM_APPEND_ENTRIES_RPC;
throw NO_MATCHING_PREVLOG; // (§5.3) }
if (!_state.findit(prevIndex, prevTerm)) { // (§5.3)
LOG(WARN)
<< "AGENCY: I have no matching set of prevLogIndex/prevLogTerm "
<< "in my own state machine. This is trouble!";
throw NO_MATCHING_PREVLOG;
}
// Delete conflits and append (§5.3) // Delete conflits and append (§5.3)
//for (size_t i = 0; i < queries->slice().length()/2; i+=2) { return _state.log (queries, term, leaderId, prevIndex, prevTerm);
// _state.log (queries->slice()[i ].toString(),
// queries->slice()[i+1].getUInt(), term, leaderId);
//}
return priv_rpc_ret_t(true, this->term());
} }
append_entries_t Agent::sendAppendEntriesRPC ( append_entries_t Agent::sendAppendEntriesRPC (
id_t slave_id, collect_ret_t const& entries) { id_t slave_id/*, collect_ret_t const& entries*/) {
index_t last_confirmed = _confirmed[slave_id];
std::vector<log_t> unconfirmed = _state.get(last_confirmed);
// RPC path // RPC path
std::stringstream path; std::stringstream path;
path << "/_api/agency_priv/appendEntries?term=" << term() << "&leaderId=" path << "/_api/agency_priv/appendEntries?term=" << term() << "&leaderId="
<< id() << "&prevLogIndex=" << entries.prev_log_index << "&prevLogTerm=" << id() << "&prevLogIndex=" << unconfirmed[0].index << "&prevLogTerm="
<< entries.prev_log_term << "&leaderCommit=" << _last_commit_index; << unconfirmed[0].index << "&leaderCommit=" << _last_commit_index;
// Headers // Headers
std::unique_ptr<std::map<std::string, std::string>> headerFields = std::unique_ptr<std::map<std::string, std::string>> headerFields =
@ -182,37 +211,44 @@ append_entries_t Agent::sendAppendEntriesRPC (
// Body // Body
Builder builder; Builder builder;
for (size_t i = 0; i < entries.size(); ++i) { builder.add(VPackValue(VPackValueType::Array));
builder.add ("index", Value(std::to_string(entries.indices[i]))); index_t last = unconfirmed[0].index;
builder.add ("query", Builder(*_state[entries.indices[i]].entry).slice()); for (size_t i = 1; i < unconfirmed.size(); ++i) {
builder.add (VPackValue(VPackValueType::Object));
builder.add ("index", VPackValue(unconfirmed[i].index));
builder.add ("query", VPackSlice(unconfirmed[i].entry->data()));
builder.close();
last = unconfirmed[i].index;
} }
builder.close(); builder.close();
// Send // Send
LOG(INFO) << "AGENCY: Appending " << unconfirmed.size() << " entries up to index "
<< last << " to follower " << slave_id;
arangodb::ClusterComm::instance()->asyncRequest arangodb::ClusterComm::instance()->asyncRequest
("1", 1, _config.end_points[slave_id], ("1", 1, _config.end_points[slave_id],
rest::HttpRequest::HTTP_REQUEST_GET, rest::HttpRequest::HTTP_REQUEST_POST,
path.str(), std::make_shared<std::string>(builder.toString()), headerFields, path.str(), std::make_shared<std::string>(builder.toJson()), headerFields,
std::make_shared<AgentCallback>(this), std::make_shared<AgentCallback>(this, slave_id, last),
1.0, true); 0, true);
return append_entries_t(this->term(), true); return append_entries_t(this->term(), true);
} }
bool Agent::load () { bool Agent::load () {
LOG(INFO) << "Loading persistent state.";
if (!_state.load())
LOG(FATAL) << "Failed to load persistent state on statup.";
LOG(INFO) << "AGENCY: Loading persistent state.";
if (!_state.load())
LOG(FATAL) << "AGENCY: Failed to load persistent state on statup.";
return true; return true;
} }
write_ret_t Agent::write (query_t const& query) { write_ret_t Agent::write (query_t const& query) {
if (_constituent.leading()) { // Leading if (_constituent.leading()) { // Leading
MUTEX_LOCKER(mutexLocker, _confirmedLock); MUTEX_LOCKER(mutexLocker, _ioLock);
std::vector<bool> applied = _spear_head.apply(query); // Apply to spearhead std::vector<bool> applied = _spearhead.apply(query); // Apply to spearhead
std::vector<index_t> indices = std::vector<index_t> indices =
_state.log (query, applied, term(), id()); // Append to log w/ indicies _state.log (query, applied, term(), id()); // Append to log w/ indicies
for (size_t i = 0; i < applied.size(); ++i) { for (size_t i = 0; i < applied.size(); ++i) {
@ -230,8 +266,8 @@ write_ret_t Agent::write (query_t const& query) {
read_ret_t Agent::read (query_t const& query) const { read_ret_t Agent::read (query_t const& query) const {
if (_constituent.leading()) { // We are leading if (_constituent.leading()) { // We are leading
auto result = (_config.size() == 1) ? auto result = (_config.size() == 1) ?
_spear_head.read(query) : _read_db.read (query); _spearhead.read(query) : _read_db.read (query);
return read_ret_t(true,_constituent.leaderID(),result);//(query); //TODO: return read_ret_t(true,_constituent.leaderID(),result);
} else { // We redirect } else { // We redirect
return read_ret_t(false,_constituent.leaderID()); return read_ret_t(false,_constituent.leaderID());
} }
@ -242,48 +278,39 @@ void Agent::run() {
CONDITION_LOCKER(guard, _cv); CONDITION_LOCKER(guard, _cv);
while (!this->isStopping()) { while (!this->isStopping()) {
if (leading())
_cv.wait(100000); _cv.wait(10000000);
else
_cv.wait();
std::vector<collect_ret_t> work(size()); std::vector<collect_ret_t> work(size());
// Collect all unacknowledged // Collect all unacknowledged
for (size_t i = 0; i < size(); ++i) { for (size_t i = 0; i < size(); ++i) {
if (i != id()) { if (i != id()) {
work[i] = _state.collectFrom(_confirmed[i]); sendAppendEntriesRPC(i);
}
} }
} }
// (re-)attempt RPCs
for (size_t j = 0; j < size(); ++j) {
if (j != id() && work[j].size()) {
sendAppendEntriesRPC(j, work[j]);
}
}
// catch up read db
catchUpReadDB();
}
} }
void Agent::beginShutdown() { void Agent::beginShutdown() {
Thread::beginShutdown(); Thread::beginShutdown();
_constituent.beginShutdown(); _constituent.beginShutdown();
// Stop callbacks _spearhead.beginShutdown();
//_agent_callback.shutdown();
// wake up all blocked rest handlers
CONDITION_LOCKER(guard, _cv); CONDITION_LOCKER(guard, _cv);
//guard.broadcast(); guard.broadcast();
} }
bool Agent::lead () { bool Agent::lead () {
rebuildDBs(); rebuildDBs();
_cv.signal();
return true; return true;
} }
bool Agent::rebuildDBs() { bool Agent::rebuildDBs() {
MUTEX_LOCKER(mutexLocker, _dbLock); MUTEX_LOCKER(mutexLocker, _ioLock);
_spearhead.apply(_state.slices());
_read_db.apply(_state.slices());
return true; return true;
} }

View File

@ -75,7 +75,7 @@ public:
/** /**
* @brief Start thread * @brief Start thread
*/ */
void start (); bool start ();
/** /**
* @brief Verbose print of myself * @brief Verbose print of myself
@ -96,6 +96,7 @@ public:
* @brief Leader ID * @brief Leader ID
*/ */
id_t leaderID () const; id_t leaderID () const;
bool leading () const;
bool lead (); bool lead ();
@ -115,21 +116,20 @@ public:
* @brief Received by followers to replicate log entries (§5.3); * @brief Received by followers to replicate log entries (§5.3);
* also used as heartbeat (§5.2). * also used as heartbeat (§5.2).
*/ */
priv_rpc_ret_t recvAppendEntriesRPC (term_t term, id_t leaderId, index_t prevIndex, bool recvAppendEntriesRPC (term_t term, id_t leaderId, index_t prevIndex,
term_t prevTerm, index_t lastCommitIndex, query_t const& queries); term_t prevTerm, index_t lastCommitIndex, query_t const& queries);
/** /**
* @brief Invoked by leader to replicate log entries (§5.3); * @brief Invoked by leader to replicate log entries (§5.3);
* also used as heartbeat (§5.2). * also used as heartbeat (§5.2).
*/ */
append_entries_t sendAppendEntriesRPC (id_t slave_id, append_entries_t sendAppendEntriesRPC (id_t slave_id);
collect_ret_t const& entries);
/** /**
* @brief 1. Deal with appendEntries to slaves. * @brief 1. Deal with appendEntries to slaves.
* 2. Report success of write processes. * 2. Report success of write processes.
*/ */
void run () override final; void run ();
void beginShutdown () override; void beginShutdown () override;
/** /**
@ -147,11 +147,6 @@ public:
*/ */
size_t size() const; size_t size() const;
/**
* @brief Catch up read db to _last_commit_index
*/
void catchUpReadDB();
/** /**
* @brief Rebuild DBs by applying state log to empty DB * @brief Rebuild DBs by applying state log to empty DB
*/ */
@ -162,6 +157,13 @@ public:
*/ */
log_t const& lastLog () const; log_t const& lastLog () const;
friend std::ostream& operator<< (std::ostream& o, Agent const& a) {
o << a.config();
return o;
}
State const& state() const;
private: private:
Constituent _constituent; /**< @brief Leader election delegate */ Constituent _constituent; /**< @brief Leader election delegate */
@ -172,7 +174,7 @@ private:
arangodb::Mutex _uncommitedLock; arangodb::Mutex _uncommitedLock;
Store _spear_head; Store _spearhead;
Store _read_db; Store _read_db;
AgentCallback _agent_callback; AgentCallback _agent_callback;
@ -184,8 +186,7 @@ private:
std::atomic<bool> _stopping; std::atomic<bool> _stopping;
std::vector<index_t> _confirmed; std::vector<index_t> _confirmed;
arangodb::Mutex _confirmedLock; /**< @brief Mutex for modifying _confirmed */ arangodb::Mutex _ioLock;
arangodb::Mutex _dbLock;
}; };

View File

@ -1,5 +1,4 @@
#include "AgentCallback.h" #include "AgentCallback.h"
#include "AgencyCommon.h"
#include "Agent.h" #include "Agent.h"
using namespace arangodb::consensus; using namespace arangodb::consensus;
@ -7,7 +6,8 @@ using namespace arangodb::velocypack;
AgentCallback::AgentCallback() : _agent(0) {} AgentCallback::AgentCallback() : _agent(0) {}
AgentCallback::AgentCallback(Agent* agent) : _agent(agent) {} AgentCallback::AgentCallback(Agent* agent, id_t slave_id, index_t last) :
_agent(agent), _last(last), _slave_id(slave_id) {}
void AgentCallback::shutdown() { void AgentCallback::shutdown() {
_agent = 0; _agent = 0;
@ -15,27 +15,12 @@ void AgentCallback::shutdown() {
bool AgentCallback::operator()(arangodb::ClusterCommResult* res) { bool AgentCallback::operator()(arangodb::ClusterCommResult* res) {
if (res->status == CL_COMM_RECEIVED) { if (res->status == CL_COMM_SENT) {
id_t agent_id;
std::vector<index_t> idx;
std::shared_ptr<VPackBuilder> builder = res->result->getBodyVelocyPack();
if (builder->hasKey("agent_id")) {
agent_id = builder->getKey("agent_id").getUInt();
} else {
return true;
}
if (builder->hasKey("indices")) {
builder->getKey("indices");
if (builder->getKey("indices").isArray()) {
for (size_t i = 0; i < builder->getKey("indices").length(); ++i) {
idx.push_back(builder->getKey("indices")[i].getUInt());
}
}
}
if(_agent) { if(_agent) {
_agent->reportIn (agent_id, idx.back()); _agent->reportIn (_slave_id, _last);
} }
} }
return true; return true;
} }

View File

@ -25,6 +25,7 @@
#define __ARANGODB_CONSENSUS_AGENT_CALLBACK__ #define __ARANGODB_CONSENSUS_AGENT_CALLBACK__
#include "Cluster/ClusterComm.h" #include "Cluster/ClusterComm.h"
#include "AgencyCommon.h"
namespace arangodb { namespace arangodb {
namespace consensus { namespace consensus {
@ -36,14 +37,16 @@ class AgentCallback : public arangodb::ClusterCommCallback {
public: public:
AgentCallback(); AgentCallback();
explicit AgentCallback(Agent* agent); explicit AgentCallback(Agent* agent, id_t slave_id, index_t last);
virtual bool operator()(arangodb::ClusterCommResult*); virtual bool operator()(arangodb::ClusterCommResult*) override final;
void shutdown(); void shutdown();
private: private:
Agent* _agent; Agent* _agent;
index_t _last;
id_t _slave_id;
}; };

View File

@ -35,7 +35,7 @@ using namespace arangodb::basics;
using namespace arangodb::rest; using namespace arangodb::rest;
ApplicationAgency::ApplicationAgency() ApplicationAgency::ApplicationAgency()
: ApplicationFeature("agency"), _size(1), _min_election_timeout(.5), : ApplicationFeature("agency"), _size(1), _min_election_timeout(0.5),
_max_election_timeout(2.0), _election_call_rate_mul(2.5), _max_election_timeout(2.0), _election_call_rate_mul(2.5),
_append_entries_retry_interval(1.0), _append_entries_retry_interval(1.0),
_agent_id(std::numeric_limits<uint32_t>::max()) { _agent_id(std::numeric_limits<uint32_t>::max()) {
@ -69,21 +69,32 @@ void ApplicationAgency::setupOptions(
} }
#include <iostream>
bool ApplicationAgency::prepare() { bool ApplicationAgency::prepare() {
if (_disabled) { if (_disabled) {
return true; return true;
} }
if (_size < 1) if (_size < 1) {
LOG(FATAL) << "agency must have size greater 0"; LOG(FATAL) << "AGENCY: agency must have size greater 0";
return false;
}
if (_agent_id == std::numeric_limits<uint32_t>::max())
if (_size % 2 == 0) {
LOG(FATAL) << "AGENCY: agency must have odd number of members";
return false;
}
if (_agent_id == std::numeric_limits<uint32_t>::max()) {
LOG(FATAL) << "agency.id must be specified"; LOG(FATAL) << "agency.id must be specified";
return false;
}
if (_min_election_timeout <= 0.) { if (_min_election_timeout <= 0.) {
LOG(FATAL) << "agency.election-timeout-min must not be negative!"; LOG(FATAL) << "agency.election-timeout-min must not be negative!";
return false;
} else if (_min_election_timeout < .15) { } else if (_min_election_timeout < .15) {
LOG(WARN) << "very short agency.election-timeout-min!"; LOG(WARN) << "very short agency.election-timeout-min!";
} }
@ -91,6 +102,7 @@ bool ApplicationAgency::prepare() {
if (_max_election_timeout <= _min_election_timeout) { if (_max_election_timeout <= _min_election_timeout) {
LOG(FATAL) << "agency.election-timeout-max must not be shorter than or" LOG(FATAL) << "agency.election-timeout-max must not be shorter than or"
<< "equal to agency.election-timeout-min."; << "equal to agency.election-timeout-min.";
return false;
} }
if (_max_election_timeout <= 2*_min_election_timeout) { if (_max_election_timeout <= 2*_min_election_timeout) {
@ -98,8 +110,6 @@ bool ApplicationAgency::prepare() {
} }
_agency_endpoints.resize(_size); _agency_endpoints.resize(_size);
std::iter_swap(_agency_endpoints.begin(),
_agency_endpoints.begin() + _agent_id);
_agent = std::unique_ptr<agent_t>( _agent = std::unique_ptr<agent_t>(
new agent_t(arangodb::consensus::config_t( new agent_t(arangodb::consensus::config_t(
@ -115,6 +125,7 @@ bool ApplicationAgency::start() {
if (_disabled) { if (_disabled) {
return true; return true;
} }
_agent->start(); _agent->start();
return true; return true;
} }

View File

@ -45,7 +45,6 @@ void Constituent::configure(Agent* agent) {
} else { } else {
_votes.resize(size()); _votes.resize(size());
_id = _agent->config().id; _id = _agent->config().id;
LOG(WARN) << " +++ my id is " << _id << "agency size is " << size();
if (_agent->config().notify) {// (notify everyone) if (_agent->config().notify) {// (notify everyone)
notifyAll(); notifyAll();
} }
@ -78,23 +77,26 @@ role_t Constituent::role () const {
} }
void Constituent::follow (term_t term) { void Constituent::follow (term_t term) {
if (_role > FOLLOWER) if (_role != FOLLOWER) {
LOG(WARN) << "Converted to follower in term " << _term ; LOG(WARN) << "Role change: Converted to follower in term " << _term ;
}
_term = term; _term = term;
_votes.assign(_votes.size(),false); // void all votes _votes.assign(_votes.size(),false); // void all votes
_role = FOLLOWER; _role = FOLLOWER;
} }
void Constituent::lead () { void Constituent::lead () {
if (_role < LEADER) if (_role != LEADER) {
LOG(WARN) << "Converted to leader in term " << _term ; LOG(WARN) << "Role change: Converted to leader in term " << _term ;
_role = LEADER;
_agent->lead(); // We need to rebuild spear_head and read_db; _agent->lead(); // We need to rebuild spear_head and read_db;
} }
_role = LEADER;
_leader_id = _id;
}
void Constituent::candidate () { void Constituent::candidate () {
if (_role != CANDIDATE) if (_role != CANDIDATE)
LOG(WARN) << "Converted to candidate in term " << _term ; LOG(WARN) << "Role change: Converted to candidate in term " << _term ;
_role = CANDIDATE; _role = CANDIDATE;
} }
@ -134,21 +136,21 @@ size_t Constituent::notifyAll () {
path << "/_api/agency_priv/notifyAll?term=" << _term << "&agencyId=" << _id; path << "/_api/agency_priv/notifyAll?term=" << _term << "&agencyId=" << _id;
// Body contains endpoints // Body contains endpoints list
Builder body; Builder body;
body.add(VPackValue(VPackValueType::Object)); body.add(VPackValue(VPackValueType::Object));
body.add("endpoints", VPackValue(VPackValueType::Array));
for (auto const& i : end_points()) { for (auto const& i : end_points()) {
body.add("endpoint", Value(i)); body.add(Value(i));
} }
body.close(); body.close();
LOG(INFO) << body.toString(); body.close();
// Send request to all but myself // Send request to all but myself
for (size_t i = 0; i < size(); ++i) { for (size_t i = 0; i < size(); ++i) {
if (i != _id) { if (i != _id) {
std::unique_ptr<std::map<std::string, std::string>> headerFields = std::unique_ptr<std::map<std::string, std::string>> headerFields =
std::make_unique<std::map<std::string, std::string> >(); std::make_unique<std::map<std::string, std::string> >();
LOG(INFO) << i << " notify " << end_point(i) << path.str() ;
results[i] = arangodb::ClusterComm::instance()->asyncRequest( results[i] = arangodb::ClusterComm::instance()->asyncRequest(
"1", 1, end_point(i), rest::HttpRequest::HTTP_REQUEST_POST, path.str(), "1", 1, end_point(i), rest::HttpRequest::HTTP_REQUEST_POST, path.str(),
std::make_shared<std::string>(body.toString()), headerFields, nullptr, std::make_shared<std::string>(body.toString()), headerFields, nullptr,
@ -161,9 +163,6 @@ size_t Constituent::notifyAll () {
bool Constituent::vote ( bool Constituent::vote (
term_t term, id_t leaderId, index_t prevLogIndex, term_t prevLogTerm) { term_t term, id_t leaderId, index_t prevLogIndex, term_t prevLogTerm) {
LOG(WARN) << "term (" << term << "," << _term << ")" ;
if (leaderId == _id) { // Won't vote for myself should never happen. if (leaderId == _id) { // Won't vote for myself should never happen.
return false; // TODO: Assertion? return false; // TODO: Assertion?
} else { } else {
@ -172,7 +171,7 @@ bool Constituent::vote (
_cast = true; // Note that I voted this time around. _cast = true; // Note that I voted this time around.
_leader_id = leaderId; // The guy I voted for I assume leader. _leader_id = leaderId; // The guy I voted for I assume leader.
if (_role>FOLLOWER) if (_role>FOLLOWER)
follow (term); follow (_term);
_cv.signal(); _cv.signal();
return true; return true;
} else { // Myself running or leading } else { // Myself running or leading
@ -213,11 +212,10 @@ void Constituent::callElection() {
"1", 1, _agent->config().end_points[i], rest::HttpRequest::HTTP_REQUEST_GET, "1", 1, _agent->config().end_points[i], rest::HttpRequest::HTTP_REQUEST_GET,
path.str(), std::make_shared<std::string>(body), headerFields, nullptr, path.str(), std::make_shared<std::string>(body), headerFields, nullptr,
_agent->config().min_ping, true); _agent->config().min_ping, true);
LOG(INFO) << _agent->config().end_points[i];
} }
} }
std::this_thread::sleep_for(sleepFor(0.0, .5*_agent->config().min_ping)); // Wait timeout std::this_thread::sleep_for(sleepFor(.5*_agent->config().min_ping, .8*_agent->config().min_ping)); // Wait timeout
for (size_t i = 0; i < _agent->config().end_points.size(); ++i) { // Collect votes for (size_t i = 0; i < _agent->config().end_points.size(); ++i) { // Collect votes
if (i != _id && end_point(i) != "") { if (i != _id && end_point(i) != "") {
@ -234,7 +232,6 @@ void Constituent::callElection() {
for (auto const& it : VPackObjectIterator(body->slice())) { for (auto const& it : VPackObjectIterator(body->slice())) {
std::string const key(it.key.copyString()); std::string const key(it.key.copyString());
if (key == "term") { if (key == "term") {
LOG(WARN) << key << " " <<it.value.getUInt();
if (it.value.isUInt()) { if (it.value.isUInt()) {
if (it.value.getUInt() > _term) { // follow? if (it.value.getUInt() > _term) { // follow?
follow(it.value.getUInt()); follow(it.value.getUInt());
@ -248,7 +245,6 @@ void Constituent::callElection() {
} }
} }
} }
LOG(WARN) << body->toJson();
} }
} else { // Request failed } else { // Request failed
_votes[i] = false; _votes[i] = false;
@ -262,11 +258,10 @@ void Constituent::callElection() {
yea++; yea++;
} }
} }
LOG(WARN) << "votes for me" << yea;
if (yea > size()/2){ if (yea > size()/2){
lead(); lead();
} else { } else {
candidate(); follow(_term);
} }
} }
@ -274,20 +269,16 @@ void Constituent::beginShutdown() {
Thread::beginShutdown(); Thread::beginShutdown();
} }
#include <iostream>
void Constituent::run() { void Constituent::run() {
// Always start off as follower // Always start off as follower
while (!this->isStopping() && size() > 1) { while (!this->isStopping() && size() > 1) {
if (_role == FOLLOWER) { if (_role == FOLLOWER) {
bool cast;
{
CONDITION_LOCKER (guard, _cv);
_cast = false; // New round set not cast vote _cast = false; // New round set not cast vote
_cv.wait( // Sleep for random time std::this_thread::sleep_for( // Sleep for random time
sleepFord(_agent->config().min_ping, _agent->config().max_ping)*1000000); sleepFor(_agent->config().min_ping, _agent->config().max_ping));
cast = _cast; if (!_cast) {
}
if (!cast) {
candidate(); // Next round, we are running candidate(); // Next round, we are running
} }
} else { } else {
@ -295,6 +286,6 @@ void Constituent::run() {
} }
} }
}; }

View File

@ -35,9 +35,12 @@ using namespace arangodb::velocypack;
using namespace arangodb::rest; using namespace arangodb::rest;
State::State(std::string const& end_point) : _end_point(end_point), _dbs_checked(false) { State::State(std::string const& end_point) : _end_point(end_point), _dbs_checked(false) {
if (!_log.size()) std::shared_ptr<Buffer<uint8_t>> buf = std::make_shared<Buffer<uint8_t>>();
_log.push_back(log_t(index_t(0), term_t(0), id_t(0), arangodb::velocypack::Slice tmp("\x00a",&Options::Defaults);
std::make_shared<Buffer<uint8_t>>())); buf->append(reinterpret_cast<char const*>(tmp.begin()), tmp.byteSize());
if (!_log.size()) {
_log.push_back(log_t(index_t(0), term_t(0), id_t(0), buf));
}
} }
State::~State() {} State::~State() {}
@ -86,6 +89,7 @@ bool State::save (arangodb::velocypack::Slice const& slice, index_t index,
//Leader //Leader
std::vector<index_t> State::log ( std::vector<index_t> State::log (
query_t const& query, std::vector<bool> const& appl, term_t term, id_t lid) { query_t const& query, std::vector<bool> const& appl, term_t term, id_t lid) {
// TODO: Check array
std::vector<index_t> idx(appl.size()); std::vector<index_t> idx(appl.size());
std::vector<bool> good = appl; std::vector<bool> good = appl;
size_t j = 0; size_t j = 0;
@ -93,10 +97,10 @@ std::vector<index_t> State::log (
for (auto const& i : VPackArrayIterator(query->slice())) { for (auto const& i : VPackArrayIterator(query->slice())) {
if (good[j]) { if (good[j]) {
std::shared_ptr<Buffer<uint8_t>> buf = std::make_shared<Buffer<uint8_t>>(); std::shared_ptr<Buffer<uint8_t>> buf = std::make_shared<Buffer<uint8_t>>();
buf->append ((char const*)i.begin(), i.byteSize()); buf->append ((char const*)i[0].begin(), i[0].byteSize());
idx[j] = _log.back().index+1; idx[j] = _log.back().index+1;
_log.push_back(log_t(idx[j], term, lid, buf)); // log to RAM _log.push_back(log_t(idx[j], term, lid, buf)); // log to RAM
save(i, idx[j], term); // log to disk // save(i, idx[j], term); // log to disk
++j; ++j;
} }
} }
@ -104,13 +108,47 @@ std::vector<index_t> State::log (
} }
//Follower //Follower
void State::log (query_t const& query, index_t index, term_t term, id_t lid) { #include <iostream>
MUTEX_LOCKER(mutexLocker, _logLock); bool State::log (query_t const& queries, term_t term, id_t leaderId,
index_t prevLogIndex, term_t prevLogTerm) { // TODO: Throw exc
if (queries->slice().type() != VPackValueType::Array) {
return false;
}
MUTEX_LOCKER(mutexLocker, _logLock); // log entries must stay in order
for (auto const& i : VPackArrayIterator(queries->slice())) {
try {
std::shared_ptr<Buffer<uint8_t>> buf = std::make_shared<Buffer<uint8_t>>(); std::shared_ptr<Buffer<uint8_t>> buf = std::make_shared<Buffer<uint8_t>>();
buf->append ((char const*)query->slice().begin(), query->slice().byteSize()); buf->append ((char const*)i.get("query").begin(), i.get("query").byteSize());
_log.push_back(log_t(index, term, lid, buf)); _log.push_back(log_t(i.get("index").getUInt(), term, leaderId, buf));
} catch (std::exception const& e) {
std::cout << e.what() << std::endl;
}
//save (builder); //save (builder);
} }
return true;
}
std::vector<log_t> State::get (index_t start, index_t end) const {
std::vector<log_t> entries;
MUTEX_LOCKER(mutexLocker, _logLock);
if (end == std::numeric_limits<uint64_t>::max())
end = _log.size() - 1;
for (size_t i = start; i <= end; ++i) {// TODO:: Check bounds
entries.push_back(_log[i]);
}
return entries;
}
std::vector<VPackSlice> State::slices (index_t start, index_t end) const {
std::vector<VPackSlice> slices;
MUTEX_LOCKER(mutexLocker, _logLock);
if (end == std::numeric_limits<uint64_t>::max())
end = _log.size() - 1;
for (size_t i = start; i <= end; ++i) {// TODO:: Check bounds
slices.push_back(VPackSlice(_log[i].entry->data()));
}
return slices;
}
bool State::findit (index_t index, term_t term) { bool State::findit (index_t index, term_t term) {
MUTEX_LOCKER(mutexLocker, _logLock); MUTEX_LOCKER(mutexLocker, _logLock);

View File

@ -52,66 +52,68 @@ class State {
public: public:
/**
* @brief Default constructor /// @brief Default constructor
*/
State (std::string const& end_point = "tcp://localhost:8529"); State (std::string const& end_point = "tcp://localhost:8529");
/**
* @brief Default Destructor /// @brief Default Destructor
*/
virtual ~State(); virtual ~State();
/**
* @brief Append log entry /// @brief Append log entry
*/
void append (query_t const& query); void append (query_t const& query);
/**
* @brief Log entries (leader) /// @brief Log entries (leader)
*/
std::vector<index_t> log (query_t const& query, std::vector<bool> const& indices, term_t term, id_t lid); std::vector<index_t> log (query_t const& query, std::vector<bool> const& indices, term_t term, id_t lid);
/**
* @brief Log entry follower
*/
void log (query_t const& query, index_t, term_t term, id_t lid);
/** /// @brief Log entries (followers)
* @brief Find entry at index with term bool log (query_t const& queries, term_t term, id_t leaderId, index_t prevLogIndex, term_t prevLogTerm);
*/
/// @brief Find entry at index with term
bool findit (index_t index, term_t term); bool findit (index_t index, term_t term);
/**
* @brief Collect all from index on /// @brief Collect all from index on
*/
collect_ret_t collectFrom (index_t index); collect_ret_t collectFrom (index_t index);
/** std::vector<log_t> get (
* @brief log entry at index i index_t = 0, index_t = std::numeric_limits<uint64_t>::max()) const;
*/
std::vector<VPackSlice> slices (
index_t = 0, index_t = std::numeric_limits<uint64_t>::max()) const;
/// @brief log entry at index i
log_t const& operator[](index_t) const; log_t const& operator[](index_t) const;
/**
* @brief last log entry /// @brief last log entry
*/
log_t const& lastLog () const; log_t const& lastLog () const;
/**
* @brief Set endpoint /// @brief Set endpoint
*/
bool setEndPoint (std::string const&); bool setEndPoint (std::string const&);
/**
* @brief Load persisted data from above or start with empty log /// @brief Load persisted data from above or start with empty log
*/
bool load (); bool load ();
friend std::ostream& operator<< (std::ostream& os, State const& s) {
for (auto const& i : s._log)
LOG(INFO) << "index(" << i.index <<") term(" << i.term << ") leader: ("
<< i.leaderId << ") query("
<< VPackSlice(i.entry->data()).toJson() << ")";
return os;
}
private: private:
/**
* @brief Save currentTerm, votedFor, log entries /// @brief Save currentTerm, votedFor, log entries
*/
bool save (arangodb::velocypack::Slice const&, index_t, term_t, bool save (arangodb::velocypack::Slice const&, index_t, term_t,
double timeout = 0.0); double timeout = 0.0);

View File

@ -87,7 +87,7 @@ Node& Node::operator= (Node const& node) { // Assign node
return *this; return *this;
} }
bool Node::operator== (arangodb::velocypack::Slice const& rhs) const { bool Node::operator== (VPackSlice const& rhs) const {
return rhs.equals(slice()); return rhs.equals(slice());
} }
@ -194,7 +194,7 @@ bool Node::addTimeToLive (long millis) {
return true; return true;
} }
bool Node::applies (arangodb::velocypack::Slice const& slice) { bool Node::applies (VPackSlice const& slice) {
if (slice.type() == ValueType::Object) { if (slice.type() == ValueType::Object) {
@ -376,7 +376,16 @@ std::vector<bool> Store::apply (query_t const& query) {
return applied; return applied;
} }
bool Store::check (arangodb::velocypack::Slice const& slice) const { std::vector<bool> Store::apply( std::vector<VPackSlice> const& queries) {
std::vector<bool> applied;
MUTEX_LOCKER(storeLocker, _storeLock);
for (auto const& i : queries) {
applied.push_back(applies(i)); // no precond
}
return applied;
}
bool Store::check (VPackSlice const& slice) const {
if (slice.type() != VPackValueType::Object) { if (slice.type() != VPackValueType::Object) {
LOG(WARN) << "Cannot check precondition: " << slice.toJson(); LOG(WARN) << "Cannot check precondition: " << slice.toJson();
return false; return false;
@ -437,7 +446,7 @@ query_t Store::read (query_t const& queries) const { // list of list of paths
return result; return result;
} }
bool Store::read (arangodb::velocypack::Slice const& query, Builder& ret) const { bool Store::read (VPackSlice const& query, Builder& ret) const {
// Collect all paths // Collect all paths
std::list<std::string> query_strs; std::list<std::string> query_strs;

View File

@ -183,9 +183,15 @@ public:
/// @brief Apply entry in query /// @brief Apply entry in query
std::vector<bool> apply (query_t const& query); std::vector<bool> apply (query_t const& query);
/// @brief Apply entry in query
std::vector<bool> apply (std::vector<Slice> const& query);
/// @brief Read specified query from store /// @brief Read specified query from store
query_t read (query_t const& query) const; query_t read (query_t const& query) const;
/// @brief Begin shutdown of thread
void beginShutdown () override;
private: private:
/// @brief Read individual entry specified in slice into builder /// @brief Read individual entry specified in slice into builder
bool read (arangodb::velocypack::Slice const&, bool read (arangodb::velocypack::Slice const&,
@ -197,9 +203,6 @@ private:
/// @brief Clear entries, whose time to live has expired /// @brief Clear entries, whose time to live has expired
void clearTimeTable (); void clearTimeTable ();
/// @brief Begin shutdown of thread
void beginShutdown () override;
/// @brief Run thread /// @brief Run thread
void run () override final; void run () override final;

View File

@ -99,19 +99,20 @@ inline HttpHandler::status_t RestAgencyHandler::handleWrite () {
errors++; errors++;
} }
} }
/* if (errors == ret.indices.size()) { // epic fail
_response->setResponseCode(HttpResponse::PRECONDITION_FAILED);
} else if (errors == 0) {// full success
} else { //
_response->setResponseCode(HttpResponse::PRECONDITION_FAILED);
}*/
body.close(); body.close();
if (errors > 0) { // epic fail
generateResult(HttpResponse::PRECONDITION_FAILED,body.slice());
} else {// full success
generateResult(body.slice()); generateResult(body.slice());
}
} else { } else {
//_response->setHeader("Location", _agent->config().end_points[ret.redirect]);
generateError(HttpResponse::TEMPORARY_REDIRECT,307); generateError(HttpResponse::TEMPORARY_REDIRECT,307);
return HttpHandler::status_t(HANDLER_DONE);
} }
} else { } else {
generateError(HttpResponse::METHOD_NOT_ALLOWED,405); generateError(HttpResponse::METHOD_NOT_ALLOWED,405);
return HttpHandler::status_t(HANDLER_DONE);
} }
return HttpHandler::status_t(HANDLER_DONE); return HttpHandler::status_t(HANDLER_DONE);
} }
@ -132,19 +133,32 @@ inline HttpHandler::status_t RestAgencyHandler::handleRead () {
generateResult(ret.result->slice()); generateResult(ret.result->slice());
} else { } else {
generateError(HttpResponse::TEMPORARY_REDIRECT,307); generateError(HttpResponse::TEMPORARY_REDIRECT,307);
return HttpHandler::status_t(HANDLER_DONE);
} }
} else { } else {
generateError(HttpResponse::METHOD_NOT_ALLOWED,405); generateError(HttpResponse::METHOD_NOT_ALLOWED,405);
return HttpHandler::status_t(HANDLER_DONE);
} }
return HttpHandler::status_t(HANDLER_DONE); return HttpHandler::status_t(HANDLER_DONE);
} }
#include <sstream>
std::stringstream s;
HttpHandler::status_t RestAgencyHandler::handleTest() { HttpHandler::status_t RestAgencyHandler::handleTest() {
Builder body; Builder body;
body.add(VPackValue(VPackValueType::Object)); body.add(VPackValue(VPackValueType::Object));
body.add("Configuration", Value(_agent->config().toString())); body.add("id", Value(_agent->id()));
body.add("term", Value(_agent->term()));
body.add("leaderId", Value(_agent->leaderID()));
body.add("configuration", Value(_agent->config().toString()));
body.close();
generateResult(body.slice());
return HttpHandler::status_t(HANDLER_DONE);
}
HttpHandler::status_t RestAgencyHandler::handleState() {
Builder body;
body.add(VPackValue(VPackValueType::Array));
for (auto const& i: _agent->state().slices())
body.add(i);
body.close(); body.close();
generateResult(body.slice()); generateResult(body.slice());
return HttpHandler::status_t(HANDLER_DONE); return HttpHandler::status_t(HANDLER_DONE);
@ -171,6 +185,11 @@ HttpHandler::status_t RestAgencyHandler::execute() {
return reportMethodNotAllowed(); return reportMethodNotAllowed();
} }
return handleTest(); return handleTest();
} else if (_request->suffix()[0] == "state") {
if (_request->requestType() != HttpRequest::HTTP_REQUEST_GET) {
return reportMethodNotAllowed();
}
return handleState();
} else { } else {
return reportUnknownMethod(); return reportUnknownMethod();
} }

View File

@ -53,6 +53,7 @@ class RestAgencyHandler : public arangodb::RestBaseHandler {
status_t handleWrite() ; status_t handleWrite() ;
status_t handleTest(); status_t handleTest();
status_t reportMethodNotAllowed(); status_t reportMethodNotAllowed();
status_t handleState();
consensus::Agent* _agent; consensus::Agent* _agent;

View File

@ -75,7 +75,7 @@ inline HttpHandler::status_t RestAgencyPrivHandler::reportMethodNotAllowed () {
generateError(HttpResponse::METHOD_NOT_ALLOWED,405); generateError(HttpResponse::METHOD_NOT_ALLOWED,405);
return HttpHandler::status_t(HANDLER_DONE); return HttpHandler::status_t(HANDLER_DONE);
} }
#include <iostream>
HttpHandler::status_t RestAgencyPrivHandler::execute() { HttpHandler::status_t RestAgencyPrivHandler::execute() {
try { try {
VPackBuilder result; VPackBuilder result;
@ -90,19 +90,19 @@ HttpHandler::status_t RestAgencyPrivHandler::execute() {
id_t id; // leaderId for appendEntries, cadidateId for requestVote id_t id; // leaderId for appendEntries, cadidateId for requestVote
index_t prevLogIndex, leaderCommit; index_t prevLogIndex, leaderCommit;
if (_request->suffix()[0] == "appendEntries") { // appendEntries if (_request->suffix()[0] == "appendEntries") { // appendEntries
if (_request->requestType() != HttpRequest::HTTP_REQUEST_POST) if (_request->requestType() != HttpRequest::HTTP_REQUEST_POST) {
return reportMethodNotAllowed(); return reportMethodNotAllowed();
}
if (readValue("term", term) && if (readValue("term", term) &&
readValue("leaderId", id) && readValue("leaderId", id) &&
readValue("prevLogIndex", prevLogIndex) && readValue("prevLogIndex", prevLogIndex) &&
readValue("prevLogTerm", prevLogTerm) && readValue("prevLogTerm", prevLogTerm) &&
readValue("leaderCommit", leaderCommit)) { // found all values readValue("leaderCommit", leaderCommit)) { // found all values
priv_rpc_ret_t ret = _agent->recvAppendEntriesRPC( bool ret = _agent->recvAppendEntriesRPC(
term, id, prevLogIndex, prevLogTerm, leaderCommit, term, id, prevLogIndex, prevLogTerm, leaderCommit,
_request->toVelocyPack(&opts)); _request->toVelocyPack(&opts));
if (ret.success) { if (ret) { // TODO: more verbose
result.add("term", VPackValue(ret.term)); result.add("success", VPackValue(ret));
result.add("success", VPackValue(ret.success));
} else { } else {
// Should neve get here // Should neve get here
TRI_ASSERT(false); TRI_ASSERT(false);

View File

@ -1866,9 +1866,9 @@ int ArangoServer::runServer(TRI_vocbase_t* vocbase) {
waitForHeartbeat(); waitForHeartbeat();
HttpHandlerFactory::setMaintenance(false); HttpHandlerFactory::setMaintenance(false);
LOG(WARN) << "LOADING PERSISTENT AGENCY STATE"; /* LOG(WARN) << "LOADING PERSISTENT AGENCY STATE";
if(_applicationAgency->agent()!=nullptr) if(_applicationAgency->agent()!=nullptr)
_applicationAgency->agent()->load(); _applicationAgency->agent()->load();*/
// just wait until we are signalled // just wait until we are signalled
_applicationServer->wait(); _applicationServer->wait();

View File

@ -66,6 +66,14 @@ function agencyTestSuite () {
return res; return res;
} }
function writeAgencyRaw(list) {
var res = request({url: agencyServers[whoseTurn] + "/_api/agency/write", method: "POST",
followRedirects: true, body: list,
headers: {"Content-Type": "application/json"}});
res.bodyParsed = JSON.parse(res.body);
return res;
}
function readAndCheck(list) { function readAndCheck(list) {
var res = readAgency(list); var res = readAgency(list);
require ("internal").print(list,res); require ("internal").print(list,res);
@ -131,7 +139,8 @@ function agencyTestSuite () {
writeAndCheck([[{"a":13},{"a":12}]]); writeAndCheck([[{"a":13},{"a":12}]]);
assertEqual(readAndCheck([["a"]]), [{a:13}]); assertEqual(readAndCheck([["a"]]), [{a:13}]);
var res = writeAgency([[{"a":14},{"a":12}]]); var res = writeAgency([[{"a":14},{"a":12}]]);
//assertEqual(res.statusCode, 412); assertEqual(res.statusCode, 412);
//assertEqual(res.bodyParsed, {error:true, successes:[]});
writeAndCheck([[{a:{op:"delete"}}]]); writeAndCheck([[{a:{op:"delete"}}]]);
}, },
@ -149,45 +158,59 @@ function agencyTestSuite () {
testOpNew : function () { testOpNew : function () {
writeAndCheck([[{"a/z":{"new":13}}]]); writeAndCheck([[{"a/z":{"new":13}}]]);
assertEqual(readAndCheck([["a/z"]]), [{"a":{"z":13}}]); assertEqual(readAndCheck([["a/z"]]), [{"a":{"z":13}}]);
writeAndCheck([[{"a/z":{"new":["hello", "world", 1.06]}}]]);
assertEqual(readAndCheck([["a/z"]]), [{"a":{"z":["hello", "world", 1.06]}}]);
}, },
testOpPush : function () { testOpPush : function () {
writeAndCheck([[{"a/b/c":{"op":"push","new":"max"}}]]); writeAndCheck([[{"a/b/c":{"op":"push","new":"max"}}]]);
assertEqual(readAndCheck([["a/b/c"]]), [{a:{b:{c:[1,2,3,"max"]}}}]); assertEqual(readAndCheck([["a/b/c"]]), [{a:{b:{c:[1,2,3,"max"]}}}]);
}, writeAndCheck([[{"a/euler":{"op":"push","new":2.71828182845904523536}}]]);
assertEqual(readAndCheck([["a/euler"]]), [{a:{euler:[2.71828182845904523536]}}]);
testOpPushOnNoneScalar : function () {
writeAndCheck([[{"a/euler":{"op":"set","new":2.71828182845904523536}}]]); writeAndCheck([[{"a/euler":{"op":"set","new":2.71828182845904523536}}]]);
assertEqual(readAndCheck([["a/euler"]]), [{a:{euler:2.71828182845904523536}}]); assertEqual(readAndCheck([["a/euler"]]), [{a:{euler:2.71828182845904523536}}]);
writeAndCheck([[{"a/euler":{"op":"push","new":2.71828182845904523536}}]]); writeAndCheck([[{"a/euler":{"op":"push","new":2.71828182845904523536}}]]);
assertEqual(readAndCheck([["a/euler"]]), [{a:{euler:[2.71828182845904523536]}}]); assertEqual(readAndCheck([["a/euler"]]), [{a:{euler:[2.71828182845904523536]}}]);
}, },
testOpPrepend : function () { testOpRemove : function () {
writeAndCheck([[{"a/b/c":{"op":"prepend","new":3.141592653589793238462643383279502884}}]]); writeAndCheck([[{"a/euler":{"op":"delete"}}]]);
assertEqual(readAndCheck([["a/b/c"]]), [{a:{b:{c:[3.141592653589793238462643383279502884,1,2,3,"max"]}}}]); assertEqual(readAndCheck([["a/euler"]]), [{}]);
}, },
testOpPrependOnScalarValue : function () { testOpPrepend : function () {
writeAndCheck([[{"a/e":{"op":"prepend","new":3.141592653589793238462643383279502884}}]]); writeAndCheck([[{"a/b/c":{"op":"prepend","new":3.141592653589793}}]]);
assertEqual(readAndCheck([["a/e"]]), [{a:{e:[3.141592653589793238462643383279502884]}}]); assertEqual(readAndCheck([["a/b/c"]]), [{a:{b:{c:[3.141592653589793,1,2,3,"max"]}}}]);
writeAndCheck([[{"a/euler":{"op":"prepend","new":2.71828182845904523536}}]]);
assertEqual(readAndCheck([["a/euler"]]), [{a:{euler:[2.71828182845904523536]}}]);
writeAndCheck([[{"a/euler":{"op":"set","new":2.71828182845904523536}}]]);
assertEqual(readAndCheck([["a/euler"]]), [{a:{euler:2.71828182845904523536}}]);
writeAndCheck([[{"a/euler":{"op":"prepend","new":2.71828182845904523536}}]]);
assertEqual(readAndCheck([["a/euler"]]), [{a:{euler:[2.71828182845904523536]}}]);
writeAndCheck([[{"a/euler":{"op":"prepend","new":1.25e-6}}]]);
assertEqual(readAndCheck([["a/euler"]]), [{a:{euler:[1.25e-6,2.71828182845904523536]}}]);
}, },
testOpShift : function () { testOpShift : function () {
writeAndCheck([[{"a/e":{"op":"shift"}}]]); writeAndCheck([[{"a/f":{"op":"shift"}}]]); // none before
assertEqual(readAndCheck([["a/e"]]), [{a:{e:[]}}]); assertEqual(readAndCheck([["a/f"]]), [{a:{f:[]}}]);
writeAndCheck([[{"a/e":{"op":"shift"}}]]); // on empty array
assertEqual(readAndCheck([["a/f"]]), [{a:{f:[]}}]);
writeAndCheck([[{"a/b/c":{"op":"shift"}}]]); // on existing array
assertEqual(readAndCheck([["a/b/c"]]), [{a:{b:{c:[1,2,3,"max"]}}}]);
writeAndCheck([[{"a/b/d":{"op":"shift"}}]]); // on existing scalar
assertEqual(readAndCheck([["a/b/d"]]), [{a:{b:{d:[]}}}]);
}, },
testOpShiftOnEmpty : function () { testOpPop : function () {
writeAndCheck([[{"a/e":{"op":"shift"}}]]); writeAndCheck([[{"a/f":{"op":"pop"}}]]); // none before
assertEqual(readAndCheck([["a/e"]]), [{a:{e:[]}}]); assertEqual(readAndCheck([["a/f"]]), [{a:{f:[]}}]);
}, writeAndCheck([[{"a/e":{"op":"pop"}}]]); // on empty array
assertEqual(readAndCheck([["a/f"]]), [{a:{f:[]}}]);
testOpShiftOnScalar : function () { writeAndCheck([[{"a/b/c":{"op":"pop"}}]]); // on existing array
writeAndCheck([[{"a/euler":2.71828182845904523536}]]); assertEqual(readAndCheck([["a/b/c"]]), [{a:{b:{c:[1,2,3]}}}]);
assertEqual(readAndCheck([["a/euler"]]), [{a:{euler:2.71828182845904523536}}]); writeAndCheck([[{"a/b/d":{"op":"pop"}}]]); // on existing scalar
writeAndCheck([[{"a/euler":{"op":"shift"}}]]); assertEqual(readAndCheck([["a/b/d"]]), [{a:{b:{d:[]}}}]);
assertEqual(readAndCheck([["a/euler"]]), [{a:{euler:[]}}]);
} }
}; };