void NetworkInterfaceASIO::_completedWriteCallback(AsyncOp* op) {
    // If we were told to send an empty message, toRecv will be empty here.

    // TODO: handle metadata readers
    const auto elapsed = [this, op]() { return now() - op->start(); };

    if (op->toRecv()->empty()) {
        LOG(3) << "received an empty message";
        return _completeOperation(op, RemoteCommandResponse(BSONObj(), BSONObj(), elapsed()));
    }

    try {
        auto reply = rpc::makeReply(op->toRecv());

        if (reply->getProtocol() != op->operationProtocol()) {
            return _completeOperation(op,
                                      Status(ErrorCodes::RPCProtocolNegotiationFailed,
                                             str::stream()
                                                 << "Mismatched RPC protocols - request was '"
                                                 << opToString(op->toSend()->operation()) << "' '"
                                                 << " but reply was '"
                                                 << opToString(op->toRecv()->operation()) << "'"));
        }

        _completeOperation(op,
                           // unavoidable copy
                           RemoteCommandResponse(reply->getCommandReply().getOwned(),
                                                 reply->getMetadata().getOwned(),
                                                 elapsed()));
    } catch (...) {
        // makeReply can throw if the reply was invalid.
        _completeOperation(op, exceptionToStatus());
    }
}
void NetworkInterfaceASIO::_beginCommunication(AsyncOp* op) {
    // The way that we connect connections for the connection pool is by
    // starting the callback chain with connect(), but getting off at the first
    // _beginCommunication. I.e. all AsyncOp's start off with _inSetup == true
    // and arrive here as they're connected and authed. Once they hit here, we
    // return to the connection pool's get() callback with _inSetup == false,
    // so we can proceed with user operations after they return to this
    // codepath.
    if (op->_inSetup) {
        log() << "Successfully connected to " << op->request().target.toString();
        op->_inSetup = false;
        op->finish(RemoteCommandResponse());
        return;
    }

    LOG(3) << "Initiating asynchronous command: " << redact(op->request().toString());

    auto beginStatus = op->beginCommand(op->request());
    if (!beginStatus.isOK()) {
        return _completeOperation(op, beginStatus);
    }

    _asyncRunCommand(op, [this, op](std::error_code ec, size_t bytes) {
        _validateAndRun(op, ec, [this, op]() { _completedOpCallback(op); });
    });
}
ResponseStatus NetworkInterfaceASIO::_responseFromMessage(const Message& received,
                                                          rpc::Protocol protocol) {
    try {
        // TODO: elapsed isn't going to be correct here, SERVER-19697
        auto start = now();
        auto reply = rpc::makeReply(&received);

        if (reply->getProtocol() != protocol) {
            auto requestProtocol = rpc::toString(static_cast<rpc::ProtocolSet>(protocol));
            if (!requestProtocol.isOK())
                return requestProtocol.getStatus();

            return Status(ErrorCodes::RPCProtocolNegotiationFailed,
                          str::stream() << "Mismatched RPC protocols - request was '"
                                        << requestProtocol.getValue().toString() << "' '"
                                        << " but reply was '" << opToString(received.operation())
                                        << "'");
        }

        // unavoidable copy
        auto ownedCommandReply = reply->getCommandReply().getOwned();
        auto ownedReplyMetadata = reply->getMetadata().getOwned();
        return ResponseStatus(
            RemoteCommandResponse(ownedCommandReply, ownedReplyMetadata, now() - start));
    } catch (...) {
        // makeReply can throw if the reply was invalid.
        return exceptionToStatus();
    }
}
Exemple #4
0
void NetworkTestEnv::onFindWithMetadataCommand(OnFindCommandWithMetadataFunction func) {
    onCommandWithMetadata([&func](const RemoteCommandRequest& request) -> RemoteCommandResponse {
        const auto& resultStatus = func(request);

        if (!resultStatus.isOK()) {
            return resultStatus.getStatus();
        }

        std::vector<BSONObj> result;
        BSONObj metadata;
        std::tie(result, metadata) = resultStatus.getValue();

        BSONArrayBuilder arr;
        for (const auto& obj : result) {
            arr.append(obj);
        }

        const NamespaceString nss =
            NamespaceString(request.dbname, request.cmdObj.firstElement().String());
        BSONObjBuilder resultBuilder;
        appendCursorResponseObject(0LL, nss.toString(), arr.arr(), &resultBuilder);

        return RemoteCommandResponse(resultBuilder.obj(), metadata, Milliseconds(1));
    });
}
void NetworkInterfaceASIO::_completedOpCallback(AsyncOp* op) {
    // If we were told to send an empty message, toRecv will be empty here.
    if (op->command().toRecv().empty()) {
        LOG(3) << "received an empty message";
        auto elapsed = now() - op->start();
        return _completeOperation(op, RemoteCommandResponse(BSONObj(), BSONObj(), elapsed));
    }

    // TODO: handle metadata readers.
    auto response = _responseFromMessage(op->command().toRecv(), op->operationProtocol());
    _completeOperation(op, response);
}
void NetworkInterfaceASIO::_completedWriteCallback(AsyncOp* op) {
    // If we were told to send an empty message, toRecv will be empty here.

    // TODO: handle metadata SERVER-19156
    BSONObj commandReply;
    if (op->toRecv()->empty()) {
        LOG(3) << "received an empty message";
    } else {
        QueryResult::View qr = op->toRecv()->singleData().view2ptr();
        // unavoidable copy
        commandReply = BSONObj(qr.data()).getOwned();
    }
    _completeOperation(
        op, RemoteCommandResponse(std::move(commandReply), BSONObj(), now() - op->start()));
}
void NetworkInterfaceMock::_connectThenEnqueueOperation_inlock(const HostAndPort& target,
                                                               NetworkOperation&& op) {
    invariant(_hook);  // if there is no hook, we shouldn't even hit this codepath
    invariant(!_connections.count(target));

    auto handshakeReplyIter = _handshakeReplies.find(target);

    auto handshakeReply = (handshakeReplyIter != std::end(_handshakeReplies))
        ? handshakeReplyIter->second
        : RemoteCommandResponse(BSONObj(), BSONObj(), Milliseconds(0));

    auto valid = _hook->validateHost(target, handshakeReply);
    if (!valid.isOK()) {
        op.setResponse(_now_inlock(), valid);
        op.finishResponse();
        return;
    }

    auto swHookPostconnectCommand = _hook->makeRequest(target);

    if (!swHookPostconnectCommand.isOK()) {
        op.setResponse(_now_inlock(), swHookPostconnectCommand.getStatus());
        op.finishResponse();
        return;
    }

    boost::optional<RemoteCommandRequest> hookPostconnectCommand =
        std::move(swHookPostconnectCommand.getValue());

    if (!hookPostconnectCommand) {
        // If we don't have a post connect command, enqueue the actual command.
        _enqueueOperation_inlock(std::move(op));
        _connections.emplace(op.getRequest().target);
        return;
    }

    // The completion handler for the postconnect command schedules the original command.
    auto postconnectCompletionHandler = [this, op](ResponseStatus rs) mutable {
        stdx::lock_guard<stdx::mutex> lk(_mutex);
        if (!rs.isOK()) {
            op.setResponse(_now_inlock(), rs);
            op.finishResponse();
            return;
        }

        auto handleStatus = _hook->handleReply(op.getRequest().target, std::move(rs));

        if (!handleStatus.isOK()) {
            op.setResponse(_now_inlock(), handleStatus);
            op.finishResponse();
            return;
        }

        _enqueueOperation_inlock(std::move(op));
        _connections.emplace(op.getRequest().target);
    };

    auto postconnectOp = NetworkOperation(op.getCallbackHandle(),
                                          std::move(*hookPostconnectCommand),
                                          _now_inlock(),
                                          std::move(postconnectCompletionHandler));

    _enqueueOperation_inlock(std::move(postconnectOp));
}
RemoteCommandRequest NetworkInterfaceMock::scheduleSuccessfulResponse(const BSONObj& response) {
    BSONObj metadata;
    return scheduleSuccessfulResponse(RemoteCommandResponse(response, metadata, Milliseconds(0)));
}
StatusWith<RemoteCommandResponse> RemoteCommandRunnerImpl::runCommand(
    const RemoteCommandRequest& request) {
    try {
        const Date_t requestStartDate = Date_t::now();
        const auto timeoutMillis = getTimeoutMillis(request.expirationDate, requestStartDate);
        if (!timeoutMillis.isOK()) {
            return StatusWith<RemoteCommandResponse>(timeoutMillis.getStatus());
        }

        ConnectionPool::ConnectionPtr conn(
            &_connPool, request.target, requestStartDate, timeoutMillis.getValue());

        BSONObj output;
        BSONObj metadata;

        // If remote server does not support either find or getMore commands, down convert
        // to using DBClientInterface::query()/getMore().
        // Perform down conversion based on wire protocol version.

        // 'commandName' will be an empty string if the command object is an empty BSON
        // document.
        StringData commandName = request.cmdObj.firstElement().fieldNameStringData();
        const auto isFindCmd = commandName == QueryRequest::kFindCommandName;
        const auto isGetMoreCmd = commandName == GetMoreRequest::kGetMoreCommandName;
        const auto isFindOrGetMoreCmd = isFindCmd || isGetMoreCmd;

        // We are using the wire version to check if we need to downconverting find/getMore
        // requests because coincidentally, the find/getMore command is only supported by
        // servers that also accept OP_COMMAND.
        bool supportsFindAndGetMoreCommands = rpc::supportsWireVersionForOpCommandInMongod(
            conn.get()->getMinWireVersion(), conn.get()->getMaxWireVersion());

        if (!isFindOrGetMoreCmd || supportsFindAndGetMoreCommands) {
            rpc::UniqueReply commandResponse =
                conn.get()->runCommandWithMetadata(request.dbname,
                                                   request.cmdObj.firstElementFieldName(),
                                                   request.metadata,
                                                   request.cmdObj);

            output = commandResponse->getCommandReply().getOwned();
            metadata = commandResponse->getMetadata().getOwned();
        } else if (isFindCmd) {
            return runDownconvertedFindCommand(conn.get(), request);
        } else if (isGetMoreCmd) {
            return runDownconvertedGetMoreCommand(conn.get(), request);
        }

        const Date_t requestFinishDate = Date_t::now();
        conn.done(requestFinishDate);

        return StatusWith<RemoteCommandResponse>(
            RemoteCommandResponse(std::move(output),
                                  std::move(metadata),
                                  Milliseconds(requestFinishDate - requestStartDate)));
    } catch (const DBException& ex) {
        return StatusWith<RemoteCommandResponse>(ex.toStatus());
    } catch (const std::exception& ex) {
        return StatusWith<RemoteCommandResponse>(
            ErrorCodes::UnknownError,
            str::stream() << "Sending command " << request.cmdObj << " on database "
                          << request.dbname
                          << " over network to "
                          << request.target.toString()
                          << " received exception "
                          << ex.what());
    }
}