Skip to content

Commit d6e5a58

Browse files
committed
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 1d73d4e. This commit brings back the exposing of the `RestClient` bean in Spring Boot when exposing the `RestHighLevelClient` or when the `RestHighLevelClient` is not present. It allows for using the Spring Boot auto configuration and its customizers of the `RestClientBuilder` in a similar way as it is done for the `RestTeamplateBuilder` and the `WebClient.Builder`. Now the presence of the `org.elasticsearch.client:elasticsearch-rest-high-level-client` is optional. This opens the door for potentially adding support to the new [Elasticsearch Java Client](https://github.com/elastic/elasticsearch-java) that is based on the same `RestClient` It also adapts the health contributor to only depend on the low level RestClient.
1 parent 980aa61 commit d6e5a58

File tree

12 files changed

+593
-106
lines changed

12 files changed

+593
-106
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthContributorAutoConfiguration.java

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,43 +16,31 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.elasticsearch;
1818

19-
import java.util.Map;
20-
2119
import org.elasticsearch.client.RestClient;
22-
import org.elasticsearch.client.RestHighLevelClient;
2320

24-
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration;
21+
import org.springframework.boot.actuate.autoconfigure.elasticsearch.ElasticSearchRestHealthContributorConfigurations.RestClientHealthContributorConfiguration;
2522
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
26-
import org.springframework.boot.actuate.elasticsearch.ElasticsearchRestHealthIndicator;
27-
import org.springframework.boot.actuate.health.HealthContributor;
2823
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
2924
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
30-
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
3125
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
32-
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3326
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration;
34-
import org.springframework.context.annotation.Bean;
3527
import org.springframework.context.annotation.Configuration;
28+
import org.springframework.context.annotation.Import;
3629

3730
/**
38-
* {@link EnableAutoConfiguration Auto-configuration} for
39-
* {@link ElasticsearchRestHealthIndicator} using the {@link RestClient}.
31+
* {@link EnableAutoConfiguration Auto-configuration} for Elasticsearch health indicator
32+
* using REST clients.
4033
*
4134
* @author Artsiom Yudovin
4235
* @since 2.1.1
4336
*/
37+
@SuppressWarnings("deprecation")
4438
@Configuration(proxyBeanMethods = false)
45-
@ConditionalOnClass(RestHighLevelClient.class)
46-
@ConditionalOnBean(RestHighLevelClient.class)
39+
@ConditionalOnClass(RestClient.class)
4740
@ConditionalOnEnabledHealthIndicator("elasticsearch")
4841
@AutoConfigureAfter(ElasticsearchRestClientAutoConfiguration.class)
49-
public class ElasticSearchRestHealthContributorAutoConfiguration
50-
extends CompositeHealthContributorConfiguration<ElasticsearchRestHealthIndicator, RestHighLevelClient> {
51-
52-
@Bean
53-
@ConditionalOnMissingBean(name = { "elasticsearchHealthIndicator", "elasticsearchHealthContributor" })
54-
public HealthContributor elasticsearchHealthContributor(Map<String, RestHighLevelClient> clients) {
55-
return createContributor(clients);
56-
}
42+
@Import({ ElasticSearchRestHealthContributorConfigurations.RestHighLevelClientHealthContributorConfiguration.class,
43+
RestClientHealthContributorConfiguration.class })
44+
public class ElasticSearchRestHealthContributorAutoConfiguration {
5745

5846
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2012-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.elasticsearch;
18+
19+
import java.util.Map;
20+
21+
import org.elasticsearch.client.RestClient;
22+
import org.elasticsearch.client.RestHighLevelClient;
23+
24+
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration;
25+
import org.springframework.boot.actuate.elasticsearch.ElasticsearchRestClientHealthIndicator;
26+
import org.springframework.boot.actuate.health.HealthContributor;
27+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
28+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
29+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
30+
import org.springframework.context.annotation.Bean;
31+
import org.springframework.context.annotation.Configuration;
32+
33+
/**
34+
* Elasticsearch rest client health contributor configurations.
35+
*
36+
* @author Filip Hrisafov
37+
*/
38+
class ElasticSearchRestHealthContributorConfigurations {
39+
40+
@Configuration(proxyBeanMethods = false)
41+
@ConditionalOnClass(RestHighLevelClient.class)
42+
@ConditionalOnBean(RestHighLevelClient.class)
43+
@Deprecated
44+
static class RestHighLevelClientHealthContributorConfiguration extends
45+
CompositeHealthContributorConfiguration<org.springframework.boot.actuate.elasticsearch.ElasticsearchRestHealthIndicator, RestHighLevelClient> {
46+
47+
@Bean
48+
@ConditionalOnMissingBean(name = { "elasticsearchHealthIndicator", "elasticsearchHealthContributor" })
49+
HealthContributor elasticsearchHealthContributor(Map<String, RestHighLevelClient> clients) {
50+
return createContributor(clients);
51+
}
52+
53+
}
54+
55+
@Configuration(proxyBeanMethods = false)
56+
@ConditionalOnBean(RestClient.class)
57+
@ConditionalOnMissingBean(RestHighLevelClient.class)
58+
static class RestClientHealthContributorConfiguration
59+
extends CompositeHealthContributorConfiguration<ElasticsearchRestClientHealthIndicator, RestClient> {
60+
61+
@Bean
62+
@ConditionalOnMissingBean(name = { "elasticsearchHealthIndicator", "elasticsearchHealthContributor" })
63+
HealthContributor elasticsearchHealthContributor(Map<String, RestClient> clients) {
64+
return createContributor(clients);
65+
}
66+
67+
}
68+
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.elasticsearch;
18+
19+
import org.elasticsearch.client.RestClient;
20+
import org.elasticsearch.client.RestClientBuilder;
21+
import org.elasticsearch.client.RestHighLevelClient;
22+
import org.junit.jupiter.api.Test;
23+
24+
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
25+
import org.springframework.boot.actuate.elasticsearch.ElasticsearchRestClientHealthIndicator;
26+
import org.springframework.boot.actuate.elasticsearch.ElasticsearchRestHealthIndicator;
27+
import org.springframework.boot.autoconfigure.AutoConfigurations;
28+
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration;
29+
import org.springframework.boot.test.context.FilteredClassLoader;
30+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
31+
import org.springframework.context.annotation.Bean;
32+
import org.springframework.context.annotation.Configuration;
33+
34+
import static org.assertj.core.api.Assertions.assertThat;
35+
36+
/**
37+
* Tests for {@link ElasticsearchRestClientAutoConfiguration}.
38+
*
39+
* @author Filip Hrisafov
40+
*/
41+
class ElasticsearchRestHealthContributorAutoConfigurationTests {
42+
43+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
44+
.withConfiguration(AutoConfigurations.of(ElasticsearchRestClientAutoConfiguration.class,
45+
ElasticSearchRestHealthContributorAutoConfiguration.class,
46+
HealthContributorAutoConfiguration.class));
47+
48+
@Test
49+
@SuppressWarnings("deprecation")
50+
void runShouldCreateIndicator() {
51+
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ElasticsearchRestHealthIndicator.class)
52+
.hasBean("elasticsearchHealthContributor"));
53+
}
54+
55+
@Test
56+
void runWithoutRestHighLevelClientAndWithoutRestClientShouldNotCreateIndicator() {
57+
this.contextRunner.withClassLoader(new FilteredClassLoader(RestHighLevelClient.class, RestClient.class))
58+
.run((context) -> assertThat(context).doesNotHaveBean(ElasticsearchRestClientHealthIndicator.class)
59+
.doesNotHaveBean("elasticsearchHealthContributor"));
60+
}
61+
62+
@Test
63+
@SuppressWarnings("deprecation")
64+
void runWithoutRestHighLevelClientAndWithRestClientShouldCreateIndicator() {
65+
this.contextRunner.withUserConfiguration(CustomRestClientConfiguration.class)
66+
.run((context) -> assertThat(context).hasSingleBean(ElasticsearchRestClientHealthIndicator.class)
67+
.doesNotHaveBean(ElasticsearchRestHealthIndicator.class)
68+
.hasBean("elasticsearchHealthContributor"));
69+
}
70+
71+
@Test
72+
@SuppressWarnings("deprecation")
73+
void runWithRestHighLevelClientAndWithRestClientShouldCreateIndicator() {
74+
this.contextRunner.withUserConfiguration(CustomRestHighClientConfiguration.class)
75+
.run((context) -> assertThat(context).hasSingleBean(ElasticsearchRestClientHealthIndicator.class)
76+
.hasBean("elasticsearchHealthContributor"));
77+
}
78+
79+
@Test
80+
void runWhenDisabledShouldNotCreateIndicator() {
81+
this.contextRunner.withPropertyValues("management.health.elasticsearch.enabled:false")
82+
.run((context) -> assertThat(context).doesNotHaveBean(ElasticsearchRestClientHealthIndicator.class)
83+
.doesNotHaveBean("elasticsearchHealthContributor"));
84+
}
85+
86+
@Configuration(proxyBeanMethods = false)
87+
static class CustomRestClientConfiguration {
88+
89+
@Bean
90+
RestClient customRestClient(RestClientBuilder builder) {
91+
return builder.build();
92+
}
93+
94+
}
95+
96+
@Configuration(proxyBeanMethods = false)
97+
static class CustomRestHighClientConfiguration {
98+
99+
@Bean
100+
RestHighLevelClient customRestHighClient(RestClientBuilder builder) {
101+
return new RestHighLevelClient(builder);
102+
}
103+
104+
@Bean
105+
RestClient customClient(RestHighLevelClient restHighLevelClient) {
106+
return restHighLevelClient.getLowLevelClient();
107+
}
108+
109+
}
110+
111+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.elasticsearch;
18+
19+
import java.io.InputStream;
20+
import java.nio.charset.StandardCharsets;
21+
import java.util.Map;
22+
23+
import org.apache.http.HttpStatus;
24+
import org.apache.http.StatusLine;
25+
import org.elasticsearch.client.Request;
26+
import org.elasticsearch.client.Response;
27+
import org.elasticsearch.client.RestClient;
28+
29+
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
30+
import org.springframework.boot.actuate.health.Health;
31+
import org.springframework.boot.actuate.health.HealthIndicator;
32+
import org.springframework.boot.json.JsonParser;
33+
import org.springframework.boot.json.JsonParserFactory;
34+
import org.springframework.util.StreamUtils;
35+
36+
/**
37+
* {@link HealthIndicator} for an Elasticsearch cluster using a {@link RestClient}.
38+
*
39+
* @author Artsiom Yudovin
40+
* @author Brian Clozel
41+
* @author Filip Hrisafov
42+
* @since 2.7
43+
*/
44+
public class ElasticsearchRestClientHealthIndicator extends AbstractHealthIndicator {
45+
46+
private static final String RED_STATUS = "red";
47+
48+
private final RestClient client;
49+
50+
private final JsonParser jsonParser;
51+
52+
public ElasticsearchRestClientHealthIndicator(RestClient client) {
53+
super("Elasticsearch health check failed");
54+
this.client = client;
55+
this.jsonParser = JsonParserFactory.getJsonParser();
56+
}
57+
58+
@Override
59+
protected void doHealthCheck(Health.Builder builder) throws Exception {
60+
Response response = this.client.performRequest(new Request("GET", "/_cluster/health/"));
61+
StatusLine statusLine = response.getStatusLine();
62+
if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
63+
builder.down();
64+
builder.withDetail("statusCode", statusLine.getStatusCode());
65+
builder.withDetail("reasonPhrase", statusLine.getReasonPhrase());
66+
return;
67+
}
68+
try (InputStream inputStream = response.getEntity().getContent()) {
69+
doHealthCheck(builder, StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8));
70+
}
71+
}
72+
73+
private void doHealthCheck(Health.Builder builder, String json) {
74+
Map<String, Object> response = this.jsonParser.parseMap(json);
75+
String status = (String) response.get("status");
76+
if (RED_STATUS.equals(status)) {
77+
builder.outOfService();
78+
}
79+
else {
80+
builder.up();
81+
}
82+
builder.withDetails(response);
83+
}
84+
85+
}

0 commit comments

Comments
 (0)