From eb3bf40bdb101e73dd6d575a7b56e141b0b92ccb Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 1 Nov 2021 13:30:00 +0100 Subject: [PATCH 1/2] Bring back Elasticsearch RestClient auto-configuration Prior to this commit, Spring Boot would only auto-configure the `RestHighLevelClient` and `RestClientBuilder` if the `RestHighLevelClient` was present. This was done in 1d73d4ed. This commit brings back the exposing of the `RestClient` bean in when exposing the `RestHighLevelClient` or when the `RestHighLevelClient` is not present. It allows for using the auto-configuration and its customizers of the `RestClientBuilder` in a similar way as it is done for the `RestTemplateBuilder` and the `WebClient.Builder`. The presence of the `elasticsearch-rest-high-level-client` module is now optional. This opens the door for potentially adding support for the new Elasticsearch Java Client[1] that is based on the same `RestClient`. The health contributor and its configuration has also been updated to only depend on the low-level RestClient. See gh-28496 [1] https://github.com/elastic/elasticsearch-java --- ...estHealthContributorAutoConfiguration.java | 9 +- ...chRestHealthContributorConfigurations.java | 69 +++++++++ ...althContributorAutoConfigurationTests.java | 1 + ...althContributorAutoConfigurationTests.java | 113 ++++++++++++++ ...lasticsearchRestClientHealthIndicator.java | 85 +++++++++++ .../ElasticsearchRestHealthIndicator.java | 55 +------ ...csearchRestClientHealthIndicatorTests.java | 143 ++++++++++++++++++ ...ElasticsearchRestHealthIndicatorTests.java | 1 + ...sticsearchRestClientAutoConfiguration.java | 3 + ...ElasticsearchRestClientConfigurations.java | 35 ++++- ...ientAutoConfigurationIntegrationTests.java | 49 +++++- ...earchRestClientAutoConfigurationTests.java | 135 ++++++++++++----- .../src/docs/asciidoc/data/nosql.adoc | 10 +- 13 files changed, 604 insertions(+), 104 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthContributorConfigurations.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchRestHealthContributorAutoConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestClientHealthIndicator.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestClientHealthIndicatorTests.java 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 51e2b1e83f5..7beb5a601b8 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 @@ -44,14 +44,13 @@ import org.springframework.context.annotation.Bean; @ConditionalOnClass(org.elasticsearch.client.RestHighLevelClient.class) @ConditionalOnBean(org.elasticsearch.client.RestHighLevelClient.class) @ConditionalOnEnabledHealthIndicator("elasticsearch") -public class ElasticSearchRestHealthContributorAutoConfiguration extends - CompositeHealthContributorConfiguration { +public class ElasticSearchRestHealthContributorAutoConfiguration + extends CompositeHealthContributorConfiguration { @Bean @ConditionalOnMissingBean(name = { "elasticsearchHealthIndicator", "elasticsearchHealthContributor" }) - public HealthContributor elasticsearchHealthContributor( - Map clients) { + public HealthContributor elasticsearchHealthContributor(Map clients) { return createContributor(clients); } -} +} \ No newline at end of file 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 00000000000..948a3c2ca3c --- /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.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(org.elasticsearch.client.RestHighLevelClient.class) + @ConditionalOnBean(org.elasticsearch.client.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(org.elasticsearch.client.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/ElasticsearchReactiveHealthContributorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchReactiveHealthContributorAutoConfigurationTests.java index c14fc96e4f6..0c81b6af650 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchReactiveHealthContributorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchReactiveHealthContributorAutoConfigurationTests.java @@ -50,6 +50,7 @@ class ElasticsearchReactiveHealthContributorAutoConfigurationTests { } @Test + @SuppressWarnings("deprecation") void runWithRegularIndicatorShouldOnlyCreateReactiveIndicator() { this.contextRunner .withConfiguration(AutoConfigurations.of(ElasticSearchRestHealthContributorAutoConfiguration.class)) 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 00000000000..0a3418329f2 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchRestHealthContributorAutoConfigurationTests.java @@ -0,0 +1,113 @@ +/* + * 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.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(org.elasticsearch.client.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) + @SuppressWarnings("deprecation") + static class CustomRestHighClientConfiguration { + + @Bean + org.elasticsearch.client.RestHighLevelClient customRestHighClient(RestClientBuilder builder) { + return new org.elasticsearch.client.RestHighLevelClient(builder); + } + + @Bean + RestClient customClient(org.elasticsearch.client.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 00000000000..da0821e1438 --- /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 468f235c3b1..596018ed168 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,22 +16,9 @@ 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}. @@ -40,14 +27,11 @@ import org.springframework.util.StreamUtils; * @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 { @SuppressWarnings("deprecation") public ElasticsearchRestHealthIndicator(org.elasticsearch.client.RestHighLevelClient client) { @@ -55,36 +39,7 @@ public class ElasticsearchRestHealthIndicator extends AbstractHealthIndicator { } 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 00000000000..80550160b05 --- /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 c283c049e2b..8312fb5dbbb 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 @@ import static org.mockito.Mockito.mock; * @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 d495db352f4..da462db9f24 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 @@ -22,7 +22,9 @@ import org.springframework.boot.autoconfigure.AutoConfiguration; 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.Import; @@ -40,6 +42,7 @@ import org.springframework.context.annotation.Import; @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 ceea098c85a..d8ed7df4173 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 @@ -36,6 +36,7 @@ import org.elasticsearch.client.sniff.SnifferBuilder; 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; @@ -46,6 +47,7 @@ import org.springframework.util.StringUtils; * Elasticsearch rest client configurations. * * @author Stephane Nicoll + * @author Filip Hrisafov */ class ElasticsearchRestClientConfigurations { @@ -126,16 +128,41 @@ class ElasticsearchRestClientConfigurations { @SuppressWarnings("deprecation") @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(Sniffer.class) + @ConditionalOnClass(org.elasticsearch.client.RestHighLevelClient.class) @ConditionalOnSingleCandidate(org.elasticsearch.client.RestHighLevelClient.class) + @ConditionalOnMissingBean(RestClient.class) + static class RestClientWithRestHighLevelClientConfiguration { + + @Bean + RestClient elasticsearchRestClient(org.elasticsearch.client.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 - Sniffer elasticsearchSniffer(org.elasticsearch.client.RestHighLevelClient client, - ElasticsearchRestClientProperties properties, + @SuppressWarnings("deprecation") + 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 24be99bfdc6..eb93f9979a2 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,19 +16,26 @@ 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.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.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.testsupport.testcontainers.DockerImageNames; @@ -40,6 +47,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Brian Clozel * @author Vedran Pavic * @author Evgeniy Cheban + * @author Filip Hrisafov */ @Testcontainers(disabledWithoutDocker = true) class ElasticsearchRestClientAutoConfigurationIntegrationTests { @@ -53,7 +61,7 @@ class ElasticsearchRestClientAutoConfigurationIntegrationTests { @Test @SuppressWarnings("deprecation") - void restClientCanQueryElasticsearchNode() { + void restHighLevelClientCanQueryElasticsearchNode() { this.contextRunner .withPropertyValues("spring.elasticsearch.uris=" + elasticsearch.getHttpHostAddress(), "spring.elasticsearch.connection-timeout=120s", "spring.elasticsearch.socket-timeout=120s") @@ -70,4 +78,43 @@ class ElasticsearchRestClientAutoConfigurationIntegrationTests { }); } + @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 + @SuppressWarnings("deprecation") + void restClientCanQueryElasticsearchNodeWithoutHighLevelClient() { + this.contextRunner.withClassLoader(new FilteredClassLoader(org.elasticsearch.client.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 d1f3049ebc4..6a34c51d50c 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,17 +63,20 @@ class ElasticsearchRestClientAutoConfigurationTests { .withConfiguration(AutoConfigurations.of(ElasticsearchRestClientAutoConfiguration.class)); @Test - void configureShouldOnlyCreateHighLevelRestClient() { - this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(RestClient.class) - .hasSingleBean(RestClientBuilder.class) - .hasSingleBean(org.elasticsearch.client.RestHighLevelClient.class)); - + void configureShouldCreateHighLevelAndLowLevelRestClient() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(RestClient.class) + .hasSingleBean(org.elasticsearch.client.RestHighLevelClient.class) + .hasSingleBean(RestClientBuilder.class); + assertThat(context.getBean(RestClient.class)) + .isEqualTo(context.getBean(org.elasticsearch.client.RestHighLevelClient.class).getLowLevelClient()); + }); } @Test - void configureWithoutRestHighLevelClientShouldOnlyCreateRestClientBuilder() { + void configureWithoutRestHighLevelClientShouldOnlyCreateRestClientBuilderAndRestClient() { this.contextRunner.withClassLoader(new FilteredClassLoader(org.elasticsearch.client.RestHighLevelClient.class)) - .run((context) -> assertThat(context).doesNotHaveBean(RestClient.class) + .run((context) -> assertThat(context).hasSingleBean(RestClient.class) .doesNotHaveBean(org.elasticsearch.client.RestHighLevelClient.class) .hasSingleBean(RestClientBuilder.class)); } @@ -87,25 +90,52 @@ class ElasticsearchRestClientAutoConfigurationTests { .hasBean("customRestClient")); } + @Test + void configureWhenCustomRestHighLevelClientIsNotPresent() { + this.contextRunner.withClassLoader(new FilteredClassLoader(org.elasticsearch.client.RestHighLevelClient.class)) + .run((context) -> assertThat(context) + .doesNotHaveBean(org.elasticsearch.client.RestHighLevelClient.class) + .hasSingleBean(RestClient.class).hasSingleBean(RestClientBuilder.class)); + } + @Test void configureWhenCustomRestHighLevelClientShouldBackOff() { - this.contextRunner.withUserConfiguration(CustomRestHighLevelClientConfiguration.class).run( - (context) -> assertThat(context).hasSingleBean(org.elasticsearch.client.RestHighLevelClient.class)); + this.contextRunner.withUserConfiguration(CustomRestHighLevelClientConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(org.elasticsearch.client.RestHighLevelClient.class) + .hasSingleBean(RestClient.class).hasBean("elasticsearchRestClient")); + } + + @Test + void configureWhenCustomRestHighLevelClientAndRestClientWithRestHighLevelClientShouldBackOff() { + this.contextRunner.withUserConfiguration(CustomRestHighLevelClientWithRestClientConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(org.elasticsearch.client.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(org.elasticsearch.client.RestHighLevelClient.class); assertThat(restHighLevelClients).hasSize(2); }); } + @Test + void configureWhenDefaultRestClientShouldCreateWhenNoUniqueRestClient() { + this.contextRunner.withClassLoader(new FilteredClassLoader(org.elasticsearch.client.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(org.elasticsearch.client.RestHighLevelClient.class); + assertThat(context).hasSingleBean(org.elasticsearch.client.RestHighLevelClient.class) + .hasSingleBean(RestClient.class); org.elasticsearch.client.RestHighLevelClient restClient = context .getBean(org.elasticsearch.client.RestHighLevelClient.class); RestClient lowLevelClient = restClient.getLowLevelClient(); @@ -118,9 +148,8 @@ class ElasticsearchRestClientAutoConfigurationTests { @Test void configureWithNoTimeoutsApplyDefaults() { this.contextRunner.run((context) -> { - assertThat(context).hasSingleBean(org.elasticsearch.client.RestHighLevelClient.class); - org.elasticsearch.client.RestHighLevelClient restClient = context - .getBean(org.elasticsearch.client.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)); }); @@ -130,9 +159,8 @@ class ElasticsearchRestClientAutoConfigurationTests { void configureWithLegacyCustomTimeouts() { this.contextRunner.withPropertyValues("spring.elasticsearch.rest.connection-timeout=15s", "spring.elasticsearch.rest.read-timeout=1m").run((context) -> { - assertThat(context).hasSingleBean(org.elasticsearch.client.RestHighLevelClient.class); - org.elasticsearch.client.RestHighLevelClient restClient = context - .getBean(org.elasticsearch.client.RestHighLevelClient.class); + assertThat(context).hasSingleBean(RestClient.class); + RestClient restClient = context.getBean(RestClient.class); assertTimeouts(restClient, Duration.ofSeconds(15), Duration.ofMinutes(1)); }); } @@ -141,25 +169,23 @@ class ElasticsearchRestClientAutoConfigurationTests { void configureWithCustomTimeouts() { this.contextRunner.withPropertyValues("spring.elasticsearch.connection-timeout=15s", "spring.elasticsearch.socket-timeout=1m").run((context) -> { - assertThat(context).hasSingleBean(org.elasticsearch.client.RestHighLevelClient.class); - org.elasticsearch.client.RestHighLevelClient restClient = context - .getBean(org.elasticsearch.client.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(org.elasticsearch.client.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(org.elasticsearch.client.RestHighLevelClient.class).getLowLevelClient(); + RestClient client = context.getBean(RestClient.class); assertThat(client.getNodes().stream().map(Node::getHost).map(HttpHost::toString)) .containsExactly("http://localhost:9876"); }); @@ -168,7 +194,7 @@ class ElasticsearchRestClientAutoConfigurationTests { @ParameterizedPropertyPrefixTest void configureUriWithUsernameOnly(String prefix) { this.contextRunner.withPropertyValues(prefix + "uris=http://user@localhost:9200").run((context) -> { - RestClient client = context.getBean(org.elasticsearch.client.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) @@ -184,7 +210,7 @@ class ElasticsearchRestClientAutoConfigurationTests { @ParameterizedPropertyPrefixTest void configureUriWithUsernameAndEmptyPassword(String prefix) { this.contextRunner.withPropertyValues(prefix + "uris=http://user:@localhost:9200").run((context) -> { - RestClient client = context.getBean(org.elasticsearch.client.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) @@ -201,8 +227,7 @@ class ElasticsearchRestClientAutoConfigurationTests { 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(org.elasticsearch.client.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) @@ -224,7 +249,7 @@ class ElasticsearchRestClientAutoConfigurationTests { @Test void configureWithCustomPathPrefix() { this.contextRunner.withPropertyValues("spring.elasticsearch.path-prefix=/some/prefix").run((context) -> { - RestClient client = context.getBean(org.elasticsearch.client.RestHighLevelClient.class).getLowLevelClient(); + RestClient client = context.getBean(RestClient.class); assertThat(client).extracting("pathPrefix").isEqualTo("/some/prefix"); }); } @@ -233,19 +258,21 @@ class ElasticsearchRestClientAutoConfigurationTests { void configureWithoutSnifferLibraryShouldNotCreateSniffer() { this.contextRunner.withClassLoader(new FilteredClassLoader("org.elasticsearch.client.sniff")) .run((context) -> assertThat(context).hasSingleBean(org.elasticsearch.client.RestHighLevelClient.class) - .doesNotHaveBean(Sniffer.class)); + .hasSingleBean(RestClient.class).doesNotHaveBean(Sniffer.class)); } @Test - void configureShouldCreateSnifferUsingRestHighLevelClient() { - this.contextRunner.run((context) -> { - assertThat(context).hasSingleBean(Sniffer.class); - assertThat(context.getBean(Sniffer.class)).hasFieldOrPropertyWithValue("restClient", - context.getBean(org.elasticsearch.client.RestHighLevelClient.class).getLowLevelClient()); - // Validate shutdown order as the sniffer must be shutdown before the client - assertThat(context.getBeanFactory().getDependentBeans("elasticsearchRestHighLevelClient")) - .contains("elasticsearchSniffer"); - }); + void configureShouldCreateSnifferUsingRestClient() { + this.contextRunner.withClassLoader(new FilteredClassLoader(org.elasticsearch.client.RestHighLevelClient.class)) + .run((context) -> { + assertThat(context).hasSingleBean(Sniffer.class); + assertThat(context.getBean(Sniffer.class)).hasFieldOrPropertyWithValue("restClient", + context.getBean(RestClient.class)); + // Validate shutdown order as the sniffer must be shutdown before the + // client + assertThat(context.getBeanFactory().getDependentBeans("elasticsearchRestClient")) + .contains("elasticsearchSniffer"); + }); } @ParameterizedSnifferPropertyPrefixTest @@ -309,6 +336,21 @@ class ElasticsearchRestClientAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class CustomRestHighLevelClientWithRestClientConfiguration { + + @Bean + org.elasticsearch.client.RestHighLevelClient customRestHighLevelClient(RestClientBuilder builder) { + return new org.elasticsearch.client.RestHighLevelClient(builder); + } + + @Bean + RestClient customRestClient(org.elasticsearch.client.RestHighLevelClient restHighLevelClient) { + return restHighLevelClient.getLowLevelClient(); + } + + } + @Configuration(proxyBeanMethods = false) static class TwoCustomRestHighLevelClientConfiguration { @@ -334,6 +376,21 @@ class ElasticsearchRestClientAutoConfigurationTests { } + @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 56823d5325d..9cc927f2bb9 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 @@ -269,14 +269,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] From a7a71da9ef159e375ff0faa344e888d435a7052c Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 12 Apr 2022 19:31:23 +0100 Subject: [PATCH 2/2] Polish "Bring back Elasticsearch RestClient auto-configuration" See gh-28496 --- ...estHealthContributorAutoConfiguration.java | 7 +- ...chRestHealthContributorConfigurations.java | 69 --------- ...lthContributorAutoConfigurationTests.java} | 22 ++- ...althContributorAutoConfigurationTests.java | 1 - ...lasticsearchRestClientHealthIndicator.java | 85 ----------- .../ElasticsearchRestHealthIndicator.java | 55 ++++++- ...csearchRestClientHealthIndicatorTests.java | 143 ------------------ ...ElasticsearchRestHealthIndicatorTests.java | 1 - ...sticsearchRestClientAutoConfiguration.java | 4 +- ...ElasticsearchRestClientConfigurations.java | 2 +- ...ientAutoConfigurationIntegrationTests.java | 21 --- ...earchRestClientAutoConfigurationTests.java | 50 ++---- .../src/docs/asciidoc/data/nosql.adoc | 12 +- 13 files changed, 88 insertions(+), 384 deletions(-) delete mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthContributorConfigurations.java rename spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/{ElasticsearchRestHealthContributorAutoConfigurationTests.java => ElasticSearchRestHealthContributorAutoConfigurationTests.java} (87%) delete mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestClientHealthIndicator.java delete mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestClientHealthIndicatorTests.java 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 7beb5a601b8..095c8e5f3e5 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 @@ -39,10 +39,9 @@ import org.springframework.context.annotation.Bean; * @author Artsiom Yudovin * @since 2.1.1 */ -@SuppressWarnings("deprecation") @AutoConfiguration(after = ElasticsearchRestClientAutoConfiguration.class) -@ConditionalOnClass(org.elasticsearch.client.RestHighLevelClient.class) -@ConditionalOnBean(org.elasticsearch.client.RestHighLevelClient.class) +@ConditionalOnClass(RestClient.class) +@ConditionalOnBean(RestClient.class) @ConditionalOnEnabledHealthIndicator("elasticsearch") public class ElasticSearchRestHealthContributorAutoConfiguration extends CompositeHealthContributorConfiguration { @@ -53,4 +52,4 @@ public class ElasticSearchRestHealthContributorAutoConfiguration return createContributor(clients); } -} \ No newline at end of file +} 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 deleted file mode 100644 index 948a3c2ca3c..00000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthContributorConfigurations.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.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(org.elasticsearch.client.RestHighLevelClient.class) - @ConditionalOnBean(org.elasticsearch.client.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(org.elasticsearch.client.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 similarity index 87% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchRestHealthContributorAutoConfigurationTests.java rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthContributorAutoConfigurationTests.java index 0a3418329f2..56972fc4ba4 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * 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. @@ -21,7 +21,6 @@ import org.elasticsearch.client.RestClientBuilder; 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; @@ -33,11 +32,12 @@ import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link ElasticsearchRestClientAutoConfiguration}. + * Tests for {@link ElasticSearchRestHealthContributorAutoConfiguration}. * * @author Filip Hrisafov + * @author Andy Wilkinson */ -class ElasticsearchRestHealthContributorAutoConfigurationTests { +class ElasticSearchRestHealthContributorAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(ElasticsearchRestClientAutoConfiguration.class, @@ -45,42 +45,40 @@ class ElasticsearchRestHealthContributorAutoConfigurationTests { HealthContributorAutoConfiguration.class)); @Test - @SuppressWarnings("deprecation") void runShouldCreateIndicator() { this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ElasticsearchRestHealthIndicator.class) .hasBean("elasticsearchHealthContributor")); } @Test + @SuppressWarnings("deprecation") void runWithoutRestHighLevelClientAndWithoutRestClientShouldNotCreateIndicator() { this.contextRunner .withClassLoader( new FilteredClassLoader(org.elasticsearch.client.RestHighLevelClient.class, RestClient.class)) - .run((context) -> assertThat(context).doesNotHaveBean(ElasticsearchRestClientHealthIndicator.class) + .run((context) -> assertThat(context).doesNotHaveBean(ElasticsearchRestHealthIndicator.class) .doesNotHaveBean("elasticsearchHealthContributor")); } @Test - @SuppressWarnings("deprecation") void runWithoutRestHighLevelClientAndWithRestClientShouldCreateIndicator() { this.contextRunner.withUserConfiguration(CustomRestClientConfiguration.class) - .run((context) -> assertThat(context).hasSingleBean(ElasticsearchRestClientHealthIndicator.class) - .doesNotHaveBean(ElasticsearchRestHealthIndicator.class) + .run((context) -> assertThat(context).hasSingleBean(ElasticsearchRestHealthIndicator.class) + .hasSingleBean(ElasticsearchRestHealthIndicator.class) .hasBean("elasticsearchHealthContributor")); } @Test - @SuppressWarnings("deprecation") void runWithRestHighLevelClientAndWithRestClientShouldCreateIndicator() { this.contextRunner.withUserConfiguration(CustomRestHighClientConfiguration.class) - .run((context) -> assertThat(context).hasSingleBean(ElasticsearchRestClientHealthIndicator.class) + .run((context) -> assertThat(context).hasSingleBean(ElasticsearchRestHealthIndicator.class) .hasBean("elasticsearchHealthContributor")); } @Test void runWhenDisabledShouldNotCreateIndicator() { this.contextRunner.withPropertyValues("management.health.elasticsearch.enabled:false") - .run((context) -> assertThat(context).doesNotHaveBean(ElasticsearchRestClientHealthIndicator.class) + .run((context) -> assertThat(context).doesNotHaveBean(ElasticsearchRestHealthIndicator.class) .doesNotHaveBean("elasticsearchHealthContributor")); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchReactiveHealthContributorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchReactiveHealthContributorAutoConfigurationTests.java index 0c81b6af650..c14fc96e4f6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchReactiveHealthContributorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchReactiveHealthContributorAutoConfigurationTests.java @@ -50,7 +50,6 @@ class ElasticsearchReactiveHealthContributorAutoConfigurationTests { } @Test - @SuppressWarnings("deprecation") void runWithRegularIndicatorShouldOnlyCreateReactiveIndicator() { this.contextRunner .withConfiguration(AutoConfigurations.of(ElasticSearchRestHealthContributorAutoConfiguration.class)) 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 deleted file mode 100644 index da0821e1438..00000000000 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestClientHealthIndicator.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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 596018ed168..468f235c3b1 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,9 +16,22 @@ 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}. @@ -27,11 +40,14 @@ import org.springframework.boot.actuate.health.HealthIndicator; * @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} */ -@Deprecated -public class ElasticsearchRestHealthIndicator extends ElasticsearchRestClientHealthIndicator { +public class ElasticsearchRestHealthIndicator extends AbstractHealthIndicator { + + private static final String RED_STATUS = "red"; + + private final RestClient client; + + private final JsonParser jsonParser; @SuppressWarnings("deprecation") public ElasticsearchRestHealthIndicator(org.elasticsearch.client.RestHighLevelClient client) { @@ -39,7 +55,36 @@ public class ElasticsearchRestHealthIndicator extends ElasticsearchRestClientHea } public ElasticsearchRestHealthIndicator(RestClient client) { - super(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/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 deleted file mode 100644 index 80550160b05..00000000000 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestClientHealthIndicatorTests.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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 8312fb5dbbb..c283c049e2b 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,7 +42,6 @@ import static org.mockito.Mockito.mock; * @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 da462db9f24..db67b1f0d38 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 @@ -23,8 +23,8 @@ 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.RestClientFromRestHighLevelClientConfiguration; 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.Import; @@ -42,7 +42,7 @@ import org.springframework.context.annotation.Import; @EnableConfigurationProperties({ ElasticsearchProperties.class, ElasticsearchRestClientProperties.class, DeprecatedElasticsearchRestClientProperties.class }) @Import({ RestClientBuilderConfiguration.class, RestHighLevelClientConfiguration.class, - RestClientWithRestHighLevelClientConfiguration.class, RestClientConfiguration.class, + RestClientFromRestHighLevelClientConfiguration.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 d8ed7df4173..8e50a3da5ff 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 @@ -131,7 +131,7 @@ class ElasticsearchRestClientConfigurations { @ConditionalOnClass(org.elasticsearch.client.RestHighLevelClient.class) @ConditionalOnSingleCandidate(org.elasticsearch.client.RestHighLevelClient.class) @ConditionalOnMissingBean(RestClient.class) - static class RestClientWithRestHighLevelClientConfiguration { + static class RestClientFromRestHighLevelClientConfiguration { @Bean RestClient elasticsearchRestClient(org.elasticsearch.client.RestHighLevelClient restHighLevelClient) { 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 eb93f9979a2..e979c4d1d82 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 @@ -35,7 +35,6 @@ import org.testcontainers.junit.jupiter.Container; 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; @@ -97,24 +96,4 @@ class ElasticsearchRestClientAutoConfigurationIntegrationTests { }); } - @Test - @SuppressWarnings("deprecation") - void restClientCanQueryElasticsearchNodeWithoutHighLevelClient() { - this.contextRunner.withClassLoader(new FilteredClassLoader(org.elasticsearch.client.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 6a34c51d50c..11a8ee9f58e 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 @@ -21,7 +21,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.time.Duration; -import java.util.Map; import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; @@ -55,6 +54,7 @@ import static org.mockito.Mockito.mock; * @author Vedran Pavic * @author Evgeniy Cheban * @author Filip Hrisafov + * @author Andy Wilkinson */ @SuppressWarnings("deprecation") class ElasticsearchRestClientAutoConfigurationTests { @@ -63,7 +63,7 @@ class ElasticsearchRestClientAutoConfigurationTests { .withConfiguration(AutoConfigurations.of(ElasticsearchRestClientAutoConfiguration.class)); @Test - void configureShouldCreateHighLevelAndLowLevelRestClient() { + void configureShouldCreateHighLevelAndLowLevelRestClients() { this.contextRunner.run((context) -> { assertThat(context).hasSingleBean(RestClient.class) .hasSingleBean(org.elasticsearch.client.RestHighLevelClient.class) @@ -77,8 +77,8 @@ class ElasticsearchRestClientAutoConfigurationTests { void configureWithoutRestHighLevelClientShouldOnlyCreateRestClientBuilderAndRestClient() { this.contextRunner.withClassLoader(new FilteredClassLoader(org.elasticsearch.client.RestHighLevelClient.class)) .run((context) -> assertThat(context).hasSingleBean(RestClient.class) - .doesNotHaveBean(org.elasticsearch.client.RestHighLevelClient.class) - .hasSingleBean(RestClientBuilder.class)); + .hasSingleBean(RestClientBuilder.class) + .doesNotHaveBean(org.elasticsearch.client.RestHighLevelClient.class)); } @Test @@ -91,44 +91,26 @@ class ElasticsearchRestClientAutoConfigurationTests { } @Test - void configureWhenCustomRestHighLevelClientIsNotPresent() { - this.contextRunner.withClassLoader(new FilteredClassLoader(org.elasticsearch.client.RestHighLevelClient.class)) - .run((context) -> assertThat(context) - .doesNotHaveBean(org.elasticsearch.client.RestHighLevelClient.class) - .hasSingleBean(RestClient.class).hasSingleBean(RestClientBuilder.class)); - } - - @Test - void configureWhenCustomRestHighLevelClientShouldBackOff() { + void configureWhenCustomRestHighLevelClientShouldDefineRestClientFromCustomHighLevelClient() { this.contextRunner.withUserConfiguration(CustomRestHighLevelClientConfiguration.class) .run((context) -> assertThat(context).hasSingleBean(org.elasticsearch.client.RestHighLevelClient.class) - .hasSingleBean(RestClient.class).hasBean("elasticsearchRestClient")); + .hasSingleBean(RestClient.class).hasBean("elasticsearchRestClient").getBean(RestClient.class) + .isEqualTo(context.getBean(org.elasticsearch.client.RestHighLevelClient.class) + .getLowLevelClient())); } @Test - void configureWhenCustomRestHighLevelClientAndRestClientWithRestHighLevelClientShouldBackOff() { + void configureWhenCustomRestHighLevelClientAndRestClientShouldBackOff() { this.contextRunner.withUserConfiguration(CustomRestHighLevelClientWithRestClientConfiguration.class) .run((context) -> assertThat(context).hasSingleBean(org.elasticsearch.client.RestHighLevelClient.class) - .hasSingleBean(RestClient.class).hasBean("customRestClient")); + .hasBean("customRestHighLevelClient").hasSingleBean(RestClient.class) + .hasBean("customRestClient")); } @Test - void configureWhenDefaultRestClientShouldCreateWhenNoUniqueRestHighLevelClient() { - this.contextRunner.withUserConfiguration(TwoCustomRestHighLevelClientConfiguration.class).run((context) -> { - assertThat(context).doesNotHaveBean(RestClient.class); - Map restHighLevelClients = context - .getBeansOfType(org.elasticsearch.client.RestHighLevelClient.class); - assertThat(restHighLevelClients).hasSize(2); - }); - } - - @Test - void configureWhenDefaultRestClientShouldCreateWhenNoUniqueRestClient() { - this.contextRunner.withClassLoader(new FilteredClassLoader(org.elasticsearch.client.RestHighLevelClient.class)) - .withUserConfiguration(TwoCustomRestClientConfiguration.class).run((context) -> { - Map restClients = context.getBeansOfType(RestClient.class); - assertThat(restClients).hasSize(2); - }); + void configureWhenNoUniqueRestHighLevelClientShouldNotDefineRestClient() { + this.contextRunner.withUserConfiguration(TwoCustomRestHighLevelClientsConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(RestClient.class)); } @Test @@ -352,7 +334,7 @@ class ElasticsearchRestClientAutoConfigurationTests { } @Configuration(proxyBeanMethods = false) - static class TwoCustomRestHighLevelClientConfiguration { + static class TwoCustomRestHighLevelClientsConfiguration { @Bean org.elasticsearch.client.RestHighLevelClient customRestHighLevelClient(RestClientBuilder builder) { @@ -360,7 +342,7 @@ class ElasticsearchRestClientAutoConfigurationTests { } @Bean - org.elasticsearch.client.RestHighLevelClient customoRestHighLevelClient1(RestClientBuilder builder) { + org.elasticsearch.client.RestHighLevelClient anotherCustomRestHighLevelClient(RestClientBuilder builder) { return new org.elasticsearch.client.RestHighLevelClient(builder); } 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 9cc927f2bb9..122a91afe9b 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 @@ -252,9 +252,8 @@ Spring Boot provides a dedicated "`Starter`", `spring-boot-starter-data-elastics [[data.nosql.elasticsearch.connecting-using-rest]] ==== Connecting to Elasticsearch using REST clients -Elasticsearch ships https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/index.html[two different REST clients] that you can use to query a cluster: the "Low Level" client and the "High Level" client. -Spring Boot provides support for the "High Level" client, which ships with `org.elasticsearch.client:elasticsearch-rest-high-level-client`. -Additionally, Spring Boot provides support for a reactive client, based on Spring Framework's `WebClient`, that ships with `org.springframework.data:spring-data-elasticsearch`. +Elasticsearch ships https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/index.html[two different REST clients] that you can use to query a cluster: the low-level client from the `org.elasticsearch.client:elasticsearch-rest-client` module and the high-level client from the `org.elasticsearch.client:elasticsearch-high-level-client` module. +Additionally, Spring Boot provides support for a reactive client, based on Spring Framework's `WebClient`, from the `org.springframework.data:spring-data-elasticsearch` module. By default, the clients will target `http://localhost:9200`. You can use `spring.elasticsearch.*` properties to further tune how the clients are configured, as shown in the following example: @@ -271,10 +270,11 @@ You can use `spring.elasticsearch.*` properties to further tune how the clients [[data.nosql.elasticsearch.connecting-using-rest.restclient]] ===== 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. +If you have `elasticsearch-rest-high-level-client` on the classpath a `RestHighLevelClient` bean will be registered as well. +In addition to the properties described previously, to fine-tune the `RestClient` and `RestHighLevelClient`, you can register an arbitrary number of beans that implement `RestClientBuilderCustomizer` for more advanced customizations. +To take full control over the clients' configuration, define a `RestClientBuilder` bean. + -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 `RestClient` bean. You can further tune how `Sniffer` is configured, as shown in the following example: