diff options
author | Xiangyu Yao <xiangyu.yao@mongodb.com> | 2018-03-19 18:01:06 -0400 |
---|---|---|
committer | Xiangyu Yao <xiangyu.yao@mongodb.com> | 2018-03-23 17:32:28 -0400 |
commit | 3c39645662cca8467de11f32bb2d0032a0c4458e (patch) | |
tree | 691b4c4ff2b4293b4aa9d5c49ccab8e30a4dd401 | |
parent | 877302630e10739e3e3fa0d9c96f45bc437f3660 (diff) | |
download | mongo-3c39645662cca8467de11f32bb2d0032a0c4458e.tar.gz |
SERVER-33834 Add unit tests for WiredTigerRecoveryUnit
-rw-r--r-- | src/mongo/db/storage/SConscript | 12 | ||||
-rw-r--r-- | src/mongo/db/storage/recovery_unit_test_harness.cpp | 104 | ||||
-rw-r--r-- | src/mongo/db/storage/recovery_unit_test_harness.h | 47 | ||||
-rw-r--r-- | src/mongo/db/storage/wiredtiger/SConscript | 12 | ||||
-rw-r--r-- | src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit_test.cpp | 232 |
5 files changed, 407 insertions, 0 deletions
diff --git a/src/mongo/db/storage/SConscript b/src/mongo/db/storage/SConscript index 1c435160278..95a8a6bd74e 100644 --- a/src/mongo/db/storage/SConscript +++ b/src/mongo/db/storage/SConscript @@ -166,6 +166,18 @@ env.Library( ], ) + +env.Library( + target='recovery_unit_test_harness', + source=[ + 'recovery_unit_test_harness.cpp' + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/unittest/unittest', + 'test_harness_helper', + ], + ) + env.Library( target='storage_engine_lock_file', source=[ diff --git a/src/mongo/db/storage/recovery_unit_test_harness.cpp b/src/mongo/db/storage/recovery_unit_test_harness.cpp new file mode 100644 index 00000000000..776aca750fd --- /dev/null +++ b/src/mongo/db/storage/recovery_unit_test_harness.cpp @@ -0,0 +1,104 @@ +/** + * Copyright (C) 2018 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/db/storage/recovery_unit_test_harness.h" +#include "mongo/db/repl/read_concern_level.h" +#include "mongo/db/repl/replication_coordinator.h" +#include "mongo/db/storage/record_store.h" +#include "mongo/db/storage/recovery_unit.h" +#include "mongo/unittest/death_test.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace { + +class RecoveryUnitTestHarness : public unittest::Test { +public: + void setUp() override { + harnessHelper = newRecoveryUnitHarnessHelper(); + opCtx = harnessHelper->newOperationContext(); + ru = opCtx->recoveryUnit(); + } + + std::unique_ptr<RecoveryUnitHarnessHelper> harnessHelper; + ServiceContext::UniqueOperationContext opCtx; + RecoveryUnit* ru; +}; + + +TEST_F(RecoveryUnitTestHarness, SetReadConcernLevel) { + ru->setReadConcernLevelAndReplicationMode(repl::ReadConcernLevel::kAvailableReadConcern, + repl::ReplicationCoordinator::modeReplSet); + ASSERT_TRUE(repl::ReadConcernLevel::kAvailableReadConcern == ru->getReadConcernLevel()); +} + +TEST_F(RecoveryUnitTestHarness, CommitUnitOfWork) { + const auto rs = harnessHelper->createRecordStore(opCtx.get(), "table1"); + ru->beginUnitOfWork(opCtx.get()); + StatusWith<RecordId> s = rs->insertRecord(opCtx.get(), "data", 4, Timestamp(), false); + ASSERT_TRUE(s.isOK()); + ASSERT_EQUALS(1, rs->numRecords(NULL)); + ru->commitUnitOfWork(); + RecordData rd; + ASSERT_TRUE(rs->findRecord(opCtx.get(), s.getValue(), &rd)); +} + +TEST_F(RecoveryUnitTestHarness, AbortUnitOfWork) { + const auto rs = harnessHelper->createRecordStore(opCtx.get(), "table1"); + ru->beginUnitOfWork(opCtx.get()); + StatusWith<RecordId> s = rs->insertRecord(opCtx.get(), "data", 4, Timestamp(), false); + ASSERT_TRUE(s.isOK()); + ASSERT_EQUALS(1, rs->numRecords(NULL)); + ru->abortUnitOfWork(); + ASSERT_FALSE(rs->findRecord(opCtx.get(), s.getValue(), nullptr)); +} + +DEATH_TEST_F(RecoveryUnitTestHarness, CommitMustBeInUnitOfWork, "invariant") { + opCtx->recoveryUnit()->commitUnitOfWork(); +} + +DEATH_TEST_F(RecoveryUnitTestHarness, AbortMustBeInUnitOfWork, "invariant") { + opCtx->recoveryUnit()->abortUnitOfWork(); +} + +DEATH_TEST_F(RecoveryUnitTestHarness, PrepareMustBeInUnitOfWork, "invariant") { + opCtx->recoveryUnit()->prepareUnitOfWork(); +} + +DEATH_TEST_F(RecoveryUnitTestHarness, WaitUntilDurableMustBeOutOfUnitOfWork, "invariant") { + opCtx->recoveryUnit()->beginUnitOfWork(opCtx.get()); + opCtx->recoveryUnit()->waitUntilDurable(); +} + +DEATH_TEST_F(RecoveryUnitTestHarness, AbandonSnapshotMustBeOutOfUnitOfWork, "invariant") { + opCtx->recoveryUnit()->beginUnitOfWork(opCtx.get()); + opCtx->recoveryUnit()->abandonSnapshot(); +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/storage/recovery_unit_test_harness.h b/src/mongo/db/storage/recovery_unit_test_harness.h new file mode 100644 index 00000000000..577f51949cc --- /dev/null +++ b/src/mongo/db/storage/recovery_unit_test_harness.h @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2018 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include "mongo/db/storage/test_harness_helper.h" + +namespace mongo { + +class RecordStore; + +class RecoveryUnitHarnessHelper : public HarnessHelper { +public: + virtual std::unique_ptr<RecoveryUnit> newRecoveryUnit() = 0; + virtual std::unique_ptr<RecordStore> createRecordStore(OperationContext* opCtx, + const std::string& ns) = 0; +}; + +inline std::unique_ptr<RecoveryUnitHarnessHelper> newRecoveryUnitHarnessHelper() { + return dynamic_ptr_cast<RecoveryUnitHarnessHelper>(newHarnessHelper()); +} +} // namespace mongo diff --git a/src/mongo/db/storage/wiredtiger/SConscript b/src/mongo/db/storage/wiredtiger/SConscript index 81ac066b7fa..ab82be68684 100644 --- a/src/mongo/db/storage/wiredtiger/SConscript +++ b/src/mongo/db/storage/wiredtiger/SConscript @@ -128,6 +128,18 @@ if wiredtiger: ], ) + wtEnv.CppUnitTest( + target='storage_wiredtiger_recovery_unit_test', + source=[ + 'wiredtiger_recovery_unit_test.cpp', + ], + LIBDEPS=[ + 'storage_wiredtiger_mock', + '$BUILD_DIR/mongo/db/storage/recovery_unit_test_harness', + '$BUILD_DIR/mongo/util/clock_source_mock', + ] + ) + wtEnv.Library( target='additional_wiredtiger_record_store_tests', source=[ diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit_test.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit_test.cpp new file mode 100644 index 00000000000..405d893c9c1 --- /dev/null +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit_test.cpp @@ -0,0 +1,232 @@ +/** + * Copyright (C) 2018 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.h" +#include "mongo/base/checked_cast.h" +#include "mongo/db/storage/recovery_unit_test_harness.h" +#include "mongo/db/storage/wiredtiger/wiredtiger_kv_engine.h" +#include "mongo/db/storage/wiredtiger/wiredtiger_record_store.h" +#include "mongo/db/storage/wiredtiger/wiredtiger_session_cache.h" +#include "mongo/db/storage/wiredtiger/wiredtiger_util.h" +#include "mongo/unittest/temp_dir.h" +#include "mongo/unittest/unittest.h" +#include "mongo/util/clock_source_mock.h" + +namespace mongo { +namespace { + +class WiredTigerRecoveryUnitHarnessHelper final : public RecoveryUnitHarnessHelper { +public: + WiredTigerRecoveryUnitHarnessHelper() + : _dbpath("wt_test"), + _engine(kWiredTigerEngineName, // .canonicalName + _dbpath.path(), // .path + &_cs, // .cs + "", // .extraOpenOptions + 1, // .cacheSizeGB + false, // .durable + false, // .ephemeral + false, // .repair + false // .readOnly + ) {} + + ~WiredTigerRecoveryUnitHarnessHelper() {} + + virtual std::unique_ptr<RecoveryUnit> newRecoveryUnit() final { + return std::unique_ptr<RecoveryUnit>(_engine.newRecoveryUnit()); + } + + virtual std::unique_ptr<RecordStore> createRecordStore(OperationContext* opCtx, + const std::string& ns) final { + std::string uri = "table:" + ns; + const bool prefixed = false; + StatusWith<std::string> result = WiredTigerRecordStore::generateCreateString( + kWiredTigerEngineName, ns, CollectionOptions(), "", prefixed); + ASSERT_TRUE(result.isOK()); + std::string config = result.getValue(); + + { + WriteUnitOfWork uow(opCtx); + WiredTigerRecoveryUnit* ru = + checked_cast<WiredTigerRecoveryUnit*>(opCtx->recoveryUnit()); + WT_SESSION* s = ru->getSession()->getSession(); + invariantWTOK(s->create(s, uri.c_str(), config.c_str())); + uow.commit(); + } + + WiredTigerRecordStore::Params params; + params.ns = ns; + params.uri = uri; + params.engineName = kWiredTigerEngineName; + params.isCapped = false; + params.isEphemeral = false; + params.cappedMaxSize = -1; + params.cappedMaxDocs = -1; + params.cappedCallback = nullptr; + params.sizeStorer = nullptr; + + auto ret = stdx::make_unique<StandardWiredTigerRecordStore>(&_engine, opCtx, params); + ret->postConstructorInit(opCtx); + return std::move(ret); + } + +private: + unittest::TempDir _dbpath; + ClockSourceMock _cs; + WiredTigerKVEngine _engine; +}; + +std::unique_ptr<HarnessHelper> makeHarnessHelper() { + return stdx::make_unique<WiredTigerRecoveryUnitHarnessHelper>(); +} + +MONGO_INITIALIZER(RegisterHarnessFactory)(InitializerContext* const) { + mongo::registerHarnessHelperFactory(makeHarnessHelper); + return Status::OK(); +} + +class WiredTigerRecoveryUnitTestFixture : public unittest::Test { +public: + typedef std::pair<ServiceContext::UniqueClient, ServiceContext::UniqueOperationContext> + ClientAndCtx; + + ClientAndCtx makeClientAndOpCtx(RecoveryUnitHarnessHelper* harnessHelper, + const std::string& clientName) { + auto sc = harnessHelper->serviceContext(); + auto client = sc->makeClient(clientName); + auto opCtx = client->makeOperationContext(); + opCtx->setRecoveryUnit(harnessHelper->newRecoveryUnit().release(), + OperationContext::kNotInUnitOfWork); + return std::make_pair(std::move(client), std::move(opCtx)); + } + + void getCursor(WiredTigerRecoveryUnit* ru, WT_CURSOR** cursor) { + WT_SESSION* wt_session = ru->getSession()->getSession(); + invariantWTOK(wt_session->create(wt_session, wt_uri, wt_config)); + invariantWTOK(wt_session->open_cursor(wt_session, wt_uri, NULL, NULL, cursor)); + } + + void setUp() override { + harnessHelper = newRecoveryUnitHarnessHelper(); + clientAndCtx1 = makeClientAndOpCtx(harnessHelper.get(), "writer"); + clientAndCtx2 = makeClientAndOpCtx(harnessHelper.get(), "reader"); + ru1 = checked_cast<WiredTigerRecoveryUnit*>(clientAndCtx1.second->recoveryUnit()); + ru2 = checked_cast<WiredTigerRecoveryUnit*>(clientAndCtx2.second->recoveryUnit()); + } + + std::unique_ptr<RecoveryUnitHarnessHelper> harnessHelper; + ClientAndCtx clientAndCtx1, clientAndCtx2; + WiredTigerRecoveryUnit *ru1, *ru2; + +private: + const char* wt_uri = "table:prepare_transaction"; + const char* wt_config = "key_format=S,value_format=S"; +}; + +TEST_F(WiredTigerRecoveryUnitTestFixture, + LocalReadOnADocumentBeingPreparedTriggersPrepareConflict) { + // Prepare but don't commit a transaction + ru1->setReadConcernLevelAndReplicationMode(repl::ReadConcernLevel::kLocalReadConcern, + repl::ReplicationCoordinator::modeNone); + ru1->beginUnitOfWork(clientAndCtx1.second.get()); + WT_CURSOR* cursor; + getCursor(ru1, &cursor); + cursor->set_key(cursor, "key"); + cursor->set_value(cursor, "value"); + invariantWTOK(cursor->insert(cursor)); + ru1->setPrepareTimestamp({1, 1}); + ru1->prepareUnitOfWork(); + + // Transaction with local readConcern triggers WT_PREPARE_CONFLICT + ru2->beginUnitOfWork(clientAndCtx2.second.get()); + ru2->setReadConcernLevelAndReplicationMode(repl::ReadConcernLevel::kLocalReadConcern, + repl::ReplicationCoordinator::modeNone); + getCursor(ru2, &cursor); + cursor->set_key(cursor, "key"); + int ret = cursor->search(cursor); + ASSERT_EQ(WT_PREPARE_CONFLICT, ret); + + ru1->abortUnitOfWork(); + ru2->abortUnitOfWork(); +} + +TEST_F(WiredTigerRecoveryUnitTestFixture, + availableReadOnADocumentBeingPreparedDoesNotTriggerPrepareConflict) { + // Prepare but don't commit a transaction + ru1->setReadConcernLevelAndReplicationMode(repl::ReadConcernLevel::kLocalReadConcern, + repl::ReplicationCoordinator::modeNone); + ru1->beginUnitOfWork(clientAndCtx1.second.get()); + WT_CURSOR* cursor; + getCursor(ru1, &cursor); + cursor->set_key(cursor, "key"); + cursor->set_value(cursor, "value"); + invariantWTOK(cursor->insert(cursor)); + ru1->setPrepareTimestamp({1, 1}); + ru1->prepareUnitOfWork(); + + // Transaction with available readConcern wouldn't trigger + // WT_PREPARE_CONFLICT + ru2->beginUnitOfWork(clientAndCtx2.second.get()); + ru2->setReadConcernLevelAndReplicationMode(repl::ReadConcernLevel::kAvailableReadConcern, + repl::ReplicationCoordinator::modeNone); + getCursor(ru2, &cursor); + cursor->set_key(cursor, "key"); + int ret = cursor->search(cursor); + ASSERT_EQ(WT_NOTFOUND, ret); + + ru1->abortUnitOfWork(); + ru2->abortUnitOfWork(); +} + +TEST_F(WiredTigerRecoveryUnitTestFixture, WriteOnADocumentBeingPreparedTriggersWTRollback) { + // Prepare but don't commit a transaction + ru1->setReadConcernLevelAndReplicationMode(repl::ReadConcernLevel::kLocalReadConcern, + repl::ReplicationCoordinator::modeNone); + ru1->beginUnitOfWork(clientAndCtx1.second.get()); + WT_CURSOR* cursor; + getCursor(ru1, &cursor); + cursor->set_key(cursor, "key"); + cursor->set_value(cursor, "value"); + invariantWTOK(cursor->insert(cursor)); + ru1->setPrepareTimestamp({1, 1}); + ru1->prepareUnitOfWork(); + + // Another transaction with wirte triggers WT_ROLLBACK + ru2->beginUnitOfWork(clientAndCtx2.second.get()); + getCursor(ru2, &cursor); + cursor->set_key(cursor, "key"); + cursor->set_value(cursor, "value2"); + int ret = cursor->insert(cursor); + ASSERT_EQ(WT_ROLLBACK, ret); + + ru1->abortUnitOfWork(); + ru2->abortUnitOfWork(); +} + +} // namespace +} // namespace mongo |