Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions spring-grpc-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,19 @@
<artifactId>grpc-stub</artifactId>
</dependency>

<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>


</dependencies>


Expand Down
8 changes: 8 additions & 0 deletions spring-grpc-dependencies/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
<grpc.version>1.63.2</grpc.version>
<protobuf-java.version>3.25.5</protobuf-java.version>
<google-common-protos.version>2.46.0</google-common-protos.version>
<micrometer.version>1.13.6</micrometer.version>
</properties>

<dependencyManagement>
Expand Down Expand Up @@ -112,6 +113,13 @@
<artifactId>proto-google-common-protos</artifactId>
<version>${google-common-protos.version}</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-bom</artifactId>
<version>${micrometer.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.springframework.grpc.autoconfigure.server;

import io.grpc.ServerBuilder;
import io.grpc.ServerInterceptor;
import io.micrometer.core.instrument.binder.grpc.ObservationGrpcServerInterceptor;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.grpc.server.ServerBuilderCustomizer;

@AutoConfiguration
@ConditionalOnClass(ObservationGrpcServerInterceptor.class)
@ConditionalOnBean(ObservationRegistry.class)
public class GrpcServerMetricAutoConfiguration {

@Bean
public ServerInterceptor observationGrpcServerInterceptor(ObservationRegistry observationRegistry) {
return new ObservationGrpcServerInterceptor(observationRegistry);
}

@Bean
<T extends ServerBuilder<T>> ServerBuilderCustomizer<T> metricsInterceptor(ServerInterceptor observationGrpcServerInterceptor) {
return (serverBuilder) -> serverBuilder.intercept(observationGrpcServerInterceptor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ private ApplicationContextRunner contextRunner() {
when(service.bindService()).thenReturn(serviceDefinition);
// NOTE: we use noop server lifecycle to avoid startup
return new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(GrpcServerAutoConfiguration.class,
GrpcServerFactoryAutoConfiguration.class, SslAutoConfiguration.class))
.withBean("noopServerLifecycle", GrpcServerLifecycle.class, Mockito::mock)
.withBean(BindableService.class, () -> service);
.withConfiguration(AutoConfigurations.of(GrpcServerAutoConfiguration.class,
GrpcServerFactoryAutoConfiguration.class, SslAutoConfiguration.class))
.withBean("noopServerLifecycle", GrpcServerLifecycle.class, Mockito::mock)
.withBean(BindableService.class, () -> service);
}

private ApplicationContextRunner contextRunnerWithLifecyle() {
Expand All @@ -78,78 +78,78 @@ private ApplicationContextRunner contextRunnerWithLifecyle() {
when(service.bindService()).thenReturn(serviceDefinition);
// NOTE: we use noop server lifecycle to avoid startup
return new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(GrpcServerAutoConfiguration.class,
GrpcServerFactoryAutoConfiguration.class, SslAutoConfiguration.class))
.withBean(BindableService.class, () -> service);
.withConfiguration(AutoConfigurations.of(GrpcServerAutoConfiguration.class,
GrpcServerFactoryAutoConfiguration.class, SslAutoConfiguration.class))
.withBean(BindableService.class, () -> service);
}

@Test
void whenGrpcNotOnClasspathAutoConfigurationIsSkipped() {
this.contextRunner()
.withClassLoader(new FilteredClassLoader(BindableService.class))
.run((context) -> assertThat(context).doesNotHaveBean(GrpcServerAutoConfiguration.class));
.withClassLoader(new FilteredClassLoader(BindableService.class))
.run((context) -> assertThat(context).doesNotHaveBean(GrpcServerAutoConfiguration.class));
}

@Test
void whenNoBindableServicesRegisteredAutoConfigurationIsSkipped() {
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(GrpcServerAutoConfiguration.class))
.run((context) -> assertThat(context).doesNotHaveBean(GrpcServerAutoConfiguration.class));
.run((context) -> assertThat(context).doesNotHaveBean(GrpcServerAutoConfiguration.class));
}

@Test
void whenHasUserDefinedServerLifecycleDoesNotAutoConfigureBean() {
GrpcServerLifecycle customServerLifecycle = mock(GrpcServerLifecycle.class);
this.contextRunnerWithLifecyle()
.withBean("customServerLifecycle", GrpcServerLifecycle.class, () -> customServerLifecycle)
.run((context) -> assertThat(context).getBean(GrpcServerLifecycle.class).isSameAs(customServerLifecycle));
.withBean("customServerLifecycle", GrpcServerLifecycle.class, () -> customServerLifecycle)
.run((context) -> assertThat(context).getBean(GrpcServerLifecycle.class).isSameAs(customServerLifecycle));
}

@Test
void serverLifecycleAutoConfiguredAsExpected() {
this.contextRunnerWithLifecyle()
.run((context) -> assertThat(context).getBean(GrpcServerLifecycle.class)
.hasFieldOrPropertyWithValue("factory", context.getBean(GrpcServerFactory.class)));
.run((context) -> assertThat(context).getBean(GrpcServerLifecycle.class)
.hasFieldOrPropertyWithValue("factory", context.getBean(GrpcServerFactory.class)));
}

@Test
void whenHasUserDefinedServerBuilderCustomizersDoesNotAutoConfigureBean() {
ServerBuilderCustomizers customCustomizers = mock(ServerBuilderCustomizers.class);
this.contextRunner()
.withBean("customCustomizers", ServerBuilderCustomizers.class, () -> customCustomizers)
.run((context) -> assertThat(context).getBean(ServerBuilderCustomizers.class).isSameAs(customCustomizers));
.withBean("customCustomizers", ServerBuilderCustomizers.class, () -> customCustomizers)
.run((context) -> assertThat(context).getBean(ServerBuilderCustomizers.class).isSameAs(customCustomizers));
}

@Test
void serverBuilderCustomizersAutoConfiguredAsExpected() {
this.contextRunner()
.withUserConfiguration(ServerBuilderCustomizersConfig.class)
.run((context) -> assertThat(context).getBean(ServerBuilderCustomizers.class)
.extracting("customizers", InstanceOfAssertFactories.list(ServerBuilderCustomizer.class))
.contains(ServerBuilderCustomizersConfig.CUSTOMIZER_BAR,
ServerBuilderCustomizersConfig.CUSTOMIZER_FOO));
.withUserConfiguration(ServerBuilderCustomizersConfig.class)
.run((context) -> assertThat(context).getBean(ServerBuilderCustomizers.class)
.extracting("customizers", InstanceOfAssertFactories.list(ServerBuilderCustomizer.class))
.contains(ServerBuilderCustomizersConfig.CUSTOMIZER_BAR,
ServerBuilderCustomizersConfig.CUSTOMIZER_FOO));
}

@Test
void whenHasUserDefinedServerFactoryDoesNotAutoConfigureBean() {
GrpcServerFactory customServerFactory = mock(GrpcServerFactory.class);
this.contextRunner()
.withBean("customServerFactory", GrpcServerFactory.class, () -> customServerFactory)
.run((context) -> assertThat(context).getBean(GrpcServerFactory.class).isSameAs(customServerFactory));
.withBean("customServerFactory", GrpcServerFactory.class, () -> customServerFactory)
.run((context) -> assertThat(context).getBean(GrpcServerFactory.class).isSameAs(customServerFactory));
}

@Test
void whenShadedAndNonShadedNettyOnClasspathShadedNettyFactoryIsAutoConfigured() {
this.contextRunner()
.run((context) -> assertThat(context).getBean(GrpcServerFactory.class)
.isInstanceOf(ShadedNettyGrpcServerFactory.class));
.run((context) -> assertThat(context).getBean(GrpcServerFactory.class)
.isInstanceOf(ShadedNettyGrpcServerFactory.class));
}

@Test
void whenOnlyNonShadedNettyOnClasspathNonShadedNettyFactoryIsAutoConfigured() {
this.contextRunner()
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
.run((context) -> assertThat(context).getBean(GrpcServerFactory.class)
.isInstanceOf(NettyGrpcServerFactory.class));
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
.run((context) -> assertThat(context).getBean(GrpcServerFactory.class)
.isInstanceOf(NettyGrpcServerFactory.class));
}

@Test
Expand All @@ -160,29 +160,29 @@ void shadedNettyServerFactoryAutoConfiguredAsExpected() {
@Test
void nettyServerFactoryAutoConfiguredAsExpected() {
serverFactoryAutoConfiguredAsExpected(this.contextRunner()
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class)),
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class)),
NettyGrpcServerFactory.class);
}

