1
0
Fork 0

Feature/optional replication factor enforcement (#3570)

This commit is contained in:
m0ppers 2017-11-02 21:41:25 +01:00 committed by Frank Celler
parent 4e68e2b3bc
commit b552853909
15 changed files with 856 additions and 23 deletions

View File

@ -1,6 +1,10 @@
devel
-----
* introduce `enforceReplicationFactor`: An optional parameter controlling
if the server should bail out during collection creation if there are not
enough DBServers available for the desired `replicationFactor`.
* fixed issue #3516: Show execution time in arangosh
this change adds more dynamic prompt components for arangosh

View File

@ -115,6 +115,11 @@ Default is *1* which means the server will only report success back to the clien
if all replicas have created the collection. Set to *0* if you want faster
server responses and don't care about full replication.
@RESTQUERYPARAM{enforceReplicationFactor,integer,optional,int64}
Default is *1* which means the server will check if there are enough replicas
available at creation time and bail out otherwise. Set to *0* to disable this
extra check.
@RESTDESCRIPTION
Creates a new collection with a given name. The request must contain an
object with the following attributes.

View File

@ -742,12 +742,14 @@ def unstashBuild(os, edition, maintainer) {
try {
if (os == "windows") {
def name = "build.zip"
powershell "echo 'y' | pscp -i C:\\Users\\Jenkins\\.ssh\\putty-jenkins.ppk jenkins@c1:/vol/cache/build-${branchLabel}-${os}-${edition}-${maintainer}.zip ${name}"
powershell "Expand-Archive -Path ${name} -Force -DestinationPath ."
bat "del /F /Q ${name}"
}
else {
def name = "build.tar.gz"
sh "scp c1:/vol/cache/build-${branchLabel}-${os}-${edition}-${maintainer}.tar.gz ${name}"
sh "tar xpzf ${name}"
sh "rm -f ${name}"

View File

@ -2444,13 +2444,15 @@ std::unordered_map<std::string, std::vector<std::string>> distributeShards(
#ifndef USE_ENTERPRISE
std::unique_ptr<LogicalCollection> ClusterMethods::createCollectionOnCoordinator(
TRI_col_type_e collectionType, TRI_vocbase_t* vocbase, VPackSlice parameters,
bool ignoreDistributeShardsLikeErrors, bool waitForSyncReplication) {
bool ignoreDistributeShardsLikeErrors, bool waitForSyncReplication,
bool enforceReplicationFactor) {
auto col = std::make_unique<LogicalCollection>(vocbase, parameters);
// Collection is a temporary collection object that undergoes sanity checks etc.
// It is not used anywhere and will be cleaned up after this call.
// Persist collection will return the real object.
return persistCollectionInAgency(
col.get(), ignoreDistributeShardsLikeErrors, waitForSyncReplication, parameters);
col.get(), ignoreDistributeShardsLikeErrors, waitForSyncReplication,
enforceReplicationFactor, parameters);
}
#endif
@ -2460,7 +2462,8 @@ std::unique_ptr<LogicalCollection> ClusterMethods::createCollectionOnCoordinator
std::unique_ptr<LogicalCollection> ClusterMethods::persistCollectionInAgency(
LogicalCollection* col, bool ignoreDistributeShardsLikeErrors,
bool waitForSyncReplication, VPackSlice parameters) {
bool waitForSyncReplication, bool enforceReplicationFactor,
VPackSlice parameters) {
std::string distributeShardsLike = col->distributeShardsLike();
std::vector<std::string> avoid = col->avoidServers();
@ -2581,12 +2584,15 @@ std::unique_ptr<LogicalCollection> ClusterMethods::persistCollectionInAgency(
dbServers = ci->getCurrentDBServers();
}
// cluster system replicationFactor is 1...allow startup with 1 DBServer.
// an addFollower job will be spawned right away and ensure proper
// resilience as soon as another DBServer is available :S
// system collections should never enforce replicationfactor
// to allow them to come up with 1 dbserver
if (enforceReplicationFactor && col->isSystem()) {
enforceReplicationFactor = false;
}
// the default behaviour however is to bail out and inform the user
// that the requested replicationFactor is not possible right now
if (dbServers.size() < replicationFactor && !col->isSystem()) {
if (enforceReplicationFactor && dbServers.size() < replicationFactor) {
THROW_ARANGO_EXCEPTION(TRI_ERROR_CLUSTER_INSUFFICIENT_DBSERVERS);
}

View File

@ -269,7 +269,9 @@ class ClusterMethods {
TRI_col_type_e collectionType, TRI_vocbase_t* vocbase,
arangodb::velocypack::Slice parameters,
bool ignoreDistributeShardsLikeErrors,
bool waitForSyncReplication);
bool waitForSyncReplication,
bool enforceReplicationFactor
);
private:
@ -279,7 +281,8 @@ class ClusterMethods {
static std::unique_ptr<LogicalCollection> persistCollectionInAgency(
LogicalCollection* col, bool ignoreDistributeShardsLikeErrors,
bool waitForSyncReplication, arangodb::velocypack::Slice parameters);
bool waitForSyncReplication, bool enforceReplicationFactor,
arangodb::velocypack::Slice parameters);
};
} // namespace arangodb

View File

@ -224,8 +224,11 @@ void RestCollectionHandler::handleCommandPost() {
auto cluster =
application_features::ApplicationServer::getFeature<ClusterFeature>(
"Cluster");
bool waitsForSync = cluster->createWaitsForSyncReplication();
waitsForSync = VelocyPackHelper::getBooleanValue(body, "body", waitsForSync);
bool waitForSyncReplication = _request->parsedValue("waitForSyncReplication",
cluster->createWaitsForSyncReplication());
bool enforceReplicationFactor = _request->parsedValue("enforceReplicationFactor",
true);
TRI_col_type_e type = TRI_col_type_e::TRI_COL_TYPE_DOCUMENT;
VPackSlice typeSlice = body.get("type");
@ -250,7 +253,7 @@ void RestCollectionHandler::handleCommandPost() {
std::string const& name = nameSlice.copyString();
VPackBuilder builder;
Result res = methods::Collections::create(
_vocbase, name, type, parameters, waitsForSync,
_vocbase, name, type, parameters, waitForSyncReplication, enforceReplicationFactor,
[&](LogicalCollection* coll) {
collectionRepresentation(builder, coll->name(), /*showProperties*/ true,
/*showFigures*/ false, /*showCount*/ false,

View File

@ -1146,9 +1146,11 @@ int RestReplicationHandler::processRestoreCollectionCoordinator(
application_features::ApplicationServer::getFeature<ClusterFeature>(
"Cluster")
->createWaitsForSyncReplication();
// in the replication case enforcing the replication factor is absolutely
// not desired, so it is hardcoded to false
auto col = ClusterMethods::createCollectionOnCoordinator(
collectionType, _vocbase, merged, ignoreDistributeShardsLikeErrors,
createWaitsForSyncReplication);
createWaitsForSyncReplication, false);
TRI_ASSERT(col != nullptr);
ExecContext const* exe = ExecContext::CURRENT;

View File

@ -248,19 +248,23 @@ static void CreateVocBase(v8::FunctionCallbackInfo<v8::Value> const& args,
// waitForSync can be 3. or 4. parameter
auto cluster = application_features::ApplicationServer::getFeature<ClusterFeature>("Cluster");
bool createWaitsForSyncReplication = cluster->createWaitsForSyncReplication();
bool enforceReplicationFactor = true;
if (args.Length() >= 3 && args[args.Length()-1]->IsObject()) {
v8::Handle<v8::Object> obj = args[args.Length()-1]->ToObject();
auto v8WaitForSyncReplication = obj->Get(TRI_V8_ASCII_STRING(isolate, "waitForSyncReplication"));
if (!v8WaitForSyncReplication->IsUndefined()) {
createWaitsForSyncReplication = TRI_ObjectToBoolean(v8WaitForSyncReplication);
}
createWaitsForSyncReplication = TRI_GetOptionalBooleanProperty(isolate,
obj, "waitForSyncReplication", createWaitsForSyncReplication);
enforceReplicationFactor = TRI_GetOptionalBooleanProperty(isolate,
obj, "enforceReplicationFactor", enforceReplicationFactor);
}
v8::Handle<v8::Value> result;
Result res = methods::Collections::create(vocbase, name, collectionType,
propSlice,
createWaitsForSyncReplication,
[&](LogicalCollection* collection) {
enforceReplicationFactor,
[&isolate, &result](LogicalCollection* collection) {
result = WrapCollection(isolate, collection);
});
if (res.fail()) {

View File

@ -125,6 +125,7 @@ Result Collections::create(TRI_vocbase_t* vocbase, std::string const& name,
TRI_col_type_e collectionType,
velocypack::Slice const& properties,
bool createWaitsForSyncReplication,
bool enforceReplicationFactor,
FuncCallback func) {
if (name.empty()) {
return TRI_ERROR_ARANGO_ILLEGAL_NAME;
@ -171,7 +172,7 @@ Result Collections::create(TRI_vocbase_t* vocbase, std::string const& name,
std::unique_ptr<LogicalCollection> col =
ClusterMethods::createCollectionOnCoordinator(
collectionType, vocbase, infoSlice, false,
createWaitsForSyncReplication);
createWaitsForSyncReplication, enforceReplicationFactor);
if (!col) {
return Result(TRI_ERROR_INTERNAL, "createCollectionOnCoordinator");
}

View File

@ -49,7 +49,8 @@ struct Collections {
static Result create(TRI_vocbase_t* vocbase, std::string const& name,
TRI_col_type_e collectionType,
velocypack::Slice const& properties,
bool createWaitsForSyncReplication, FuncCallback);
bool createWaitsForSyncReplication,
bool enforceReplicationFactor, FuncCallback);
static Result load(TRI_vocbase_t* vocbase, LogicalCollection* coll);
static Result unload(TRI_vocbase_t* vocbase, LogicalCollection* coll);

View File

@ -0,0 +1,746 @@
/* jshint strict: false */
/*global ArangoClusterInfo */
// //////////////////////////////////////////////////////////////////////////////
// / @brief querying and managing collections
// /
// / @file
// /
// / DISCLAIMER
// /
// / Copyright 2014 ArangoDB 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 Achim Brandt
// / @author Copyright 2014, ArangoDB GmbH, Cologne, Germany
// / @author Copyright 2012, triAGENS GmbH, Cologne, Germany
// //////////////////////////////////////////////////////////////////////////////
var arangodb = require('@arangodb');
var actions = require('@arangodb/actions');
var cluster = require('@arangodb/cluster');
var errors = require('internal').errors;
// //////////////////////////////////////////////////////////////////////////////
// / @brief return a prefixed URL
// //////////////////////////////////////////////////////////////////////////////
function databasePrefix (req, url) {
// location response (e.g. /_db/dbname/_api/collection/xyz)
return '/_db/' + encodeURIComponent(arangodb.db._name()) + url;
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief collection representation
// //////////////////////////////////////////////////////////////////////////////
function collectionRepresentation(collection, showProperties, showCount, showFigures) {
var result = {};
result.id = collection._id;
result.name = collection.name();
result.isSystem = (result.name.charAt(0) === '_');
if (showProperties) {
var properties = collection.properties();
result.doCompact = properties.doCompact;
result.isVolatile = properties.isVolatile;
result.journalSize = properties.journalSize;
result.keyOptions = properties.keyOptions;
result.waitForSync = properties.waitForSync;
result.indexBuckets = properties.indexBuckets;
if (properties.cacheEnabled) {
result.cacheEnabled = properties.cacheEnabled;
}
if (cluster.isCoordinator()) {
result.avoidServers = properties.avoidServers;
result.numberOfShards = properties.numberOfShards;
result.replicationFactor = properties.replicationFactor;
result.avoidServers = properties.avoidServers;
result.distributeShardsLike = properties.distributeShardsLike;
result.shardKeys = properties.shardKeys;
}
}
if (showCount) {
// show either the count value as a number or the detailed shard counts
result.count = collection.count(showCount === 'details');
}
if (showFigures) {
var figures = collection.figures();
if (figures) {
result.figures = figures;
}
}
result.globallyUniqueId = collection.globallyUniqueId();
result.status = collection.status();
result.type = collection.type();
return result;
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief helper to parse arguments for creating collections
// //////////////////////////////////////////////////////////////////////////////
function parseBodyForCreateCollection (req, res) {
var body = actions.getJsonBody(req, res);
if (body === undefined) {
return { bodyIsEmpty: true };
}
var r = {};
if (!body.hasOwnProperty('name')) {
r.name = '';
} else {
r.name = body.name;
}
r.parameters = { waitForSync: require('internal').options()['database.wait-for-sync']};
r.type = arangodb.ArangoCollection.TYPE_DOCUMENT;
if (body.hasOwnProperty('doCompact')) {
r.parameters.doCompact = body.doCompact;
}
if (body.hasOwnProperty('isSystem')) {
r.parameters.isSystem = (body.isSystem && r.name[0] === '_');
}
if (body.hasOwnProperty('id')) {
r.parameters.id = body.id;
}
if (body.hasOwnProperty('isVolatile')) {
r.parameters.isVolatile = body.isVolatile;
}
if (body.hasOwnProperty('journalSize')) {
r.parameters.journalSize = body.journalSize;
}
if (body.hasOwnProperty('indexBuckets')) {
r.parameters.indexBuckets = body.indexBuckets;
}
if (body.hasOwnProperty('keyOptions')) {
r.parameters.keyOptions = body.keyOptions;
}
if (body.hasOwnProperty('type')) {
r.type = body.type;
}
if (body.hasOwnProperty('waitForSync')) {
r.parameters.waitForSync = body.waitForSync;
}
if (body.hasOwnProperty('cacheEnabled')) {
r.parameters.cacheEnabled = body.cacheEnabled;
}
if (cluster.isCoordinator()) {
if (body.hasOwnProperty('shardKeys')) {
r.parameters.shardKeys = body.shardKeys || { };
}
if (body.hasOwnProperty('numberOfShards')) {
r.parameters.numberOfShards = body.numberOfShards || 0;
}
if (body.hasOwnProperty('distributeShardsLike')) {
r.parameters.distributeShardsLike = body.distributeShardsLike || '';
}
if (body.hasOwnProperty('avoidServers')) {
r.parameters.avoidServers = body.avoidServers || [];
}
if (body.hasOwnProperty('isSmart')) {
r.parameters.isSmart = body.isSmart || '';
}
if (body.hasOwnProperty('smartGraphAttribute')) {
r.parameters.smartGraphAttribute = body.smartGraphAttribute || '';
}
if (body.hasOwnProperty('replicationFactor')) {
r.parameters.replicationFactor = body.replicationFactor || '';
}
if (body.hasOwnProperty('servers')) {
r.parameters.servers = body.servers || '';
}
}
return r;
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSF_post_api_collection
// //////////////////////////////////////////////////////////////////////////////
function post_api_collection (req, res) {
var r = parseBodyForCreateCollection(req, res);
if (r.bodyIsEmpty) {
return; // error in JSON, is already reported
}
if (r.name === '') {
actions.resultBad(req, res, arangodb.ERROR_ARANGO_ILLEGAL_NAME,
'name must be non-empty');
return;
}
let getBooleanProperty = function(obj, property, defaultValue) {
let value = defaultValue;
if (req.parameters.hasOwnProperty(property)) {
var valueStr = req.parameters[property].toLowerCase();
if (valueStr === 'true' || valueStr === 'yes' || valueStr === 'on' || valueStr === 'y' || valueStr === '1') {
value = true;
} else {
value = false;
}
}
return value;
};
try {
var options = {
};
let waitForSyncReplication = getBooleanProperty(req.parameters, 'waitForSyncReplication', undefined);
if (waitForSyncReplication !== undefined) {
options.waitForSyncReplication = waitForSyncReplication;
}
let enforceReplicationFactor = getBooleanProperty(req.parameters, 'enforceReplicationFactor', undefined);
if (enforceReplicationFactor !== undefined) {
options.enforceReplicationFactor = enforceReplicationFactor;
}
var collection;
if (typeof (r.type) === 'string') {
if (r.type.toLowerCase() === 'edge' || r.type === '3') {
r.type = arangodb.ArangoCollection.TYPE_EDGE;
}
}
if (r.type === arangodb.ArangoCollection.TYPE_EDGE) {
collection = arangodb.db._createEdgeCollection(r.name, r.parameters, options);
} else {
collection = arangodb.db._createDocumentCollection(r.name, r.parameters, options);
}
var result = {};
result.id = collection._id;
result.name = collection.name();
result.waitForSync = r.parameters.waitForSync || false;
result.isVolatile = r.parameters.isVolatile || false;
result.isSystem = r.parameters.isSystem || false;
result.status = collection.status();
result.type = collection.type();
result.keyOptions = collection.keyOptions;
if (r.parameters.cacheEnabled !== undefined) {
result.cacheEnabled = r.parameters.cacheEnabled;
}
if (cluster.isCoordinator()) {
result.shardKeys = collection.shardKeys;
result.numberOfShards = collection.numberOfShards;
result.distributeShardsLike = collection.distributeShardsLike || '';
}
var headers = {
location: databasePrefix(req, '/_api/collection/' + result.name)
};
actions.resultOk(req, res, actions.HTTP_OK, result, headers);
} catch (err) {
actions.resultException(req, res, err, undefined, false);
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSF_get_api_collections
// //////////////////////////////////////////////////////////////////////////////
function get_api_collections (req, res) {
var excludeSystem;
var collections = arangodb.db._collections();
excludeSystem = false;
if (req.parameters.hasOwnProperty('excludeSystem')) {
var value = req.parameters.excludeSystem.toLowerCase();
if (value === 'true' || value === 'yes' || value === 'on' || value === 'y' || value === '1') {
excludeSystem = true;
}
}
var list = [];
for (var i = 0; i < collections.length; ++i) {
var collection = collections[i];
var rep = collectionRepresentation(collection);
// include system collections or exclude them?
if (!excludeSystem || rep.name.substr(0, 1) !== '_') {
list.push(rep);
}
}
actions.resultOk(req, res, actions.HTTP_OK, { result: list });
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSA_get_api_collection_name
// //////////////////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSA_get_api_collection_properties
// //////////////////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSA_get_api_collection_count
// //////////////////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSA_get_api_collection_figures
// //////////////////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSA_get_api_collection_revision
// //////////////////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSA_get_api_collection_checksum
// //////////////////////////////////////////////////////////////////////////////
function get_api_collection (req, res) {
var name;
var result;
var sub;
// .............................................................................
// /_api/collection
// .............................................................................
if (req.suffix.length === 0 && req.parameters.id === undefined) {
get_api_collections(req, res);
return;
}
// .............................................................................
// /_api/collection/<name>
// .............................................................................
name = req.suffix[0];
var collection = arangodb.db._collection(name);
if (collection === null) {
actions.collectionNotFound(req, res, name);
return;
}
var headers;
// .............................................................................
// /_api/collection/<name>
// .............................................................................
if (req.suffix.length === 1) {
result = collectionRepresentation(collection, false, false, false);
headers = {
location: databasePrefix(req, '/_api/collection/' + collection.name())
};
actions.resultOk(req, res, actions.HTTP_OK, result, headers);
return;
}
if (req.suffix.length === 2) {
sub = req.suffix[1];
// .............................................................................
// /_api/collection/<identifier>/checksum
// .............................................................................
if (sub === 'checksum') {
var withRevisions = false;
var withData = false;
var value;
if (req.parameters.hasOwnProperty('withRevisions')) {
value = req.parameters.withRevisions.toLowerCase();
if (value === 'true' || value === 'yes' || value === 'on' || value === 'y' || value === '1') {
withRevisions = true;
}
}
if (req.parameters.hasOwnProperty('withData')) {
value = req.parameters.withData.toLowerCase();
if (value === 'true' || value === 'yes' || value === 'on' || value === 'y' || value === '1') {
withData = true;
}
}
result = collectionRepresentation(collection, false, false, false);
var checksum = collection.checksum(withRevisions, withData);
result.checksum = checksum.checksum;
result.revision = checksum.revision;
actions.resultOk(req, res, actions.HTTP_OK, result);
}
// .............................................................................
// /_api/collection/<identifier>/figures
// .............................................................................
else if (sub === 'figures') {
result = collectionRepresentation(collection, true, true, true);
headers = {
location: databasePrefix(req, '/_api/collection/' + collection.name() + '/figures')
};
actions.resultOk(req, res, actions.HTTP_OK, result, headers);
}
// .............................................................................
// /_api/collection/<identifier>/count
// .............................................................................
else if (sub === 'count') {
// show either the count value as a number or the detailed shard counts
if (req.parameters.details === 'true') {
result = collectionRepresentation(collection, true, 'details', false);
} else {
result = collectionRepresentation(collection, true, true, false);
}
headers = {
location: databasePrefix(req, '/_api/collection/' + collection.name() + '/count')
};
actions.resultOk(req, res, actions.HTTP_OK, result, headers);
}
// .............................................................................
// /_api/collection/<identifier>/properties
// .............................................................................
else if (sub === 'properties') {
result = collectionRepresentation(collection, true, false, false);
headers = {
location: databasePrefix(req, '/_api/collection/' + collection.name() + '/properties')
};
actions.resultOk(req, res, actions.HTTP_OK, result, headers);
}
// .............................................................................
// /_api/collection/<identifier>/revision
// .............................................................................
else if (sub === 'revision') {
result = collectionRepresentation(collection, false, false, false);
result.revision = collection.revision();
actions.resultOk(req, res, actions.HTTP_OK, result);
}
else if (sub === 'shards') {
result = collectionRepresentation(collection, false, false, false);
result.shards = Object.keys(ArangoClusterInfo.getCollectionInfo(arangodb.db._name(), collection.name()).shardShorts);
actions.resultOk(req, res, actions.HTTP_OK, result);
} else {
actions.resultNotFound(req, res, arangodb.ERROR_HTTP_NOT_FOUND,
"expecting one of the resources 'checksum', 'count',"
+ " 'figures', 'properties', 'revision', 'shards'");
}
} else {
actions.resultBad(req, res, arangodb.ERROR_HTTP_BAD_PARAMETER,
'expect GET /_api/collection/<collection-name>/<method>');
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSF_put_api_collection_load
// //////////////////////////////////////////////////////////////////////////////
function put_api_collection_load (req, res, collection) {
try {
collection.load();
var showCount = true;
var body = actions.getJsonBody(req, res);
if (body && body.hasOwnProperty('count')) {
showCount = body.count;
}
var result = collectionRepresentation(collection, false, showCount, false);
actions.resultOk(req, res, actions.HTTP_OK, result);
} catch (err) {
actions.resultException(req, res, err, undefined, false);
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSF_put_api_collection_loadIndexesIntoMemory
// //////////////////////////////////////////////////////////////////////////////
function put_api_collection_load_indexes_in_memory (req, res, collection) {
try {
// Load all index values into Memory
collection.loadIndexesIntoMemory();
actions.resultOk(req, res, actions.HTTP_OK, { result: true });
} catch (err) {
actions.resultException(req, res, err, undefined, false);
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSF_put_api_collection_unload
// //////////////////////////////////////////////////////////////////////////////
function put_api_collection_unload (req, res, collection) {
try {
if (req.parameters.hasOwnProperty('flush')) {
var value = req.parameters.flush.toLowerCase();
if (value === 'true' || value === 'yes' || value === 'on' || value === 'y' || value === '1') {
if (collection.status() === 3 /* loaded */ &&
collection.figures().uncollectedLogfileEntries > 0) {
// flush WAL so uncollected logfile entries can get collected
require('internal').wal.flush();
}
}
}
// then unload
collection.unload();
var result = collectionRepresentation(collection);
actions.resultOk(req, res, actions.HTTP_OK, result);
} catch (err) {
actions.resultException(req, res, err, undefined, false);
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSF_put_api_collection_truncate
// //////////////////////////////////////////////////////////////////////////////
function put_api_collection_truncate (req, res, collection) {
let waitForSync = false;
if (req.parameters.hasOwnProperty('waitForSync')) {
let value = req.parameters.waitForSync.toLowerCase();
if (value === 'true' || value === 'yes' ||
value === 'on' || value === 'y' || value === '1') {
waitForSync = true;
}
}
let isSynchronousReplicationFrom = "";
if (req.parameters.hasOwnProperty('isSynchronousReplication')) {
isSynchronousReplicationFrom = req.parameters.isSynchronousReplication;
}
try {
collection.truncate(waitForSync, isSynchronousReplicationFrom);
var result = collectionRepresentation(collection);
actions.resultOk(req, res, actions.HTTP_OK, result);
} catch (err) {
actions.resultException(req, res, err, undefined, false);
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSF_put_api_collection_properties
// //////////////////////////////////////////////////////////////////////////////
function put_api_collection_properties (req, res, collection) {
var body = actions.getJsonBody(req, res);
if (body === undefined) {
return;
}
try {
collection.properties(body);
var result = collectionRepresentation(collection, true);
actions.resultOk(req, res, actions.HTTP_OK, result);
} catch (err) {
actions.resultException(req, res, err, undefined, false);
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSF_put_api_collection_rename
// //////////////////////////////////////////////////////////////////////////////
function put_api_collection_rename (req, res, collection) {
var body = actions.getJsonBody(req, res);
if (body === undefined) {
return;
}
if (!body.hasOwnProperty('name')) {
actions.resultBad(req, res, arangodb.ERROR_ARANGO_ILLEGAL_NAME,
'name must be non-empty');
return;
}
var name = body.name;
try {
collection.rename(name);
var result = collectionRepresentation(collection);
actions.resultOk(req, res, actions.HTTP_OK, result);
} catch (err) {
actions.resultException(req, res, err, undefined, false);
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSF_put_api_collection_rotate
// //////////////////////////////////////////////////////////////////////////////
function put_api_collection_rotate (req, res, collection) {
try {
collection.rotate();
actions.resultOk(req, res, actions.HTTP_OK, { result: true });
} catch (err) {
actions.resultException(req, res, err, undefined, false);
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief changes a collection
// //////////////////////////////////////////////////////////////////////////////
function put_api_collection (req, res) {
if (req.suffix.length !== 2) {
actions.resultBad(req, res, arangodb.ERROR_HTTP_BAD_PARAMETER,
'expected PUT /_api/collection/<collection-name>/<action>');
return;
}
var name = req.suffix[0];
var collection = arangodb.db._collection(name);
if (collection === null) {
actions.collectionNotFound(req, res, name);
return;
}
var sub = req.suffix[1];
if (sub === 'load') {
put_api_collection_load(req, res, collection);
} else if (sub === 'unload') {
put_api_collection_unload(req, res, collection);
collection = null;
// run garbage collection once in all threads
require('internal').executeGlobalContextFunction('collectGarbage');
} else if (sub === 'truncate') {
put_api_collection_truncate(req, res, collection);
} else if (sub === 'properties') {
put_api_collection_properties(req, res, collection);
} else if (sub === 'rename') {
put_api_collection_rename(req, res, collection);
} else if (sub === 'rotate') {
put_api_collection_rotate(req, res, collection);
} else if (sub === 'loadIndexesIntoMemory') {
put_api_collection_load_indexes_in_memory(req, res, collection);
} else {
actions.resultNotFound(req, res, arangodb.ERROR_HTTP_NOT_FOUND,
"expecting one of the actions 'load', 'unload',"
+ " 'truncate', 'properties', 'rename', 'loadIndexesIntoMemory'");
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief was docuBlock JSF_delete_api_collection
// //////////////////////////////////////////////////////////////////////////////
function delete_api_collection (req, res) {
if (req.suffix.length !== 1) {
actions.resultBad(req, res, arangodb.ERROR_HTTP_BAD_PARAMETER,
'expected DELETE /_api/collection/<collection-name>');
} else {
var name = req.suffix[0];
var collection = arangodb.db._collection(name);
if (collection === null) {
actions.collectionNotFound(req, res, name);
} else {
try {
var result = {
id: collection._id
};
var options = {};
if (req.parameters.hasOwnProperty('isSystem')) {
// are we allowed to drop system collections?
var value = req.parameters.isSystem.toLowerCase();
if (value === 'true' || value === 'yes' || value === 'on' || value === 'y' || value === '1') {
options.isSystem = true;
}
}
collection.drop(options);
actions.resultOk(req, res, actions.HTTP_OK, result);
} catch (err) {
actions.resultException(req, res, err, undefined, false);
}
}
}
}
// //////////////////////////////////////////////////////////////////////////////
// / @brief handles a collection request
// //////////////////////////////////////////////////////////////////////////////
actions.defineHttp({
url: '_api/collection',
callback: function (req, res) {
try {
if (req.requestType === actions.GET) {
get_api_collection(req, res);
} else if (req.requestType === actions.DELETE) {
delete_api_collection(req, res);
} else if (req.requestType === actions.POST) {
post_api_collection(req, res);
} else if (req.requestType === actions.PUT) {
put_api_collection(req, res);
} else {
actions.resultUnsupported(req, res);
}
} catch (err) {
actions.resultException(req, res, err, undefined, false);
}
}
});

View File

@ -357,15 +357,27 @@ ArangoDatabase.prototype._create = function (name, properties, type, options) {
});
}
let urlAddon = '';
let urlAddons = [];
if (typeof options === "object" && options !== null) {
if (options.hasOwnProperty('waitForSyncReplication')) {
if (options.waitForSyncReplication) {
urlAddon = '?waitForSyncReplication=1';
urlAddons.push('waitForSyncReplication=1');
} else {
urlAddon = '?waitForSyncReplication=0';
urlAddons.push('waitForSyncReplication=0');
}
}
if (options.hasOwnProperty('enforceReplicationFactor')) {
if (options.enforceReplicationFactor) {
urlAddons.push('enforceReplicationFactor=1');
} else {
urlAddons.push('enforceReplicationFactor=0');
}
}
}
let urlAddon = '';
if (urlAddons.length > 0) {
urlAddon += '?' + urlAddons.join('&');
}
if (type !== undefined) {

View File

@ -209,3 +209,30 @@ describe('Update collection properties with distributeShardsLike, ', function()
}
});
});
describe('Replication factor constraints', function() {
beforeEach(function() {
db._useDatabase("_system");
});
afterEach(function() {
db._useDatabase("_system");
try {
db._drop(cn1);
} catch (e) {}
});
it('should not allow to create a collection with more replicas than dbservers available', function() {
try {
db._create(cn1, {replicationFactor: 5});
throw new Error('Should not reach this');
} catch (e) {
expect(e.errorNum).to.equal(errors.ERROR_CLUSTER_INSUFFICIENT_DBSERVERS.code);
}
});
it('should allow to create a collection with more replicas than dbservers when explicitly requested', function() {
db._create(cn1, {replicationFactor: 5}, {enforceReplicationFactor: false});
});
});

View File

@ -129,3 +129,15 @@ bool TRI_ObjectToBoolean(v8::Handle<v8::Value> const value) {
return false;
}
/// @brief extracts an optional boolean property from a V8 object
bool TRI_GetOptionalBooleanProperty(v8::Isolate* isolate,
v8::Handle<v8::Object> const obj,
const char* property,
bool defaultValue) {
auto value = obj->Get(TRI_V8_ASCII_STRING(isolate, property));
if (!value->IsUndefined()) {
return TRI_ObjectToBoolean(value);
} else {
return defaultValue;
}
}

View File

@ -55,4 +55,9 @@ double TRI_ObjectToDouble(v8::Handle<v8::Value> const, bool& error);
// converts a V8 object to a boolean
bool TRI_ObjectToBoolean(v8::Handle<v8::Value> const);
bool TRI_GetOptionalBooleanProperty(v8::Isolate* isolate,
v8::Handle<v8::Object> const obj,
const char* property,
bool defaultValue);
#endif