diff --git a/arangod/Aql/ExecutionBlock.cpp b/arangod/Aql/ExecutionBlock.cpp index 062fc2a797..6f0b5be198 100644 --- a/arangod/Aql/ExecutionBlock.cpp +++ b/arangod/Aql/ExecutionBlock.cpp @@ -28,6 +28,7 @@ #include "Aql/ExecutionBlock.h" #include "Aql/ExecutionEngine.h" #include "Basics/StringUtils.h" +#include "Basics/StringBuffer.h" #include "Basics/json-utilities.h" #include "HashIndex/hash-index.h" #include "Utils/Exception.h" @@ -40,6 +41,7 @@ using namespace triagens::aql; using Json = triagens::basics::Json; using JsonHelper = triagens::basics::JsonHelper; +using StringBuffer = triagens::basics::StringBuffer; // ----------------------------------------------------------------------------- // --SECTION-- struct AggregatorGroup @@ -3932,6 +3934,34 @@ size_t ScatterBlock::skipSomeForShard (size_t atLeast, size_t atMost, std::strin // --SECTION-- class RemoteBlock // ----------------------------------------------------------------------------- +//////////////////////////////////////////////////////////////////////////////// +/// @brief timeout +//////////////////////////////////////////////////////////////////////////////// + +double const RemoteBlock::defaultTimeOut = 3600.0; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief local helper to throw an exception if a HTTP request went wrong +//////////////////////////////////////////////////////////////////////////////// + +static void throwExceptionAfterBadSyncRequest (ClusterCommResult* res) { + if (res->status == CL_COMM_TIMEOUT) { + // No reply, we give up: + THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_CLUSTER_TIMEOUT, + "timeout in cluster AQL operation"); + } + if (res->status == CL_COMM_ERROR) { + // This could be a broken connection or an Http error: + if (res->result == nullptr || ! res->result->isComplete()) { + // there is no result + THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_CLUSTER_CONNECTION_LOST, + "lost connection within cluster"); + } + // In this case a proper HTTP error was reported by the DBserver, + THROW_ARANGO_EXCEPTION(TRI_ERROR_CLUSTER_AQL_COMMUNICATION); + } +} + //////////////////////////////////////////////////////////////////////////////// /// @brief initialize //////////////////////////////////////////////////////////////////////////////// @@ -3969,15 +3999,103 @@ int RemoteBlock::shutdown () { AqlItemBlock* RemoteBlock::getSome (size_t atLeast, size_t atMost) { - return nullptr; + // For every call we simply forward via HTTP + + ClusterComm* cc = ClusterComm::instance(); + std::unique_ptr res; + + // Later, we probably want to set these sensibly: + ClientTransactionID const clientTransactionId = "AQL"; + CoordTransactionID const coordTransactionId = 1; + + Json body(Json::Array, 2); + body("atLeast", Json(static_cast(atLeast))) + ("atMost", Json(static_cast(atMost))); + std::string bodyString(body.toString()); + std::map headers; + + res.reset(cc->syncRequest(clientTransactionId, + coordTransactionId, + _server, + rest::HttpRequest::HTTP_REQUEST_PUT, + std::string("/_db/") + + _engine->getTransaction()->vocbase()->_name + + "/_api/aql/getSome/" + _queryId, + bodyString, + headers, + 3600.0)); + + throwExceptionAfterBadSyncRequest(res.get()); + + // If we get here, then res->result is the response which will be + // a serialized AqlItemBlock: + StringBuffer const& responseBodyBuf(res->result->getBody()); + Json responseBodyJson(TRI_UNKNOWN_MEM_ZONE, + TRI_JsonString(TRI_UNKNOWN_MEM_ZONE, + responseBodyBuf.begin())); + if (JsonHelper::getBooleanValue(responseBodyJson.json(), "exhausted", true)) { + return nullptr; + } + else { + auto items = new triagens::aql::AqlItemBlock(responseBodyJson); + return items; + } } //////////////////////////////////////////////////////////////////////////////// /// @brief skipSome //////////////////////////////////////////////////////////////////////////////// +ClusterCommResult* RemoteBlock::sendRequest ( + rest::HttpRequest::HttpRequestType type, + std::string urlPart, + std::string const& body) { + + ClusterComm* cc = ClusterComm::instance(); + + // Later, we probably want to set these sensibly: + ClientTransactionID const clientTransactionId = "AQL"; + CoordTransactionID const coordTransactionId = 1; + std::map headers; + + return cc->syncRequest(clientTransactionId, + coordTransactionId, + _server, + type, + std::string("/_db/") + + _engine->getTransaction()->vocbase()->_name + + urlPart + _queryId, + body, + headers, + defaultTimeOut); +} + size_t RemoteBlock::skipSome (size_t atLeast, size_t atMost) { - return 0; + // For every call we simply forward via HTTP + + Json body(Json::Array, 2); + body("atLeast", Json(static_cast(atLeast))) + ("atMost", Json(static_cast(atMost))); + std::string bodyString(body.toString()); + + std::unique_ptr res; + res.reset(sendRequest(rest::HttpRequest::HTTP_REQUEST_PUT, + "/_api/aql/skipSome/", + bodyString)); + throwExceptionAfterBadSyncRequest(res.get()); + + // If we get here, then res->result is the response which will be + // a serialized AqlItemBlock: + StringBuffer const& responseBodyBuf(res->result->getBody()); + Json responseBodyJson(TRI_UNKNOWN_MEM_ZONE, + TRI_JsonString(TRI_UNKNOWN_MEM_ZONE, + responseBodyBuf.begin())); + if (JsonHelper::getBooleanValue(responseBodyJson.json(), "error", true)) { + THROW_ARANGO_EXCEPTION(TRI_ERROR_CLUSTER_AQL_COMMUNICATION); + } + size_t skipped = JsonHelper::getNumericValue(responseBodyJson.json(), + "skipped", 0); + return skipped; } //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/ExecutionBlock.h b/arangod/Aql/ExecutionBlock.h index e1c2819071..4f80aa3b62 100644 --- a/arangod/Aql/ExecutionBlock.h +++ b/arangod/Aql/ExecutionBlock.h @@ -39,6 +39,7 @@ #include "Utils/AqlTransaction.h" #include "Utils/transactions.h" #include "Utils/V8TransactionContext.h" +#include "Cluster/ClusterComm.h" namespace triagens { namespace aql { @@ -1638,20 +1639,33 @@ public: class RemoteBlock : public ExecutionBlock { +//////////////////////////////////////////////////////////////////////////////// +/// @brief constructors/destructors +//////////////////////////////////////////////////////////////////////////////// + public: RemoteBlock (ExecutionEngine* engine, RemoteNode const* en, std::string const& server, - std::string const& ownName) + std::string const& ownName, + std::string const& queryId) : ExecutionBlock(engine, en), _server(server), - _ownName(ownName) { + _ownName(ownName), + _queryId(queryId) { + TRI_ASSERT(! queryId.empty()); } ~RemoteBlock () { } +//////////////////////////////////////////////////////////////////////////////// +/// @brief timeout +//////////////////////////////////////////////////////////////////////////////// + + static double const defaultTimeOut; + //////////////////////////////////////////////////////////////////////////////// /// @brief initialize //////////////////////////////////////////////////////////////////////////////// @@ -1702,19 +1716,35 @@ public: int64_t remaining () final; //////////////////////////////////////////////////////////////////////////////// -/// @brief our server, can be like "shard:S1000" or like "server:Claus" +/// @brief internal method to send a request //////////////////////////////////////////////////////////////////////////////// private: - std::string _server; + triagens::arango::ClusterCommResult* sendRequest ( + rest::HttpRequest::HttpRequestType type, + std::string urlPart, + std::string const& body); //////////////////////////////////////////////////////////////////////////////// /// @brief our server, can be like "shard:S1000" or like "server:Claus" +//////////////////////////////////////////////////////////////////////////////// + + std::string _server; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief our own identity, in case of the coordinator this is empty, +/// in case of the DBservers, this is the shard ID as a string //////////////////////////////////////////////////////////////////////////////// std::string _ownName; +//////////////////////////////////////////////////////////////////////////////// +/// @brief the ID of the query on the server as a string +//////////////////////////////////////////////////////////////////////////////// + + std::string _queryId; + }; } // namespace triagens::aql diff --git a/arangod/Aql/ExecutionEngine.cpp b/arangod/Aql/ExecutionEngine.cpp index 3029b9eee9..e941952b84 100644 --- a/arangod/Aql/ExecutionEngine.cpp +++ b/arangod/Aql/ExecutionEngine.cpp @@ -451,7 +451,8 @@ struct CoordinatorInstanciator : public WalkerWorker { // now we'll create a remote node for each shard and add it to the gather node auto&& shardIds = static_cast((*en))->collection()->shardIds(); for (auto shardId : shardIds) { - ExecutionBlock* r = new RemoteBlock(engine.get(), remoteNode, "shard:" + shardId, ""); + // TODO: pass actual queryId into RemoteBlock + ExecutionBlock* r = new RemoteBlock(engine.get(), remoteNode, "shard:" + shardId, "", "" /*TODO*/); try { engine.get()->addBlock(r); diff --git a/arangod/Aql/ExecutionEngine.h b/arangod/Aql/ExecutionEngine.h index 4c13d77cf4..b98bd2a6d8 100644 --- a/arangod/Aql/ExecutionEngine.h +++ b/arangod/Aql/ExecutionEngine.h @@ -114,6 +114,22 @@ namespace triagens { return _query; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief initializeCursor, could be called multiple times +//////////////////////////////////////////////////////////////////////////////// + + int initializeCursor (AqlItemBlock* items, size_t pos) { + return _root->initializeCursor(items, pos); + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief shutdown, will be called exactly once for the whole query +//////////////////////////////////////////////////////////////////////////////// + + int shutdown () { + return _root->shutdown(); + } + //////////////////////////////////////////////////////////////////////////////// /// @brief getSome //////////////////////////////////////////////////////////////////////////////// @@ -122,6 +138,14 @@ namespace triagens { return _root->getSome(atLeast, atMost); } +//////////////////////////////////////////////////////////////////////////////// +/// @brief skipSome +//////////////////////////////////////////////////////////////////////////////// + + size_t skipSome (size_t atLeast, size_t atMost) { + return _root->skipSome(atLeast, atMost); + } + //////////////////////////////////////////////////////////////////////////////// /// @brief getOne //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/ExecutionNode.cpp b/arangod/Aql/ExecutionNode.cpp index a2b03a973c..132e4a5a6e 100644 --- a/arangod/Aql/ExecutionNode.cpp +++ b/arangod/Aql/ExecutionNode.cpp @@ -93,10 +93,10 @@ void ExecutionNode::validateType (int type) { } } -void ExecutionNode::getSortElements(SortElementVector elements, - ExecutionPlan* plan, - triagens::basics::Json const& oneNode, - char const* which) +void ExecutionNode::getSortElements(SortElementVector& elements, + ExecutionPlan* plan, + triagens::basics::Json const& oneNode, + char const* which) { triagens::basics::Json jsonElements = oneNode.get("elements"); if (! jsonElements.isList()){ diff --git a/arangod/Aql/ExecutionNode.h b/arangod/Aql/ExecutionNode.h index 59f59bd182..21f10824a1 100644 --- a/arangod/Aql/ExecutionNode.h +++ b/arangod/Aql/ExecutionNode.h @@ -505,17 +505,17 @@ namespace triagens { static Variable* varFromJson (Ast* ast, triagens::basics::Json const& base, - const char *variableName, + char const* variableName, bool optional = false); //////////////////////////////////////////////////////////////////////////////// /// @brief factory for sort Elements from json. //////////////////////////////////////////////////////////////////////////////// - static void getSortElements(SortElementVector elements, - ExecutionPlan* plan, - triagens::basics::Json const& oneNode, - char const* which); + static void getSortElements (SortElementVector& elements, + ExecutionPlan* plan, + triagens::basics::Json const& oneNode, + char const* which); //////////////////////////////////////////////////////////////////////////////// /// @brief toJsonHelper, for a generic node @@ -1697,7 +1697,7 @@ namespace triagens { /// @brief get Variables Used Here including ASC/DESC //////////////////////////////////////////////////////////////////////////////// - SortElementVector getElements () const { + SortElementVector const & getElements () const { return _elements; } @@ -2866,11 +2866,11 @@ namespace triagens { /// @brief get Variables Used Here including ASC/DESC //////////////////////////////////////////////////////////////////////////////// - SortElementVector getElements () const { + SortElementVector const & getElements () const { return _elements; } - void setElements (SortElementVector const src) { + void setElements (SortElementVector const & src) { _elements = src; } @@ -2890,7 +2890,6 @@ namespace triagens { return _collection; } - private: //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Aql/RestAqlHandler.cpp b/arangod/Aql/RestAqlHandler.cpp index e67eaf0923..0d56c35f0f 100644 --- a/arangod/Aql/RestAqlHandler.cpp +++ b/arangod/Aql/RestAqlHandler.cpp @@ -381,7 +381,8 @@ void RestAqlHandler::deleteQuery (std::string const& idString) { //////////////////////////////////////////////////////////////////////////////// /// @brief PUT method for /_api/aql//, this is using /// the part of the cursor API with side effects. -/// : can be "getSome" or "skip". +/// : can be "getSome" or "skip" or "initializeCursor" or +/// "shutdown". /// The body must be a Json with the following attributes: /// For the "getSome" operation one has to give: /// "atLeast": @@ -395,6 +396,19 @@ void RestAqlHandler::deleteQuery (std::string const& idString) { /// AqlItemBlock. /// If "atLeast" is not given it defaults to 1, if "atMost" is not /// given it defaults to ExecutionBlock::DefaultBatchSize. +/// For the "skipSome" operation one has to give: +/// "atLeast": +/// "atMost": both must be positive integers, the cursor skips never +/// more than "atMost" items and tries to skip at least +/// "atLeast". Note that it is possible to skip fewer than +/// "atLeast", for example if there are only fewer items +/// left. However, the implementation may skip fewer items +/// than "atLeast" for internal reasons, for example to avoid +/// excessive copying. The result is a JSON object with a +/// single attribute "skipped" containing the number of +/// skipped items. +/// If "atLeast" is not given it defaults to 1, if "atMost" is not +/// given it defaults to ExecutionBlock::DefaultBatchSize. /// For the "skip" operation one should give: /// "number": must be a positive integer, the cursor skips as many items, /// possibly exhausting the cursor. @@ -402,6 +416,13 @@ void RestAqlHandler::deleteQuery (std::string const& idString) { /// "errorMessage" (if applicable) and "exhausted" (boolean) /// to indicate whether or not the cursor is exhausted. /// If "number" is not given it defaults to 1. +/// For the "initializeCursor" operation, one has to bind the following +/// attributes: +/// "items": This is a serialised AqlItemBlock with usually only one row +/// and the correct number of columns. +/// "pos": The number of the row in "items" to take, usually 0. +/// For the "shutdown" operation no additional arguments are required and +/// an empty JSON object in the body is OK. //////////////////////////////////////////////////////////////////////////////// void RestAqlHandler::useQuery (std::string const& operation, @@ -447,9 +468,27 @@ void RestAqlHandler::useQuery (std::string const& operation, } } } + else if (operation == "getSome") { + auto atLeast = JsonHelper::getNumericValue(queryJson.json(), + "atLeast", 1); + auto atMost = JsonHelper::getNumericValue(queryJson.json(), + "atMost", ExecutionBlock::DefaultBatchSize); + size_t skipped; + try { + skipped = query->engine()->skipSome(atLeast, atMost); + } + catch (...) { + _queryRegistry->close(vocbase, qId); + generateError(HttpResponse::SERVER_ERROR, TRI_ERROR_HTTP_SERVER_ERROR, + "skipSome lead to an exception"); + return; + } + answerBody("skipped", Json(static_cast(skipped))) + ("error", Json(false)); + } else if (operation == "skip") { auto number = JsonHelper::getNumericValue(queryJson.json(), - "number", 1); + "number", 1); try { bool exhausted = query->engine()->skip(number); answerBody("exhausted", Json(exhausted)) @@ -462,6 +501,38 @@ void RestAqlHandler::useQuery (std::string const& operation, return; } } + else if (operation == "initializeCursor") { + auto pos = JsonHelper::getNumericValue(queryJson.json(), + "pos", 0); + std::unique_ptr items; + int res; + try { + items.reset(new AqlItemBlock(queryJson.get("items"))); + res = query->engine()->initializeCursor(items.get(), pos); + } + catch (...) { + _queryRegistry->close(vocbase, qId); + generateError(HttpResponse::SERVER_ERROR, TRI_ERROR_HTTP_SERVER_ERROR, + "initializeCursor lead to an exception"); + return; + } + answerBody("error", res == TRI_ERROR_NO_ERROR ? Json(false) : Json(true)) + ("code", Json(static_cast(res))); + } + else if (operation == "shutdown") { + int res; + try { + res = query->engine()->shutdown(); + } + catch (...) { + _queryRegistry->close(vocbase, qId); + generateError(HttpResponse::SERVER_ERROR, TRI_ERROR_HTTP_SERVER_ERROR, + "shutdown lead to an exception"); + return; + } + answerBody("error", res == TRI_ERROR_NO_ERROR ? Json(false) : Json(true)) + ("code", Json(static_cast(res))); + } else { _queryRegistry->close(vocbase, qId); generateError(HttpResponse::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND); diff --git a/arangod/RestHandler/RestReplicationHandler.cpp b/arangod/RestHandler/RestReplicationHandler.cpp index 7f3e6928a3..d83ab979be 100644 --- a/arangod/RestHandler/RestReplicationHandler.cpp +++ b/arangod/RestHandler/RestReplicationHandler.cpp @@ -860,7 +860,7 @@ void RestReplicationHandler::handleTrampolineCoordinator () { } if (res->status == CL_COMM_ERROR) { // This could be a broken connection or an Http error: - if (res->result == 0 || !res->result->isComplete()) { + if (res->result == nullptr || !res->result->isComplete()) { // there is no result delete res; generateError(HttpResponse::BAD, TRI_ERROR_CLUSTER_CONNECTION_LOST, diff --git a/js/apps/system/aardvark/frontend/js/bootstrap/errors.js b/js/apps/system/aardvark/frontend/js/bootstrap/errors.js index b63ede088a..f2e991c0fa 100644 --- a/js/apps/system/aardvark/frontend/js/bootstrap/errors.js +++ b/js/apps/system/aardvark/frontend/js/bootstrap/errors.js @@ -144,6 +144,7 @@ "ERROR_CLUSTER_ONLY_ON_COORDINATOR" : { "code" : 1471, "message" : "this operation is only valid on a coordinator in a cluster" }, "ERROR_CLUSTER_READING_PLAN_AGENCY" : { "code" : 1472, "message" : "error reading Plan in agency" }, "ERROR_CLUSTER_COULD_NOT_TRUNCATE_COLLECTION" : { "code" : 1473, "message" : "could not truncate collection" }, + "ERROR_CLUSTER_AQL_COMMUNICATION" : { "code" : 1474, "message" : "error in cluster internal communication for AQL" }, "ERROR_QUERY_KILLED" : { "code" : 1500, "message" : "query killed" }, "ERROR_QUERY_PARSE" : { "code" : 1501, "message" : "%s" }, "ERROR_QUERY_EMPTY" : { "code" : 1502, "message" : "query is empty" }, diff --git a/js/common/bootstrap/errors.js b/js/common/bootstrap/errors.js index b63ede088a..f2e991c0fa 100644 --- a/js/common/bootstrap/errors.js +++ b/js/common/bootstrap/errors.js @@ -144,6 +144,7 @@ "ERROR_CLUSTER_ONLY_ON_COORDINATOR" : { "code" : 1471, "message" : "this operation is only valid on a coordinator in a cluster" }, "ERROR_CLUSTER_READING_PLAN_AGENCY" : { "code" : 1472, "message" : "error reading Plan in agency" }, "ERROR_CLUSTER_COULD_NOT_TRUNCATE_COLLECTION" : { "code" : 1473, "message" : "could not truncate collection" }, + "ERROR_CLUSTER_AQL_COMMUNICATION" : { "code" : 1474, "message" : "error in cluster internal communication for AQL" }, "ERROR_QUERY_KILLED" : { "code" : 1500, "message" : "query killed" }, "ERROR_QUERY_PARSE" : { "code" : 1501, "message" : "%s" }, "ERROR_QUERY_EMPTY" : { "code" : 1502, "message" : "query is empty" }, diff --git a/js/server/modules/org/arangodb/testing.js b/js/server/modules/org/arangodb/testing.js index 32ff0ee3db..06bd5f0f7f 100644 --- a/js/server/modules/org/arangodb/testing.js +++ b/js/server/modules/org/arangodb/testing.js @@ -86,6 +86,7 @@ var _ = require("underscore"); var testFuncs = {}; var print = require("internal").print; +var time = require("internal").time; var fs = require("fs"); var download = require("internal").download; var wait = require("internal").wait; @@ -398,17 +399,34 @@ function runThere (options, instanceInfo, file) { function executeAndWait (cmd, args) { var pid = executeExternal(cmd, args); + var startTime = time(); var res = statusExternal(pid, true); + var deltaTime = time() - startTime; + if (res.status === "TERMINATED") { + print("Finished: " + res.status + " Exitcode: " + res.exit + " Time Elapsed: " + deltaTime); if (res.exit === 0) { - return { status: true, message: "" }; + return { status: true, message: "", duration: deltaTime}; } else { - return { status: false, message: "exit code was " + res.exit}; + return { status: false, message: "exit code was " + res.exit, duration: deltaTime}; } } + else if (res.status === "ABORTED") { + print("Finished: " + res.status + " Signal: " + res.signal + " Time Elapsed: " + deltaTime); + return { + status: false, + message: "irregular termination: " + res.status + " Exit-Signal: " + res.signal, + duration: deltaTime + }; + } else { - return { status: false, message: "irregular termination: " + res.status}; + print("Finished: " + res.status + " Exitcode: " + res.exit + " Time Elapsed: " + deltaTime); + return { + status: false, + message: "irregular termination: " + res.status + " Exit-Code: " + res.exit, + duration: deltaTime + }; } } @@ -671,19 +689,35 @@ function rubyTests (options, ssl) { "--format", "d", "--require", tmpname, fs.join("UnitTests","HttpInterface",n)]; var pid = executeExternal("rspec", args); + var startTime = time(); var r = statusExternal(pid, true); - + var deltaTime = time() - startTime; if (r.status === "TERMINATED") { + print("Finished: " + r.status + " Signal: " + r.exit + " Time Elapsed: " + deltaTime); if (r.exit === 0) { - result[n] = { status: true, message: "" }; + result[n] = { status: true, message: "", duration: deltaTime }; } else { - result[n] = { status: false, message: "exit code was " + r.exit}; + result[n] = { status: false, message: " exit code was " + r.exit, duration: deltaTime}; } } - else { - result[n] = { status: false, message: "irregular termination: " + r.status}; + else if (r.status === "ABORTED") { + print("Finished: " + r.status + " Signal: " + r.exit + " Time Elapsed: " + deltaTime); + result[n] = { + status: false, + message: "irregular termination: " + r.status + " Exit-Signal: " + r.signal, + duration: deltaTime + }; } + else { + print("Finished: " + r.status + " Exit Status: " + r.exit + " Time Elapsed: " + deltaTime); + result[n] = { + status: false, + message: "irregular termination: " + r.status + " Exit-code: " + r.exit, + duration: deltaTime + }; + } + if (r.status === false && !options.force) { break; } diff --git a/lib/Basics/errors.dat b/lib/Basics/errors.dat index 7bcc217d8c..69d75dd296 100755 --- a/lib/Basics/errors.dat +++ b/lib/Basics/errors.dat @@ -180,6 +180,7 @@ ERROR_CLUSTER_UNSUPPORTED,1470,"unsupported operation or parameter","Will be rai ERROR_CLUSTER_ONLY_ON_COORDINATOR,1471,"this operation is only valid on a coordinator in a cluster","Will be raised if there is an attempt to run a coordinator-only operation on a different type of node." ERROR_CLUSTER_READING_PLAN_AGENCY,1472,"error reading Plan in agency","Will be raised if a coordinator or DBserver cannot read the Plan in the agency." ERROR_CLUSTER_COULD_NOT_TRUNCATE_COLLECTION,1473,"could not truncate collection","Will be raised if a coordinator cannot truncate all shards of a cluster collection." +ERROR_CLUSTER_AQL_COMMUNICATION,1474,"error in cluster internal communication for AQL","Will be raised if the internal communication of the cluster for AQL produces an error." ################################################################################ ## ArangoDB query errors diff --git a/lib/Basics/voc-errors.cpp b/lib/Basics/voc-errors.cpp index 1e076bc299..4f4f4439d7 100644 --- a/lib/Basics/voc-errors.cpp +++ b/lib/Basics/voc-errors.cpp @@ -140,6 +140,7 @@ void TRI_InitialiseErrorMessages (void) { REG_ERROR(ERROR_CLUSTER_ONLY_ON_COORDINATOR, "this operation is only valid on a coordinator in a cluster"); REG_ERROR(ERROR_CLUSTER_READING_PLAN_AGENCY, "error reading Plan in agency"); REG_ERROR(ERROR_CLUSTER_COULD_NOT_TRUNCATE_COLLECTION, "could not truncate collection"); + REG_ERROR(ERROR_CLUSTER_AQL_COMMUNICATION, "error in cluster internal communication for AQL"); REG_ERROR(ERROR_QUERY_KILLED, "query killed"); REG_ERROR(ERROR_QUERY_PARSE, "%s"); REG_ERROR(ERROR_QUERY_EMPTY, "query is empty"); diff --git a/lib/Basics/voc-errors.h b/lib/Basics/voc-errors.h index 2588dd6992..e8eff0d72d 100644 --- a/lib/Basics/voc-errors.h +++ b/lib/Basics/voc-errors.h @@ -341,6 +341,9 @@ /// - 1473: @LIT{could not truncate collection} /// Will be raised if a coordinator cannot truncate all shards of a cluster /// collection. +/// - 1474: @LIT{error in cluster internal communication for AQL} +/// Will be raised if the internal communication of the cluster for AQL +/// produces an error. /// - 1500: @LIT{query killed} /// Will be raised when a running query is killed by an explicit admin /// command. @@ -1996,6 +1999,17 @@ void TRI_InitialiseErrorMessages (void); #define TRI_ERROR_CLUSTER_COULD_NOT_TRUNCATE_COLLECTION (1473) +//////////////////////////////////////////////////////////////////////////////// +/// @brief 1474: ERROR_CLUSTER_AQL_COMMUNICATION +/// +/// error in cluster internal communication for AQL +/// +/// Will be raised if the internal communication of the cluster for AQL +/// produces an error. +//////////////////////////////////////////////////////////////////////////////// + +#define TRI_ERROR_CLUSTER_AQL_COMMUNICATION (1474) + //////////////////////////////////////////////////////////////////////////////// /// @brief 1500: ERROR_QUERY_KILLED /// diff --git a/scripts/unittest.js b/scripts/unittest.js index c60623c548..746c99abed 100644 --- a/scripts/unittest.js +++ b/scripts/unittest.js @@ -31,7 +31,9 @@ function resultsToXml(results, baseName) { .text(xmlEscape(String(attrs[a]))).text("\""); } - close && this.text("/"); + if (close) { + this.text("/"); + } this.text(">\n"); return this; @@ -64,6 +66,13 @@ function resultsToXml(results, baseName) { } } + if ((!results[testrun][test].status) && + results[testrun][test].hasOwnProperty('message')) + { + xml.elem("failure"); + xml.text('\n'); + xml.elem("/failure"); + } xml.elem("/testsuite"); var fn = baseName + testrun.replace(/\//g, '_') + '_' + test.replace(/\//g, '_') + ".xml"; //print('Writing: '+ fn);