mirror of https://gitee.com/bigwinds/arangodb
458 lines
15 KiB
C++
458 lines
15 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ätzer
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "Databases.h"
|
|
#include "Basics/Common.h"
|
|
|
|
#include "Agency/AgencyComm.h"
|
|
#include "ApplicationFeatures/ApplicationServer.h"
|
|
#include "Basics/StaticStrings.h"
|
|
#include "Basics/StringUtils.h"
|
|
#include "Basics/VelocyPackHelper.h"
|
|
#include "Cluster/ClusterFeature.h"
|
|
#include "Cluster/ClusterInfo.h"
|
|
#include "Cluster/ServerState.h"
|
|
#include "GeneralServer/AuthenticationFeature.h"
|
|
#include "Logger/LogMacros.h"
|
|
#include "Logger/Logger.h"
|
|
#include "Logger/LoggerStream.h"
|
|
#include "RestServer/DatabaseFeature.h"
|
|
#include "RestServer/SystemDatabaseFeature.h"
|
|
#include "Sharding/ShardingInfo.h"
|
|
#include "Utils/Events.h"
|
|
#include "Utils/ExecContext.h"
|
|
#include "V8/JavaScriptSecurityContext.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 <chrono>
|
|
#include <thread>
|
|
|
|
#include <v8.h>
|
|
#include <velocypack/Builder.h>
|
|
#include <velocypack/Iterator.h>
|
|
#include <velocypack/Slice.h>
|
|
#include <velocypack/velocypack-aliases.h>
|
|
|
|
using namespace arangodb;
|
|
using namespace arangodb::methods;
|
|
using namespace arangodb::velocypack;
|
|
|
|
TRI_vocbase_t* Databases::lookup(std::string const& dbname) {
|
|
if (DatabaseFeature::DATABASE != nullptr) {
|
|
return DatabaseFeature::DATABASE->lookupDatabase(dbname);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::vector<std::string> Databases::list(std::string const& user) {
|
|
auto& server = application_features::ApplicationServer::server();
|
|
if (!server.hasFeature<DatabaseFeature>()) {
|
|
return std::vector<std::string>();
|
|
}
|
|
DatabaseFeature& databaseFeature = server.getFeature<DatabaseFeature>();
|
|
|
|
if (user.empty()) {
|
|
if (ServerState::instance()->isCoordinator()) {
|
|
ClusterInfo& ci = server.getFeature<ClusterFeature>().clusterInfo();
|
|
return ci.databases(true);
|
|
} else {
|
|
// list of all databases
|
|
return databaseFeature.getDatabaseNames();
|
|
}
|
|
} else {
|
|
// slow path for user case
|
|
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("87642", TRACE, Logger::COMMUNICATION)
|
|
<< "rest database handler: no agency communication";
|
|
return Result(commRes.errorCode(), commRes.errorMessage());
|
|
}
|
|
|
|
VPackSlice value = commRes.slice()[0].get<std::string>(
|
|
{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();
|
|
}
|
|
|
|
// Grant permissions on newly created database to current user
|
|
// to be able to run the upgrade script
|
|
arangodb::Result Databases::grantCurrentUser(CreateDatabaseInfo const& info, int64_t timeout) {
|
|
auth::UserManager* um = AuthenticationFeature::instance()->userManager();
|
|
|
|
Result res;
|
|
|
|
if (um != nullptr) {
|
|
ExecContext const& exec = ExecContext::current();
|
|
// If the current user is empty (which happens if a Maintenance job
|
|
// called us, or when authentication is off), granting rights
|
|
// will fail. We hence ignore it here, but issue a warning below
|
|
if (!exec.isAdminUser()) {
|
|
auto const endTime = std::chrono::steady_clock::now() + std::chrono::seconds(timeout);
|
|
while (true) {
|
|
res = um->updateUser(exec.user(), [&](auth::User& entry) {
|
|
entry.grantDatabase(info.getName(), auth::Level::RW);
|
|
entry.grantCollection(info.getName(), "*", auth::Level::RW);
|
|
return TRI_ERROR_NO_ERROR;
|
|
});
|
|
if (res.ok() ||
|
|
!res.is(TRI_ERROR_ARANGO_CONFLICT) ||
|
|
std::chrono::steady_clock::now() > endTime) {
|
|
break;
|
|
}
|
|
|
|
if (info.server().isStopping()) {
|
|
res.reset(TRI_ERROR_SHUTTING_DOWN);
|
|
break;
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
}
|
|
}
|
|
|
|
LOG_TOPIC("2a4dd", DEBUG, Logger::FIXME)
|
|
<< "current ExecContext's user() is empty. "
|
|
<< "Database will be created without any user having permissions";
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
// Create database on cluster;
|
|
Result Databases::createCoordinator(CreateDatabaseInfo const& info) {
|
|
TRI_ASSERT(ServerState::instance()->isCoordinator());
|
|
|
|
if (!TRI_vocbase_t::IsAllowedName(/*_isSystemDB*/ false, arangodb::velocypack::StringRef(info.getName()))) {
|
|
return Result(TRI_ERROR_ARANGO_DATABASE_NAME_INVALID);
|
|
}
|
|
|
|
// This operation enters the database as isBuilding into the agency
|
|
// while the database is still building it is not visible.
|
|
ClusterInfo& ci = info.server().getFeature<ClusterFeature>().clusterInfo();
|
|
Result res = ci.createIsBuildingDatabaseCoordinator(info);
|
|
|
|
// Even entering the database as building failed; This can happen
|
|
// because a database with this name already exists, or because we could
|
|
// not write to Plan/ in the agency
|
|
if (!res.ok()) {
|
|
events::CreateDatabase(info.getName(), res.errorNumber());
|
|
return res;
|
|
}
|
|
|
|
auto failureGuard = scopeGuard([&ci, info]() {
|
|
LOG_TOPIC("8cc61", ERR, Logger::FIXME)
|
|
<< "Failed to create database '" << info.getName() << "', rolling back.";
|
|
Result res = ci.cancelCreateDatabaseCoordinator(info);
|
|
if (!res.ok()) {
|
|
// this cannot happen since cancelCreateDatabaseCoordinator keeps retrying
|
|
// indefinitely until the cancellation is either successful or the cluster
|
|
// is shut down.
|
|
LOG_TOPIC("92157", ERR, Logger::FIXME)
|
|
<< "Failed to rollback creation of database '" << info.getName() <<
|
|
"'. Cleanup will happen through a supervision job.";
|
|
}
|
|
});
|
|
|
|
res = grantCurrentUser(info, 5);
|
|
if (!res.ok()) {
|
|
return res;
|
|
}
|
|
|
|
// This vocbase is needed for the call to methods::Upgrade::createDB, but
|
|
// is just a placeholder
|
|
CreateDatabaseInfo tmp_info = info;
|
|
TRI_vocbase_t vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, std::move(tmp_info));
|
|
|
|
// Now create *all* system collections for the database,
|
|
// if any of these fail, database creation is considered unsuccessful
|
|
|
|
VPackBuilder userBuilder;
|
|
info.UsersToVelocyPack(userBuilder);
|
|
UpgradeResult upgradeRes = methods::Upgrade::createDB(vocbase, userBuilder.slice());
|
|
failureGuard.cancel();
|
|
|
|
// If the creation of system collections was successful,
|
|
// make the database visible, otherwise clean up what we can.
|
|
if (upgradeRes.ok()) {
|
|
return ci.createFinalizeDatabaseCoordinator(info);
|
|
}
|
|
|
|
// We leave this handling here to be able to capture
|
|
// error messages and return
|
|
// Cleanup entries in agency.
|
|
res = ci.cancelCreateDatabaseCoordinator(info);
|
|
if (!res.ok()) {
|
|
// this should never happen as cancelCreateDatabaseCoordinator keeps retrying
|
|
// until either cancellation is successful or the cluster is shut down.
|
|
return res;
|
|
}
|
|
|
|
return std::move(upgradeRes.result());
|
|
}
|
|
|
|
// Create a database on SingleServer, DBServer,
|
|
Result Databases::createOther(CreateDatabaseInfo const& info) {
|
|
// Without the database feature, we can't create a database
|
|
if (!info.server().hasFeature<DatabaseFeature>()) {
|
|
events::CreateDatabase(info.getName(), TRI_ERROR_INTERNAL);
|
|
return {TRI_ERROR_INTERNAL};
|
|
}
|
|
DatabaseFeature& databaseFeature = info.server().getFeature<DatabaseFeature>();
|
|
|
|
TRI_vocbase_t* vocbase = nullptr;
|
|
auto tmp_info = info;
|
|
Result createResult = databaseFeature.createDatabase(std::move(tmp_info), vocbase);
|
|
if (createResult.fail()) {
|
|
return createResult;
|
|
}
|
|
|
|
TRI_ASSERT(vocbase != nullptr);
|
|
TRI_ASSERT(!vocbase->isDangling());
|
|
|
|
TRI_DEFER(vocbase->release());
|
|
|
|
Result res = grantCurrentUser(info, 10);
|
|
if (!res.ok()) {
|
|
return res;
|
|
}
|
|
|
|
VPackBuilder userBuilder;
|
|
info.UsersToVelocyPack(userBuilder);
|
|
UpgradeResult upgradeRes = methods::Upgrade::createDB(*vocbase, userBuilder.slice());
|
|
|
|
return std::move(upgradeRes.result());
|
|
}
|
|
|
|
arangodb::Result Databases::create(application_features::ApplicationServer& server,
|
|
std::string const& dbName, VPackSlice const& users,
|
|
VPackSlice const& options) {
|
|
arangodb::Result res;
|
|
|
|
// Only admin users are permitted to create databases
|
|
ExecContext const& exec = ExecContext::current();
|
|
|
|
if (!exec.isAdminUser()) {
|
|
events::CreateDatabase(dbName, TRI_ERROR_FORBIDDEN);
|
|
return Result(TRI_ERROR_FORBIDDEN);
|
|
}
|
|
|
|
CreateDatabaseInfo createInfo(server);
|
|
res = createInfo.load(dbName, options, users);
|
|
|
|
if (!res.ok()) {
|
|
LOG_TOPIC("15580", ERR, Logger::FIXME)
|
|
<< "Could not create database: " << res.errorMessage();
|
|
events::CreateDatabase(dbName, res.errorNumber());
|
|
return res;
|
|
}
|
|
|
|
if (ServerState::instance()->isCoordinator() /* REVIEW! && !localDatabase*/) {
|
|
if (!createInfo.validId()) {
|
|
auto& clusterInfo = server.getFeature<ClusterFeature>().clusterInfo();
|
|
createInfo.setId(clusterInfo.uniqid());
|
|
}
|
|
|
|
res = ShardingInfo::validateShardsAndReplicationFactor(options, server);
|
|
if (res.ok()) {
|
|
res = createCoordinator(createInfo);
|
|
}
|
|
|
|
} else { // Single, DBServer, Agency
|
|
if (!createInfo.validId()) {
|
|
createInfo.setId(TRI_NewTickServer());
|
|
}
|
|
res = createOther(createInfo);
|
|
}
|
|
|
|
if (res.fail()) {
|
|
LOG_TOPIC("1964a", ERR, Logger::FIXME)
|
|
<< "Could not create database: " << res.errorMessage();
|
|
return res;
|
|
}
|
|
|
|
// Invalidate Foxx Queue database cache. We do not care if this fails,
|
|
// because the cache entry has a TTL
|
|
if (ServerState::instance()->isSingleServerOrCoordinator()) {
|
|
try {
|
|
auto& server = application_features::ApplicationServer::server();
|
|
auto& sysDbFeature = server.getFeature<arangodb::SystemDatabaseFeature>();
|
|
auto database = sysDbFeature.use();
|
|
|
|
TRI_ExpireFoxxQueueDatabaseCache(database.get());
|
|
} catch (...) {
|
|
}
|
|
}
|
|
|
|
return Result();
|
|
}
|
|
|
|
namespace {
|
|
int dropDBCoordinator(std::string const& dbName) {
|
|
// Arguments are already checked, there is exactly one argument
|
|
DatabaseFeature* databaseFeature = DatabaseFeature::DATABASE;
|
|
TRI_vocbase_t* vocbase = databaseFeature->useDatabase(dbName);
|
|
|
|
if (vocbase == nullptr) {
|
|
events::DropDatabase(dbName, TRI_ERROR_ARANGO_DATABASE_NOT_FOUND);
|
|
return TRI_ERROR_ARANGO_DATABASE_NOT_FOUND;
|
|
}
|
|
|
|
TRI_voc_tick_t const id = vocbase->id();
|
|
|
|
vocbase->release();
|
|
|
|
ClusterInfo& ci = vocbase->server().getFeature<ClusterFeature>().clusterInfo();
|
|
auto res = ci.dropDatabaseCoordinator(dbName, 120.0);
|
|
|
|
if (!res.ok()) {
|
|
events::DropDatabase(dbName, res.errorNumber());
|
|
return res.errorNumber();
|
|
}
|
|
|
|
// now wait for heartbeat thread to drop the database object
|
|
int tries = 0;
|
|
|
|
while (++tries <= 6000) {
|
|
TRI_vocbase_t* vocbase = databaseFeature->useDatabase(id);
|
|
|
|
if (vocbase == nullptr) {
|
|
// object has vanished
|
|
break;
|
|
}
|
|
|
|
vocbase->release();
|
|
// sleep
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
}
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
const std::string dropError = "Error when dropping Datbase";
|
|
} // namespace
|
|
|
|
arangodb::Result Databases::drop(TRI_vocbase_t* systemVocbase, std::string const& dbName) {
|
|
TRI_ASSERT(systemVocbase->isSystem());
|
|
ExecContext const& exec = ExecContext::current();
|
|
if (exec.systemAuthLevel() != auth::Level::RW) {
|
|
events::DropDatabase(dbName, TRI_ERROR_FORBIDDEN);
|
|
return TRI_ERROR_FORBIDDEN;
|
|
}
|
|
|
|
Result res;
|
|
V8DealerFeature* dealer = V8DealerFeature::DEALER;
|
|
if (dealer != nullptr && dealer->isEnabled()) {
|
|
try {
|
|
JavaScriptSecurityContext securityContext =
|
|
JavaScriptSecurityContext::createInternalContext();
|
|
|
|
V8ContextGuard guard(systemVocbase, securityContext);
|
|
v8::Isolate* isolate = guard.isolate();
|
|
|
|
v8::HandleScope scope(isolate);
|
|
|
|
// clear collections in cache object
|
|
TRI_ClearObjectCacheV8(isolate);
|
|
|
|
if (ServerState::instance()->isCoordinator()) {
|
|
// If we are a coordinator in a cluster, we have to behave differently:
|
|
res = ::dropDBCoordinator(dbName);
|
|
} else {
|
|
res = DatabaseFeature::DATABASE->dropDatabase(dbName, false, true);
|
|
|
|
if (res.fail()) {
|
|
events::DropDatabase(dbName, res.errorNumber());
|
|
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);
|
|
V8DealerFeature::DEALER->addGlobalContextMethod("reloadRouting");
|
|
}
|
|
} catch (arangodb::basics::Exception const& ex) {
|
|
events::DropDatabase(dbName, TRI_ERROR_INTERNAL);
|
|
return Result(ex.code(), dropError + ex.message());
|
|
} catch (std::exception const& ex) {
|
|
events::DropDatabase(dbName, TRI_ERROR_INTERNAL);
|
|
return Result(TRI_ERROR_INTERNAL, dropError + ex.what());
|
|
} catch (...) {
|
|
events::DropDatabase(dbName, TRI_ERROR_INTERNAL);
|
|
return Result(TRI_ERROR_INTERNAL, dropError);
|
|
}
|
|
} else {
|
|
if (ServerState::instance()->isCoordinator()) {
|
|
// If we are a coordinator in a cluster, we have to behave differently:
|
|
res = ::dropDBCoordinator(dbName);
|
|
} else {
|
|
res = DatabaseFeature::DATABASE->dropDatabase(dbName, false, true);
|
|
}
|
|
}
|
|
|
|
auth::UserManager* um = AuthenticationFeature::instance()->userManager();
|
|
if (res.ok() && um != nullptr) {
|
|
auto cb = [&](auth::User& entry) -> bool {
|
|
return entry.removeDatabase(dbName);
|
|
};
|
|
res = um->enumerateUsers(cb, /*retryOnConflict*/ true);
|
|
}
|
|
|
|
return res;
|
|
}
|