Tidy up dependencies in sample

This commit is contained in:
Dave Syer
2025-05-16 09:46:49 +01:00
parent 6403bd96fa
commit 61a0ad9976
5 changed files with 1 additions and 615 deletions

View File

@@ -37,11 +37,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.grpc:grpc-services'
implementation "io.grpc:grpc-kotlin-stub:${kotlinStubVersion}"
implementation 'com.google.protobuf:protobuf-kotlin'
implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.grpc:spring-grpc-test'

View File

@@ -51,24 +51,15 @@
<groupId>org.springframework.grpc</groupId>
<artifactId>spring-grpc-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
<version>1.10.2</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-kotlin-stub</artifactId>
<version>${grpc.kotlin.version}</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-kotlin</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-services</artifactId>
@@ -90,24 +81,6 @@
<artifactId>spring-grpc-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>24.0.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>24.0.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>24.0.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>

View File

@@ -1,84 +0,0 @@
package org.springframework.grpc.sample
import io.grpc.stub.AbstractStub
import org.assertj.core.api.Assertions
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.ApplicationContext
import org.springframework.grpc.client.FutureStubFactory
import org.springframework.grpc.client.ImportGrpcClients
import org.springframework.grpc.sample.proto.SimpleGrpc
import org.springframework.grpc.sample.proto.SimpleGrpc.SimpleBlockingStub
import org.springframework.grpc.sample.proto.SimpleGrpc.SimpleFutureStub
import org.springframework.grpc.test.AutoConfigureInProcessTransport
@SpringBootTest
@AutoConfigureInProcessTransport
class NoAutowiredClients {
@Autowired
private lateinit var context: ApplicationContext
@Test
fun noStubIsCreated() {
assertThat(context.containsBeanDefinition("simpleBlockingStub")).isFalse()
assertThat(context.containsBeanDefinition("simpleStub")).isFalse()
assertThat(context.containsBeanDefinition("simpleFutureStub")).isFalse()
assertThat(context.getBeanNamesForType(AbstractStub::class.java))
.isEmpty()
}
}
@SpringBootTest(properties = ["spring.grpc.client.default-channel.address=0.0.0.0:9090"])
@AutoConfigureInProcessTransport
class DefaultAutowiredClients {
@Autowired
private lateinit var context: ApplicationContext
@Test
fun onlyDefaultStubIsCreated() {
Assertions.assertThat(context.containsBeanDefinition("simpleBlockingStub")).isTrue()
assertThat(
context.getBean(
SimpleBlockingStub::class.java
)
).isNotNull()
assertThat(context.containsBeanDefinition("simpleStub")).isFalse()
assertThat(context.containsBeanDefinition("simpleFutureStub")).isFalse()
assertThat(context.getBeanNamesForType(AbstractStub::class.java))
.hasSize(1)
}
}
@SpringBootTest(
properties = ["spring.grpc.client.default-channel.address=0.0.0.0:9090"]
)
@AutoConfigureInProcessTransport
class SpecificAutowiredClients {
@Autowired
private lateinit var context: ApplicationContext
@Test
fun stubOfCorrectTypeIsCreated() {
assertThat(context.containsBeanDefinition("simpleFutureStub")).isTrue()
assertThat(
context.getBean(
SimpleFutureStub::class.java
)
).isNotNull()
assertThat(context.containsBeanDefinition("simpleStub")).isFalse()
assertThat(context.containsBeanDefinition("simpleBlockingStub")).isFalse()
assertThat(context.getBeanNamesForType(AbstractStub::class.java))
.hasSize(1)
}
@TestConfiguration
@ImportGrpcClients(basePackageClasses = [SimpleGrpc::class], factory = FutureStubFactory::class)
internal open class TestConfig
}

View File

@@ -1,169 +0,0 @@
/*
* Copyright 2024-2024 the original author or 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
*
* https://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 org.springframework.grpc.sample
import io.grpc.StatusRuntimeException
import io.grpc.health.v1.HealthCheckRequest
import io.grpc.health.v1.HealthCheckResponse.ServingStatus
import io.grpc.health.v1.HealthGrpc
import io.grpc.health.v1.HealthGrpc.HealthBlockingStub
import io.grpc.protobuf.services.HealthStatusManager
import org.assertj.core.api.Assertions
import org.assertj.core.api.ThrowableAssert
import org.awaitility.Awaitility
import org.awaitility.core.ThrowingRunnable
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator
import org.springframework.boot.actuate.health.Health
import org.springframework.boot.actuate.health.HealthIndicator
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.grpc.client.GrpcChannelFactory
import org.springframework.grpc.sample.proto.HelloRequest
import org.springframework.grpc.sample.proto.SimpleGrpc
import org.springframework.grpc.sample.proto.SimpleGrpc.SimpleBlockingStub
import org.springframework.grpc.test.AutoConfigureInProcessTransport
import org.springframework.test.annotation.DirtiesContext
import java.time.Duration
/**
* Integration tests for gRPC server health feature.
*/
@SpringBootTest(
properties = ["spring.grpc.server.port=0", "spring.grpc.client.channels.health-test.address=static://0.0.0.0:\${local.grpc.port}", "spring.grpc.client.channels.health-test.health.enabled=true", "spring.grpc.client.channels.health-test.health.service-name=my-service"]
)
@DirtiesContext
class WithClientHealthEnabled {
@Test
fun loadBalancerRespectsServerHealth(
@Autowired channels: GrpcChannelFactory,
@Autowired healthStatusManager: HealthStatusManager
) {
val channel = channels.createChannel("health-test")
val client = SimpleGrpc.newBlockingStub(channel)
// put the service up (SERVING) and give load balancer time to update
updateHealthStatusAndWait("my-service", ServingStatus.SERVING, healthStatusManager)
// initially the status should be SERVING
assertThatResponseIsServedToChannel(client)
// put the service down (NOT_SERVING) and give load balancer time to update
updateHealthStatusAndWait("my-service", ServingStatus.NOT_SERVING, healthStatusManager)
// now the request should fail
assertThatResponseIsNotServedToChannel(client)
// put the service up (SERVING) and give load balancer time to update
updateHealthStatusAndWait("my-service", ServingStatus.SERVING, healthStatusManager)
// now the request should pass
assertThatResponseIsServedToChannel(client)
}
private fun updateHealthStatusAndWait(
serviceName: String?, healthStatus: ServingStatus,
healthStatusManager: HealthStatusManager
) {
healthStatusManager.setStatus(serviceName, healthStatus)
try {
Thread.sleep(2000L)
} catch (e: InterruptedException) {
throw RuntimeException(e)
}
}
private fun assertThatResponseIsServedToChannel(client: SimpleBlockingStub) {
val response = client.sayHello(HelloRequest.newBuilder().setName("Alien").build())
Assertions.assertThat(response.getMessage()).isEqualTo("Hello ==> Alien")
}
private fun assertThatResponseIsNotServedToChannel(client: SimpleBlockingStub) {
Assertions.assertThatExceptionOfType<StatusRuntimeException?>(StatusRuntimeException::class.java)
.isThrownBy(ThrowableAssert.ThrowingCallable {
client.sayHello(
HelloRequest.newBuilder().setName("Alien").build()
)
})
.withMessageContaining("UNAVAILABLE: Health-check service responded NOT_SERVING for 'my-service'")
}
}
@SpringBootTest(
properties = ["spring.grpc.server.health.actuator.health-indicator-paths=custom", "spring.grpc.server.health.actuator.update-initial-delay=3s", "spring.grpc.server.health.actuator.update-rate=3s", "management.health.defaults.enabled=true"]
)
@AutoConfigureInProcessTransport
@DirtiesContext
class WithActuatorHealthAdapter {
@Test
fun healthIndicatorsAdaptedToGrpcHealthStatus(
@Autowired channels: GrpcChannelFactory,
@Autowired customHealthIndicator: CustomHealthIndicator
) {
val channel = channels.createChannel("0.0.0.0:0")
val healthStub = HealthGrpc.newBlockingStub(channel)
val serviceName = "custom"
// initially the status should be SERVING
assertThatGrpcHealthStatusIs(healthStub, serviceName, ServingStatus.SERVING, Duration.ofSeconds(4))
// put the service down and the status should then be NOT_SERVING
customHealthIndicator.SERVICE_IS_UP = false
assertThatGrpcHealthStatusIs(healthStub, serviceName, ServingStatus.NOT_SERVING, Duration.ofSeconds(4))
// put the service up and the status should be SERVING
customHealthIndicator.SERVICE_IS_UP = true
assertThatGrpcHealthStatusIs(healthStub, serviceName, ServingStatus.SERVING, Duration.ofSeconds(4))
}
private fun assertThatGrpcHealthStatusIs(
healthBlockingStub: HealthBlockingStub, service: String,
expectedStatus: ServingStatus?, maxWaitTime: Duration?
) {
Awaitility.await().atMost(maxWaitTime).ignoreException(StatusRuntimeException::class.java).untilAsserted {
val healthRequest = HealthCheckRequest.newBuilder().setService(service).build()
val healthResponse = healthBlockingStub.check(healthRequest)
Assertions.assertThat(healthResponse.getStatus()).isEqualTo(expectedStatus)
// verify the overall status as well
val overallHealthRequest = HealthCheckRequest.newBuilder().setService("").build()
val overallHealthResponse = healthBlockingStub.check(overallHealthRequest)
Assertions.assertThat(overallHealthResponse.getStatus()).isEqualTo(expectedStatus)
}
}
@TestConfiguration
internal open class MyHealthIndicatorsConfig {
@ConditionalOnEnabledHealthIndicator("custom")
@Bean
open fun customHealthIndicator(): CustomHealthIndicator {
return CustomHealthIndicator()
}
}
class CustomHealthIndicator : HealthIndicator {
override fun health(): Health? {
return if (SERVICE_IS_UP) Health.up().build() else Health.down().build()
}
var SERVICE_IS_UP: Boolean = true
}
}

View File

@@ -1,332 +0,0 @@
/*
* Copyright 2024-2024 the original author or 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
*
* https://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 org.springframework.grpc.sample
import io.grpc.ForwardingServerCallListener
import io.grpc.ManagedChannel
import io.grpc.Metadata
import io.grpc.ServerCall
import io.grpc.ServerCallHandler
import io.grpc.ServerInterceptor
import io.grpc.Status
import io.grpc.StatusRuntimeException
import io.grpc.netty.NettyChannelBuilder
import org.assertj.core.api.Assertions
import org.junit.Assert
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.condition.EnabledOnOs
import org.junit.jupiter.api.condition.OS
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.grpc.client.ChannelBuilderOptions
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer
import org.springframework.grpc.client.GrpcChannelFactory
import org.springframework.grpc.sample.proto.HelloRequest
import org.springframework.grpc.sample.proto.SimpleGrpc
import org.springframework.grpc.server.GlobalServerInterceptor
import org.springframework.grpc.test.AutoConfigureInProcessTransport
import org.springframework.grpc.test.LocalGrpcPort
import org.springframework.test.annotation.DirtiesContext
import org.springframework.test.context.ActiveProfiles
import java.util.concurrent.atomic.AtomicInteger
/**
* More detailed integration tests for [gRPC server factories][GrpcServerFactory] and
* various [GrpcServerProperties].
*/
@SpringBootTest
@AutoConfigureInProcessTransport
class ServerWithInProcessChannel {
@Test
fun servesResponseToClient(@Autowired channels: GrpcChannelFactory) {
assertThatResponseIsServedToChannel(channels.createChannel("0.0.0.0:0"))
}
}
@SpringBootTest
@AutoConfigureInProcessTransport
class ServerWithException {
@Test
fun specificErrorResponse(@Autowired channels: GrpcChannelFactory) {
val client = SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:0"))
Assertions.assertThat(
Assert.assertThrows(StatusRuntimeException::class.java) {
client.sayHello(
HelloRequest.newBuilder().setName("error").build()
)
}
.status
.code
).isEqualTo(Status.Code.INVALID_ARGUMENT)
}
@Test
fun defaultErrorResponseIsUnknown(@Autowired channels: GrpcChannelFactory) {
val client = SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:0"))
Assertions.assertThat(
Assert.assertThrows(
StatusRuntimeException::class.java
) { client.sayHello(HelloRequest.newBuilder().setName("internal").build()) }
.status
.code
).isEqualTo(Status.Code.UNKNOWN)
}
}
@SpringBootTest
@AutoConfigureInProcessTransport
class ServerWithExceptionInInterceptorCall {
@Test
fun specificErrorResponse(@Autowired channels: GrpcChannelFactory) {
val client = SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:0"))
Assertions.assertThat(
Assert.assertThrows(
StatusRuntimeException::class.java
) { client.sayHello(HelloRequest.newBuilder().setName("error").build()) }
.status
.code
).isEqualTo(Status.Code.INVALID_ARGUMENT)
}
@TestConfiguration
internal open class TestConfig {
@Bean
@GlobalServerInterceptor
open fun exceptionInterceptor(): ServerInterceptor {
return CustomInterceptor()
}
internal class CustomInterceptor : ServerInterceptor {
override fun <ReqT, RespT> interceptCall(
call: ServerCall<ReqT?, RespT?>?, headers: Metadata?,
next: ServerCallHandler<ReqT?, RespT?>?
): ServerCall.Listener<ReqT?>? {
throw IllegalArgumentException("test")
}
}
}
}
@SpringBootTest
@AutoConfigureInProcessTransport
class ServerWithExceptionInInterceptorListener {
@Test
fun specificErrorResponse(
@Autowired channels: GrpcChannelFactory,
@Autowired testConfig: TestConfig
) {
testConfig.reset()
val client = SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:0"))
Assertions.assertThat(
Assert.assertThrows(
StatusRuntimeException::class.java
) { client.sayHello(HelloRequest.newBuilder().setName("foo").build()) }
.status
.code
).isEqualTo(Status.Code.INVALID_ARGUMENT)
Assertions.assertThat(TestConfig.readyCount.get()).isEqualTo(1)
Assertions.assertThat(TestConfig.callCount.get()).isEqualTo(0)
Assertions.assertThat(TestConfig.messageCount.get()).isEqualTo(0)
}
@TestConfiguration
open class TestConfig {
companion object {
var callCount: AtomicInteger = AtomicInteger()
var messageCount: AtomicInteger = AtomicInteger()
var readyCount: AtomicInteger = AtomicInteger()
}
fun reset() {
callCount.set(0)
messageCount.set(0)
readyCount.set(0)
}
@Bean
@GlobalServerInterceptor
open fun exceptionInterceptor(): ServerInterceptor {
return CustomInterceptor()
}
internal class CustomInterceptor : ServerInterceptor {
override fun <ReqT, RespT> interceptCall(
call: ServerCall<ReqT?, RespT?>?, headers: Metadata?,
next: ServerCallHandler<ReqT?, RespT?>
): ServerCall.Listener<ReqT?> {
return CustomListener(next.startCall(call, headers))
}
}
internal class CustomListener<ReqT>(private val delegate: ServerCall.Listener<ReqT?>?) :
ForwardingServerCallListener<ReqT?>() {
override fun onReady() {
readyCount.incrementAndGet()
throw IllegalArgumentException("test")
}
override fun onHalfClose() {
callCount.incrementAndGet()
super.onHalfClose()
}
override fun onMessage(message: ReqT?) {
messageCount.incrementAndGet()
super.onMessage(message)
}
override fun delegate(): ServerCall.Listener<ReqT?>? {
return this.delegate
}
}
}
}
@SpringBootTest("spring.grpc.server.exception-handler.enabled=false")
@AutoConfigureInProcessTransport
class ServerWithUnhandledException {
@Test
fun specificErrorResponse(@Autowired channels: GrpcChannelFactory) {
val client = SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:0"))
Assertions.assertThat<Status.Code?>(
Assert.assertThrows(StatusRuntimeException::class.java) {
client.sayHello(
HelloRequest.newBuilder().setName("error").build()
)
}
.status
.code
).isEqualTo(Status.Code.UNKNOWN)
}
@Test
fun defaultErrorResponseIsUnknown(@Autowired channels: GrpcChannelFactory) {
val client = SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:0"))
Assertions.assertThat(
Assert.assertThrows(
StatusRuntimeException::class.java
) { client.sayHello(HelloRequest.newBuilder().setName("internal").build()) }
.status
.code
).isEqualTo(Status.Code.UNKNOWN)
}
}
@SpringBootTest(properties = ["spring.grpc.server.host=0.0.0.0", "spring.grpc.server.port=0"])
class ServerWithAnyIPv4AddressAndRandomPort {
@Test
fun servesResponseToClientWithAnyIPv4AddressAndRandomPort(
@Autowired channels: GrpcChannelFactory,
@LocalGrpcPort port: Int
) {
assertThatResponseIsServedToChannel(channels.createChannel("0.0.0.0:" + port))
}
}
@SpringBootTest(properties = ["spring.grpc.server.host=::", "spring.grpc.server.port=0"])
class ServerWithAnyIPv6AddressAndRandomPort {
@Test
fun servesResponseToClientWithAnyIPv4AddressAndRandomPort(
@Autowired channels: GrpcChannelFactory,
@LocalGrpcPort port: Int
) {
assertThatResponseIsServedToChannel(channels.createChannel("0.0.0.0:" + port))
}
}
@SpringBootTest(properties = ["spring.grpc.server.host=127.0.0.1", "spring.grpc.server.port=0"])
class ServerWithLocalhostAndRandomPort {
@Test
fun servesResponseToClientWithLocalhostAndRandomPort(
@Autowired channels: GrpcChannelFactory,
@LocalGrpcPort port: Int
) {
assertThatResponseIsServedToChannel(channels.createChannel("127.0.0.1:" + port))
}
}
@SpringBootTest(
properties = ["spring.grpc.server.port=0", "spring.grpc.client.channels.test-channel.address=static://0.0.0.0:\${local.grpc.port}"]
)
@DirtiesContext
class ServerConfiguredWithStaticClientChannel {
@Test
fun servesResponseToClientWithConfiguredChannel(@Autowired channels: GrpcChannelFactory) {
assertThatResponseIsServedToChannel(channels.createChannel("test-channel"))
}
}
@SpringBootTest(properties = ["spring.grpc.server.address=unix:unix-test-channel"])
@EnabledOnOs(OS.LINUX)
class ServerWithUnixDomain {
@Test
fun clientChannelWithUnixDomain(@Autowired channels: GrpcChannelFactory) {
assertThatResponseIsServedToChannel(
channels.createChannel(
"unix:unix-test-channel",
ChannelBuilderOptions.defaults()
.withCustomizer<NettyChannelBuilder?>(GrpcChannelBuilderCustomizer { `__`: String?, b: NettyChannelBuilder? -> b!!.usePlaintext() })
)
)
}
}
@SpringBootTest(
properties = ["spring.grpc.server.port=0", "spring.grpc.client.channels.test-channel.address=static://0.0.0.0:\${local.grpc.port}", "spring.grpc.client.channels.test-channel.negotiation-type=TLS", "spring.grpc.client.channels.test-channel.secure=false"]
)
@ActiveProfiles("ssl")
@DirtiesContext
class ServerWithSsl {
@Test
fun clientChannelWithSsl(@Autowired channels: GrpcChannelFactory) {
assertThatResponseIsServedToChannel(channels.createChannel("test-channel"))
}
}
@SpringBootTest(
properties = ["spring.grpc.server.port=0", "spring.grpc.server.ssl.client-auth=REQUIRE", "spring.grpc.server.ssl.secure=false", "spring.grpc.client.channels.test-channel.address=static://0.0.0.0:\${local.grpc.port}", "spring.grpc.client.channels.test-channel.ssl.bundle=ssltest", "spring.grpc.client.channels.test-channel.negotiation-type=TLS", "spring.grpc.client.channels.test-channel.secure=false"]
)
@ActiveProfiles("ssl")
@DirtiesContext
class ServerWithClientAuth {
@Test
fun clientChannelWithSsl(@Autowired channels: GrpcChannelFactory) {
assertThatResponseIsServedToChannel(channels.createChannel("test-channel"))
}
}
private fun assertThatResponseIsServedToChannel(clientChannel: ManagedChannel?) {
val client = SimpleGrpc.newBlockingStub(clientChannel)
val response = client.sayHello(HelloRequest.newBuilder().setName("Alien").build())
Assertions.assertThat(response.getMessage()).isEqualTo("Hello ==> Alien")
}