//////////////////////////////////////////////////////////////////////////////// /// 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 /// @author Max Neunhoeffer //////////////////////////////////////////////////////////////////////////////// #ifndef ARANGOD_CLUSTER_AGENCY_COMM_H #define ARANGOD_CLUSTER_AGENCY_COMM_H 1 #include "Basics/Common.h" #include "Basics/ReadWriteLock.h" #include "Basics/json.h" #include "Rest/HttpRequest.h" #include #include #include #include namespace arangodb { class Endpoint; namespace httpclient { class GeneralClientConnection; } namespace velocypack { class Builder; class Slice; } class AgencyComm; struct AgencyEndpoint { ////////////////////////////////////////////////////////////////////////////// /// @brief creates an agency endpoint ////////////////////////////////////////////////////////////////////////////// AgencyEndpoint(Endpoint*, arangodb::httpclient::GeneralClientConnection*); ////////////////////////////////////////////////////////////////////////////// /// @brief destroys an agency endpoint ////////////////////////////////////////////////////////////////////////////// ~AgencyEndpoint(); ////////////////////////////////////////////////////////////////////////////// /// @brief the endpoint ////////////////////////////////////////////////////////////////////////////// Endpoint* _endpoint; ////////////////////////////////////////////////////////////////////////////// /// @brief the connection ////////////////////////////////////////////////////////////////////////////// arangodb::httpclient::GeneralClientConnection* _connection; ////////////////////////////////////////////////////////////////////////////// /// @brief whether or not the endpoint is busy ////////////////////////////////////////////////////////////////////////////// bool _busy; }; struct AgencyConnectionOptions { double _connectTimeout; double _requestTimeout; double _lockTimeout; size_t _connectRetries; }; struct AgencyCommResultEntry { uint64_t _index; std::shared_ptr _vpack; bool _isDir; }; enum class AgencyValueOperationType { SET, OBSERVE, UNOBSERVE, PUSH, PREPEND }; enum class AgencySimpleOperationType { INCREMENT_OP, DECREMENT_OP, DELETE_OP, POP_OP, SHIFT_OP }; struct AgencyOperationType { enum {VALUE, SIMPLE} type; union { AgencyValueOperationType value; AgencySimpleOperationType simple; }; // mop: hmmm...explicit implementation...maybe use to_string? std::string toString() const { switch(type) { case VALUE: switch(value) { case AgencyValueOperationType::SET: return "set"; case AgencyValueOperationType::OBSERVE: return "observe"; case AgencyValueOperationType::UNOBSERVE: return "unobserve"; case AgencyValueOperationType::PUSH: return "push"; case AgencyValueOperationType::PREPEND: return "prepend"; default: return "unknown_operation_type"; } break; case SIMPLE: switch(simple) { case AgencySimpleOperationType::INCREMENT_OP: return "increment"; case AgencySimpleOperationType::DECREMENT_OP: return "decrement"; case AgencySimpleOperationType::DELETE_OP: return "delete"; case AgencySimpleOperationType::POP_OP: return "pop"; case AgencySimpleOperationType::SHIFT_OP: return "shift"; default: return "unknown_operation_type"; } default: return "unknown_operation_type"; } } }; struct AgencyPrecondition { typedef enum {NONE, EMPTY, VALUE} Type; AgencyPrecondition(std::string const& key, Type t, bool e); AgencyPrecondition(std::string const& key, Type t, VPackSlice const s); void toVelocyPack(arangodb::velocypack::Builder& builder) const; std::string key; Type type; bool empty; VPackSlice const value; }; struct AgencyOperation { ////////////////////////////////////////////////////////////////////////////// /// @brief constructs an operation ////////////////////////////////////////////////////////////////////////////// AgencyOperation(std::string const& key, AgencySimpleOperationType opType); ////////////////////////////////////////////////////////////////////////////// /// @brief constructs an operation with value ////////////////////////////////////////////////////////////////////////////// AgencyOperation( std::string const& key, AgencyValueOperationType opType, VPackSlice const value ); ////////////////////////////////////////////////////////////////////////////// /// @brief returns to full operation formatted as a vpack slice and put /// it into the argument builder ////////////////////////////////////////////////////////////////////////////// void toVelocyPack(arangodb::velocypack::Builder& builder) const; uint32_t _ttl = 0; VPackSlice _oldValue; private: std::string const _key; AgencyOperationType _opType; VPackSlice _value; }; ////////////////////////////////////////////////////////////////////////////// /// @brief AgencyTransaction base class ////////////////////////////////////////////////////////////////////////////// struct AgencyTransaction { virtual std::string toJson() const = 0; virtual void toVelocyPack(arangodb::velocypack::Builder& builder) const = 0; virtual ~AgencyTransaction() { } virtual bool isWriteTransaction() const = 0; }; struct AgencyWriteTransaction : public AgencyTransaction { ////////////////////////////////////////////////////////////////////////////// /// @brief vector of preconditions ////////////////////////////////////////////////////////////////////////////// std::vector preconditions; ////////////////////////////////////////////////////////////////////////////// /// @brief vector of operations ////////////////////////////////////////////////////////////////////////////// std::vector operations; ////////////////////////////////////////////////////////////////////////////// /// @brief converts the transaction to velocypack ////////////////////////////////////////////////////////////////////////////// void toVelocyPack(arangodb::velocypack::Builder& builder) const override final; ////////////////////////////////////////////////////////////////////////////// /// @brief converts the transaction to json ////////////////////////////////////////////////////////////////////////////// std::string toJson() const override final; ////////////////////////////////////////////////////////////////////////////// /// @brief shortcut to create a transaction with one operation ////////////////////////////////////////////////////////////////////////////// explicit AgencyWriteTransaction(AgencyOperation const& operation) { operations.push_back(operation); } ////////////////////////////////////////////////////////////////////////////// /// @brief shortcut to create a transaction with one operation ////////////////////////////////////////////////////////////////////////////// explicit AgencyWriteTransaction(std::vector const& _operations) : operations(_operations) { } ////////////////////////////////////////////////////////////////////////////// /// @brief shortcut to create a transaction with one operation and a /// precondition ////////////////////////////////////////////////////////////////////////////// explicit AgencyWriteTransaction(AgencyOperation const& operation, AgencyPrecondition const& precondition) { operations.push_back(operation); preconditions.push_back(precondition); } ////////////////////////////////////////////////////////////////////////////// /// @brief shortcut to create a transaction with one operation and a /// precondition ////////////////////////////////////////////////////////////////////////////// explicit AgencyWriteTransaction(std::vector const& _operations, AgencyPrecondition const& precondition) { for (auto const& op : _operations) { operations.push_back(op); } preconditions.push_back(precondition); } ////////////////////////////////////////////////////////////////////////////// /// @brief shortcut to create a transaction with one operation and a /// precondition ////////////////////////////////////////////////////////////////////////////// explicit AgencyWriteTransaction(std::vector const& opers, std::vector const& precs) { for (auto const& op : opers) { operations.push_back(op); } for (auto const& pre : precs) { preconditions.push_back(pre); } } ////////////////////////////////////////////////////////////////////////////// /// @brief default constructor ////////////////////////////////////////////////////////////////////////////// AgencyWriteTransaction() = default; ////////////////////////////////////////////////////////////////////////////// /// @brief return type of transaction ////////////////////////////////////////////////////////////////////////////// bool isWriteTransaction() const override final { return true; } }; struct AgencyReadTransaction : public AgencyTransaction { ////////////////////////////////////////////////////////////////////////////// /// @brief vector of operations ////////////////////////////////////////////////////////////////////////////// std::vector keys; ////////////////////////////////////////////////////////////////////////////// /// @brief converts the transaction to velocypack ////////////////////////////////////////////////////////////////////////////// void toVelocyPack(arangodb::velocypack::Builder& builder) const override final; ////////////////////////////////////////////////////////////////////////////// /// @brief converts the transaction to json ////////////////////////////////////////////////////////////////////////////// std::string toJson() const override final; ////////////////////////////////////////////////////////////////////////////// /// @brief shortcut to create a transaction with one operation ////////////////////////////////////////////////////////////////////////////// explicit AgencyReadTransaction(std::string const& key) { keys.push_back(key); } ////////////////////////////////////////////////////////////////////////////// /// @brief shortcut to create a transaction with more than one operation ////////////////////////////////////////////////////////////////////////////// explicit AgencyReadTransaction(std::vector&& k) : keys(k) { } ////////////////////////////////////////////////////////////////////////////// /// @brief default constructor ////////////////////////////////////////////////////////////////////////////// AgencyReadTransaction() = default; ////////////////////////////////////////////////////////////////////////////// /// @brief return type of transaction ////////////////////////////////////////////////////////////////////////////// bool isWriteTransaction() const override final { return false; } }; struct AgencyCommResult { ////////////////////////////////////////////////////////////////////////////// /// @brief constructs a communication result ////////////////////////////////////////////////////////////////////////////// AgencyCommResult(); ////////////////////////////////////////////////////////////////////////////// /// @brief destroys a communication result ////////////////////////////////////////////////////////////////////////////// ~AgencyCommResult(); ////////////////////////////////////////////////////////////////////////////// /// @brief returns whether the last request was successful ////////////////////////////////////////////////////////////////////////////// inline bool successful() const { return (_statusCode >= 200 && _statusCode <= 299); } ////////////////////////////////////////////////////////////////////////////// /// @brief extract the connected flag from the result ////////////////////////////////////////////////////////////////////////////// bool connected() const; ////////////////////////////////////////////////////////////////////////////// /// @brief extract the http code from the result ////////////////////////////////////////////////////////////////////////////// int httpCode() const; ////////////////////////////////////////////////////////////////////////////// /// @brief extract the error code from the result ////////////////////////////////////////////////////////////////////////////// int errorCode() const; ////////////////////////////////////////////////////////////////////////////// /// @brief extract the error message from the result /// if there is no error, an empty string will be returned ////////////////////////////////////////////////////////////////////////////// std::string errorMessage() const; ////////////////////////////////////////////////////////////////////////////// /// @brief extract the error details from the result /// if there is no error, an empty string will be returned ////////////////////////////////////////////////////////////////////////////// std::string errorDetails() const; ////////////////////////////////////////////////////////////////////////////// /// @brief return the location header (might be empty) ////////////////////////////////////////////////////////////////////////////// std::string const location() const { return _location; } ////////////////////////////////////////////////////////////////////////////// /// @brief return the body (might be empty) ////////////////////////////////////////////////////////////////////////////// std::string const body() const { return _body; } ////////////////////////////////////////////////////////////////////////////// /// @brief flush the internal result buffer ////////////////////////////////////////////////////////////////////////////// void clear(); ////////////////////////////////////////////////////////////////////////////// /// @brief recursively flatten the VelocyPack response into a map /// /// stripKeyPrefix is decoded, as is the _globalPrefix ////////////////////////////////////////////////////////////////////////////// //bool parseVelocyPackNode(arangodb::velocypack::Slice const&, // std::string const&, bool); ////////////////////////////////////////////////////////////////////////////// /// parse an agency result /// note that stripKeyPrefix is a decoded, normal key! ////////////////////////////////////////////////////////////////////////////// //bool parse(std::string const&, bool); //VPackSlice parse(std::string const&); VPackSlice slice(); void setVPack(std::shared_ptr vpack) { _vpack = vpack; } private: std::shared_ptr _vpack; public: std::string _location; std::string _message; std::string _body; std::string _realBody; std::map _values; int _statusCode; bool _connected; }; class AgencyComm { friend struct AgencyCommResult; public: ////////////////////////////////////////////////////////////////////////////// /// @brief cleans up all connections ////////////////////////////////////////////////////////////////////////////// static void cleanup(); ////////////////////////////////////////////////////////////////////////////// /// @brief initialize agency comm channel ////////////////////////////////////////////////////////////////////////////// static bool initialize(); ////////////////////////////////////////////////////////////////////////////// /// @brief disconnects all communication channels ////////////////////////////////////////////////////////////////////////////// static void disconnect(); ////////////////////////////////////////////////////////////////////////////// /// @brief adds an endpoint to the agents list ////////////////////////////////////////////////////////////////////////////// static bool addEndpoint(std::string const&, bool = false); ////////////////////////////////////////////////////////////////////////////// /// @brief checks if an endpoint is present ////////////////////////////////////////////////////////////////////////////// static bool hasEndpoint(std::string const&); ////////////////////////////////////////////////////////////////////////////// /// @brief get a stringified version of the endpoints ////////////////////////////////////////////////////////////////////////////// static std::vector getEndpoints(); ////////////////////////////////////////////////////////////////////////////// /// @brief get a stringified version of the endpoints ////////////////////////////////////////////////////////////////////////////// static std::string getEndpointsString(); ////////////////////////////////////////////////////////////////////////////// /// @brief get a stringified version of the endpoints (unique) ////////////////////////////////////////////////////////////////////////////// static std::string getUniqueEndpointsString(); ////////////////////////////////////////////////////////////////////////////// /// @brief sets the global prefix for all operations ////////////////////////////////////////////////////////////////////////////// static bool setPrefix(std::string const&); ////////////////////////////////////////////////////////////////////////////// /// @brief returns the global prefix for all operations ////////////////////////////////////////////////////////////////////////////// static std::string prefixPath(); static std::string prefix(); ////////////////////////////////////////////////////////////////////////////// /// @brief generate a timestamp ////////////////////////////////////////////////////////////////////////////// static std::string generateStamp(); ////////////////////////////////////////////////////////////////////////////// /// @brief creates a new agency endpoint ////////////////////////////////////////////////////////////////////////////// static AgencyEndpoint* createAgencyEndpoint(std::string const&); ////////////////////////////////////////////////////////////////////////////// /// @brief sends the current server state to the agency ////////////////////////////////////////////////////////////////////////////// AgencyCommResult sendServerState(double ttl); ////////////////////////////////////////////////////////////////////////////// /// @brief gets the backend version ////////////////////////////////////////////////////////////////////////////// std::string getVersion(); ////////////////////////////////////////////////////////////////////////////// /// @brief update a version number in the agency ////////////////////////////////////////////////////////////////////////////// inline bool increaseVersion(std::string const& key) { AgencyCommResult result = increment(key); return result.successful(); } ////////////////////////////////////////////////////////////////////////////// /// @brief creates a directory in the backend ////////////////////////////////////////////////////////////////////////////// AgencyCommResult createDirectory(std::string const&); ////////////////////////////////////////////////////////////////////////////// /// @brief sets a value in the back end as string ////////////////////////////////////////////////////////////////////////////// AgencyCommResult setValue(std::string const&, std::string const&, double); ////////////////////////////////////////////////////////////////////////////// /// @brief sets a value in the back end ////////////////////////////////////////////////////////////////////////////// AgencyCommResult setValue(std::string const&, arangodb::velocypack::Slice const&, double); ////////////////////////////////////////////////////////////////////////////// /// @brief checks whether a key exists ////////////////////////////////////////////////////////////////////////////// bool exists(std::string const&); ////////////////////////////////////////////////////////////////////////////// /// @brief gets one or multiple values from the back end ////////////////////////////////////////////////////////////////////////////// AgencyCommResult getValues(std::string const&); ////////////////////////////////////////////////////////////////////////////// /// @brief increment a value ////////////////////////////////////////////////////////////////////////////// AgencyCommResult increment(std::string const&); ////////////////////////////////////////////////////////////////////////////// /// @brief removes one or multiple values from the back end ////////////////////////////////////////////////////////////////////////////// AgencyCommResult removeValues(std::string const&, bool); ////////////////////////////////////////////////////////////////////////////// /// @brief compares and swaps a single value in the backend /// the CAS condition is whether or not a previous value existed for the key ////////////////////////////////////////////////////////////////////////////// AgencyCommResult casValue(std::string const&, arangodb::velocypack::Slice const&, bool, double, double); ////////////////////////////////////////////////////////////////////////////// /// @brief compares and swaps a single value in the back end /// the CAS condition is whether or not the previous value for the key was /// identical to `oldValue` ////////////////////////////////////////////////////////////////////////////// AgencyCommResult casValue(std::string const&, arangodb::velocypack::Slice const&, arangodb::velocypack::Slice const&, double, double); ////////////////////////////////////////////////////////////////////////////// /// @brief get unique id ////////////////////////////////////////////////////////////////////////////// uint64_t uniqid(uint64_t, double); ////////////////////////////////////////////////////////////////////////////// /// @brief registers a callback on a key ////////////////////////////////////////////////////////////////////////////// bool registerCallback(std::string const& key, std::string const& endpoint); ////////////////////////////////////////////////////////////////////////////// /// @brief unregisters a callback on a key ////////////////////////////////////////////////////////////////////////////// bool unregisterCallback(std::string const& key, std::string const& endpoint); ////////////////////////////////////////////////////////////////////////////// /// @brief acquire a read lock ////////////////////////////////////////////////////////////////////////////// bool lockRead(std::string const&, double, double); ////////////////////////////////////////////////////////////////////////////// /// @brief acquire a write lock ////////////////////////////////////////////////////////////////////////////// bool lockWrite(std::string const&, double, double); ////////////////////////////////////////////////////////////////////////////// /// @brief release a read lock ////////////////////////////////////////////////////////////////////////////// bool unlockRead(std::string const&, double); ////////////////////////////////////////////////////////////////////////////// /// @brief release a write lock ////////////////////////////////////////////////////////////////////////////// bool unlockWrite(std::string const&, double); ////////////////////////////////////////////////////////////////////////////// /// @brief sends a transaction to the agency, handling failover ////////////////////////////////////////////////////////////////////////////// AgencyCommResult sendTransactionWithFailover( AgencyTransaction const&, double timeout = 0.0 ); private: ////////////////////////////////////////////////////////////////////////////// /// @brief acquire a lock ////////////////////////////////////////////////////////////////////////////// bool lock(std::string const&, double, double, arangodb::velocypack::Slice const&); ////////////////////////////////////////////////////////////////////////////// /// @brief release a lock ////////////////////////////////////////////////////////////////////////////// bool unlock(std::string const&, arangodb::velocypack::Slice const&, double); ////////////////////////////////////////////////////////////////////////////// /// @brief pop an endpoint from the queue ////////////////////////////////////////////////////////////////////////////// AgencyEndpoint* popEndpoint(std::string const&); ////////////////////////////////////////////////////////////////////////////// /// @brief reinsert an endpoint into the queue ////////////////////////////////////////////////////////////////////////////// void requeueEndpoint(AgencyEndpoint*, bool); ////////////////////////////////////////////////////////////////////////////// /// @brief construct a URL, without a key ////////////////////////////////////////////////////////////////////////////// std::string buildUrl() const; ////////////////////////////////////////////////////////////////////////////// /// @brief sends a write HTTP request to the agency, handling failover ////////////////////////////////////////////////////////////////////////////// AgencyCommResult sendWithFailover( arangodb::GeneralRequest::RequestType, double, std::string const&, std::string const&, bool ); ////////////////////////////////////////////////////////////////////////////// /// @brief sends data to the URL ////////////////////////////////////////////////////////////////////////////// AgencyCommResult send(arangodb::httpclient::GeneralClientConnection*, arangodb::GeneralRequest::RequestType, double, std::string const&, std::string const&); ////////////////////////////////////////////////////////////////////////////// /// @brief tries to establish a communication channel ////////////////////////////////////////////////////////////////////////////// static bool tryConnect(); ////////////////////////////////////////////////////////////////////////////// /// @brief will initialize agency if it is freshly started ////////////////////////////////////////////////////////////////////////////// bool ensureStructureInitialized(); ////////////////////////////////////////////////////////////////////////////// /// @brief will try to initialize a new agency ////////////////////////////////////////////////////////////////////////////// bool tryInitializeStructure(std::string const& jwtSecret); ////////////////////////////////////////////////////////////////////////////// /// @brief checks if we are responsible for initializing the agency ////////////////////////////////////////////////////////////////////////////// bool shouldInitializeStructure(); private: ////////////////////////////////////////////////////////////////////////////// /// @brief the static global URL prefix ////////////////////////////////////////////////////////////////////////////// static std::string const AGENCY_URL_PREFIX; ////////////////////////////////////////////////////////////////////////////// /// @brief the (variable) global prefix ////////////////////////////////////////////////////////////////////////////// static std::string _globalPrefix; static std::string _globalPrefixStripped; ////////////////////////////////////////////////////////////////////////////// /// @brief endpoints lock ////////////////////////////////////////////////////////////////////////////// static arangodb::basics::ReadWriteLock _globalLock; ////////////////////////////////////////////////////////////////////////////// /// @brief all endpoints ////////////////////////////////////////////////////////////////////////////// static std::list _globalEndpoints; ////////////////////////////////////////////////////////////////////////////// /// @brief global connection options ////////////////////////////////////////////////////////////////////////////// static AgencyConnectionOptions _globalConnectionOptions; ////////////////////////////////////////////////////////////////////////////// /// @brief number of connections per endpoint ////////////////////////////////////////////////////////////////////////////// static size_t const NumConnections = 3; ////////////////////////////////////////////////////////////////////////////// /// @brief initial sleep time ////////////////////////////////////////////////////////////////////////////// static unsigned long const InitialSleepTime = 5000; ////////////////////////////////////////////////////////////////////////////// /// @brief maximum sleep time ////////////////////////////////////////////////////////////////////////////// static unsigned long const MaxSleepTime = 50000; }; } #endif