PingRequestHandler is now directly configured with a "healthcheckFile" instead of looking for the legacy <admin><healthcheck/></admin> syntax. Filenames specified as relative paths have been fixed so that they are resolved against the data dir instead of the CWD of the java process.

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1333598 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Chris M. Hostetter 2012-05-03 19:56:13 +00:00
parent b0a4eaba31
commit d5eeb396a5
16 changed files with 296 additions and 181 deletions

View File

@ -78,6 +78,12 @@ Upgrading from Solr 3.6-dev
CommonsHttpSolrServer is now HttpSolrServer, and
StreamingUpdateSolrServer is now ConcurrentUpdateSolrServer.
* The PingRequestHandler no longer looks for a <healthcheck/> option in the
(legacy) <admin> section of solrconfig.xml. Users who wish to take
advantage of this feature should configure a "healthcheckFile" init param
directly on the PingRequestHandler. As part of this change, relative file
paths have been fixed to be resolved against the data dir. See the example
solrconfig.xml and SOLR-1258 for more details.
Detailed Change List
----------------------
@ -535,6 +541,12 @@ Other Changes
* SOLR-3403: Deprecated Analysis Factories now log their own deprecation messages.
No logging support is provided by Factory parent classes. (Chris Male)
* SOLR-1258: PingRequestHandler is now directly configured with a
"healthcheckFile" instead of looking for the legacy
<admin><healthcheck/></admin> syntax. Filenames specified as relative
paths have been fixed so that they are resolved against the data dir
instead of the CWD of the java process. (hossman)
Documentation
----------------------

View File

@ -464,10 +464,6 @@
<!-- config for the admin interface -->
<admin>
<defaultQuery>solr</defaultQuery>
<!-- configure a healthcheck file for servers behind a loadbalancer
<healthcheck type="file">server-enabled</healthcheck>
-->
</admin>
</config>

View File

@ -305,10 +305,6 @@
<!-- config for the admin interface -->
<admin>
<defaultQuery>*:*</defaultQuery>
<!-- configure a healthcheck file for servers behind a loadbalancer
<healthcheck type="file">server-enabled</healthcheck>
-->
</admin>
</config>

View File

@ -309,10 +309,6 @@
<!-- config for the admin interface -->
<admin>
<defaultQuery>*:*</defaultQuery>
<!-- configure a healthcheck file for servers behind a loadbalancer
<healthcheck type="file">server-enabled</healthcheck>
-->
</admin>
<updateRequestProcessorChain key="contentstream" default="true">

View File

@ -307,10 +307,6 @@
<!-- config for the admin interface -->
<admin>
<defaultQuery>*:*</defaultQuery>
<!-- configure a healthcheck file for servers behind a loadbalancer
<healthcheck type="file">server-enabled</healthcheck>
-->
</admin>
</config>

View File

@ -305,10 +305,6 @@
<!-- config for the admin interface -->
<admin>
<defaultQuery>*:*</defaultQuery>
<!-- configure a healthcheck file for servers behind a loadbalancer
<healthcheck type="file">server-enabled</healthcheck>
-->
</admin>
<updateRequestProcessorChain key="dataimport" default="true">

View File

@ -968,11 +968,6 @@
<!-- config for the admin interface -->
<admin>
<defaultQuery>*</defaultQuery>
<!--
configure a healthcheck file for servers behind a loadbalancer
<healthcheck type="file">server-enabled</healthcheck>
-->
</admin>
</config>

View File

@ -850,11 +850,6 @@
<!-- config for the admin interface -->
<admin>
<defaultQuery>*</defaultQuery>
<!--
configure a healthcheck file for servers behind a loadbalancer
<healthcheck type="file">server-enabled</healthcheck>
-->
</admin>
</config>

View File

@ -19,30 +19,154 @@ package org.apache.solr.handler;
import java.io.File;
import java.io.FileWriter;
import java.text.SimpleDateFormat;
import java.io.IOException;
import java.util.Date;
import java.util.Locale;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.SolrCore;
import org.apache.solr.util.plugin.SolrCoreAware;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.schema.DateField;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Ping solr core
* Ping Request Handler for reporting SolrCore health to a Load Balancer.
*
* <p>
* This handler is designed to be used as the endpoint for an HTTP
* Load-Balancer to use when checking the "health" or "up status" of a
* Solr server.
* </p>
*
* <p>
* In it's simplest form, the PingRequestHandler should be
* configured with some defaults indicating a request that should be
* executed. If the request succeeds, then the PingRequestHandler
* will respond back with a simple "OK" status. If the request fails,
* then the PingRequestHandler will respond back with the
* corrisponding HTTP Error code. Clients (such as load balancers)
* can be configured to poll the PingRequestHandler monitoring for
* these types of responses (or for a simple connection failure) to
* know if there is a problem with the Solr server.
* </p>
*
* <pre class="prettyprint">
* &lt;requestHandler name="/admin/ping" class="solr.PingRequestHandler"&gt;
* &lt;lst name="invariants"&gt;
* &lt;str name="qt"&gt;/search&lt;/str&gt;&lt;!-- handler to delegate to --&gt;
* &lt;str name="q"&gt;some test query&lt;/str&gt;
* &lt;/lst&gt;
* &lt;/requestHandler&gt;
* </pre>
*
* <p>
* A more advanced option available, is to configure the handler with a
* "healthcheckFile" which can be used to enable/disable the PingRequestHandler.
* </p>
*
* <pre class="prettyprint">
* &lt;requestHandler name="/admin/ping" class="solr.PingRequestHandler"&gt;
* &lt;!-- relative paths are resolved against the data dir --&gt;
* &lt;str name="healthcheckFile"&gt;server-enabled.txt&lt;/str&gt;
* &lt;lst name="invariants"&gt;
* &lt;str name="qt"&gt;/search&lt;/str&gt;&lt;!-- handler to delegate to --&gt;
* &lt;str name="q"&gt;some test query&lt;/str&gt;
* &lt;/lst&gt;
* &lt;/requestHandler&gt;
* </pre>
*
* <ul>
* <li>If the health check file exists, the handler will execute the
* delegated query and return status as described above.
* </li>
* <li>If the health check file does not exist, the handler will return
* an HTTP error even if the server is working fine and the delegated
* query would have succeeded
* </li>
* </ul>
*
* <p>
* This health check file feature can be used as a way to indicate
* to some Load Balancers that the server should be "removed from
* rotation" for maintenance, or upgrades, or whatever reason you may
* wish.
* </p>
*
* <p>
* The health check file may be created/deleted by any external
* system, or the PingRequestHandler itself can be used to
* create/delete the file by specifying an "action" param in a
* request:
* </p>
*
* <ul>
* <li><code>http://.../ping?action=enable</code>
* - creates the health check file if it does not already exist
* </li>
* <li><code>http://.../ping?action=disable</code>
* - deletes the health check file if it exists
* </li>
* <li><code>http://.../ping?action=status</code>
* - returns a status code indicating if the healthcheck file exists
* ("<code>enabled</code>") or not ("<code>disabled<code>")
* </li>
* </ul>
*
* @since solr 1.3
*/
public class PingRequestHandler extends RequestHandlerBase
public class PingRequestHandler extends RequestHandlerBase implements SolrCoreAware
{
public static Logger log = LoggerFactory.getLogger(PingRequestHandler.class);
SimpleDateFormat formatRFC3339 = new SimpleDateFormat("yyyy-MM-dd'T'h:m:ss.SZ");
public static final String HEALTHCHECK_FILE_PARAM = "healthcheckFile";
protected enum ACTIONS {STATUS, ENABLE, DISABLE, PING};
private String healthcheck = null;
private String healthFileName = null;
private File healthcheck = null;
public void init(NamedList args) {
super.init(args);
Object tmp = args.get(HEALTHCHECK_FILE_PARAM);
healthFileName = (null == tmp ? null : tmp.toString());
}
public void inform( SolrCore core ) {
if (null != healthFileName) {
healthcheck = new File(healthFileName);
if ( ! healthcheck.isAbsolute()) {
healthcheck = new File(core.getDataDir(), healthFileName);
healthcheck = healthcheck.getAbsoluteFile();
}
if ( ! healthcheck.getParentFile().canWrite()) {
// this is not fatal, users may not care about enable/disable via
// solr request, file might be touched/deleted by an external system
log.warn("Directory for configured healthcheck file is not writable by solr, PingRequestHandler will not be able to control enable/disable: {}",
healthcheck.getParentFile().getAbsolutePath());
}
}
}
/**
* Returns true if the healthcheck flag-file is enabled but does not exist,
* otherwise (no file configured, or file configured and exists)
* returns false.
*/
public boolean isPingDisabled() {
return (null != healthcheck && ! healthcheck.exists() );
}
@Override
public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception
@ -51,9 +175,6 @@ public class PingRequestHandler extends RequestHandlerBase
SolrParams params = req.getParams();
SolrCore core = req.getCore();
// Check if the service is available
healthcheck = core.getSolrConfig().get("admin/healthcheck/text()", null );
String actionParam = params.get("action");
ACTIONS action = null;
if (actionParam == null){
@ -70,32 +191,28 @@ public class PingRequestHandler extends RequestHandlerBase
}
switch(action){
case PING:
if( healthcheck != null && !new File(healthcheck).exists() ) {
throw new SolrException(SolrException.ErrorCode.SERVICE_UNAVAILABLE, "Service disabled");
if( isPingDisabled() ) {
throw new SolrException(SolrException.ErrorCode.SERVICE_UNAVAILABLE,
"Service disabled");
}
handlePing(req, rsp);
break;
case ENABLE:
handleEnable(healthcheck,true);
handleEnable(true);
break;
case DISABLE:
handleEnable(healthcheck,false);
handleEnable(false);
break;
case STATUS:
if( healthcheck == null){
SolrException e = new SolrException(SolrException.ErrorCode.SERVICE_UNAVAILABLE, "healthcheck not configured");
if( healthcheck == null ){
SolrException e = new SolrException
(SolrException.ErrorCode.SERVICE_UNAVAILABLE,
"healthcheck not configured");
rsp.setException(e);
}
else {
if ( new File(healthcheck).exists() ){
rsp.add( "status", "enabled");
}
else {
rsp.add( "status", "disabled");
}
} else {
rsp.add( "status", isPingDisabled() ? "disabled" : "enabled" );
}
}
}
protected void handlePing(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception
@ -136,24 +253,25 @@ public class PingRequestHandler extends RequestHandlerBase
rsp.add( "status", "OK" );
}
protected void handleEnable(String healthcheck, boolean enable) throws Exception
{
protected void handleEnable(boolean enable) throws SolrException {
if (healthcheck == null) {
throw new SolrException(SolrException.ErrorCode.SERVICE_UNAVAILABLE,
"No healthcheck file defined.");
}
File enableFile = new File(healthcheck);
if ( enable ) {
enableFile.createNewFile();
// write out when the file was created
FileWriter fw = new FileWriter(enableFile);
fw.write(formatRFC3339.format(new Date()));
fw.close();
try {
// write out when the file was created
FileUtils.write(healthcheck,
DateField.formatExternal(new Date()), "UTF-8");
} catch (IOException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Unable to write healthcheck flag file", e);
}
} else {
if (enableFile.exists() && !enableFile.delete()){
throw new SolrException( SolrException.ErrorCode.NOT_FOUND,"Did not successfully delete healthcheck file:'"+healthcheck+"'");
if (healthcheck.exists() && !healthcheck.delete()){
throw new SolrException(SolrException.ErrorCode.NOT_FOUND,
"Did not successfully delete healthcheck file: "
+healthcheck.getAbsolutePath());
}
}
}

View File

@ -20,21 +20,24 @@ package org.apache.solr.handler;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.commons.io.FileUtils;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
public class PingRequestHandlerTest extends SolrTestCaseJ4 {
private String healthcheck = "server-enabled";
private File healthcheckFile = new File(healthcheck);
private final String fileName = this.getClass().getName() + ".server-enabled";
private File healthcheckFile = null;
private PingRequestHandler handler = null;
@BeforeClass
@ -43,116 +46,149 @@ public class PingRequestHandlerTest extends SolrTestCaseJ4 {
}
@Before
public void before() {
healthcheckFile.delete();
public void before() throws IOException {
// by default, use relative file in dataDir
healthcheckFile = new File(dataDir, fileName);
String fileNameParam = fileName;
// sometimes randomly use an absolute File path instead
if (random().nextBoolean()) {
healthcheckFile = new File(TEMP_DIR, fileName);
fileNameParam = healthcheckFile.getAbsolutePath();
}
if (healthcheckFile.exists()) FileUtils.forceDelete(healthcheckFile);
handler = new PingRequestHandler();
NamedList initParams = new NamedList();
initParams.add(PingRequestHandler.HEALTHCHECK_FILE_PARAM,
fileNameParam);
handler.init(initParams);
handler.inform(h.getCore());
}
@Test
public void testPing() throws Exception {
public void testPingWithNoHealthCheck() throws Exception {
SolrQueryRequest req = req();
SolrQueryResponse rsp = new SolrQueryResponse();
// for this test, we don't want any healthcheck file configured at all
handler = new PingRequestHandler();
handler.init(new NamedList());
handler.inform(h.getCore());
handler.handleRequestBody(req, rsp);
String status = (String) rsp.getValues().get("status");
assertEquals("OK", status);
req.close();
SolrQueryResponse rsp = null;
req = req("action","ping");
rsp = new SolrQueryResponse();
rsp = makeRequest(handler, req());
assertEquals("OK", rsp.getValues().get("status"));
rsp = makeRequest(handler, req("action","ping"));
assertEquals("OK", rsp.getValues().get("status"));
handler.handleRequestBody(req, rsp);
status = (String) rsp.getValues().get("status");
assertEquals("OK", status);
req.close();
}
@Test
public void testEnablingServer() throws Exception {
assertTrue(! healthcheckFile.exists());
handler.handleEnable(healthcheck, true);
// first make sure that ping responds back that the service is disabled
try {
makeRequest(handler, req());
fail("Should have thrown a SolrException because not enabled yet");
} catch (SolrException se){
assertEquals(SolrException.ErrorCode.SERVICE_UNAVAILABLE.code,se.code());
}
// now enable
makeRequest(handler, req("action", "enable"));
assertTrue(healthcheckFile.exists());
assertNotNull(FileUtils.readFileToString(healthcheckFile), "UTF-8");
SolrQueryRequest req = req();
SolrQueryResponse rsp = new SolrQueryResponse();
// now verify that the handler response with success
handler.handlePing(req, rsp);
String status = (String) rsp.getValues().get("status");
assertEquals("OK", status);
req.close();
SolrQueryResponse rsp = makeRequest(handler, req());
assertEquals("OK", rsp.getValues().get("status"));
FileReader fr = new FileReader(healthcheckFile);
BufferedReader br = new BufferedReader(fr);
String s = br.readLine();
assertNotNull(s);
// enable when already enabled shouldn't cause any problems
makeRequest(handler, req("action", "enable"));
assertTrue(healthcheckFile.exists());
}
@Test
public void testDisablingServer() throws Exception {
assertTrue(! healthcheckFile.exists());
healthcheckFile.createNewFile();
handler.handleEnable(healthcheck, false);
// first make sure that ping responds back that the service is enabled
SolrQueryResponse rsp = makeRequest(handler, req());
assertEquals("OK", rsp.getValues().get("status"));
// now disable
makeRequest(handler, req("action", "disable"));
assertFalse(healthcheckFile.exists());
}
@Test
@Ignore // because of how we load the healthcheck file, we have to change it in schema.xml
public void testGettingStatus() throws Exception {
handler.handleEnable(healthcheck, true);
SolrQueryRequest req = req("action", "status");
SolrQueryResponse rsp = new SolrQueryResponse();
handler.handleRequestBody(req, rsp);
String status = (String) rsp.getValues().get("status");
assertEquals("enabled", status);
req.close();
handler.handleEnable(healthcheck, false);
req = req("action", "status");
rsp = new SolrQueryResponse();
handler.handleRequestBody(req, rsp);
status = (String) rsp.getValues().get("status");
assertEquals("disabled", status);
req.close();
}
@Test
public void testBadActionRaisesException() throws Exception {
SolrQueryRequest req = req("action", "badaction");
SolrQueryResponse rsp = new SolrQueryResponse();
// now make sure that ping responds back that the service is disabled
try {
handler.handleRequestBody(req, rsp);
fail("Should have thrown a SolrException for the bad action");
makeRequest(handler, req());
fail("Should have thrown a SolrException because not enabled yet");
} catch (SolrException se){
assertEquals(SolrException.ErrorCode.SERVICE_UNAVAILABLE.code,se.code());
}
catch (SolrException se){
// disable when already disabled shouldn't cause any problems
makeRequest(handler, req("action", "disable"));
assertFalse(healthcheckFile.exists());
}
public void testGettingStatus() throws Exception {
SolrQueryResponse rsp = null;
handler.handleEnable(true);
rsp = makeRequest(handler, req("action", "status"));
assertEquals("enabled", rsp.getValues().get("status"));
handler.handleEnable(false);
rsp = makeRequest(handler, req("action", "status"));
assertEquals("disabled", rsp.getValues().get("status"));
}
public void testBadActionRaisesException() throws Exception {
try {
SolrQueryResponse rsp = makeRequest(handler, req("action", "badaction"));
fail("Should have thrown a SolrException for the bad action");
} catch (SolrException se){
assertEquals(SolrException.ErrorCode.BAD_REQUEST.code,se.code());
}
}
req.close();
/**
* Helper Method: Executes the request against the handler, returns
* the response, and closes the request.
*/
private SolrQueryResponse makeRequest(PingRequestHandler handler,
SolrQueryRequest req)
throws Exception {
SolrQueryResponse rsp = new SolrQueryResponse();
try {
handler.handleRequestBody(req, rsp);
} finally {
req.close();
}
return rsp;
}
}

View File

@ -488,10 +488,6 @@
<!-- config for the admin interface -->
<admin>
<defaultQuery>*:*</defaultQuery>
<!-- configure a healthcheck file for servers behind a loadbalancer
<healthcheck type="file">server-enabled</healthcheck>
-->
</admin>
</config>

View File

@ -674,10 +674,6 @@
<!-- config for the admin interface -->
<admin>
<defaultQuery>solr</defaultQuery>
<!-- configure a healthcheck file for servers behind a loadbalancer
<healthcheck type="file">server-enabled</healthcheck>
-->
</admin>
</config>

View File

@ -487,10 +487,6 @@
<!-- config for the admin interface -->
<admin>
<defaultQuery>*:*</defaultQuery>
<!-- configure a healthcheck file for servers behind a loadbalancer
<healthcheck type="file">server-enabled</healthcheck>
-->
</admin>
</config>

View File

@ -486,10 +486,6 @@
<!-- config for the admin interface -->
<admin>
<defaultQuery>*:*</defaultQuery>
<!-- configure a healthcheck file for servers behind a loadbalancer
<healthcheck type="file">server-enabled</healthcheck>
-->
</admin>
</config>

View File

@ -311,10 +311,6 @@
<!-- config for the admin interface -->
<admin>
<defaultQuery>*:*</defaultQuery>
<!-- configure a healthcheck file for servers behind a loadbalancer
<healthcheck type="file">server-enabled</healthcheck>
-->
</admin>
</config>

View File

@ -1064,6 +1064,12 @@
<lst name="defaults">
<str name="echoParams">all</str>
</lst>
<!-- An optional feature of the PingRequestHandler is to configure the
handler with a "healthcheckFile" which can be used to enable/disable
the PingRequestHandler.
relative paths are resolved against the data dir
-->
<!-- <str name="healthcheckFile">server-enabled.txt</str> -->
</requestHandler>
<!-- Echo the request contents back to the client -->
@ -1683,13 +1689,6 @@
<!-- Legacy config for the admin interface -->
<admin>
<defaultQuery>*:*</defaultQuery>
<!-- configure a healthcheck file for servers behind a
loadbalancer
-->
<!--
<healthcheck type="file">server-enabled</healthcheck>
-->
</admin>
</config>