@Test
void noServerFactoryAutoConfiguredAsExpected() {
this.contextRunner()
.withClassLoader(new FilteredClassLoader(NettyServerBuilder.class,
io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
.run((context) -> assertThat(context).doesNotHaveBean(GrpcServerFactory.class));
.withClassLoader(new FilteredClassLoader(NettyServerBuilder.class,
io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class))
.run((context) -> assertThat(context).doesNotHaveBean(GrpcServerFactory.class));
}

private void serverFactoryAutoConfiguredAsExpected(ApplicationContextRunner contextRunner,
Class<?> expectedServerFactoryType) {
Class<?> expectedServerFactoryType) {
contextRunner.withPropertyValues("spring.grpc.server.host=myhost", "spring.grpc.server.port=6160")
.run((context) -> assertThat(context).getBean(GrpcServerFactory.class)
.isInstanceOf(expectedServerFactoryType)
.hasFieldOrPropertyWithValue("address", "myhost:6160")
.extracting("serviceList", InstanceOfAssertFactories.list(ServerServiceDefinition.class))
.singleElement()
.extracting(ServerServiceDefinition::getServiceDescriptor)
.extracting(ServiceDescriptor::getName)
.isEqualTo("my-service"));
.run((context) -> assertThat(context).getBean(GrpcServerFactory.class)
.isInstanceOf(expectedServerFactoryType)
.hasFieldOrPropertyWithValue("address", "myhost:6160")
.extracting("serviceList", InstanceOfAssertFactories.list(ServerServiceDefinition.class))
.singleElement()
.extracting(ServerServiceDefinition::getServiceDescriptor)
.extracting(ServiceDescriptor::getName)
.isEqualTo("my-service"));
}

@Test
Expand All @@ -202,11 +202,11 @@ void nettyServerFactoryAutoConfiguredWithCustomizers() {
// real world.
try (MockedStatic<Grpc> serverBuilderForPort = Mockito.mockStatic(Grpc.class)) {
serverBuilderForPort.when(() -> Grpc.newServerBuilderForPort(anyInt(), any()))
.thenAnswer((Answer<NettyServerBuilder>) invocation -> NettyServerBuilder
.forPort(invocation.getArgument(0)));
.thenAnswer((Answer<NettyServerBuilder>) invocation -> NettyServerBuilder
.forPort(invocation.getArgument(0)));
NettyServerBuilder builder = mock();
serverFactoryAutoConfiguredWithCustomizers(this.contextRunnerWithLifecyle()
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class)),
.withClassLoader(new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class)),
builder, NettyGrpcServerFactory.class);
}
}
Expand All @@ -219,30 +219,30 @@ private <T extends ServerBuilder<T>> void serverFactoryAutoConfiguredWithCustomi
ServerBuilderCustomizer<T> customizer2 = (serverBuilder) -> serverBuilder.keepAliveTime(50L, TimeUnit.SECONDS);
ServerBuilderCustomizers customizers = new ServerBuilderCustomizers(List.of(customizer1, customizer2));
contextRunner.withPropertyValues("spring.grpc.server.port=0", "spring.grpc.server.keep-alive.time=30s")
.withBean("serverBuilderCustomizers", ServerBuilderCustomizers.class, () -> customizers)
.run((context) -> assertThat(context).getBean(GrpcServerFactory.class)
.isInstanceOf(expectedServerFactoryType)
.extracting("serverBuilderCustomizers", InstanceOfAssertFactories.list(ServerBuilderCustomizer.class))
.satisfies((allCustomizers) -> {
allCustomizers.forEach((c) -> c.customize(mockServerBuilder));
InOrder ordered = inOrder(mockServerBuilder);
ordered.verify(mockServerBuilder)
.keepAliveTime(Duration.ofSeconds(30L).toNanos(), TimeUnit.NANOSECONDS);
ordered.verify(mockServerBuilder).keepAliveTime(40L, TimeUnit.SECONDS);
ordered.verify(mockServerBuilder).keepAliveTime(50L, TimeUnit.SECONDS);
}));
.withBean("serverBuilderCustomizers", ServerBuilderCustomizers.class, () -> customizers)
.run((context) -> assertThat(context).getBean(GrpcServerFactory.class)
.isInstanceOf(expectedServerFactoryType)
.extracting("serverBuilderCustomizers", InstanceOfAssertFactories.list(ServerBuilderCustomizer.class))
.satisfies((allCustomizers) -> {
allCustomizers.forEach((c) -> c.customize(mockServerBuilder));
InOrder ordered = inOrder(mockServerBuilder);
ordered.verify(mockServerBuilder)
.keepAliveTime(Duration.ofSeconds(30L).toNanos(), TimeUnit.NANOSECONDS);
ordered.verify(mockServerBuilder).keepAliveTime(40L, TimeUnit.SECONDS);
ordered.verify(mockServerBuilder).keepAliveTime(50L, TimeUnit.SECONDS);
}));
}

