Implement actor client metadata. (#1165)

Signed-off-by: Artur Souza <asouza.pro@gmail.com>
Co-authored-by: Cassie Coyle <cassie@diagrid.io>
This commit is contained in:
Artur Souza 2024-12-03 13:52:14 -08:00 committed by GitHub
parent bd1667b043
commit fcdf3c3188
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 92 additions and 20 deletions

View File

@ -22,6 +22,9 @@ import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder; import io.grpc.ManagedChannelBuilder;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import java.util.Collections;
import java.util.Map;
/** /**
* Holds a client for Dapr sidecar communication. ActorClient should be reused. * Holds a client for Dapr sidecar communication. ActorClient should be reused.
*/ */
@ -59,7 +62,7 @@ public class ActorClient implements AutoCloseable {
* @param overrideProperties Override properties. * @param overrideProperties Override properties.
*/ */
public ActorClient(Properties overrideProperties) { public ActorClient(Properties overrideProperties) {
this(buildManagedChannel(overrideProperties), null, overrideProperties.getValue(Properties.API_TOKEN)); this(overrideProperties, null);
} }
/** /**
@ -69,21 +72,38 @@ public class ActorClient implements AutoCloseable {
* @param resiliencyOptions Client resiliency options. * @param resiliencyOptions Client resiliency options.
*/ */
public ActorClient(Properties overrideProperties, ResiliencyOptions resiliencyOptions) { public ActorClient(Properties overrideProperties, ResiliencyOptions resiliencyOptions) {
this(buildManagedChannel(overrideProperties), resiliencyOptions, overrideProperties.getValue(Properties.API_TOKEN)); this(overrideProperties, null, resiliencyOptions);
}
/**
* Instantiates a new channel for Dapr sidecar communication.
*
* @param overrideProperties Override properties.
* @param metadata gRPC metadata or HTTP headers for actor invocation.
* @param resiliencyOptions Client resiliency options.
*/
public ActorClient(Properties overrideProperties, Map<String, String> metadata, ResiliencyOptions resiliencyOptions) {
this(buildManagedChannel(overrideProperties),
metadata,
resiliencyOptions,
overrideProperties.getValue(Properties.API_TOKEN));
} }
/** /**
* Instantiates a new channel for Dapr sidecar communication. * Instantiates a new channel for Dapr sidecar communication.
* *
* @param grpcManagedChannel gRPC channel. * @param grpcManagedChannel gRPC channel.
* @param metadata gRPC metadata or HTTP headers for actor invocation.
* @param resiliencyOptions Client resiliency options. * @param resiliencyOptions Client resiliency options.
* @param daprApiToken Dapr API token.
*/ */
private ActorClient( private ActorClient(
ManagedChannel grpcManagedChannel, ManagedChannel grpcManagedChannel,
Map<String, String> metadata,
ResiliencyOptions resiliencyOptions, ResiliencyOptions resiliencyOptions,
String daprApiToken) { String daprApiToken) {
this.grpcManagedChannel = grpcManagedChannel; this.grpcManagedChannel = grpcManagedChannel;
this.daprClient = buildDaprClient(grpcManagedChannel, resiliencyOptions, daprApiToken); this.daprClient = buildDaprClient(grpcManagedChannel, metadata, resiliencyOptions, daprApiToken);
} }
/** /**
@ -137,10 +157,12 @@ public class ActorClient implements AutoCloseable {
*/ */
private static DaprClient buildDaprClient( private static DaprClient buildDaprClient(
Channel grpcManagedChannel, Channel grpcManagedChannel,
Map<String, String> metadata,
ResiliencyOptions resiliencyOptions, ResiliencyOptions resiliencyOptions,
String daprApiToken) { String daprApiToken) {
return new DaprClientImpl( return new DaprClientImpl(
DaprGrpc.newStub(grpcManagedChannel), DaprGrpc.newStub(grpcManagedChannel),
metadata == null ? null : Collections.unmodifiableMap(metadata),
resiliencyOptions, resiliencyOptions,
daprApiToken); daprApiToken);
} }

View File

@ -34,6 +34,7 @@ import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoSink; import reactor.core.publisher.MonoSink;
import reactor.util.context.ContextView; import reactor.util.context.ContextView;
import java.util.Map;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -57,19 +58,30 @@ class DaprClientImpl implements DaprClient {
*/ */
private final DaprClientGrpcInterceptors grpcInterceptors; private final DaprClientGrpcInterceptors grpcInterceptors;
/**
* Metadata for actor invocation requests.
*/
private final Map<String, String> metadata;
/** /**
* Internal constructor. * Internal constructor.
* *
* @param grpcClient Dapr's GRPC client. * @param grpcClient Dapr's GRPC client.
* @param metadata gRPC metadata or HTTP headers for actor server to receive.
* @param resiliencyOptions Client resiliency options (optional). * @param resiliencyOptions Client resiliency options (optional).
* @param daprApiToken Dapr API token (optional). * @param daprApiToken Dapr API token (optional).
*/ */
DaprClientImpl(DaprGrpc.DaprStub grpcClient, ResiliencyOptions resiliencyOptions, String daprApiToken) { DaprClientImpl(
DaprGrpc.DaprStub grpcClient,
Map<String, String> metadata,
ResiliencyOptions resiliencyOptions,
String daprApiToken) {
this.client = grpcClient; this.client = grpcClient;
this.grpcInterceptors = new DaprClientGrpcInterceptors(daprApiToken, this.grpcInterceptors = new DaprClientGrpcInterceptors(daprApiToken,
new TimeoutPolicy(resiliencyOptions == null ? null : resiliencyOptions.getTimeout())); new TimeoutPolicy(resiliencyOptions == null ? null : resiliencyOptions.getTimeout()));
this.retryPolicy = new RetryPolicy( this.retryPolicy = new RetryPolicy(
resiliencyOptions == null ? null : resiliencyOptions.getMaxRetries()); resiliencyOptions == null ? null : resiliencyOptions.getMaxRetries());
this.metadata = metadata == null ? Map.of() : metadata;
} }
/** /**
@ -82,6 +94,7 @@ class DaprClientImpl implements DaprClient {
.setActorType(actorType) .setActorType(actorType)
.setActorId(actorId) .setActorId(actorId)
.setMethod(methodName) .setMethod(methodName)
.putAllMetadata(this.metadata)
.setData(jsonPayload == null ? ByteString.EMPTY : ByteString.copyFrom(jsonPayload)) .setData(jsonPayload == null ? ByteString.EMPTY : ByteString.copyFrom(jsonPayload))
.build(); .build();
return Mono.deferContextual( return Mono.deferContextual(

View File

@ -106,7 +106,7 @@ public class DaprGrpcClientTest {
InProcessChannelBuilder.forName(serverName).directExecutor().build()); InProcessChannelBuilder.forName(serverName).directExecutor().build());
// Create a HelloWorldClient using the in-process channel; // Create a HelloWorldClient using the in-process channel;
client = new DaprClientImpl(DaprGrpc.newStub(channel), null, null); client = new DaprClientImpl(DaprGrpc.newStub(channel), null, null, null);
} }
@Test @Test

View File

@ -187,11 +187,19 @@ public class DaprRun implements Stoppable {
} }
public ActorClient newActorClient() { public ActorClient newActorClient() {
return this.newActorClient(null); return this.newActorClient(null, null);
}
public ActorClient newActorClient(Map<String, String> metadata) {
return this.newActorClient(metadata, null);
} }
public ActorClient newActorClient(ResiliencyOptions resiliencyOptions) { public ActorClient newActorClient(ResiliencyOptions resiliencyOptions) {
return new ActorClient(new Properties(this.getPropertyOverrides()), resiliencyOptions); return this.newActorClient(null, resiliencyOptions);
}
public ActorClient newActorClient(Map<String, String> metadata, ResiliencyOptions resiliencyOptions) {
return new ActorClient(new Properties(this.getPropertyOverrides()), metadata, resiliencyOptions);
} }
public void waitForAppHealth(int maxWaitMilliseconds) throws InterruptedException { public void waitForAppHealth(int maxWaitMilliseconds) throws InterruptedException {

View File

@ -16,12 +16,17 @@ package io.dapr.it.actors;
import io.dapr.actors.ActorId; import io.dapr.actors.ActorId;
import io.dapr.actors.client.ActorProxyBuilder; import io.dapr.actors.client.ActorProxyBuilder;
import io.dapr.it.BaseIT; import io.dapr.it.BaseIT;
import io.dapr.it.DaprRun;
import io.dapr.it.actors.app.MyActor; import io.dapr.it.actors.app.MyActor;
import io.dapr.it.actors.app.MyActorService; import io.dapr.it.actors.app.MyActorService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.Map;
import static io.dapr.it.Retry.callWithRetry; import static io.dapr.it.Retry.callWithRetry;
import static io.dapr.it.TestUtils.assertThrowsDaprExceptionSubstring; import static io.dapr.it.TestUtils.assertThrowsDaprExceptionSubstring;
@ -30,23 +35,24 @@ public class ActorExceptionIT extends BaseIT {
private static Logger logger = LoggerFactory.getLogger(ActorExceptionIT.class); private static Logger logger = LoggerFactory.getLogger(ActorExceptionIT.class);
@Test private static DaprRun run;
public void exceptionTest() throws Exception {
@BeforeAll
public static void start() throws Exception {
// The call below will fail if service cannot start successfully. // The call below will fail if service cannot start successfully.
var run = startDaprApp( run = startDaprApp(
ActorExceptionIT.class.getSimpleName(), ActorExceptionIT.class.getSimpleName(),
MyActorService.SUCCESS_MESSAGE, MyActorService.SUCCESS_MESSAGE,
MyActorService.class, MyActorService.class,
true, true,
60000); 60000);
}
logger.debug("Creating proxy builder"); @Test
public void exceptionTest() throws Exception {
ActorProxyBuilder<MyActor> proxyBuilder = ActorProxyBuilder<MyActor> proxyBuilder =
new ActorProxyBuilder("MyActorTest", MyActor.class, deferClose(run.newActorClient())); new ActorProxyBuilder("MyActorTest", MyActor.class, deferClose(run.newActorClient()));
logger.debug("Creating actorId"); MyActor proxy = proxyBuilder.build(new ActorId("1"));
ActorId actorId1 = new ActorId("1");
logger.debug("Building proxy");
MyActor proxy = proxyBuilder.build(actorId1);
callWithRetry(() -> { callWithRetry(() -> {
assertThrowsDaprExceptionSubstring( assertThrowsDaprExceptionSubstring(
@ -55,4 +61,20 @@ public class ActorExceptionIT extends BaseIT {
() -> proxy.throwException()); () -> proxy.throwException());
}, 10000); }, 10000);
} }
@Test
public void exceptionDueToMetadataTest() throws Exception {
// Setting this HTTP header via actor metadata will cause the Actor HTTP server to error.
Map<String, String> metadata = Map.of("Content-Length", "9999");
ActorProxyBuilder<MyActor> proxyBuilderMetadataOverride =
new ActorProxyBuilder("MyActorTest", MyActor.class, deferClose(run.newActorClient(metadata)));
MyActor proxyWithMetadata = proxyBuilderMetadataOverride.build(new ActorId("2"));
callWithRetry(() -> {
assertThrowsDaprExceptionSubstring(
"INTERNAL",
"ContentLength=9999 with Body length 13",
() -> proxyWithMetadata.say("hello world"));
}, 10000);
}
} }

View File

@ -15,7 +15,7 @@ package io.dapr.internal.grpc;
import io.dapr.internal.grpc.interceptors.DaprApiTokenInterceptor; import io.dapr.internal.grpc.interceptors.DaprApiTokenInterceptor;
import io.dapr.internal.grpc.interceptors.DaprAppIdInterceptor; import io.dapr.internal.grpc.interceptors.DaprAppIdInterceptor;
import io.dapr.internal.grpc.interceptors.DaprMetadataInterceptor; import io.dapr.internal.grpc.interceptors.DaprMetadataReceiverInterceptor;
import io.dapr.internal.grpc.interceptors.DaprTimeoutInterceptor; import io.dapr.internal.grpc.interceptors.DaprTimeoutInterceptor;
import io.dapr.internal.grpc.interceptors.DaprTracingInterceptor; import io.dapr.internal.grpc.interceptors.DaprTracingInterceptor;
import io.dapr.internal.resiliency.TimeoutPolicy; import io.dapr.internal.resiliency.TimeoutPolicy;
@ -35,10 +35,18 @@ public class DaprClientGrpcInterceptors {
private final TimeoutPolicy timeoutPolicy; private final TimeoutPolicy timeoutPolicy;
/**
* Instantiates a holder of all gRPC interceptors.
*/
public DaprClientGrpcInterceptors() { public DaprClientGrpcInterceptors() {
this(null, null); this(null, null);
} }
/**
* Instantiates a holder of all gRPC interceptors.
* @param daprApiToken Dapr API token.
* @param timeoutPolicy Timeout Policy.
*/
public DaprClientGrpcInterceptors(String daprApiToken, TimeoutPolicy timeoutPolicy) { public DaprClientGrpcInterceptors(String daprApiToken, TimeoutPolicy timeoutPolicy) {
this.daprApiToken = daprApiToken; this.daprApiToken = daprApiToken;
this.timeoutPolicy = timeoutPolicy; this.timeoutPolicy = timeoutPolicy;
@ -118,7 +126,7 @@ public class DaprClientGrpcInterceptors {
new DaprApiTokenInterceptor(this.daprApiToken), new DaprApiTokenInterceptor(this.daprApiToken),
new DaprTimeoutInterceptor(this.timeoutPolicy), new DaprTimeoutInterceptor(this.timeoutPolicy),
new DaprTracingInterceptor(context), new DaprTracingInterceptor(context),
new DaprMetadataInterceptor(metadataConsumer)); new DaprMetadataReceiverInterceptor(metadataConsumer));
} }
} }

