//////////////////////////////////////////////////////////////////////////////// /// DISCLAIMER /// /// Copyright 2014-2016 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 Dr. Frank Celler /// @author Jan Steemann //////////////////////////////////////////////////////////////////////////////// #include "v8-dispatcher.h" #include #include #include "Basics/StringUtils.h" #include "Basics/tri-strings.h" #include "Cluster/ServerState.h" #include "Logger/Logger.h" #include "Scheduler/Scheduler.h" #include "Scheduler/SchedulerFeature.h" #include "Transaction/Hints.h" #include "Transaction/V8Context.h" #include "Utils/DatabaseGuard.h" #include "Utils/ExecContext.h" #include "Utils/OperationOptions.h" #include "Utils/SingleCollectionTransaction.h" #include "V8/v8-conv.h" #include "V8/v8-utils.h" #include "V8/v8-vpack.h" #include "V8Server/V8Context.h" #include "V8Server/V8DealerFeature.h" #include "VocBase/AccessMode.h" #include "VocBase/Methods/Tasks.h" #include "VocBase/ticks.h" #include "VocBase/vocbase.h" using namespace arangodb; using namespace arangodb::basics; using namespace arangodb::rest; // ----------------------------------------------------------------------------- // --SECTION-- private helper // functions // ----------------------------------------------------------------------------- static std::string GetTaskId(v8::Isolate* isolate, v8::Handle arg) { v8::Local context = isolate->GetCurrentContext(); if (arg->IsObject()) { // extract "id" from object v8::Handle obj = arg.As(); if (TRI_HasProperty(context, isolate, obj, "id")) { return TRI_ObjectToString(isolate, obj->Get(TRI_IGETC, TRI_V8_ASCII_STRING(isolate, "id")) .FromMaybe(v8::Local())); } } return TRI_ObjectToString(isolate, arg); } // ----------------------------------------------------------------------------- // --SECTION-- Javascript functions // ----------------------------------------------------------------------------- static void JS_RegisterTask(v8::FunctionCallbackInfo const& args) { using arangodb::Task; TRI_V8_TRY_CATCH_BEGIN(isolate); v8::Local context = isolate->GetCurrentContext(); v8::HandleScope scope(isolate); if (SchedulerFeature::SCHEDULER == nullptr) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "no scheduler found"); } else if (application_features::ApplicationServer::isStopping()) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_SHUTTING_DOWN); } if (args.Length() != 1 || !args[0]->IsObject()) { TRI_V8_THROW_EXCEPTION_USAGE("register()"); } TRI_GET_GLOBALS(); ExecContext const* exec = ExecContext::CURRENT; if (exec != nullptr) { if (exec->databaseAuthLevel() != auth::Level::RW) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_FORBIDDEN, "registerTask() needs db RW permissions"); } } v8::Handle obj = args[0].As(); // job id std::string id; if (TRI_HasProperty(context, isolate, obj, "id")) { // user-specified id id = TRI_ObjectToString(isolate, obj->Get(TRI_IGETC, TRI_V8_ASCII_STRING(isolate, "id")) .FromMaybe(v8::Local())); } else { // auto-generated id id = std::to_string(TRI_NewServerSpecificTick()); } // job name std::string name; if (TRI_HasProperty(context, isolate, obj, "name")) { name = TRI_ObjectToString(isolate, obj->Get(TRI_IGETC, TRI_V8_ASCII_STRING(isolate, "name")) .FromMaybe(v8::Local())); } else { name = "user-defined task"; } bool isSystem = false; if (TRI_HasProperty(context, isolate, obj, "isSystem")) { isSystem = TRI_ObjectToBoolean( isolate, obj->Get(TRI_IGETC, TRI_V8_ASCII_STRING(isolate, "isSystem")) .FromMaybe(v8::Local())); } if(isSystem && !v8g->_securityContext.isInternal()) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_FORBIDDEN, "Only Internal Context may create System Tasks"); } // offset in seconds into period or from now on if no period double offset = 0.0; if (TRI_HasProperty(context, isolate, obj, "offset")) { offset = TRI_ObjectToDouble(isolate, obj->Get(TRI_IGETC, TRI_V8_ASCII_STRING(isolate, "offset")) .FromMaybe(v8::Local())); } // period in seconds & count double period = 0.0; if (TRI_HasProperty(context, isolate, obj, "period")) { period = TRI_ObjectToDouble(isolate, obj->Get(TRI_IGETC, TRI_V8_ASCII_STRING(isolate, "period")) .FromMaybe(v8::Local())); if (period <= 0.0) { TRI_V8_THROW_EXCEPTION_PARAMETER( "task period must be specified and positive"); } } std::string runAsUser; if (TRI_HasProperty(context, isolate, obj, "runAsUser")) { runAsUser = TRI_ObjectToString( isolate, obj->Get(TRI_IGETC, TRI_V8_ASCII_STRING(isolate, "runAsUser")) .FromMaybe(v8::Local())); } // only the superroot is allowed to run tasks as an arbitrary user TRI_ASSERT(exec == ExecContext::CURRENT); if (exec != nullptr) { if (runAsUser.empty()) { // execute task as the same user runAsUser = exec->user(); } else if (exec->user() != runAsUser) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_FORBIDDEN); } } // extract the command if (!TRI_HasProperty(context, isolate, obj, "command")) { TRI_V8_THROW_EXCEPTION_PARAMETER("command must be specified"); } std::string command; if (obj->Get(TRI_IGETC, TRI_V8_ASCII_STRING(isolate, "command")) .FromMaybe(v8::Local()) ->IsFunction()) { // need to add ( and ) around function because call would otherwise break command = "(" + TRI_ObjectToString(isolate, obj->Get(TRI_IGETC, TRI_V8_ASCII_STRING(isolate, "command")) .FromMaybe(v8::Local())) + ")(params)"; } else { command = TRI_ObjectToString( isolate, obj->Get(TRI_IGETC, TRI_V8_ASCII_STRING(isolate, "command")) .FromMaybe(v8::Local())); } if (!Task::tryCompile(isolate, command)) { TRI_V8_THROW_EXCEPTION_PARAMETER("cannot compile command"); } // extract the parameters auto parameters = std::make_shared(); if (TRI_HasProperty(context, isolate, obj, "params")) { int res = TRI_V8ToVPack(isolate, *parameters, obj->Get(TRI_IGETC, TRI_V8_ASCII_STRING(isolate, "params")) .FromMaybe(v8::Local()), false); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_EXCEPTION(res); } } command = "(function (params) { " + command + " } )(params);"; int res; std::shared_ptr task = Task::createTask(id, name, v8g->_vocbase, command, isSystem, res); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_EXCEPTION(res); } // set the user this will run as if (!runAsUser.empty()) { task->setUser(runAsUser); } // set execution parameters task->setParameter(parameters); if (period > 0.0) { // create a new periodic task task->setPeriod(offset, period); } else { // create a run-once timer task task->setOffset(offset); } // get the VelocyPack representation of the task std::shared_ptr builder = task->toVelocyPack(); if (builder == nullptr) { TRI_V8_THROW_EXCEPTION_MEMORY(); } task->start(); v8::Handle result = TRI_VPackToV8(isolate, builder->slice()); TRI_V8_RETURN(result); TRI_V8_TRY_CATCH_END } static void JS_UnregisterTask(v8::FunctionCallbackInfo const& args) { using arangodb::Task; TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() != 1) { TRI_V8_THROW_EXCEPTION_USAGE("unregister()"); } ExecContext const* exec = ExecContext::CURRENT; if (exec != nullptr) { if (exec->databaseAuthLevel() != auth::Level::RW) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_FORBIDDEN, "registerTask() needs db RW permissions"); } } int res = Task::unregisterTask(GetTaskId(isolate, args[0]), true); if (res != TRI_ERROR_NO_ERROR) { TRI_V8_THROW_EXCEPTION(res); } TRI_V8_RETURN_TRUE(); TRI_V8_TRY_CATCH_END } static void JS_GetTask(v8::FunctionCallbackInfo const& args) { using arangodb::Task; TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() > 1) { TRI_V8_THROW_EXCEPTION_USAGE("get()"); } std::shared_ptr builder; if (args.Length() == 1) { // get a single task builder = Task::registeredTask(GetTaskId(isolate, args[0])); } else { // get all tasks builder = Task::registeredTasks(); } if (builder == nullptr) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_TASK_NOT_FOUND); } v8::Handle result = TRI_VPackToV8(isolate, builder->slice()); TRI_V8_RETURN(result); TRI_V8_TRY_CATCH_END } /// creates a new object in _queues, circumvents permission blocks static void JS_CreateQueue(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); TRI_GET_GLOBALS(); TRI_vocbase_t* vocbase = v8g->_vocbase; if (vocbase == nullptr || vocbase->isDropped()) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND); } if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsNumber()) { TRI_V8_THROW_EXCEPTION_USAGE("createQueue(, )"); } std::string runAsUser; ExecContext const* exec = ExecContext::CURRENT; if (exec != nullptr) { if (exec->databaseAuthLevel() != auth::Level::RW) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_FORBIDDEN, "createQueue() needs db RW permissions"); } runAsUser = exec->user(); TRI_ASSERT(exec->isAdminUser() || !runAsUser.empty()); } std::string key = TRI_ObjectToString(isolate, args[0]); uint64_t maxWorkers = std::min(TRI_ObjectToUInt64(isolate, args[1], false), (uint64_t)64); VPackBuilder doc; doc.openObject(); doc.add(StaticStrings::KeyString, VPackValue(key)); doc.add("maxWorkers", VPackValue(maxWorkers)); doc.add("runAsUser", VPackValue(runAsUser)); doc.close(); LOG_TOPIC("aeb56", TRACE, Logger::FIXME) << "Adding queue " << key; ExecContextScope exscope(ExecContext::superuser()); auto ctx = transaction::V8Context::Create(*vocbase, true); SingleCollectionTransaction trx(ctx, "_queues", AccessMode::Type::EXCLUSIVE); Result res = trx.begin(); if (!res.ok()) { TRI_V8_THROW_EXCEPTION(res); } OperationOptions opts; OperationResult result = trx.insert("_queues", doc.slice(), opts); if (result.fail() && result.is(TRI_ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED)) { result = trx.replace("_queues", doc.slice(), opts); } res = trx.finish(result.result); if (!res.ok()) { TRI_V8_THROW_EXCEPTION(res); } TRI_V8_RETURN(v8::Boolean::New(isolate, result.ok())); TRI_V8_TRY_CATCH_END } static void JS_DeleteQueue(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); TRI_GET_GLOBALS(); TRI_vocbase_t* vocbase = v8g->_vocbase; if (vocbase == nullptr || vocbase->isDropped()) { TRI_V8_THROW_EXCEPTION(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND); } if (args.Length() < 1) { TRI_V8_THROW_EXCEPTION_USAGE("deleteQueue()"); } std::string key = TRI_ObjectToString(isolate, args[0]); VPackBuilder doc; doc(VPackValue(VPackValueType::Object))(StaticStrings::KeyString, VPackValue(key))(); ExecContext const* exec = ExecContext::CURRENT; if (exec != nullptr && exec->databaseAuthLevel() != auth::Level::RW) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_ERROR_FORBIDDEN, "deleteQueue() needs db RW permissions"); } LOG_TOPIC("2cef9", TRACE, Logger::FIXME) << "Removing queue " << key; ExecContextScope exscope(ExecContext::superuser()); auto ctx = transaction::V8Context::Create(*vocbase, true); SingleCollectionTransaction trx(ctx, "_queues", AccessMode::Type::WRITE); trx.addHint(transaction::Hints::Hint::SINGLE_OPERATION); Result res = trx.begin(); if (!res.ok()) { TRI_V8_THROW_EXCEPTION(res); } OperationOptions opts; OperationResult result = trx.remove("_queues", doc.slice(), opts); res = trx.finish(result.result); if (!res.ok()) { TRI_V8_THROW_EXCEPTION(res); } TRI_V8_RETURN(v8::Boolean::New(isolate, result.ok())); TRI_V8_TRY_CATCH_END } // ----------------------------------------------------------------------------- // --SECTION-- module initialization // ----------------------------------------------------------------------------- void TRI_InitV8Dispatcher(v8::Isolate* isolate, v8::Handle context) { v8::HandleScope scope(isolate); // _queues is a RO collection and can only be written in C++, as superroot TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "SYS_CREATE_QUEUE"), JS_CreateQueue); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "SYS_DELETE_QUEUE"), JS_DeleteQueue); // we need a scheduler and a dispatcher to define periodic tasks TRI_AddGlobalFunctionVocbase( isolate, TRI_V8_ASCII_STRING(isolate, "SYS_REGISTER_TASK"), JS_RegisterTask); TRI_AddGlobalFunctionVocbase( isolate, TRI_V8_ASCII_STRING(isolate, "SYS_UNREGISTER_TASK"), JS_UnregisterTask); TRI_AddGlobalFunctionVocbase(isolate, TRI_V8_ASCII_STRING(isolate, "SYS_GET_TASK"), JS_GetTask); } void TRI_ShutdownV8Dispatcher() { using arangodb::Task; Task::shutdownTasks(); } void TRI_RemoveDatabaseTasksV8Dispatcher(std::string const& name) { using arangodb::Task; Task::removeTasksForDatabase(name); }