Merge branch 'stable-3.7' into stable-3.8
* stable-3.7:
Rename config.md to metrics.md
Add the ability to fail healthcheck by creating a fail file
Change-Id: Ic01fbf65ddfccd6abb3856600d7c4919bc2819e7
diff --git a/README.md b/README.md
index 8be0b33..bd49524 100644
--- a/README.md
+++ b/README.md
@@ -126,6 +126,25 @@
}
```
+It's also possible to artificially make the healthcheck fail by placing a file
+at a configurable path specified like:
+
+```
+[healtcheck]
+ failFileFlaPath="data/healthcheck/fail"
+```
+
+This will make the healthcheck endpoint return 500 even if the node is otherwise
+healthy. This is useful when a node needs to be removed from the pool of
+available Gerrit instance while it undergoes maintenance.
+
+**NOTE**: If the path starts with `/` then even paths outside of Gerrit's home
+will be checked. If the path starts WITHOUT `/` then the path is relative to
+Gerrit's home.
+
+**NOTE**: The file needs to be a real file rather than a symlink.
+
+
## Metrics
As for all other endpoints in Gerrit, some metrics are automatically emitted when the `/config/server/healthcheck~status`
@@ -134,4 +153,4 @@
Some additional metrics are also produced to give extra insights on their result about results and latency of healthcheck
sub component, such as jgit, reviewdb, etc.
-More information can be found in the [config.md](resources/Documentation/config.md) file.
+More information can be found in the [metrics.md](resources/Documentation/metrics.md) file.
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckConfig.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckConfig.java
index 651dbe9..1dc143c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckConfig.java
@@ -47,6 +47,7 @@
private static final int ACTIVE_WORKERS_THRESHOLD_DEFAULT = 80;
private static final String USERNAME_DEFAULT = "healthcheck";
private static final String PASSWORD_DEFAULT = "";
+ private static final String FAIL_FILE_FLAG_DEFAULT = "data/healthcheck/fail";
private static final boolean HEALTH_CHECK_ENABLED_DEFAULT = true;
private final AllProjectsName allProjectsName;
private final AllUsersName allUsersName;
@@ -135,6 +136,10 @@
return getStringWithFallback("password", healthCheckName, PASSWORD_DEFAULT);
}
+ public String getFailFileFlagPath() {
+ return getStringWithFallback("failFileFlagPath", null, FAIL_FILE_FLAG_DEFAULT);
+ }
+
public boolean healthCheckEnabled(String healthCheckName) {
if (isReplica && HEALTH_CHECK_DISABLED_FOR_REPLICAS.contains(healthCheckName)) {
return false;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/api/HealthCheckStatusEndpoint.java b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/api/HealthCheckStatusEndpoint.java
index 2d5f7de..6819ae1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/healthcheck/api/HealthCheckStatusEndpoint.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/healthcheck/api/HealthCheckStatusEndpoint.java
@@ -18,9 +18,14 @@
import com.google.gerrit.server.config.ConfigResource;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.healthcheck.HealthCheckConfig;
import com.googlesource.gerrit.plugins.healthcheck.check.GlobalHealthCheck;
import com.googlesource.gerrit.plugins.healthcheck.check.HealthCheck;
import com.googlesource.gerrit.plugins.healthcheck.check.HealthCheck.Result;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
@@ -29,14 +34,21 @@
private final GlobalHealthCheck healthChecks;
+ private final String failedFileFlagPath;
+
@Inject
- public HealthCheckStatusEndpoint(GlobalHealthCheck healthChecks) {
+ public HealthCheckStatusEndpoint(GlobalHealthCheck healthChecks, HealthCheckConfig config) {
this.healthChecks = healthChecks;
+ this.failedFileFlagPath = config.getFailFileFlagPath();
}
@Override
public Response<Map<String, Object>> apply(ConfigResource resource)
throws AuthException, BadRequestException, ResourceConflictException, Exception {
+ if (failFlagFileExists()) {
+ return Response.withStatusCode(
+ HttpServletResponse.SC_INTERNAL_SERVER_ERROR, Map.of("reason", "Fail Flag File exists"));
+ }
HealthCheck.StatusSummary globalHealthCheckStatus = healthChecks.run();
Map<String, Object> result = globalHealthCheckStatus.subChecks;
@@ -50,4 +62,13 @@
? HttpServletResponse.SC_INTERNAL_SERVER_ERROR
: HttpServletResponse.SC_OK;
}
+
+ private boolean failFlagFileExists() throws IOException {
+ File file = new File(failedFileFlagPath);
+ try (InputStream targetStream = new FileInputStream(file)) {
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckIT.java b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckIT.java
index e9901a9..1cd37f4 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/healthcheck/HealthCheckIT.java
@@ -31,7 +31,9 @@
import com.google.gson.JsonObject;
import com.google.inject.Key;
import com.googlesource.gerrit.plugins.healthcheck.check.HealthCheckNames;
+import java.io.File;
import java.io.IOException;
+import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.junit.Before;
import org.junit.Test;
@@ -45,6 +47,7 @@
Gson gson = new Gson();
HealthCheckConfig config;
String healthCheckUriPath;
+ String failFilePath;
@Override
@Before
@@ -52,6 +55,9 @@
super.setUpTestPlugin();
config = plugin.getSysInjector().getInstance(HealthCheckConfig.class);
+ failFilePath = "/tmp/fail";
+ new File(failFilePath).delete();
+
int numChanges = config.getLimit(HealthCheckNames.QUERYCHANGES);
for (int i = 0; i < numChanges; i++) {
createChange("refs/for/master");
@@ -195,6 +201,25 @@
assertCheckResult(getResponseJson(resp), ACTIVEWORKERS, "passed");
}
+ @Test
+ public void shouldReturnFailedIfFailFlagFileExists() throws Exception {
+ setFailFlagFilePath(failFilePath);
+ createFailFileFlag(failFilePath);
+ getHealthCheckStatus();
+ RestResponse resp = getHealthCheckStatus();
+ resp.assertStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+
+ JsonObject respBody = getResponseJson(resp);
+ assertThat(respBody.has("reason")).isTrue();
+ assertThat(respBody.get("reason").getAsString()).isEqualTo("Fail Flag File exists");
+ }
+
+ private void createFailFileFlag(String path) throws IOException {
+ File file = new File(path);
+ file.createNewFile();
+ file.deleteOnExit();
+ }
+
private RestResponse getHealthCheckStatus() throws IOException {
return adminRestSession.get(healthCheckUriPath);
}
@@ -214,6 +239,10 @@
config.fromText(String.format("[healthcheck \"%s\"]\n" + "enabled = false", check));
}
+ private void setFailFlagFilePath(String path) throws ConfigInvalidException {
+ config.fromText(String.format("[healthcheck]\n" + "failFileFlagPath = %s", path));
+ }
+
private JsonObject getResponseJson(RestResponse resp) throws IOException {
JsonObject respPayload = gson.fromJson(resp.getReader(), JsonObject.class);
return respPayload;