@Test
void nettyServerFactoryAutoConfiguredWithSsl() {
serverFactoryAutoConfiguredAsExpected(
this.contextRunner()
.withPropertyValues("spring.grpc.server.ssl.bundle=ssltest",
"spring.ssl.bundle.jks.ssltest.keystore.location=classpath:test.jks",
"spring.ssl.bundle.jks.ssltest.keystore.password=secret",
"spring.ssl.bundle.jks.ssltest.key.password=password")
.withClassLoader(
new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class)),
.withPropertyValues("spring.grpc.server.ssl.bundle=ssltest",
"spring.ssl.bundle.jks.ssltest.keystore.location=classpath:test.jks",
"spring.ssl.bundle.jks.ssltest.keystore.password=secret",
"spring.ssl.bundle.jks.ssltest.key.password=password")
.withClassLoader(
new FilteredClassLoader(io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder.class)),
NettyGrpcServerFactory.class);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.springframework.grpc.autoconfigure.server;


import io.grpc.ServerBuilder;
import io.grpc.ServerInterceptor;
import io.micrometer.core.instrument.binder.grpc.ObservationGrpcServerInterceptor;
import io.micrometer.observation.ObservationRegistry;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.grpc.server.ServerBuilderCustomizer;

import static org.assertj.core.api.Assertions.assertThat;

class GrpcServerMetricAutoConfigurationTests {

private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(GrpcServerMetricAutoConfiguration.class));

@Test
void whenObservationRegistryNotProvided_thenMetricsInterceptorNotConfigured() {
this.contextRunner.run(context -> {
assertThat(context).doesNotHaveBean(ServerBuilderCustomizer.class);
});
}

@Test
void whenMetricsInterceptorConfigured_thenServerBuilderCustomizerConfigured() {
this.contextRunner
.withBean(ObservationRegistry.class, ObservationRegistry::create)
.run(context -> {
assertThat(context).hasSingleBean(ServerBuilderCustomizer.class);
assertThat(context).hasSingleBean(ServerInterceptor.class);
ServerInterceptor interceptor = context.getBean(ServerInterceptor.class);
ServerBuilderCustomizer customizer = context.getBean(ServerBuilderCustomizer.class);
ServerBuilder<?> builder = org.mockito.Mockito.mock(ServerBuilder.class);
customizer.customize(builder);
org.mockito.Mockito.verify(builder , org.mockito.Mockito.times(1)).intercept(interceptor);
});
}

}
Loading