1
0
Fork 0
arangodb/tests/Transaction/Manager-test.cpp

475 lines
17 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// @brief test suite for transaction Manager
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2019 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.
///
/// @author Simon Grätzer
////////////////////////////////////////////////////////////////////////////////
#include "Aql/Query.h"
#include "Transaction/Manager.h"
#include "Transaction/SmartContext.h"
#include "Transaction/StandaloneContext.h"
#include "Transaction/Status.h"
#include "Utils/ExecContext.h"
#include "Utils/SingleCollectionTransaction.h"
#include "VocBase/LogicalCollection.h"
#include <velocypack/Parser.h>
#include <velocypack/velocypack-aliases.h>
#include "gtest/gtest.h"
#include "ManagerSetup.h"
using namespace arangodb;
static arangodb::aql::QueryResult executeQuery(TRI_vocbase_t& vocbase,
std::string const& queryString,
std::shared_ptr<transaction::Context> ctx) {
auto options = std::make_shared<VPackBuilder>();
options->openObject();
options->close();
std::shared_ptr<arangodb::velocypack::Builder> bindVars;
arangodb::aql::Query query(false, vocbase, arangodb::aql::QueryString(queryString),
bindVars, options, arangodb::aql::PART_MAIN);
query.setTransactionContext(std::move(ctx));
std::shared_ptr<arangodb::aql::SharedQueryState> ss = query.sharedState();
arangodb::aql::QueryResult result;
while (true) {
auto state = query.execute(arangodb::QueryRegistryFeature::registry(), result);
if (state == arangodb::aql::ExecutionState::WAITING) {
ss->waitForAsyncResponse();
} else {
break;
}
}
return result;
}
// -----------------------------------------------------------------------------
// --SECTION-- setup / tear-down
// -----------------------------------------------------------------------------
class TransactionManagerTest : public ::testing::Test {
protected:
arangodb::tests::mocks::TransactionManagerSetup setup;
TRI_vocbase_t vocbase;
transaction::Manager* mgr;
TRI_voc_tid_t tid;
TransactionManagerTest()
: vocbase(TRI_vocbase_type_e::TRI_VOCBASE_TYPE_NORMAL, 1, "testVocbase"),
mgr(transaction::ManagerFeature::manager()),
tid(TRI_NewTickServer()) {}
~TransactionManagerTest() { mgr->garbageCollect(true); }
};
// -----------------------------------------------------------------------------
// --SECTION-- test suite
// -----------------------------------------------------------------------------
TEST_F(TransactionManagerTest, parsing_errors) {
auto json = arangodb::velocypack::Parser::fromJson("{ \"write\": [33] }");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
EXPECT_TRUE(res.is(TRI_ERROR_BAD_PARAMETER));
json = arangodb::velocypack::Parser::fromJson(
"{ \"collections\":{\"write\": \"33\"}, \"lockTimeout\": -1 }");
res = mgr->createManagedTrx(vocbase, tid, json->slice());
EXPECT_TRUE(res.is(TRI_ERROR_BAD_PARAMETER));
}
TEST_F(TransactionManagerTest, collection_not_found) {
arangodb::ExecContextScope execContextScope(arangodb::ExecContext::superuser());
auto json = arangodb::velocypack::Parser::fromJson(
"{ \"collections\":{\"read\": [\"33\"]}}");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
EXPECT_TRUE(res.errorNumber() == TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND);
json = arangodb::velocypack::Parser::fromJson(
"{ \"collections\":{\"write\": [\"33\"]}}");
res = mgr->createManagedTrx(vocbase, tid, json->slice());
EXPECT_TRUE(res.errorNumber() == TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND);
json = arangodb::velocypack::Parser::fromJson(
"{ \"collections\":{\"exclusive\": [\"33\"]}}");
res = mgr->createManagedTrx(vocbase, tid, json->slice());
EXPECT_TRUE(res.errorNumber() == TRI_ERROR_ARANGO_DATA_SOURCE_NOT_FOUND);
}
TEST_F(TransactionManagerTest, transaction_id_reuse) {
std::shared_ptr<LogicalCollection> coll;
{
auto json =
VPackParser::fromJson("{ \"name\": \"testCollection\", \"id\": 42 }");
coll = vocbase.createCollection(json->slice());
}
ASSERT_TRUE(coll != nullptr);
auto json = arangodb::velocypack::Parser::fromJson(
"{ \"collections\":{\"read\": [\"42\"]}}");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
ASSERT_TRUE(res.ok());
json = arangodb::velocypack::Parser::fromJson(
"{ \"collections\":{\"write\": [\"33\"]}}");
res = mgr->createManagedTrx(vocbase, tid, json->slice());
EXPECT_TRUE(res.errorNumber() == TRI_ERROR_TRANSACTION_INTERNAL);
res = mgr->abortManagedTrx(tid);
ASSERT_TRUE(res.ok());
}
TEST_F(TransactionManagerTest, simple_transaction_and_abort) {
std::shared_ptr<LogicalCollection> coll;
{
auto json =
VPackParser::fromJson("{ \"name\": \"testCollection\", \"id\": 42 }");
coll = vocbase.createCollection(json->slice());
}
ASSERT_TRUE(coll != nullptr);
auto json = arangodb::velocypack::Parser::fromJson(
"{ \"collections\":{\"write\": [\"42\"]}}");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
ASSERT_TRUE(res.ok());
auto doc = arangodb::velocypack::Parser::fromJson("{ \"_key\": \"1\"}");
{
auto ctx = mgr->leaseManagedTrx(tid, AccessMode::Type::WRITE);
ASSERT_TRUE(ctx.get() != nullptr);
SingleCollectionTransaction trx(ctx, "testCollection", AccessMode::Type::WRITE);
ASSERT_TRUE(trx.state()->isEmbeddedTransaction());
OperationOptions opts;
auto opRes = trx.insert(coll->name(), doc->slice(), opts);
ASSERT_TRUE(opRes.ok());
ASSERT_TRUE(trx.finish(opRes.result).ok());
}
ASSERT_TRUE((mgr->getManagedTrxStatus(tid) == transaction::Status::RUNNING));
{ // lease again
auto ctx = mgr->leaseManagedTrx(tid, AccessMode::Type::WRITE);
ASSERT_TRUE(ctx.get() != nullptr);
SingleCollectionTransaction trx(ctx, "testCollection", AccessMode::Type::READ);
ASSERT_TRUE(trx.state()->isEmbeddedTransaction());
OperationOptions opts;
auto opRes = trx.document(coll->name(), doc->slice(), opts);
ASSERT_TRUE(opRes.ok());
ASSERT_TRUE(trx.finish(opRes.result).ok());
}
ASSERT_TRUE((mgr->getManagedTrxStatus(tid) == transaction::Status::RUNNING));
ASSERT_TRUE(mgr->abortManagedTrx(tid).ok());
// perform same operation
ASSERT_TRUE(mgr->abortManagedTrx(tid).ok());
// cannot commit aborted transaction
ASSERT_TRUE(mgr->commitManagedTrx(tid).is(TRI_ERROR_TRANSACTION_DISALLOWED_OPERATION));
ASSERT_TRUE((mgr->getManagedTrxStatus(tid) == transaction::Status::ABORTED));
}
TEST_F(TransactionManagerTest, simple_transaction_and_commit) {
std::shared_ptr<LogicalCollection> coll;
{
auto json =
VPackParser::fromJson("{ \"name\": \"testCollection\", \"id\": 42 }");
coll = vocbase.createCollection(json->slice());
}
ASSERT_TRUE(coll != nullptr);
auto json = arangodb::velocypack::Parser::fromJson(
"{ \"collections\":{\"write\": [\"42\"]}}");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
ASSERT_TRUE(res.ok());
{
auto ctx = mgr->leaseManagedTrx(tid, AccessMode::Type::WRITE);
ASSERT_TRUE(ctx.get() != nullptr);
SingleCollectionTransaction trx(ctx, "testCollection", AccessMode::Type::WRITE);
ASSERT_TRUE(trx.state()->isEmbeddedTransaction());
auto doc = arangodb::velocypack::Parser::fromJson("{ \"abc\": 1}");
OperationOptions opts;
auto opRes = trx.insert(coll->name(), doc->slice(), opts);
ASSERT_TRUE(opRes.ok());
ASSERT_TRUE(trx.finish(opRes.result).ok());
}
ASSERT_TRUE((mgr->getManagedTrxStatus(tid) == transaction::Status::RUNNING));
ASSERT_TRUE(mgr->commitManagedTrx(tid).ok());
// perform same operation
ASSERT_TRUE(mgr->commitManagedTrx(tid).ok());
// cannot commit aborted transaction
ASSERT_TRUE(mgr->abortManagedTrx(tid).is(TRI_ERROR_TRANSACTION_DISALLOWED_OPERATION));
ASSERT_TRUE((mgr->getManagedTrxStatus(tid) == transaction::Status::COMMITTED));
}
TEST_F(TransactionManagerTest, simple_transaction_and_commit_while_in_use) {
std::shared_ptr<LogicalCollection> coll;
{
auto json =
VPackParser::fromJson("{ \"name\": \"testCollection\", \"id\": 42 }");
coll = vocbase.createCollection(json->slice());
}
ASSERT_TRUE(coll != nullptr);
auto json = arangodb::velocypack::Parser::fromJson(
"{ \"collections\":{\"write\": [\"42\"]}}");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
ASSERT_TRUE(res.ok());
{
auto ctx = mgr->leaseManagedTrx(tid, AccessMode::Type::WRITE);
ASSERT_TRUE(ctx.get() != nullptr);
SingleCollectionTransaction trx(ctx, "testCollection", AccessMode::Type::WRITE);
ASSERT_TRUE(trx.state()->isEmbeddedTransaction());
auto doc = arangodb::velocypack::Parser::fromJson("{ \"abc\": 1}");
OperationOptions opts;
auto opRes = trx.insert(coll->name(), doc->slice(), opts);
ASSERT_TRUE(opRes.ok());
ASSERT_EQ(TRI_ERROR_LOCKED, mgr->commitManagedTrx(tid).errorNumber());
ASSERT_TRUE(trx.finish(opRes.result).ok());
}
ASSERT_TRUE((mgr->getManagedTrxStatus(tid) == transaction::Status::RUNNING));
ASSERT_TRUE(mgr->commitManagedTrx(tid).ok());
// perform same operation
ASSERT_TRUE(mgr->commitManagedTrx(tid).ok());
// cannot abort committed transaction
ASSERT_TRUE(mgr->abortManagedTrx(tid).is(TRI_ERROR_TRANSACTION_DISALLOWED_OPERATION));
ASSERT_TRUE((mgr->getManagedTrxStatus(tid) == transaction::Status::COMMITTED));
}
TEST_F(TransactionManagerTest, leading_multiple_readonly_transactions) {
std::shared_ptr<LogicalCollection> coll;
{
auto json =
VPackParser::fromJson("{ \"name\": \"testCollection\", \"id\": 42 }");
coll = vocbase.createCollection(json->slice());
}
ASSERT_TRUE(coll != nullptr);
auto json = arangodb::velocypack::Parser::fromJson(
"{ \"collections\":{\"read\": [\"42\"]}}");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
ASSERT_TRUE(res.ok());
{
auto ctx = mgr->leaseManagedTrx(tid, AccessMode::Type::READ);
ASSERT_TRUE(ctx.get() != nullptr);
ASSERT_TRUE(ctx->getParentTransaction() != nullptr);
auto ctx2 = mgr->leaseManagedTrx(tid, AccessMode::Type::READ);
ASSERT_TRUE(ctx2.get() != nullptr);
EXPECT_TRUE(ctx->getParentTransaction() == ctx2->getParentTransaction());
auto ctx3 = mgr->leaseManagedTrx(tid, AccessMode::Type::READ);
ASSERT_TRUE(ctx3.get() != nullptr);
EXPECT_TRUE(ctx->getParentTransaction() == ctx3->getParentTransaction());
}
ASSERT_TRUE(mgr->abortManagedTrx(tid).ok());
ASSERT_TRUE((mgr->getManagedTrxStatus(tid) == transaction::Status::ABORTED));
}
TEST_F(TransactionManagerTest, lock_conflict) {
std::shared_ptr<LogicalCollection> coll;
{
auto json =
VPackParser::fromJson("{ \"name\": \"testCollection\", \"id\": 42 }");
coll = vocbase.createCollection(json->slice());
}
ASSERT_TRUE(coll != nullptr);
auto json = arangodb::velocypack::Parser::fromJson(
"{ \"collections\":{\"write\": [\"42\"]}}");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
ASSERT_TRUE(res.ok());
{
auto ctx = mgr->leaseManagedTrx(tid, AccessMode::Type::WRITE);
ASSERT_TRUE(ctx.get() != nullptr);
ASSERT_TRUE(ctx->getParentTransaction() != nullptr);
ASSERT_ANY_THROW(mgr->leaseManagedTrx(tid, AccessMode::Type::READ));
}
ASSERT_TRUE(mgr->abortManagedTrx(tid).ok());
ASSERT_TRUE((mgr->getManagedTrxStatus(tid) == transaction::Status::ABORTED));
}
TEST_F(TransactionManagerTest, garbage_collection_shutdown) {
std::shared_ptr<LogicalCollection> coll;
{
auto json =
VPackParser::fromJson("{ \"name\": \"testCollection\", \"id\": 42 }");
coll = vocbase.createCollection(json->slice());
}
ASSERT_TRUE(coll != nullptr);
auto json = arangodb::velocypack::Parser::fromJson(
"{ \"collections\":{\"write\": [\"42\"]}}");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
ASSERT_TRUE(res.ok());
{
auto ctx = mgr->leaseManagedTrx(tid, AccessMode::Type::WRITE);
ASSERT_TRUE(ctx.get() != nullptr);
ASSERT_TRUE(ctx->getParentTransaction() != nullptr);
}
ASSERT_TRUE((mgr->getManagedTrxStatus(tid) == transaction::Status::RUNNING));
ASSERT_TRUE(mgr->garbageCollect(/*abortAll*/ true));
ASSERT_TRUE((mgr->getManagedTrxStatus(tid) == transaction::Status::ABORTED));
}
TEST_F(TransactionManagerTest, aql_standalone_transaction) {
std::shared_ptr<LogicalCollection> coll;
{
auto json =
VPackParser::fromJson("{ \"name\": \"testCollection\", \"id\": 42 }");
coll = vocbase.createCollection(json->slice());
}
ASSERT_TRUE(coll != nullptr);
{
auto ctx = transaction::StandaloneContext::Create(vocbase);
SingleCollectionTransaction trx(ctx, "testCollection", AccessMode::Type::WRITE);
ASSERT_TRUE(trx.begin().ok());
auto doc = arangodb::velocypack::Parser::fromJson("{ \"abc\": 1}");
OperationOptions opts;
auto opRes = trx.insert(coll->name(), doc->slice(), opts);
ASSERT_TRUE(opRes.ok());
ASSERT_TRUE(trx.finish(opRes.result).ok());
}
auto ctx = std::make_shared<transaction::AQLStandaloneContext>(vocbase, tid);
auto qq = "FOR doc IN testCollection RETURN doc";
arangodb::aql::QueryResult qres = executeQuery(vocbase, qq, ctx);
ASSERT_TRUE(qres.ok());
VPackSlice data = qres.data->slice();
ASSERT_TRUE(data.isArray());
ASSERT_TRUE(data.length() == 1);
EXPECT_TRUE(data.at(0).isObject());
EXPECT_TRUE(data.at(0).hasKey("abc"));
}
TEST_F(TransactionManagerTest, abort_transactions_with_matcher) {
std::shared_ptr<LogicalCollection> coll;
{
auto json =
VPackParser::fromJson("{ \"name\": \"testCollection\", \"id\": 42 }");
coll = vocbase.createCollection(json->slice());
}
ASSERT_TRUE(coll != nullptr);
auto json = arangodb::velocypack::Parser::fromJson(
"{ \"collections\":{\"write\": [\"42\"]}}");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
ASSERT_TRUE(res.ok());
{
auto ctx = mgr->leaseManagedTrx(tid, AccessMode::Type::WRITE);
ASSERT_TRUE(ctx.get() != nullptr);
SingleCollectionTransaction trx(ctx, "testCollection", AccessMode::Type::WRITE);
ASSERT_TRUE(trx.state()->isEmbeddedTransaction());
auto doc = arangodb::velocypack::Parser::fromJson("{ \"abc\": 1}");
OperationOptions opts;
auto opRes = trx.insert(coll->name(), doc->slice(), opts);
ASSERT_TRUE(opRes.ok());
ASSERT_TRUE(trx.finish(opRes.result).ok());
}
ASSERT_TRUE((mgr->getManagedTrxStatus(tid) == transaction::Status::RUNNING));
//
mgr->abortManagedTrx([](TransactionState const& state) -> bool {
TransactionCollection* tcoll = state.collection(42, AccessMode::Type::NONE);
return tcoll != nullptr;
});
ASSERT_TRUE((mgr->getManagedTrxStatus(tid) == transaction::Status::ABORTED));
}
TEST_F(TransactionManagerTest, permission_denied_readonly) {
std::shared_ptr<LogicalCollection> coll;
{
auto json =
VPackParser::fromJson("{ \"name\": \"testCollection\", \"id\": 42 }");
coll = vocbase.createCollection(json->slice());
}
ASSERT_TRUE(coll != nullptr);
struct ExecContext : public arangodb::ExecContext {
ExecContext()
: arangodb::ExecContext(arangodb::ExecContext::Type::Internal, "dummy",
"testVocbase", arangodb::auth::Level::RO,
arangodb::auth::Level::RO) {}
} execContext;
arangodb::ExecContextScope execContextScope(&execContext);
auto json = arangodb::velocypack::Parser::fromJson(
"{ \"collections\":{\"read\": [\"42\"]}}");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
EXPECT_TRUE(res.ok());
ASSERT_TRUE(mgr->abortManagedTrx(tid).ok());
tid = TRI_NewTickServer();
json = arangodb::velocypack::Parser::fromJson(
"{ \"collections\":{\"write\": [\"42\"]}}");
res = mgr->createManagedTrx(vocbase, tid, json->slice());
ASSERT_TRUE(res.errorNumber() == TRI_ERROR_ARANGO_READ_ONLY);
}
TEST_F(TransactionManagerTest, permission_denied_forbidden) {
std::shared_ptr<LogicalCollection> coll;
{
auto json =
VPackParser::fromJson("{ \"name\": \"testCollection\", \"id\": 42 }");
coll = vocbase.createCollection(json->slice());
}
ASSERT_TRUE(coll != nullptr);
struct ExecContext : public arangodb::ExecContext {
ExecContext()
: arangodb::ExecContext(arangodb::ExecContext::Type::Internal, "dummy",
"testVocbase", arangodb::auth::Level::NONE,
arangodb::auth::Level::NONE) {}
} execContext;
arangodb::ExecContextScope execContextScope(&execContext);
auto json = arangodb::velocypack::Parser::fromJson(
"{ \"collections\":{\"read\": [\"42\"]}}");
Result res = mgr->createManagedTrx(vocbase, tid, json->slice());
ASSERT_TRUE(res.errorNumber() == TRI_ERROR_FORBIDDEN);
}