diff --git a/dev-support/checkstyle.xml b/dev-support/checkstyle.xml index db4954fb49..16dd31ad77 100644 --- a/dev-support/checkstyle.xml +++ b/dev-support/checkstyle.xml @@ -60,6 +60,10 @@ + + + + diff --git a/ratis-common/src/main/java/org/apache/ratis/protocol/RaftClientRequest.java b/ratis-common/src/main/java/org/apache/ratis/protocol/RaftClientRequest.java index b04402fe15..ca7b6cc88a 100644 --- a/ratis-common/src/main/java/org/apache/ratis/protocol/RaftClientRequest.java +++ b/ratis-common/src/main/java/org/apache/ratis/protocol/RaftClientRequest.java @@ -46,11 +46,11 @@ public class RaftClientRequest extends RaftClientMessage { private static final Type WATCH_DEFAULT = new Type( WatchRequestTypeProto.newBuilder().setIndex(0L).setReplication(ReplicationLevel.MAJORITY).build()); - private static final Type READ_AFTER_WRITE_CONSISTENT_DEFAULT - = new Type(ReadRequestTypeProto.newBuilder().setReadAfterWriteConsistent(true).build()); - private static final Type READ_DEFAULT = new Type(ReadRequestTypeProto.getDefaultInstance()); - private static final Type READ_NONLINEARIZABLE_DEFAULT - = new Type(ReadRequestTypeProto.newBuilder().setPreferNonLinearizable(true).build()); + private static final ReadTypes READ_TYPES = new ReadTypes(); + private static final Type READ_DEFAULT = readRequestType(false, false, false); + private static final Type READ_NONLINEARIZABLE_DEFAULT = readRequestType(true, false, false); + private static final Type READ_AFTER_WRITE_CONSISTENT_DEFAULT = readRequestType(false, true, false); + private static final Type STALE_READ_DEFAULT = new Type(StaleReadRequestTypeProto.getDefaultInstance()); private static final Map WRITE_REQUEST_TYPES; @@ -71,6 +71,44 @@ public static Type writeRequestType(ReplicationLevel replication) { return WRITE_REQUEST_TYPES.get(replication); } + private static final class ReadTypes { + private final Type[] array = new Type[8]; + + private ReadTypes() { + for (int i = 0; i < array.length; i++) { + array[i] = new Type(ReadRequestTypeProto.newBuilder() + .setPreferNonLinearizable((i & 1) != 0) + .setReadAfterWriteConsistent((i & 2) != 0) + .setDummy((i & 4) != 0) + .build()); + } + + assertArray(); + } + + private Type getImpl(boolean nonLinearizable, boolean readAfterWriteConsistent, boolean dummy) { + final int i = (nonLinearizable ? 1 : 0) + | (readAfterWriteConsistent ? 2 : 0) + | (dummy ? 4 : 0); + return array[i]; + } + + Type get(boolean nonLinearizable, boolean readAfterWriteConsistent, boolean dummy) { + if (nonLinearizable && readAfterWriteConsistent) { + throw new IllegalArgumentException("Cannot be both nonLinearizable and readAfterWriteConsistent"); + } + return getImpl(nonLinearizable, readAfterWriteConsistent, dummy); + } + + private void assertArray() { + for (final Type type : array) { + final ReadRequestTypeProto read = type.getRead(); + final Type got = getImpl(read.getPreferNonLinearizable(), read.getReadAfterWriteConsistent(), read.getDummy()); + Preconditions.assertSame(type, got, "type"); + } + } + } + public static Type writeRequestType() { return writeRequestType(ReplicationLevel.MAJORITY); } @@ -91,6 +129,10 @@ public static Type messageStreamRequestType(long streamId, long messageId, boole .build()); } + public static Type readRequestType(boolean nonLinearizable, boolean readAfterWriteConsistent, boolean dummy) { + return READ_TYPES.get(nonLinearizable, readAfterWriteConsistent, dummy); + } + public static Type readAfterWriteConsistentRequestType() { return READ_AFTER_WRITE_CONSISTENT_DEFAULT; } @@ -312,6 +354,22 @@ public RaftClientRequest build() { return new RaftClientRequest(this); } + public Builder set(RaftClientRequest request) { + this.clientId = request.getClientId(); + this.serverId = request.getServerId(); + this.groupId = request.getRaftGroupId(); + this.callId = request.getCallId(); + this.toLeader = request.isToLeader(); + this.repliedCallIds = request.getRepliedCallIds(); + this.message = request.getMessage(); + this.type = request.getType(); + this.slidingWindowEntry = request.getSlidingWindowEntry(); + this.routingTable = request.getRoutingTable(); + this.timeoutMs = request.getTimeoutMs(); + this.spanContext = request.getSpanContext(); + return this; + } + public Builder setClientId(ClientId clientId) { this.clientId = clientId; return this; diff --git a/ratis-netty/src/main/java/org/apache/ratis/netty/server/ReadStreamManagement.java b/ratis-netty/src/main/java/org/apache/ratis/netty/server/ReadStreamManagement.java index 5336760a0b..46d169bbfa 100644 --- a/ratis-netty/src/main/java/org/apache/ratis/netty/server/ReadStreamManagement.java +++ b/ratis-netty/src/main/java/org/apache/ratis/netty/server/ReadStreamManagement.java @@ -17,9 +17,11 @@ */ package org.apache.ratis.netty.server; +import org.apache.ratis.client.impl.OrderedAsync; import org.apache.ratis.conf.RaftProperties; import org.apache.ratis.datastream.impl.DataStreamReplyByteBuffer; import org.apache.ratis.datastream.impl.DataStreamRequestByteBuf; +import org.apache.ratis.proto.RaftProtos; import org.apache.ratis.proto.RaftProtos.DataStreamPacketHeaderProto.Type; import org.apache.ratis.proto.RaftProtos.RaftClientRequestProto; import org.apache.ratis.proto.RaftProtos.RaftClientRequestProto.TypeCase; @@ -47,6 +49,7 @@ import static org.apache.ratis.client.impl.ClientProtoUtils.toRaftClientRequest; import static org.apache.ratis.client.impl.ClientProtoUtils.toRaftClientReplyProto; +import static org.apache.ratis.netty.server.DataStreamManagement.newDataStreamReplyByteBuffer; import static org.apache.ratis.netty.server.DataStreamManagement.replyDataStreamException; public class ReadStreamManagement { @@ -60,23 +63,24 @@ static class ReadStream implements WritableByteChannel { private final DataStreamReplyByteBuffer terminalReply; private long streamOffset; - ReadStream(RaftClientRequest request, long streamId, ChannelHandlerContext ctx) { + ReadStream(RaftClientRequest request, long streamId, ChannelHandlerContext ctx, RaftClientReply terminalReply) { this.clientId = request.getClientId(); this.streamId = streamId; this.ctx = ctx; - final RaftClientReply reply = RaftClientReply.newBuilder() - .setRequest(request) - .setSuccess() - .build(); - this.terminalReply = DataStreamReplyByteBuffer.newBuilder() + this.terminalReply = newReadStreamTerminalReply(clientId, streamId, terminalReply); + } + + private static DataStreamReplyByteBuffer newReadStreamTerminalReply( + ClientId clientId, long streamId, RaftClientReply reply) { + return DataStreamReplyByteBuffer.newBuilder() .setClientId(clientId) .setType(Type.STREAM_HEADER) .setStreamId(streamId) .setStreamOffset(0) .setBuffer(toRaftClientReplyProto(reply).toByteString().asReadOnlyByteBuffer()) - .setSuccess(true) - .setBytesWritten(0) + .setSuccess(reply.isSuccess()) + .setCommitInfos(reply.getCommitInfos()) .build(); } @@ -186,17 +190,45 @@ private boolean processImpl(DataStreamRequestByteBuf requestBuf, ChannelHandlerC return true; } - final ReadStream stream = new ReadStream(request, requestBuf.getStreamId(), ctx); - requestExecutor.execute(() -> { + final CompletableFuture readCheck; + try { + readCheck = server.submitClientRequestAsync(newDummyReadRequest(request)); + } catch (IOException e) { + replyDataStreamException(server, e, request, requestBuf, ctx); + return true; + } + + readCheck.whenCompleteAsync((readCheckReply, exception) -> { + if (exception != null) { + replyDataStreamException(server, exception, request, requestBuf, ctx); + return; + } + + if (!readCheckReply.isSuccess()) { + ctx.writeAndFlush(newDataStreamReplyByteBuffer(requestBuf, readCheckReply)); + return; + } + + final ReadStream stream = new ReadStream(request, requestBuf.getStreamId(), ctx, readCheckReply); try { division.getStateMachine().data().query(request.getMessage(), stream); } catch (Throwable t) { LOG.error("{}: Failed read-only data stream query for {}", this, request, t); } - }); + }, requestExecutor); return true; } + private static RaftClientRequest newDummyReadRequest(RaftClientRequest request) { + final RaftProtos.ReadRequestTypeProto original = request.getType().getRead(); + return RaftClientRequest.newBuilder() + .set(request) + .setMessage(OrderedAsync.DUMMY) + .setType(RaftClientRequest.readRequestType( + original.getPreferNonLinearizable(), original.getReadAfterWriteConsistent(), true)) + .build(); + } + @Override public String toString() { return name; diff --git a/ratis-proto/src/main/proto/Raft.proto b/ratis-proto/src/main/proto/Raft.proto index eba5de3b74..c1bb9d64ee 100644 --- a/ratis-proto/src/main/proto/Raft.proto +++ b/ratis-proto/src/main/proto/Raft.proto @@ -302,6 +302,7 @@ message ForwardRequestTypeProto { message ReadRequestTypeProto { bool preferNonLinearizable = 1; bool readAfterWriteConsistent = 2; + bool dummy = 3; } message StaleReadRequestTypeProto { diff --git a/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerImpl.java b/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerImpl.java index f758fd0edc..f4e740407a 100644 --- a/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerImpl.java +++ b/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerImpl.java @@ -43,6 +43,7 @@ import org.apache.ratis.proto.RaftProtos.RoleInfoProto; import org.apache.ratis.proto.RaftProtos.StartLeaderElectionReplyProto; import org.apache.ratis.proto.RaftProtos.StartLeaderElectionRequestProto; +import org.apache.ratis.protocol.ClientId; import org.apache.ratis.protocol.ClientInvocationId; import org.apache.ratis.protocol.GroupInfoReply; import org.apache.ratis.protocol.GroupInfoRequest; @@ -260,6 +261,8 @@ public long[] getFollowerMatchIndices() { private final ExecutorService clientExecutor; private final ThreadGroup threadGroup; + private final CompletableFuture dummySuccessReply; + RaftServerImpl(RaftGroup group, StateMachine stateMachine, RaftServerProxy proxy, RaftStorage.StartupOption option) throws IOException { final RaftPeerId id = proxy.getId(); @@ -303,6 +306,13 @@ public long[] getFollowerMatchIndices() { RaftServerConfigKeys.ThreadPool.clientSize(properties), id + "-client"); this.threadGroup = new ThreadGroup(proxy.getThreadGroup(), getMemberId().toString()); + + this.dummySuccessReply = CompletableFuture.completedFuture(RaftClientReply.newBuilder() + .setClientId(ClientId.emptyClientId()) + .setServerId(id) + .setGroupId(group.getGroupId()) + .setSuccess() + .build()); } private long getCommitIndex(RaftPeerId id) { @@ -1113,11 +1123,12 @@ private CompletableFuture readAsync(RaftClientRequest request) if (request.getType().getRead().getPreferNonLinearizable() || readOption == RaftServerConfigKeys.Read.Option.DEFAULT) { final CompletableFuture reply = checkLeaderState(request); - if (reply != null) { - return reply; - } - return queryStateMachine(request); - } else if (readOption == RaftServerConfigKeys.Read.Option.LINEARIZABLE){ + if (reply != null) { + return reply; + } + return isDummyRead(request) ? CompletableFuture.completedFuture(newSuccessReply(request)) + : queryStateMachine(request); + } else if (readOption == RaftServerConfigKeys.Read.Option.LINEARIZABLE) { final LeaderStateImpl leader = role.getLeaderState().orElse(null); final CompletableFuture replyFuture; if (leader != null) { @@ -1136,12 +1147,17 @@ private CompletableFuture readAsync(RaftClientRequest request) return replyFuture .thenCompose(readIndex -> getState().getReadRequests().waitToAdvance(readIndex, () -> getReadException("add", snapshotInstallationHandler.getInProgressInstallSnapshotIndex(), false))) - .thenCompose(readIndex -> queryStateMachine(request)) + .thenCompose(readIndex -> isDummyRead(request) + ? CompletableFuture.completedFuture(newSuccessReply(request)) : queryStateMachine(request)) .exceptionally(e -> readException2Reply(request, e)); } else { throw new IllegalStateException("Unexpected read option: " + readOption); } } + private static boolean isDummyRead(RaftClientRequest request) { + return request.getMessage() != null && OrderedAsync.DUMMY.getContent().equals(request.getMessage().getContent()); + } + private RaftClientReply readException2Reply(RaftClientRequest request, Throwable e) { e = JavaUtils.unwrapCompletionException(e); if (e instanceof StateMachineException ) { @@ -1183,6 +1199,9 @@ private CompletableFuture streamEndOfRequestAsync(RaftClientR } CompletableFuture queryStateMachine(RaftClientRequest request) { + if (request.getType().getRead().getDummy()) { + return dummySuccessReply; + } return processQueryFuture(stateMachine.query(request.getMessage()), request); } diff --git a/ratis-test/src/test/java/org/apache/ratis/client/impl/TestDataStreamClientImpl.java b/ratis-test/src/test/java/org/apache/ratis/client/impl/TestDataStreamClientImpl.java index 523c49f053..a85a9f4830 100644 --- a/ratis-test/src/test/java/org/apache/ratis/client/impl/TestDataStreamClientImpl.java +++ b/ratis-test/src/test/java/org/apache/ratis/client/impl/TestDataStreamClientImpl.java @@ -22,9 +22,7 @@ import org.apache.ratis.client.api.DataStreamInput; import org.apache.ratis.conf.RaftProperties; import org.apache.ratis.datastream.DataStreamObserver; -import org.apache.ratis.datastream.impl.DataStreamReplyByteBuf; import org.apache.ratis.datastream.impl.DataStreamRequestByteBuffer; -import org.apache.ratis.proto.RaftProtos.DataStreamPacketHeaderProto.Type; import org.apache.ratis.proto.RaftProtos.RaftClientRequestProto; import org.apache.ratis.protocol.ClientId; import org.apache.ratis.protocol.DataStreamReply; @@ -32,7 +30,6 @@ import org.apache.ratis.protocol.RaftClientRequest; import org.apache.ratis.protocol.RaftGroupId; import org.apache.ratis.protocol.RaftPeer; -import org.apache.ratis.thirdparty.io.netty.buffer.Unpooled; import org.apache.ratis.util.ReferenceCountedObject; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -69,20 +66,6 @@ public CompletableFuture streamAsync( return future; } - RaftClientRequest getRequest() { - return request.get(); - } - - void receive(DataStreamReplyByteBuf reply) { - final ReferenceCountedObject ref = DataStreamReplyByteBuf.asReferenceCounted(reply); - ref.retain(); - try { - replyHandler.get().onNext(ref); - } finally { - ref.release(); - } - } - void complete() { replyHandler.get().onCompleted(); replyFuture.get().complete(null); diff --git a/ratis-test/src/test/java/org/apache/ratis/datastream/DataStreamClusterTests.java b/ratis-test/src/test/java/org/apache/ratis/datastream/DataStreamClusterTests.java index 95e9bef49c..7478c36287 100644 --- a/ratis-test/src/test/java/org/apache/ratis/datastream/DataStreamClusterTests.java +++ b/ratis-test/src/test/java/org/apache/ratis/datastream/DataStreamClusterTests.java @@ -32,9 +32,6 @@ import org.apache.ratis.proto.RaftProtos.DataStreamPacketHeaderProto.Type; import org.apache.ratis.proto.RaftProtos.ReplicationLevel; import org.apache.ratis.protocol.DataStreamReply; -import org.apache.ratis.protocol.DataStreamRequest; -import org.apache.ratis.protocol.DataStreamRequestHeader; -import org.apache.ratis.protocol.Message; import org.apache.ratis.protocol.RaftClientReply; import org.apache.ratis.protocol.RaftClientRequest; import org.apache.ratis.retry.RetryPolicies; diff --git a/ratis-test/src/test/java/org/apache/ratis/netty/server/TestDataStreamManagement.java b/ratis-test/src/test/java/org/apache/ratis/netty/server/TestDataStreamManagement.java index 188f119fca..56ecd166bc 100644 --- a/ratis-test/src/test/java/org/apache/ratis/netty/server/TestDataStreamManagement.java +++ b/ratis-test/src/test/java/org/apache/ratis/netty/server/TestDataStreamManagement.java @@ -19,6 +19,7 @@ import org.apache.ratis.client.impl.ClientProtoUtils; import org.apache.ratis.client.impl.DataStreamClientImpl.DataStreamOutputImpl; +import org.apache.ratis.client.impl.OrderedAsync; import org.apache.ratis.conf.RaftProperties; import org.apache.ratis.datastream.impl.DataStreamReplyByteBuffer; import org.apache.ratis.datastream.impl.DataStreamRequestByteBuf; @@ -28,10 +29,12 @@ import org.apache.ratis.protocol.ClientId; import org.apache.ratis.protocol.DataStreamReply; import org.apache.ratis.protocol.Message; +import org.apache.ratis.protocol.RaftClientReply; import org.apache.ratis.protocol.RaftClientRequest; import org.apache.ratis.protocol.RaftGroupId; import org.apache.ratis.protocol.RaftPeer; import org.apache.ratis.protocol.RaftPeerId; +import org.apache.ratis.protocol.exceptions.ReadIndexException; import org.apache.ratis.server.RaftServer; import org.apache.ratis.statemachine.StateMachine; import org.apache.ratis.statemachine.StateMachine.DataApi; @@ -59,14 +62,17 @@ import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; class TestDataStreamManagement { @@ -96,8 +102,9 @@ public void query(Message request, WritableByteChannel stream) { assertTrue(management.process(readOnlyRequest.request, embeddedChannel.pipeline().firstContext())); assertEquals(0, readOnlyRequest.headerBuf.refCnt()); + JavaUtils.attempt(() -> assertNotNull(streamRef.get()), 10, + TimeDuration.valueOf(100, TimeUnit.MILLISECONDS), "read-only stream", null); final WritableByteChannel stream = streamRef.get(); - assertNotNull(stream); stream.write(response.asReadOnlyByteBuffer()); stream.close(); @@ -116,6 +123,99 @@ public void query(Message request, WritableByteChannel stream) { assertTrue(ClientProtoUtils.getRaftClientReply(replies.get(1)).isSuccess()); } finally { embeddedChannel.finishAndReleaseAll(); + management.shutdown(); + } + } + + @Test + void readOnlyRequestWaitsForLinearizableCheck() throws Exception { + final RaftPeerId serverId = RaftPeerId.valueOf("s1"); + final ClientId clientId = ClientId.randomId(); + final RaftGroupId groupId = RaftGroupId.randomId(); + final ByteString query = ByteString.copyFromUtf8("query"); + final CompletableFuture readOnlyCheck = new CompletableFuture<>(); + final AtomicReference submittedReadOnlyCheck = new AtomicReference<>(); + final AtomicReference messageRef = new AtomicReference<>(); + final AtomicReference streamRef = new AtomicReference<>(); + + final DataApi dataApi = new DataApi() { + @Override + public void query(Message request, WritableByteChannel stream) { + messageRef.set(request); + streamRef.set(stream); + } + }; + final ReadStreamManagement management = newReadStreamManagement(serverId, groupId, dataApi, request -> { + submittedReadOnlyCheck.set(request); + return readOnlyCheck; + }); + final EmbeddedChannel embeddedChannel = new EmbeddedChannel(new ChannelInboundHandlerAdapter()); + final ReadOnlyRequest readOnlyRequest = newReadOnlyRequest(clientId, serverId, groupId, 1L, query); + + try { + assertTrue(management.process(readOnlyRequest.request, embeddedChannel.pipeline().firstContext())); + assertEquals(0, readOnlyRequest.headerBuf.refCnt()); + + final RaftClientRequest checkRequest = submittedReadOnlyCheck.get(); + assertNotNull(checkRequest); + assertEquals(OrderedAsync.DUMMY.getContent(), checkRequest.getMessage().getContent()); + assertNull(streamRef.get(), "state machine query should wait for the read-only check"); + + readOnlyCheck.complete(RaftClientReply.newBuilder().setRequest(checkRequest).setSuccess().build()); + JavaUtils.attempt(() -> assertNotNull(streamRef.get()), 10, + TimeDuration.valueOf(100, TimeUnit.MILLISECONDS), "linearizable read-only stream", null); + assertEquals(query, messageRef.get().getContent()); + } finally { + embeddedChannel.finishAndReleaseAll(); + management.shutdown(); + } + } + + @Test + void readOnlyCheckFailureSkipsStateMachineQuery() throws Exception { + final RaftPeerId serverId = RaftPeerId.valueOf("s1"); + final ClientId clientId = ClientId.randomId(); + final RaftGroupId groupId = RaftGroupId.randomId(); + final ByteString query = ByteString.copyFromUtf8("query"); + final AtomicBoolean queryCalled = new AtomicBoolean(); + + final DataApi dataApi = new DataApi() { + @Override + public void query(Message request, WritableByteChannel stream) { + queryCalled.set(true); + } + }; + final ReadStreamManagement management = newReadStreamManagement(serverId, groupId, dataApi, request -> + CompletableFuture.completedFuture(RaftClientReply.newBuilder() + .setRequest(request) + .setException(new ReadIndexException("read index failed")) + .build())); + final EmbeddedChannel embeddedChannel = new EmbeddedChannel(new ChannelInboundHandlerAdapter()); + final ReadOnlyRequest readOnlyRequest = newReadOnlyRequest(clientId, serverId, groupId, 1L, query); + + try { + assertTrue(management.process(readOnlyRequest.request, embeddedChannel.pipeline().firstContext())); + assertEquals(0, readOnlyRequest.headerBuf.refCnt()); + + final List replies = new ArrayList<>(); + JavaUtils.attempt(() -> { + for (Object outbound; (outbound = embeddedChannel.readOutbound()) != null;) { + replies.add((DataStreamReply) outbound); + } + assertEquals(1, replies.size()); + }, 10, TimeDuration.valueOf(100, TimeUnit.MILLISECONDS), "read-only check failure reply", null); + + assertFalse(queryCalled.get(), "state machine query should not run when the read-only check fails"); + final DataStreamReply reply = replies.get(0); + assertEquals(Type.STREAM_HEADER, reply.getType()); + assertFalse(reply.isSuccess()); + final RaftClientReply clientReply = ClientProtoUtils.getRaftClientReply(reply); + assertFalse(clientReply.isSuccess()); + assertNotNull(clientReply.getReadIndexException()); + assertEquals(serverId, clientReply.getServerId()); + } finally { + embeddedChannel.finishAndReleaseAll(); + management.shutdown(); } } @@ -231,13 +331,19 @@ private static class ReadOnlyRequest { private static ReadStreamManagement newReadStreamManagement( RaftPeerId serverId, RaftGroupId groupId, DataApi dataApi) { + return newReadStreamManagement(serverId, groupId, dataApi, TestDataStreamManagement::successReply); + } + + private static ReadStreamManagement newReadStreamManagement(RaftPeerId serverId, RaftGroupId groupId, + DataApi dataApi, Function> submitClientRequestAsync) { final StateMachine stateMachine = new BaseStateMachine() { @Override public DataApi data() { return dataApi; } }; - final RaftServer server = newRaftServer(serverId, new RaftProperties(), groupId, newDivision(stateMachine)); + final RaftServer server = newRaftServer(serverId, new RaftProperties(), groupId, newDivision(stateMachine), + submitClientRequestAsync); return new ReadStreamManagement(server); } @@ -275,6 +381,16 @@ private static RaftServer newRaftServer(RaftPeerId serverId, RaftProperties prop private static RaftServer newRaftServer(RaftPeerId serverId, RaftProperties properties, RaftGroupId groupId, RaftServer.Division division) { + return newRaftServer(serverId, properties, groupId, division, TestDataStreamManagement::successReply); + } + + private static CompletableFuture successReply(RaftClientRequest request) { + return CompletableFuture.completedFuture(RaftClientReply.newBuilder().setRequest(request).setSuccess().build()); + } + + private static RaftServer newRaftServer(RaftPeerId serverId, RaftProperties properties, + RaftGroupId groupId, RaftServer.Division division, + Function> submitClientRequestAsync) { return (RaftServer) Proxy.newProxyInstance(RaftServer.class.getClassLoader(), new Class[]{RaftServer.class}, (proxy, method, args) -> { switch (method.getName()) { @@ -287,6 +403,8 @@ private static RaftServer newRaftServer(RaftPeerId serverId, RaftProperties prop return division; } throw new IOException("Division not found: " + args[0]); + case "submitClientRequestAsync": + return submitClientRequestAsync.apply((RaftClientRequest) args[0]); case "close": return null; case "toString":