Add default service config to GrpcClientProperties

Ensure that it doesn't conflict with health service configuration.

Also ensure random port is used in all tests.

Fixes gh-171 (as far as we can)
This commit is contained in:
Dave Syer
2025-06-16 09:22:23 +01:00
parent ced5936c02
commit 08988c042f
9 changed files with 50 additions and 13 deletions

View File

@@ -15,7 +15,8 @@ import org.springframework.grpc.sample.proto.SimpleGrpc;
import org.springframework.test.annotation.DirtiesContext;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT,
properties = "spring.grpc.client.default-channel.address=0.0.0.0:${local.grpc.port}")
properties = { "spring.grpc.client.default-channel.address=0.0.0.0:${local.grpc.port}",
"spring.grpc.server.port=0" })
@DirtiesContext
public class GrpcServerApplicationTests {

View File

@@ -15,18 +15,21 @@
*/
package org.springframework.grpc.autoconfigure.client;
import io.grpc.ManagedChannelBuilder;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.grpc.autoconfigure.client.GrpcClientProperties.ChannelConfig;
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer;
import org.springframework.grpc.client.interceptor.DefaultDeadlineSetupClientInterceptor;
import org.springframework.util.unit.DataSize;
import io.grpc.ManagedChannelBuilder;
/**
* A {@link GrpcChannelBuilderCustomizer} that maps {@link GrpcClientProperties client
* properties} to a channel builder.
@@ -58,11 +61,14 @@ class ClientPropertiesChannelBuilderCustomizer<T extends ManagedChannelBuilder<T
mapper.from(channel.getKeepAliveTimeout()).to(durationProperty(builder::keepAliveTimeout));
mapper.from(channel.getIdleTimeout()).to(durationProperty(builder::idleTimeout));
mapper.from(channel.isKeepAliveWithoutCalls()).to(builder::keepAliveWithoutCalls);
Map<String, Object> defaultServiceConfig = new HashMap<String, Object>(channel.getServiceConfig());
if (channel.getHealth().isEnabled()) {
String serviceNameToCheck = channel.getHealth().getServiceName() != null
? channel.getHealth().getServiceName() : "";
Map<String, ?> healthCheckConfig = Map.of("healthCheckConfig", Map.of("serviceName", serviceNameToCheck));
builder.defaultServiceConfig(healthCheckConfig);
defaultServiceConfig.put("healthCheckConfig", Map.of("serviceName", serviceNameToCheck));
}
if (!defaultServiceConfig.isEmpty()) {
builder.defaultServiceConfig(defaultServiceConfig);
}
if (channel.getDefaultDeadline() != null && channel.getDefaultDeadline().toMillis() > 0L) {
builder.intercept(new DefaultDeadlineSetupClientInterceptor(channel.getDefaultDeadline()));

View File

@@ -150,6 +150,16 @@ public class GrpcClientProperties implements EnvironmentAware, VirtualTargets {
return this.health;
}
private final Map<String, ?> serviceConfig = new HashMap<>();
/**
* The service config to use for the channel.
* @return the service config
*/
public Map<String, ?> getServiceConfig() {
return this.serviceConfig;
}
/**
* The negotiation type for the channel.
*/

View File

@@ -169,6 +169,17 @@ class GrpcClientPropertiesTests {
assertThat(defaultChannel.getMaxInboundMetadataSize()).isEqualTo(DataSize.ofBytes(256));
}
@Test
void withServiceConfig() {
Map<String, String> map = new HashMap<>();
// we have to at least bind one property or bind() fails
map.put("spring.grpc.client.%s.service-config.something.key".formatted("default-channel"), "value");
GrpcClientProperties properties = bindProperties(map);
var channel = properties.getDefaultChannel();
assertThat(channel.getServiceConfig()).hasSize(1);
assertThat(channel.getServiceConfig().get("something")).isInstanceOf(Map.class);
}
}
@Nested

View File

