/** * Copyright 2015 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 . * * 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/platform/basic.h" #include #include "mongo/db/jsobj.h" #include "mongo/db/repl/oplog_interface_mock.h" #include "mongo/db/repl/roll_back_local_operations.h" #include "mongo/unittest/unittest.h" namespace { using namespace mongo; using namespace mongo::repl; const OplogInterfaceMock::Operations kEmptyMockOperations; BSONObj makeOp(int seconds, long long hash) { return BSON("ts" << Timestamp(Seconds(seconds), 0) << "h" << hash); } int recordId = 0; OplogInterfaceMock::Operation makeOpAndRecordId(int seconds, long long hash) { return std::make_pair(makeOp(seconds, hash), RecordId(++recordId)); } TEST(RollBackLocalOperationsTest, InvalidLocalOplogIterator) { class InvalidOplogInterface : public OplogInterface { public: std::string toString() const override { return ""; } std::unique_ptr makeIterator() const override { return std::unique_ptr(); } } invalidOplog; ASSERT_THROWS_CODE( RollBackLocalOperations(invalidOplog, [](const BSONObj&) { return Status::OK(); }), UserException, ErrorCodes::BadValue); } TEST(RollBackLocalOperationsTest, InvalidRollbackOperationFunction) { ASSERT_THROWS_CODE(RollBackLocalOperations(OplogInterfaceMock({makeOpAndRecordId(1, 0)}), RollBackLocalOperations::RollbackOperationFn()), UserException, ErrorCodes::BadValue); } TEST(RollBackLocalOperationsTest, EmptyLocalOplog) { OplogInterfaceMock localOplog(kEmptyMockOperations); RollBackLocalOperations finder(localOplog, [](const BSONObj&) { return Status::OK(); }); auto result = finder.onRemoteOperation(makeOp(1, 0)); ASSERT_EQUALS(ErrorCodes::OplogStartMissing, result.getStatus().code()); } TEST(RollBackLocalOperationsTest, RollbackPeriodTooLong) { OplogInterfaceMock localOplog({makeOpAndRecordId(1802, 0)}); RollBackLocalOperations finder(localOplog, [](const BSONObj&) { return Status::OK(); }); auto result = finder.onRemoteOperation(makeOp(1, 0)); ASSERT_EQUALS(ErrorCodes::ExceededTimeLimit, result.getStatus().code()); } TEST(RollBackLocalOperationsTest, RollbackMultipleLocalOperations) { auto commonOperation = makeOpAndRecordId(1, 1); OplogInterfaceMock::Operations localOperations({ makeOpAndRecordId(5, 1), makeOpAndRecordId(4, 1), makeOpAndRecordId(3, 1), makeOpAndRecordId(2, 1), commonOperation, }); OplogInterfaceMock localOplog(localOperations); auto i = localOperations.cbegin(); auto rollbackOperation = [&](const BSONObj& operation) { ASSERT_EQUALS(i->first, operation); i++; return Status::OK(); }; RollBackLocalOperations finder(localOplog, rollbackOperation); auto result = finder.onRemoteOperation(commonOperation.first); ASSERT_OK(result.getStatus()); ASSERT_EQUALS(commonOperation.first["ts"].timestamp(), result.getValue().first); ASSERT_EQUALS(commonOperation.second, result.getValue().second); ASSERT_FALSE(i == localOperations.cend()); ASSERT_EQUALS(commonOperation.first, i->first); i++; ASSERT_TRUE(i == localOperations.cend()); } TEST(RollBackLocalOperationsTest, RollbackOperationFailed) { auto commonOperation = makeOpAndRecordId(1, 1); OplogInterfaceMock::Operations localOperations({ makeOpAndRecordId(2, 1), commonOperation, }); OplogInterfaceMock localOplog(localOperations); auto rollbackOperation = [&](const BSONObj& operation) { return Status(ErrorCodes::OperationFailed, ""); }; RollBackLocalOperations finder(localOplog, rollbackOperation); auto result = finder.onRemoteOperation(commonOperation.first); ASSERT_EQUALS(ErrorCodes::OperationFailed, result.getStatus().code()); } TEST(RollBackLocalOperationsTest, EndOfLocalOplog) { auto commonOperation = makeOpAndRecordId(1, 1); OplogInterfaceMock::Operations localOperations({ makeOpAndRecordId(2, 1), }); OplogInterfaceMock localOplog(localOperations); RollBackLocalOperations finder(localOplog, [](const BSONObj&) { return Status::OK(); }); auto result = finder.onRemoteOperation(commonOperation.first); ASSERT_EQUALS(ErrorCodes::NoMatchingDocument, result.getStatus().code()); } TEST(RollBackLocalOperationsTest, SkipRemoteOperations) { auto commonOperation = makeOpAndRecordId(1, 1); OplogInterfaceMock::Operations localOperations({ makeOpAndRecordId(5, 1), makeOpAndRecordId(4, 1), makeOpAndRecordId(2, 1), commonOperation, }); OplogInterfaceMock localOplog(localOperations); auto i = localOperations.cbegin(); auto rollbackOperation = [&](const BSONObj& operation) { ASSERT_EQUALS(i->first, operation); i++; return Status::OK(); }; RollBackLocalOperations finder(localOplog, rollbackOperation); { auto result = finder.onRemoteOperation(makeOp(6, 1)); ASSERT_EQUALS(ErrorCodes::NoSuchKey, result.getStatus().code()); ASSERT_TRUE(i == localOperations.cbegin()); } { auto result = finder.onRemoteOperation(makeOp(3, 1)); ASSERT_EQUALS(ErrorCodes::NoSuchKey, result.getStatus().code()); ASSERT_TRUE(std::distance(localOperations.cbegin(), i) == 2); } auto result = finder.onRemoteOperation(commonOperation.first); ASSERT_OK(result.getStatus()); ASSERT_EQUALS(commonOperation.first["ts"].timestamp(), result.getValue().first); ASSERT_EQUALS(commonOperation.second, result.getValue().second); ASSERT_FALSE(i == localOperations.cend()); ASSERT_EQUALS(commonOperation.first, i->first); i++; ASSERT_TRUE(i == localOperations.cend()); } TEST(RollBackLocalOperationsTest, SameTimestampDifferentHashess) { auto commonOperation = makeOpAndRecordId(1, 1); OplogInterfaceMock::Operations localOperations({ makeOpAndRecordId(1, 5), makeOpAndRecordId(1, 3), commonOperation, }); OplogInterfaceMock localOplog(localOperations); auto i = localOperations.cbegin(); auto rollbackOperation = [&](const BSONObj& operation) { ASSERT_EQUALS(i->first, operation); i++; return Status::OK(); }; RollBackLocalOperations finder(localOplog, rollbackOperation); { auto result = finder.onRemoteOperation(makeOp(1, 4)); ASSERT_EQUALS(ErrorCodes::NoSuchKey, result.getStatus().code()); ASSERT_TRUE(std::distance(localOperations.cbegin(), i) == 1); } { auto result = finder.onRemoteOperation(makeOp(1, 2)); ASSERT_EQUALS(ErrorCodes::NoSuchKey, result.getStatus().code()); ASSERT_TRUE(std::distance(localOperations.cbegin(), i) == 2); } auto result = finder.onRemoteOperation(commonOperation.first); ASSERT_OK(result.getStatus()); ASSERT_EQUALS(commonOperation.first["ts"].timestamp(), result.getValue().first); ASSERT_EQUALS(commonOperation.second, result.getValue().second); ASSERT_FALSE(i == localOperations.cend()); ASSERT_EQUALS(commonOperation.first, i->first); i++; ASSERT_TRUE(i == localOperations.cend()); } TEST(RollBackLocalOperationsTest, SameTimestampDifferentHashesRollbackOperationFailed) { auto commonOperation = makeOpAndRecordId(1, 1); OplogInterfaceMock::Operations localOperations({ makeOpAndRecordId(1, 3), commonOperation, }); OplogInterfaceMock localOplog(localOperations); auto rollbackOperation = [&](const BSONObj& operation) { return Status(ErrorCodes::OperationFailed, ""); }; RollBackLocalOperations finder(localOplog, rollbackOperation); auto result = finder.onRemoteOperation(makeOp(1, 2)); ASSERT_EQUALS(ErrorCodes::OperationFailed, result.getStatus().code()); } TEST(RollBackLocalOperationsTest, SameTimestampDifferentHashesEndOfLocalOplog) { OplogInterfaceMock::Operations localOperations({ makeOpAndRecordId(1, 3), }); OplogInterfaceMock localOplog(localOperations); RollBackLocalOperations finder(localOplog, [](const BSONObj&) { return Status::OK(); }); auto result = finder.onRemoteOperation(makeOp(1, 2)); ASSERT_EQUALS(ErrorCodes::NoMatchingDocument, result.getStatus().code()); } TEST(SyncRollBackLocalOperationsTest, OplogStartMissing) { ASSERT_EQUALS(ErrorCodes::OplogStartMissing, syncRollBackLocalOperations(OplogInterfaceMock(kEmptyMockOperations), OplogInterfaceMock({makeOpAndRecordId(1, 0)}), [](const BSONObj&) { return Status::OK(); }) .getStatus() .code()); } TEST(SyncRollBackLocalOperationsTest, RemoteOplogMissing) { ASSERT_EQUALS(ErrorCodes::InvalidSyncSource, syncRollBackLocalOperations(OplogInterfaceMock({makeOpAndRecordId(1, 0)}), OplogInterfaceMock(kEmptyMockOperations), [](const BSONObj&) { return Status::OK(); }) .getStatus() .code()); } TEST(SyncRollBackLocalOperationsTest, RollbackPeriodTooLong) { ASSERT_EQUALS(ErrorCodes::ExceededTimeLimit, syncRollBackLocalOperations(OplogInterfaceMock({makeOpAndRecordId(1802, 0)}), OplogInterfaceMock({makeOpAndRecordId(1, 0)}), [](const BSONObj&) { return Status::OK(); }) .getStatus() .code()); } TEST(SyncRollBackLocalOperationsTest, RollbackTwoOperations) { auto commonOperation = makeOpAndRecordId(1, 1); OplogInterfaceMock::Operations localOperations({ makeOpAndRecordId(3, 1), makeOpAndRecordId(2, 1), commonOperation, }); auto i = localOperations.cbegin(); auto result = syncRollBackLocalOperations(OplogInterfaceMock(localOperations), OplogInterfaceMock({commonOperation}), [&](const BSONObj& operation) { ASSERT_EQUALS(i->first, operation); i++; return Status::OK(); }); ASSERT_OK(result.getStatus()); ASSERT_EQUALS(commonOperation.first["ts"].timestamp(), result.getValue().first); ASSERT_EQUALS(commonOperation.second, result.getValue().second); ASSERT_FALSE(i == localOperations.cend()); ASSERT_EQUALS(commonOperation.first, i->first); i++; ASSERT_TRUE(i == localOperations.cend()); } TEST(SyncRollBackLocalOperationsTest, SkipOneRemoteOperation) { auto commonOperation = makeOpAndRecordId(1, 1); auto remoteOperation = makeOpAndRecordId(2, 1); auto result = syncRollBackLocalOperations(OplogInterfaceMock({commonOperation}), OplogInterfaceMock({remoteOperation, commonOperation}), [&](const BSONObj& operation) { FAIL("should not reach here"); return Status::OK(); }); ASSERT_OK(result.getStatus()); ASSERT_EQUALS(commonOperation.first["ts"].timestamp(), result.getValue().first); ASSERT_EQUALS(commonOperation.second, result.getValue().second); } TEST(SyncRollBackLocalOperationsTest, SameTimestampDifferentHashes) { auto commonOperation = makeOpAndRecordId(1, 1); auto localOperation = makeOpAndRecordId(1, 2); auto remoteOperation = makeOpAndRecordId(1, 3); bool called = false; auto result = syncRollBackLocalOperations(OplogInterfaceMock({localOperation, commonOperation}), OplogInterfaceMock({remoteOperation, commonOperation}), [&](const BSONObj& operation) { ASSERT_EQUALS(localOperation.first, operation); called = true; return Status::OK(); }); ASSERT_OK(result.getStatus()); ASSERT_EQUALS(commonOperation.first["ts"].timestamp(), result.getValue().first); ASSERT_EQUALS(commonOperation.second, result.getValue().second); ASSERT_TRUE(called); } TEST(SyncRollBackLocalOperationsTest, SameTimestampEndOfLocalOplog) { auto commonOperation = makeOpAndRecordId(1, 1); auto localOperation = makeOpAndRecordId(1, 2); auto remoteOperation = makeOpAndRecordId(1, 3); bool called = false; auto result = syncRollBackLocalOperations(OplogInterfaceMock({localOperation}), OplogInterfaceMock({remoteOperation, commonOperation}), [&](const BSONObj& operation) { ASSERT_EQUALS(localOperation.first, operation); called = true; return Status::OK(); }); ASSERT_EQUALS(ErrorCodes::NoMatchingDocument, result.getStatus().code()); ASSERT_STRING_CONTAINS(result.getStatus().reason(), "RS101 reached beginning of local oplog [1]"); ASSERT_TRUE(called); } TEST(SyncRollBackLocalOperationsTest, SameTimestampRollbackOperationFailed) { auto commonOperation = makeOpAndRecordId(1, 1); auto localOperation = makeOpAndRecordId(1, 2); auto remoteOperation = makeOpAndRecordId(1, 3); auto result = syncRollBackLocalOperations( OplogInterfaceMock({localOperation, commonOperation}), OplogInterfaceMock({remoteOperation, commonOperation}), [&](const BSONObj& operation) { return Status(ErrorCodes::OperationFailed, ""); }); ASSERT_EQUALS(ErrorCodes::OperationFailed, result.getStatus().code()); } TEST(SyncRollBackLocalOperationsTest, SameTimestampEndOfRemoteOplog) { auto commonOperation = makeOpAndRecordId(1, 1); auto localOperation = makeOpAndRecordId(1, 2); auto remoteOperation = makeOpAndRecordId(1, 3); bool called = false; auto result = syncRollBackLocalOperations(OplogInterfaceMock({localOperation, commonOperation}), OplogInterfaceMock({remoteOperation}), [&](const BSONObj& operation) { ASSERT_EQUALS(localOperation.first, operation); called = true; return Status::OK(); }); ASSERT_EQUALS(ErrorCodes::NoMatchingDocument, result.getStatus().code()); ASSERT_STRING_CONTAINS(result.getStatus().reason(), "RS100 reached beginning of remote oplog"); ASSERT_TRUE(called); } TEST(SyncRollBackLocalOperationsTest, DifferentTimestampEndOfLocalOplog) { auto commonOperation = makeOpAndRecordId(1, 1); auto localOperation = makeOpAndRecordId(3, 1); auto remoteOperation = makeOpAndRecordId(2, 1); bool called = false; auto result = syncRollBackLocalOperations(OplogInterfaceMock({localOperation}), OplogInterfaceMock({remoteOperation, commonOperation}), [&](const BSONObj& operation) { ASSERT_EQUALS(localOperation.first, operation); called = true; return Status::OK(); }); ASSERT_EQUALS(ErrorCodes::NoMatchingDocument, result.getStatus().code()); ASSERT_STRING_CONTAINS(result.getStatus().reason(), "RS101 reached beginning of local oplog [2]"); ASSERT_TRUE(called); } TEST(SyncRollBackLocalOperationsTest, DifferentTimestampRollbackOperationFailed) { auto localOperation = makeOpAndRecordId(3, 1); auto remoteOperation = makeOpAndRecordId(2, 1); auto result = syncRollBackLocalOperations( OplogInterfaceMock({localOperation}), OplogInterfaceMock({remoteOperation}), [&](const BSONObj& operation) { return Status(ErrorCodes::OperationFailed, ""); }); ASSERT_EQUALS(ErrorCodes::OperationFailed, result.getStatus().code()); } TEST(SyncRollBackLocalOperationsTest, DifferentTimestampEndOfRemoteOplog) { auto commonOperation = makeOpAndRecordId(1, 1); auto localOperation = makeOpAndRecordId(2, 1); auto remoteOperation = makeOpAndRecordId(3, 1); auto result = syncRollBackLocalOperations(OplogInterfaceMock({localOperation, commonOperation}), OplogInterfaceMock({remoteOperation}), [&](const BSONObj& operation) { FAIL("Should not reach here"); return Status::OK(); }); ASSERT_EQUALS(ErrorCodes::NoMatchingDocument, result.getStatus().code()); ASSERT_STRING_CONTAINS(result.getStatus().reason(), "RS100 reached beginning of remote oplog [1]"); } } // namespace