From 1ec2bbf54fd761023ef6e708cbea9444ed3ab024 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Tue, 11 Dec 2018 11:47:46 +0100 Subject: [PATCH] Include details in ElasticsearchRestHealthIndicator This commit adds more information to the ElasticSearch REST health indicator. When the ES instance responds with an error HTTP status, the health details now include the actual status code and reason phrase. When the ES instance returns a HTTP 200 response, the entire response map is used as health details. See gh-15366 --- .../ElasticsearchRestHealthIndicator.java | 18 ++++- .../ElasticsearchRestHealthIndicatorTest.java | 74 +++++++++++++++++-- 2 files changed, 80 insertions(+), 12 deletions(-) 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 963faa73bd..1024dded02 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 @@ -18,8 +18,10 @@ package org.springframework.boot.actuate.elasticsearch; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.Map; import org.apache.http.HttpStatus; +import org.apache.http.StatusLine; import org.elasticsearch.client.Request; import org.elasticsearch.client.Response; import org.elasticsearch.client.RestClient; @@ -36,6 +38,7 @@ import org.springframework.util.StreamUtils; * * @author Artsiom Yudovin * @author Brian Clozel + * @author Filip Hrisafov * @since 2.1.1 */ public class ElasticsearchRestHealthIndicator extends AbstractHealthIndicator { @@ -56,8 +59,11 @@ public class ElasticsearchRestHealthIndicator extends AbstractHealthIndicator { protected void doHealthCheck(Health.Builder builder) throws Exception { Response response = this.client .performRequest(new Request("GET", "/_cluster/health/")); - if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + 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()) { @@ -67,12 +73,16 @@ public class ElasticsearchRestHealthIndicator extends AbstractHealthIndicator { } private void doHealthCheck(Health.Builder builder, String json) { - String status = (String) this.jsonParser.parseMap(json).get("status"); + Map response = this.jsonParser.parseMap(json); + String status = (String) response.get("status"); if (RED_STATUS.equals(status)) { builder.outOfService(); - return; } - builder.up(); + else { + builder.up(); + } + + builder.withDetails(response); } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestHealthIndicatorTest.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestHealthIndicatorTest.java index 67caab025a..7f3bd7427d 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestHealthIndicatorTest.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestHealthIndicatorTest.java @@ -26,9 +26,11 @@ import org.elasticsearch.client.Response; import org.elasticsearch.client.RestClient; import org.junit.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.mock; import static org.mockito.BDDMockito.when; @@ -37,6 +39,7 @@ import static org.mockito.BDDMockito.when; * Tests for {@link ElasticsearchRestHealthIndicator}. * * @author Artsiom Yudovin + * @author Filip Hrisafov */ public class ElasticsearchRestHealthIndicatorTest { @@ -59,8 +62,48 @@ public class ElasticsearchRestHealthIndicatorTest { when(response.getEntity()).thenReturn(httpEntity); when(this.restClient.performRequest(any(Request.class))).thenReturn(response); - assertThat(this.elasticsearchRestHealthIndicator.health().getStatus()) - .isEqualTo(Status.UP); + Health health = this.elasticsearchRestHealthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + + assertThat(health.getDetails()).contains(entry("cluster_name", "elasticsearch"), + entry("status", "green"), 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)); + } + + @Test + public 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); + + when(statusLine.getStatusCode()).thenReturn(200); + when(response.getStatusLine()).thenReturn(statusLine); + when(response.getEntity()).thenReturn(httpEntity); + when(this.restClient.performRequest(any(Request.class))).thenReturn(response); + + Health health = this.elasticsearchRestHealthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + + assertThat(health.getDetails()).contains(entry("cluster_name", "elasticsearch"), + entry("status", "yellow"), 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)); } @Test @@ -68,8 +111,10 @@ public class ElasticsearchRestHealthIndicatorTest { when(this.restClient.performRequest(any(Request.class))) .thenThrow(new IOException("Couldn't connect")); - assertThat(this.elasticsearchRestHealthIndicator.health().getStatus()) - .isEqualTo(Status.DOWN); + Health health = this.elasticsearchRestHealthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.DOWN); + assertThat(health.getDetails()) + .contains(entry("error", "java.io.IOException: Couldn't connect")); } @Test @@ -79,11 +124,14 @@ public class ElasticsearchRestHealthIndicatorTest { StatusLine statusLine = mock(StatusLine.class); when(statusLine.getStatusCode()).thenReturn(500); + when(statusLine.getReasonPhrase()).thenReturn("Internal server error"); when(response.getStatusLine()).thenReturn(statusLine); when(this.restClient.performRequest(any(Request.class))).thenReturn(response); - assertThat(this.elasticsearchRestHealthIndicator.health().getStatus()) - .isEqualTo(Status.DOWN); + Health health = this.elasticsearchRestHealthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.DOWN); + assertThat(health.getDetails()).contains(entry("statusCode", 500), + entry("reasonPhrase", "Internal server error")); } @Test @@ -100,8 +148,18 @@ public class ElasticsearchRestHealthIndicatorTest { when(response.getEntity()).thenReturn(httpEntity); when(this.restClient.performRequest(any(Request.class))).thenReturn(response); - assertThat(this.elasticsearchRestHealthIndicator.health().getStatus()) - .isEqualTo(Status.OUT_OF_SERVICE); + Health health = this.elasticsearchRestHealthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.OUT_OF_SERVICE); + assertThat(health.getDetails()).contains(entry("cluster_name", "elasticsearch"), + entry("status", "red"), 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) {