Skip to content

Commit 71a3964

Browse files
committed
Add request drain flow control verification test.
Add givenRequestDrainActive_whenAppRequestsMessages_thenRequestsDrained to verify that early request(n) calls are buffered when IDLE, and successfully drained to the data plane server upon activation if only request draining is active (which should not block the response/read path). TAG=agy CONV=2c1e4760-c239-4698-810a-162bf10fccc4
1 parent f270d8f commit 71a3964

1 file changed

Lines changed: 140 additions & 0 deletions

File tree

xds/src/test/java/io/grpc/xds/ExternalProcessorClientInterceptorTest.java

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9157,6 +9157,146 @@ public void request(int numMessages) {
91579157
channelManager.close();
91589158
}
91599159

9160+
@Test
9161+
@SuppressWarnings("unchecked")
9162+
public void givenRequestDrainActive_whenAppRequestsMessages_thenRequestsDrained()
9163+
throws Exception {
9164+
ExternalProcessor proto = ExternalProcessor.newBuilder()
9165+
.setGrpcService(GrpcService.newBuilder()
9166+
.setGoogleGrpc(GrpcService.GoogleGrpc.newBuilder()
9167+
.setTargetUri("in-process:///" + extProcServerName)
9168+
.addChannelCredentialsPlugin(Any.newBuilder()
9169+
.setTypeUrl("type.googleapis.com/envoy.extensions.grpc_service."
9170+
+ "channel_credentials.insecure.v3.InsecureCredentials")
9171+
.build())
9172+
.build())
9173+
.build())
9174+
.setProcessingMode(ProcessingMode.newBuilder()
9175+
.setRequestBodyMode(ProcessingMode.BodySendMode.GRPC)
9176+
.setRequestTrailerMode(ProcessingMode.HeaderSendMode.SEND)
9177+
.build())
9178+
.build();
9179+
ConfigOrError<ExternalProcessorFilterConfig> configOrError =
9180+
provider.parseFilterConfig(Any.pack(proto), filterContext);
9181+
assertThat(configOrError.errorDetail).isNull();
9182+
ExternalProcessorFilterConfig filterConfig = configOrError.config;
9183+
9184+
final CountDownLatch drainSentLatch = new CountDownLatch(1);
9185+
final CountDownLatch headersReceivedLatch = new CountDownLatch(1);
9186+
final CountDownLatch sendDrainLatch = new CountDownLatch(1);
9187+
final CountDownLatch filterSentDrainCompleteLatch = new CountDownLatch(1);
9188+
// External Processor Server
9189+
ExternalProcessorGrpc.ExternalProcessorImplBase extProcImpl;
9190+
extProcImpl = new ExternalProcessorGrpc.ExternalProcessorImplBase() {
9191+
@Override
9192+
@SuppressWarnings("unchecked")
9193+
public StreamObserver<ProcessingRequest> process(
9194+
final StreamObserver<ProcessingResponse> responseObserver) {
9195+
((ServerCallStreamObserver<ProcessingResponse>) responseObserver).request(100);
9196+
return new StreamObserver<ProcessingRequest>() {
9197+
@Override
9198+
public void onNext(ProcessingRequest request) {
9199+
if (request.hasRequestHeaders()) {
9200+
headersReceivedLatch.countDown();
9201+
new Thread(() -> {
9202+
try {
9203+
sendDrainLatch.await();
9204+
synchronized (responseObserver) {
9205+
responseObserver.onNext(ProcessingResponse.newBuilder()
9206+
.setRequestDrainRequests(true)
9207+
.build());
9208+
}
9209+
drainSentLatch.countDown();
9210+
} catch (Exception e) {}
9211+
}).start();
9212+
} else if (request.hasRequestBody()) {
9213+
if (request.getRequestBody().getDrainComplete()) {
9214+
filterSentDrainCompleteLatch.countDown();
9215+
}
9216+
}
9217+
}
9218+
9219+
@Override
9220+
public void onError(Throwable t) {
9221+
}
9222+
9223+
@Override
9224+
public void onCompleted() {
9225+
}
9226+
};
9227+
}
9228+
};
9229+
grpcCleanup.register(InProcessServerBuilder.forName(extProcServerName)
9230+
.addService(extProcImpl)
9231+
.directExecutor()
9232+
.build().start());
9233+
9234+
CachedChannelManager channelManager = new CachedChannelManager(config -> {
9235+
return grpcCleanup.register(
9236+
InProcessChannelBuilder.forName(extProcServerName).directExecutor().build());
9237+
});
9238+
9239+
dataPlaneServiceRegistry.addService(ServerServiceDefinition.builder("test.TestService")
9240+
.addMethod(METHOD_SAY_HELLO, ServerCalls.asyncUnaryCall(
9241+
(request, responseObserver) -> {
9242+
responseObserver.onNext("Hello " + request);
9243+
responseObserver.onCompleted();
9244+
}))
9245+
.build());
9246+
9247+
final AtomicInteger dataPlaneRequestCount = new AtomicInteger();
9248+
ExternalProcessorClientInterceptor interceptor = new ExternalProcessorClientInterceptor(
9249+
filterConfig, channelManager, scheduler, FAKE_CONTEXT);
9250+
9251+
ManagedChannel dataPlaneChannel = grpcCleanup.register(
9252+
InProcessChannelBuilder.forName(dataPlaneServerName)
9253+
.directExecutor()
9254+
.intercept(new ClientInterceptor() {
9255+
@Override
9256+
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
9257+
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
9258+
return new io.grpc.ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
9259+
next.newCall(method, callOptions)) {
9260+
@Override
9261+
public void request(int numMessages) {
9262+
dataPlaneRequestCount.addAndGet(numMessages);
9263+
super.request(numMessages);
9264+
}
9265+
};
9266+
}
9267+
})
9268+
.build());
9269+
9270+
CallOptions callOptions = DEFAULT_CALL_OPTIONS.withExecutor(MoreExecutors.directExecutor());
9271+
ClientCall<String, String> proxyCall =
9272+
interceptCall(interceptor, METHOD_SAY_HELLO, callOptions, dataPlaneChannel);
9273+
proxyCall.start(new ClientCall.Listener<String>() {}, new Metadata());
9274+
9275+
// Wait for headers to reach mock server
9276+
assertThat(headersReceivedLatch.await(5, TimeUnit.SECONDS)).isTrue();
9277+
9278+
// App requests messages early (before drain is received)
9279+
proxyCall.request(3);
9280+
9281+
// Verify requests are buffered and not sent to data plane yet (call is IDLE)
9282+
assertThat(dataPlaneRequestCount.get()).isEqualTo(0);
9283+
9284+
// Now trigger drain (which also activates the call)
9285+
sendDrainLatch.countDown();
9286+
9287+
// Wait for drain to be processed
9288+
assertThat(drainSentLatch.await(5, TimeUnit.SECONDS)).isTrue();
9289+
9290+
// Verify requests are now drained to data plane (request drain does not block response path)
9291+
assertThat(dataPlaneRequestCount.get()).isEqualTo(3);
9292+
9293+
// Wait for filter to send drain_complete to mock server
9294+
assertThat(filterSentDrainCompleteLatch.await(5, TimeUnit.SECONDS)).isTrue();
9295+
9296+
proxyCall.cancel("Cleanup", null);
9297+
channelManager.close();
9298+
}
9299+
91609300
@Test
91619301
@SuppressWarnings("unchecked")
91629302
public void givenBufferedRequests_whenExtProcStreamBecomesReady_thenDataPlaneDrained()

0 commit comments

Comments
 (0)