1
0
Fork 0
arangodb/arangod/RestHandler/RestReplicationHandler.cpp

592 lines
19 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// @brief replication request handler
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2004-2013 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-2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
#include "RestReplicationHandler.h"
#include "build.h"
#include "BasicsC/conversions.h"
#include "BasicsC/files.h"
#include "Logger/Logger.h"
#include "HttpServer/HttpServer.h"
#include "Rest/HttpRequest.h"
#include "VocBase/replication.h"
#include "VocBase/server-id.h"
#ifdef TRI_ENABLE_REPLICATION
using namespace std;
using namespace triagens::basics;
using namespace triagens::rest;
using namespace triagens::arango;
const uint64_t RestReplicationHandler::minChunkSize = 512 * 1024;
// -----------------------------------------------------------------------------
// --SECTION-- constructors and destructors
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoDB
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @brief constructor
////////////////////////////////////////////////////////////////////////////////
RestReplicationHandler::RestReplicationHandler (HttpRequest* request,
TRI_vocbase_t* vocbase)
: RestVocbaseBaseHandler(request, vocbase) {
}
////////////////////////////////////////////////////////////////////////////////
/// @brief destructor
////////////////////////////////////////////////////////////////////////////////
RestReplicationHandler::~RestReplicationHandler () {
}
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- Handler methods
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoDB
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
bool RestReplicationHandler::isDirect () {
return false;
}
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
string const& RestReplicationHandler::queue () const {
static string const client = "STANDARD";
return client;
}
////////////////////////////////////////////////////////////////////////////////
/// {@inheritDoc}
////////////////////////////////////////////////////////////////////////////////
Handler::status_e RestReplicationHandler::execute() {
// extract the request type
const HttpRequest::HttpRequestType type = _request->requestType();
vector<string> const& suffix = _request->suffix();
const size_t len = suffix.size();
if (len == 1) {
const string& command = suffix[0];
if (command == "start") {
if (type != HttpRequest::HTTP_REQUEST_PUT) {
goto BAD_CALL;
}
handleCommandStart();
}
else if (command == "stop") {
if (type != HttpRequest::HTTP_REQUEST_PUT) {
goto BAD_CALL;
}
handleCommandStop();
}
else if (command == "state") {
if (type != HttpRequest::HTTP_REQUEST_GET) {
goto BAD_CALL;
}
handleCommandState();
}
else if (command == "inventory") {
if (type != HttpRequest::HTTP_REQUEST_GET) {
goto BAD_CALL;
}
handleCommandInventory();
}
else if (command == "dump") {
if (type != HttpRequest::HTTP_REQUEST_GET) {
goto BAD_CALL;
}
handleCommandDump();
}
else if (command == "follow") {
if (type != HttpRequest::HTTP_REQUEST_GET) {
goto BAD_CALL;
}
handleCommandFollow();
}
else {
generateError(HttpResponse::BAD,
TRI_ERROR_HTTP_BAD_PARAMETER,
"invalid command");
}
return Handler::HANDLER_DONE;
}
BAD_CALL:
if (len != 1) {
generateError(HttpResponse::BAD,
TRI_ERROR_HTTP_SUPERFLUOUS_SUFFICES,
"expecting URL /_api/replication/<command>");
}
else {
generateError(HttpResponse::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED);
}
return Handler::HANDLER_DONE;
}
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- public static methods
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoDB
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @brief filter a collection based on collection attributes
////////////////////////////////////////////////////////////////////////////////
bool RestReplicationHandler::filterCollection (TRI_vocbase_col_t* collection,
void* data) {
const char* name = collection->_name;
if (name == NULL) {
// invalid collection
return false;
}
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;
}
if (*name == '_' && TRI_ExcludeCollectionReplication(name)) {
// system collection
return false;
}
// all other cases should be included
return true;
}
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- private methods
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoDB
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @brief determine the minimum chunk size
////////////////////////////////////////////////////////////////////////////////
uint64_t RestReplicationHandler::determineChunkSize () const {
// determine chunk size
uint64_t chunkSize = minChunkSize;
bool found;
const char* value = _request->value("chunkSize", found);
if (found) {
chunkSize = (uint64_t) StringUtils::uint64(value);
}
if (chunkSize < minChunkSize) {
chunkSize = minChunkSize;
}
return chunkSize;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief add replication state to a JSON array
////////////////////////////////////////////////////////////////////////////////
void RestReplicationHandler::addState (TRI_json_t* dst,
TRI_replication_log_state_t const* state) {
TRI_json_t* stateJson = TRI_CreateArray2Json(TRI_CORE_MEM_ZONE, 3);
// add replication state
TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, stateJson, "running", TRI_CreateBooleanJson(TRI_CORE_MEM_ZONE, state->_active));
char* firstString = TRI_StringUInt64(state->_firstTick);
TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, stateJson, "firstTick", TRI_CreateStringJson(TRI_CORE_MEM_ZONE, firstString));
char* lastString = TRI_StringUInt64(state->_lastTick);
TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, stateJson, "lastTick", TRI_CreateStringJson(TRI_CORE_MEM_ZONE, lastString));
TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, dst, "state", stateJson);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief remotely start the replication
////////////////////////////////////////////////////////////////////////////////
void RestReplicationHandler::handleCommandStart () {
assert(_vocbase->_replicationLogger != 0);
int res = TRI_StartReplicationLogger(_vocbase->_replicationLogger);
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, "running", TRI_CreateBooleanJson(TRI_CORE_MEM_ZONE, true));
generateResult(&result);
TRI_DestroyJson(TRI_CORE_MEM_ZONE, &result);
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief remotely stop the replication
////////////////////////////////////////////////////////////////////////////////
void RestReplicationHandler::handleCommandStop () {
assert(_vocbase->_replicationLogger != 0);
int res = TRI_StopReplicationLogger(_vocbase->_replicationLogger);
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, "running", TRI_CreateBooleanJson(TRI_CORE_MEM_ZONE, false));
generateResult(&result);
TRI_DestroyJson(TRI_CORE_MEM_ZONE, &result);
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the state of the replication
////////////////////////////////////////////////////////////////////////////////
void RestReplicationHandler::handleCommandState () {
assert(_vocbase->_replicationLogger != 0);
TRI_replication_log_state_t state;
int res = TRI_StateReplicationLogger(_vocbase->_replicationLogger, &state);
if (res != TRI_ERROR_NO_ERROR) {
generateError(HttpResponse::SERVER_ERROR, res);
}
else {
TRI_json_t result;
TRI_InitArrayJson(TRI_CORE_MEM_ZONE, &result);
addState(&result, &state);
// add server info
TRI_json_t* server = TRI_CreateArrayJson(TRI_CORE_MEM_ZONE);
TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, server, "version", TRI_CreateStringCopyJson(TRI_CORE_MEM_ZONE, TRIAGENS_VERSION));
TRI_server_id_t serverId = TRI_GetServerId();
TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, server, "serverId", TRI_CreateStringJson(TRI_CORE_MEM_ZONE, TRI_StringUInt64(serverId)));
TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, &result, "server", server);
generateResult(&result);
TRI_DestroyJson(TRI_CORE_MEM_ZONE, &result);
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return the inventory (current replication and collection state)
////////////////////////////////////////////////////////////////////////////////
void RestReplicationHandler::handleCommandInventory () {
assert(_vocbase->_replicationLogger != 0);
TRI_voc_tick_t tick = TRI_CurrentTickVocBase();
// collections
TRI_json_t* collections = TRI_ParametersCollectionsVocBase(_vocbase, tick, &filterCollection, NULL);
TRI_replication_log_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);
}
else {
TRI_json_t result;
TRI_InitArrayJson(TRI_CORE_MEM_ZONE, &result);
// add collections data
TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, &result, "collections", collections);
addState(&result, &state);
generateResult(&result);
TRI_DestroyJson(TRI_CORE_MEM_ZONE, &result);
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief handle a dump command for a specific collection
////////////////////////////////////////////////////////////////////////////////
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 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();
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;
LOGGER_DEBUG("request collection dump for collection '" << collection << "', "
"tickStart: " << tickStart << ", tickEnd: " << 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;
TRI_InitDumpReplication(&dump);
dump._buffer = TRI_CreateSizedStringBuffer(TRI_CORE_MEM_ZONE, (size_t) minChunkSize);
if (dump._buffer == 0) {
TRI_ReleaseCollectionVocBase(_vocbase, col);
generateError(HttpResponse::SERVER_ERROR, TRI_ERROR_OUT_OF_MEMORY);
return;
}
int res = TRI_DumpCollectionReplication(&dump, col, tickStart, tickEnd, chunkSize);
TRI_ReleaseCollectionVocBase(_vocbase, col);
if (res == TRI_ERROR_NO_ERROR) {
// generate the result
_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_LASTFOUND,
strlen(TRI_REPLICATION_HEADER_LASTFOUND),
StringUtils::itoa(dump._lastFoundTick));
// transfer ownership of the buffer contents
_response->body().appendText(TRI_BeginStringBuffer(dump._buffer), TRI_LengthStringBuffer(dump._buffer));
// avoid double freeing
dump._buffer->_buffer = 0;
}
else {
generateError(HttpResponse::SERVER_ERROR, res);
}
TRI_FreeStringBuffer(TRI_CORE_MEM_ZONE, dump._buffer);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief handle a follow command for the replication log
////////////////////////////////////////////////////////////////////////////////
void RestReplicationHandler::handleCommandFollow () {
// 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;
}
TRI_replication_log_state_t state;
int res = TRI_StateReplicationLogger(_vocbase->_replicationLogger, &state);
if (res != TRI_ERROR_NO_ERROR) {
generateError(HttpResponse::SERVER_ERROR, res);
return;
}
const uint64_t chunkSize = determineChunkSize();
// initialise the dump container
TRI_replication_dump_t dump;
TRI_InitDumpReplication(&dump);
dump._buffer = TRI_CreateSizedStringBuffer(TRI_CORE_MEM_ZONE, (size_t) minChunkSize);
if (dump._buffer == 0) {
generateError(HttpResponse::SERVER_ERROR, TRI_ERROR_OUT_OF_MEMORY);
return;
}
res = TRI_DumpLogReplication(_vocbase, &dump, tickStart, tickEnd, chunkSize);
if (res == TRI_ERROR_NO_ERROR) {
const bool checkMore = (dump._lastFoundTick > 0 && dump._lastFoundTick != state._lastTick);
// generate the result
_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_LASTFOUND,
strlen(TRI_REPLICATION_HEADER_LASTFOUND),
StringUtils::itoa(dump._lastFoundTick));
_response->setHeader(TRI_REPLICATION_HEADER_ACTIVE,
strlen(TRI_REPLICATION_HEADER_ACTIVE),
state._active ? "true" : "false");
// transfer ownership of the buffer contents
_response->body().appendText(TRI_BeginStringBuffer(dump._buffer), TRI_LengthStringBuffer(dump._buffer));
// avoid double freeing
dump._buffer->_buffer = 0;
}
else {
generateError(HttpResponse::SERVER_ERROR, res);
}
TRI_FreeStringBuffer(TRI_CORE_MEM_ZONE, dump._buffer);
}
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
#endif
// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// {@inheritDoc}\\|/// @addtogroup\\|/// @page\\|// --SECTION--\\|/// @\\}"
// End: