1
0
Fork 0
arangodb/arangod/VocBase/Methods/Databases.cpp

421 lines
14 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2017 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 Simon Gräter
////////////////////////////////////////////////////////////////////////////////
#include "Databases.h"
#include "Basics/Common.h"
#include "Agency/AgencyComm.h"
#include "Basics/StringUtils.h"
#include "Basics/VelocyPackHelper.h"
#include "Cluster/ClusterInfo.h"
#include "Cluster/ServerState.h"
#include "GeneralServer/AuthenticationFeature.h"
#include "Rest/HttpRequest.h"
#include "RestServer/DatabaseFeature.h"
#include "Utils/ExecContext.h"
#include "V8/v8-utils.h"
#include "V8/v8-vpack.h"
#include "V8Server/V8Context.h"
#include "V8Server/V8DealerFeature.h"
#include "V8Server/v8-dispatcher.h"
#include "V8Server/v8-user-structures.h"
#include "VocBase/Methods/Upgrade.h"
#include "VocBase/vocbase.h"
#include <v8.h>
#include <velocypack/Builder.h>
#include <velocypack/Iterator.h>
#include <velocypack/velocypack-aliases.h>
using namespace arangodb;
using namespace arangodb::methods;
TRI_vocbase_t* Databases::lookup(std::string const& dbname) {
if (DatabaseFeature::DATABASE != nullptr) {
if (ServerState::instance()->isCoordinator()) {
return DatabaseFeature::DATABASE->lookupDatabaseCoordinator(dbname);
}
return DatabaseFeature::DATABASE->lookupDatabase(dbname);
}
return nullptr;
}
std::vector<std::string> Databases::list(std::string const& user) {
DatabaseFeature* databaseFeature =
application_features::ApplicationServer::getFeature<DatabaseFeature>(
"Database");
if (databaseFeature == nullptr) {
return std::vector<std::string>();
}
if (user.empty()) {
if (ServerState::instance()->isCoordinator()) {
ClusterInfo* ci = ClusterInfo::instance();
return ci->databases(true);
} else {
// list of all databases
return databaseFeature->getDatabaseNames();
}
} else {
// slow path for user case
if (ServerState::instance()->isCoordinator()) {
AuthenticationFeature* af = AuthenticationFeature::instance();
auth::UserManager* um = af->userManager();
std::vector<std::string> names;
std::vector<std::string> dbs =
databaseFeature->getDatabaseNamesCoordinator();
for (std::string const& db : dbs) {
if (!af->isActive() || (um != nullptr &&
um->databaseAuthLevel(user, db) > auth::Level::NONE)) {
names.push_back(db);
}
}
return names;
} else {
return databaseFeature->getDatabaseNamesForUser(user);
}
}
}
arangodb::Result Databases::info(TRI_vocbase_t* vocbase, VPackBuilder& result) {
if (ServerState::instance()->isCoordinator()) {
AgencyComm agency;
AgencyCommResult commRes =
agency.getValues("Plan/Databases/" + vocbase->name());
if (!commRes.successful()) {
// Error in communication, note that value not found is not an error
LOG_TOPIC(TRACE, Logger::REQUESTS)
<< "rest database handler: no agency communication";
return Result(commRes.errorCode(), commRes.errorMessage());
}
VPackSlice value = commRes.slice()[0].get(
{AgencyCommManager::path(), "Plan", "Databases", vocbase->name()});
if (value.isObject() && value.hasKey("name")) {
VPackValueLength l = 0;
const char* name = value.get("name").getString(l);
TRI_ASSERT(l > 0);
VPackObjectBuilder b(&result);
result.add("name", value.get("name"));
if (value.get("id").isString()) {
result.add("id", value.get("id"));
} else if (value.get("id").isNumber()) {
result.add("id", VPackValue(std::to_string(value.get("id").getUInt())));
} else {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,
"unexpected type for 'id' attribute");
}
result.add("path", VPackValue("none"));
result.add("isSystem", VPackValue(name[0] == '_'));
}
} else {
VPackObjectBuilder b(&result);
result.add("name", VPackValue(vocbase->name()));
result.add("id", VPackValue(std::to_string(vocbase->id())));
result.add("path", VPackValue(vocbase->path()));
result.add("isSystem", VPackValue(vocbase->isSystem()));
}
return Result();
}
arangodb::Result Databases::create(std::string const& dbName,
VPackSlice const& inUsers,
VPackSlice const& inOptions) {
auth::UserManager* um = AuthenticationFeature::instance()->userManager();
ExecContext const* exec = ExecContext::CURRENT;
if (exec != nullptr) {
if (!exec->isAdminUser()) {
return TRI_ERROR_FORBIDDEN;
} else if (!exec->isSuperuser() && !ServerState::writeOpsEnabled()) {
return Result(TRI_ERROR_ARANGO_READ_ONLY, "server is in read-only mode");
}
}
VPackSlice options = inOptions;
if (options.isNone() || options.isNull()) {
options = VPackSlice::emptyObjectSlice();
} else if (!options.isObject()) {
return Result(TRI_ERROR_HTTP_BAD_PARAMETER, "invalid options slice");
}
VPackSlice users = inUsers;
if (users.isNone() || users.isNull()) {
users = VPackSlice::emptyArraySlice();
} else if (!users.isArray()) {
return Result(TRI_ERROR_HTTP_BAD_PARAMETER, "invalid users slice");
}
VPackBuilder sanitizedUsers;
sanitizedUsers.openArray();
for (VPackSlice const& user : VPackArrayIterator(users)) {
sanitizedUsers.openObject();
if (!user.isObject()) {
return Result(TRI_ERROR_HTTP_BAD_PARAMETER);
}
VPackSlice name;
if (user.hasKey("username")) {
name = user.get("username");
} else if (user.hasKey("user")) {
name = user.get("user");
}
if (!name.isString()) { // empty names are silently ignored later
return Result(TRI_ERROR_HTTP_BAD_PARAMETER);
}
sanitizedUsers.add("username", name);
if (user.hasKey("passwd")) {
VPackSlice passwd = user.get("passwd");
if (!passwd.isString()) {
return Result(TRI_ERROR_HTTP_BAD_PARAMETER);
}
sanitizedUsers.add("passwd", passwd);
} else {
sanitizedUsers.add("passwd", VPackValue(""));
}
VPackSlice active = user.get("active");
if (!active.isBool()) {
sanitizedUsers.add("active", VPackValue(true));
} else {
sanitizedUsers.add("active", active);
}
VPackSlice extra = user.get("extra");
if (extra.isObject()) {
sanitizedUsers.add("extra", extra);
}
sanitizedUsers.close();
}
sanitizedUsers.close();
DatabaseFeature* databaseFeature = DatabaseFeature::DATABASE;
if (databaseFeature == nullptr) {
return Result(TRI_ERROR_INTERNAL);
}
UpgradeResult upgradeRes;
if (ServerState::instance()->isCoordinator()) {
if (!TRI_vocbase_t::IsAllowedName(false, arangodb::velocypack::StringRef(dbName))) {
return Result(TRI_ERROR_ARANGO_DATABASE_NAME_INVALID);
}
uint64_t const id = ClusterInfo::instance()->uniqid();
VPackBuilder builder;
try {
VPackObjectBuilder b(&builder);
std::string const idString(basics::StringUtils::itoa(id));
builder.add("id", VPackValue(idString));
builder.add("name", VPackValue(dbName));
builder.add("options", options);
builder.add("coordinator", VPackValue(ServerState::instance()->getId()));
} catch (VPackException const& e) {
return Result(e.errorCode());
}
ClusterInfo* ci = ClusterInfo::instance();
std::string errorMsg;
int res =
ci->createDatabaseCoordinator(dbName, builder.slice(), errorMsg, 120.0);
if (res != TRI_ERROR_NO_ERROR) {
return Result(res);
}
// database was created successfully in agency
// now wait for heartbeat thread to create the database object
TRI_vocbase_t* vocbase = nullptr;
int tries = 0;
while (++tries <= 6000) {
vocbase = databaseFeature->useDatabaseCoordinator(id);
if (vocbase != nullptr) {
break;
}
// sleep
std::this_thread::sleep_for(std::chrono::microseconds(10000));
}
if (vocbase == nullptr) {
return Result(TRI_ERROR_INTERNAL, "unable to find database");
}
TRI_DEFER(vocbase->release());
TRI_ASSERT(vocbase->id() == id);
TRI_ASSERT(vocbase->name() == dbName);
// we need to add the permissions before running the upgrade script
if (ExecContext::CURRENT != nullptr && um != nullptr) {
// ignore errors here Result r =
um->updateUser(
ExecContext::CURRENT->user(), [&](auth::User& entry) {
entry.grantDatabase(dbName, auth::Level::RW);
entry.grantCollection(dbName, "*", auth::Level::RW);
return TRI_ERROR_NO_ERROR;
});
}
TRI_ASSERT(sanitizedUsers.slice().isArray());
upgradeRes = methods::Upgrade::createDB(vocbase, sanitizedUsers.slice());
} else { // Single, DBServer, Agency
// options for database (currently only allows setting "id"
// for testing purposes)
TRI_voc_tick_t id = 0;
if (options.hasKey("id")) {
id = basics::VelocyPackHelper::stringUInt64(options, "id");
}
TRI_vocbase_t* vocbase = nullptr;
int res = databaseFeature->createDatabase(id, dbName, vocbase);
if (res != TRI_ERROR_NO_ERROR) {
return Result(res);
}
TRI_ASSERT(vocbase != nullptr);
TRI_ASSERT(!vocbase->isDangling());
TRI_DEFER(vocbase->release());
// we need to add the permissions before running the upgrade script
if (ExecContext::CURRENT != nullptr && um != nullptr) {
// ignore errors here Result r =
um->updateUser(
ExecContext::CURRENT->user(), [&](auth::User& entry) {
entry.grantDatabase(dbName, auth::Level::RW);
entry.grantCollection(dbName, "*", auth::Level::RW);
return TRI_ERROR_NO_ERROR;
});
}
upgradeRes = methods::Upgrade::createDB(vocbase, sanitizedUsers.slice());
}
if (upgradeRes.fail()) {
LOG_TOPIC(ERR, Logger::FIXME) << "Could not create database "
<< upgradeRes.errorMessage();
return upgradeRes;
}
// Entirely Foxx related:
if (ServerState::instance()->isSingleServerOrCoordinator()) {
try {
TRI_ExpireFoxxQueueDatabaseCache(databaseFeature->systemDatabase());
} catch(...) {
// it is of no real importance if cache invalidation fails, because
// the cache entry has a ttl
}
}
return Result();
}
arangodb::Result Databases::drop(TRI_vocbase_t* systemVocbase,
std::string const& dbName) {
TRI_ASSERT(systemVocbase->isSystem());
ExecContext const* exec = ExecContext::CURRENT;
if (exec != nullptr) {
if (exec->systemAuthLevel() != auth::Level::RW) {
return TRI_ERROR_FORBIDDEN;
} else if (!exec->isSuperuser() && !ServerState::writeOpsEnabled()) {
return Result(TRI_ERROR_ARANGO_READ_ONLY, "server is in read-only mode");
}
}
auto ctx = V8DealerFeature::DEALER->enterContext(systemVocbase, true);
if (ctx == nullptr) {
return Result(TRI_ERROR_INTERNAL, "Could not get v8 context");
}
TRI_DEFER(V8DealerFeature::DEALER->exitContext(ctx));
v8::Isolate* isolate = ctx->_isolate;
v8::HandleScope scope(isolate);
// clear collections in cache object
TRI_ClearObjectCacheV8(isolate);
// If we are a coordinator in a cluster, we have to behave differently:
if (ServerState::instance()->isCoordinator()) {
// Arguments are already checked, there is exactly one argument
DatabaseFeature* databaseFeature = DatabaseFeature::DATABASE;
TRI_vocbase_t* vocbase = databaseFeature->useDatabaseCoordinator(dbName);
if (vocbase == nullptr) {
return Result(TRI_ERROR_ARANGO_DATABASE_NOT_FOUND);
}
TRI_voc_tick_t const id = vocbase->id();
vocbase->release();
ClusterInfo* ci = ClusterInfo::instance();
std::string errorMsg;
int res = ci->dropDatabaseCoordinator(dbName, errorMsg, 120.0);
if (res != TRI_ERROR_NO_ERROR) {
return Result(res);
}
// now wait for heartbeat thread to drop the database object
int tries = 0;
while (++tries <= 6000) {
TRI_vocbase_t* vocbase = databaseFeature->useDatabaseCoordinator(id);
if (vocbase == nullptr) {
// object has vanished
break;
}
vocbase->release();
// sleep
std::this_thread::sleep_for(std::chrono::microseconds(10000));
}
} else {
int res = DatabaseFeature::DATABASE->dropDatabase(dbName, false, true);
if (res != TRI_ERROR_NO_ERROR) {
return Result(res);
}
TRI_RemoveDatabaseTasksV8Dispatcher(dbName);
// run the garbage collection in case the database held some objects which
// can now be freed
TRI_RunGarbageCollectionV8(isolate, 0.25);
TRI_ExecuteJavaScriptString(
isolate, isolate->GetCurrentContext(),
TRI_V8_ASCII_STRING(isolate,
"require('internal').executeGlobalContextFunction('"
"reloadRouting')"),
TRI_V8_ASCII_STRING(isolate, "reload routing"), false);
}
Result res;
auth::UserManager* um = AuthenticationFeature::instance()->userManager();
if (um != nullptr) {
res = um->enumerateUsers([&](auth::User& entry) -> bool {
return entry.removeDatabase(dbName);
});
}
return res;
}