diff --git a/remote-api/src/main/java/org/alfresco/rest/api/probes/ProbeEntityResource.java b/remote-api/src/main/java/org/alfresco/rest/api/probes/ProbeEntityResource.java
index d470efad26..9b6b5c0dbe 100644
--- a/remote-api/src/main/java/org/alfresco/rest/api/probes/ProbeEntityResource.java
+++ b/remote-api/src/main/java/org/alfresco/rest/api/probes/ProbeEntityResource.java
@@ -23,8 +23,10 @@
* along with Alfresco. If not, see .
* #L%
*/
+
package org.alfresco.rest.api.probes;
+import org.alfresco.repo.admin.RepoHealthChecker;
import org.alfresco.rest.api.discovery.DiscoveryApiWebscript;
import org.alfresco.rest.api.model.Probe;
import org.alfresco.rest.framework.WebApiDescription;
@@ -35,29 +37,26 @@ import org.alfresco.rest.framework.core.exceptions.ServiceUnavailableException;
import org.alfresco.rest.framework.resource.EntityResource;
import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction;
import org.alfresco.rest.framework.resource.parameters.Parameters;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* An implementation of an Entity Resource for Probes.
*/
-@EntityResource(name="probes", title = "Probes")
-public class ProbeEntityResource implements EntityResourceAction.ReadById
+@EntityResource(name = "probes", title = "Probes") public class ProbeEntityResource
+ implements EntityResourceAction.ReadById
{
- public static final String LIVE = "-live-";
- public static final String READY = "-ready-";
-
public static final long CHECK_PERIOD = 10 * 1000; // Maximum of only one checkResult every 10 seconds.
- protected static Log logger = LogFactory.getLog(ProbeEntityResource.class);;
-
- private long nextCheckTime = 0;
+ private final static Logger logger = LoggerFactory.getLogger(ProbeEntityResource.class);
+ private final Object lock = new Object();
+ private final Probe liveProbe = new Probe("liveProbe: Success - Tested");
+ private long lastCheckTime = 0;
private Boolean checkResult;
- private Boolean checking = false;
- private boolean readySent;
-
private DiscoveryApiWebscript discovery;
+ private RepoHealthChecker repoHealthChecker;
+
public DiscoveryApiWebscript setDiscovery(DiscoveryApiWebscript discovery)
{
DiscoveryApiWebscript result = this.discovery;
@@ -65,145 +64,141 @@ public class ProbeEntityResource implements EntityResourceAction.ReadById
return result;
}
+ public void setRepoHealthChecker(RepoHealthChecker repoHealthChecker)
+ {
+ this.repoHealthChecker = repoHealthChecker;
+ }
+
/**
* Returns a status code of 200 for okay. The probe contains little information for security reasons.
- *
* Note: does *not* require authenticated access, so limits the amount of work performed to avoid a DDOS.
*/
- @Override
- @WebApiDescription(title="Get probe status", description = "Returns 200 if valid")
- @WebApiParam(name = "probeName", title = "The probe's name")
- @WebApiNoAuth
- public Probe readById(String name, Parameters parameters)
+ @Override @WebApiDescription(title = "Get probe status", description = "Returns 200 if valid") @WebApiParam(name = "probeName", title = "The probe's name") @WebApiNoAuth public Probe readById(
+ String name, Parameters parameters)
{
- boolean isLiveProbe = LIVE.equalsIgnoreCase(name);
- if (!isLiveProbe && !READY.equalsIgnoreCase(name))
+ ProbeType probeType = ProbeType.fromString(name);
+ Probe probeResponse = null;
+
+ switch (probeType)
{
- throw new InvalidArgumentException("Bad probe name");
+ case LIVE:
+ probeResponse = liveProbe;
+ break;
+ case READY:
+ String message = doReadyCheck();
+ probeResponse = new Probe(message);
+ break;
+ case UNKNOWN:
+ throw new InvalidArgumentException("Bad probe name");
}
- String message = doCheckOrNothing(isLiveProbe);
- return new Probe(message);
+ return probeResponse;
}
// We don't want to be doing checks all the time or holding monitors for a long time to avoid a DDOS.
- public String doCheckOrNothing(boolean isLiveProbe)
+ public String doReadyCheck()
{
- boolean doCheck = false;
- long now = 0;
- boolean result;
- String message = "No test";
- boolean logInfo = false;
- synchronized(checking)
+
+ synchronized (lock)
{
- // Initially ready needs to be false so we don't get requests and live true so the pod is not killed.
- if (checkResult == null)
+ String message;
+ long now = System.currentTimeMillis();
+
+ if (checkResult == null || isAfterCheckPeriod(now))
{
- result = isLiveProbe;
+ try
+ {
+ performReadinessCheck();
+ checkResult = true;
+ }
+ catch (Exception e)
+ {
+ checkResult = false;
+ logger.debug("Exception during readiness check", e);
+ }
+ finally
+ {
+
+ setLastCheckTime(now);
+ message = getMessage(checkResult, "Tested");
+ logger.info(message);
+
+ }
}
else
{
- result = checkResult;
+ // if no check is performed, use previous check result
+ message = getMessage(checkResult, "No test");
+ logger.debug(message);
+
+ }
+ if (checkResult)
+ {
+ return message;
}
- if (checking) // Is another thread is checking?
- {
- if (!readySent && result && !isLiveProbe)
- {
- readySent = true;
- logInfo = true;
- }
- }
- else // This thread will do a check
- {
- now = System.currentTimeMillis();
- if (checkResult == null || nextCheckTime <= now)
- {
- doCheck = true;
- checking = true;
- }
- }
+ throw new ServiceUnavailableException(message);
}
- if (doCheck)
- {
- try
- {
- message = "Tested";
- doCheck(isLiveProbe);
- result = true;
- }
- catch (Exception e)
- {
- result = false;
- }
- finally
- {
- synchronized (checking)
- {
- checking = false;
- checkResult = result;
- setNextCheckTime(now);
- if (result && !readySent && !isLiveProbe) // Are we initially ready
- {
- readySent = true;
- logInfo = true;
- }
- else if (!result && (isLiveProbe || readySent)) // Are we sick
- {
- logInfo = true;
- }
- }
- }
- }
-
- message = getMessage(isLiveProbe, result, message);
-
- if (logInfo)
- {
- logger.info(message);
- }
- else
- {
- logger.debug(message);
- }
-
- if (result)
- {
- return message;
- }
- throw new ServiceUnavailableException(message);
}
- private String getMessage(boolean isLiveProbe, boolean result, String message)
+ private String getMessage(boolean result, String message)
{
- return (isLiveProbe ? "liveProbe" : "readyProbe")+": "+
- (result ? "Success" : "Failure") +
- " - "+message;
+
+ return "readyProbe: " + (result ? "Success" : "Failure") + " - " + message;
+
}
- private void doCheck(boolean isLiveProbe)
+ private void performReadinessCheck()
{
+
discovery.getRepositoryInfo();
+ repoHealthChecker.checkDatabase();
+ logger.debug("All checks complete");
+
}
- private void setNextCheckTime(long now)
+ private void setLastCheckTime(long time)
{
- long oldValue = nextCheckTime;
- if (nextCheckTime == 0)
+ this.lastCheckTime = time;
+ long nextCheckTime = lastCheckTime + CHECK_PERIOD;
+
+ logger.trace("nextCheckTime: {} (+{} secs)", nextCheckTime, ((CHECK_PERIOD) / 1000));
+
+ }
+
+ private boolean isAfterCheckPeriod(long currentTime)
+ {
+ return ((currentTime - lastCheckTime) >= CHECK_PERIOD);
+ }
+
+ public enum ProbeType
+ {
+ LIVE("-live-"), READY("-ready-"), UNKNOWN("");
+
+ String value;
+
+ ProbeType(String strValue)
{
- nextCheckTime = (now / 60000) * 60000;
+ value = strValue;
}
- do
+ public static ProbeType fromString(String text)
{
- nextCheckTime += CHECK_PERIOD;
+ for (ProbeType p : ProbeType.values())
+ {
+ if (p.value.equalsIgnoreCase(text))
+ {
+ return p;
+ }
+ }
+ return UNKNOWN;
}
- while (nextCheckTime <= now);
- if (logger.isTraceEnabled())
+ public String getValue()
{
- logger.trace("nextCheckTime: " + nextCheckTime + " (+" + ((nextCheckTime - oldValue) / 1000) + " secs)");
+ return value;
}
}
+
}
diff --git a/remote-api/src/main/resources/alfresco/public-rest-context.xml b/remote-api/src/main/resources/alfresco/public-rest-context.xml
index 030e3bcf48..540019fa74 100644
--- a/remote-api/src/main/resources/alfresco/public-rest-context.xml
+++ b/remote-api/src/main/resources/alfresco/public-rest-context.xml
@@ -1075,6 +1075,7 @@
+
diff --git a/remote-api/src/test/java/org/alfresco/rest/api/tests/ProbeApiTest.java b/remote-api/src/test/java/org/alfresco/rest/api/tests/ProbeApiTest.java
index afad7aa219..d77d35b28a 100644
--- a/remote-api/src/test/java/org/alfresco/rest/api/tests/ProbeApiTest.java
+++ b/remote-api/src/test/java/org/alfresco/rest/api/tests/ProbeApiTest.java
@@ -26,21 +26,26 @@
package org.alfresco.rest.api.tests;
import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.repo.admin.RepoHealthChecker;
import org.alfresco.rest.api.discovery.DiscoveryApiWebscript;
import org.alfresco.rest.api.probes.ProbeEntityResource;
import org.alfresco.rest.api.tests.client.HttpResponse;
import org.json.simple.JSONObject;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import static org.alfresco.rest.api.probes.ProbeEntityResource.*;
+import static org.alfresco.rest.api.probes.ProbeEntityResource.ProbeType.LIVE;
+import static org.alfresco.rest.api.probes.ProbeEntityResource.ProbeType.READY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.lenient;
/**
* V1 REST API tests for Probes (Live and Ready)
@@ -53,7 +58,6 @@ import static org.mockito.Mockito.when;
public class ProbeApiTest extends AbstractBaseApiTest
{
private static final boolean OK = true;
- private static final boolean ERROR = false;
private ProbeEntityResource probe;
private DiscoveryApiWebscript origDiscovery;
@@ -64,6 +68,9 @@ public class ProbeApiTest extends AbstractBaseApiTest
@Mock
private DiscoveryApiWebscript badDiscovery;
+ @Mock
+ private RepoHealthChecker repoHealthChecker;
+
@Before
@Override
public void setup() throws Exception
@@ -73,7 +80,9 @@ public class ProbeApiTest extends AbstractBaseApiTest
String beanName = ProbeEntityResource.class.getCanonicalName()+".get";
probe = applicationContext.getBean(beanName, ProbeEntityResource.class);
- when(badDiscovery.getRepositoryInfo()).thenThrow(AlfrescoRuntimeException.class);
+ lenient().when(badDiscovery.getRepositoryInfo()).thenThrow(AlfrescoRuntimeException.class);
+ Mockito.doNothing().when(repoHealthChecker).checkDatabase();
+ probe.setRepoHealthChecker(repoHealthChecker);
origDiscovery = probe.setDiscovery(badDiscovery);
}
@@ -91,7 +100,7 @@ public class ProbeApiTest extends AbstractBaseApiTest
return "public";
}
- private void assertResponse(String probeName, Boolean ready, String expected, int expectedStatus) throws Exception
+ private void assertResponse(ProbeType probeType, Boolean ready, String expected, int expectedStatus) throws Exception
{
String[] keys = expectedStatus == 200
? new String[]{"entry", "message"}
@@ -103,7 +112,7 @@ public class ProbeApiTest extends AbstractBaseApiTest
? goodDiscovery
: badDiscovery);
- HttpResponse response = getSingle(ProbeEntityResource.class, probeName, null, expectedStatus);
+ HttpResponse response = getSingle(ProbeEntityResource.class, probeType.getValue(), null, expectedStatus);
Object object = response.getJsonResponse();
for (String key: keys)
{
@@ -128,32 +137,29 @@ public class ProbeApiTest extends AbstractBaseApiTest
public void testProbes() throws Exception
{
// Live first
- assertResponse(LIVE, ERROR, "liveProbe: Failure - Tested", 503);
- assertResponse(LIVE, null, "liveProbe: Failure - No test", 503); // Need to wait 10 seconds.
- assertResponse(LIVE, null, "liveProbe: Failure - No test", 503);
- assertResponse(READY, null, "readyProbe: Failure - No test", 503);
+ assertResponse(LIVE, OK, "liveProbe: Success - Tested", 200);
+ assertResponse(READY, null, "readyProbe: Failure - Tested", 503);
+
+ Thread.currentThread().sleep(CHECK_PERIOD);
+ assertResponse(READY, OK, "readyProbe: Success - Tested", 200);
+ assertResponse(READY, null, "readyProbe: Success - No test", 200);
Thread.currentThread().sleep(CHECK_PERIOD);
assertResponse(LIVE, OK, "liveProbe: Success - Tested", 200);
- assertResponse(LIVE, null, "liveProbe: Success - No test", 200);
- assertResponse(READY, null, "readyProbe: Success - No test", 200);
- assertResponse(LIVE, null, "liveProbe: Success - No test", 200);
-
- Thread.currentThread().sleep(CHECK_PERIOD);
- assertResponse(LIVE, ERROR, "liveProbe: Failure - Tested", 503);
- assertResponse(LIVE, null, "liveProbe: Failure - No test", 503);
- assertResponse(READY, null, "readyProbe: Failure - No test", 503);
-
-
+ assertResponse(READY, null, "readyProbe: Failure - Tested", 503);
// Ready first
Thread.currentThread().sleep(CHECK_PERIOD);
assertResponse(READY, OK, "readyProbe: Success - Tested", 200);
- assertResponse(LIVE, null, "liveProbe: Success - No test", 200);
+ assertResponse(LIVE, OK, "liveProbe: Success - Tested", 200);
assertResponse(READY, null, "readyProbe: Success - No test", 200);
+ // check db failure
Thread.currentThread().sleep(CHECK_PERIOD);
- assertResponse(READY, ERROR, "readyProbe: Failure - Tested", 503);
- assertResponse(LIVE, null, "liveProbe: Failure - No test", 503);
+ Mockito.doThrow(AlfrescoRuntimeException.class).when(repoHealthChecker).checkDatabase();
+ assertResponse(READY, OK, "readyProbe: Failure - Tested", 503);
+ assertResponse(READY, OK, "readyProbe: Failure - No test", 503);
+
}
+
}
diff --git a/repository/src/main/java/org/alfresco/repo/admin/RepoHealthChecker.java b/repository/src/main/java/org/alfresco/repo/admin/RepoHealthChecker.java
new file mode 100644
index 0000000000..376ebac89b
--- /dev/null
+++ b/repository/src/main/java/org/alfresco/repo/admin/RepoHealthChecker.java
@@ -0,0 +1,47 @@
+/*
+ * #%L
+ * Alfresco Remote API
+ * %%
+ * Copyright (C) 2005 - 2018 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.repo.admin;
+
+import org.alfresco.repo.domain.schema.DataSourceCheck;
+
+/**
+ * A utility service to do the database and other health checks.
+ */
+public class RepoHealthChecker
+{
+ private final DataSourceCheck dataSourceCheck;
+
+ public RepoHealthChecker(DataSourceCheck dataSourceCheck)
+ {
+ this.dataSourceCheck = dataSourceCheck;
+ }
+
+ public void checkDatabase()
+ {
+ dataSourceCheck.init();
+ }
+
+}
diff --git a/repository/src/main/resources/alfresco/repo-admin-context.xml b/repository/src/main/resources/alfresco/repo-admin-context.xml
index dc78e5dfa8..ed394ac90f 100644
--- a/repository/src/main/resources/alfresco/repo-admin-context.xml
+++ b/repository/src/main/resources/alfresco/repo-admin-context.xml
@@ -58,5 +58,8 @@
path
+
+
+