Skip to content

Commit b9764e8

Browse files
schuchphilwebb
authored andcommitted
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
1 parent ca6395c commit b9764e8

File tree

2 files changed

+135
-6
lines changed

2 files changed

+135
-6
lines changed

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/solr/SolrHealthIndicator.java

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616

1717
package org.springframework.boot.actuate.solr;
1818

19+
import java.io.IOException;
20+
1921
import org.apache.solr.client.solrj.SolrClient;
22+
import org.apache.solr.client.solrj.SolrServerException;
23+
import org.apache.solr.client.solrj.impl.HttpSolrClient;
2024
import org.apache.solr.client.solrj.request.CoreAdminRequest;
2125
import org.apache.solr.client.solrj.response.CoreAdminResponse;
2226
import org.apache.solr.common.params.CoreAdminParams;
@@ -25,31 +29,87 @@
2529
import org.springframework.boot.actuate.health.Health;
2630
import org.springframework.boot.actuate.health.HealthIndicator;
2731
import org.springframework.boot.actuate.health.Status;
32+
import org.springframework.http.HttpStatus;
2833

2934
/**
3035
* {@link HealthIndicator} for Apache Solr.
3136
*
3237
* @author Andy Wilkinson
3338
* @author Stephane Nicoll
39+
* @author Markus Schuch
3440
* @since 2.0.0
3541
*/
3642
public class SolrHealthIndicator extends AbstractHealthIndicator {
3743

3844
private final SolrClient solrClient;
3945

46+
private PathType detectedPathType = PathType.UNKNOWN;
47+
4048
public SolrHealthIndicator(SolrClient solrClient) {
4149
super("Solr health check failed");
4250
this.solrClient = solrClient;
4351
}
4452

45-
@Override
4653
protected void doHealthCheck(Health.Builder builder) throws Exception {
54+
int statusCode;
55+
56+
if (this.detectedPathType == PathType.ROOT) {
57+
statusCode = doCoreAdminCheck();
58+
}
59+
else if (this.detectedPathType == PathType.PARTICULAR_CORE) {
60+
statusCode = doPingCheck();
61+
}
62+
else {
63+
// We do not know yet, which is the promising
64+
// health check strategy, so we start trying with
65+
// a CoreAdmin check, which is the common case
66+
try {
67+
statusCode = doCoreAdminCheck();
68+
// When the CoreAdmin request returns with a
69+
// valid response, we can assume that this
70+
// SolrClient is configured with a baseUrl
71+
// pointing to the root path of the Solr instance
72+
this.detectedPathType = PathType.ROOT;
73+
}
74+
catch (HttpSolrClient.RemoteSolrException ex) {
75+
// CoreAdmin requests to not work with
76+
// SolrClients configured with a baseUrl pointing to
77+
// a particular core and a 404 response indicates
78+
// that this might be the case.
79+
if (ex.code() == HttpStatus.NOT_FOUND.value()) {
80+
statusCode = doPingCheck();
81+
// When the SolrPing returns with a valid
82+
// response, we can assume that the baseUrl
83+
// of this SolrClient points to a particular core
84+
this.detectedPathType = PathType.PARTICULAR_CORE;
85+
}
86+
else {
87+
// Rethrow every other response code leaving us
88+
// in the dark about the type of the baseUrl
89+
throw ex;
90+
}
91+
}
92+
}
93+
Status status = (statusCode != 0) ? Status.DOWN : Status.UP;
94+
builder.status(status).withDetail("status", statusCode).withDetail("detectedPathType",
95+
this.detectedPathType.toString());
96+
}
97+
98+
private int doCoreAdminCheck() throws IOException, SolrServerException {
4799
CoreAdminRequest request = new CoreAdminRequest();
48100
request.setAction(CoreAdminParams.CoreAdminAction.STATUS);
49101
CoreAdminResponse response = request.process(this.solrClient);
50-
int statusCode = response.getStatus();
51-
Status status = (statusCode != 0) ? Status.DOWN : Status.UP;
52-
builder.status(status).withDetail("status", statusCode);
102+
return response.getStatus();
103+
}
104+
105+
private int doPingCheck() throws IOException, SolrServerException {
106+
return this.solrClient.ping().getStatus();
107+
}
108+
109+
enum PathType {
110+
111+
ROOT, PARTICULAR_CORE, UNKNOWN
112+
53113
}
54114

55115
}

spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/solr/SolrHealthIndicatorTests.java

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
import java.io.IOException;
2020

2121
import org.apache.solr.client.solrj.SolrClient;
22+
import org.apache.solr.client.solrj.impl.HttpSolrClient;
2223
import org.apache.solr.client.solrj.request.CoreAdminRequest;
24+
import org.apache.solr.client.solrj.response.SolrPingResponse;
2325
import org.apache.solr.common.util.NamedList;
2426
import org.junit.After;
2527
import org.junit.Test;
@@ -33,6 +35,9 @@
3335
import static org.mockito.ArgumentMatchers.isNull;
3436
import static org.mockito.BDDMockito.given;
3537
import static org.mockito.Mockito.mock;
38+
import static org.mockito.Mockito.times;
39+
import static org.mockito.Mockito.verify;
40+
import static org.mockito.Mockito.verifyNoMoreInteractions;
3641

