1
0
Fork 0
arangodb/js/actions/api-cluster.js

427 lines
15 KiB
JavaScript

/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true, evil: true */
/*global require, exports, module, SYS_CLUSTER_TEST */
////////////////////////////////////////////////////////////////////////////////
/// @brief cluster actions
///
/// @file js/api-cluster.js
///
/// DISCLAIMER
///
/// Copyright 2014-2014 triagens GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Max Neunhoeffer
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var actions = require("org/arangodb/actions");
// -----------------------------------------------------------------------------
// --SECTION-- private functions
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// --SECTION-- public functions
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_cluster_test_GET
/// @brief executes a cluster roundtrip for sharding
///
/// @RESTHEADER{GET /_admin/cluster-test,executes a cluster roundtrip}
///
/// @RESTDESCRIPTION
///
/// Executes a cluster roundtrip from a coordinator to a DB server and
/// back. This call only works in a coordinator node in a cluster.
/// One can and should append an arbitrary path to the URL and the
/// part after `/_admin/cluster-test` is used as the path of the HTTP
/// request which is sent from the coordinator to a DB node. Likewise,
/// any form data appended to the URL is forwarded in the request to the
/// DB node. This handler takes care of all request types (see below)
/// and uses the same request type in its request to the DB node.
///
/// The following HTTP headers are interpreted in a special way:
///
/// - `X-Shard-ID`: This specifies the ID of the shard to which the
/// cluster request is sent and thus tells the system to which DB server
/// to send the cluster request. Note that the mapping from the
/// shard ID to the responsible server has to be defined in the
/// agency under `Current/ShardLocation/<shardID>`. One has to give
/// this header, otherwise the system does not know where to send
/// the request.
/// - `X-Client-Transaction-ID`: the value of this header is taken
/// as the client transaction ID for the request
/// - `X-Timeout`: specifies a timeout in seconds for the cluster
/// operation. If the answer does not arrive within the specified
/// timeout, an corresponding error is returned and any subsequent
/// real answer is ignored. The default if not given is 24 hours.
/// - `X-Synchronous-Mode`: If set to `true` the test function uses
/// synchronous mode, otherwise the default asynchronous operation
/// mode is used. This is mainly for debugging purposes.
/// - `Host`: This header is ignored and not forwarded to the DB server.
/// - `User-Agent`: This header is ignored and not forwarded to the DB
/// server.
///
/// All other HTTP headers and the body of the request (if present, see
/// other HTTP methods below) are forwarded as given in the original request.
///
/// In asynchronous mode the DB server answers with an HTTP request of its
/// own, in synchronous mode it sends a HTTP response. In both cases the
/// headers and the body are used to produce the HTTP response of this
/// API call.
///
/// @RESTRETURNCODES
///
/// The return code can be anything the cluster request returns, as well as:
///
/// @RESTRETURNCODE{200}
/// is returned when everything went well, or if a timeout occurred. In the
/// latter case a body of type application/json indicating the timeout
/// is returned.
///
/// @RESTRETURNCODE{403}
/// is returned if ArangoDB is not running in cluster mode.
///
/// @RESTRETURNCODE{404}
/// is returned if ArangoDB was not compiled for cluster operation.
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_cluster_test_POST
/// @brief executes a cluster roundtrip for sharding
///
/// @RESTHEADER{POST /_admin/cluster-test,executes a cluster roundtrip}
///
/// @RESTBODYPARAM{body,anything,required}
///
/// @RESTDESCRIPTION
/// See GET method. The body can be any type and is simply forwarded.
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_cluster_test_PUT
/// @brief executes a cluster roundtrip for sharding
///
/// @RESTHEADER{PUT /_admin/cluster-test,executes a cluster roundtrip}
///
/// @RESTBODYPARAM{body,anything,required}
///
/// @RESTDESCRIPTION
/// See GET method. The body can be any type and is simply forwarded.
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_cluster_test_DELETE
/// @brief executes a cluster roundtrip for sharding
///
/// @RESTHEADER{DELETE /_admin/cluster-test,executes a cluster roundtrip}
///
/// @RESTDESCRIPTION
/// See GET method. The body can be any type and is simply forwarded.
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_cluster_test_PATCH
/// @brief executes a cluster roundtrip for sharding
///
/// @RESTHEADER{PATCH /_admin/cluster-test,executes a cluster roundtrip}
///
/// @RESTBODYPARAM{body,anything,required}
///
/// @RESTDESCRIPTION
/// See GET method. The body can be any type and is simply forwarded.
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_cluster_test_HEAD
/// @brief executes a cluster roundtrip for sharding
///
/// @RESTHEADER{HEAD /_admin/cluster-test,executes a cluster roundtrip}
///
/// @RESTDESCRIPTION
/// See GET method. The body can be any type and is simply forwarded.
////////////////////////////////////////////////////////////////////////////////
actions.defineHttp({
url : "_admin/cluster-test",
context : "admin",
prefix : true,
callback : function (req, res) {
var path;
if (req.hasOwnProperty('suffix') && req.suffix.length !== 0) {
path = "/"+req.suffix.join("/");
}
else {
path = "/_admin/version";
}
var params = "";
var shard = "";
var p;
for (p in req.parameters) {
if (req.parameters.hasOwnProperty(p)) {
if (params === "") {
params = "?";
}
else {
params += "&";
}
params += p+"="+ encodeURIComponent(String(req.parameters[p]));
}
}
if (params !== "") {
path += params;
}
var headers = {};
var transID = "";
var timeout = 24*3600.0;
var asyncMode = true;
for (p in req.headers) {
if (req.headers.hasOwnProperty(p)) {
if (p === "x-client-transaction-id") {
transID = req.headers[p];
}
else if (p === "x-timeout") {
timeout = parseFloat(req.headers[p]);
if (isNaN(timeout)) {
timeout = 24*3600.0;
}
}
else if (p === "x-synchronous-mode") {
asyncMode = false;
}
else if (p === "x-shard-id") {
shard = req.headers[p];
}
else {
headers[p] = req.headers[p];
}
}
}
var body;
if (req.requestBody === undefined || typeof req.requestBody !== "string") {
body = "";
}
else {
body = req.requestBody;
}
var r;
if (typeof SYS_CLUSTER_TEST === "undefined") {
actions.resultError(req, res, actions.HTTP_NOT_FOUND,
"Not compiled for cluster operation");
}
else {
try {
r = SYS_CLUSTER_TEST(req, res, shard, path, transID,
headers, body, timeout, asyncMode);
if (r.timeout || typeof r.errorMessage === 'string') {
res.responseCode = actions.HTTP_OK;
res.contentType = "application/json; charset=utf-8";
var s = JSON.stringify(r);
res.body = s;
}
else {
res.responseCode = actions.HTTP_OK;
res.contentType = r.headers.contentType;
res.headers = r.headers;
res.body = r.body;
}
}
catch(err) {
actions.resultError(req, res, actions.HTTP_FORBIDDEN, String(err));
}
}
}
});
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_cluster_planner_POST
/// @brief exposes the kickstarter planning function
///
/// @RESTHEADER{POST /_admin/kickstarter,produce a cluster startup plan}
///
/// @RESTBODYPARAM{body,json,required}
///
/// @RESTDESCRIPTION Given a description of a cluster, this plans the details
/// of a cluster and returns a JSON description of a plan to start up this
/// cluster.
///
/// @RESTRETURNCODES
///
/// @RESTRETURNCODE{200} is returned when everything went well.
///
/// @RESTRETURNCODE{400} the posted body was not valid JSON.
////////////////////////////////////////////////////////////////////////////////
actions.defineHttp({
url : "_admin/clusterPlanner",
context : "admin",
prefix : "false",
callback : function (req, res) {
if (req.requestType !== actions.POST) {
actions.resultError(req, res, actions.HTTP_FORBIDDEN);
return;
}
var userconfig;
try {
userconfig = JSON.parse(req.requestBody);
}
catch (err) {
actions.resultError(req, res, actions.HTTP_BAD,
"Posted body was not valid JSON.");
return;
}
var Planner = require("org/arangodb/cluster/planner").Planner;
try {
var p = new Planner(userconfig);
res.responseCode = actions.HTTP_OK;
res.contentType = "application/json; charset=utf-8";
res.body = JSON.stringify({"clusterPlan": p.getPlan(),
"config": p.config});
}
catch (error) {
actions.resultException(req, res, error, undefined, false);
}
}
});
////////////////////////////////////////////////////////////////////////////////
/// @fn JSF_cluster_dispatcher_POST
/// @brief exposes the dispatcher functionality to start up a cluster
/// according to a startup plan as for example provided by the kickstarter.
///
/// @RESTHEADER{POST /_admin/dispatch,execute startup commands}
///
/// @RESTQUERYPARAMETERS
///
/// @RESTBODYPARAM{body,json,required}
///
/// @RESTDESCRIPTION Given a list of cluster startup commands, this
/// call executes the plan by either starting up processes personally
/// or by delegating to other dispatchers.
///
/// @RESTRETURNCODES
///
/// @RESTRETURNCODE{200} is returned when everything went well.
///
/// @RESTRETURNCODE{400} the posted body was not valid JSON, or something
/// went wrong with the startup.
////////////////////////////////////////////////////////////////////////////////
actions.defineHttp({
url : "_admin/clusterDispatch",
context : "admin",
prefix : "false",
callback : function (req, res) {
if (req.requestType !== actions.POST) {
actions.resultError(req, res, actions.HTTP_FORBIDDEN);
return;
}
var input;
try {
input = JSON.parse(req.requestBody);
}
catch (error) {
actions.resultError(req, res, actions.HTTP_BAD,
"Posted body was not valid JSON.");
return;
}
if (!input.hasOwnProperty("clusterPlan")) {
actions.resultError(req, res, actions.HTTP_BAD,
'Posted body needs a "clusterPlan" property.');
return;
}
if (!input.hasOwnProperty("myname")) {
actions.resultError(req, res, actions.HTTP_BAD,
'Posted body needs a "myname" property.');
return;
}
var action = input.action;
var Kickstarter, k, r;
if (action === "launch") {
Kickstarter = require("org/arangodb/cluster/kickstarter").Kickstarter;
try {
k = new Kickstarter(input.clusterPlan, input.myname);
r = k.launch();
res.responseCode = actions.HTTP_OK;
res.contentType = "application/json; charset=utf-8";
res.body = JSON.stringify(r);
}
catch (error2) {
actions.resultException(req, res, error2, undefined, false);
}
}
else if (action === "relaunch") {
Kickstarter = require("org/arangodb/cluster/kickstarter").Kickstarter;
try {
k = new Kickstarter(input.clusterPlan, input.myname);
r = k.relaunch();
res.responseCode = actions.HTTP_OK;
res.contentType = "application/json; charset=utf-8";
res.body = JSON.stringify(r);
}
catch (error3) {
actions.resultException(req, res, error3, undefined, false);
}
}
else if (action === "shutdown") {
if (!input.hasOwnProperty("runInfo")) {
actions.resultError(req, res, actions.HTTP_BAD,
'Posted body needs a "runInfo" property.');
return;
}
Kickstarter = require("org/arangodb/cluster/kickstarter").Kickstarter;
try {
k = new Kickstarter(input.clusterPlan, input.myname);
k.runInfo = input.runInfo;
r = k.shutdown();
res.responseCode = actions.HTTP_OK;
res.contentType = "application/json; charset=utf-8";
res.body = JSON.stringify(r);
}
catch (error4) {
actions.resultException(req, res, error4, undefined, false);
}
}
else {
actions.resultError(req, res, actions.HTTP_BAD,
'Action '+action+' not yet implemented.');
}
}
});
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------
// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @\\}"
// End: