Allow ConsulHealthIndicator to bypass the services query

Fixes gh-646
This commit is contained in:
bono007
2020-07-12 09:55:13 -05:00
committed by spencergibb
parent 50c74c9eba
commit 4a6d7027f0
9 changed files with 330 additions and 74 deletions

View File

@@ -71,6 +71,7 @@ public class ConsulAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Endpoint.class)
@EnableConfigurationProperties(ConsulHealthIndicatorProperties.class)
protected static class ConsulHealthConfig {
@Bean
@@ -83,8 +84,9 @@ public class ConsulAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledHealthIndicator("consul")
public ConsulHealthIndicator consulHealthIndicator(ConsulClient consulClient) {
return new ConsulHealthIndicator(consulClient);
public ConsulHealthIndicator consulHealthIndicator(ConsulClient consulClient,
ConsulHealthIndicatorProperties properties) {
return new ConsulHealthIndicator(consulClient, properties);
}
}

View File

@@ -34,18 +34,24 @@ public class ConsulHealthIndicator extends AbstractHealthIndicator {
private ConsulClient consul;
public ConsulHealthIndicator(ConsulClient consul) {
private ConsulHealthIndicatorProperties properties;
public ConsulHealthIndicator(ConsulClient consul,
ConsulHealthIndicatorProperties properties) {
this.consul = consul;
this.properties = properties;
}
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
protected void doHealthCheck(Health.Builder builder) {
final Response<String> leaderStatus = this.consul.getStatusLeader();
final Response<Map<String, List<String>>> services = this.consul
.getCatalogServices(CatalogServicesRequest.newBuilder()
.setQueryParams(QueryParams.DEFAULT).build());
builder.up().withDetail("leader", leaderStatus.getValue()).withDetail("services",
services.getValue());
builder.up().withDetail("leader", leaderStatus.getValue());
if (properties.isIncludeServicesQuery()) {
final Response<Map<String, List<String>>> services = this.consul
.getCatalogServices(CatalogServicesRequest.newBuilder()
.setQueryParams(QueryParams.DEFAULT).build());
builder.withDetail("services", services.getValue());
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2013-2019 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.cloud.consul;
import com.ecwid.consul.v1.ConsulClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.style.ToStringCreator;
/**
* Configuration properties for {@link ConsulHealthIndicator}.
*
* @author Chris Bono
*/
@ConfigurationProperties("spring.cloud.consul.health-indicator")
public class ConsulHealthIndicatorProperties {
/**
* Whether or not the indicator should include a query for all registered services
* during its execution. When set to {@code false} the indicator only uses the lighter
* {@link ConsulClient#getStatusLeader()}. This can be helpful in large deployments
* where the number of services returned makes the operation unnecessarily heavy.
*/
private boolean includeServicesQuery = true;
boolean isIncludeServicesQuery() {
return includeServicesQuery;
}
void setIncludeServicesQuery(boolean includeServicesQuery) {
this.includeServicesQuery = includeServicesQuery;
}
@Override
public String toString() {
return new ToStringCreator(this)
.append("includeServicesQuery", this.includeServicesQuery).toString();
}
}

View File

@@ -22,46 +22,107 @@ import com.ecwid.consul.v1.ConsulClient;
import com.ecwid.consul.v1.ConsulRawClient;
import com.ecwid.consul.v1.catalog.CatalogConsulClient;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.consul.test.ConsulTestcontainers;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest(properties = { "spring.cloud.consul.tls.key-store-instance-type=JKS",
"spring.cloud.consul.tls.key-store-path=src/test/resources/server.jks",
"spring.cloud.consul.tls.key-store-password=letmein",
"spring.cloud.consul.tls.certificate-path=src/test/resources/trustStore.jks",
"spring.cloud.consul.tls.certificate-password=change_me" })
@ContextConfiguration(initializers = ConsulTestcontainers.class)
/**
* Auto-configuration integration tests for {@link ConsulAutoConfiguration}.
*
* @author Chris Bono
*/
public class ConsulAutoConfigurationTests {
@Autowired
private ConsulClient consulClient;
private final ApplicationContextRunner appContextRunner = new ApplicationContextRunner()
.withInitializer(new ConsulTestcontainers())
.withConfiguration(AutoConfigurations.of(ConsulAutoConfiguration.class));
@Test
public void defaultConfiguration() {
appContextRunner.run(context -> assertThat(context).hasNotFailed()
.hasSingleBean(ConsulProperties.class).hasSingleBean(ConsulClient.class)
.hasSingleBean(ConsulHealthIndicator.class)
.doesNotHaveBean(ConsulEndpoint.class));
}
@Test
public void consulDisabled() {
appContextRunner.withPropertyValues("spring.cloud.consul.enabled=false")
.run(context -> assertThat(context).hasNotFailed()
.doesNotHaveBean(ConsulProperties.class)
.doesNotHaveBean(ConsulClient.class)
.doesNotHaveBean(ConsulHealthIndicator.class)
.doesNotHaveBean(ConsulEndpoint.class));
}
@Test
public void tlsConfigured() {
CatalogConsulClient client = (CatalogConsulClient) ReflectionTestUtils
.getField(this.consulClient, "catalogClient");
ConsulRawClient rawClient = (ConsulRawClient) ReflectionTestUtils.getField(client,
"rawClient");
HttpTransport httpTransport = (HttpTransport) ReflectionTestUtils
.getField(rawClient, "httpTransport");
assertThat(httpTransport).isInstanceOf(DefaultHttpsTransport.class);
appContextRunner.withPropertyValues(
"spring.cloud.consul.tls.key-store-instance-type=JKS",
"spring.cloud.consul.tls.key-store-path=src/test/resources/server.jks",
"spring.cloud.consul.tls.key-store-password=letmein",
"spring.cloud.consul.tls.certificate-path=src/test/resources/trustStore.jks",
"spring.cloud.consul.tls.certificate-password=change_me").run(context -> {
assertThat(context).hasNotFailed().hasSingleBean(ConsulClient.class);
ConsulClient consulClient = context.getBean(ConsulClient.class);
CatalogConsulClient catalogClient = (CatalogConsulClient) ReflectionTestUtils
.getField(consulClient, "catalogClient");
ConsulRawClient rawClient = (ConsulRawClient) ReflectionTestUtils
.getField(catalogClient, "rawClient");
HttpTransport httpTransport = (HttpTransport) ReflectionTestUtils
.getField(rawClient, "httpTransport");
assertThat(httpTransport).isInstanceOf(DefaultHttpsTransport.class);
});
}
@EnableAutoConfiguration
@SpringBootConfiguration
protected static class TestConfig {
@Test
public void nonActuatorAppGetsNoEndpointOrHealthIndicator() {
appContextRunner.withClassLoader(new FilteredClassLoader(Endpoint.class))
.withPropertyValues("management.endpoints.web.exposure.include=consul")
.run(context -> assertThat(context).hasNotFailed()
.doesNotHaveBean(ConsulHealthIndicator.class)
.doesNotHaveBean(ConsulEndpoint.class));
}
@Test
public void consulEndpointAvailable() {
appContextRunner
.withPropertyValues("management.endpoints.web.exposure.include=consul")
.run(context -> assertThat(context).hasNotFailed()
.hasSingleBean(ConsulEndpoint.class));
}
@Test
public void consulEndpointAvailableButDisabled() {
appContextRunner
.withPropertyValues("management.endpoints.web.exposure.include=consul",
"management.endpoint.consul.enabled=false")
.run(context -> assertThat(context).hasNotFailed()
.doesNotHaveBean(ConsulEndpoint.class));
}
@Test
public void consulEndpointDisabled() {
appContextRunner.withPropertyValues("spring.cloud.consul.enabled=false")
.run(context -> assertThat(context).hasNotFailed()
.doesNotHaveBean(ConsulProperties.class)
.doesNotHaveBean(ConsulClient.class)
.doesNotHaveBean(ConsulHealthIndicator.class)
.doesNotHaveBean(ConsulEndpoint.class));
}
@Test
public void consulHealthIndicatorDisabled() {
appContextRunner.withPropertyValues("management.health.consul.enabled=false")
.run(context -> assertThat(context).hasNotFailed()
.doesNotHaveBean(ConsulHealthIndicator.class));
}
}

View File

@@ -16,6 +16,9 @@
package org.springframework.cloud.consul;
import com.ecwid.consul.v1.ConsulClient;
import com.ecwid.consul.v1.Response;
import com.ecwid.consul.v1.catalog.CatalogServicesRequest;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -25,24 +28,47 @@ import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Integration test for {@link ConsulHealthIndicator} when its in the DOWN status.
*
* @author Lomesh Patel (lomeshpatel)
*/
@RunWith(SpringRunner.class)
@SpringBootTest(properties = "spring.cloud.consul.host=invalidhost")
@SpringBootTest
public class ConsulHealthIndicatorDownTest {
@MockBean
private ConsulClient consulClient;
@Autowired
private HealthEndpoint healthEndpoint;
@Test
public void doHealthCheck() {
public void statusIsDownWhenConsulClientFailsToGetLeaderStatus() {
when(consulClient.getStatusLeader()).thenThrow(new RuntimeException("no leader"));
assertThat(this.healthEndpoint.health().getStatus())
.as("health status was not DOWN").isEqualTo(Status.DOWN);
verify(consulClient).getStatusLeader();
}
@Test
public void statusIsDownWhenConsulClientFailsToGetServices() {
Response<String> leaderStatus = new Response<>("OK", 5150L, true,
System.currentTimeMillis());
when(consulClient.getStatusLeader()).thenReturn(leaderStatus);
when(consulClient.getCatalogServices(any(CatalogServicesRequest.class)))
.thenThrow(new RuntimeException("no services"));
assertThat(this.healthEndpoint.health().getStatus())
.as("health status was not DOWN").isEqualTo(Status.DOWN);
verify(consulClient).getCatalogServices(any(CatalogServicesRequest.class));
}
@EnableAutoConfiguration

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2013-2019 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.cloud.consul;
import com.ecwid.consul.v1.ConsulClient;
import com.ecwid.consul.v1.catalog.CatalogServicesRequest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.cloud.consul.test.ConsulTestcontainers;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
/**
* Integration test for {@link ConsulHealthIndicator} using its lightweight check when its
* in the UP status.
*
* @author Lomesh Patel (lomeshpatel)
*/
@RunWith(SpringRunner.class)
@SpringBootTest(
properties = "spring.cloud.consul.health-indicator.include-services-query=false")
@ContextConfiguration(initializers = ConsulTestcontainers.class)
public class ConsulHealthIndicatorLightweightUpTest {
@SpyBean
private ConsulClient consulClient;
@Autowired
private HealthEndpoint healthEndpoint;
@Test
public void statusIsUp() {
assertThat(this.healthEndpoint.health().getStatus())
.as("health status was not UP").isEqualTo(Status.UP);
verify(consulClient).getStatusLeader();
verify(consulClient, never())
.getCatalogServices(any(CatalogServicesRequest.class));
}
@EnableAutoConfiguration
@SpringBootConfiguration
protected static class TestConfig {
}
}

View File

@@ -16,6 +16,8 @@
package org.springframework.cloud.consul;
import com.ecwid.consul.v1.ConsulClient;
import com.ecwid.consul.v1.catalog.CatalogServicesRequest;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -25,13 +27,18 @@ import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.cloud.consul.test.ConsulTestcontainers;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
/**
* Integration test for {@link ConsulHealthIndicator} when its in the UP status.
*
* @author Lomesh Patel (lomeshpatel)
*/
@RunWith(SpringRunner.class)
@@ -39,13 +46,18 @@ import static org.assertj.core.api.Assertions.assertThat;
@ContextConfiguration(initializers = ConsulTestcontainers.class)
public class ConsulHealthIndicatorUpTest {
@SpyBean
private ConsulClient consulClient;
@Autowired
private HealthEndpoint healthEndpoint;
@Test
public void doHealthCheck() {
public void statusIsUp() {
assertThat(this.healthEndpoint.health().getStatus())
.as("health status was not UP").isEqualTo(Status.UP);
verify(consulClient).getStatusLeader();
verify(consulClient).getCatalogServices(any(CatalogServicesRequest.class));
}
@EnableAutoConfiguration