@@ -138,6 +138,7 @@ class GrpcServerAutoConfigurationTests {
GrpcServiceDiscoverer customGrpcServiceDiscoverer = mock(GrpcServiceDiscoverer.class);
this.contextRunnerWithLifecyle()
.withBean("customGrpcServiceDiscoverer", GrpcServiceDiscoverer.class, () -> customGrpcServiceDiscoverer)
.withPropertyValues("spring.grpc.server.port=0")
.run((context) -> assertThat(context).getBean(GrpcServiceDiscoverer.class)
.isSameAs(customGrpcServiceDiscoverer));
}
@@ -145,6 +146,7 @@ class GrpcServerAutoConfigurationTests {
@Test
void grpcServiceDiscovererAutoConfiguredAsExpected() {
this.contextRunnerWithLifecyle()
.withPropertyValues("spring.grpc.server.port=0")
.run((context) -> assertThat(context).getBean(GrpcServiceDiscoverer.class)
.extracting(GrpcServiceDiscoverer::findServices,
InstanceOfAssertFactories.list(ServerServiceDefinition.class))
@@ -163,6 +165,7 @@ class GrpcServerAutoConfigurationTests {
@Test
void grpcServiceConfigurerAutoConfiguredAsExpected() {
this.contextRunnerWithLifecyle()
.withPropertyValues("spring.grpc.server.port=0")
.run((context) -> assertThat(context).getBean(GrpcServiceConfigurer.class)
.isInstanceOf(DefaultGrpcServiceConfigurer.class));
}

View File

@@ -77,6 +77,7 @@ class GrpcServletAutoConfigurationTests {
void whenGrpcServletNotOnClasspathAutoConfigurationIsSkipped() {
this.contextRunner()
.withClassLoader(new FilteredClassLoader(GrpcServlet.class))
.withPropertyValues("spring.grpc.server.port=0")
.run((context) -> assertThat(context).doesNotHaveBean(GrpcServletConfiguration.class)
.doesNotHaveBean(ServletRegistrationBean.class));
}

View File

@@ -117,6 +117,7 @@ class GrpcServerHealthAutoConfigurationTests {
.withBean("grpcServicesDiscoverer", GrpcServiceDiscoverer.class, Mockito::mock)
.withBean("sslBundles", SslBundles.class, Mockito::mock)
.withBean(BindableService.class, () -> service)
.withPropertyValues("spring.grpc.server.port=0")
.run((context) -> assertThatBeanDefinitionsContainInOrder(context, GrpcServerHealthAutoConfiguration.class,
GrpcServerFactoryAutoConfiguration.class));
}

View File

@@ -116,7 +116,7 @@ class OAuth2ResourceServerAutoConfigurationTests {
.withBean(BindableService.class, () -> service)
.withBean("noopServerLifecycle", GrpcServerLifecycle.class, Mockito::mock)
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:9000",
"spring.grpc.server.servlet.enabled=false")
"spring.grpc.server.servlet.enabled=false", "spring.grpc.server.port=0")
.run((context) -> {
assertThat(context).hasSingleBean(AuthenticationProcessInterceptor.class);
});

View File

@@ -62,7 +62,8 @@ class InProcessTestAutoConfigurationTests {
@Test
void whenTestInProcessEnabledPropIsSetToTrueDoesAutoConfigureBeans() {
this.contextRunner()
.withPropertyValues("spring.grpc.test.inprocess.enabled=true", "spring.grpc.server.inprocess.name=foo")
.withPropertyValues("spring.grpc.test.inprocess.enabled=true", "spring.grpc.server.inprocess.name=foo",
"spring.grpc.server.port=0")
.run((context) -> {
assertThat(context).getBeans(GrpcServerFactory.class)
.containsOnlyKeys("testInProcessGrpcServerFactory", "nettyGrpcServerFactory");
@@ -73,18 +74,21 @@ class InProcessTestAutoConfigurationTests {
@Test
void whenTestInProcessEnabledPropIsNotSetDoesNotAutoConfigureBeans() {
this.contextRunner().withPropertyValues("spring.grpc.server.inprocess.name=foo").run((context) -> {
assertThat(context).getBeans(GrpcServerFactory.class)
.containsOnlyKeys("inProcessGrpcServerFactory", "nettyGrpcServerFactory");
assertThat(context).getBeans(GrpcChannelFactory.class)
.containsOnlyKeys("inProcessGrpcChannelFactory", "nettyGrpcChannelFactory");
});
this.contextRunner()
.withPropertyValues("spring.grpc.server.inprocess.name=foo", "spring.grpc.server.port=0")
.run((context) -> {
assertThat(context).getBeans(GrpcServerFactory.class)
.containsOnlyKeys("inProcessGrpcServerFactory", "nettyGrpcServerFactory");
assertThat(context).getBeans(GrpcChannelFactory.class)
.containsOnlyKeys("inProcessGrpcChannelFactory", "nettyGrpcChannelFactory");
});
}
@Test
void whenTestInProcessEnabledPropIsSetToFalseDoesNotAutoConfigureBeans() {
this.contextRunner()
.withPropertyValues("spring.grpc.test.inprocess.enabled=false", "spring.grpc.server.inprocess.name=foo")
.withPropertyValues("spring.grpc.test.inprocess.enabled=false", "spring.grpc.server.inprocess.name=foo",
"spring.grpc.server.port=0")
.run((context) -> {
assertThat(context).getBeans(GrpcServerFactory.class)
.containsOnlyKeys("inProcessGrpcServerFactory", "nettyGrpcServerFactory");