View File

@ -14,7 +14,6 @@ limitations under the License.
package io.dapr.internal.grpc.interceptors; package io.dapr.internal.grpc.interceptors;
import io.dapr.client.Headers; import io.dapr.client.Headers;
import io.dapr.config.Properties;
import io.grpc.CallOptions; import io.grpc.CallOptions;
import io.grpc.Channel; import io.grpc.Channel;
import io.grpc.ClientCall; import io.grpc.ClientCall;

View File

@ -27,7 +27,7 @@ import java.util.function.Consumer;
/** /**
* Consumes gRPC metadata. * Consumes gRPC metadata.
*/ */
public class DaprMetadataInterceptor implements ClientInterceptor { public class DaprMetadataReceiverInterceptor implements ClientInterceptor {
private final Consumer<Metadata> metadataConsumer; private final Consumer<Metadata> metadataConsumer;
@ -35,7 +35,7 @@ public class DaprMetadataInterceptor implements ClientInterceptor {
* Creates an instance of the consumer for gRPC metadata. * Creates an instance of the consumer for gRPC metadata.
* @param metadataConsumer gRPC metadata consumer * @param metadataConsumer gRPC metadata consumer
*/ */
public DaprMetadataInterceptor(Consumer<Metadata> metadataConsumer) { public DaprMetadataReceiverInterceptor(Consumer<Metadata> metadataConsumer) {
this.metadataConsumer = metadataConsumer; this.metadataConsumer = metadataConsumer;
} }