TEST(ParticipantList, ReceiveParticipantListMissingParticipantThatAlreadyVotedCommitThrows) {
    ParticipantList participantList;
    participantList.recordVoteCommit(ShardId("shard0000"), dummyTimestamp);
    ASSERT_THROWS_CODE(participantList.recordFullList({ShardId("shard0001")}),
                       AssertionException,
                       ErrorCodes::InternalError);
}
TEST(ParticipantList, ReceiveConflictingParticipantListsFirstListIsSupersetOfSecondThrows) {
    ParticipantList participantList;
    participantList.recordFullList({ShardId("shard0000"), ShardId("shard0001")});
    ASSERT_THROWS_CODE(participantList.recordFullList({ShardId("shard0000")}),
                       AssertionException,
                       ErrorCodes::InternalError);
}
TEST(ParticipantList, ReceiveVoteCommitFromParticipantNotInListThrows) {
    ParticipantList participantList;
    participantList.recordFullList({ShardId("shard0000")});
    ASSERT_THROWS_CODE(participantList.recordVoteCommit(ShardId("shard0001"), dummyTimestamp),
                       AssertionException,
                       ErrorCodes::InternalError);
}
TEST(ParticipantList, ParticipantChangesPrepareTimestampThrows) {
    ParticipantList participantList;
    participantList.recordVoteCommit(ShardId("shard0000"), Timestamp::min());
    ASSERT_THROWS_CODE(participantList.recordVoteCommit(ShardId("shard0000"), Timestamp::max()),
                       AssertionException,
                       ErrorCodes::InternalError);
}
TEST(ParticipantList, ReceiveConflictingParticipantListsNoOverlapThrows) {
    ParticipantList participantList;
    participantList.recordFullList({ShardId("shard0000"), ShardId("shard0001")});
    ASSERT_THROWS_CODE(participantList.recordFullList({ShardId("shard0002"), ShardId("shard0003")}),
                       AssertionException,
                       ErrorCodes::InternalError);
}
TEST(ParticipantList, ParticipantChangesVoteFromCommitToAbortThrows) {
    ParticipantList participantList;
    participantList.recordVoteCommit(ShardId("shard0000"), dummyTimestamp);
    ASSERT_THROWS_CODE(participantList.recordVoteAbort(ShardId("shard0000")),
                       AssertionException,
                       ErrorCodes::InternalError);
}
Esempio n. 7
0
std::vector<AsyncRequestsSender::Response> TransactionRouter::abortTransaction(
    OperationContext* opCtx, bool isImplicit) {
    // The router has yet to send any commands to a remote shard for this transaction.
    // Return the same error that would have been returned by a shard.
    uassert(ErrorCodes::NoSuchTransaction,
            "no known command has been sent by this router for this transaction",
            !_participants.empty());

    auto abortCmd = BSON("abortTransaction" << 1);

    std::vector<AsyncRequestsSender::Request> abortRequests;
    for (const auto& participantEntry : _participants) {
        abortRequests.emplace_back(ShardId(participantEntry.first), abortCmd);
    }

    // Implicit aborts log earlier.
    if (!isImplicit) {
        LOG(0) << _txnIdToString() << " Aborting transaction on " << _participants.size()
               << " shard(s)";
    }

    return gatherResponses(opCtx,
                           NamespaceString::kAdminDb,
                           ReadPreferenceSetting{ReadPreference::PrimaryOnly},
                           Shard::RetryPolicy::kIdempotent,
                           abortRequests);
}
Esempio n. 8
0
void CollectionShardingState::onDeleteOp(OperationContext* txn,
                                         const CollectionShardingState::DeleteState& deleteState) {
    dassert(txn->lockState()->isCollectionLockedForMode(_nss.ns(), MODE_IX));

    if (txn->writesAreReplicated() && serverGlobalParams.clusterRole == ClusterRole::ShardServer &&
        _nss == NamespaceString::kConfigCollectionNamespace) {
        if (auto idElem = deleteState.idDoc["_id"]) {
            uassert(40070,
                    "cannot delete shardIdentity document while in --shardsvr mode",
                    idElem.str() != ShardIdentityType::IdName);
        }
    }

    // For backwards compatibility, cancel a pending asynchronous addShard task created on the
    // primary config as a result of a 3.2 mongos doing addShard for the shard with id
    // deletedDocId.
    if (serverGlobalParams.clusterRole == ClusterRole::ConfigServer &&
        _nss == ShardType::ConfigNS) {
        BSONElement idElement = deleteState.idDoc["_id"];
        invariant(!idElement.eoo());
        auto shardIdStr = idElement.valuestrsafe();
        txn->recoveryUnit()->registerChange(
            new RemoveShardLogOpHandler(txn, ShardId(std::move(shardIdStr))));
    }

    checkShardVersionOrThrow(txn);

    if (_sourceMgr && deleteState.isMigrating) {
        _sourceMgr->getCloner()->onDeleteOp(txn, deleteState.idDoc);
    }
}
Esempio n. 9
0
std::vector<ShardId> TransactionRouter::_getPendingParticipants() const {
    std::vector<ShardId> pendingParticipants;
    for (const auto& participant : _participants) {
        if (participant.second.stmtIdCreatedAt == _latestStmtId) {
            pendingParticipants.emplace_back(ShardId(participant.first));
        }
    }
    return pendingParticipants;
}
Esempio n. 10
0
StatusWith<SetShardVersionRequest> SetShardVersionRequest::parseFromBSON(const BSONObj& cmdObj) {
    SetShardVersionRequest request;

    {
        std::string shardName;
        Status status = bsonExtractStringField(cmdObj, kShardName, &shardName);
        request._shardName = ShardId(shardName);

        if (!status.isOK())
            return status;
    }

    {
        std::string shardCS;
        Status status = bsonExtractStringField(cmdObj, kShardConnectionString, &shardCS);
        if (!status.isOK())
            return status;

        auto shardCSStatus = ConnectionString::parse(shardCS);
        if (!shardCSStatus.isOK())
            return shardCSStatus.getStatus();

        request._shardCS = std::move(shardCSStatus.getValue());
    }

    {
        Status status = bsonExtractBooleanFieldWithDefault(cmdObj, kInit, false, &request._init);
        if (!status.isOK())
            return status;
    }

    {
        Status status = bsonExtractBooleanFieldWithDefault(
            cmdObj, kAuthoritative, false, &request._isAuthoritative);
        if (!status.isOK())
            return status;
    }

    {
        Status status = bsonExtractBooleanFieldWithDefault(
            cmdObj, kNoConnectionVersioning, false, &request._noConnectionVersioning);
        if (!status.isOK())
            return status;
    }

    if (request.isInit()) {
        return request;
    }

    // Only initialize the version information if this is not an "init" request

    {
        std::string ns;
        Status status = bsonExtractStringField(cmdObj, kCmdName, &ns);
        if (!status.isOK())
            return status;

        NamespaceString nss(ns);

        if (!nss.isValid()) {
            return {ErrorCodes::InvalidNamespace,
                    str::stream() << ns << " is not a valid namespace"};
        }

        request._nss = std::move(nss);
    }

    {
        auto versionStatus = ChunkVersion::parseFromBSONForSetShardVersion(cmdObj);
        if (!versionStatus.isOK())
            return versionStatus.getStatus();

        request._version = versionStatus.getValue();
    }

    return request;
}
Esempio n. 11
0
void CollectionCloner::_establishCollectionCursorsCallback(const RemoteCommandCallbackArgs& rcbd,
                                                           EstablishCursorsCommand cursorCommand) {
    if (_state == State::kShuttingDown) {
        Status shuttingDownStatus{ErrorCodes::CallbackCanceled, "Cloner shutting down."};
        _finishCallback(shuttingDownStatus);
        return;
    }
    auto response = rcbd.response;
    if (!response.isOK()) {
        _finishCallback(response.status);
        return;
    }
    Status commandStatus = getStatusFromCommandResult(response.data);
    if (commandStatus == ErrorCodes::NamespaceNotFound) {
        _finishCallback(Status::OK());
        return;
    }
    if (!commandStatus.isOK()) {
        _finishCallback(commandStatus.withContext(
            str::stream() << "Error querying collection '" << _sourceNss.ns() << "'"));
        return;
    }

    std::vector<CursorResponse> cursorResponses;
    Status parseResponseStatus =
        _parseCursorResponse(response.data, &cursorResponses, cursorCommand);
    if (!parseResponseStatus.isOK()) {
        _finishCallback(parseResponseStatus);
        return;
    }
    LOG(1) << "Collection cloner running with " << cursorResponses.size()
           << " cursors established.";

    // Initialize the 'AsyncResultsMerger'(ARM).
    std::vector<ClusterClientCursorParams::RemoteCursor> remoteCursors;
    for (auto&& cursorResponse : cursorResponses) {
        // A placeholder 'ShardId' is used until the ARM is made less sharding specific.
        remoteCursors.emplace_back(
            ShardId("CollectionClonerSyncSource"), _source, std::move(cursorResponse));
    }

    // An empty list of authenticated users is passed into the cluster parameters
    // as user information is not used in the ARM in context of collection cloning.
    _clusterClientCursorParams =
        stdx::make_unique<ClusterClientCursorParams>(_sourceNss, UserNameIterator());
    _clusterClientCursorParams->remotes = std::move(remoteCursors);
    if (_collectionCloningBatchSize > 0)
        _clusterClientCursorParams->batchSize = _collectionCloningBatchSize;
    Client::initThreadIfNotAlready();
    _arm = stdx::make_unique<AsyncResultsMerger>(
        cc().getOperationContext(), _executor, _clusterClientCursorParams.get());

    // This completion guard invokes _finishCallback on destruction.
    auto cancelRemainingWorkInLock = [this]() { _cancelRemainingWork_inlock(); };
    auto finishCallbackFn = [this](const Status& status) { _finishCallback(status); };
    auto onCompletionGuard =
        std::make_shared<OnCompletionGuard>(cancelRemainingWorkInLock, finishCallbackFn);

    // Lock guard must be declared after completion guard. If there is an error in this function
    // that will cause the destructor of the completion guard to run, the destructor must be run
    // outside the mutex. This is a necessary condition to invoke _finishCallback.
    stdx::lock_guard<stdx::mutex> lock(_mutex);
    Status scheduleStatus = _scheduleNextARMResultsCallback(onCompletionGuard);
    _arm->detachFromOperationContext();
    if (!scheduleStatus.isOK()) {
        onCompletionGuard->setResultAndCancelRemainingWork_inlock(lock, scheduleStatus);
        return;
    }
}
TEST(ParticipantList, ReceiveSameParticipantListMultipleTimesSucceeds) {
    ParticipantList participantList;
    participantList.recordFullList({ShardId("shard0000"), ShardId("shard0001")});
    participantList.recordFullList({ShardId("shard0000"), ShardId("shard0001")});
    participantList.recordFullList({ShardId("shard0000"), ShardId("shard0001")});
}
TEST(ParticipantList, ParticipantResendsVoteCommitSucceeds) {
    ParticipantList participantList;
    participantList.recordVoteCommit(ShardId("shard0000"), dummyTimestamp);
    participantList.recordVoteCommit(ShardId("shard0000"), dummyTimestamp);
}
TEST(ParticipantList, ParticipantResendsVoteAbortSucceeds) {
    ParticipantList participantList;
    participantList.recordVoteAbort(ShardId("shard0001"));
    participantList.recordVoteAbort(ShardId("shard0001"));
}
boost::intrusive_ptr<DocumentSource> DocumentSourceMergeCursors::createFromBson(
    BSONElement elem, const boost::intrusive_ptr<ExpressionContext>& expCtx) {
    if (elem.type() == BSONType::Object) {
        // This is the modern serialization format. We de-serialize using the IDL.
        auto ownedObj = elem.embeddedObject().getOwned();
        auto armParams =
            AsyncResultsMergerParams::parse(IDLParserErrorContext(kStageName), ownedObj);
        return new DocumentSourceMergeCursors(
            Grid::get(expCtx->opCtx)->getExecutorPool()->getArbitraryExecutor(),
            std::move(armParams),
            expCtx,
            std::move(ownedObj));
    }

    // This is the old serialization format which can still be generated by mongos processes
    // older than 4.0.
    // TODO SERVER-34009 Remove support for this format.
    uassert(17026,
            "$mergeCursors stage expected either an array or an object as argument",
            elem.type() == BSONType::Array);
    const auto serializedRemotes = elem.Array();
    uassert(50729,
            "$mergeCursors stage expected array with at least one entry",
            serializedRemotes.size() > 0);

    boost::optional<NamespaceString> nss;
    std::vector<RemoteCursor> remotes;
    for (auto&& cursor : serializedRemotes) {
        BSONElement nsElem;
        BSONElement hostElem;
        BSONElement idElem;
        uassert(17027,
                "$mergeCursors stage requires each cursor in array to be an object",
                cursor.type() == BSONType::Object);
        for (auto&& cursorElem : cursor.Obj()) {
            const auto fieldName = cursorElem.fieldNameStringData();
            if (fieldName == "ns"_sd) {
                nsElem = cursorElem;
            } else if (fieldName == "host"_sd) {
                hostElem = cursorElem;
            } else if (fieldName == "id"_sd) {
                idElem = cursorElem;
            } else {
                uasserted(50730,
                          str::stream() << "Unrecognized option " << fieldName
                                        << " within cursor provided to $mergeCursors: "
                                        << cursor);
            }
        }
        uassert(
            50731,
            "$mergeCursors stage requires \'ns\' field with type string for each cursor in array",
            nsElem.type() == BSONType::String);

        // We require each cursor to have the same namespace. This isn't a fundamental limit of the
        // system, but needs to be true due to the implementation of AsyncResultsMerger, which
        // tracks one namespace for all cursors.
        uassert(50720,
                "$mergeCursors requires each cursor to have the same namespace",
                !nss || nss->ns() == nsElem.valueStringData());
        nss = NamespaceString(nsElem.String());

        uassert(
            50721,
            "$mergeCursors stage requires \'host\' field with type string for each cursor in array",
            hostElem.type() == BSONType::String);
        auto host = uassertStatusOK(HostAndPort::parse(hostElem.valueStringData()));

        uassert(50722,
                "$mergeCursors stage requires \'id\' field with type long for each cursor in array",
                idElem.type() == BSONType::NumberLong);
        auto cursorId = idElem.Long();

        // We are assuming that none of the cursors have been iterated at all, and so will not have
        // any data in the initial batch.
        // TODO SERVER-33323 We use a fake shard id because the AsyncResultsMerger won't use it for
        // anything, and finding the real one is non-trivial.
        RemoteCursor remoteCursor;
        remoteCursor.setShardId(ShardId("fakeShardIdForMergeCursors"));
        remoteCursor.setHostAndPort(std::move(host));
        std::vector<BSONObj> emptyBatch;
        remoteCursor.setCursorResponse(CursorResponse{*nss, cursorId, emptyBatch});
        remotes.push_back(std::move(remoteCursor));
    }
    invariant(nss);  // We know there is at least one cursor in 'serializedRemotes', and we require
                     // each cursor to have a 'ns' field.

    AsyncResultsMergerParams params;
    params.setRemotes(std::move(remotes));
    params.setNss(*nss);
    return new DocumentSourceMergeCursors(
        Grid::get(expCtx->opCtx)->getExecutorPool()->getArbitraryExecutor(),
        std::move(params),
        expCtx,
        elem.embeddedObject().getOwned());
}