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(); } }
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()); } }