diff --git a/spring-boot-project/spring-boot-autoconfigure/build.gradle b/spring-boot-project/spring-boot-autoconfigure/build.gradle index b20e4d383e51..3fe8e6d23b0e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure/build.gradle @@ -76,6 +76,7 @@ dependencies { optional("org.eclipse.jetty.websocket:javax-websocket-server-impl") optional("org.ehcache:ehcache") optional("org.elasticsearch.client:elasticsearch-rest-client") + optional("org.elasticsearch.client:elasticsearch-rest-client-sniffer") optional("org.elasticsearch.client:elasticsearch-rest-high-level-client") optional("org.flywaydb:flyway-core") optional("org.freemarker:freemarker") diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfiguration.java index e30da5b261b0..f10aec8aae75 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfiguration.java @@ -30,6 +30,7 @@ import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.client.sniff.Sniffer; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -80,6 +81,20 @@ RestClientBuilder elasticsearchRestClientBuilder(ElasticsearchRestClientProperti return builder; } + @Bean + @ConditionalOnClass(name = "org.elasticsearch.client.sniff.Sniffer") + @ConditionalOnMissingBean(RestHighLevelClient.class) + Sniffer elasticsearchSniffer(ElasticsearchRestClientProperties properties) { + RestClient restClient = new RestHighLevelClient(this.elasticsearchRestClientBuilder(properties, null)) + .getLowLevelClient(); + return Sniffer.builder(restClient) + .setSniffIntervalMillis( + Math.toIntExact(ElasticsearchRestClientProperties.Sniffer.getSniffInterval().toMillis())) + .setSniffAfterFailureDelayMillis( + Math.toIntExact(ElasticsearchRestClientProperties.Sniffer.getSniffInterval().toMillis())) + .build(); + } + private HttpHost createHttpHost(String uri) { try { return createHttpHost(URI.create(uri)); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientProperties.java index 1cc76ff8a4dd..13d8d11a2309 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientProperties.java @@ -97,4 +97,39 @@ public void setReadTimeout(Duration readTimeout) { this.readTimeout = readTimeout; } + /** + * Sniffer specific properties. + */ + public static class Sniffer { + + private Duration readTimeout = Duration.ofSeconds(30); + + /** + * Sniffer interval. + */ + private static Duration sniffInterval = Duration.ofMinutes(5L); + + /** + * Sniffer failure delay. + */ + private static Duration sniffFailureDelay = Duration.ofMinutes(1L); + + public static Duration getSniffInterval() { + return sniffInterval; + } + + public void setSniffInterval(Duration sniffInterval) { + Sniffer.sniffInterval = sniffInterval; + } + + public static Duration getSniffFailureDelay() { + return sniffFailureDelay; + } + + public void setSniffFailureDelay(Duration sniffFailureDelay) { + Sniffer.sniffFailureDelay = sniffFailureDelay; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationTests.java index 364cd1620ed3..ff35472303dd 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationTests.java @@ -16,10 +16,6 @@ package org.springframework.boot.autoconfigure.elasticsearch; -import java.time.Duration; -import java.util.HashMap; -import java.util.Map; - import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; @@ -29,21 +25,22 @@ import org.assertj.core.api.InstanceOfAssertFactories; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.index.IndexRequest; -import org.elasticsearch.client.Node; -import org.elasticsearch.client.RequestOptions; -import org.elasticsearch.client.RestClient; -import org.elasticsearch.client.RestClientBuilder; -import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.client.*; +import org.elasticsearch.client.sniff.Sniffer; +import org.junit.Assert; import org.junit.jupiter.api.Test; -import org.testcontainers.elasticsearch.ElasticsearchContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; - import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.testcontainers.elasticsearch.ElasticsearchContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -220,6 +217,53 @@ void configureUriWithUsernameAndPasswordWhenUsernameAndPasswordPropertiesSet() { }); } + @Test + void configureShouldOnlyCreateSnifferInstance() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(Sniffer.class); + assertThat(context).doesNotHaveBean(RestClient.class).hasSingleBean(RestHighLevelClient.class); + }); + } + + @Test + void configureShouldHaveSnifferInstance() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(Sniffer.class); + assertThat(context).doesNotHaveBean(RestClient.class).hasSingleBean(RestHighLevelClient.class); + Assert.assertNotNull(context.getBean(Sniffer.class)); + }); + } + + @Test + void configureWithCustomSetIntervalProperties() { + this.contextRunner.withPropertyValues( + "spring.elasticsearch.rest.sniffInterval=1m, " + "spring.elasticsearch.rest.sniffFailureDelay=1m") + .run((context) -> { + Assert.assertNotNull(context.getBean(Sniffer.class)); + assertThat(context).hasSingleBean(RestHighLevelClient.class); + RestHighLevelClient restClient = context.getBean(RestHighLevelClient.class); + assertThat(restClient.getLowLevelClient()).extracting("spring.elasticsearch.rest.sniffInterval") + .isEqualTo(Math.toIntExact(Duration.ofMinutes(1L).toMillis())); + assertThat(restClient.getLowLevelClient()).extracting("spring.elasticsearch.rest.sniffFailureDelay") + .isEqualTo(Math.toIntExact(Duration.ofMinutes(1L).toMillis())); + }); + } + + @Test + void configureWithNoAutoConfigurationPropertySet() { + this.contextRunner.run((context) -> { + assertThat(context).doesNotHaveBean(Sniffer.class); + Assert.assertNull(context.getBean(Sniffer.class)); + }); + } + + @Test + void configureWhenCustomSnifferWhenPresent() { + this.contextRunner.withBean("customSniffer", Sniffer.class, () -> mock(Sniffer.class)) + .run((context) -> assertThat(context).hasSingleBean(RestHighLevelClient.class) + .hasSingleBean(Sniffer.class).hasBean("customSniffer")); + } + @Configuration(proxyBeanMethods = false) static class BuilderCustomizerConfiguration {