3742
/**
3843
* Tests for {@link SolrHealthIndicator}
@@ -51,23 +56,79 @@ public void close() {
5156
}
5257

5358
@Test
54-
public void solrIsUp() throws Exception {
59+
public void solrIsUpWithBaseUrlPointsToRoot() throws Exception {
5560
SolrClient solrClient = mock(SolrClient.class);
5661
given(solrClient.request(any(CoreAdminRequest.class), isNull())).willReturn(mockResponse(0));
5762
SolrHealthIndicator healthIndicator = new SolrHealthIndicator(solrClient);
5863
Health health = healthIndicator.health();
5964
assertThat(health.getStatus()).isEqualTo(Status.UP);
6065
assertThat(health.getDetails().get("status")).isEqualTo(0);
66+
assertThat(health.getDetails().get("detectedPathType")).isEqualTo(SolrHealthIndicator.PathType.ROOT.toString());
67+
verify(solrClient, times(1)).request(any(CoreAdminRequest.class), isNull());
68+
verifyNoMoreInteractions(solrClient);
6169
}
6270

6371
@Test
64-
public void solrIsUpAndRequestFailed() throws Exception {
72+
public void solrIsUpWithBaseUrlPointsToParticularCore() throws Exception {
73+
SolrClient solrClient = mock(SolrClient.class);
74+
given(solrClient.request(any(CoreAdminRequest.class), isNull()))
75+
.willThrow(new HttpSolrClient.RemoteSolrException("mock", 404, "", null));
76+
given(solrClient.ping()).willReturn(mockPingResponse(0));
77+
SolrHealthIndicator healthIndicator = new SolrHealthIndicator(solrClient);
78+
Health health = healthIndicator.health();
79+
assertThat(health.getStatus()).isEqualTo(Status.UP);
80+
assertThat(health.getDetails().get("status")).isEqualTo(0);
81+
assertThat(health.getDetails().get("detectedPathType"))
82+
.isEqualTo(SolrHealthIndicator.PathType.PARTICULAR_CORE.toString());
83+
verify(solrClient, times(1)).request(any(CoreAdminRequest.class), isNull());
84+
verify(solrClient, times(1)).ping();
85+
verifyNoMoreInteractions(solrClient);
86+
}
87+
88+
@Test
89+
public void pathTypeIsRememberedForConsecutiveChecks() throws Exception {
90+
SolrClient solrClient = mock(SolrClient.class);
91+
given(solrClient.request(any(CoreAdminRequest.class), isNull()))
92+
.willThrow(new HttpSolrClient.RemoteSolrException("mock", 404, "", null));
93+
given(solrClient.ping()).willReturn(mockPingResponse(0));
94+
SolrHealthIndicator healthIndicator = new SolrHealthIndicator(solrClient);
95+
healthIndicator.health();
96+
verify(solrClient, times(1)).request(any(CoreAdminRequest.class), isNull());
97+
verify(solrClient, times(1)).ping();
98+
verifyNoMoreInteractions(solrClient);
99+
healthIndicator.health();
100+
verify(solrClient, times(2)).ping();
101+
verifyNoMoreInteractions(solrClient);
102+
}
103+
104+
@Test
105+
public void solrIsUpAndRequestFailedWithBaseUrlPointsToRoot() throws Exception {
65106
SolrClient solrClient = mock(SolrClient.class);
66107
given(solrClient.request(any(CoreAdminRequest.class), isNull())).willReturn(mockResponse(400));
67108
SolrHealthIndicator healthIndicator = new SolrHealthIndicator(solrClient);
68109
Health health = healthIndicator.health();
69110
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
70111
assertThat(health.getDetails().get("status")).isEqualTo(400);
112+
assertThat(health.getDetails().get("detectedPathType")).isEqualTo(SolrHealthIndicator.PathType.ROOT.toString());
113+
verify(solrClient, times(1)).request(any(CoreAdminRequest.class), isNull());
114+
verifyNoMoreInteractions(solrClient);
115+
}
116+
117+
@Test
118+
public void solrIsUpAndRequestFailedWithBaseUrlPointsToParticularCore() throws Exception {
119+
SolrClient solrClient = mock(SolrClient.class);
120+
given(solrClient.request(any(CoreAdminRequest.class), isNull()))
121+
.willThrow(new HttpSolrClient.RemoteSolrException("mock", 404, "", null));
122+
given(solrClient.ping()).willReturn(mockPingResponse(400));
123+
SolrHealthIndicator healthIndicator = new SolrHealthIndicator(solrClient);
124+
Health health = healthIndicator.health();
125+
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
126+
assertThat(health.getDetails().get("status")).isEqualTo(400);
127+
assertThat(health.getDetails().get("detectedPathType"))
128+
.isEqualTo(SolrHealthIndicator.PathType.PARTICULAR_CORE.toString());
129+
verify(solrClient, times(1)).request(any(CoreAdminRequest.class), isNull());
130+
verify(solrClient, times(1)).ping();
131+
verifyNoMoreInteractions(solrClient);
71132
}
72133

73134
@Test
@@ -79,6 +140,8 @@ public void solrIsDown() throws Exception {
79140
Health health = healthIndicator.health();
80141
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
81142
assertThat((String) health.getDetails().get("error")).contains("Connection failed");
143+
verify(solrClient, times(1)).request(any(CoreAdminRequest.class), isNull());
144+
verifyNoMoreInteractions(solrClient);
82145
}
83146

84147
private NamedList<Object> mockResponse(int status) {
@@ -89,4 +152,10 @@ private NamedList<Object> mockResponse(int status) {
89152
return response;
90153
}
91154

155+
private SolrPingResponse mockPingResponse(int status) {
156+
SolrPingResponse pingResponse = new SolrPingResponse();
157+
pingResponse.setResponse(mockResponse(status));
158+
return pingResponse;
159+
}
160+
92161
}

0 commit comments

Comments
 (0)