diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthContributorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthContributorAutoConfiguration.java index 0fe95723dc24..f9884acffec7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthContributorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthContributorAutoConfiguration.java @@ -16,43 +16,31 @@ package org.springframework.boot.actuate.autoconfigure.elasticsearch; -import java.util.Map; - import org.elasticsearch.client.RestClient; -import org.elasticsearch.client.RestHighLevelClient; -import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration; +import org.springframework.boot.actuate.autoconfigure.elasticsearch.ElasticSearchRestHealthContributorConfigurations.RestClientHealthContributorConfiguration; import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator; -import org.springframework.boot.actuate.elasticsearch.ElasticsearchRestHealthIndicator; -import org.springframework.boot.actuate.health.HealthContributor; 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.elasticsearch.ElasticsearchRestClientAutoConfiguration; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; /** - * {@link EnableAutoConfiguration Auto-configuration} for - * {@link ElasticsearchRestHealthIndicator} using the {@link RestClient}. + * {@link EnableAutoConfiguration Auto-configuration} for Elasticsearch health indicator + * using REST clients. * * @author Artsiom Yudovin * @since 2.1.1 */ +@SuppressWarnings("deprecation") @Configuration(proxyBeanMethods = false) -@ConditionalOnClass(RestHighLevelClient.class) -@ConditionalOnBean(RestHighLevelClient.class) +@ConditionalOnClass(RestClient.class) @ConditionalOnEnabledHealthIndicator("elasticsearch") @AutoConfigureAfter(ElasticsearchRestClientAutoConfiguration.class) -public class ElasticSearchRestHealthContributorAutoConfiguration - extends CompositeHealthContributorConfiguration { - - @Bean - @ConditionalOnMissingBean(name = { "elasticsearchHealthIndicator", "elasticsearchHealthContributor" }) - public HealthContributor elasticsearchHealthContributor(Map clients) { - return createContributor(clients); - } +@Import({ ElasticSearchRestHealthContributorConfigurations.RestHighLevelClientHealthContributorConfiguration.class, + RestClientHealthContributorConfiguration.class }) +public class ElasticSearchRestHealthContributorAutoConfiguration { } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthContributorConfigurations.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthContributorConfigurations.java new file mode 100644 index 000000000000..b346ff1fe147 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthContributorConfigurations.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2022 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.boot.actuate.autoconfigure.elasticsearch; + +import java.util.Map; + +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestHighLevelClient; + +import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration; +import org.springframework.boot.actuate.elasticsearch.ElasticsearchRestClientHealthIndicator; +import org.springframework.boot.actuate.health.HealthContributor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Elasticsearch rest client health contributor configurations. + * + * @author Filip Hrisafov + */ +class ElasticSearchRestHealthContributorConfigurations { + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(RestHighLevelClient.class) + @ConditionalOnBean(RestHighLevelClient.class) + @Deprecated + static class RestHighLevelClientHealthContributorConfiguration extends + CompositeHealthContributorConfiguration { + + @Bean + @ConditionalOnMissingBean(name = { "elasticsearchHealthIndicator", "elasticsearchHealthContributor" }) + HealthContributor elasticsearchHealthContributor(Map clients) { + return createContributor(clients); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnBean(RestClient.class) + @ConditionalOnMissingBean(RestHighLevelClient.class) + static class RestClientHealthContributorConfiguration + extends CompositeHealthContributorConfiguration { + + @Bean + @ConditionalOnMissingBean(name = { "elasticsearchHealthIndicator", "elasticsearchHealthContributor" }) + HealthContributor elasticsearchHealthContributor(Map clients) { + return createContributor(clients); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchRestHealthContributorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchRestHealthContributorAutoConfigurationTests.java new file mode 100644 index 000000000000..5610d0e8b749 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchRestHealthContributorAutoConfigurationTests.java @@ -0,0 +1,111 @@ +/* + * Copyright 2012-2020 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.boot.actuate.autoconfigure.elasticsearch; + +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; +import org.elasticsearch.client.RestHighLevelClient; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; +import org.springframework.boot.actuate.elasticsearch.ElasticsearchRestClientHealthIndicator; +import org.springframework.boot.actuate.elasticsearch.ElasticsearchRestHealthIndicator; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ElasticsearchRestClientAutoConfiguration}. + * + * @author Filip Hrisafov + */ +class ElasticsearchRestHealthContributorAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(ElasticsearchRestClientAutoConfiguration.class, + ElasticSearchRestHealthContributorAutoConfiguration.class, + HealthContributorAutoConfiguration.class)); + + @Test + @SuppressWarnings("deprecation") + void runShouldCreateIndicator() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ElasticsearchRestHealthIndicator.class) + .hasBean("elasticsearchHealthContributor")); + } + + @Test + void runWithoutRestHighLevelClientAndWithoutRestClientShouldNotCreateIndicator() { + this.contextRunner.withClassLoader(new FilteredClassLoader(RestHighLevelClient.class, RestClient.class)) + .run((context) -> assertThat(context).doesNotHaveBean(ElasticsearchRestClientHealthIndicator.class) + .doesNotHaveBean("elasticsearchHealthContributor")); + } + + @Test + @SuppressWarnings("deprecation") + void runWithoutRestHighLevelClientAndWithRestClientShouldCreateIndicator() { + this.contextRunner.withUserConfiguration(CustomRestClientConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(ElasticsearchRestClientHealthIndicator.class) + .doesNotHaveBean(ElasticsearchRestHealthIndicator.class) + .hasBean("elasticsearchHealthContributor")); + } + + @Test + @SuppressWarnings("deprecation") + void runWithRestHighLevelClientAndWithRestClientShouldCreateIndicator() { + this.contextRunner.withUserConfiguration(CustomRestHighClientConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(ElasticsearchRestClientHealthIndicator.class) + .hasBean("elasticsearchHealthContributor")); + } + + @Test + void runWhenDisabledShouldNotCreateIndicator() { + this.contextRunner.withPropertyValues("management.health.elasticsearch.enabled:false") + .run((context) -> assertThat(context).doesNotHaveBean(ElasticsearchRestClientHealthIndicator.class) + .doesNotHaveBean("elasticsearchHealthContributor")); + } + + @Configuration(proxyBeanMethods = false) + static class CustomRestClientConfiguration { + + @Bean + RestClient customRestClient(RestClientBuilder builder) { + return builder.build(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class CustomRestHighClientConfiguration { + + @Bean + RestHighLevelClient customRestHighClient(RestClientBuilder builder) { + return new RestHighLevelClient(builder); + } + + @Bean + RestClient customClient(RestHighLevelClient restHighLevelClient) { + return restHighLevelClient.getLowLevelClient(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestClientHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestClientHealthIndicator.java new file mode 100644 index 000000000000..da0821e14385 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestClientHealthIndicator.java @@ -0,0 +1,85 @@ +/* + * Copyright 2012-2020 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.boot.actuate.elasticsearch; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import org.apache.http.HttpStatus; +import org.apache.http.StatusLine; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.Response; +import org.elasticsearch.client.RestClient; + +import org.springframework.boot.actuate.health.AbstractHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.json.JsonParser; +import org.springframework.boot.json.JsonParserFactory; +import org.springframework.util.StreamUtils; + +/** + * {@link HealthIndicator} for an Elasticsearch cluster using a {@link RestClient}. + * + * @author Artsiom Yudovin + * @author Brian Clozel + * @author Filip Hrisafov + * @since 2.7 + */ +public class ElasticsearchRestClientHealthIndicator extends AbstractHealthIndicator { + + private static final String RED_STATUS = "red"; + + private final RestClient client; + + private final JsonParser jsonParser; + + public ElasticsearchRestClientHealthIndicator(RestClient client) { + super("Elasticsearch health check failed"); + this.client = client; + this.jsonParser = JsonParserFactory.getJsonParser(); + } + + @Override + protected void doHealthCheck(Health.Builder builder) throws Exception { + Response response = this.client.performRequest(new Request("GET", "/_cluster/health/")); + StatusLine statusLine = response.getStatusLine(); + if (statusLine.getStatusCode() != HttpStatus.SC_OK) { + builder.down(); + builder.withDetail("statusCode", statusLine.getStatusCode()); + builder.withDetail("reasonPhrase", statusLine.getReasonPhrase()); + return; + } + try (InputStream inputStream = response.getEntity().getContent()) { + doHealthCheck(builder, StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8)); + } + } + + private void doHealthCheck(Health.Builder builder, String json) { + Map response = this.jsonParser.parseMap(json); + String status = (String) response.get("status"); + if (RED_STATUS.equals(status)) { + builder.outOfService(); + } + else { + builder.up(); + } + builder.withDetails(response); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestHealthIndicator.java index c6b7f69332cb..62325eeba722 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestHealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestHealthIndicator.java @@ -16,23 +16,10 @@ package org.springframework.boot.actuate.elasticsearch; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.Map; - -import org.apache.http.HttpStatus; -import org.apache.http.StatusLine; -import org.elasticsearch.client.Request; -import org.elasticsearch.client.Response; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestHighLevelClient; -import org.springframework.boot.actuate.health.AbstractHealthIndicator; -import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.json.JsonParser; -import org.springframework.boot.json.JsonParserFactory; -import org.springframework.util.StreamUtils; /** * {@link HealthIndicator} for an Elasticsearch cluster using a {@link RestClient}. @@ -41,50 +28,18 @@ * @author Brian Clozel * @author Filip Hrisafov * @since 2.1.1 + * @deprecated since 2.7.0 for removal in 2.9.0 in favor of + * {@link ElasticsearchRestClientHealthIndicator} */ -public class ElasticsearchRestHealthIndicator extends AbstractHealthIndicator { - - private static final String RED_STATUS = "red"; - - private final RestClient client; - - private final JsonParser jsonParser; +@Deprecated +public class ElasticsearchRestHealthIndicator extends ElasticsearchRestClientHealthIndicator { public ElasticsearchRestHealthIndicator(RestHighLevelClient client) { this(client.getLowLevelClient()); } public ElasticsearchRestHealthIndicator(RestClient client) { - super("Elasticsearch health check failed"); - this.client = client; - this.jsonParser = JsonParserFactory.getJsonParser(); - } - - @Override - protected void doHealthCheck(Health.Builder builder) throws Exception { - Response response = this.client.performRequest(new Request("GET", "/_cluster/health/")); - StatusLine statusLine = response.getStatusLine(); - if (statusLine.getStatusCode() != HttpStatus.SC_OK) { - builder.down(); - builder.withDetail("statusCode", statusLine.getStatusCode()); - builder.withDetail("reasonPhrase", statusLine.getReasonPhrase()); - return; - } - try (InputStream inputStream = response.getEntity().getContent()) { - doHealthCheck(builder, StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8)); - } - } - - private void doHealthCheck(Health.Builder builder, String json) { - Map response = this.jsonParser.parseMap(json); - String status = (String) response.get("status"); - if (RED_STATUS.equals(status)) { - builder.outOfService(); - } - else { - builder.up(); - } - builder.withDetails(response); + super(client); } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestClientHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestClientHealthIndicatorTests.java new file mode 100644 index 000000000000..80550160b055 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestClientHealthIndicatorTests.java @@ -0,0 +1,143 @@ +/* + * Copyright 2012-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.boot.actuate.elasticsearch; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Map; + +import org.apache.http.StatusLine; +import org.apache.http.entity.BasicHttpEntity; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.Response; +import org.elasticsearch.client.RestClient; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.Status; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link ElasticsearchRestClientHealthIndicator}. + * + * @author Artsiom Yudovin + * @author Filip Hrisafov + */ +class ElasticsearchRestClientHealthIndicatorTests { + + private final RestClient restClient = mock(RestClient.class); + + private final ElasticsearchRestClientHealthIndicator elasticsearchRestHealthIndicator = new ElasticsearchRestClientHealthIndicator( + this.restClient); + + @Test + void elasticsearchIsUp() throws IOException { + BasicHttpEntity httpEntity = new BasicHttpEntity(); + httpEntity.setContent(new ByteArrayInputStream(createJsonResult(200, "green").getBytes())); + Response response = mock(Response.class); + StatusLine statusLine = mock(StatusLine.class); + given(statusLine.getStatusCode()).willReturn(200); + given(response.getStatusLine()).willReturn(statusLine); + given(response.getEntity()).willReturn(httpEntity); + given(this.restClient.performRequest(any(Request.class))).willReturn(response); + Health health = this.elasticsearchRestHealthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertHealthDetailsWithStatus(health.getDetails(), "green"); + } + + @Test + void elasticsearchWithYellowStatusIsUp() throws IOException { + BasicHttpEntity httpEntity = new BasicHttpEntity(); + httpEntity.setContent(new ByteArrayInputStream(createJsonResult(200, "yellow").getBytes())); + Response response = mock(Response.class); + StatusLine statusLine = mock(StatusLine.class); + given(statusLine.getStatusCode()).willReturn(200); + given(response.getStatusLine()).willReturn(statusLine); + given(response.getEntity()).willReturn(httpEntity); + given(this.restClient.performRequest(any(Request.class))).willReturn(response); + Health health = this.elasticsearchRestHealthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertHealthDetailsWithStatus(health.getDetails(), "yellow"); + } + + @Test + void elasticsearchIsDown() throws IOException { + given(this.restClient.performRequest(any(Request.class))).willThrow(new IOException("Couldn't connect")); + Health health = this.elasticsearchRestHealthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.DOWN); + assertThat(health.getDetails()).contains(entry("error", "java.io.IOException: Couldn't connect")); + } + + @Test + void elasticsearchIsDownByResponseCode() throws IOException { + Response response = mock(Response.class); + StatusLine statusLine = mock(StatusLine.class); + given(statusLine.getStatusCode()).willReturn(500); + given(statusLine.getReasonPhrase()).willReturn("Internal server error"); + given(response.getStatusLine()).willReturn(statusLine); + given(this.restClient.performRequest(any(Request.class))).willReturn(response); + Health health = this.elasticsearchRestHealthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.DOWN); + assertThat(health.getDetails()).contains(entry("statusCode", 500), + entry("reasonPhrase", "Internal server error")); + } + + @Test + void elasticsearchIsOutOfServiceByStatus() throws IOException { + BasicHttpEntity httpEntity = new BasicHttpEntity(); + httpEntity.setContent(new ByteArrayInputStream(createJsonResult(200, "red").getBytes())); + Response response = mock(Response.class); + StatusLine statusLine = mock(StatusLine.class); + given(statusLine.getStatusCode()).willReturn(200); + given(response.getStatusLine()).willReturn(statusLine); + given(response.getEntity()).willReturn(httpEntity); + given(this.restClient.performRequest(any(Request.class))).willReturn(response); + Health health = this.elasticsearchRestHealthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.OUT_OF_SERVICE); + assertHealthDetailsWithStatus(health.getDetails(), "red"); + } + + private void assertHealthDetailsWithStatus(Map details, String status) { + assertThat(details).contains(entry("cluster_name", "elasticsearch"), entry("status", status), + entry("timed_out", false), entry("number_of_nodes", 1), entry("number_of_data_nodes", 1), + entry("active_primary_shards", 0), entry("active_shards", 0), entry("relocating_shards", 0), + entry("initializing_shards", 0), entry("unassigned_shards", 0), entry("delayed_unassigned_shards", 0), + entry("number_of_pending_tasks", 0), entry("number_of_in_flight_fetch", 0), + entry("task_max_waiting_in_queue_millis", 0), entry("active_shards_percent_as_number", 100.0)); + } + + private String createJsonResult(int responseCode, String status) { + if (responseCode == 200) { + return String.format( + "{\"cluster_name\":\"elasticsearch\"," + + "\"status\":\"%s\",\"timed_out\":false,\"number_of_nodes\":1," + + "\"number_of_data_nodes\":1,\"active_primary_shards\":0," + + "\"active_shards\":0,\"relocating_shards\":0,\"initializing_shards\":0," + + "\"unassigned_shards\":0,\"delayed_unassigned_shards\":0," + + "\"number_of_pending_tasks\":0,\"number_of_in_flight_fetch\":0," + + "\"task_max_waiting_in_queue_millis\":0,\"active_shards_percent_as_number\":100.0}", + status); + } + return "{\n \"error\": \"Server Error\",\n \"status\": " + responseCode + "\n}"; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestHealthIndicatorTests.java index c283c049e2b3..8312fb5dbbb5 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestHealthIndicatorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestHealthIndicatorTests.java @@ -42,6 +42,7 @@ * @author Artsiom Yudovin * @author Filip Hrisafov */ +@Deprecated class ElasticsearchRestHealthIndicatorTests { private final RestClient restClient = mock(RestClient.class); 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 6064f0f33279..38aba2ad197a 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 @@ -21,7 +21,9 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientConfigurations.RestClientBuilderConfiguration; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientConfigurations.RestClientConfiguration; import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientConfigurations.RestClientSnifferConfiguration; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientConfigurations.RestClientWithRestHighLevelClientConfiguration; import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientConfigurations.RestHighLevelClientConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; @@ -40,6 +42,7 @@ @EnableConfigurationProperties({ ElasticsearchProperties.class, ElasticsearchRestClientProperties.class, DeprecatedElasticsearchRestClientProperties.class }) @Import({ RestClientBuilderConfiguration.class, RestHighLevelClientConfiguration.class, + RestClientWithRestHighLevelClientConfiguration.class, RestClientConfiguration.class, RestClientSnifferConfiguration.class }) public class ElasticsearchRestClientAutoConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientConfigurations.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientConfigurations.java index 0e02b5ec52c1..1f8e845db578 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientConfigurations.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientConfigurations.java @@ -37,6 +37,7 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.context.annotation.Bean; @@ -47,6 +48,7 @@ * Elasticsearch rest client configurations. * * @author Stephane Nicoll + * @author Filip Hrisafov */ class ElasticsearchRestClientConfigurations { @@ -124,16 +126,41 @@ RestHighLevelClient elasticsearchRestHighLevelClient(RestClientBuilder restClien } @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(Sniffer.class) + @ConditionalOnClass(RestHighLevelClient.class) @ConditionalOnSingleCandidate(RestHighLevelClient.class) + @ConditionalOnMissingBean(RestClient.class) + static class RestClientWithRestHighLevelClientConfiguration { + + @Bean + RestClient elasticsearchRestClient(RestHighLevelClient restHighLevelClient) { + return restHighLevelClient.getLowLevelClient(); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnMissingClass("org.elasticsearch.client.RestHighLevelClient") + @ConditionalOnMissingBean(RestClient.class) + static class RestClientConfiguration { + + @Bean + RestClient elasticsearchRestClient(RestClientBuilder restClientBuilder) { + return restClientBuilder.build(); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(Sniffer.class) + @ConditionalOnSingleCandidate(RestClient.class) static class RestClientSnifferConfiguration { @Bean @ConditionalOnMissingBean @SuppressWarnings("deprecation") - Sniffer elasticsearchSniffer(RestHighLevelClient client, ElasticsearchRestClientProperties properties, + Sniffer elasticsearchSniffer(RestClient client, ElasticsearchRestClientProperties properties, DeprecatedElasticsearchRestClientProperties deprecatedProperties) { - SnifferBuilder builder = Sniffer.builder(client.getLowLevelClient()); + SnifferBuilder builder = Sniffer.builder(client); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); Duration interval = deprecatedProperties.isCustomized() ? deprecatedProperties.getSniffer().getInterval() : properties.getSniffer().getInterval(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationIntegrationTests.java index 49dbdb7e2890..52510ba98052 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/elasticsearch/ElasticsearchRestClientAutoConfigurationIntegrationTests.java @@ -16,13 +16,19 @@ package org.springframework.boot.autoconfigure.elasticsearch; +import java.io.InputStream; import java.time.Duration; import java.util.HashMap; import java.util.Map; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.client.Request; import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.Response; +import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestHighLevelClient; import org.junit.jupiter.api.Test; import org.testcontainers.elasticsearch.ElasticsearchContainer; @@ -30,6 +36,7 @@ import org.testcontainers.junit.jupiter.Testcontainers; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; @@ -41,6 +48,7 @@ * @author Brian Clozel * @author Vedran Pavic * @author Evgeniy Cheban + * @author Filip Hrisafov */ @Testcontainers(disabledWithoutDocker = true) class ElasticsearchRestClientAutoConfigurationIntegrationTests { @@ -53,7 +61,7 @@ class ElasticsearchRestClientAutoConfigurationIntegrationTests { .withConfiguration(AutoConfigurations.of(ElasticsearchRestClientAutoConfiguration.class)); @Test - void restClientCanQueryElasticsearchNode() { + void restHighLevelClientCanQueryElasticsearchNode() { this.contextRunner .withPropertyValues("spring.elasticsearch.uris=" + elasticsearch.getHttpHostAddress(), "spring.elasticsearch.connection-timeout=120s", "spring.elasticsearch.socket-timeout=120s") @@ -69,4 +77,42 @@ void restClientCanQueryElasticsearchNode() { }); } + @Test + void restClientCanQueryElasticsearchNode() { + this.contextRunner + .withPropertyValues("spring.elasticsearch.uris=" + elasticsearch.getHttpHostAddress(), + "spring.elasticsearch.connection-timeout=120s", "spring.elasticsearch.socket-timeout=120s") + .run((context) -> { + RestClient client = context.getBean(RestClient.class); + Request index = new Request("PUT", "/test/_doc/2"); + index.setJsonEntity("{" + " \"a\": \"alpha\"," + " \"b\": \"bravo\"" + "}"); + client.performRequest(index); + Request getRequest = new Request("GET", "/test/_doc/2"); + Response response = client.performRequest(getRequest); + try (InputStream input = response.getEntity().getContent()) { + JsonNode result = new ObjectMapper().readTree(input); + assertThat(result.path("found").asBoolean()).isTrue(); + } + }); + } + + @Test + void restClientCanQueryElasticsearchNodeWithoutHighLevelClient() { + this.contextRunner.withClassLoader(new FilteredClassLoader(RestHighLevelClient.class)) + .withPropertyValues("spring.elasticsearch.uris=" + elasticsearch.getHttpHostAddress(), + "spring.elasticsearch.connection-timeout=120s", "spring.elasticsearch.socket-timeout=120s") + .run((context) -> { + RestClient client = context.getBean(RestClient.class); + Request index = new Request("PUT", "/test/_doc/3"); + index.setJsonEntity("{" + " \"a\": \"alpha\"," + " \"b\": \"bravo\"" + "}"); + client.performRequest(index); + Request getRequest = new Request("GET", "/test/_doc/3"); + Response response = client.performRequest(getRequest); + try (InputStream input = response.getEntity().getContent()) { + JsonNode result = new ObjectMapper().readTree(input); + assertThat(result.path("found").asBoolean()).isTrue(); + } + }); + } + } 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 deb6969d7503..a445928c55f2 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 @@ -63,15 +63,19 @@ class ElasticsearchRestClientAutoConfigurationTests { .withConfiguration(AutoConfigurations.of(ElasticsearchRestClientAutoConfiguration.class)); @Test - void configureShouldOnlyCreateHighLevelRestClient() { - this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(RestClient.class) - .hasSingleBean(RestClientBuilder.class).hasSingleBean(RestHighLevelClient.class)); + void configureShouldCreateHighLevelAndLowLevelRestClient() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(RestClient.class).hasSingleBean(RestHighLevelClient.class) + .hasSingleBean(RestClientBuilder.class); + assertThat(context.getBean(RestClient.class)) + .isEqualTo(context.getBean(RestHighLevelClient.class).getLowLevelClient()); + }); } @Test - void configureWithoutRestHighLevelClientShouldOnlyCreateRestClientBuilder() { + void configureWithoutRestHighLevelClientShouldOnlyCreateRestClientBuilderAndRestClient() { this.contextRunner.withClassLoader(new FilteredClassLoader(RestHighLevelClient.class)) - .run((context) -> assertThat(context).doesNotHaveBean(RestClient.class) + .run((context) -> assertThat(context).hasSingleBean(RestClient.class) .doesNotHaveBean(RestHighLevelClient.class).hasSingleBean(RestClientBuilder.class)); } @@ -83,24 +87,49 @@ void configureWhenCustomRestClientShouldBackOff() { .hasBean("customRestClient")); } + @Test + void configureWhenCustomRestHighLevelClientIsNotPresent() { + this.contextRunner.withClassLoader(new FilteredClassLoader(RestHighLevelClient.class)) + .run((context) -> assertThat(context).doesNotHaveBean(RestHighLevelClient.class) + .hasSingleBean(RestClient.class).hasSingleBean(RestClientBuilder.class)); + } + @Test void configureWhenCustomRestHighLevelClientShouldBackOff() { this.contextRunner.withUserConfiguration(CustomRestHighLevelClientConfiguration.class) - .run((context) -> assertThat(context).hasSingleBean(RestHighLevelClient.class)); + .run((context) -> assertThat(context).hasSingleBean(RestHighLevelClient.class) + .hasSingleBean(RestClient.class).hasBean("elasticsearchRestClient")); + } + + @Test + void configureWhenCustomRestHighLevelClientAndRestClientWithRestHighLevelClientShouldBackOff() { + this.contextRunner.withUserConfiguration(CustomRestHighLevelClientWithRestClientConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(RestHighLevelClient.class) + .hasSingleBean(RestClient.class).hasBean("customRestClient")); } @Test void configureWhenDefaultRestClientShouldCreateWhenNoUniqueRestHighLevelClient() { this.contextRunner.withUserConfiguration(TwoCustomRestHighLevelClientConfiguration.class).run((context) -> { + assertThat(context).doesNotHaveBean(RestClient.class); Map restHighLevelClients = context.getBeansOfType(RestHighLevelClient.class); assertThat(restHighLevelClients).hasSize(2); }); } + @Test + void configureWhenDefaultRestClientShouldCreateWhenNoUniqueRestClient() { + this.contextRunner.withClassLoader(new FilteredClassLoader(RestHighLevelClient.class)) + .withUserConfiguration(TwoCustomRestClientConfiguration.class).run((context) -> { + Map restClients = context.getBeansOfType(RestClient.class); + assertThat(restClients).hasSize(2); + }); + } + @Test void configureWhenBuilderCustomizerShouldApply() { this.contextRunner.withUserConfiguration(BuilderCustomizerConfiguration.class).run((context) -> { - assertThat(context).hasSingleBean(RestHighLevelClient.class); + assertThat(context).hasSingleBean(RestHighLevelClient.class).hasSingleBean(RestClient.class); RestHighLevelClient restClient = context.getBean(RestHighLevelClient.class); RestClient lowLevelClient = restClient.getLowLevelClient(); assertThat(lowLevelClient).hasFieldOrPropertyWithValue("pathPrefix", "/test"); @@ -112,8 +141,8 @@ void configureWhenBuilderCustomizerShouldApply() { @Test void configureWithNoTimeoutsApplyDefaults() { this.contextRunner.run((context) -> { - assertThat(context).hasSingleBean(RestHighLevelClient.class); - RestHighLevelClient restClient = context.getBean(RestHighLevelClient.class); + assertThat(context).hasSingleBean(RestClient.class); + RestClient restClient = context.getBean(RestClient.class); assertTimeouts(restClient, Duration.ofMillis(RestClientBuilder.DEFAULT_CONNECT_TIMEOUT_MILLIS), Duration.ofMillis(RestClientBuilder.DEFAULT_SOCKET_TIMEOUT_MILLIS)); }); @@ -123,8 +152,8 @@ void configureWithNoTimeoutsApplyDefaults() { void configureWithLegacyCustomTimeouts() { this.contextRunner.withPropertyValues("spring.elasticsearch.rest.connection-timeout=15s", "spring.elasticsearch.rest.read-timeout=1m").run((context) -> { - assertThat(context).hasSingleBean(RestHighLevelClient.class); - RestHighLevelClient restClient = context.getBean(RestHighLevelClient.class); + assertThat(context).hasSingleBean(RestClient.class); + RestClient restClient = context.getBean(RestClient.class); assertTimeouts(restClient, Duration.ofSeconds(15), Duration.ofMinutes(1)); }); } @@ -133,23 +162,23 @@ void configureWithLegacyCustomTimeouts() { void configureWithCustomTimeouts() { this.contextRunner.withPropertyValues("spring.elasticsearch.connection-timeout=15s", "spring.elasticsearch.socket-timeout=1m").run((context) -> { - assertThat(context).hasSingleBean(RestHighLevelClient.class); - RestHighLevelClient restClient = context.getBean(RestHighLevelClient.class); + assertThat(context).hasSingleBean(RestClient.class); + RestClient restClient = context.getBean(RestClient.class); assertTimeouts(restClient, Duration.ofSeconds(15), Duration.ofMinutes(1)); }); } - private static void assertTimeouts(RestHighLevelClient restClient, Duration connectTimeout, Duration readTimeout) { - assertThat(restClient.getLowLevelClient()).extracting("client.defaultConfig.socketTimeout") + private static void assertTimeouts(RestClient restClient, Duration connectTimeout, Duration readTimeout) { + assertThat(restClient).extracting("client.defaultConfig.socketTimeout") .isEqualTo(Math.toIntExact(readTimeout.toMillis())); - assertThat(restClient.getLowLevelClient()).extracting("client.defaultConfig.connectTimeout") + assertThat(restClient).extracting("client.defaultConfig.connectTimeout") .isEqualTo(Math.toIntExact(connectTimeout.toMillis())); } @ParameterizedPropertyPrefixTest void configureUriWithNoScheme(String prefix) { this.contextRunner.withPropertyValues(prefix + "uris=localhost:9876").run((context) -> { - RestClient client = context.getBean(RestHighLevelClient.class).getLowLevelClient(); + RestClient client = context.getBean(RestClient.class); assertThat(client.getNodes().stream().map(Node::getHost).map(HttpHost::toString)) .containsExactly("http://localhost:9876"); }); @@ -158,7 +187,7 @@ void configureUriWithNoScheme(String prefix) { @ParameterizedPropertyPrefixTest void configureUriWithUsernameOnly(String prefix) { this.contextRunner.withPropertyValues(prefix + "uris=http://user@localhost:9200").run((context) -> { - RestClient client = context.getBean(RestHighLevelClient.class).getLowLevelClient(); + RestClient client = context.getBean(RestClient.class); assertThat(client.getNodes().stream().map(Node::getHost).map(HttpHost::toString)) .containsExactly("http://localhost:9200"); assertThat(client) @@ -174,7 +203,7 @@ void configureUriWithUsernameOnly(String prefix) { @ParameterizedPropertyPrefixTest void configureUriWithUsernameAndEmptyPassword(String prefix) { this.contextRunner.withPropertyValues(prefix + "uris=http://user:@localhost:9200").run((context) -> { - RestClient client = context.getBean(RestHighLevelClient.class).getLowLevelClient(); + RestClient client = context.getBean(RestClient.class); assertThat(client.getNodes().stream().map(Node::getHost).map(HttpHost::toString)) .containsExactly("http://localhost:9200"); assertThat(client) @@ -191,7 +220,7 @@ void configureUriWithUsernameAndEmptyPassword(String prefix) { void configureUriWithUsernameAndPasswordWhenUsernameAndPasswordPropertiesSet(String prefix) { this.contextRunner.withPropertyValues(prefix + "uris=http://user:password@localhost:9200,localhost:9201", prefix + "username=admin", prefix + "password=admin").run((context) -> { - RestClient client = context.getBean(RestHighLevelClient.class).getLowLevelClient(); + RestClient client = context.getBean(RestClient.class); assertThat(client.getNodes().stream().map(Node::getHost).map(HttpHost::toString)) .containsExactly("http://localhost:9200", "http://localhost:9201"); assertThat(client) @@ -213,7 +242,7 @@ void configureUriWithUsernameAndPasswordWhenUsernameAndPasswordPropertiesSet(Str @Test void configureWithCustomPathPrefix() { this.contextRunner.withPropertyValues("spring.elasticsearch.path-prefix=/some/prefix").run((context) -> { - RestClient client = context.getBean(RestHighLevelClient.class).getLowLevelClient(); + RestClient client = context.getBean(RestClient.class); assertThat(client).extracting("pathPrefix").isEqualTo("/some/prefix"); }); } @@ -222,17 +251,17 @@ void configureWithCustomPathPrefix() { void configureWithoutSnifferLibraryShouldNotCreateSniffer() { this.contextRunner.withClassLoader(new FilteredClassLoader("org.elasticsearch.client.sniff")) .run((context) -> assertThat(context).hasSingleBean(RestHighLevelClient.class) - .doesNotHaveBean(Sniffer.class)); + .hasSingleBean(RestClient.class).doesNotHaveBean(Sniffer.class)); } @Test - void configureShouldCreateSnifferUsingRestHighLevelClient() { - this.contextRunner.run((context) -> { + void configureShouldCreateSnifferUsingRestClient() { + this.contextRunner.withClassLoader(new FilteredClassLoader(RestHighLevelClient.class)).run((context) -> { assertThat(context).hasSingleBean(Sniffer.class); assertThat(context.getBean(Sniffer.class)).hasFieldOrPropertyWithValue("restClient", - context.getBean(RestHighLevelClient.class).getLowLevelClient()); + context.getBean(RestClient.class)); // Validate shutdown order as the sniffer must be shutdown before the client - assertThat(context.getBeanFactory().getDependentBeans("elasticsearchRestHighLevelClient")) + assertThat(context.getBeanFactory().getDependentBeans("elasticsearchRestClient")) .contains("elasticsearchSniffer"); }); } @@ -298,6 +327,21 @@ RestHighLevelClient customRestHighLevelClient(RestClientBuilder builder) { } + @Configuration(proxyBeanMethods = false) + static class CustomRestHighLevelClientWithRestClientConfiguration { + + @Bean + RestHighLevelClient customRestHighLevelClient(RestClientBuilder builder) { + return new RestHighLevelClient(builder); + } + + @Bean + RestClient customRestClient(RestHighLevelClient restHighLevelClient) { + return restHighLevelClient.getLowLevelClient(); + } + + } + @Configuration(proxyBeanMethods = false) static class TwoCustomRestHighLevelClientConfiguration { @@ -323,6 +367,21 @@ RestClient customRestClient(RestClientBuilder builder) { } + @Configuration(proxyBeanMethods = false) + static class TwoCustomRestClientConfiguration { + + @Bean + RestClient customRestClient(RestClientBuilder builder) { + return builder.build(); + } + + @Bean + RestClient customRestClient1(RestClientBuilder builder) { + return builder.build(); + } + + } + @ParameterizedTest @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/nosql.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/nosql.adoc index 2c01cffebc25..ddcf853ed6fe 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/nosql.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/data/nosql.adoc @@ -292,14 +292,14 @@ You can use `spring.elasticsearch.*` properties to further tune how the clients ---- [[data.nosql.elasticsearch.connecting-using-rest.restclient]] -===== Connecting to Elasticsearch using RestHighLevelClient -If you have `elasticsearch-rest-high-level-client` on the classpath, Spring Boot will auto-configure and register a `RestHighLevelClient` bean. -In addition to the properties described previously, to fine-tune the `RestHighLevelClient`, you can register an arbitrary number of beans that implement `RestClientBuilderCustomizer` for more advanced customizations. +===== Connecting to Elasticsearch using RestClient +If you have `elasticsearch-rest-client` on the classpath, Spring Boot will auto-configure and register a `RestClient` bean. +In addition to the properties described previously, to fine-tune the `RestClient`, you can register an arbitrary number of beans that implement `RestClientBuilderCustomizer` for more advanced customizations. To take full control over its registration, define a `RestClientBuilder` bean. -TIP: If your application needs access to a "Low Level" `RestClient`, you can get it by calling `client.getLowLevelClient()` on the auto-configured `RestHighLevelClient`. +NOTE: If you have `elasticsearch-rest-high-level-client` on the classpath a `RestHighLevelClient` bean will be exposed as well -Additionally, if `elasticsearch-rest-client-sniffer` is on the classpath, a `Sniffer` is auto-configured to automatically discover nodes from a running Elasticsearch cluster and set them on the `RestHighLevelClient` bean. +Additionally, if `elasticsearch-rest-client-sniffer` is on the classpath, a `Sniffer` is auto-configured to automatically discover nodes from a running Elasticsearch cluster and set them on the `RestClient` bean. You can further tune how `Sniffer` is configured, as shown in the following example: [source,yaml,indent=0,subs="verbatim",configprops,configblocks]