//////////////////////////////////////////////////////////////////////////////// /// DISCLAIMER /// /// Copyright 2014-2018 ArangoDB GmbH, Cologne, Germany /// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// /// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Jan Steemann //////////////////////////////////////////////////////////////////////////////// #include "v8-cluster.h" #include #include #include "Agency/AgencyComm.h" #include "ApplicationFeatures/ApplicationServer.h" #include "Basics/Exceptions.h" #include "Basics/StringBuffer.h" #include "Cluster/ClusterComm.h" #include "Cluster/ClusterInfo.h" #include "Cluster/ServerState.h" #include "GeneralServer/AuthenticationFeature.h" #include "Replication/ReplicationFeature.h" #include "Sharding/ShardDistributionReporter.h" #include "V8/v8-buffer.h" #include "V8/v8-conv.h" #include "V8/v8-globals.h" #include "V8/v8-utils.h" #include "V8/v8-vpack.h" #include "V8Server/v8-vocbaseprivate.h" #include "VocBase/LogicalCollection.h" #include "VocBase/ticks.h" using namespace arangodb; using namespace arangodb::basics; //////////////////////////////////////////////////////////////////////////////// /// @brief creates a v8 exception object //////////////////////////////////////////////////////////////////////////////// #define THROW_AGENCY_EXCEPTION(data) \ CreateAgencyException(args, data); \ return; static void onlyInCluster() { if (ServerState::instance()->isRunningInCluster()) { return; } THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "ArangoDB is not running in cluster mode"); } static void onlyInClusterOrActiveFailover() { auto replicationFeature = ReplicationFeature::INSTANCE; if (replicationFeature != nullptr && replicationFeature->isActiveFailoverEnabled()) { // active failover enabled return; } return onlyInCluster(); } static void CreateAgencyException(v8::FunctionCallbackInfo const& args, AgencyCommResult const& result) { v8::Isolate* isolate = args.GetIsolate(); TRI_V8_CURRENT_GLOBALS_AND_SCOPE; std::string const errorDetails = result.errorDetails(); v8::Handle errorMessage = TRI_V8_STD_STRING(isolate, errorDetails); if (errorMessage.IsEmpty()) { isolate->ThrowException(v8::Object::New(isolate)); return; } v8::Handle errorObject = v8::Exception::Error(errorMessage)->ToObject(TRI_IGETC).FromMaybe(v8::Local()); if (errorObject.IsEmpty()) { isolate->ThrowException(v8::Object::New(isolate)); return; } errorObject->Set(TRI_V8_STD_STRING(isolate, StaticStrings::Code), v8::Number::New(isolate, result.httpCode())); errorObject->Set(TRI_V8_STD_STRING(isolate, StaticStrings::ErrorNum), v8::Number::New(isolate, result.errorCode())); errorObject->Set(TRI_V8_STD_STRING(isolate, StaticStrings::ErrorMessage), errorMessage); errorObject->Set(TRI_V8_STD_STRING(isolate, StaticStrings::Error), v8::True(isolate)); TRI_GET_GLOBAL(ArangoErrorTempl, v8::ObjectTemplate); v8::Handle proto = ArangoErrorTempl->NewInstance(); if (!proto.IsEmpty()) { errorObject->SetPrototype(TRI_IGETC, proto).FromMaybe(false); } args.GetIsolate()->ThrowException(errorObject); } //////////////////////////////////////////////////////////////////////////////// /// @brief compares and swaps a value in the agency //////////////////////////////////////////////////////////////////////////////// static void JS_CasAgency(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInClusterOrActiveFailover(); if (args.Length() < 3) { TRI_V8_THROW_EXCEPTION_USAGE( "cas(, , , , , )"); } std::string const key = TRI_ObjectToString(isolate, args[0]); VPackBuilder oldBuilder; int res = TRI_V8ToVPack(isolate, oldBuilder, args[1], false); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_EXCEPTION_PARAMETER("cannot convert to VPack"); } VPackBuilder newBuilder; res = TRI_V8ToVPack(isolate, newBuilder, args[2], false); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_EXCEPTION_PARAMETER("cannot convert to VPack"); } double ttl = 0.0; if (args.Length() > 3) { ttl = TRI_ObjectToDouble(isolate, args[3]); } double timeout = 1.0; if (args.Length() > 4) { timeout = TRI_ObjectToDouble(isolate, args[4]); } bool shouldThrow = false; if (args.Length() > 5) { shouldThrow = TRI_ObjectToBoolean(isolate, args[5]); } AgencyComm comm; AgencyCommResult result = comm.casValue(key, oldBuilder.slice(), newBuilder.slice(), ttl, timeout); if (!result.successful()) { if (!shouldThrow) { TRI_V8_RETURN_FALSE(); } THROW_AGENCY_EXCEPTION(result); } TRI_V8_RETURN_TRUE(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief creates a directory in the agency //////////////////////////////////////////////////////////////////////////////// static void JS_CreateDirectoryAgency(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInClusterOrActiveFailover(); if (args.Length() != 1) { TRI_V8_THROW_EXCEPTION_USAGE("createDirectory()"); } std::string const key = TRI_ObjectToString(isolate, args[0]); AgencyComm comm; AgencyCommResult result = comm.createDirectory(key); if (!result.successful()) { THROW_AGENCY_EXCEPTION(result); } TRI_V8_RETURN_TRUE(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief whether or not the agency is enabled //////////////////////////////////////////////////////////////////////////////// static void JS_IsEnabledAgency(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate) v8::HandleScope scope(isolate); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("isEnabled()"); } if (AgencyCommManager::isEnabled()) { TRI_V8_RETURN_TRUE(); } TRI_V8_RETURN_FALSE(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief increase the version number //////////////////////////////////////////////////////////////////////////////// static void JS_IncreaseVersionAgency(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate) v8::HandleScope scope(isolate); onlyInClusterOrActiveFailover(); if (args.Length() != 1) { TRI_V8_THROW_EXCEPTION_USAGE("increaseVersion()"); } std::string const key = TRI_ObjectToString(isolate, args[0]); AgencyComm comm; if (!comm.increaseVersion(key)) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "unable to increase version"); } TRI_V8_RETURN_TRUE(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief gets a value from the agency //////////////////////////////////////////////////////////////////////////////// static void JS_GetAgency(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate) v8::HandleScope scope(isolate); onlyInClusterOrActiveFailover(); if (args.Length() < 1) { TRI_V8_THROW_EXCEPTION_USAGE("get()"); } std::string const key = TRI_ObjectToString(isolate, args[0]); AgencyComm comm; AgencyCommResult result = comm.getValues(key); if (!result.successful()) { THROW_AGENCY_EXCEPTION(result); } v8::Handle l = v8::Object::New(isolate); // return just the value for each key for (auto const& a : VPackArrayIterator(result.slice())) { for (auto const& o : VPackObjectIterator(a)) { std::string const key = o.key.copyString(); VPackSlice const slice = o.value; if (!slice.isNone()) { l->Set(TRI_V8_STD_STRING(isolate, key), TRI_VPackToV8(isolate, slice)); } } } TRI_V8_RETURN(l); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief read transaction to the agency //////////////////////////////////////////////////////////////////////////////// static void JS_APIAgency(std::string const& envelope, v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate) v8::HandleScope scope(isolate); onlyInClusterOrActiveFailover(); if (args.Length() < 1) { TRI_V8_THROW_EXCEPTION_USAGE(std::string(envelope) + "([[...]])"); } VPackBuilder builder; int res = TRI_V8ToVPack(isolate, builder, args[0], false); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_EXCEPTION_PARAMETER("cannot convert query to JSON"); } AgencyComm comm; AgencyCommResult result = comm.sendWithFailover(arangodb::rest::RequestType::POST, AgencyCommManager::CONNECTION_OPTIONS._requestTimeout, std::string("/_api/agency/") + envelope, builder.slice()); if (!result.successful()) { THROW_AGENCY_EXCEPTION(result); } try { result.setVPack(VPackParser::fromJson(result.bodyRef())); result._body.clear(); } catch (std::exception const& e) { LOG_TOPIC("57115", ERR, Logger::AGENCYCOMM) << "Error transforming result: " << e.what(); result.clear(); } catch (...) { LOG_TOPIC("ec86e", ERR, Logger::AGENCYCOMM) << "Error transforming result: out of memory"; result.clear(); } auto l = TRI_VPackToV8(isolate, result.slice()); TRI_V8_RETURN(l); TRI_V8_TRY_CATCH_END } static void JS_ReadAgency(v8::FunctionCallbackInfo const& args) { JS_APIAgency("read", args); } static void JS_WriteAgency(v8::FunctionCallbackInfo const& args) { JS_APIAgency("write", args); } static void JS_TransactAgency(v8::FunctionCallbackInfo const& args) { JS_APIAgency("transact", args); } static void JS_TransientAgency(v8::FunctionCallbackInfo const& args) { JS_APIAgency("transient", args); } //////////////////////////////////////////////////////////////////////////////// /// @brief acquires a write-lock in the agency //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief removes a value from the agency //////////////////////////////////////////////////////////////////////////////// static void JS_RemoveAgency(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInClusterOrActiveFailover(); if (args.Length() < 1) { TRI_V8_THROW_EXCEPTION_USAGE("remove(, )"); } std::string const key = TRI_ObjectToString(isolate, args[0]); bool recursive = false; if (args.Length() > 1) { recursive = TRI_ObjectToBoolean(isolate, args[1]); } AgencyComm comm; AgencyCommResult result = comm.removeValues(key, recursive); if (!result.successful()) { THROW_AGENCY_EXCEPTION(result); } TRI_V8_RETURN_TRUE(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief sets a value in the agency //////////////////////////////////////////////////////////////////////////////// static void JS_SetAgency(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInClusterOrActiveFailover(); if (args.Length() < 2) { TRI_V8_THROW_EXCEPTION_USAGE("set(, , )"); } std::string const key = TRI_ObjectToString(isolate, args[0]); VPackBuilder builder; int res = TRI_V8ToVPack(isolate, builder, args[1], false); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_EXCEPTION_PARAMETER("cannot convert to JSON"); } double ttl = 0.0; if (args.Length() > 2) { ttl = TRI_ObjectToDouble(isolate, args[2]); } AgencyComm comm; AgencyCommResult result = comm.setValue(key, builder.slice(), ttl); if (!result.successful()) { THROW_AGENCY_EXCEPTION(result); } TRI_V8_RETURN_TRUE(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the agency summery //////////////////////////////////////////////////////////////////////////////// static void JS_Agency(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate) v8::HandleScope scope(isolate); onlyInClusterOrActiveFailover(); if (args.Length() > 0) { TRI_V8_THROW_EXCEPTION_USAGE("agency()"); } VPackBuilder builder; AgencyComm comm; AgencyCommResult result = comm.sendWithFailover(arangodb::rest::RequestType::GET, AgencyCommManager::CONNECTION_OPTIONS._requestTimeout, std::string("/_api/agency/config"), builder.slice()); if (!result.successful()) { THROW_AGENCY_EXCEPTION(result); } try { result.setVPack(VPackParser::fromJson(result.bodyRef())); result._body.clear(); } catch (std::exception const& e) { LOG_TOPIC("d8594", ERR, Logger::AGENCYCOMM) << "Error transforming result: " << e.what(); result.clear(); } catch (...) { LOG_TOPIC("0ba23", ERR, Logger::AGENCYCOMM) << "Error transforming result: out of memory"; result.clear(); } auto l = TRI_VPackToV8(isolate, result.slice()); TRI_V8_RETURN(l); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the agency endpoints //////////////////////////////////////////////////////////////////////////////// static void JS_EndpointsAgency(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInClusterOrActiveFailover(); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("endpoints()"); } std::vector endpoints = AgencyCommManager::MANAGER->endpoints(); // make the list of endpoints unique std::sort(endpoints.begin(), endpoints.end()); endpoints.assign(endpoints.begin(), std::unique(endpoints.begin(), endpoints.end())); v8::Handle l = v8::Array::New(isolate); for (size_t i = 0; i < endpoints.size(); ++i) { std::string const endpoint = endpoints[i]; l->Set((uint32_t)i, TRI_V8_STD_STRING(isolate, endpoint)); } TRI_V8_RETURN(l); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the agency prefix //////////////////////////////////////////////////////////////////////////////// static void JS_PrefixAgency(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); std::string const prefix = AgencyCommManager::path(); TRI_V8_RETURN_STD_STRING(prefix); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief creates a uniqid //////////////////////////////////////////////////////////////////////////////// static void JS_UniqidAgency(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInClusterOrActiveFailover(); if (args.Length() > 2) { TRI_V8_THROW_EXCEPTION_USAGE("uniqid(, )"); } uint64_t count = 1; if (args.Length() > 0) { count = TRI_ObjectToUInt64(isolate, args[0], true); } if (count < 1 || count > 10000000) { TRI_V8_THROW_EXCEPTION_PARAMETER(" is invalid"); } double timeout = 0.0; if (args.Length() > 1) { timeout = TRI_ObjectToDouble(isolate, args[1]); } AgencyComm comm; uint64_t result = comm.uniqid(count, timeout); std::string const value = StringUtils::itoa(result); TRI_V8_RETURN_STD_STRING(value); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the agency version //////////////////////////////////////////////////////////////////////////////// static void JS_VersionAgency(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInClusterOrActiveFailover(); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("version()"); } AgencyComm comm; std::string const version = comm.version(); TRI_V8_RETURN_STD_STRING(version); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief whether or not a specific database exists //////////////////////////////////////////////////////////////////////////////// static void JS_DoesDatabaseExistClusterInfo(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInCluster(); if (args.Length() != 1) { TRI_V8_THROW_EXCEPTION_USAGE("doesDatabaseExist()"); } bool const result = ClusterInfo::instance()->doesDatabaseExist(TRI_ObjectToString(isolate, args[0]), true); if (result) { TRI_V8_RETURN_TRUE(); } TRI_V8_RETURN_FALSE(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief get the list of databases in the cluster //////////////////////////////////////////////////////////////////////////////// static void JS_Databases(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInCluster(); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("databases()"); } std::vector res = ClusterInfo::instance()->databases(true); v8::Handle a = v8::Array::New(isolate, (int)res.size()); std::vector::iterator it; int count = 0; for (it = res.begin(); it != res.end(); ++it) { a->Set((uint32_t)count++, TRI_V8_STD_STRING(isolate, (*it))); } TRI_V8_RETURN(a); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief flush the caches (used for testing only) //////////////////////////////////////////////////////////////////////////////// static void JS_FlushClusterInfo(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInCluster(); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("flush()"); } ClusterInfo::instance()->flush(); TRI_V8_RETURN_TRUE(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief get the info about a collection in Plan //////////////////////////////////////////////////////////////////////////////// static void JS_GetCollectionInfoClusterInfo(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInCluster(); if (args.Length() != 2) { TRI_V8_THROW_EXCEPTION_USAGE( "getCollectionInfo(, )"); } auto databaseID = TRI_ObjectToString(isolate, args[0]); auto collectionID = TRI_ObjectToString(isolate, args[1]); std::shared_ptr ci = ClusterInfo::instance()->getCollectionNT(databaseID, collectionID); if (ci == nullptr) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND, ClusterInfo::getCollectionNotFoundMsg(databaseID, collectionID)); } std::unordered_set ignoreKeys{"allowUserKeys", "avoidServers", "cid", "globallyUniqueId", "count", "distributeShardsLike", "indexBuckets", "keyOptions", "numberOfShards", "path", "planId", "version", "objectId"}; VPackBuilder infoBuilder = ci->toVelocyPackIgnore(ignoreKeys, LogicalCollection::Serialization::List); VPackSlice info = infoBuilder.slice(); TRI_ASSERT(info.isObject()); v8::Handle result = TRI_VPackToV8(isolate, info)->ToObject(TRI_IGETC).FromMaybe(v8::Local()); // Compute ShardShorts auto serverAliases = ClusterInfo::instance()->getServerAliases(); VPackSlice shards = info.get("shards"); TRI_ASSERT(shards.isObject()); v8::Handle shardShorts = v8::Object::New(isolate); for (auto const& p : VPackObjectIterator(shards)) { TRI_ASSERT(p.value.isArray()); v8::Handle shorts = v8::Array::New(isolate); uint32_t pos = 0; for (auto const& s : VPackArrayIterator(p.value)) { try { std::string t = s.copyString(); if (t.at(0) == '_') { t = t.substr(1); } shorts->Set(pos, TRI_V8_STD_STRING(isolate, serverAliases.at(t))); pos++; } catch (...) { } } shardShorts->Set(TRI_V8_STD_STRING(isolate, p.key.copyString()), shorts); } result->Set(TRI_V8_ASCII_STRING(isolate, "shardShorts"), shardShorts); TRI_V8_RETURN(result); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief get the info about a collection in Current //////////////////////////////////////////////////////////////////////////////// static void JS_GetCollectionInfoCurrentClusterInfo(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInCluster(); if (args.Length() != 3) { TRI_V8_THROW_EXCEPTION_USAGE( "getCollectionInfoCurrent(, , )"); } ShardID shardID = TRI_ObjectToString(isolate, args[2]); auto databaseID = TRI_ObjectToString(isolate, args[0]); auto collectionID = TRI_ObjectToString(isolate, args[1]); std::shared_ptr ci = ClusterInfo::instance()->getCollectionNT(databaseID, collectionID); if (ci == nullptr) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND, ClusterInfo::getCollectionNotFoundMsg(databaseID, collectionID)); } v8::Handle result = v8::Object::New(isolate); // First some stuff from Plan for which Current does not make sense: auto cid = std::to_string(ci->id()); std::string const& name = ci->name(); result->Set(TRI_V8_ASCII_STRING(isolate, "id"), TRI_V8_STD_STRING(isolate, cid)); result->Set(TRI_V8_ASCII_STRING(isolate, "name"), TRI_V8_STD_STRING(isolate, name)); std::shared_ptr cic = ClusterInfo::instance()->getCollectionCurrent(TRI_ObjectToString(isolate, args[0]), cid); result->Set(TRI_V8_ASCII_STRING(isolate, "currentVersion"), v8::Number::New(isolate, (double)cic->getCurrentVersion())); result->Set(TRI_V8_ASCII_STRING(isolate, "type"), v8::Number::New(isolate, (int)ci->type())); VPackSlice slice = cic->getIndexes(shardID); v8::Handle indexes = TRI_VPackToV8(isolate, slice); result->Set(TRI_V8_ASCII_STRING(isolate, "indexes"), indexes); // Finally, report any possible error: bool error = cic->error(shardID); result->Set(TRI_V8_STD_STRING(isolate, StaticStrings::Error), v8::Boolean::New(isolate, error)); if (error) { result->Set(TRI_V8_STD_STRING(isolate, StaticStrings::ErrorNum), v8::Number::New(isolate, cic->errorNum(shardID))); std::string const errorMessage = cic->errorMessage(shardID); result->Set(TRI_V8_STD_STRING(isolate, StaticStrings::ErrorMessage), TRI_V8_STD_STRING(isolate, errorMessage)); } auto servers = cic->servers(shardID); v8::Handle list = v8::Array::New(isolate, static_cast(servers.size())); v8::Handle shorts = v8::Array::New(isolate); auto serverAliases = ClusterInfo::instance()->getServerAliases(); uint32_t pos = 0; for (auto const& s : servers) { try { shorts->Set(pos, TRI_V8_STD_STRING(isolate, serverAliases.at(s))); } catch (...) { } list->Set(pos, TRI_V8_STD_STRING(isolate, s)); pos++; } result->Set(TRI_V8_ASCII_STRING(isolate, "servers"), list); result->Set(TRI_V8_ASCII_STRING(isolate, "shorts"), shorts); TRI_V8_RETURN(result); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief get the responsible server //////////////////////////////////////////////////////////////////////////////// static void JS_GetResponsibleServerClusterInfo(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInCluster(); if (args.Length() != 1) { TRI_V8_THROW_EXCEPTION_USAGE("getResponsibleServer()"); } auto result = ClusterInfo::instance()->getResponsibleServer( TRI_ObjectToString(isolate, args[0])); v8::Handle list = v8::Array::New(isolate, (int)result->size()); uint32_t count = 0; for (auto const& s : *result) { list->Set(count++, TRI_V8_STD_STRING(isolate, s)); } TRI_V8_RETURN(list); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief get the responsible shard //////////////////////////////////////////////////////////////////////////////// static void JS_GetResponsibleShardClusterInfo(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInCluster(); if (args.Length() < 2 || args.Length() > 3) { TRI_V8_THROW_EXCEPTION_USAGE( "getResponsibleShard(, , " ")"); } if (!args[0]->IsString() && !args[0]->IsStringObject()) { TRI_V8_THROW_TYPE_ERROR("expecting a string for )"); } if (!args[1]->IsObject()) { TRI_V8_THROW_TYPE_ERROR("expecting an object for )"); } bool documentIsComplete = true; if (args.Length() > 2) { documentIsComplete = TRI_ObjectToBoolean(isolate, args[2]); } VPackBuilder builder; int res = TRI_V8ToVPack(isolate, builder, args[1], false); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_EXCEPTION(res); } ShardID shardId; CollectionID collectionId = TRI_ObjectToString(isolate, args[0]); auto& vocbase = GetContextVocBase(isolate); auto ci = ClusterInfo::instance(); auto collInfo = ci->getCollectionNT(vocbase.name(), collectionId); if (collInfo == nullptr) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND, ClusterInfo::getCollectionNotFoundMsg(vocbase.name(), collectionId)); } bool usesDefaultShardingAttributes; res = collInfo->getResponsibleShard(builder.slice(), documentIsComplete, shardId, usesDefaultShardingAttributes); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_EXCEPTION(res); } v8::Handle result = v8::Object::New(isolate); result->Set(TRI_V8_ASCII_STRING(isolate, "shardId"), TRI_V8_STD_STRING(isolate, shardId)); result->Set(TRI_V8_ASCII_STRING(isolate, "usesDefaultShardingAttributes"), v8::Boolean::New(isolate, usesDefaultShardingAttributes)); TRI_V8_RETURN(result); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief get the server endpoint for a server //////////////////////////////////////////////////////////////////////////////// static void JS_GetServerEndpointClusterInfo(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInCluster(); if (args.Length() != 1) { TRI_V8_THROW_EXCEPTION_USAGE("getServerEndpoint()"); } std::string const result = ClusterInfo::instance()->getServerEndpoint(TRI_ObjectToString(isolate, args[0])); TRI_V8_RETURN_STD_STRING(result); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief get the server name for an endpoint //////////////////////////////////////////////////////////////////////////////// static void JS_GetServerNameClusterInfo(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInCluster(); if (args.Length() != 1) { TRI_V8_THROW_EXCEPTION_USAGE("getServerName()"); } std::string const result = ClusterInfo::instance()->getServerName(TRI_ObjectToString(isolate, args[0])); TRI_V8_RETURN_STD_STRING(result); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the DBServers currently registered //////////////////////////////////////////////////////////////////////////////// static void JS_GetDBServers(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInCluster(); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("getDBServers()"); } auto DBServers = ClusterInfo::instance()->getCurrentDBServers(); auto serverAliases = ClusterInfo::instance()->getServerAliases(); v8::Handle l = v8::Array::New(isolate); for (size_t i = 0; i < DBServers.size(); ++i) { v8::Handle result = v8::Object::New(isolate); auto id = DBServers[i]; result->Set(TRI_V8_ASCII_STRING(isolate, "serverId"), TRI_V8_STD_STRING(isolate, id)); auto itr = serverAliases.find(id); if (itr != serverAliases.end()) { result->Set(TRI_V8_ASCII_STRING(isolate, "serverName"), TRI_V8_STD_STRING(isolate, itr->second)); } else { result->Set(TRI_V8_ASCII_STRING(isolate, "serverName"), TRI_V8_STD_STRING(isolate, id)); } l->Set((uint32_t)i, result); } TRI_V8_RETURN(l); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the coordinators currently registered //////////////////////////////////////////////////////////////////////////////// static void JS_GetCoordinators(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInCluster(); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("getCoordinators()"); } std::vector coordinators = ClusterInfo::instance()->getCurrentCoordinators(); v8::Handle l = v8::Array::New(isolate); for (size_t i = 0; i < coordinators.size(); ++i) { ServerID const sid = coordinators[i]; l->Set((uint32_t)i, TRI_V8_STD_STRING(isolate, sid)); } TRI_V8_RETURN(l); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief returns a unique id //////////////////////////////////////////////////////////////////////////////// static void JS_UniqidClusterInfo(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() > 1) { TRI_V8_THROW_EXCEPTION_USAGE("uniqid()"); } uint64_t count = 1; if (args.Length() > 0) { count = TRI_ObjectToUInt64(isolate, args[0], true); } if (count == 0) { TRI_V8_THROW_EXCEPTION_PARAMETER(" is invalid"); } uint64_t value = ClusterInfo::instance()->uniqid(count); if (value == 0) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "unable to generate unique id"); } std::string const id = StringUtils::itoa(value); TRI_V8_RETURN_STD_STRING(id); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief return the servers address //////////////////////////////////////////////////////////////////////////////// static void JS_AddressServerState(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("address()"); } std::string const address = ServerState::instance()->getEndpoint(); TRI_V8_RETURN_STD_STRING(address); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief return the servers id //////////////////////////////////////////////////////////////////////////////// static void JS_IdServerState(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInClusterOrActiveFailover(); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("id()"); } std::string const id = ServerState::instance()->getId(); TRI_V8_RETURN_STD_STRING(id); TRI_V8_TRY_CATCH_END } static void JS_isFoxxmaster(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("isFoxxmaster()"); } if (ServerState::instance()->isFoxxmaster()) { TRI_V8_RETURN_TRUE(); } else { TRI_V8_RETURN_FALSE(); } TRI_V8_TRY_CATCH_END } static void JS_getFoxxmasterQueueupdate(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("getFoxxmasterQueueupdate()"); } if (ServerState::instance()->getFoxxmasterQueueupdate()) { TRI_V8_RETURN_TRUE(); } else { TRI_V8_RETURN_FALSE(); } TRI_V8_TRY_CATCH_END } static void JS_setFoxxmasterQueueupdate(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() != 1) { TRI_V8_THROW_EXCEPTION_USAGE("setFoxxmasterQueueupdate(bool)"); } bool queueUpdate = TRI_ObjectToBoolean(isolate, args[0]); ServerState::instance()->setFoxxmasterQueueupdate(queueUpdate); if (AgencyCommManager::isEnabled()) { AgencyComm comm; std::string key = "Current/FoxxmasterQueueupdate"; VPackSlice val = queueUpdate ? VPackSlice::trueSlice() : VPackSlice::falseSlice(); AgencyCommResult result = comm.setValue(key, val, 0.0); if (result.successful()) { result = comm.increment("Current/Version"); } if (!result.successful() && result.errorCode() != TRI_ERROR_SHUTTING_DOWN && !application_features::ApplicationServer::isStopping()) { // gracefully ignore any shutdown errors here THROW_AGENCY_EXCEPTION(result); } } TRI_V8_TRY_CATCH_END } static void JS_GetFoxxmasterSince(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("getFoxxmasterSince()"); } // Ticks can be up to 56 bits, but JS numbers can represent ints up to // 53 bits only. std::string const foxxmasterSince = std::to_string(ServerState::instance()->getFoxxmasterSince()); TRI_V8_RETURN_STD_STRING(foxxmasterSince); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief return whether the cluster is initialized //////////////////////////////////////////////////////////////////////////////// static void JS_InitializedServerState(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("initialized()"); } if (ServerState::instance()->initialized()) { TRI_V8_RETURN_TRUE(); } TRI_V8_RETURN_FALSE(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief whether or not the server is a coordinator //////////////////////////////////////////////////////////////////////////////// static void JS_IsCoordinatorServerState(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("isCoordinator()"); } if (ServerState::instance()->getRole() == ServerState::ROLE_COORDINATOR) { TRI_V8_RETURN_TRUE(); } TRI_V8_RETURN_FALSE(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the server role //////////////////////////////////////////////////////////////////////////////// static void JS_RoleServerState(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("role()"); } std::string const role = ServerState::roleToString(ServerState::instance()->getRole()); TRI_V8_RETURN_STD_STRING(role); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief sets the server role (used for testing) //////////////////////////////////////////////////////////////////////////////// static void JS_SetRoleServerState(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() != 1) { TRI_V8_THROW_EXCEPTION_USAGE("setRole()"); } std::string const role = TRI_ObjectToString(isolate, args[0]); ServerState::RoleEnum r = ServerState::stringToRole(role); if (r == ServerState::ROLE_UNDEFINED) { TRI_V8_THROW_EXCEPTION_PARAMETER(" is invalid"); } ServerState::instance()->setRole(r); TRI_V8_RETURN_TRUE(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the server state //////////////////////////////////////////////////////////////////////////////// static void JS_StatusServerState(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("status()"); } std::string const state = ServerState::stateToString(ServerState::instance()->getState()); TRI_V8_RETURN_STD_STRING(state); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief prepare to send a request /// /// this is used for asynchronous as well as synchronous requests. //////////////////////////////////////////////////////////////////////////////// static void PrepareClusterCommRequest(v8::FunctionCallbackInfo const& args, arangodb::rest::RequestType& reqType, std::string& destination, std::string& path, std::string& body, std::unordered_map& headerFields, CoordTransactionID& coordTransactionID, double& timeout, bool& singleRequest, double& initTimeout) { v8::Isolate* isolate = args.GetIsolate(); v8::Local context = isolate->GetCurrentContext(); TRI_V8_CURRENT_GLOBALS_AND_SCOPE; onlyInClusterOrActiveFailover(); TRI_ASSERT(args.Length() >= 4); reqType = arangodb::rest::RequestType::GET; if (args[0]->IsString()) { TRI_Utf8ValueNFC UTF8(isolate, args[0]); std::string methstring = *UTF8; reqType = arangodb::HttpRequest::translateMethod(methstring); if (reqType == arangodb::rest::RequestType::ILLEGAL) { reqType = arangodb::rest::RequestType::GET; } } destination = TRI_ObjectToString(isolate, args[1]); std::string dbname = TRI_ObjectToString(isolate, args[2]); path = TRI_ObjectToString(isolate, args[3]); path = "/_db/" + dbname + path; body.clear(); if (!args[4]->IsUndefined()) { if (args[4]->IsObject() && V8Buffer::hasInstance(isolate, args[4])) { // supplied body is a Buffer object char const* data = V8Buffer::data(isolate, args[4].As()); size_t size = V8Buffer::length(isolate, args[4].As()); if (data == nullptr) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_BAD_PARAMETER, "invalid buffer value"); } body.assign(data, size); } else { body = TRI_ObjectToString(isolate, args[4]); } } if (args.Length() > 5 && args[5]->IsObject()) { v8::Handle obj = args[5].As(); v8::Handle props = obj->GetOwnPropertyNames(); uint32_t i; for (i = 0; i < props->Length(); ++i) { v8::Handle prop = props->Get(i); v8::Handle val = obj->Get(prop); std::string propstring = TRI_ObjectToString(isolate, prop); std::string valstring = TRI_ObjectToString(isolate, val); if (propstring != "") { headerFields.insert(std::make_pair(propstring, valstring)); } } } coordTransactionID = 0; timeout = 24 * 3600.0; singleRequest = false; if (args.Length() > 6 && args[6]->IsObject()) { v8::Handle opt = args[6].As(); TRI_GET_GLOBAL_STRING(CoordTransactionIDKey); if (TRI_HasProperty(context, isolate, opt, CoordTransactionIDKey)) { coordTransactionID = TRI_ObjectToUInt64(isolate, opt->Get(CoordTransactionIDKey), true); } TRI_GET_GLOBAL_STRING(TimeoutKey); if (TRI_HasProperty(context, isolate, opt, TimeoutKey)) { timeout = TRI_ObjectToDouble(isolate, opt->Get(TimeoutKey)); } TRI_GET_GLOBAL_STRING(SingleRequestKey); if (TRI_HasProperty(context, isolate, opt, SingleRequestKey)) { singleRequest = TRI_ObjectToBoolean(isolate, opt->Get(SingleRequestKey)); } TRI_GET_GLOBAL_STRING(InitTimeoutKey); if (TRI_HasProperty(context, isolate, opt, InitTimeoutKey)) { initTimeout = TRI_ObjectToDouble(isolate, opt->Get(InitTimeoutKey)); } } if (coordTransactionID == 0) { coordTransactionID = TRI_NewTickServer(); } if (timeout == 0) { timeout = 24 * 3600.0; } } //////////////////////////////////////////////////////////////////////////////// /// @brief prepare a ClusterCommResult for JavaScript //////////////////////////////////////////////////////////////////////////////// static void Return_PrepareClusterCommResultForJS(v8::FunctionCallbackInfo const& args, ClusterCommResult const& res) { v8::Isolate* isolate = args.GetIsolate(); TRI_V8_CURRENT_GLOBALS_AND_SCOPE; v8::Handle r = v8::Object::New(isolate); if (res.dropped) { TRI_GET_GLOBAL_STRING(ErrorMessageKey); r->Set(ErrorMessageKey, TRI_V8_ASCII_STRING(isolate, "operation was dropped")); } else { // convert the ids to strings as uint64_t might be too big for JavaScript // numbers TRI_GET_GLOBAL_STRING(CoordTransactionIDKey); std::string id = StringUtils::itoa(res.coordTransactionID); r->Set(CoordTransactionIDKey, TRI_V8_STD_STRING(isolate, id)); id = StringUtils::itoa(res.operationID); TRI_GET_GLOBAL_STRING(OperationIDKey); r->Set(OperationIDKey, TRI_V8_STD_STRING(isolate, id)); TRI_GET_GLOBAL_STRING(EndpointKey); r->Set(EndpointKey, TRI_V8_STD_STRING(isolate, res.endpoint)); TRI_GET_GLOBAL_STRING(SingleRequestKey); r->Set(SingleRequestKey, v8::Boolean::New(isolate, res.single)); TRI_GET_GLOBAL_STRING(ShardIDKey); r->Set(ShardIDKey, TRI_V8_STD_STRING(isolate, res.shardID)); if (res.status == CL_COMM_SUBMITTED) { TRI_GET_GLOBAL_STRING(StatusKey); r->Set(StatusKey, TRI_V8_ASCII_STRING(isolate, "SUBMITTED")); } else if (res.status == CL_COMM_SENDING) { TRI_GET_GLOBAL_STRING(StatusKey); r->Set(StatusKey, TRI_V8_ASCII_STRING(isolate, "SENDING")); } else if (res.status == CL_COMM_SENT) { TRI_GET_GLOBAL_STRING(StatusKey); r->Set(StatusKey, TRI_V8_ASCII_STRING(isolate, "SENT")); // This might be the result of a synchronous request or an asynchronous // request with the `singleRequest` flag true and thus contain the // actual response. If it is an asynchronous request which has not // yet been answered, the following information is probably rather // boring: // The headers: TRI_ASSERT(res.result != nullptr); v8::Handle h = v8::Object::New(isolate); for (auto const& i : res.result->getHeaderFields()) { h->Set(TRI_V8_STD_STRING(isolate, i.first), TRI_V8_STD_STRING(isolate, i.second)); } r->Set(TRI_V8_ASCII_STRING(isolate, "headers"), h); // The body: arangodb::basics::StringBuffer& body = res.result->getBody(); if (body.length() != 0) { r->Set(TRI_V8_ASCII_STRING(isolate, "body"), TRI_V8_STD_STRING(isolate, body)); V8Buffer* buffer = V8Buffer::New(isolate, body.c_str(), body.length()); v8::Local bufferObject = v8::Local::New(isolate, buffer->_handle); r->Set(TRI_V8_ASCII_STRING(isolate, "rawBody"), bufferObject); } } else if (res.status == CL_COMM_TIMEOUT) { TRI_GET_GLOBAL_STRING(StatusKey); r->Set(StatusKey, TRI_V8_ASCII_STRING(isolate, "TIMEOUT")); TRI_GET_GLOBAL_STRING(TimeoutKey); r->Set(TimeoutKey, v8::BooleanObject::New(isolate, true)); } else if (res.status == CL_COMM_ERROR) { TRI_GET_GLOBAL_STRING(StatusKey); r->Set(StatusKey, TRI_V8_ASCII_STRING(isolate, "ERROR")); if (res.result && res.result->isComplete()) { v8::Handle details = v8::Object::New(isolate); details->Set(TRI_V8_ASCII_STRING(isolate, "code"), v8::Number::New(isolate, res.result->getHttpReturnCode())); details->Set(TRI_V8_ASCII_STRING(isolate, "message"), TRI_V8_STD_STRING(isolate, res.result->getHttpReturnMessage())); arangodb::basics::StringBuffer& body = res.result->getBody(); details->Set(TRI_V8_ASCII_STRING(isolate, "body"), TRI_V8_STD_STRING(isolate, body)); V8Buffer* buffer = V8Buffer::New(isolate, body.c_str(), body.length()); v8::Local bufferObject = v8::Local::New(isolate, buffer->_handle); details->Set(TRI_V8_ASCII_STRING(isolate, "rawBody"), bufferObject); r->Set(TRI_V8_ASCII_STRING(isolate, "details"), details); TRI_GET_GLOBAL_STRING(ErrorMessageKey); r->Set(ErrorMessageKey, TRI_V8_STD_STRING(isolate, res.errorMessage)); } else { TRI_GET_GLOBAL_STRING(ErrorMessageKey); r->Set(ErrorMessageKey, TRI_V8_STD_STRING(isolate, res.errorMessage)); } } else if (res.status == CL_COMM_DROPPED) { TRI_GET_GLOBAL_STRING(StatusKey); r->Set(StatusKey, TRI_V8_ASCII_STRING(isolate, "DROPPED")); TRI_GET_GLOBAL_STRING(ErrorMessageKey); r->Set(ErrorMessageKey, TRI_V8_ASCII_STRING(isolate, "request dropped whilst waiting for answer")); } else if (res.status == CL_COMM_BACKEND_UNAVAILABLE) { TRI_GET_GLOBAL_STRING(StatusKey); r->Set(StatusKey, TRI_V8_ASCII_STRING(isolate, "BACKEND_UNAVAILABLE")); TRI_GET_GLOBAL_STRING(ErrorMessageKey); r->Set(ErrorMessageKey, TRI_V8_ASCII_STRING(isolate, "required backend was not available")); } else if (res.status == CL_COMM_RECEIVED) { // Everything is OK // FIXME HANDLE VST auto httpRequest = std::dynamic_pointer_cast(res.answer); if (httpRequest == nullptr) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "invalid request type"); } // The headers: v8::Handle h = v8::Object::New(isolate); TRI_GET_GLOBAL_STRING(StatusKey); r->Set(StatusKey, TRI_V8_ASCII_STRING(isolate, "RECEIVED")); TRI_ASSERT(res.answer != nullptr); std::unordered_map headers = res.answer->headers(); headers["content-length"] = StringUtils::itoa(httpRequest->contentLength()); for (auto& it : headers) { h->Set(TRI_V8_STD_STRING(isolate, it.first), TRI_V8_STD_STRING(isolate, it.second)); } r->Set(TRI_V8_ASCII_STRING(isolate, "headers"), h); // The body: std::string const& body = httpRequest->body(); if (!body.empty()) { r->Set(TRI_V8_ASCII_STRING(isolate, "body"), TRI_V8_STD_STRING(isolate, body)); V8Buffer* buffer = V8Buffer::New(isolate, body.c_str(), body.length()); v8::Local bufferObject = v8::Local::New(isolate, buffer->_handle); r->Set(TRI_V8_ASCII_STRING(isolate, "rawBody"), bufferObject); } } else { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "unknown ClusterComm result status"); } } TRI_V8_RETURN(r); } //////////////////////////////////////////////////////////////////////////////// /// @brief send an asynchronous request //////////////////////////////////////////////////////////////////////////////// static void JS_AsyncRequest(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInClusterOrActiveFailover(); if (args.Length() < 4 || args.Length() > 7) { TRI_V8_THROW_EXCEPTION_USAGE( "asyncRequest(" "reqType, destination, dbname, path, body, headers, options)"); } // Possible options: // - coordTransactionID (number) // - timeout (number) // - singleRequest (boolean) default is false // - initTimeout (number) auto cc = ClusterComm::instance(); if (cc == nullptr) { TRI_V8_THROW_EXCEPTION_MESSAGE( TRI_ERROR_SHUTTING_DOWN, "clustercomm object not found (JS_AsyncRequest)"); } arangodb::rest::RequestType reqType; std::string destination; std::string path; auto body = std::make_shared(); std::unordered_map headerFields; CoordTransactionID coordTransactionID; double timeout; double initTimeout = -1.0; bool singleRequest = false; PrepareClusterCommRequest(args, reqType, destination, path, *body, headerFields, coordTransactionID, timeout, singleRequest, initTimeout); OperationID opId = cc->asyncRequest(coordTransactionID, destination, reqType, path, body, headerFields, nullptr, timeout, singleRequest, initTimeout); ClusterCommResult res = cc->enquire(opId); if (res.status == CL_COMM_BACKEND_UNAVAILABLE) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "couldn't queue async request"); } LOG_TOPIC("cea85", DEBUG, Logger::CLUSTER) << "JS_AsyncRequest: request has been submitted"; Return_PrepareClusterCommResultForJS(args, res); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief send a synchronous request //////////////////////////////////////////////////////////////////////////////// static void JS_SyncRequest(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInCluster(); if (args.Length() < 4 || args.Length() > 7) { TRI_V8_THROW_EXCEPTION_USAGE( "syncRequest(" "reqType, destination, dbname, path, body, headers, options)"); } // Possible options: // - coordTransactionID (number) // - timeout (number) // Disabled to allow communication originating in a DBserver: // 31.7.2014 Max // if (ServerState::instance()->getRole() != ServerState::ROLE_COORDINATOR) { // TRI_V8_THROW_EXCEPTION_INTERNAL(scope,"request works only in coordinator // role"); //} auto cc = ClusterComm::instance(); if (cc == nullptr) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_SHUTTING_DOWN, "clustercomm object not found"); } arangodb::rest::RequestType reqType; std::string destination; std::string path; std::string body; auto headerFields = std::make_unique>(); CoordTransactionID coordTransactionID; double timeout; double initTimeout = -1.0; bool singleRequest = false; // of no relevance here PrepareClusterCommRequest(args, reqType, destination, path, body, *headerFields, coordTransactionID, timeout, singleRequest, initTimeout); std::unique_ptr res = cc->syncRequest(coordTransactionID, destination, reqType, path, body, *headerFields, timeout); if (res.get() == nullptr) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "couldn't do sync request"); } LOG_TOPIC("0f902", DEBUG, Logger::CLUSTER) << "JS_SyncRequest: request has been done"; Return_PrepareClusterCommResultForJS(args, *res); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief enquire information about an asynchronous request //////////////////////////////////////////////////////////////////////////////// static void JS_Enquire(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); onlyInCluster(); if (args.Length() != 1) { TRI_V8_THROW_EXCEPTION_USAGE("enquire(operationID)"); } auto cc = ClusterComm::instance(); if (cc == nullptr) { TRI_V8_THROW_EXCEPTION_MESSAGE( TRI_ERROR_INTERNAL, "clustercomm object not found (JS_SyncRequest)"); } OperationID operationID = TRI_ObjectToUInt64(isolate, args[0], true); LOG_TOPIC("77dbd", DEBUG, Logger::CLUSTER) << "JS_Enquire: calling ClusterComm::enquire()"; ClusterCommResult const res = cc->enquire(operationID); Return_PrepareClusterCommResultForJS(args, res); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief wait for the result of an asynchronous request //////////////////////////////////////////////////////////////////////////////// static void JS_Wait(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::Local context = isolate->GetCurrentContext(); TRI_V8_CURRENT_GLOBALS_AND_SCOPE; onlyInClusterOrActiveFailover(); if (args.Length() != 1) { TRI_V8_THROW_EXCEPTION_USAGE("wait(obj)"); } // Possible options: // - clientTransactionID (string) // - coordTransactionID (number) // - operationID (number) // - shardID (string) // - timeout (number) auto cc = ClusterComm::instance(); if (cc == nullptr) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_SHUTTING_DOWN, "clustercomm object not found (JS_Wait)"); } CoordTransactionID mycoordTransactionID = 0; OperationID myoperationID = 0; ShardID myshardID = ""; double mytimeout = 24 * 3600.0; if (args[0]->IsObject()) { v8::Handle obj = args[0].As(); TRI_GET_GLOBAL_STRING(CoordTransactionIDKey); if (TRI_HasProperty(context, isolate, obj, CoordTransactionIDKey)) { mycoordTransactionID = TRI_ObjectToUInt64(isolate, obj->Get(CoordTransactionIDKey), true); } TRI_GET_GLOBAL_STRING(OperationIDKey); if (TRI_HasProperty(context, isolate, obj, OperationIDKey)) { myoperationID = TRI_ObjectToUInt64(isolate, obj->Get(OperationIDKey), true); } TRI_GET_GLOBAL_STRING(ShardIDKey); if (TRI_HasProperty(context, isolate, obj, ShardIDKey)) { myshardID = TRI_ObjectToString(isolate, obj->Get(ShardIDKey)); } TRI_GET_GLOBAL_STRING(TimeoutKey); if (TRI_HasProperty(context, isolate, obj, TimeoutKey)) { mytimeout = TRI_ObjectToDouble(isolate, obj->Get(TimeoutKey)); if (mytimeout == 0.0) { mytimeout = 24 * 3600.0; } } } LOG_TOPIC("04f61", DEBUG, Logger::CLUSTER) << "JS_Wait: calling ClusterComm::wait()"; ClusterCommResult const res = cc->wait(mycoordTransactionID, myoperationID, myshardID, mytimeout); Return_PrepareClusterCommResultForJS(args, res); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief drop the result of an asynchronous request //////////////////////////////////////////////////////////////////////////////// static void JS_Drop(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::Local context = isolate->GetCurrentContext(); TRI_V8_CURRENT_GLOBALS_AND_SCOPE; onlyInCluster(); if (args.Length() != 1) { TRI_V8_THROW_EXCEPTION_USAGE("drop(obj)"); } // Possible options: // - coordTransactionID (number) // - operationID (number) // - shardID (string) auto cc = ClusterComm::instance(); if (cc == nullptr) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "clustercomm object not found (JS_Drop)"); } CoordTransactionID mycoordTransactionID = 0; OperationID myoperationID = 0; ShardID myshardID = ""; if (args[0]->IsObject()) { v8::Handle obj = args[0].As(); TRI_GET_GLOBAL_STRING(CoordTransactionIDKey); if (TRI_HasProperty(context, isolate, obj, CoordTransactionIDKey)) { mycoordTransactionID = TRI_ObjectToUInt64(isolate, obj->Get(CoordTransactionIDKey), true); } TRI_GET_GLOBAL_STRING(OperationIDKey); if (TRI_HasProperty(context, isolate, obj, OperationIDKey)) { myoperationID = TRI_ObjectToUInt64(isolate, obj->Get(OperationIDKey), true); } TRI_GET_GLOBAL_STRING(ShardIDKey); if (TRI_HasProperty(context, isolate, obj, ShardIDKey)) { myshardID = TRI_ObjectToString(isolate, obj->Get(ShardIDKey)); } } LOG_TOPIC("f2376", DEBUG, Logger::CLUSTER) << "JS_Drop: calling ClusterComm::drop()"; cc->drop(mycoordTransactionID, myoperationID, myshardID); TRI_V8_RETURN_UNDEFINED(); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief get an ID for use with coordTransactionId //////////////////////////////////////////////////////////////////////////////// static void JS_GetId(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("getId()"); } auto id = TRI_NewTickServer(); std::string st = StringUtils::itoa(id); v8::Handle s = TRI_V8_ASCII_STRING(isolate, st.c_str()); TRI_V8_RETURN(s); TRI_V8_TRY_CATCH_END } static void JS_ClusterDownload(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::Local context = isolate->GetCurrentContext(); AuthenticationFeature* af = AuthenticationFeature::instance(); if (af != nullptr && af->isActive()) { // mop: really quick and dirty v8::Handle options = v8::Object::New(isolate); v8::Handle headers = v8::Object::New(isolate); if (args.Length() > 2) { if (args[2]->IsObject()) { options = v8::Handle::Cast(args[2]); if (TRI_HasProperty(context, isolate, options, "headers")) { headers = v8::Handle::Cast( options->Get(TRI_V8_ASCII_STRING(isolate, "headers"))); } } } options->Set(TRI_V8_ASCII_STRING(isolate, "headers"), headers); std::string authorization = "bearer " + af->tokenCache().jwtToken(); v8::Handle v8Authorization = TRI_V8_STD_STRING(isolate, authorization); headers->Set(TRI_V8_ASCII_STRING(isolate, "Authorization"), v8Authorization); args[2] = options; } TRI_V8_TRY_CATCH_END return JS_Download(args); } //////////////////////////////////////////////////////////////////////////////// /// @brief Collect the distribution of shards //////////////////////////////////////////////////////////////////////////////// static void JS_GetShardDistribution(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); onlyInCluster(); v8::HandleScope scope(isolate); auto& vocbase = GetContextVocBase(isolate); auto reporter = cluster::ShardDistributionReporter::instance(); VPackBuilder result; reporter->getDistributionForDatabase(vocbase.name(), result); TRI_V8_RETURN(TRI_VPackToV8(isolate, result.slice())); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief Collect the distribution of shards of a specific collection //////////////////////////////////////////////////////////////////////////////// static void JS_GetCollectionShardDistribution(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); onlyInCluster(); if (args.Length() != 1) { TRI_V8_THROW_EXCEPTION_USAGE( "GetCollectionShardDistribution()"); } std::string const colName = TRI_ObjectToString(isolate, args[0]); v8::HandleScope scope(isolate); auto& vocbase = GetContextVocBase(isolate); auto reporter = cluster::ShardDistributionReporter::instance(); VPackBuilder result; reporter->getCollectionDistributionForDatabase(vocbase.name(), colName, result); TRI_V8_RETURN(TRI_VPackToV8(isolate, result.slice())); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief creates a global cluster context //////////////////////////////////////////////////////////////////////////////// void TRI_InitV8Cluster(v8::Isolate* isolate, v8::Handle context) { TRI_V8_CURRENT_GLOBALS_AND_SCOPE; TRI_ASSERT(v8g != nullptr); v8::Handle rt; v8::Handle ft; // ........................................................................... // generate the agency template // ........................................................................... ft = v8::FunctionTemplate::New(isolate); ft->SetClassName(TRI_V8_ASCII_STRING(isolate, "ArangoAgency")); rt = ft->InstanceTemplate(); rt->SetInternalFieldCount(2); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "agency"), JS_Agency); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "read"), JS_ReadAgency); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "write"), JS_WriteAgency); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "transact"), JS_TransactAgency); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "transient"), JS_TransientAgency); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "cas"), JS_CasAgency); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "createDirectory"), JS_CreateDirectoryAgency); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "get"), JS_GetAgency); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "isEnabled"), JS_IsEnabledAgency); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "increaseVersion"), JS_IncreaseVersionAgency); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "remove"), JS_RemoveAgency); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "set"), JS_SetAgency); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "endpoints"), JS_EndpointsAgency); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "prefix"), JS_PrefixAgency); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "uniqid"), JS_UniqidAgency); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "version"), JS_VersionAgency); v8g->AgencyTempl.Reset(isolate, rt); ft->SetClassName(TRI_V8_ASCII_STRING(isolate, "ArangoAgencyCtor")); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "ArangoAgencyCtor"), ft->GetFunction(), true); // register the global object v8::Handle aa = rt->NewInstance(); if (!aa.IsEmpty()) { TRI_AddGlobalVariableVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "ArangoAgency"), aa); } // ........................................................................... // generate the cluster info template // ........................................................................... ft = v8::FunctionTemplate::New(isolate); ft->SetClassName(TRI_V8_ASCII_STRING(isolate, "ArangoClusterInfo")); rt = ft->InstanceTemplate(); rt->SetInternalFieldCount(2); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "doesDatabaseExist"), JS_DoesDatabaseExistClusterInfo); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "databases"), JS_Databases); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "flush"), JS_FlushClusterInfo, true); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "getCollectionInfo"), JS_GetCollectionInfoClusterInfo); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "getCollectionInfoCurrent"), JS_GetCollectionInfoCurrentClusterInfo); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "getResponsibleServer"), JS_GetResponsibleServerClusterInfo); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "getResponsibleShard"), JS_GetResponsibleShardClusterInfo); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "getServerEndpoint"), JS_GetServerEndpointClusterInfo); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "getServerName"), JS_GetServerNameClusterInfo); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "getDBServers"), JS_GetDBServers); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "getCoordinators"), JS_GetCoordinators); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "uniqid"), JS_UniqidClusterInfo); v8g->ClusterInfoTempl.Reset(isolate, rt); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "ArangoClusterInfoCtor"), ft->GetFunction(), true); // register the global object v8::Handle ci = rt->NewInstance(); if (!ci.IsEmpty()) { TRI_AddGlobalVariableVocbase( isolate, TRI_V8_ASCII_STRING(isolate, "ArangoClusterInfo"), ci); } // ............................................................................. // generate the server state template // ........................................................................... ft = v8::FunctionTemplate::New(isolate); ft->SetClassName(TRI_V8_ASCII_STRING(isolate, "ArangoServerState")); rt = ft->InstanceTemplate(); rt->SetInternalFieldCount(2); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "address"), JS_AddressServerState); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "id"), JS_IdServerState); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "isFoxxmaster"), JS_isFoxxmaster); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "getFoxxmasterQueueupdate"), JS_getFoxxmasterQueueupdate); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "setFoxxmasterQueueupdate"), JS_setFoxxmasterQueueupdate); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "getFoxxmasterSince"), JS_GetFoxxmasterSince); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "initialized"), JS_InitializedServerState); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "isCoordinator"), JS_IsCoordinatorServerState); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "role"), JS_RoleServerState); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "setRole"), JS_SetRoleServerState, true); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "status"), JS_StatusServerState); v8g->ServerStateTempl.Reset(isolate, rt); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "ArangoServerStateCtor"), ft->GetFunction(), true); // register the global object v8::Handle ss = rt->NewInstance(); if (!ss.IsEmpty()) { TRI_AddGlobalVariableVocbase( isolate, TRI_V8_ASCII_STRING(isolate, "ArangoServerState"), ss); } // ........................................................................... // generate the cluster comm template // ........................................................................... ft = v8::FunctionTemplate::New(isolate); ft->SetClassName(TRI_V8_ASCII_STRING(isolate, "ArangoClusterComm")); rt = ft->InstanceTemplate(); rt->SetInternalFieldCount(2); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "asyncRequest"), JS_AsyncRequest); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "syncRequest"), JS_SyncRequest); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "enquire"), JS_Enquire); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "wait"), JS_Wait); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "drop"), JS_Drop); TRI_AddMethodVocbase(isolate, rt, TRI_V8_ASCII_STRING(isolate, "getId"), JS_GetId); v8g->ClusterCommTempl.Reset(isolate, rt); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "ArangoClusterCommCtor"), ft->GetFunction(), true); // register the global object ss = rt->NewInstance(); if (!ss.IsEmpty()) { TRI_AddGlobalVariableVocbase( isolate, TRI_V8_ASCII_STRING(isolate, "ArangoClusterComm"), ss); } TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "SYS_CLUSTER_DOWNLOAD"), JS_ClusterDownload); TRI_AddGlobalFunctionVocbase( isolate, TRI_V8_ASCII_STRING(isolate, "SYS_CLUSTER_SHARD_DISTRIBUTION"), JS_GetShardDistribution); TRI_AddGlobalFunctionVocbase( isolate, TRI_V8_ASCII_STRING(isolate, "SYS_CLUSTER_COLLECTION_SHARD_DISTRIBUTION"), JS_GetCollectionShardDistribution); }