From b9764e8de8a3c54d5e1ec9894593711d490d1ea1 Mon Sep 17 00:00:00 2001 From: Markus Schuch Date: Sat, 6 Apr 2019 16:53:49 +0200 Subject: [PATCH 1/2] Fallback to ping if Solr URL references core Update `SolrHealthIndicator` to fallback to a basic ping operation if the `baseUrl` references a particular core rather than the root context. Prior to this commit, if the Solr `baseUrl` pointed to a particular core then the health indicator would incorrectly report `DOWN`. See gh-16477 --- .../actuate/solr/SolrHealthIndicator.java | 68 ++++++++++++++++- .../solr/SolrHealthIndicatorTests.java | 73 ++++++++++++++++++- 2 files changed, 135 insertions(+), 6 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/solr/SolrHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/solr/SolrHealthIndicator.java index 0feaf4dde1..fe6a56b1cd 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/solr/SolrHealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/solr/SolrHealthIndicator.java @@ -16,7 +16,11 @@ package org.springframework.boot.actuate.solr; +import java.io.IOException; + import org.apache.solr.client.solrj.SolrClient; +import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.impl.HttpSolrClient; import org.apache.solr.client.solrj.request.CoreAdminRequest; import org.apache.solr.client.solrj.response.CoreAdminResponse; import org.apache.solr.common.params.CoreAdminParams; @@ -25,31 +29,87 @@ 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.actuate.health.Status; +import org.springframework.http.HttpStatus; /** * {@link HealthIndicator} for Apache Solr. * * @author Andy Wilkinson * @author Stephane Nicoll + * @author Markus Schuch * @since 2.0.0 */ public class SolrHealthIndicator extends AbstractHealthIndicator { private final SolrClient solrClient; + private PathType detectedPathType = PathType.UNKNOWN; + public SolrHealthIndicator(SolrClient solrClient) { super("Solr health check failed"); this.solrClient = solrClient; } - @Override protected void doHealthCheck(Health.Builder builder) throws Exception { + int statusCode; + + if (this.detectedPathType == PathType.ROOT) { + statusCode = doCoreAdminCheck(); + } + else if (this.detectedPathType == PathType.PARTICULAR_CORE) { + statusCode = doPingCheck(); + } + else { + // We do not know yet, which is the promising + // health check strategy, so we start trying with + // a CoreAdmin check, which is the common case + try { + statusCode = doCoreAdminCheck(); + // When the CoreAdmin request returns with a + // valid response, we can assume that this + // SolrClient is configured with a baseUrl + // pointing to the root path of the Solr instance + this.detectedPathType = PathType.ROOT; + } + catch (HttpSolrClient.RemoteSolrException ex) { + // CoreAdmin requests to not work with + // SolrClients configured with a baseUrl pointing to + // a particular core and a 404 response indicates + // that this might be the case. + if (ex.code() == HttpStatus.NOT_FOUND.value()) { + statusCode = doPingCheck(); + // When the SolrPing returns with a valid + // response, we can assume that the baseUrl + // of this SolrClient points to a particular core + this.detectedPathType = PathType.PARTICULAR_CORE; + } + else { + // Rethrow every other response code leaving us + // in the dark about the type of the baseUrl + throw ex; + } + } + } + Status status = (statusCode != 0) ? Status.DOWN : Status.UP; + builder.status(status).withDetail("status", statusCode).withDetail("detectedPathType", + this.detectedPathType.toString()); + } + + private int doCoreAdminCheck() throws IOException, SolrServerException { CoreAdminRequest request = new CoreAdminRequest(); request.setAction(CoreAdminParams.CoreAdminAction.STATUS); CoreAdminResponse response = request.process(this.solrClient); - int statusCode = response.getStatus(); - Status status = (statusCode != 0) ? Status.DOWN : Status.UP; - builder.status(status).withDetail("status", statusCode); + return response.getStatus(); + } + + private int doPingCheck() throws IOException, SolrServerException { + return this.solrClient.ping().getStatus(); + } + + enum PathType { + + ROOT, PARTICULAR_CORE, UNKNOWN + } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/solr/SolrHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/solr/SolrHealthIndicatorTests.java index 253e70065f..5a157880e9 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/solr/SolrHealthIndicatorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/solr/SolrHealthIndicatorTests.java @@ -19,7 +19,9 @@ package org.springframework.boot.actuate.solr; import java.io.IOException; import org.apache.solr.client.solrj.SolrClient; +import org.apache.solr.client.solrj.impl.HttpSolrClient; import org.apache.solr.client.solrj.request.CoreAdminRequest; +import org.apache.solr.client.solrj.response.SolrPingResponse; import org.apache.solr.common.util.NamedList; import org.junit.After; import org.junit.Test; @@ -33,6 +35,9 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; /** * Tests for {@link SolrHealthIndicator} @@ -51,23 +56,79 @@ public class SolrHealthIndicatorTests { } @Test - public void solrIsUp() throws Exception { + public void solrIsUpWithBaseUrlPointsToRoot() throws Exception { SolrClient solrClient = mock(SolrClient.class); given(solrClient.request(any(CoreAdminRequest.class), isNull())).willReturn(mockResponse(0)); SolrHealthIndicator healthIndicator = new SolrHealthIndicator(solrClient); Health health = healthIndicator.health(); assertThat(health.getStatus()).isEqualTo(Status.UP); assertThat(health.getDetails().get("status")).isEqualTo(0); + assertThat(health.getDetails().get("detectedPathType")).isEqualTo(SolrHealthIndicator.PathType.ROOT.toString()); + verify(solrClient, times(1)).request(any(CoreAdminRequest.class), isNull()); + verifyNoMoreInteractions(solrClient); } @Test - public void solrIsUpAndRequestFailed() throws Exception { + public void solrIsUpWithBaseUrlPointsToParticularCore() throws Exception { + SolrClient solrClient = mock(SolrClient.class); + given(solrClient.request(any(CoreAdminRequest.class), isNull())) + .willThrow(new HttpSolrClient.RemoteSolrException("mock", 404, "", null)); + given(solrClient.ping()).willReturn(mockPingResponse(0)); + SolrHealthIndicator healthIndicator = new SolrHealthIndicator(solrClient); + Health health = healthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails().get("status")).isEqualTo(0); + assertThat(health.getDetails().get("detectedPathType")) + .isEqualTo(SolrHealthIndicator.PathType.PARTICULAR_CORE.toString()); + verify(solrClient, times(1)).request(any(CoreAdminRequest.class), isNull()); + verify(solrClient, times(1)).ping(); + verifyNoMoreInteractions(solrClient); + } + + @Test + public void pathTypeIsRememberedForConsecutiveChecks() throws Exception { + SolrClient solrClient = mock(SolrClient.class); + given(solrClient.request(any(CoreAdminRequest.class), isNull())) + .willThrow(new HttpSolrClient.RemoteSolrException("mock", 404, "", null)); + given(solrClient.ping()).willReturn(mockPingResponse(0)); + SolrHealthIndicator healthIndicator = new SolrHealthIndicator(solrClient); + healthIndicator.health(); + verify(solrClient, times(1)).request(any(CoreAdminRequest.class), isNull()); + verify(solrClient, times(1)).ping(); + verifyNoMoreInteractions(solrClient); + healthIndicator.health(); + verify(solrClient, times(2)).ping(); + verifyNoMoreInteractions(solrClient); + } + + @Test + public void solrIsUpAndRequestFailedWithBaseUrlPointsToRoot() throws Exception { SolrClient solrClient = mock(SolrClient.class); given(solrClient.request(any(CoreAdminRequest.class), isNull())).willReturn(mockResponse(400)); SolrHealthIndicator healthIndicator = new SolrHealthIndicator(solrClient); Health health = healthIndicator.health(); assertThat(health.getStatus()).isEqualTo(Status.DOWN); assertThat(health.getDetails().get("status")).isEqualTo(400); + assertThat(health.getDetails().get("detectedPathType")).isEqualTo(SolrHealthIndicator.PathType.ROOT.toString()); + verify(solrClient, times(1)).request(any(CoreAdminRequest.class), isNull()); + verifyNoMoreInteractions(solrClient); + } + + @Test + public void solrIsUpAndRequestFailedWithBaseUrlPointsToParticularCore() throws Exception { + SolrClient solrClient = mock(SolrClient.class); + given(solrClient.request(any(CoreAdminRequest.class), isNull())) + .willThrow(new HttpSolrClient.RemoteSolrException("mock", 404, "", null)); + given(solrClient.ping()).willReturn(mockPingResponse(400)); + SolrHealthIndicator healthIndicator = new SolrHealthIndicator(solrClient); + Health health = healthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.DOWN); + assertThat(health.getDetails().get("status")).isEqualTo(400); + assertThat(health.getDetails().get("detectedPathType")) + .isEqualTo(SolrHealthIndicator.PathType.PARTICULAR_CORE.toString()); + verify(solrClient, times(1)).request(any(CoreAdminRequest.class), isNull()); + verify(solrClient, times(1)).ping(); + verifyNoMoreInteractions(solrClient); } @Test @@ -79,6 +140,8 @@ public class SolrHealthIndicatorTests { Health health = healthIndicator.health(); assertThat(health.getStatus()).isEqualTo(Status.DOWN); assertThat((String) health.getDetails().get("error")).contains("Connection failed"); + verify(solrClient, times(1)).request(any(CoreAdminRequest.class), isNull()); + verifyNoMoreInteractions(solrClient); } private NamedList mockResponse(int status) { @@ -89,4 +152,10 @@ public class SolrHealthIndicatorTests { return response; } + private SolrPingResponse mockPingResponse(int status) { + SolrPingResponse pingResponse = new SolrPingResponse(); + pingResponse.setResponse(mockResponse(status)); + return pingResponse; + } + } From e8d9b6f49835f294a238afed7319356b7c2513e0 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 3 Sep 2019 13:51:10 -0700 Subject: [PATCH 2/2] Polish "Fallback to ping if Solr URL references core" See gh-16477 --- .../actuate/solr/SolrHealthIndicator.java | 127 ++++++++++-------- .../solr/SolrHealthIndicatorTests.java | 76 +++++------ 2 files changed, 110 insertions(+), 93 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/solr/SolrHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/solr/SolrHealthIndicator.java index fe6a56b1cd..f9edd53f46 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/solr/SolrHealthIndicator.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/solr/SolrHealthIndicator.java @@ -16,20 +16,15 @@ package org.springframework.boot.actuate.solr; -import java.io.IOException; - import org.apache.solr.client.solrj.SolrClient; -import org.apache.solr.client.solrj.SolrServerException; -import org.apache.solr.client.solrj.impl.HttpSolrClient; +import org.apache.solr.client.solrj.impl.HttpSolrClient.RemoteSolrException; import org.apache.solr.client.solrj.request.CoreAdminRequest; -import org.apache.solr.client.solrj.response.CoreAdminResponse; import org.apache.solr.common.params.CoreAdminParams; 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.actuate.health.Status; -import org.springframework.http.HttpStatus; /** * {@link HealthIndicator} for Apache Solr. @@ -37,78 +32,104 @@ import org.springframework.http.HttpStatus; * @author Andy Wilkinson * @author Stephane Nicoll * @author Markus Schuch + * @author Phillip Webb * @since 2.0.0 */ public class SolrHealthIndicator extends AbstractHealthIndicator { + private static final int HTTP_NOT_FOUND_STATUS = 404; + private final SolrClient solrClient; - private PathType detectedPathType = PathType.UNKNOWN; + private volatile StatusCheck statusCheck; public SolrHealthIndicator(SolrClient solrClient) { super("Solr health check failed"); this.solrClient = solrClient; } + @Override protected void doHealthCheck(Health.Builder builder) throws Exception { - int statusCode; + int statusCode = initializeStatusCheck(); + Status status = (statusCode != 0) ? Status.DOWN : Status.UP; + builder.status(status).withDetail("status", statusCode).withDetail("detectedPathType", + this.statusCheck.getPathType()); + } - if (this.detectedPathType == PathType.ROOT) { - statusCode = doCoreAdminCheck(); + private int initializeStatusCheck() throws Exception { + StatusCheck statusCheck = this.statusCheck; + if (statusCheck != null) { + // Already initilized + return statusCheck.getStatus(this.solrClient); } - else if (this.detectedPathType == PathType.PARTICULAR_CORE) { - statusCode = doPingCheck(); + try { + return initializeStatusCheck(new RootStatusCheck()); } - else { - // We do not know yet, which is the promising - // health check strategy, so we start trying with - // a CoreAdmin check, which is the common case - try { - statusCode = doCoreAdminCheck(); - // When the CoreAdmin request returns with a - // valid response, we can assume that this - // SolrClient is configured with a baseUrl - // pointing to the root path of the Solr instance - this.detectedPathType = PathType.ROOT; - } - catch (HttpSolrClient.RemoteSolrException ex) { - // CoreAdmin requests to not work with - // SolrClients configured with a baseUrl pointing to - // a particular core and a 404 response indicates - // that this might be the case. - if (ex.code() == HttpStatus.NOT_FOUND.value()) { - statusCode = doPingCheck(); - // When the SolrPing returns with a valid - // response, we can assume that the baseUrl - // of this SolrClient points to a particular core - this.detectedPathType = PathType.PARTICULAR_CORE; - } - else { - // Rethrow every other response code leaving us - // in the dark about the type of the baseUrl - throw ex; - } + catch (RemoteSolrException ex) { + // 404 is thrown when SolrClient has a baseUrl pointing to a particular core. + if (ex.code() == HTTP_NOT_FOUND_STATUS) { + return initializeStatusCheck(new ParticularCoreStatusCheck()); } + throw ex; } - Status status = (statusCode != 0) ? Status.DOWN : Status.UP; - builder.status(status).withDetail("status", statusCode).withDetail("detectedPathType", - this.detectedPathType.toString()); } - private int doCoreAdminCheck() throws IOException, SolrServerException { - CoreAdminRequest request = new CoreAdminRequest(); - request.setAction(CoreAdminParams.CoreAdminAction.STATUS); - CoreAdminResponse response = request.process(this.solrClient); - return response.getStatus(); + private int initializeStatusCheck(StatusCheck statusCheck) throws Exception { + int result = statusCheck.getStatus(this.solrClient); + this.statusCheck = statusCheck; + return result; } - private int doPingCheck() throws IOException, SolrServerException { - return this.solrClient.ping().getStatus(); + /** + * Strategy used to perform the status check. + */ + private abstract static class StatusCheck { + + private final String pathType; + + StatusCheck(String pathType) { + this.pathType = pathType; + } + + abstract int getStatus(SolrClient client) throws Exception; + + String getPathType() { + return this.pathType; + } + + } + + /** + * {@link StatusCheck} used when {@code baseUrl} points to the root context. + */ + private static class RootStatusCheck extends StatusCheck { + + RootStatusCheck() { + super("root"); + } + + @Override + public int getStatus(SolrClient client) throws Exception { + CoreAdminRequest request = new CoreAdminRequest(); + request.setAction(CoreAdminParams.CoreAdminAction.STATUS); + return request.process(client).getStatus(); + } + } - enum PathType { + /** + * {@link StatusCheck} used when {@code baseUrl} points to the particular core. + */ + private static class ParticularCoreStatusCheck extends StatusCheck { - ROOT, PARTICULAR_CORE, UNKNOWN + ParticularCoreStatusCheck() { + super("particular core"); + } + + @Override + public int getStatus(SolrClient client) throws Exception { + return client.ping().getStatus(); + } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/solr/SolrHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/solr/SolrHealthIndicatorTests.java index 5a157880e9..89e87c8bc6 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/solr/SolrHealthIndicatorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/solr/SolrHealthIndicatorTests.java @@ -19,7 +19,7 @@ package org.springframework.boot.actuate.solr; import java.io.IOException; import org.apache.solr.client.solrj.SolrClient; -import org.apache.solr.client.solrj.impl.HttpSolrClient; +import org.apache.solr.client.solrj.impl.HttpSolrClient.RemoteSolrException; import org.apache.solr.client.solrj.request.CoreAdminRequest; import org.apache.solr.client.solrj.response.SolrPingResponse; import org.apache.solr.common.util.NamedList; @@ -43,6 +43,8 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; * Tests for {@link SolrHealthIndicator} * * @author Andy Wilkinson + * @author Markus Schuch + * @author Phillip Webb */ public class SolrHealthIndicatorTests { @@ -56,94 +58,88 @@ public class SolrHealthIndicatorTests { } @Test - public void solrIsUpWithBaseUrlPointsToRoot() throws Exception { + public void healthWhenSolrStatusUpAndBaseUrlPointsToRootReturnsUp() throws Exception { SolrClient solrClient = mock(SolrClient.class); given(solrClient.request(any(CoreAdminRequest.class), isNull())).willReturn(mockResponse(0)); SolrHealthIndicator healthIndicator = new SolrHealthIndicator(solrClient); - Health health = healthIndicator.health(); - assertThat(health.getStatus()).isEqualTo(Status.UP); - assertThat(health.getDetails().get("status")).isEqualTo(0); - assertThat(health.getDetails().get("detectedPathType")).isEqualTo(SolrHealthIndicator.PathType.ROOT.toString()); + assertHealth(healthIndicator, Status.UP, 0, "root"); verify(solrClient, times(1)).request(any(CoreAdminRequest.class), isNull()); verifyNoMoreInteractions(solrClient); } @Test - public void solrIsUpWithBaseUrlPointsToParticularCore() throws Exception { + public void healthWhenSolrStatusDownAndBaseUrlPointsToRootReturnsDown() throws Exception { SolrClient solrClient = mock(SolrClient.class); - given(solrClient.request(any(CoreAdminRequest.class), isNull())) - .willThrow(new HttpSolrClient.RemoteSolrException("mock", 404, "", null)); - given(solrClient.ping()).willReturn(mockPingResponse(0)); + given(solrClient.request(any(CoreAdminRequest.class), isNull())).willReturn(mockResponse(400)); SolrHealthIndicator healthIndicator = new SolrHealthIndicator(solrClient); - Health health = healthIndicator.health(); - assertThat(health.getStatus()).isEqualTo(Status.UP); - assertThat(health.getDetails().get("status")).isEqualTo(0); - assertThat(health.getDetails().get("detectedPathType")) - .isEqualTo(SolrHealthIndicator.PathType.PARTICULAR_CORE.toString()); + assertHealth(healthIndicator, Status.DOWN, 400, "root"); verify(solrClient, times(1)).request(any(CoreAdminRequest.class), isNull()); - verify(solrClient, times(1)).ping(); verifyNoMoreInteractions(solrClient); } @Test - public void pathTypeIsRememberedForConsecutiveChecks() throws Exception { + public void healthWhenSolrStatusUpAndBaseUrlPointsToParticularCoreReturnsUp() throws Exception { SolrClient solrClient = mock(SolrClient.class); given(solrClient.request(any(CoreAdminRequest.class), isNull())) - .willThrow(new HttpSolrClient.RemoteSolrException("mock", 404, "", null)); + .willThrow(new RemoteSolrException("mock", 404, "", null)); given(solrClient.ping()).willReturn(mockPingResponse(0)); SolrHealthIndicator healthIndicator = new SolrHealthIndicator(solrClient); - healthIndicator.health(); + assertHealth(healthIndicator, Status.UP, 0, "particular core"); verify(solrClient, times(1)).request(any(CoreAdminRequest.class), isNull()); verify(solrClient, times(1)).ping(); verifyNoMoreInteractions(solrClient); - healthIndicator.health(); - verify(solrClient, times(2)).ping(); - verifyNoMoreInteractions(solrClient); } @Test - public void solrIsUpAndRequestFailedWithBaseUrlPointsToRoot() throws Exception { + public void healthWhenSolrStatusDownAndBaseUrlPointsToParticularCoreReturnsDown() throws Exception { SolrClient solrClient = mock(SolrClient.class); - given(solrClient.request(any(CoreAdminRequest.class), isNull())).willReturn(mockResponse(400)); + given(solrClient.request(any(CoreAdminRequest.class), isNull())) + .willThrow(new RemoteSolrException("mock", 404, "", null)); + given(solrClient.ping()).willReturn(mockPingResponse(400)); SolrHealthIndicator healthIndicator = new SolrHealthIndicator(solrClient); - Health health = healthIndicator.health(); - assertThat(health.getStatus()).isEqualTo(Status.DOWN); - assertThat(health.getDetails().get("status")).isEqualTo(400); - assertThat(health.getDetails().get("detectedPathType")).isEqualTo(SolrHealthIndicator.PathType.ROOT.toString()); + assertHealth(healthIndicator, Status.DOWN, 400, "particular core"); verify(solrClient, times(1)).request(any(CoreAdminRequest.class), isNull()); + verify(solrClient, times(1)).ping(); verifyNoMoreInteractions(solrClient); } @Test - public void solrIsUpAndRequestFailedWithBaseUrlPointsToParticularCore() throws Exception { + public void healthWhenSolrConnectionFailsReturnsDown() throws Exception { SolrClient solrClient = mock(SolrClient.class); given(solrClient.request(any(CoreAdminRequest.class), isNull())) - .willThrow(new HttpSolrClient.RemoteSolrException("mock", 404, "", null)); - given(solrClient.ping()).willReturn(mockPingResponse(400)); + .willThrow(new IOException("Connection failed")); SolrHealthIndicator healthIndicator = new SolrHealthIndicator(solrClient); Health health = healthIndicator.health(); assertThat(health.getStatus()).isEqualTo(Status.DOWN); - assertThat(health.getDetails().get("status")).isEqualTo(400); - assertThat(health.getDetails().get("detectedPathType")) - .isEqualTo(SolrHealthIndicator.PathType.PARTICULAR_CORE.toString()); + assertThat((String) health.getDetails().get("error")).contains("Connection failed"); verify(solrClient, times(1)).request(any(CoreAdminRequest.class), isNull()); - verify(solrClient, times(1)).ping(); verifyNoMoreInteractions(solrClient); } @Test - public void solrIsDown() throws Exception { + public void healthWhenMakingMultipleCallsRemembersStatusStrategy() throws Exception { SolrClient solrClient = mock(SolrClient.class); given(solrClient.request(any(CoreAdminRequest.class), isNull())) - .willThrow(new IOException("Connection failed")); + .willThrow(new RemoteSolrException("mock", 404, "", null)); + given(solrClient.ping()).willReturn(mockPingResponse(0)); SolrHealthIndicator healthIndicator = new SolrHealthIndicator(solrClient); - Health health = healthIndicator.health(); - assertThat(health.getStatus()).isEqualTo(Status.DOWN); - assertThat((String) health.getDetails().get("error")).contains("Connection failed"); + healthIndicator.health(); verify(solrClient, times(1)).request(any(CoreAdminRequest.class), isNull()); + verify(solrClient, times(1)).ping(); + verifyNoMoreInteractions(solrClient); + healthIndicator.health(); + verify(solrClient, times(2)).ping(); verifyNoMoreInteractions(solrClient); } + private void assertHealth(SolrHealthIndicator healthIndicator, Status expectedStatus, int expectedStatusCode, + String expectedPathType) { + Health health = healthIndicator.health(); + assertThat(health.getStatus()).isEqualTo(expectedStatus); + assertThat(health.getDetails().get("status")).isEqualTo(expectedStatusCode); + assertThat(health.getDetails().get("detectedPathType")).isEqualTo(expectedPathType); + } + private NamedList mockResponse(int status) { NamedList response = new NamedList<>(); NamedList headers = new NamedList<>();