//////////////////////////////////////////////////////////////////////////////// /// @brief replication request handler /// /// @file /// /// DISCLAIMER /// /// 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 triAGENS GmbH, Cologne, Germany /// /// @author Jan Steemann /// @author Copyright 2012-2014, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// #include "RestReplicationHandler.h" #include "BasicsC/common.h" #include "Basics/JsonHelper.h" #include "BasicsC/conversions.h" #include "BasicsC/files.h" #include "BasicsC/logging.h" #include "HttpServer/HttpServer.h" #include "Replication/InitialSyncer.h" #include "Rest/HttpRequest.h" #include "Utils/EmbeddableTransaction.h" #include "Utils/RestTransactionContext.h" #include "Utils/SingleCollectionWriteTransaction.h" #include "VocBase/compactor.h" #include "VocBase/replication-applier.h" #include "VocBase/replication-dump.h" #include "VocBase/replication-logger.h" #include "VocBase/server.h" #include "VocBase/update-policy.h" using namespace std; using namespace triagens::basics; using namespace triagens::rest; using namespace triagens::arango; // ----------------------------------------------------------------------------- // --SECTION-- initialise static variables // ----------------------------------------------------------------------------- const uint64_t RestReplicationHandler::defaultChunkSize = 16 * 1024; const uint64_t RestReplicationHandler::maxChunkSize = 128 * 1024 * 1024; // ----------------------------------------------------------------------------- // --SECTION-- constructors and destructors // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief constructor //////////////////////////////////////////////////////////////////////////////// RestReplicationHandler::RestReplicationHandler (HttpRequest* request) : RestVocbaseBaseHandler(request) { } //////////////////////////////////////////////////////////////////////////////// /// @brief destructor //////////////////////////////////////////////////////////////////////////////// RestReplicationHandler::~RestReplicationHandler () { } // ----------------------------------------------------------------------------- // --SECTION-- Handler methods // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// {@inheritDoc} //////////////////////////////////////////////////////////////////////////////// Handler::status_t RestReplicationHandler::execute() { // extract the request type const HttpRequest::HttpRequestType type = _request->requestType(); vector const& suffix = _request->suffix(); const size_t len = suffix.size(); if (len >= 1) { const string& command = suffix[0]; if (command == "logger-start") { if (type != HttpRequest::HTTP_REQUEST_PUT) { goto BAD_CALL; } if (isCoordinatorError()) { return status_t(Handler::HANDLER_DONE); } handleCommandLoggerStart(); } else if (command == "logger-stop") { if (type != HttpRequest::HTTP_REQUEST_PUT) { goto BAD_CALL; } if (isCoordinatorError()) { return status_t(Handler::HANDLER_DONE); } handleCommandLoggerStop(); } else if (command == "logger-state") { if (type != HttpRequest::HTTP_REQUEST_GET) { goto BAD_CALL; } handleCommandLoggerState(); } else if (command == "logger-config") { if (type == HttpRequest::HTTP_REQUEST_GET) { handleCommandLoggerGetConfig(); } else { if (type != HttpRequest::HTTP_REQUEST_PUT) { goto BAD_CALL; } handleCommandLoggerSetConfig(); } } else if (command == "logger-follow") { if (type != HttpRequest::HTTP_REQUEST_GET) { goto BAD_CALL; } handleCommandLoggerFollow(); } else if (command == "batch") { if (isCoordinatorError()) { return status_t(Handler::HANDLER_DONE); } handleCommandBatch(); } else if (command == "inventory") { if (type != HttpRequest::HTTP_REQUEST_GET) { goto BAD_CALL; } if (isCoordinatorError()) { return status_t(Handler::HANDLER_DONE); } handleCommandInventory(); } else if (command == "dump") { if (type != HttpRequest::HTTP_REQUEST_GET) { goto BAD_CALL; } if (isCoordinatorError()) { return status_t(Handler::HANDLER_DONE); } handleCommandDump(); } else if (command == "restore-collection") { if (type != HttpRequest::HTTP_REQUEST_PUT) { goto BAD_CALL; } if (isCoordinatorError()) { return status_t(Handler::HANDLER_DONE); } handleCommandRestoreCollection(); } else if (command == "restore-indexes") { if (type != HttpRequest::HTTP_REQUEST_PUT) { goto BAD_CALL; } if (isCoordinatorError()) { return status_t(Handler::HANDLER_DONE); } handleCommandRestoreIndexes(); } else if (command == "restore-data") { if (type != HttpRequest::HTTP_REQUEST_PUT) { goto BAD_CALL; } if (isCoordinatorError()) { return status_t(Handler::HANDLER_DONE); } handleCommandRestoreData(); } else if (command == "sync") { if (type != HttpRequest::HTTP_REQUEST_PUT) { goto BAD_CALL; } if (isCoordinatorError()) { return status_t(Handler::HANDLER_DONE); } handleCommandSync(); } else if (command == "server-id") { if (type != HttpRequest::HTTP_REQUEST_GET) { goto BAD_CALL; } handleCommandServerId(); } else if (command == "applier-config") { if (type == HttpRequest::HTTP_REQUEST_GET) { handleCommandApplierGetConfig(); } else { if (type != HttpRequest::HTTP_REQUEST_PUT) { goto BAD_CALL; } handleCommandApplierSetConfig(); } } else if (command == "applier-start") { if (type != HttpRequest::HTTP_REQUEST_PUT) { goto BAD_CALL; } if (isCoordinatorError()) { return status_t(Handler::HANDLER_DONE); } handleCommandApplierStart(); } else if (command == "applier-stop") { if (type != HttpRequest::HTTP_REQUEST_PUT) { goto BAD_CALL; } if (isCoordinatorError()) { return status_t(Handler::HANDLER_DONE); } handleCommandApplierStop(); } else if (command == "applier-state") { if (type == HttpRequest::HTTP_REQUEST_DELETE) { handleCommandApplierDeleteState(); } else { if (type != HttpRequest::HTTP_REQUEST_GET) { goto BAD_CALL; } handleCommandApplierGetState(); } } else { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "invalid command"); } return status_t(Handler::HANDLER_DONE); } BAD_CALL: if (len != 1) { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_SUPERFLUOUS_SUFFICES, "expecting URL /_api/replication/"); } else { generateError(HttpResponse::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); } return status_t(Handler::HANDLER_DONE); } // ----------------------------------------------------------------------------- // --SECTION-- public static methods // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief comparator to sort collections /// sort order is by collection type first (vertices before edges, this is /// because edges depend on vertices being there), then name //////////////////////////////////////////////////////////////////////////////// int RestReplicationHandler::sortCollections (const void* l, const void* r) { TRI_json_t const* left = JsonHelper::getArrayElement((TRI_json_t const*) l, "parameters"); TRI_json_t const* right = JsonHelper::getArrayElement((TRI_json_t const*) r, "parameters"); int leftType = JsonHelper::getNumericValue(left, "type", (int) TRI_COL_TYPE_DOCUMENT); int rightType = JsonHelper::getNumericValue(right, "type", (int) TRI_COL_TYPE_DOCUMENT); if (leftType != rightType) { return leftType - rightType; } string leftName = JsonHelper::getStringValue(left, "name", ""); string rightName = JsonHelper::getStringValue(right, "name", ""); return strcasecmp(leftName.c_str(), rightName.c_str()); } //////////////////////////////////////////////////////////////////////////////// /// @brief filter a collection based on collection attributes //////////////////////////////////////////////////////////////////////////////// bool RestReplicationHandler::filterCollection (TRI_vocbase_col_t* collection, void* data) { if (collection->_type != (TRI_col_type_t) TRI_COL_TYPE_DOCUMENT && collection->_type != (TRI_col_type_t) TRI_COL_TYPE_EDGE) { // invalid type return false; } bool includeSystem = *((bool*) data); if (! includeSystem && collection->_name[0] == '_') { // exclude all system collections return false; } if (TRI_ExcludeCollectionReplication(collection->_name)) { // collection is excluded from replication if (includeSystem) { // now check if we still should include it in the dump if (! TRI_EqualString(collection->_name, TRI_COL_NAME_USERS) && ! TRI_EqualString(collection->_name, "_aal") && ! TRI_EqualString(collection->_name, "_modules") && ! TRI_EqualString(collection->_name, "_routing")) { return false; } } // _aal, _modules, _routing should be included } // all other cases should be included return true; } // ----------------------------------------------------------------------------- // --SECTION-- private methods // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief creates an error if called on a coordinator server //////////////////////////////////////////////////////////////////////////////// bool RestReplicationHandler::isCoordinatorError () { #ifdef TRI_ENABLE_CLUSTER if (_vocbase->_type == TRI_VOCBASE_TYPE_COORDINATOR) { generateError(HttpResponse::NOT_IMPLEMENTED, TRI_ERROR_CLUSTER_UNSUPPORTED, "replication API is not supported on a coordinator"); return true; } #endif return false; } //////////////////////////////////////////////////////////////////////////////// /// @brief insert the applier action into an action list //////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::insertClient (TRI_voc_tick_t lastServedTick) { bool found; char const* value; value = _request->value("serverId", found); if (found) { TRI_server_id_t serverId = (TRI_server_id_t) StringUtils::uint64(value); if (serverId > 0) { TRI_UpdateClientReplicationLogger(_vocbase->_replicationLogger, serverId, lastServedTick); } } } //////////////////////////////////////////////////////////////////////////////// /// @brief determine the chunk size //////////////////////////////////////////////////////////////////////////////// uint64_t RestReplicationHandler::determineChunkSize () const { // determine chunk size uint64_t chunkSize = defaultChunkSize; bool found; const char* value = _request->value("chunkSize", found); if (found) { // url parameter "chunkSize" was specified chunkSize = StringUtils::uint64(value); // don't allow overly big allocations if (chunkSize > maxChunkSize) { chunkSize = maxChunkSize; } } return chunkSize; } //////////////////////////////////////////////////////////////////////////////// /// @brief starts the replication logger /// /// @RESTHEADER{PUT /_api/replication/logger-start,starts the replication logger} /// /// @RESTDESCRIPTION /// Starts the server's replication logger. Will do nothing if the replication /// logger is already running. /// /// The body of the response contains a JSON object with the following /// attributes: /// /// - `running`: will contain `true` /// /// Note: this method is not supported on a coordinator in a cluster. /// /// @RESTRETURNCODES /// /// @RESTRETURNCODE{200} /// is returned if the logger was started successfully, or was already running. /// /// @RESTRETURNCODE{405} /// is returned when an invalid HTTP method is used. /// /// @RESTRETURNCODE{500} /// is returned if the logger could not be started. /// /// @RESTRETURNCODE{501} /// is returned when this operation is called on a coordinator in a cluster. /// /// @EXAMPLES /// /// Starts the replication logger. /// /// @EXAMPLE_ARANGOSH_RUN{RestReplicationLoggerStart} /// var url = "/_api/replication/logger-start"; /// /// var response = logCurlRequest('PUT', url, ""); /// /// assert(response.code === 200); /// /// logJsonResponse(response); /// @END_EXAMPLE_ARANGOSH_RUN //////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::handleCommandLoggerStart () { assert(_vocbase->_replicationLogger != 0); int res = TRI_StartReplicationLogger(_vocbase->_replicationLogger); if (res != TRI_ERROR_NO_ERROR) { generateError(HttpResponse::SERVER_ERROR, res); return; } TRI_json_t result; TRI_InitArrayJson(TRI_CORE_MEM_ZONE, &result); TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, &result, "running", TRI_CreateBooleanJson(TRI_CORE_MEM_ZONE, true)); generateResult(&result); TRI_DestroyJson(TRI_CORE_MEM_ZONE, &result); } //////////////////////////////////////////////////////////////////////////////// /// @brief stops the replication logger /// /// @RESTHEADER{PUT /_api/replication/logger-stop,stops the replication logger} /// /// @RESTDESCRIPTION /// Stops the server's replication logger. Will do nothing if the replication /// logger is not running. /// /// The body of the response contains a JSON object with the following /// attributes: /// /// - `running`: will contain `false` /// /// Note: this method is not supported on a coordinator in a cluster. /// /// @RESTRETURNCODES /// /// @RESTRETURNCODE{200} /// is returned if the logger was stopped successfully, or was not running /// before. /// /// @RESTRETURNCODE{405} /// is returned when an invalid HTTP method is used. /// /// @RESTRETURNCODE{500} /// is returned if the logger could not be stopped. /// /// @RESTRETURNCODE{501} /// is returned when this operation is called on a coordinator in a cluster. /// /// @EXAMPLES /// /// Starts the replication logger. /// /// @EXAMPLE_ARANGOSH_RUN{RestReplicationLoggerStop} /// var url = "/_api/replication/logger-stop"; /// /// var response = logCurlRequest('PUT', url, ""); /// /// assert(response.code === 200); /// /// logJsonResponse(response); /// @END_EXAMPLE_ARANGOSH_RUN //////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::handleCommandLoggerStop () { assert(_vocbase->_replicationLogger != 0); int res = TRI_StopReplicationLogger(_vocbase->_replicationLogger); if (res != TRI_ERROR_NO_ERROR) { generateError(HttpResponse::SERVER_ERROR, res); return; } TRI_json_t result; TRI_InitArrayJson(TRI_CORE_MEM_ZONE, &result); TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, &result, "running", TRI_CreateBooleanJson(TRI_CORE_MEM_ZONE, false)); generateResult(&result); TRI_DestroyJson(TRI_CORE_MEM_ZONE, &result); } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the state of the replication logger /// /// @RESTHEADER{GET /_api/replication/logger-state,returns the replication logger state} /// /// @RESTDESCRIPTION /// Returns the current state of the server's replication logger. The state will /// include information about whether the logger is running and about the last /// logged tick value. This tick value is important for incremental fetching of /// data. /// /// The state API can be called regardless of whether the logger is currently /// running or not. /// /// The body of the response contains a JSON object with the following /// attributes: /// /// - `state`: the current logger state as a JSON hash array with the following /// sub-attributes: /// /// - `running`: whether or not the logger is running /// /// - `lastLogTick`: the tick value of the latest tick the logger has logged. /// This value can be used for incremental fetching of log data. /// /// - `totalEvents`: total number of events logged since the server was started. /// The value is not reset between multiple stops and re-starts of the logger. /// /// - `time`: the current date and time on the logger server /// /// - `server`: a JSON hash with the following sub-attributes: /// /// - `version`: the logger server's version /// /// - `serverId`: the logger server's id /// /// - `clients`: a list of all replication clients that ever connected to /// the logger since it was started. This list can be used to determine /// approximately how much data the individual clients have already fetched /// from the logger server. Each entry in the list contains a `time` value /// indicating the server time the client last fetched data from the /// replication logger. The `lastServedTick` value of each client indicates /// the latest tick value sent to the client upon a client request to the /// replication logger. /// /// @RESTRETURNCODES /// /// @RESTRETURNCODE{200} /// is returned if the logger state could be determined successfully. /// /// @RESTRETURNCODE{405} /// is returned when an invalid HTTP method is used. /// /// @RESTRETURNCODE{500} /// is returned if the logger state could not be determined. /// /// @EXAMPLES /// /// Returns the state of an inactive replication logger. /// /// @EXAMPLE_ARANGOSH_RUN{RestReplicationLoggerStateInactive} /// var re = require("org/arangodb/replication"); /// re.logger.stop(); /// /// var url = "/_api/replication/logger-state"; /// /// var response = logCurlRequest('GET', url); /// /// assert(response.code === 200); /// /// logJsonResponse(response); /// @END_EXAMPLE_ARANGOSH_RUN /// /// Returns the state of an active replication logger. /// /// @EXAMPLE_ARANGOSH_RUN{RestReplicationLoggerStateActive} /// var re = require("org/arangodb/replication"); /// re.logger.start(); /// /// var url = "/_api/replication/logger-state"; /// /// var response = logCurlRequest('GET', url); /// re.logger.stop(); /// /// assert(response.code === 200); /// /// logJsonResponse(response); /// @END_EXAMPLE_ARANGOSH_RUN //////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::handleCommandLoggerState () { assert(_vocbase->_replicationLogger != 0); TRI_json_t* json = TRI_JsonReplicationLogger(_vocbase->_replicationLogger); if (json == 0) { generateError(HttpResponse::SERVER_ERROR, TRI_ERROR_OUT_OF_MEMORY); return; } generateResult(json); TRI_FreeJson(TRI_CORE_MEM_ZONE, json); } //////////////////////////////////////////////////////////////////////////////// /// @brief get the configuration of the replication logger /// /// @RESTHEADER{GET /_api/replication/logger-config,returns the configuration of the replication logger} /// /// @RESTDESCRIPTION /// Returns the configuration of the replication logger. /// /// The body of the response is a JSON hash with the configuration. The /// following attributes may be present in the configuration: /// /// - `autoStart`: whether or not to automatically start the replication logger /// on server startup /// /// - `logRemoteChanges`: whether or not externally created changes should be /// logged by the local logger /// /// - `maxEvents`: the maximum number of log events kept by the replication /// logger before deleting oldest events. A value of `0` means that the /// number of events is not restricted. /// /// - `maxEventsSize`: the maximum cumulated size of log event data kept by the /// replication logger before deleting oldest events. A value of `0` means /// that the cumulated size of events is not restricted. /// /// @RESTRETURNCODES /// /// @RESTRETURNCODE{200} /// is returned if the request was executed successfully. /// /// @RESTRETURNCODE{405} /// is returned when an invalid HTTP method is used. /// /// @RESTRETURNCODE{500} /// is returned if an error occurred while assembling the response. /// /// @EXAMPLES /// /// @EXAMPLE_ARANGOSH_RUN{RestReplicationLoggerGetConfig} /// var url = "/_api/replication/logger-config"; /// var response = logCurlRequest('GET', url); /// /// assert(response.code === 200); /// logJsonResponse(response); /// @END_EXAMPLE_ARANGOSH_RUN //////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::handleCommandLoggerGetConfig () { assert(_vocbase->_replicationLogger != 0); TRI_replication_logger_configuration_t config; TRI_ReadLockReadWriteLock(&_vocbase->_replicationLogger->_statusLock); TRI_CopyConfigurationReplicationLogger(&_vocbase->_replicationLogger->_configuration, &config); TRI_ReadUnlockReadWriteLock(&_vocbase->_replicationLogger->_statusLock); TRI_json_t* json = TRI_JsonConfigurationReplicationLogger(&config); if (json == 0) { generateError(HttpResponse::SERVER_ERROR, TRI_ERROR_OUT_OF_MEMORY); return; } generateResult(json); TRI_FreeJson(TRI_CORE_MEM_ZONE, json); } //////////////////////////////////////////////////////////////////////////////// /// @brief set the configuration of the replication logger /// /// @RESTHEADER{PUT /_api/replication/logger-config,adjusts the configuration of the replication logger} /// /// @RESTBODYPARAM{configuration,json,required} /// A JSON representation of the configuration. /// /// @RESTDESCRIPTION /// Sets the configuration of the replication logger. /// /// The body of the request must be JSON hash with the configuration. The /// following attributes are allowed for the configuration: /// /// - `autoStart`: whether or not to automatically start the replication logger /// on server startup /// /// - `logRemoteChanges`: whether or not externally created changes should be /// logged by the local logger /// /// - `maxEvents`: the maximum number of log events kept by the replication /// logger before deleting oldest events. Use a value of `0` to not restrict /// the number of events. /// /// - `maxEventsSize`: the maximum cumulated size of log event data kept by the /// replication logger before deleting oldest events. Use a value of `0` to /// not restrict the size. /// /// Note that when setting both `maxEvents` and `maxEventsSize`, reaching /// either limitation will trigger a deletion of the "oldest" log events from /// the replication log. /// /// In case of success, the body of the response is a JSON hash with the updated /// configuration. /// /// @RESTRETURNCODES /// /// @RESTRETURNCODE{200} /// is returned if the request was executed successfully. /// /// @RESTRETURNCODE{400} /// is returned if the configuration is incomplete or malformed. /// /// @RESTRETURNCODE{405} /// is returned when an invalid HTTP method is used. /// /// @RESTRETURNCODE{500} /// is returned if an error occurred while assembling the response. /// /// @EXAMPLES /// /// @EXAMPLE_ARANGOSH_RUN{RestReplicationLoggerSetConfig1} /// var re = require("org/arangodb/replication"); /// re.applier.stop(); /// /// var url = "/_api/replication/logger-config"; /// var body = { /// logRemoteChanges: true, /// maxEvents: 1048576 /// }; /// /// var response = logCurlRequest('PUT', url, JSON.stringify(body)); /// /// assert(response.code === 200); /// logJsonResponse(response); /// @END_EXAMPLE_ARANGOSH_RUN /// /// @EXAMPLE_ARANGOSH_RUN{RestReplicationLoggerSetConfig2} /// var re = require("org/arangodb/replication"); /// re.applier.stop(); /// /// var url = "/_api/replication/logger-config"; /// var body = { /// logRemoteChanges: false, /// maxEvents: 16384, /// maxEventsSize: 16777216 /// }; /// /// var response = logCurlRequest('PUT', url, JSON.stringify(body)); /// /// assert(response.code === 200); /// logJsonResponse(response); /// @END_EXAMPLE_ARANGOSH_RUN //////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::handleCommandLoggerSetConfig () { assert(_vocbase->_replicationLogger != 0); TRI_replication_logger_configuration_t config; // copy previous config TRI_ReadLockReadWriteLock(&_vocbase->_replicationLogger->_statusLock); TRI_CopyConfigurationReplicationLogger(&_vocbase->_replicationLogger->_configuration, &config); TRI_ReadUnlockReadWriteLock(&_vocbase->_replicationLogger->_statusLock); TRI_json_t* json = parseJsonBody(); if (json == 0) { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_BAD_PARAMETER); return; } TRI_json_t const* value; value = JsonHelper::getArrayElement(json, "autoStart"); if (JsonHelper::isBoolean(value)) { config._autoStart = value->_value._boolean; } value = JsonHelper::getArrayElement(json, "logRemoteChanges"); if (JsonHelper::isBoolean(value)) { config._logRemoteChanges = value->_value._boolean; } value = JsonHelper::getArrayElement(json, "maxEvents"); if (JsonHelper::isNumber(value)) { config._maxEvents = (uint64_t) value->_value._number; } value = JsonHelper::getArrayElement(json, "maxEventsSize"); if (JsonHelper::isNumber(value)) { config._maxEventsSize = (uint64_t) value->_value._number; } TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); int res = TRI_ConfigureReplicationLogger(_vocbase->_replicationLogger, &config); if (res != TRI_ERROR_NO_ERROR) { if (res == TRI_ERROR_REPLICATION_INVALID_APPLIER_CONFIGURATION) { generateError(HttpResponse::BAD, res); } else { generateError(HttpResponse::SERVER_ERROR, res); } return; } handleCommandLoggerGetConfig(); } //////////////////////////////////////////////////////////////////////////////// /// @brief handle a dump batch command /// /// @RESTHEADER{POST /_api/replication/batch,creates a new dump batch} /// /// @RESTBODYPARAM{body,json,required} /// A JSON object with the batch configration. /// /// @RESTDESCRIPTION /// Creates a new dump batch and returns the batch's id. /// /// The body of the request must be a JSON hash with the following attributes: /// /// - `ttl`: the time-to-live for the new batch (in seconds) /// /// The response is a JSON hash with the following attributes: /// /// - `id`: the id of the batch /// /// Note: this method is not supported on a coordinator in a cluster. /// /// @RESTRETURNCODES /// /// @RESTRETURNCODE{204} /// is returned if the batch was created successfully. /// /// @RESTRETURNCODE{400} /// is returned if the ttl value is invalid. /// /// @RESTRETURNCODE{405} /// is returned when an invalid HTTP method is used. /// /// @RESTRETURNCODE{501} /// is returned when this operation is called on a coordinator in a cluster. //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief handle a dump batch command /// /// @RESTHEADER{PUT /_api/replication/batch/`id`,prolongs an existing dump batch} /// /// @RESTBODYPARAM{body,json,required} /// A JSON object with the batch configration. /// /// @RESTURLPARAMETERS /// /// @RESTURLPARAM{id,string,required} /// The id of the batch. /// /// @RESTDESCRIPTION /// Extends the ttl of an existing dump batch, using the batch's id and /// the provided ttl value. /// /// The body of the request must be a JSON hash with the following attributes: /// /// - `ttl`: the time-to-live for the batch (in seconds) /// /// If the batch's ttl can be extended successully, the response is empty. /// /// Note: this method is not supported on a coordinator in a cluster. /// /// @RESTRETURNCODES /// /// @RESTRETURNCODE{204} /// is returned if the batch's ttl was extended successfully. /// /// @RESTRETURNCODE{400} /// is returned if the ttl value is invalid or the batch was not found. /// /// @RESTRETURNCODE{405} /// is returned when an invalid HTTP method is used. /// /// @RESTRETURNCODE{501} /// is returned when this operation is called on a coordinator in a cluster. //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief handle a dump batch command /// /// @RESTHEADER{DELETE /_api/replication/batch/`id`,deletes an existing dump batch} /// /// @RESTURLPARAMETERS /// /// @RESTURLPARAM{id,string,required} /// The id of the batch. /// /// @RESTDESCRIPTION /// Deletes the existing dump batch, allowing compaction and cleanup to resume. /// /// Note: this method is not supported on a coordinator in a cluster. /// /// @RESTRETURNCODES /// /// @RESTRETURNCODE{204} /// is returned if the batch was deleted successfully. /// /// @RESTRETURNCODE{400} /// is returned if the batch was not found. /// /// @RESTRETURNCODE{405} /// is returned when an invalid HTTP method is used. /// /// @RESTRETURNCODE{501} /// is returned when this operation is called on a coordinator in a cluster. //////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::handleCommandBatch () { // extract the request type const HttpRequest::HttpRequestType type = _request->requestType(); vector const& suffix = _request->suffix(); const size_t len = suffix.size(); assert(len >= 1); if (type == HttpRequest::HTTP_REQUEST_POST) { // create a new blocker TRI_json_t* input = _request->toJson(0); if (input == 0) { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "invalid JSON"); return; } // extract ttl double expires = JsonHelper::getNumericValue(input, "ttl", 0); TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, input); TRI_voc_tick_t id; int res = TRI_InsertBlockerCompactorVocBase(_vocbase, expires, &id); if (res != TRI_ERROR_NO_ERROR) { generateError(HttpResponse::BAD, res); } TRI_json_t json; TRI_InitArrayJson(TRI_CORE_MEM_ZONE, &json); TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, &json, "id", TRI_CreateStringJson(TRI_CORE_MEM_ZONE, TRI_StringUInt64((uint64_t) id))); generateResult(&json); TRI_DestroyJson(TRI_CORE_MEM_ZONE, &json); return; } if (type == HttpRequest::HTTP_REQUEST_PUT && len >= 2) { // extend an existing blocker TRI_voc_tick_t id = (TRI_voc_tick_t) StringUtils::uint64(suffix[1]); TRI_json_t* input = _request->toJson(0); if (input == 0) { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "invalid JSON"); return; } // extract ttl double expires = JsonHelper::getNumericValue(input, "ttl", 0); TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, input); // now extend the blocker int res = TRI_TouchBlockerCompactorVocBase(_vocbase, id, expires); if (res == TRI_ERROR_NO_ERROR) { _response = createResponse(HttpResponse::NO_CONTENT); } else { generateError(HttpResponse::BAD, res); } return; } if (type == HttpRequest::HTTP_REQUEST_DELETE && len >= 2) { // delete an existing blocker TRI_voc_tick_t id = (TRI_voc_tick_t) StringUtils::uint64(suffix[1]); int res = TRI_RemoveBlockerCompactorVocBase(_vocbase, id); if (res == TRI_ERROR_NO_ERROR) { _response = createResponse(HttpResponse::NO_CONTENT); } else { generateError(HttpResponse::BAD, res); } return; } // we get here if anything above is invalid generateError(HttpResponse::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); } //////////////////////////////////////////////////////////////////////////////// /// @brief returns ranged data from the replication log /// /// @RESTHEADER{GET /_api/replication/logger-follow,returns recent log entries from the replication log} /// /// @RESTQUERYPARAMETERS /// /// @RESTQUERYPARAM{from,number,optional} /// Lower bound tick value for results. /// /// @RESTQUERYPARAM{to,number,optional} /// Upper bound tick value for results. /// /// @RESTQUERYPARAM{chunkSize,number,optional} /// Approximate maximum size of the returned result. /// /// @RESTDESCRIPTION /// Returns data from the server's replication log. This method can be called /// by replication clients after an initial synchronisation of data. The method /// will return all "recent" log entries from the logger server, and the clients /// can replay and apply these entries locally so they get to the same data /// state as the logger server. /// /// Clients can call this method repeatedly to incrementally fetch all changes /// from the logger server. In this case, they should provide the `from` value so /// they will only get returned the log events since their last fetch. /// /// When the `from` URL parameter is not used, the logger server will return log /// entries starting at the beginning of its replication log. When the `from` /// parameter is used, the logger server will only return log entries which have /// higher tick values than the specified `from` value (note: the log entry with a /// tick value equal to `from` will be excluded). Use the `from` value when /// incrementally fetching log data. /// /// The `to` URL parameter can be used to optionally restrict the upper bound of /// the result to a certain tick value. If used, the result will contain only log events /// with tick values up to (including) `to`. In incremental fetching, there is no /// need to use the `to` parameter. It only makes sense in special situations, /// when only parts of the change log are required. /// /// The `chunkSize` URL parameter can be used to control the size of the result. /// It must be specified in bytes. The `chunkSize` value will only be honored /// approximately. Otherwise a too low `chunkSize` value could cause the server /// to not be able to put just one log entry into the result and return it. /// Therefore, the `chunkSize` value will only be consulted after a log entry has /// been written into the result. If the result size is then bigger than /// `chunkSize`, the server will respond with as many log entries as there are /// in the response already. If the result size is still smaller than `chunkSize`, /// the server will try to return more data if there's more data left to return. /// /// If `chunkSize` is not specified, some server-side default value will be used. /// /// The `Content-Type` of the result is `application/x-arango-dump`. This is an /// easy-to-process format, with all log events going onto separate lines in the /// response body. Each log event itself is a JSON hash, with at least the /// following attributes: /// /// - `tick`: the log event tick value /// /// - `type`: the log event type /// /// Individual log events will also have additional attributes, depending on the /// event type. A few common attributes which are used for multiple events types /// are: /// /// - `cid`: id of the collection the event was for /// /// - `tid`: id of the transaction the event was contained in /// /// - `key`: document key /// /// - `rev`: document revision id /// /// - `data`: the original document data /// /// A more detailed description of the individual replication event types and their /// data structures can be found in @ref RefManualReplicationEventTypes. /// /// The response will also contain the following HTTP headers: /// /// - `x-arango-replication-active`: whether or not the logger is active. Clients /// can use this flag as an indication for their polling frequency. If the /// logger is not active and there are no more replication events available, it /// might be sensible for a client to abort, or to go to sleep for a long time /// and try again later to check whether the logger has been activated. /// /// - `x-arango-replication-lastincluded`: the tick value of the last included /// value in the result. In incremental log fetching, this value can be used /// as the `from` value for the following request. Note that if the result is /// empty, the value will be `0`. This value should not be used as `from` value /// by clients in the next request (otherwise the server would return the log /// events from the start of the log again). /// /// - `x-arango-replication-lasttick`: the last tick value the logger server has /// logged (not necessarily included in the result). By comparing the the last /// tick and last included tick values, clients have an approximate indication of /// how many events there are still left to fetch. /// /// - `x-arango-replication-checkmore`: whether or not there already exists more /// log data which the client could fetch immediately. If there is more log data /// available, the client could call `logger-follow` again with an adjusted `from` /// value to fetch remaining log entries until there are no more. /// /// If there isn't any more log data to fetch, the client might decide to go /// to sleep for a while before calling the logger again. /// /// Note: this method is not supported on a coordinator in a cluster. /// /// @RESTRETURNCODES /// /// @RESTRETURNCODE{200} /// is returned if the request was executed successfully, and there are log /// events available for the requested range. The response body will not be empty /// in this case. /// /// @RESTRETURNCODE{204} /// is returned if the request was executed successfully, but there are no log /// events available for the requested range. The response body will be empty /// in this case. /// /// @RESTRETURNCODE{400} /// is returned if either the `from` or `to` values are invalid. /// /// @RESTRETURNCODE{405} /// is returned when an invalid HTTP method is used. /// /// @RESTRETURNCODE{500} /// is returned if an error occurred while assembling the response. /// /// @RESTRETURNCODE{501} /// is returned when this operation is called on a coordinator in a cluster. /// /// @EXAMPLES /// /// No log events available: /// /// @EXAMPLE_ARANGOSH_RUN{RestReplicationLoggerFollowEmpty} /// var re = require("org/arangodb/replication"); /// re.logger.start(); /// var lastTick = re.logger.state().state.lastLogTick; /// /// var url = "/_api/replication/logger-follow?from=" + lastTick; /// var response = logCurlRequest('GET', url); /// /// re.logger.stop(); /// assert(response.code === 204); /// /// logRawResponse(response); /// @END_EXAMPLE_ARANGOSH_RUN /// /// A few log events: /// /// @EXAMPLE_ARANGOSH_RUN{RestReplicationLoggerFollowSome} /// var re = require("org/arangodb/replication"); /// db._drop("products"); /// /// re.logger.start(); /// var lastTick = re.logger.state().state.lastLogTick; /// /// db._create("products"); /// db.products.save({ "_key": "p1", "name" : "flux compensator" }); /// db.products.save({ "_key": "p2", "name" : "hybrid hovercraft", "hp" : 5100 }); /// db.products.remove("p1"); /// db.products.update("p2", { "name" : "broken hovercraft" }); /// db.products.drop(); /// /// var url = "/_api/replication/logger-follow?from=" + lastTick; /// var response = logCurlRequest('GET', url); /// /// re.logger.stop(); /// assert(response.code === 200); /// /// logRawResponse(response); /// @END_EXAMPLE_ARANGOSH_RUN /// /// More events than would fit into the response: /// /// @EXAMPLE_ARANGOSH_RUN{RestReplicationLoggerFollowBufferLimit} /// var re = require("org/arangodb/replication"); /// db._drop("products"); /// /// re.logger.start(); /// var lastTick = re.logger.state().state.lastLogTick; /// /// db._create("products"); /// db.products.save({ "_key": "p1", "name" : "flux compensator" }); /// db.products.save({ "_key": "p2", "name" : "hybrid hovercraft", "hp" : 5100 }); /// db.products.remove("p1"); /// db.products.update("p2", { "name" : "broken hovercraft" }); /// db.products.drop(); /// /// var url = "/_api/replication/logger-follow?from=" + lastTick + "&chunkSize=400"; /// var response = logCurlRequest('GET', url); /// /// re.logger.stop(); /// assert(response.code === 200); /// /// logRawResponse(response); /// @END_EXAMPLE_ARANGOSH_RUN //////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::handleCommandLoggerFollow () { // determine start tick TRI_voc_tick_t tickStart = 0; TRI_voc_tick_t tickEnd = (TRI_voc_tick_t) UINT64_MAX; bool found; char const* value; value = _request->value("from", found); if (found) { tickStart = (TRI_voc_tick_t) StringUtils::uint64(value); } // determine end tick for dump value = _request->value("to", found); if (found) { tickEnd = (TRI_voc_tick_t) StringUtils::uint64(value); } if (tickStart > tickEnd || tickEnd == 0) { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "invalid from/to values"); return; } const uint64_t chunkSize = determineChunkSize(); // initialise the dump container TRI_replication_dump_t dump; if (TRI_InitDumpReplication(&dump, _vocbase, (size_t) defaultChunkSize) != TRI_ERROR_NO_ERROR) { generateError(HttpResponse::SERVER_ERROR, TRI_ERROR_OUT_OF_MEMORY); return; } int res = TRI_DumpLogReplication(_vocbase, &dump, tickStart, tickEnd, chunkSize); TRI_replication_logger_state_t state; if (res == TRI_ERROR_NO_ERROR) { res = TRI_StateReplicationLogger(_vocbase->_replicationLogger, &state); } if (res == TRI_ERROR_NO_ERROR) { const bool checkMore = (dump._lastFoundTick > 0 && dump._lastFoundTick != state._lastLogTick); // generate the result const size_t length = TRI_LengthStringBuffer(dump._buffer); if (length == 0) { _response = createResponse(HttpResponse::NO_CONTENT); } else { _response = createResponse(HttpResponse::OK); } _response->setContentType("application/x-arango-dump; charset=utf-8"); // set headers _response->setHeader(TRI_REPLICATION_HEADER_CHECKMORE, strlen(TRI_REPLICATION_HEADER_CHECKMORE), checkMore ? "true" : "false"); _response->setHeader(TRI_REPLICATION_HEADER_LASTINCLUDED, strlen(TRI_REPLICATION_HEADER_LASTINCLUDED), StringUtils::itoa(dump._lastFoundTick)); _response->setHeader(TRI_REPLICATION_HEADER_LASTTICK, strlen(TRI_REPLICATION_HEADER_LASTTICK), StringUtils::itoa(state._lastLogTick)); _response->setHeader(TRI_REPLICATION_HEADER_ACTIVE, strlen(TRI_REPLICATION_HEADER_ACTIVE), state._active ? "true" : "false"); if (length > 0) { // transfer ownership of the buffer contents _response->body().set(dump._buffer); // avoid double freeing TRI_StealStringBuffer(dump._buffer); } insertClient(dump._lastFoundTick); } else { generateError(HttpResponse::SERVER_ERROR, res); } TRI_DestroyDumpReplication(&dump); } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the server inventory /// /// @RESTHEADER{GET /_api/replication/inventory,returns an inventory of collections and indexes} /// /// @RESTQUERYPARAMETERS /// /// @RESTQUERYPARAM{includeSystem,boolean,optional} /// Include system collections in the result. The default value is `false`. /// /// @RESTDESCRIPTION /// Returns the list of collections and indexes available on the server. This /// list can be used by replication clients to initiate an initial sync with the /// server. /// /// The response will contain a JSON hash array with the `collection` and `state` /// attributes. /// /// `collections` is a list of collections with the following sub-attributes: /// /// - `parameters`: the collection properties /// /// - `indexes`: a list of the indexes of a the collection. Primary indexes and edges indexes /// are not included in this list. /// /// `tick`: the system-wide tick value at the start of the dump /// /// The `state` attribute contains the current state of the replication logger. It /// contains the following sub-attributes: /// /// - `running`: whether or not the replication logger is currently active /// /// - `lastLogTick`: the value of the last tick the replication logger has written /// /// - `time`: the current time on the server /// /// Replication clients should note the `lastLogTick` value returned. They can then /// fetch collections' data using the dump method up to the value of lastLogTick, and /// query the continuous replication log for log events after this tick value. /// /// To create a full copy of the collections on the logger server, a replication client /// can execute these steps: /// /// - call the `/inventory` API method. This returns the `lastLogTick` value and the /// list of collections and indexes from the logger server. /// /// - for each collection returned by `/inventory`, create the collection locally and /// call `/dump` to stream the collection data to the client, up to the value of /// `lastLogTick`. /// After that, the client can create the indexes on the collections as they were /// reported by `/inventory`. /// /// If the clients wants to continuously stream replication log events from the logger /// server, the following additional steps need to be carried out: /// /// - the client should call `/logger-follow` initially to fetch the first batch of /// replication events that were logged after the client's call to `/inventory`. /// /// The call to `/logger-follow` should use a `from` parameter with the value of the /// `lastLogTick` as reported by `/inventory`. The call to `/logger-follow` will return the /// `x-arango-replication-lastincluded` which will contain the last tick value included /// in the response. /// /// - the client can then continuously call `/logger-follow` to incrementally fetch new /// replication events that occurred after the last transfer. /// /// Calls should use a `from` parameter with the value of the `x-arango-replication-lastincluded` /// header of the previous response. If there are no more replication events, the /// response will be empty and clients can go to sleep for a while and try again /// later. /// /// Note: this method is not supported on a coordinator in a cluster. /// /// @RESTRETURNCODES /// /// @RESTRETURNCODE{200} /// is returned if the request was executed successfully. /// /// @RESTRETURNCODE{405} /// is returned when an invalid HTTP method is used. /// /// @RESTRETURNCODE{500} /// is returned if an error occurred while assembling the response. /// /// @RESTRETURNCODE{501} /// is returned when this operation is called on a coordinator in a cluster. /// /// @EXAMPLES /// /// @EXAMPLE_ARANGOSH_RUN{RestReplicationInventory} /// var url = "/_api/replication/inventory"; /// var response = logCurlRequest('GET', url); /// /// assert(response.code === 200); /// /// logJsonResponse(response); /// @END_EXAMPLE_ARANGOSH_RUN /// /// With some additional indexes: /// /// @EXAMPLE_ARANGOSH_RUN{RestReplicationInventoryIndexes} /// db._drop("IndexedCollection1"); /// var c1 = db._create("IndexedCollection1"); /// c1.ensureHashIndex("name"); /// c1.ensureUniqueSkiplist("a", "b"); /// c1.ensureCapConstraint(500); /// /// db._drop("IndexedCollection2"); /// var c2 = db._create("IndexedCollection2"); /// c2.ensureFulltextIndex("text", 10); /// c2.ensureSkiplist("a"); /// c2.ensureCapConstraint(0, 1048576); /// /// var url = "/_api/replication/inventory"; /// var response = logCurlRequest('GET', url); /// /// assert(response.code === 200); /// logJsonResponse(response); /// /// db._flushCache(); /// db._drop("IndexedCollection1"); /// db._drop("IndexedCollection2"); /// @END_EXAMPLE_ARANGOSH_RUN //////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::handleCommandInventory () { assert(_vocbase->_replicationLogger != 0); TRI_voc_tick_t tick = TRI_CurrentTickServer(); // include system collections? bool includeSystem = false; bool found; const char* value = _request->value("includeSystem", found); if (found) { includeSystem = StringUtils::boolean(value); } // collections and indexes TRI_json_t* collections = TRI_InventoryCollectionsVocBase(_vocbase, tick, &filterCollection, (void*) &includeSystem); if (collections == 0) { generateError(HttpResponse::SERVER_ERROR, TRI_ERROR_OUT_OF_MEMORY); return; } assert(JsonHelper::isList(collections)); // sort collections by type, then name const size_t n = collections->_value._objects._length; if (n > 1) { // sort by collection type (vertices before edges), then name qsort(collections->_value._objects._buffer, n, sizeof(TRI_json_t), &sortCollections); } TRI_replication_logger_state_t state; int res = TRI_StateReplicationLogger(_vocbase->_replicationLogger, &state); if (res != TRI_ERROR_NO_ERROR) { TRI_Free(TRI_CORE_MEM_ZONE, collections); generateError(HttpResponse::SERVER_ERROR, res); return; } TRI_json_t json; TRI_InitArrayJson(TRI_CORE_MEM_ZONE, &json); char* tickString = TRI_StringUInt64(tick); // add collections data TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, &json, "collections", collections); TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, &json, "state", TRI_JsonStateReplicationLogger(&state)); TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, &json, "tick", TRI_CreateStringJson(TRI_CORE_MEM_ZONE, tickString)); generateResult(&json); TRI_DestroyJson(TRI_CORE_MEM_ZONE, &json); } //////////////////////////////////////////////////////////////////////////////// /// @brief extract the collection id from JSON TODO: move //////////////////////////////////////////////////////////////////////////////// TRI_voc_cid_t RestReplicationHandler::getCid (TRI_json_t const* json) const { if (! JsonHelper::isArray(json)) { return 0; } TRI_json_t const* id = JsonHelper::getArrayElement(json, "cid"); if (JsonHelper::isString(id)) { return StringUtils::uint64(id->_value._string.data, id->_value._string.length - 1); } else if (JsonHelper::isNumber(id)) { return (TRI_voc_cid_t) id->_value._number; } return 0; } //////////////////////////////////////////////////////////////////////////////// /// @brief creates a collection, based on the JSON provided TODO: move //////////////////////////////////////////////////////////////////////////////// int RestReplicationHandler::createCollection (TRI_json_t const* json, TRI_vocbase_col_t** dst, bool reuseId, TRI_server_id_t remoteServerId) { if (dst != 0) { *dst = 0; } if (! JsonHelper::isArray(json)) { return TRI_ERROR_HTTP_BAD_PARAMETER; } const string name = JsonHelper::getStringValue(json, "name", ""); if (name.empty()) { return TRI_ERROR_HTTP_BAD_PARAMETER; } TRI_voc_cid_t cid = 0; if (reuseId) { cid = getCid(json); if (cid == 0) { return TRI_ERROR_HTTP_BAD_PARAMETER; } } const TRI_col_type_e type = (TRI_col_type_e) JsonHelper::getNumericValue(json, "type", (int) TRI_COL_TYPE_DOCUMENT); TRI_vocbase_col_t* col = 0; if (cid > 0) { col = TRI_LookupCollectionByIdVocBase(_vocbase, cid); } if (col != 0 && (TRI_col_type_t) col->_type == (TRI_col_type_t) type) { // collection already exists. TODO: compare attributes return TRI_ERROR_NO_ERROR; } TRI_json_t* keyOptions = 0; if (JsonHelper::isArray(JsonHelper::getArrayElement(json, "keyOptions"))) { keyOptions = TRI_CopyJson(TRI_CORE_MEM_ZONE, JsonHelper::getArrayElement(json, "keyOptions")); } TRI_col_info_t params; TRI_InitCollectionInfo(_vocbase, ¶ms, name.c_str(), type, (TRI_voc_size_t) JsonHelper::getNumericValue(json, "maximalSize", (int64_t) TRI_JOURNAL_DEFAULT_MAXIMAL_SIZE), keyOptions); if (keyOptions != 0) { TRI_FreeJson(TRI_CORE_MEM_ZONE, keyOptions); } params._doCompact = JsonHelper::getBooleanValue(json, "doCompact", true); params._waitForSync = JsonHelper::getBooleanValue(json, "waitForSync", _vocbase->_settings.defaultWaitForSync); params._isVolatile = JsonHelper::getBooleanValue(json, "isVolatile", false); params._isSystem = (name[0] == '_'); params._planId = 0; TRI_voc_cid_t planId = JsonHelper::stringUInt64(json, "planId"); if (planId > 0) { params._planId = planId; } if (cid > 0) { // wait for "old" collection to be dropped char* dirName = TRI_GetDirectoryCollection(_vocbase->_path, name.c_str(), type, cid); if (dirName != 0) { char* parameterName = TRI_Concatenate2File(dirName, TRI_VOC_PARAMETER_FILE); if (parameterName != 0) { int iterations = 0; // TODO: adjust sleep timer & maxiterations while (TRI_IsDirectory(dirName) && TRI_ExistsFile(parameterName) && iterations++ < 1200) { usleep(100 * 1000); } TRI_FreeString(TRI_CORE_MEM_ZONE, parameterName); } TRI_FreeString(TRI_CORE_MEM_ZONE, dirName); } } col = TRI_CreateCollectionVocBase(_vocbase, ¶ms, cid, remoteServerId); TRI_FreeCollectionInfoOptions(¶ms); if (col == NULL) { return TRI_errno(); } if (dst != 0) { *dst = col; } return TRI_ERROR_NO_ERROR; } //////////////////////////////////////////////////////////////////////////////// /// @brief restores the structure of a collection TODO //////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::handleCommandRestoreCollection () { TRI_json_t* json = _request->toJson(0); if (json == 0) { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "invalid JSON"); return; } bool found; char const* value; bool overwrite = false; value = _request->value("overwrite", found); if (found) { overwrite = StringUtils::boolean(value); } bool recycleIds = false; value = _request->value("recycleIds", found); if (found) { recycleIds = StringUtils::boolean(value); } bool force = false; value = _request->value("force", found); if (found) { force = StringUtils::boolean(value); } TRI_server_id_t remoteServerId = 0; // TODO string errorMsg; int res = processRestoreCollection(json, overwrite, recycleIds, force, remoteServerId, errorMsg); TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); if (res != TRI_ERROR_NO_ERROR) { generateError(HttpResponse::SERVER_ERROR, res); } else { TRI_json_t result; TRI_InitArrayJson(TRI_CORE_MEM_ZONE, &result); TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, &result, "result", TRI_CreateBooleanJson(TRI_CORE_MEM_ZONE, true)); generateResult(&result); } } //////////////////////////////////////////////////////////////////////////////// /// @brief restores the indexes of a collection TODO //////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::handleCommandRestoreIndexes () { TRI_json_t* json = _request->toJson(0); if (json == 0) { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "invalid JSON"); return; } bool found; bool force = false; char const* value = _request->value("force", found); if (found) { force = StringUtils::boolean(value); } TRI_server_id_t remoteServerId = 0; // TODO string errorMsg; int res = processRestoreIndexes(json, force, remoteServerId, errorMsg); TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); if (res != TRI_ERROR_NO_ERROR) { generateError(HttpResponse::SERVER_ERROR, res); } else { TRI_json_t result; TRI_InitArrayJson(TRI_CORE_MEM_ZONE, &result); TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, &result, "result", TRI_CreateBooleanJson(TRI_CORE_MEM_ZONE, true)); generateResult(&result); } } //////////////////////////////////////////////////////////////////////////////// /// @brief restores the structure of a collection TODO MOVE //////////////////////////////////////////////////////////////////////////////// int RestReplicationHandler::processRestoreCollection (TRI_json_t const* collection, bool dropExisting, bool reuseId, bool force, TRI_server_id_t remoteServerId, string& errorMsg) { if (! JsonHelper::isArray(collection)) { errorMsg = "collection declaration is invalid"; return TRI_ERROR_HTTP_BAD_PARAMETER; } TRI_json_t const* parameters = JsonHelper::getArrayElement(collection, "parameters"); if (! JsonHelper::isArray(parameters)) { errorMsg = "collection parameters declaration is invalid"; return TRI_ERROR_HTTP_BAD_PARAMETER; } TRI_json_t const* indexes = JsonHelper::getArrayElement(collection, "indexes"); if (! JsonHelper::isList(indexes)) { errorMsg = "collection indexes declaration is invalid"; return TRI_ERROR_HTTP_BAD_PARAMETER; } const string name = JsonHelper::getStringValue(parameters, "name", ""); if (name.empty()) { errorMsg = "collection name is missing"; return TRI_ERROR_HTTP_BAD_PARAMETER; } if (JsonHelper::getBooleanValue(parameters, "deleted", false)) { // we don't care about deleted collections return TRI_ERROR_NO_ERROR; } TRI_vocbase_col_t* col = 0; if (reuseId) { TRI_json_t const* idString = JsonHelper::getArrayElement(parameters, "cid"); if (! JsonHelper::isString(idString)) { errorMsg = "collection id is missing"; return TRI_ERROR_HTTP_BAD_PARAMETER; } TRI_voc_cid_t cid = StringUtils::uint64(idString->_value._string.data, idString->_value._string.length - 1); // first look up the collection by the cid col = TRI_LookupCollectionByIdVocBase(_vocbase, cid); } if (col == 0) { // not found, try name next col = TRI_LookupCollectionByNameVocBase(_vocbase, name.c_str()); } // drop an existing collection if it exists if (col != 0) { if (dropExisting) { int res = TRI_DropCollectionVocBase(_vocbase, col, remoteServerId); if (res == TRI_ERROR_FORBIDDEN) { // some collections must not be dropped // instead, truncate them CollectionNameResolver resolver(_vocbase); SingleCollectionWriteTransaction, UINT64_MAX> trx(_vocbase, resolver, col->_cid); res = trx.begin(); if (res != TRI_ERROR_NO_ERROR) { return res; } TRI_barrier_t* barrier = TRI_CreateBarrierElement(&(trx.primaryCollection()->_barrierList)); if (barrier == 0) { return TRI_ERROR_INTERNAL; } res = trx.truncate(false); res = trx.finish(res); TRI_FreeBarrier(barrier); return res; } if (res != TRI_ERROR_NO_ERROR) { errorMsg = "unable to drop collection '" + name + "': " + string(TRI_errno_string(res)); return res; } } else { int res = TRI_ERROR_ARANGO_DUPLICATE_NAME; errorMsg = "unable to create collection '" + name + "': " + string(TRI_errno_string(res)); return res; } } // now re-create the collection int res = createCollection(parameters, &col, reuseId, remoteServerId); if (res != TRI_ERROR_NO_ERROR) { errorMsg = "unable to create collection: " + string(TRI_errno_string(res)); return res; } return TRI_ERROR_NO_ERROR; } //////////////////////////////////////////////////////////////////////////////// /// @brief restores the indexes of a collection TODO MOVE //////////////////////////////////////////////////////////////////////////////// int RestReplicationHandler::processRestoreIndexes (TRI_json_t const* collection, bool force, TRI_server_id_t remoteServerId, string& errorMsg) { if (! JsonHelper::isArray(collection)) { errorMsg = "collection declaration is invalid"; return TRI_ERROR_HTTP_BAD_PARAMETER; } TRI_json_t const* parameters = JsonHelper::getArrayElement(collection, "parameters"); if (! JsonHelper::isArray(parameters)) { errorMsg = "collection parameters declaration is invalid"; return TRI_ERROR_HTTP_BAD_PARAMETER; } TRI_json_t const* indexes = JsonHelper::getArrayElement(collection, "indexes"); if (! JsonHelper::isList(indexes)) { errorMsg = "collection indexes declaration is invalid"; return TRI_ERROR_HTTP_BAD_PARAMETER; } const size_t n = indexes->_value._objects._length; if (n == 0) { // nothing to do return TRI_ERROR_NO_ERROR; } const string name = JsonHelper::getStringValue(parameters, "name", ""); if (name.empty()) { errorMsg = "collection name is missing"; return TRI_ERROR_HTTP_BAD_PARAMETER; } if (JsonHelper::getBooleanValue(parameters, "deleted", false)) { // we don't care about deleted collections return TRI_ERROR_NO_ERROR; } // look up the collection TRI_vocbase_col_t* col = TRI_LookupCollectionByNameVocBase(_vocbase, name.c_str()); if (col == 0) { errorMsg = "could not find collection '" + name + "'"; return TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND; } int res = TRI_UseCollectionVocBase(_vocbase, col); if (res != TRI_ERROR_NO_ERROR) { return res; } TRI_primary_collection_t* primary = col->_collection; TRI_ReadLockReadWriteLock(&_vocbase->_inventoryLock); TRI_WRITE_LOCK_DOCUMENTS_INDEXES_PRIMARY_COLLECTION(primary); for (size_t i = 0; i < n; ++i) { TRI_json_t const* idxDef = (TRI_json_t const*) TRI_AtVector(&indexes->_value._objects, i); TRI_index_t* idx = 0; // {"id":"229907440927234","type":"hash","unique":false,"fields":["x","Y"]} res = TRI_FromJsonIndexDocumentCollection((TRI_document_collection_t*) primary, idxDef, &idx); if (res != TRI_ERROR_NO_ERROR) { errorMsg = "could not create index: " + string(TRI_errno_string(res)); break; } else { assert(idx != 0); res = TRI_SaveIndex(primary, idx, remoteServerId); if (res != TRI_ERROR_NO_ERROR) { errorMsg = "could not save index: " + string(TRI_errno_string(res)); break; } } } TRI_WRITE_UNLOCK_DOCUMENTS_INDEXES_PRIMARY_COLLECTION(primary); TRI_ReadUnlockReadWriteLock(&_vocbase->_inventoryLock); TRI_ReleaseCollectionVocBase(_vocbase, col); return TRI_ERROR_NO_ERROR; } //////////////////////////////////////////////////////////////////////////////// /// @brief apply the data from a collection dump or the continuous log //////////////////////////////////////////////////////////////////////////////// int RestReplicationHandler::applyCollectionDumpMarker (CollectionNameResolver const& resolver, TRI_transaction_collection_t* trxCollection, TRI_replication_operation_e type, const TRI_voc_key_t key, const TRI_voc_rid_t rid, TRI_json_t const* json, string& errorMsg) { if (type == MARKER_DOCUMENT || type == MARKER_EDGE) { // {"type":2400,"key":"230274209405676","data":{"_key":"230274209405676","_rev":"230274209405676","foo":"bar"}} assert(json != 0); TRI_primary_collection_t* primary = trxCollection->_collection->_collection; TRI_memory_zone_t* zone = primary->_shaper->_memoryZone; TRI_shaped_json_t* shaped = TRI_ShapedJsonJson(primary->_shaper, json, true, true); if (shaped != 0) { TRI_doc_mptr_t mptr; int res = primary->read(trxCollection, key, &mptr, false); if (res == TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND) { // insert if (type == MARKER_EDGE) { // edge if (primary->base._info._type != TRI_COL_TYPE_EDGE) { res = TRI_ERROR_ARANGO_COLLECTION_TYPE_INVALID; } else { res = TRI_ERROR_NO_ERROR; } const string from = JsonHelper::getStringValue(json, TRI_VOC_ATTRIBUTE_FROM, ""); const string to = JsonHelper::getStringValue(json, TRI_VOC_ATTRIBUTE_TO, ""); // parse _from TRI_document_edge_t edge; if (! DocumentHelper::parseDocumentId(resolver, from.c_str(), edge._fromCid, &edge._fromKey)) { res = TRI_ERROR_ARANGO_DOCUMENT_HANDLE_BAD; } // parse _to if (! DocumentHelper::parseDocumentId(resolver, to.c_str(), edge._toCid, &edge._toKey)) { res = TRI_ERROR_ARANGO_DOCUMENT_HANDLE_BAD; } if (res == TRI_ERROR_NO_ERROR) { res = primary->insert(trxCollection, key, rid, &mptr, TRI_DOC_MARKER_KEY_EDGE, shaped, &edge, false, false, true); } } else { // document if (primary->base._info._type != TRI_COL_TYPE_DOCUMENT) { res = TRI_ERROR_ARANGO_COLLECTION_TYPE_INVALID; } else { res = primary->insert(trxCollection, key, rid, &mptr, TRI_DOC_MARKER_KEY_DOCUMENT, shaped, 0, false, false, true); } } } else { // update // init the update policy TRI_doc_update_policy_t policy; TRI_InitUpdatePolicy(&policy, TRI_DOC_UPDATE_LAST_WRITE, 0, 0); res = primary->update(trxCollection, key, rid, &mptr, shaped, &policy, false, false); } TRI_FreeShapedJson(zone, shaped); return res; } else { errorMsg = TRI_errno_string(TRI_ERROR_OUT_OF_MEMORY); return TRI_ERROR_OUT_OF_MEMORY; } } else if (type == MARKER_REMOVE) { // {"type":2402,"key":"592063"} // init the update policy TRI_doc_update_policy_t policy; TRI_InitUpdatePolicy(&policy, TRI_DOC_UPDATE_LAST_WRITE, 0, 0); TRI_primary_collection_t* primary = trxCollection->_collection->_collection; int res = primary->remove(trxCollection, key, rid, &policy, false, false); if (res != TRI_ERROR_NO_ERROR) { if (res == TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND) { // ignore this error res = TRI_ERROR_NO_ERROR; } else { errorMsg = "document removal operation failed: " + string(TRI_errno_string(res)); } } return res; } else { errorMsg = "unexpected marker type " + StringUtils::itoa(type); return TRI_ERROR_REPLICATION_UNEXPECTED_MARKER; } } //////////////////////////////////////////////////////////////////////////////// /// @brief restores the data of a collection TODO MOVE //////////////////////////////////////////////////////////////////////////////// int RestReplicationHandler::processRestoreDataBatch (CollectionNameResolver const& resolver, TRI_transaction_collection_t* trxCollection, TRI_server_id_t generatingServer, bool useRevision, bool force, std::string& errorMsg) { const string invalidMsg = "received invalid JSON data for collection " + StringUtils::itoa(trxCollection->_cid); char const* ptr = _request->body(); char const* end = ptr + _request->bodySize(); while (ptr < end) { char const* pos = strchr(ptr, '\n'); if (pos == 0) { pos = end; } else { *((char*) pos) = '\0'; } if (pos - ptr > 1) { // found something TRI_json_t* json = TRI_JsonString(TRI_CORE_MEM_ZONE, ptr); if (! JsonHelper::isArray(json)) { if (json != 0) { TRI_FreeJson(TRI_CORE_MEM_ZONE, json); } errorMsg = invalidMsg; return TRI_ERROR_HTTP_CORRUPTED_JSON; } TRI_replication_operation_e type = REPLICATION_INVALID; const char* key = 0; TRI_voc_rid_t rid = 0; TRI_json_t const* doc = 0; const size_t n = json->_value._objects._length; for (size_t i = 0; i < n; i += 2) { TRI_json_t const* element = (TRI_json_t const*) TRI_AtVector(&json->_value._objects, i); if (! JsonHelper::isString(element)) { TRI_FreeJson(TRI_CORE_MEM_ZONE, json); errorMsg = invalidMsg; return TRI_ERROR_HTTP_CORRUPTED_JSON; } const char* attributeName = element->_value._string.data; TRI_json_t const* value = (TRI_json_t const*) TRI_AtVector(&json->_value._objects, i + 1); if (TRI_EqualString(attributeName, "type")) { if (JsonHelper::isNumber(value)) { type = (TRI_replication_operation_e) (int) value->_value._number; } } else if (TRI_EqualString(attributeName, "key")) { if (JsonHelper::isString(value)) { key = value->_value._string.data; } } else if (useRevision && TRI_EqualString(attributeName, "rev")) { if (JsonHelper::isString(value)) { rid = StringUtils::uint64(value->_value._string.data, value->_value._string.length - 1); } } else if (TRI_EqualString(attributeName, "data")) { if (JsonHelper::isArray(value)) { doc = value; } } } // key must not be 0, but doc can be 0! if (key == 0) { TRI_FreeJson(TRI_CORE_MEM_ZONE, json); errorMsg = invalidMsg; return TRI_ERROR_HTTP_BAD_PARAMETER; } int res = applyCollectionDumpMarker(resolver, trxCollection, type, (const TRI_voc_key_t) key, rid, doc, errorMsg); TRI_FreeJson(TRI_CORE_MEM_ZONE, json); if (res != TRI_ERROR_NO_ERROR && ! force) { return res; } } ptr = pos + 1; } return TRI_ERROR_NO_ERROR; } //////////////////////////////////////////////////////////////////////////////// /// @brief restores the data of a collection TODO //////////////////////////////////////////////////////////////////////////////// int RestReplicationHandler::processRestoreData (CollectionNameResolver const& resolver, TRI_voc_cid_t cid, TRI_server_id_t generatingServer, bool useRevision, bool force, string& errorMsg) { TRI_transaction_t* trx = TRI_CreateTransaction(_vocbase, generatingServer, false, 0.0, false); if (trx == 0) { errorMsg = "unable to start transaction"; return TRI_ERROR_OUT_OF_MEMORY; } int res = TRI_AddCollectionTransaction(trx, cid, TRI_TRANSACTION_WRITE, TRI_TRANSACTION_TOP_LEVEL); if (res != TRI_ERROR_NO_ERROR) { TRI_FreeTransaction(trx); errorMsg = "unable to start transaction: " + string(TRI_errno_string(res)); return res; } // TODO: can we use this hint? res = TRI_BeginTransaction(trx, (TRI_transaction_hint_t) TRI_TRANSACTION_HINT_SINGLE_OPERATION, TRI_TRANSACTION_TOP_LEVEL); if (res != TRI_ERROR_NO_ERROR) { TRI_FreeTransaction(trx); errorMsg = "unable to start transaction: " + string(TRI_errno_string(res)); return TRI_ERROR_INTERNAL; } TRI_transaction_collection_t* trxCollection = TRI_GetCollectionTransaction(trx, cid, TRI_TRANSACTION_WRITE); if (trxCollection == NULL) { res = TRI_ERROR_INTERNAL; errorMsg = "unable to start transaction: " + string(TRI_errno_string(res)); } else { // TODO: waitForSync disabled here. use for initial replication, too // sync at end of trx trxCollection->_waitForSync = false; res = processRestoreDataBatch(resolver, trxCollection, generatingServer, useRevision, force, errorMsg); } if (res == TRI_ERROR_NO_ERROR) { TRI_CommitTransaction(trx, TRI_TRANSACTION_TOP_LEVEL); } TRI_FreeTransaction(trx); return res; } //////////////////////////////////////////////////////////////////////////////// /// @brief restores the data of a collection TODO //////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::handleCommandRestoreData () { char const* value = _request->value("collection"); if (value == 0) { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "invalid collection parameter"); return; } CollectionNameResolver resolver(_vocbase); TRI_voc_cid_t cid = resolver.getCollectionId(value); if (cid == 0) { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "invalid collection parameter"); return; } bool recycleIds = false; value = _request->value("recycleIds"); if (value != 0) { recycleIds = StringUtils::boolean(value); } bool force = false; value = _request->value("force"); if (value != 0) { force = StringUtils::boolean(value); } TRI_server_id_t remoteServerId = 0; // TODO string errorMsg; int res = processRestoreData(resolver, cid, remoteServerId, recycleIds, force, errorMsg); if (res != TRI_ERROR_NO_ERROR) { generateError(HttpResponse::SERVER_ERROR, res); } else { TRI_json_t result; TRI_InitArrayJson(TRI_CORE_MEM_ZONE, &result); TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, &result, "result", TRI_CreateBooleanJson(TRI_CORE_MEM_ZONE, true)); generateResult(&result); } } //////////////////////////////////////////////////////////////////////////////// /// @brief dumps the data of a collection /// /// @RESTHEADER{GET /_api/replication/dump,returns the data of a collection} /// /// @RESTQUERYPARAMETERS /// /// @RESTQUERYPARAM{collection,string,required} /// The name or id of the collection to dump. /// /// @RESTQUERYPARAM{from,number,optional} /// Lower bound tick value for results. /// /// @RESTQUERYPARAM{to,number,optional} /// Upper bound tick value for results. /// /// @RESTQUERYPARAM{chunkSize,number,optional} /// Approximate maximum size of the returned result. /// /// @RESTQUERYPARAM{ticks,boolean,optional} /// Whether or not to include tick values in the dump. Default value is `true`. /// /// @RESTDESCRIPTION /// Returns the data from the collection for the requested range. /// /// When the `from` URL parameter is not used, collection events are returned from /// the beginning. When the `from` parameter is used, the result will only contain /// collection entries which have higher tick values than the specified `from` value /// (note: the log entry with a tick value equal to `from` will be excluded). /// /// The `to` URL parameter can be used to optionally restrict the upper bound of /// the result to a certain tick value. If used, the result will only contain /// collection entries with tick values up to (including) `to`. /// /// The `chunkSize` URL parameter can be used to control the size of the result. /// It must be specified in bytes. The `chunkSize` value will only be honored /// approximately. Otherwise a too low `chunkSize` value could cause the server /// to not be able to put just one entry into the result and return it. /// Therefore, the `chunkSize` value will only be consulted after an entry has /// been written into the result. If the result size is then bigger than /// `chunkSize`, the server will respond with as many entries as there are /// in the response already. If the result size is still smaller than `chunkSize`, /// the server will try to return more data if there's more data left to return. /// /// If `chunkSize` is not specified, some server-side default value will be used. /// /// The `Content-Type` of the result is `application/x-arango-dump`. This is an /// easy-to-process format, with all entries going onto separate lines in the /// response body. /// /// Each line itself is a JSON hash, with at least the following attributes: /// /// - `type`: the type of entry. Possible values for `type` are: /// /// - 2300: document insertion/update /// /// - 2301: edge insertion/update /// /// - 2302: document/edge deletion /// /// - `key`: the key of the document/edge or the key used in the deletion operation /// /// - `rev`: the revision id of the document/edge or the deletion operation /// /// - `data`: the actual document/edge data for types 2300 and 2301. The full /// document/edge data will be returned even for updates. /// /// A more detailed description of the different entry types and their /// data structures can be found in @ref RefManualReplicationEventTypes. /// /// Note: there will be no distinction between inserts and updates when calling this method. /// /// @RESTRETURNCODES /// /// @RESTRETURNCODE{200} /// is returned if the request was executed successfully. /// /// @RESTRETURNCODE{400} /// is returned if either the `from` or `to` values are invalid. /// /// @RESTRETURNCODE{404} /// is returned when the collection could not be found. /// /// @RESTRETURNCODE{405} /// is returned when an invalid HTTP method is used. /// /// @RESTRETURNCODE{500} /// is returned if an error occurred while assembling the response. /// /// @EXAMPLES /// /// Empty collection: /// /// @EXAMPLE_ARANGOSH_RUN{RestReplicationDumpEmpty} /// db._drop("testCollection"); /// var c = db._create("testCollection"); /// var url = "/_api/replication/dump?collection=" + c.name(); /// var response = logCurlRequest('GET', url); /// /// assert(response.code === 204); /// logRawResponse(response); /// /// c.drop(); /// @END_EXAMPLE_ARANGOSH_RUN /// /// Non-empty collection: /// /// @EXAMPLE_ARANGOSH_RUN{RestReplicationDump} /// db._drop("testCollection"); /// var c = db._create("testCollection"); /// c.save({ "test" : true, "a" : "abc", "_key" : "abcdef" }); /// c.save({ "b" : 1, "c" : false, "_key" : "123456" }); /// c.update("123456", { "d" : "additional value" }); /// c.save({ "_key": "foobar" }); /// c.remove("foobar"); /// c.remove("abcdef"); /// /// var url = "/_api/replication/dump?collection=" + c.name(); /// var response = logCurlRequest('GET', url); /// /// assert(response.code === 200); /// logRawResponse(response); /// /// c.drop(); /// @END_EXAMPLE_ARANGOSH_RUN //////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::handleCommandDump () { char const* collection = _request->value("collection"); if (collection == 0) { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "invalid collection parameter"); return; } // determine start tick for dump TRI_voc_tick_t tickStart = 0; TRI_voc_tick_t tickEnd = (TRI_voc_tick_t) UINT64_MAX; bool withTicks = true; bool translateCollectionIds = true; bool found; char const* value; value = _request->value("from", found); if (found) { tickStart = (TRI_voc_tick_t) StringUtils::uint64(value); } // determine end tick for dump value = _request->value("to", found); if (found) { tickEnd = (TRI_voc_tick_t) StringUtils::uint64(value); } if (tickStart > tickEnd || tickEnd == 0) { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "invalid from/to values"); return; } value = _request->value("ticks", found); if (found) { withTicks = StringUtils::boolean(value); } value = _request->value("translateIds", found); if (found) { translateCollectionIds = StringUtils::boolean(value); } const uint64_t chunkSize = determineChunkSize(); TRI_vocbase_col_t* c = TRI_LookupCollectionByNameVocBase(_vocbase, collection); if (c == 0) { generateError(HttpResponse::NOT_FOUND, TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND); return; } const TRI_voc_cid_t cid = c->_cid; LOG_DEBUG("requested collection dump for collection '%s', tickStart: %llu, tickEnd: %llu", collection, (unsigned long long) tickStart, (unsigned long long) tickEnd); TRI_vocbase_col_t* col = TRI_UseCollectionByIdVocBase(_vocbase, cid); if (col == 0) { generateError(HttpResponse::NOT_FOUND, TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND); return; } // initialise the dump container TRI_replication_dump_t dump; if (TRI_InitDumpReplication(&dump, _vocbase, (size_t) defaultChunkSize) != TRI_ERROR_NO_ERROR) { TRI_ReleaseCollectionVocBase(_vocbase, col); generateError(HttpResponse::SERVER_ERROR, TRI_ERROR_OUT_OF_MEMORY); return; } int res = TRI_DumpCollectionReplication(&dump, col, tickStart, tickEnd, chunkSize, withTicks, translateCollectionIds); TRI_ReleaseCollectionVocBase(_vocbase, col); if (res == TRI_ERROR_NO_ERROR) { // generate the result const size_t length = TRI_LengthStringBuffer(dump._buffer); if (length == 0) { _response = createResponse(HttpResponse::NO_CONTENT); } else { _response = createResponse(HttpResponse::OK); } _response->setContentType("application/x-arango-dump; charset=utf-8"); // set headers _response->setHeader(TRI_REPLICATION_HEADER_CHECKMORE, strlen(TRI_REPLICATION_HEADER_CHECKMORE), ((dump._hasMore || dump._bufferFull) ? "true" : "false")); _response->setHeader(TRI_REPLICATION_HEADER_LASTINCLUDED, strlen(TRI_REPLICATION_HEADER_LASTINCLUDED), StringUtils::itoa(dump._lastFoundTick)); // transfer ownership of the buffer contents _response->body().set(dump._buffer); // avoid double freeing TRI_StealStringBuffer(dump._buffer); } else { generateError(HttpResponse::SERVER_ERROR, res); } TRI_DestroyDumpReplication(&dump); } //////////////////////////////////////////////////////////////////////////////// /// @brief synchronises data from a remote endpoint /// /// @RESTHEADER{PUT /_api/replication/sync,synchronises data from a remote endpoint} /// /// @RESTBODYPARAM{configuration,json,required} /// A JSON representation of the configuration. /// /// @RESTDESCRIPTION /// Starts a full data synchronisation from a remote endpoint into the local /// ArangoDB database. /// /// The body of the request must be JSON hash with the configuration. The /// following attributes are allowed for the configuration: /// /// - `endpoint`: the endpoint to connect to (e.g. "tcp://192.168.173.13:8529"). /// /// - `database`: the database name on the master (if not specified, defaults to the /// name of the local current database). /// /// - `username`: an optional ArangoDB username to use when connecting to the endpoint. /// /// - `password`: the password to use when connecting to the endpoint. /// /// - `restrictType`: an optional string value for collection filtering. When /// specified, the allowed values are `include` or `exclude`. /// /// - `restrictCollections`: an optional list of collections for use with /// `restrictType`. If `restrictType` is `include`, only the specified collections /// will be sychronised. If `restrictType` is `exclude`, all but the specified /// collections will be synchronised. /// /// In case of success, the body of the response is a JSON hash with the following /// attributes: /// /// - `collections`: a list of collections that were transferred from the endpoint /// /// - `lastLogTick`: the last log tick on the endpoint at the time the transfer /// was started. Use this value as the `from` value when starting the continuous /// synchronisation later. /// /// WARNING: calling this method will sychronise data from the collections found /// on the remote endpoint to the local ArangoDB database. All data in the local /// collections will be purged and replaced with data from the endpoint. /// /// Use with caution! /// /// Note: this method is not supported on a coordinator in a cluster. /// /// @RESTRETURNCODES /// /// @RESTRETURNCODE{200} /// is returned if the request was executed successfully. /// /// @RESTRETURNCODE{400} /// is returned if the configuration is incomplete or malformed. /// /// @RESTRETURNCODE{405} /// is returned when an invalid HTTP method is used. /// /// @RESTRETURNCODE{500} /// is returned if an error occurred during sychronisation. /// /// @RESTRETURNCODE{501} /// is returned when this operation is called on a coordinator in a cluster. //////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::handleCommandSync () { TRI_json_t* json = parseJsonBody(); if (json == 0) { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_BAD_PARAMETER); return; } const string endpoint = JsonHelper::getStringValue(json, "endpoint", ""); const string database = JsonHelper::getStringValue(json, "database", _vocbase->_name); const string username = JsonHelper::getStringValue(json, "username", ""); const string password = JsonHelper::getStringValue(json, "password", ""); if (endpoint.empty()) { TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); generateError(HttpResponse::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, " must be a valid endpoint"); return; } map restrictCollections; TRI_json_t* restriction = JsonHelper::getArrayElement(json, "restrictCollections"); if (restriction != 0 && restriction->_type == TRI_JSON_LIST) { size_t i; const size_t n = restriction->_value._objects._length; for (i = 0; i < n; ++i) { TRI_json_t const* cname = (TRI_json_t const*) TRI_AtVector(&restriction->_value._objects, i); if (JsonHelper::isString(cname)) { restrictCollections.insert(pair(string(cname->_value._string.data, cname->_value._string.length - 1), true)); } } } string restrictType = JsonHelper::getStringValue(json, "restrictType", ""); TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); if ((restrictType.empty() && ! restrictCollections.empty()) || (! restrictType.empty() && restrictCollections.empty()) || (! restrictType.empty() && restrictType != "include" && restrictType != "exclude")) { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_BAD_PARAMETER, "invalid value for or "); return; } TRI_replication_applier_configuration_t config; TRI_InitConfigurationReplicationApplier(&config); config._endpoint = TRI_DuplicateString2Z(TRI_CORE_MEM_ZONE, endpoint.c_str(), endpoint.size()); config._database = TRI_DuplicateString2Z(TRI_CORE_MEM_ZONE, database.c_str(), database.size()); config._username = TRI_DuplicateString2Z(TRI_CORE_MEM_ZONE, username.c_str(), username.size()); config._password = TRI_DuplicateString2Z(TRI_CORE_MEM_ZONE, password.c_str(), password.size()); InitialSyncer syncer(_vocbase, &config, restrictCollections, restrictType, false); TRI_DestroyConfigurationReplicationApplier(&config); int res = TRI_ERROR_NO_ERROR; string errorMsg = ""; try { res = syncer.run(errorMsg); } catch (...) { errorMsg = "caught an exception"; res = TRI_ERROR_INTERNAL; } if (res != TRI_ERROR_NO_ERROR) { generateError(HttpResponse::SERVER_ERROR, res, errorMsg); return; } TRI_json_t result; TRI_InitArrayJson(TRI_CORE_MEM_ZONE, &result); TRI_json_t* jsonCollections = TRI_CreateListJson(TRI_CORE_MEM_ZONE); if (jsonCollections != 0) { map::const_iterator it; const map& c = syncer.getProcessedCollections(); for (it = c.begin(); it != c.end(); ++it) { const string cidString = StringUtils::itoa((*it).first); TRI_json_t* ci = TRI_CreateArray2Json(TRI_CORE_MEM_ZONE, 2); if (ci != 0) { TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, ci, "id", TRI_CreateString2CopyJson(TRI_CORE_MEM_ZONE, cidString.c_str(), cidString.size())); TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, ci, "name", TRI_CreateString2CopyJson(TRI_CORE_MEM_ZONE, (*it).second.c_str(), (*it).second.size())); TRI_PushBack3ListJson(TRI_CORE_MEM_ZONE, jsonCollections, ci); } } TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, &result, "collections", jsonCollections); } char* tickString = TRI_StringUInt64(syncer.getLastLogTick()); TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, &result, "lastLogTick", TRI_CreateStringJson(TRI_CORE_MEM_ZONE, tickString)); generateResult(&result); TRI_DestroyJson(TRI_CORE_MEM_ZONE, &result); } //////////////////////////////////////////////////////////////////////////////// /// @brief get the server's id /// /// @RESTHEADER{GET /_api/replication/server-id,returns the servers id} /// /// @RESTDESCRIPTION /// Returns the servers id. The id is also returned by other replication API /// methods, and this method is an easy means of determining a server's id. /// /// The body of the response is a JSON hash with the attribute `serverId`. The /// server id is returned as a string. /// /// @RESTRETURNCODES /// /// @RESTRETURNCODE{200} /// is returned if the request was executed successfully. /// /// @RESTRETURNCODE{405} /// is returned when an invalid HTTP method is used. /// /// @RESTRETURNCODE{500} /// is returned if an error occurred while assembling the response. /// /// @EXAMPLES /// /// @EXAMPLE_ARANGOSH_RUN{RestReplicationServerId} /// var url = "/_api/replication/server-id"; /// var response = logCurlRequest('GET', url); /// /// assert(response.code === 200); /// logJsonResponse(response); /// @END_EXAMPLE_ARANGOSH_RUN //////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::handleCommandServerId () { TRI_json_t result; TRI_InitArrayJson(TRI_CORE_MEM_ZONE, &result); const string serverId = StringUtils::itoa(TRI_GetIdServer()); TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, &result, "serverId", TRI_CreateString2CopyJson(TRI_CORE_MEM_ZONE, serverId.c_str(), serverId.size())); generateResult(&result); TRI_DestroyJson(TRI_CORE_MEM_ZONE, &result); } //////////////////////////////////////////////////////////////////////////////// /// @brief get the configuration of the replication applier /// /// @RESTHEADER{GET /_api/replication/applier-config,returns the configuration of the replication applier} /// /// @RESTDESCRIPTION /// Returns the configuration of the replication applier. /// /// The body of the response is a JSON hash with the configuration. The /// following attributes may be present in the configuration: /// /// - `endpoint`: the logger server to connect to (e.g. "tcp://192.168.173.13:8529"). /// /// - `database`: the name of the database to connect to (e.g. "_system"). /// /// - `username`: an optional ArangoDB username to use when connecting to the endpoint. /// /// - `password`: the password to use when connecting to the endpoint. /// /// - `maxConnectRetries`: the maximum number of connection attempts the applier /// will make in a row. If the applier cannot establish a connection to the /// endpoint in this number of attempts, it will stop itself. /// /// - `connectTimeout`: the timeout (in seconds) when attempting to connect to the /// endpoint. This value is used for each connection attempt. /// /// - `requestTimeout`: the timeout (in seconds) for individual requests to the endpoint. /// /// - `chunkSize`: the requested maximum size for log transfer packets that /// is used when the endpoint is contacted. /// /// - `autoStart`: whether or not to auto-start the replication applier on /// (next and following) server starts /// /// - `adaptivePolling`: whether or not the replication applier will use /// adaptive polling. /// /// @RESTRETURNCODES /// /// @RESTRETURNCODE{200} /// is returned if the request was executed successfully. /// /// @RESTRETURNCODE{405} /// is returned when an invalid HTTP method is used. /// /// @RESTRETURNCODE{500} /// is returned if an error occurred while assembling the response. /// /// @EXAMPLES /// /// @EXAMPLE_ARANGOSH_RUN{RestReplicationApplierGetConfig} /// var url = "/_api/replication/applier-config"; /// var response = logCurlRequest('GET', url); /// /// assert(response.code === 200); /// logJsonResponse(response); /// @END_EXAMPLE_ARANGOSH_RUN //////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::handleCommandApplierGetConfig () { assert(_vocbase->_replicationApplier != 0); TRI_replication_applier_configuration_t config; TRI_InitConfigurationReplicationApplier(&config); TRI_ReadLockReadWriteLock(&_vocbase->_replicationApplier->_statusLock); TRI_CopyConfigurationReplicationApplier(&_vocbase->_replicationApplier->_configuration, &config); TRI_ReadUnlockReadWriteLock(&_vocbase->_replicationApplier->_statusLock); TRI_json_t* json = TRI_JsonConfigurationReplicationApplier(&config); TRI_DestroyConfigurationReplicationApplier(&config); if (json == 0) { generateError(HttpResponse::SERVER_ERROR, TRI_ERROR_OUT_OF_MEMORY); return; } generateResult(json); TRI_FreeJson(TRI_CORE_MEM_ZONE, json); } //////////////////////////////////////////////////////////////////////////////// /// @brief set the configuration of the replication applier /// /// @RESTHEADER{PUT /_api/replication/applier-config,adjusts the configuration of the replication applier} /// /// @RESTBODYPARAM{configuration,json,required} /// A JSON representation of the configuration. /// /// @RESTDESCRIPTION /// Sets the configuration of the replication applier. The configuration can /// only be changed while the applier is not running. The updated configuration /// will be saved immediately but only become active with the next start of the /// applier. /// /// The body of the request must be JSON hash with the configuration. The /// following attributes are allowed for the configuration: /// /// - `endpoint`: the logger server to connect to (e.g. "tcp://192.168.173.13:8529"). /// The endpoint must be specified. /// /// - `database`: the name of the database on the endpoint. If not specified, defaults /// to the current local database name. /// /// - `username`: an optional ArangoDB username to use when connecting to the endpoint. /// /// - `password`: the password to use when connecting to the endpoint. /// /// - `maxConnectRetries`: the maximum number of connection attempts the applier /// will make in a row. If the applier cannot establish a connection to the /// endpoint in this number of attempts, it will stop itself. /// /// - `connectTimeout`: the timeout (in seconds) when attempting to connect to the /// endpoint. This value is used for each connection attempt. /// /// - `requestTimeout`: the timeout (in seconds) for individual requests to the endpoint. /// /// - `chunkSize`: the requested maximum size for log transfer packets that /// is used when the endpoint is contacted. /// /// - `autoStart`: whether or not to auto-start the replication applier on /// (next and following) server starts /// /// - `adaptivePolling`: if set to `true`, the replication applier will fall /// to sleep for an increasingly long period in case the logger server at the /// endpoint does not have any more replication events to apply. Using /// adaptive polling is thus useful to reduce the amount of work for both the /// applier and the logger server for cases when there are only infrequent /// changes. The downside is that when using adaptive polling, it might take /// longer for the replication applier to detect that there are new replication /// events on the logger server. /// /// Setting `adaptivePolling` to false will make the replication applier /// contact the logger server in a constant interval, regardless of whether /// the logger server provides updates frequently or seldomly. /// /// In case of success, the body of the response is a JSON hash with the updated /// configuration. /// /// @RESTRETURNCODES /// /// @RESTRETURNCODE{200} /// is returned if the request was executed successfully. /// /// @RESTRETURNCODE{400} /// is returned if the configuration is incomplete or malformed, or if the /// replication applier is currently running. /// /// @RESTRETURNCODE{405} /// is returned when an invalid HTTP method is used. /// /// @RESTRETURNCODE{500} /// is returned if an error occurred while assembling the response. /// /// @EXAMPLES /// /// @EXAMPLE_ARANGOSH_RUN{RestReplicationApplierSetConfig} /// var re = require("org/arangodb/replication"); /// re.applier.stop(); /// /// var url = "/_api/replication/applier-config"; /// var body = { /// endpoint: "tcp://127.0.0.1:8529", /// username: "replicationApplier", /// password: "applier1234@foxx", /// chunkSize: 4194304, /// autoStart: false, /// adaptivePolling: true /// }; /// /// var response = logCurlRequest('PUT', url, JSON.stringify(body)); /// /// assert(response.code === 200); /// logJsonResponse(response); /// @END_EXAMPLE_ARANGOSH_RUN //////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::handleCommandApplierSetConfig () { assert(_vocbase->_replicationApplier != 0); TRI_replication_applier_configuration_t config; TRI_InitConfigurationReplicationApplier(&config); TRI_json_t* json = parseJsonBody(); if (json == 0) { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_BAD_PARAMETER); return; } TRI_ReadLockReadWriteLock(&_vocbase->_replicationApplier->_statusLock); TRI_CopyConfigurationReplicationApplier(&_vocbase->_replicationApplier->_configuration, &config); TRI_ReadUnlockReadWriteLock(&_vocbase->_replicationApplier->_statusLock); TRI_json_t const* value; const string endpoint = JsonHelper::getStringValue(json, "endpoint", ""); if (! endpoint.empty()) { if (config._endpoint != 0) { TRI_FreeString(TRI_CORE_MEM_ZONE, config._endpoint); } config._endpoint = TRI_DuplicateString2Z(TRI_CORE_MEM_ZONE, endpoint.c_str(), endpoint.size()); } value = JsonHelper::getArrayElement(json, "database"); if (config._database != 0) { // free old value TRI_FreeString(TRI_CORE_MEM_ZONE, config._database); } if (JsonHelper::isString(value)) { config._database = TRI_DuplicateString2Z(TRI_CORE_MEM_ZONE, value->_value._string.data, value->_value._string.length - 1); } else { config._database = TRI_DuplicateStringZ(TRI_CORE_MEM_ZONE, _vocbase->_name); } value = JsonHelper::getArrayElement(json, "username"); if (JsonHelper::isString(value)) { if (config._username != 0) { TRI_FreeString(TRI_CORE_MEM_ZONE, config._username); } config._username = TRI_DuplicateString2Z(TRI_CORE_MEM_ZONE, value->_value._string.data, value->_value._string.length - 1); } value = JsonHelper::getArrayElement(json, "password"); if (JsonHelper::isString(value)) { if (config._password != 0) { TRI_FreeString(TRI_CORE_MEM_ZONE, config._password); } config._password = TRI_DuplicateString2Z(TRI_CORE_MEM_ZONE, value->_value._string.data, value->_value._string.length - 1); } config._requestTimeout = JsonHelper::getNumericValue(json, "requestTimeout", config._requestTimeout); config._connectTimeout = JsonHelper::getNumericValue(json, "connectTimeout", config._connectTimeout); config._ignoreErrors = JsonHelper::getNumericValue(json, "ignoreErrors", config._ignoreErrors); config._maxConnectRetries = JsonHelper::getNumericValue(json, "maxConnectRetries", config._maxConnectRetries); config._sslProtocol = JsonHelper::getNumericValue(json, "sslProtocol", config._sslProtocol); config._chunkSize = JsonHelper::getNumericValue(json, "chunkSize", config._chunkSize); config._autoStart = JsonHelper::getBooleanValue(json, "autoStart", config._autoStart); config._adaptivePolling = JsonHelper::getBooleanValue(json, "adaptivePolling", config._adaptivePolling); TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); int res = TRI_ConfigureReplicationApplier(_vocbase->_replicationApplier, &config); TRI_DestroyConfigurationReplicationApplier(&config); if (res != TRI_ERROR_NO_ERROR) { if (res == TRI_ERROR_REPLICATION_INVALID_APPLIER_CONFIGURATION || res == TRI_ERROR_REPLICATION_RUNNING) { generateError(HttpResponse::BAD, res); } else { generateError(HttpResponse::SERVER_ERROR, res); } return; } handleCommandApplierGetConfig(); } //////////////////////////////////////////////////////////////////////////////// /// @brief start the replication applier /// /// @RESTHEADER{PUT /_api/replication/applier-start,starts the replication applier} /// /// @RESTQUERYPARAMETERS /// /// @RESTQUERYPARAM{from,string,optional} /// The remote `lastLogTick` value from which to start applying. If not specified, /// the last saved tick from the previous applier run is used. If there is no /// previous applier state saved, the applier will start at the beginning of the /// logger server's log. /// /// @RESTDESCRIPTION /// Starts the replication applier. This will return immediately if the /// replication applier is already running. /// /// If the replication applier is not already running, the applier configuration /// will be checked, and if it is complete, the applier will be started in a /// background thread. This means that even if the applier will encounter any /// errors while running, they will not be reported in the response to this /// method. /// /// To detect replication applier errors after the applier was started, use the /// `/_api/replication/applier-state` API instead. /// /// @RESTRETURNCODES /// /// @RESTRETURNCODE{200} /// is returned if the request was executed successfully. /// /// @RESTRETURNCODE{400} /// is returned if the replication applier is not fully configured or the /// configuration is invalid. /// /// @RESTRETURNCODE{405} /// is returned when an invalid HTTP method is used. /// /// @RESTRETURNCODE{500} /// is returned if an error occurred while assembling the response. /// /// @EXAMPLES /// /// @EXAMPLE_ARANGOSH_RUN{RestReplicationApplierStart} /// var re = require("org/arangodb/replication"); /// re.applier.stop(); /// re.applier.properties({ /// endpoint: "tcp://127.0.0.1:8529", /// username: "replicationApplier", /// password: "applier1234@foxx", /// autoStart: false, /// adaptivePolling: true /// }); /// /// var url = "/_api/replication/applier-start"; /// var response = logCurlRequest('PUT', url, ""); /// re.applier.stop(); /// /// assert(response.code === 200); /// logJsonResponse(response); /// @END_EXAMPLE_ARANGOSH_RUN //////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::handleCommandApplierStart () { assert(_vocbase->_replicationApplier != 0); bool found; const char* value = _request->value("from", found); TRI_voc_tick_t initialTick = 0; if (found) { // url parameter "from" specified initialTick = (TRI_voc_tick_t) StringUtils::uint64(value); } int res = TRI_StartReplicationApplier(_vocbase->_replicationApplier, initialTick, found); if (res != TRI_ERROR_NO_ERROR) { if (res == TRI_ERROR_REPLICATION_INVALID_APPLIER_CONFIGURATION || res == TRI_ERROR_REPLICATION_RUNNING) { generateError(HttpResponse::BAD, res); } else { generateError(HttpResponse::SERVER_ERROR, res); } return; } handleCommandApplierGetState(); } //////////////////////////////////////////////////////////////////////////////// /// @brief stops the replication applier /// /// @RESTHEADER{PUT /_api/replication/applier-stop,stops the replication applier} /// /// @RESTDESCRIPTION /// Stops the replication applier. This will return immediately if the /// replication applier is not running. /// /// @RESTRETURNCODES /// /// @RESTRETURNCODE{200} /// is returned if the request was executed successfully. /// /// @RESTRETURNCODE{405} /// is returned when an invalid HTTP method is used. /// /// @RESTRETURNCODE{500} /// is returned if an error occurred while assembling the response. /// /// @EXAMPLES /// /// @EXAMPLE_ARANGOSH_RUN{RestReplicationApplierStop} /// var re = require("org/arangodb/replication"); /// re.applier.stop(); /// re.applier.properties({ /// endpoint: "tcp://127.0.0.1:8529", /// username: "replicationApplier", /// password: "applier1234@foxx", /// autoStart: false, /// adaptivePolling: true /// }); /// /// re.applier.start(); /// var url = "/_api/replication/applier-stop"; /// var response = logCurlRequest('PUT', url, ""); /// re.applier.stop(); /// /// assert(response.code === 200); /// logJsonResponse(response); /// @END_EXAMPLE_ARANGOSH_RUN //////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::handleCommandApplierStop () { assert(_vocbase->_replicationApplier != 0); int res = TRI_StopReplicationApplier(_vocbase->_replicationApplier, true); if (res != TRI_ERROR_NO_ERROR) { generateError(HttpResponse::SERVER_ERROR, res); return; } handleCommandApplierGetState(); } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the state of the replication applier /// /// @RESTHEADER{GET /_api/replication/applier-state,returns the state of the replication applier} /// /// @RESTDESCRIPTION /// Returns the state of the replication applier, regardless of whether the /// applier is currently running or not. /// /// The response is a JSON hash with the following attributes: /// /// - `state`: a JSON hash with the following sub-attributes: /// /// - `running`: whether or not the applier is active and running /// /// - `lastAppliedContinuousTick`: the last tick value from the continuous /// replication log the applier has applied. /// /// - `lastProcessedContinuousTick`: the last tick value from the continuous /// replication log the applier has processed. /// /// Regularly, the last applied and last processed tick values should be /// identical. For transactional operations, the replication applier will first /// process incoming log events before applying them, so the processed tick /// value might be higher than the applied tick value. This will be the case /// until the applier encounters the `transaction commit` log event for the /// transaction. /// /// - `lastAvailableContinuousTick`: the last tick value the logger server can /// provide. /// /// - `time`: the time on the applier server. /// /// - `totalRequests`: the total number of requests the applier has made to the /// endpoint. /// /// - `totalFailedConnects`: the total number of failed connection attempts the /// applier has made. /// /// - `totalEvents`: the total number of log events the applier has processed. /// /// - `progress`: a JSON hash with details about the replication applier progress. /// It contains the following sub-attributes if there is progress to report: /// /// - `message`: a textual description of the progress /// /// - `time`: the date and time the progress was logged /// /// - `failedConnects`: the current number of failed connection attempts /// /// - `lastError`: a JSON hash with details about the last error that happened on /// the applier. It contains the following sub-attributes if there was an error: /// /// - `errorNum`: a numerical error code /// /// - `errorMessage`: a textual error description /// /// - `time`: the date and time the error occurred /// /// In case no error has occurred, `lastError` will be empty. /// /// - `server`: a JSON hash with the following sub-attributes: /// /// - `version`: the applier server's version /// /// - `serverId`: the applier server's id /// /// - `endpoint`: the endpoint the applier is connected to (if applier is /// active) or will connect to (if applier is currently inactive) /// /// - `database`: the name of the database the applier is connected to (if applier is /// active) or will connect to (if applier is currently inactive) /// /// @RESTRETURNCODES /// /// @RESTRETURNCODE{200} /// is returned if the request was executed successfully. /// /// @RESTRETURNCODE{405} /// is returned when an invalid HTTP method is used. /// /// @RESTRETURNCODE{500} /// is returned if an error occurred while assembling the response. /// /// @EXAMPLES /// /// Fetching the state of an inactive applier: /// /// @EXAMPLE_ARANGOSH_RUN{RestReplicationApplierStateNotRunning} /// var re = require("org/arangodb/replication"); /// re.applier.stop(); /// /// var url = "/_api/replication/applier-state"; /// var response = logCurlRequest('GET', url); /// /// assert(response.code === 200); /// logJsonResponse(response); /// @END_EXAMPLE_ARANGOSH_RUN /// /// Fetching the state of an active applier: /// /// @EXAMPLE_ARANGOSH_RUN{RestReplicationApplierStateRunning} /// var re = require("org/arangodb/replication"); /// re.applier.stop(); /// re.applier.start(); /// /// var url = "/_api/replication/applier-state"; /// var response = logCurlRequest('GET', url); /// re.applier.stop(); /// /// assert(response.code === 200); /// logJsonResponse(response); /// @END_EXAMPLE_ARANGOSH_RUN //////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::handleCommandApplierGetState () { assert(_vocbase->_replicationApplier != 0); TRI_json_t* json = TRI_JsonReplicationApplier(_vocbase->_replicationApplier); if (json == 0) { generateError(HttpResponse::SERVER_ERROR, TRI_ERROR_OUT_OF_MEMORY); return; } generateResult(json); TRI_FreeJson(TRI_CORE_MEM_ZONE, json); } //////////////////////////////////////////////////////////////////////////////// /// @brief delete the state of the replication applier //////////////////////////////////////////////////////////////////////////////// void RestReplicationHandler::handleCommandApplierDeleteState () { assert(_vocbase->_replicationApplier != 0); int res = TRI_ForgetReplicationApplier(_vocbase->_replicationApplier); if (res != TRI_ERROR_NO_ERROR) { generateError(HttpResponse::SERVER_ERROR, res); return; } handleCommandApplierGetState(); } // Local Variables: // mode: outline-minor // outline-regexp: "/// @brief\\|/// {@inheritDoc}\\|/// @addtogroup\\|/// @page\\|// --SECTION--\\|/// @\\}" // End: