mirror of https://github.com/dapr/java-sdk.git
chore: Add grpc keepalives (#1382)
* chore: Add grpc keepalives Signed-off-by: Javier Aliaga <javier@diagrid.io> * chore: Make grpc keepalive configurable Signed-off-by: Javier Aliaga <javier@diagrid.io> * chore: Fix review comments Signed-off-by: Javier Aliaga <javier@diagrid.io> * chore: Missing keepalive config for GRPC TLS INSECURE Signed-off-by: Javier Aliaga <javier@diagrid.io> * chore: Add test Signed-off-by: Javier Aliaga <javier@diagrid.io> * fix: Comment typo Signed-off-by: Javier Aliaga <javier@diagrid.io> --------- Signed-off-by: Javier Aliaga <javier@diagrid.io>
This commit is contained in:
parent
e4cc0303fa
commit
e84d2c4e61
|
@ -145,6 +145,38 @@ public class Properties {
|
|||
"DAPR_GRPC_ENDPOINT",
|
||||
null);
|
||||
|
||||
/**
|
||||
* GRPC enable keep alive.
|
||||
*/
|
||||
public static final Property<Boolean> GRPC_ENABLE_KEEP_ALIVE = new BooleanProperty(
|
||||
"dapr.grpc.enableKeepAlive",
|
||||
"DAPR_GRPC_ENABLE_KEEP_ALIVE",
|
||||
false);
|
||||
|
||||
/**
|
||||
* GRPC keep alive time in seconds.
|
||||
*/
|
||||
public static final Property<Duration> GRPC_KEEP_ALIVE_TIME_SECONDS = new SecondsDurationProperty(
|
||||
"dapr.grpc.keepAliveTimeSeconds",
|
||||
"DAPR_GRPC_KEEP_ALIVE_TIME_SECONDS",
|
||||
Duration.ofSeconds(10));
|
||||
|
||||
/**
|
||||
* GRPC keep alive timeout in seconds.
|
||||
*/
|
||||
public static final Property<Duration> GRPC_KEEP_ALIVE_TIMEOUT_SECONDS = new SecondsDurationProperty(
|
||||
"dapr.grpc.keepAliveTimeoutSeconds",
|
||||
"DAPR_GRPC_KEEP_ALIVE_TIMEOUT_SECONDS",
|
||||
Duration.ofSeconds(5));
|
||||
|
||||
/**
|
||||
* GRPC keep alive without calls.
|
||||
*/
|
||||
public static final Property<Boolean> GRPC_KEEP_ALIVE_WITHOUT_CALLS = new BooleanProperty(
|
||||
"dapr.grpc.keepAliveWithoutCalls",
|
||||
"DAPR_GRPC_KEEP_ALIVE_WITHOUT_CALLS",
|
||||
true);
|
||||
|
||||
/**
|
||||
* GRPC endpoint for remote sidecar connectivity.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright 2025 The Dapr Authors
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package io.dapr.config;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Integer configuration property.
|
||||
*/
|
||||
public class SecondsDurationProperty extends Property<Duration> {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
SecondsDurationProperty(String name, String envName, Duration defaultValue) {
|
||||
super(name, envName, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected Duration parse(String value) {
|
||||
long longValue = Long.parseLong(value);
|
||||
return Duration.ofSeconds(longValue);
|
||||
}
|
||||
|
||||
}
|
|
@ -32,9 +32,15 @@ import java.io.InputStream;
|
|||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static io.dapr.config.Properties.GRPC_ENABLE_KEEP_ALIVE;
|
||||
import static io.dapr.config.Properties.GRPC_ENDPOINT;
|
||||
import static io.dapr.config.Properties.GRPC_KEEP_ALIVE_TIMEOUT_SECONDS;
|
||||
import static io.dapr.config.Properties.GRPC_KEEP_ALIVE_TIME_SECONDS;
|
||||
import static io.dapr.config.Properties.GRPC_KEEP_ALIVE_WITHOUT_CALLS;
|
||||
import static io.dapr.config.Properties.GRPC_PORT;
|
||||
import static io.dapr.config.Properties.GRPC_TLS_CA_PATH;
|
||||
import static io.dapr.config.Properties.GRPC_TLS_CERT_PATH;
|
||||
|
@ -68,8 +74,8 @@ public final class NetworkUtils {
|
|||
|
||||
private static final String GRPC_ENDPOINT_HOSTNAME_REGEX_PART = "(([A-Za-z0-9_\\-\\.]+)|(\\[" + IPV6_REGEX + "\\]))";
|
||||
|
||||
private static final String GRPC_ENDPOINT_DNS_AUTHORITY_REGEX_PART =
|
||||
"(?<dnsWithAuthority>dns://)(?<authorityEndpoint>"
|
||||
private static final String GRPC_ENDPOINT_DNS_AUTHORITY_REGEX_PART = "(?<dnsWithAuthority>dns://)"
|
||||
+ "(?<authorityEndpoint>"
|
||||
+ GRPC_ENDPOINT_HOSTNAME_REGEX_PART + ":[0-9]+)?/";
|
||||
|
||||
private static final String GRPC_ENDPOINT_PARAM_REGEX_PART = "(\\?(?<param>tls\\=((true)|(false))))?";
|
||||
|
@ -140,11 +146,19 @@ public final class NetworkUtils {
|
|||
if (interceptors != null && interceptors.length > 0) {
|
||||
builder = builder.intercept(interceptors);
|
||||
}
|
||||
|
||||
if (settings.enableKeepAlive) {
|
||||
builder.keepAliveTime(settings.keepAliveTimeSeconds.toSeconds(), TimeUnit.SECONDS)
|
||||
.keepAliveTimeout(settings.keepAliveTimeoutSeconds.toSeconds(), TimeUnit.SECONDS)
|
||||
.keepAliveWithoutCalls(settings.keepAliveWithoutCalls);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
} catch (Exception e) {
|
||||
throw new DaprException(
|
||||
new DaprError().setErrorCode("TLS_CREDENTIALS_ERROR")
|
||||
.setMessage("Failed to create insecure TLS credentials"), e);
|
||||
.setMessage("Failed to create insecure TLS credentials"),
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,23 +169,24 @@ public final class NetworkUtils {
|
|||
ManagedChannelBuilder<?> builder = ManagedChannelBuilder.forTarget(settings.endpoint);
|
||||
|
||||
if (clientCertPath != null && clientKeyPath != null) {
|
||||
// mTLS case - using client cert and key, with optional CA cert for server authentication
|
||||
// mTLS case - using client cert and key, with optional CA cert for server
|
||||
// authentication
|
||||
try (
|
||||
InputStream clientCertInputStream = new FileInputStream(clientCertPath);
|
||||
InputStream clientKeyInputStream = new FileInputStream(clientKeyPath);
|
||||
InputStream caCertInputStream = caCertPath != null ? new FileInputStream(caCertPath) : null
|
||||
) {
|
||||
InputStream caCertInputStream = caCertPath != null ? new FileInputStream(caCertPath) : null) {
|
||||
TlsChannelCredentials.Builder builderCreds = TlsChannelCredentials.newBuilder()
|
||||
.keyManager(clientCertInputStream, clientKeyInputStream); // For client authentication
|
||||
.keyManager(clientCertInputStream, clientKeyInputStream); // For client authentication
|
||||
if (caCertInputStream != null) {
|
||||
builderCreds.trustManager(caCertInputStream); // For server authentication
|
||||
builderCreds.trustManager(caCertInputStream); // For server authentication
|
||||
}
|
||||
ChannelCredentials credentials = builderCreds.build();
|
||||
builder = Grpc.newChannelBuilder(settings.endpoint, credentials);
|
||||
} catch (IOException e) {
|
||||
throw new DaprException(
|
||||
new DaprError().setErrorCode("TLS_CREDENTIALS_ERROR")
|
||||
.setMessage("Failed to create mTLS credentials" + (caCertPath != null ? " with CA cert" : "")), e);
|
||||
.setMessage("Failed to create mTLS credentials" + (caCertPath != null ? " with CA cert" : "")),
|
||||
e);
|
||||
}
|
||||
} else if (caCertPath != null) {
|
||||
// Simple TLS case - using CA cert only for server authentication
|
||||
|
@ -183,7 +198,8 @@ public final class NetworkUtils {
|
|||
} catch (IOException e) {
|
||||
throw new DaprException(
|
||||
new DaprError().setErrorCode("TLS_CREDENTIALS_ERROR")
|
||||
.setMessage("Failed to create TLS credentials with CA cert"), e);
|
||||
.setMessage("Failed to create TLS credentials with CA cert"),
|
||||
e);
|
||||
}
|
||||
} else if (!settings.secure) {
|
||||
builder = builder.usePlaintext();
|
||||
|
@ -194,6 +210,13 @@ public final class NetworkUtils {
|
|||
if (interceptors != null && interceptors.length > 0) {
|
||||
builder = builder.intercept(interceptors);
|
||||
}
|
||||
|
||||
if (settings.enableKeepAlive) {
|
||||
builder.keepAliveTime(settings.keepAliveTimeSeconds.toSeconds(), TimeUnit.SECONDS)
|
||||
.keepAliveTimeout(settings.keepAliveTimeoutSeconds.toSeconds(), TimeUnit.SECONDS)
|
||||
.keepAliveWithoutCalls(settings.keepAliveWithoutCalls);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
@ -205,13 +228,24 @@ public final class NetworkUtils {
|
|||
final String tlsCertPath;
|
||||
final String tlsCaPath;
|
||||
|
||||
final boolean enableKeepAlive;
|
||||
final Duration keepAliveTimeSeconds;
|
||||
final Duration keepAliveTimeoutSeconds;
|
||||
final boolean keepAliveWithoutCalls;
|
||||
|
||||
private GrpcEndpointSettings(
|
||||
String endpoint, boolean secure, String tlsPrivateKeyPath, String tlsCertPath, String tlsCaPath) {
|
||||
String endpoint, boolean secure, String tlsPrivateKeyPath, String tlsCertPath, String tlsCaPath,
|
||||
boolean enableKeepAlive, Duration keepAliveTimeSeconds, Duration keepAliveTimeoutSeconds,
|
||||
boolean keepAliveWithoutCalls) {
|
||||
this.endpoint = endpoint;
|
||||
this.secure = secure;
|
||||
this.tlsPrivateKeyPath = tlsPrivateKeyPath;
|
||||
this.tlsCertPath = tlsCertPath;
|
||||
this.tlsCaPath = tlsCaPath;
|
||||
this.enableKeepAlive = enableKeepAlive;
|
||||
this.keepAliveTimeSeconds = keepAliveTimeSeconds;
|
||||
this.keepAliveTimeoutSeconds = keepAliveTimeoutSeconds;
|
||||
this.keepAliveWithoutCalls = keepAliveWithoutCalls;
|
||||
}
|
||||
|
||||
static GrpcEndpointSettings parse(Properties properties) {
|
||||
|
@ -220,6 +254,10 @@ public final class NetworkUtils {
|
|||
String clientKeyPath = properties.getValue(GRPC_TLS_KEY_PATH);
|
||||
String clientCertPath = properties.getValue(GRPC_TLS_CERT_PATH);
|
||||
String caCertPath = properties.getValue(GRPC_TLS_CA_PATH);
|
||||
boolean enablekeepAlive = properties.getValue(GRPC_ENABLE_KEEP_ALIVE);
|
||||
Duration keepAliveTimeSeconds = properties.getValue(GRPC_KEEP_ALIVE_TIME_SECONDS);
|
||||
Duration keepAliveTimeoutSeconds = properties.getValue(GRPC_KEEP_ALIVE_TIMEOUT_SECONDS);
|
||||
boolean keepAliveWithoutCalls = properties.getValue(GRPC_KEEP_ALIVE_WITHOUT_CALLS);
|
||||
|
||||
boolean secure = false;
|
||||
String grpcEndpoint = properties.getValue(GRPC_ENDPOINT);
|
||||
|
@ -257,30 +295,33 @@ public final class NetworkUtils {
|
|||
var authorityEndpoint = matcher.group("authorityEndpoint");
|
||||
if (authorityEndpoint != null) {
|
||||
return new GrpcEndpointSettings(
|
||||
String.format(
|
||||
"dns://%s/%s:%d",
|
||||
authorityEndpoint,
|
||||
address,
|
||||
port
|
||||
), secure, clientKeyPath, clientCertPath, caCertPath);
|
||||
String.format(
|
||||
"dns://%s/%s:%d",
|
||||
authorityEndpoint,
|
||||
address,
|
||||
port),
|
||||
secure, clientKeyPath, clientCertPath, caCertPath, enablekeepAlive, keepAliveTimeSeconds,
|
||||
keepAliveTimeoutSeconds, keepAliveWithoutCalls);
|
||||
}
|
||||
|
||||
var socket = matcher.group("socket");
|
||||
if (socket != null) {
|
||||
return new GrpcEndpointSettings(socket, secure, clientKeyPath, clientCertPath, caCertPath);
|
||||
return new GrpcEndpointSettings(socket, secure, clientKeyPath, clientCertPath, caCertPath, enablekeepAlive,
|
||||
keepAliveTimeSeconds, keepAliveTimeoutSeconds, keepAliveWithoutCalls);
|
||||
}
|
||||
|
||||
var vsocket = matcher.group("vsocket");
|
||||
if (vsocket != null) {
|
||||
return new GrpcEndpointSettings(vsocket, secure, clientKeyPath, clientCertPath, caCertPath);
|
||||
return new GrpcEndpointSettings(vsocket, secure, clientKeyPath, clientCertPath, caCertPath, enablekeepAlive,
|
||||
keepAliveTimeSeconds, keepAliveTimeoutSeconds, keepAliveWithoutCalls);
|
||||
}
|
||||
}
|
||||
|
||||
return new GrpcEndpointSettings(String.format(
|
||||
"dns:///%s:%d",
|
||||
address,
|
||||
port
|
||||
), secure, clientKeyPath, clientCertPath, caCertPath);
|
||||
"dns:///%s:%d",
|
||||
address,
|
||||
port), secure, clientKeyPath, clientCertPath, caCertPath, enablekeepAlive, keepAliveTimeSeconds,
|
||||
keepAliveTimeoutSeconds, keepAliveWithoutCalls);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ package io.dapr.utils;
|
|||
|
||||
import io.dapr.config.Properties;
|
||||
import io.dapr.exceptions.DaprException;
|
||||
import io.dapr.utils.NetworkUtils.GrpcEndpointSettings;
|
||||
import io.grpc.ManagedChannel;
|
||||
import org.junit.Assert;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
|
@ -591,4 +592,44 @@ public class NetworkUtilsTest {
|
|||
// Verify the channel is active and using TLS (not plaintext)
|
||||
Assertions.assertFalse(channel.isTerminated(), "Channel should be active");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildGrpcManagedChannelWithKeepAliveDefaults() throws Exception {
|
||||
var properties = new Properties(Map.of(
|
||||
Properties.GRPC_ENABLE_KEEP_ALIVE.getName(), "true"
|
||||
));
|
||||
|
||||
channel = NetworkUtils.buildGrpcManagedChannel(properties);
|
||||
channels.add(channel);
|
||||
|
||||
// Verify the channel is active and using TLS (not plaintext)
|
||||
Assertions.assertFalse(channel.isTerminated(), "Channel should be active");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultKeepAliveSettings() throws Exception {
|
||||
Properties properties = new Properties();
|
||||
|
||||
GrpcEndpointSettings settings = NetworkUtils.GrpcEndpointSettings.parse(properties);
|
||||
Assertions.assertEquals(false, settings.enableKeepAlive);
|
||||
Assertions.assertEquals(10, settings.keepAliveTimeSeconds.getSeconds());
|
||||
Assertions.assertEquals(5, settings.keepAliveTimeoutSeconds.getSeconds());
|
||||
Assertions.assertEquals(true, settings.keepAliveWithoutCalls);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultKeepAliveOverride() throws Exception {
|
||||
Properties properties = new Properties(Map.of(
|
||||
Properties.GRPC_ENABLE_KEEP_ALIVE.getName(), "true",
|
||||
Properties.GRPC_KEEP_ALIVE_TIME_SECONDS.getName(), "100",
|
||||
Properties.GRPC_KEEP_ALIVE_TIMEOUT_SECONDS.getName(), "50",
|
||||
Properties.GRPC_KEEP_ALIVE_WITHOUT_CALLS.getName(), "false"
|
||||
));
|
||||
|
||||
GrpcEndpointSettings settings = NetworkUtils.GrpcEndpointSettings.parse(properties);
|
||||
Assertions.assertEquals(true, settings.enableKeepAlive);
|
||||
Assertions.assertEquals(100, settings.keepAliveTimeSeconds.getSeconds());
|
||||
Assertions.assertEquals(50, settings.keepAliveTimeoutSeconds.getSeconds());
|
||||
Assertions.assertEquals(false, settings.keepAliveWithoutCalls);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue