From ab5518230422a46f3c206f25d0bb3c7d397b72b9 Mon Sep 17 00:00:00 2001 From: Chris Bono Date: Wed, 20 Nov 2024 23:14:43 -0600 Subject: [PATCH] Add autoconfiguration for server-side health This commit autoconfigures the gRPC health service by providing the following beans: - gRPC HealthStatusManager - gRPC HealthService - Actuator health check adapter (skeleton impl) See #56 Signed-off-by: Chris Bono --- .../modules/ROOT/partials/_configprops.adoc | 27 +-- spring-grpc-spring-boot-autoconfigure/pom.xml | 13 +- .../GrpcServerHealthAutoConfiguration.java | 83 +++++++++ .../server/GrpcServerProperties.java | 65 ++++++- ...ot.autoconfigure.AutoConfiguration.imports | 4 +- ...rpcServerHealthAutoConfigurationTests.java | 176 ++++++++++++++++++ .../server/GrpcServerPropertiesTests.java | 28 +++ 7 files changed, 378 insertions(+), 18 deletions(-) create mode 100644 spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerHealthAutoConfiguration.java create mode 100644 spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerHealthAutoConfigurationTests.java diff --git a/spring-grpc-docs/src/main/antora/modules/ROOT/partials/_configprops.adoc b/spring-grpc-docs/src/main/antora/modules/ROOT/partials/_configprops.adoc index a174fd9..85009f1 100644 --- a/spring-grpc-docs/src/main/antora/modules/ROOT/partials/_configprops.adoc +++ b/spring-grpc-docs/src/main/antora/modules/ROOT/partials/_configprops.adoc @@ -12,26 +12,31 @@ |spring.grpc.client.default-channel.max-inbound-message-size | | |spring.grpc.client.default-channel.max-inbound-metadata-size | | |spring.grpc.client.default-channel.negotiation-type | | The negotiation type for the channel. Default is {@link NegotiationType#PLAINTEXT}. -|spring.grpc.client.default-channel.secure | | Flag to say that strict SSL checks are not enabled (so the remote certificate could be anonymous). +|spring.grpc.client.default-channel.secure | `+++true+++` | Flag to say that strict SSL checks are not enabled (so the remote certificate could be anonymous). |spring.grpc.client.default-channel.ssl.bundle | | SSL bundle name. |spring.grpc.client.default-channel.ssl.enabled | | Whether to enable SSL support. Enabled automatically if "bundle" is provided unless specified otherwise. |spring.grpc.client.default-channel.user-agent | | |spring.grpc.server.address | | The address to bind to. could be a host:port combination or a pseudo URL like static://host:port. Can not be set if host or port are set independently. -|spring.grpc.server.host | | Server address to bind to. The default is any IP address ('*'). +|spring.grpc.server.health.actuator.enabled | `+++true+++` | Whether to adapt Actuator health checks into gRPC health checks. +|spring.grpc.server.health.actuator.endpoints | | List of Actuator health checks to adapt into gRPC health checks. +|spring.grpc.server.health.enabled | `+++true+++` | Whether to auto-configure Health feature on the gRPC server. +|spring.grpc.server.host | `+++*+++` | Server address to bind to. The default is any IP address ('*'). |spring.grpc.server.keep-alive.max-age | | Maximum time a connection may exist before being gracefully terminated (default infinite). |spring.grpc.server.keep-alive.max-age-grace | | Maximum time for graceful connection termination (default infinite). |spring.grpc.server.keep-alive.max-idle | | Maximum time a connection can remain idle before being gracefully terminated (default infinite). -|spring.grpc.server.keep-alive.permit-time | | Maximum keep-alive time clients are permitted to configure (default 5m). -|spring.grpc.server.keep-alive.permit-without-calls | | Whether clients are permitted to send keep alive pings when there are no outstanding RPCs on the connection (default false). -|spring.grpc.server.keep-alive.time | | Duration without read activity before sending a keep alive ping (default 2h). -|spring.grpc.server.keep-alive.timeout | | Maximum time to wait for read activity after sending a keep alive ping. If sender does not receive an acknowledgment within this time, it will close the connection (default 20s). -|spring.grpc.server.max-inbound-message-size | | Maximum message size allowed to be received by the server (default 4MiB). -|spring.grpc.server.max-inbound-metadata-size | | Maximum metadata size allowed to be received by the server (default 8KiB). -|spring.grpc.server.port | | Server port to listen on. When the value is 0, a random available port is selected. The default is 9090. -|spring.grpc.server.shutdown-grace-period | | Maximum time to wait for the server to gracefully shutdown. When the value is negative, the server waits forever. When the value is 0, the server will force shutdown immediately. The default is 30 seconds. +|spring.grpc.server.keep-alive.permit-time | `+++5m+++` | Maximum keep-alive time clients are permitted to configure (default 5m). +|spring.grpc.server.keep-alive.permit-without-calls | `+++false+++` | Whether clients are permitted to send keep alive pings when there are no outstanding RPCs on the connection (default false). +|spring.grpc.server.keep-alive.time | `+++2h+++` | Duration without read activity before sending a keep alive ping (default 2h). +|spring.grpc.server.keep-alive.timeout | `+++20s+++` | Maximum time to wait for read activity after sending a keep alive ping. If sender does not receive an acknowledgment within this time, it will close the connection (default 20s). +|spring.grpc.server.max-inbound-message-size | `+++4194304B+++` | Maximum message size allowed to be received by the server (default 4MiB). +|spring.grpc.server.max-inbound-metadata-size | `+++8192B+++` | Maximum metadata size allowed to be received by the server (default 8KiB). +|spring.grpc.server.observations.enabled | `+++true+++` | Whether to enable Observations on the server. +|spring.grpc.server.port | `+++9090+++` | Server port to listen on. When the value is 0, a random available port is selected. The default is 9090. +|spring.grpc.server.reflection.enabled | `+++true+++` | Whether to enable Reflection on the gRPC server. +|spring.grpc.server.shutdown-grace-period | `+++30s+++` | Maximum time to wait for the server to gracefully shutdown. When the value is negative, the server waits forever. When the value is 0, the server will force shutdown immediately. The default is 30 seconds. |spring.grpc.server.ssl.bundle | | SSL bundle name. |spring.grpc.server.ssl.client-auth | | Client authentication mode. |spring.grpc.server.ssl.enabled | | Whether to enable SSL support. Enabled automatically if "bundle" is provided unless specified otherwise. -|spring.grpc.server.ssl.secure | | Flag to indicate that client authentication is secure (i.e. certificates are checked). Do not set this to false in production. +|spring.grpc.server.ssl.secure | `+++true+++` | Flag to indicate that client authentication is secure (i.e. certificates are checked). Do not set this to false in production. |=== \ No newline at end of file diff --git a/spring-grpc-spring-boot-autoconfigure/pom.xml b/spring-grpc-spring-boot-autoconfigure/pom.xml index 55b82e0..12eb73a 100644 --- a/spring-grpc-spring-boot-autoconfigure/pom.xml +++ b/spring-grpc-spring-boot-autoconfigure/pom.xml @@ -84,8 +84,16 @@ org.springframework.boot - spring-boot-starter - provided + spring-boot + + + org.springframework.boot + spring-boot-autoconfigure + + + org.springframework.boot + spring-boot-actuator-autoconfigure + true io.micrometer @@ -97,7 +105,6 @@ micrometer-core true - org.springframework.boot diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerHealthAutoConfiguration.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerHealthAutoConfiguration.java new file mode 100644 index 0000000..256f344 --- /dev/null +++ b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerHealthAutoConfiguration.java @@ -0,0 +1,83 @@ +/* + * Copyright 2023-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. + * + * Partial copy from net.devh:grpc-spring-boot-starter. + */ + +package org.springframework.grpc.autoconfigure.server; + +import org.springframework.boot.actuate.health.HealthContributor; +import org.springframework.boot.actuate.health.HealthEndpoint; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.grpc.BindableService; +import io.grpc.protobuf.services.HealthStatusManager; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for gRPC server-side health service. + * + * @author Daniel Theuke (daniel.theuke@heuboe.de) + * @author Chris Bono + */ +@AutoConfiguration(before = GrpcServerFactoryAutoConfiguration.class) +@ConditionalOnClass(HealthStatusManager.class) +@ConditionalOnProperty(name = "spring.grpc.server.health.enabled", havingValue = "true", matchIfMissing = true) +public class GrpcServerHealthAutoConfiguration { + + @Bean(destroyMethod = "enterTerminalState") + @ConditionalOnMissingBean + HealthStatusManager healthStatusManager() { + return new HealthStatusManager(); + } + + @Bean + BindableService grpcHealthService(HealthStatusManager healthStatusManager) { + return healthStatusManager.getHealthService(); + } + + @Configuration(proxyBeanMethods = false) + @AutoConfigureAfter(name = "org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration") + @ConditionalOnClass(HealthEndpoint.class) + @ConditionalOnBean(HealthEndpoint.class) + @EnableConfigurationProperties(GrpcServerProperties.class) + static class ActuatorHealthAdapterConfiguration { + + @Bean + ActuatorHealthAdapter healthAdapter(HealthStatusManager healthStatusManager, HealthEndpoint healthEndpoint, + GrpcServerProperties serverProperties) { + return new ActuatorHealthAdapter(); + } + + } + + /** + * Adapts {@link HealthContributor Actuator health checks} into gRPC health checks by + * periodically invoking {@link HealthEndpoint health endpoints} and updating the + * health status in gRPC {@link HealthStatusManager}. + */ + static class ActuatorHealthAdapter { + + } + +} diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerProperties.java b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerProperties.java index dfd187e..fb28013 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerProperties.java +++ b/spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/server/GrpcServerProperties.java @@ -17,6 +17,8 @@ package org.springframework.grpc.autoconfigure.server; import java.time.Duration; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.convert.DataSizeUnit; @@ -66,6 +68,8 @@ public class GrpcServerProperties { @DataSizeUnit(DataUnit.BYTES) private DataSize maxInboundMetadataSize = DataSize.ofBytes(8192); + private final Health health = new Health(); + private final KeepAlive keepAlive = new KeepAlive(); /** @@ -83,7 +87,7 @@ public class GrpcServerProperties { } public String getHost() { - return host; + return this.host; } public void setHost(String host) { @@ -131,10 +135,67 @@ public class GrpcServerProperties { this.maxInboundMetadataSize = maxInboundMetadataSize; } + public Health getHealth() { + return this.health; + } + public KeepAlive getKeepAlive() { return this.keepAlive; } + public static class Health { + + /** + * Whether to auto-configure Health feature on the gRPC server. + */ + private Boolean enabled = true; + + private final ActuatorAdapt actuator = new ActuatorAdapt(); + + public Boolean getEnabled() { + return this.enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public ActuatorAdapt getActuator() { + return this.actuator; + } + + } + + public static class ActuatorAdapt { + + /** + * Whether to adapt Actuator health checks into gRPC health checks. + */ + private Boolean enabled = true; + + /** + * List of Actuator health checks to adapt into gRPC health checks. + */ + private List endpoints = new ArrayList<>(); + + public Boolean getEnabled() { + return this.enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public List getEndpoints() { + return this.endpoints; + } + + public void setEndpoints(List endpoints) { + this.endpoints = endpoints; + } + + } + public static class KeepAlive { /** @@ -302,7 +363,7 @@ public class GrpcServerProperties { } public ClientAuth getClientAuth() { - return clientAuth; + return this.clientAuth; } public void setSecure(boolean secure) { diff --git a/spring-grpc-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-grpc-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 172a191..48d95eb 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-grpc-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,6 +1,6 @@ +org.springframework.grpc.autoconfigure.client.GrpcClientAutoConfiguration org.springframework.grpc.autoconfigure.server.GrpcServerFactoryAutoConfiguration org.springframework.grpc.autoconfigure.server.GrpcServerAutoConfiguration +org.springframework.grpc.autoconfigure.server.GrpcServerHealthAutoConfiguration org.springframework.grpc.autoconfigure.server.GrpcServerObservationAutoConfiguration -org.springframework.grpc.autoconfigure.client.GrpcClientAutoConfiguration org.springframework.grpc.autoconfigure.server.GrpcServerReflectionAutoConfiguration - diff --git a/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerHealthAutoConfigurationTests.java b/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerHealthAutoConfigurationTests.java new file mode 100644 index 0000000..4e1c2f3 --- /dev/null +++ b/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerHealthAutoConfigurationTests.java @@ -0,0 +1,176 @@ +/* + * Copyright 2023-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.autoconfigure.server; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Arrays; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; +import org.springframework.boot.actuate.health.HealthEndpoint; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.grpc.autoconfigure.server.GrpcServerHealthAutoConfiguration.ActuatorHealthAdapter; +import org.springframework.grpc.autoconfigure.server.GrpcServerHealthAutoConfiguration.ActuatorHealthAdapterConfiguration; +import org.springframework.grpc.server.lifecycle.GrpcServerLifecycle; +import org.springframework.grpc.server.service.GrpcServiceDiscoverer; +import org.springframework.util.StringUtils; + +import io.grpc.BindableService; +import io.grpc.ServerServiceDefinition; +import io.grpc.protobuf.services.HealthStatusManager; + +/** + * Tests for {@link GrpcServerHealthAutoConfiguration}. + * + * @author Chris Bono + */ +class GrpcServerHealthAutoConfigurationTests { + + private ApplicationContextRunner contextRunner() { + return new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(GrpcServerHealthAutoConfiguration.class)); + } + + @Test + void whenHealthStatusManagerNotOnClasspathAutoConfigurationIsSkipped() { + this.contextRunner() + .withClassLoader(new FilteredClassLoader(HealthStatusManager.class)) + .run((context) -> assertThat(context).doesNotHaveBean(GrpcServerHealthAutoConfiguration.class)); + } + + @Test + void whenHealthPropertyNotSetHealthIsAutoConfigured() { + this.contextRunner() + .run((context) -> assertThat(context).hasSingleBean(GrpcServerHealthAutoConfiguration.class)); + } + + @Test + void whenHealthPropertyIsTrueHealthIsAutoConfigured() { + this.contextRunner() + .withPropertyValues("spring.grpc.server.health.enabled=true") + .run((context) -> assertThat(context).hasSingleBean(GrpcServerHealthAutoConfiguration.class)); + } + + @Test + void whenHealthPropertyIsFalseAutoConfigurationIsSkipped() { + this.contextRunner() + .withPropertyValues("spring.grpc.server.health.enabled=false") + .run((context) -> assertThat(context).doesNotHaveBean(GrpcServerHealthAutoConfiguration.class)); + } + + @Test + void healthIsAutoConfiguredBeforeGrpcServerFactory() { + BindableService service = mock(); + ServerServiceDefinition serviceDefinition = ServerServiceDefinition.builder("my-service").build(); + when(service.bindService()).thenReturn(serviceDefinition); + this.contextRunner() + .withConfiguration(AutoConfigurations.of(GrpcServerFactoryAutoConfiguration.class)) + .withBean("noopServerLifecycle", GrpcServerLifecycle.class, Mockito::mock) + .withBean("serverBuilderCustomizers", ServerBuilderCustomizers.class, Mockito::mock) + .withBean("grpcServicesDiscoverer", GrpcServiceDiscoverer.class, Mockito::mock) + .withBean("sslBundles", SslBundles.class, Mockito::mock) + .withBean(BindableService.class, () -> service) + .run((context) -> assertThatBeanDefinitionsContainInOrder(context, GrpcServerHealthAutoConfiguration.class, + GrpcServerFactoryAutoConfiguration.class)); + } + + @Disabled("Will be tested in an integration test once the Actuator adapter is implemented") + @Test + void enterTerminalStateIsCalledWhenStatusManagerIsStopped() { + } + + @Test + void whenHasUserDefinedHealthStatusManagerDoesNotAutoConfigureBean() { + HealthStatusManager customHealthStatusManager = mock(); + this.contextRunner() + .withBean("customHealthStatusManager", HealthStatusManager.class, () -> customHealthStatusManager) + .withPropertyValues("spring.grpc.server.health.enabled=false") + .run((context) -> assertThat(context).getBean(HealthStatusManager.class) + .isSameAs(customHealthStatusManager)); + } + + @Test + void healthStatusManagerAutoConfiguredAsExpected() { + this.contextRunner().run((context) -> { + assertThat(context).hasSingleBean(GrpcServerHealthAutoConfiguration.class); + assertThat(context).hasSingleBean(HealthStatusManager.class); + assertThat(context).getBean("grpcHealthService", BindableService.class).isNotNull(); + }); + } + + private void assertThatBeanDefinitionsContainInOrder(ConfigurableApplicationContext context, + Class... configClasses) { + var configBeanDefNames = Arrays.stream(configClasses).map(this::beanDefinitionNameForConfigClass).toList(); + var filteredBeanDefNames = Arrays.stream(context.getBeanDefinitionNames()) + .filter(configBeanDefNames::contains) + .toList(); + assertThat(filteredBeanDefNames).containsExactlyElementsOf(configBeanDefNames); + } + + private String beanDefinitionNameForConfigClass(Class configClass) { + var fullName = configClass.getName(); + var beanDefName = fullName.contains("$") ? fullName : configClass.getSimpleName(); + return StringUtils.uncapitalize(beanDefName); + } + + @Nested + class ActuatorHealthAdapterConfigurationTests { + + @Test + void adapterIsAutoConfiguredAfterHealthAutoConfiguration() { + GrpcServerHealthAutoConfigurationTests.this.contextRunner() + .withConfiguration(AutoConfigurations.of(HealthEndpointAutoConfiguration.class)) + .run((context) -> assertThatBeanDefinitionsContainInOrder(context, + HealthEndpointAutoConfiguration.class, ActuatorHealthAdapterConfiguration.class)); + } + + @Test + void whenHealthEndpointNotOnClasspathAutoConfigurationIsSkipped() { + GrpcServerHealthAutoConfigurationTests.this.contextRunner() + .withClassLoader(new FilteredClassLoader(HealthEndpoint.class)) + .run((context) -> assertThat(context) + .doesNotHaveBean(GrpcServerHealthAutoConfiguration.ActuatorHealthAdapterConfiguration.class)); + } + + @Test + void whenHealthEndpointBeanNotAvailableAutoConfigurationIsSkipped() { + GrpcServerHealthAutoConfigurationTests.this.contextRunner() + .run((context) -> assertThat(context) + .doesNotHaveBean(GrpcServerHealthAutoConfiguration.ActuatorHealthAdapterConfiguration.class)); + } + + @Test + void adapterAutoConfiguredAsExpected() { + GrpcServerHealthAutoConfigurationTests.this.contextRunner() + .withBean("healthEndpoint", HealthEndpoint.class, Mockito::mock) + .run((context) -> assertThat(context).hasSingleBean(ActuatorHealthAdapter.class)); + } + + } + +} diff --git a/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerPropertiesTests.java b/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerPropertiesTests.java index a2af87b..51150dc 100644 --- a/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerPropertiesTests.java +++ b/spring-grpc-spring-boot-autoconfigure/src/test/java/org/springframework/grpc/autoconfigure/server/GrpcServerPropertiesTests.java @@ -25,6 +25,7 @@ import java.util.Map; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; + import org.springframework.boot.context.properties.bind.BindException; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.source.MapConfigurationPropertySource; @@ -60,6 +61,33 @@ class GrpcServerPropertiesTests { } + @Nested + class HealthProperties { + + @Test + void bindWithNoSettings() { + Map map = new HashMap<>(); + map.put("spring.grpc.server.host", "my-server-ip"); + GrpcServerProperties.Health properties = bindProperties(map).getHealth(); + assertThat(properties.getEnabled()).isTrue(); + assertThat(properties.getActuator().getEnabled()).isTrue(); + assertThat(properties.getActuator().getEndpoints()).isEmpty(); + } + + @Test + void bindWithoutUnitsSpecified() { + Map map = new HashMap<>(); + map.put("spring.grpc.server.health.enabled", "false"); + map.put("spring.grpc.server.health.actuator.enabled", "false"); + map.put("spring.grpc.server.health.actuator.endpoints", "a,b,c"); + GrpcServerProperties.Health properties = bindProperties(map).getHealth(); + assertThat(properties.getEnabled()).isFalse(); + assertThat(properties.getActuator().getEnabled()).isFalse(); + assertThat(properties.getActuator().getEndpoints()).containsExactly("a", "b", "c"); + } + + } + @Nested class KeepAliveProperties {