Merge branch 'stable-2.14' into stable-2.15

* stable-2.14:
  Allow to disable health check endpoint
  AbstractIndexRestApiServlet: Log operation type in lower case
  Make debug logging consistent in forwarder and cache servlet
  Use Logger's built-in string formatting
  Restrict endpoints to change health status to admins
  Add unit tests for HealthServlet
  Initial implementation of health servlet

Change-Id: Idb83c5beac343490b8cb7ea031b2fb40863c7f25
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
index aba6c05..32e49af 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
@@ -84,6 +84,11 @@
   // common parameters to cache, event index and websession sections
   static final String SYNCHRONIZE_KEY = "synchronize";
 
+  // health check section
+  static final String HEALTH_CHECK_SECTION = "healthCheck";
+  static final String ENABLE_KEY = "enable";
+  static final boolean DEFAULT_HEALTH_CHECK_ENABLED = true;
+
   // websession section
   static final String WEBSESSION_SECTION = "websession";
   static final String CLEANUP_INTERVAL_KEY = "cleanupInterval";
@@ -110,6 +115,7 @@
   private final Websession websession;
   private PeerInfoStatic peerInfoStatic;
   private PeerInfoJGroups peerInfoJGroups;
+  private HealthCheck healthCheck;
 
   public enum PeerInfoStrategy {
     JGROUPS,
@@ -138,6 +144,7 @@
     event = new Event(cfg);
     index = new Index(cfg);
     websession = new Websession(cfg);
+    healthCheck = new HealthCheck(cfg);
   }
 
   public Main main() {
@@ -180,12 +187,16 @@
     return websession;
   }
 
+  public HealthCheck healthCheck() {
+    return healthCheck;
+  }
+
   private static int getInt(Config cfg, String section, String name, int defaultValue) {
     try {
       return cfg.getInt(section, name, defaultValue);
     } catch (IllegalArgumentException e) {
-      log.error(String.format("invalid value for %s; using default value %d", name, defaultValue));
-      log.debug("Failed to retrieve integer value: " + e.getMessage(), e);
+      log.error("invalid value for {}; using default value {}", name, defaultValue);
+      log.debug("Failed to retrieve integer value: {}", e.getMessage(), e);
       return defaultValue;
     }
   }
@@ -194,8 +205,8 @@
     try {
       return cfg.getBoolean(section, name, defaultValue);
     } catch (IllegalArgumentException e) {
-      log.error(String.format("invalid value for %s; using default value %s", name, defaultValue));
-      log.debug("Failed to retrieve boolean value: " + e.getMessage(), e);
+      log.error("invalid value for {}; using default value {}", name, defaultValue);
+      log.debug("Failed to retrieve boolean value: {}", e.getMessage(), e);
       return defaultValue;
     }
   }
@@ -403,4 +414,16 @@
       return cleanupInterval;
     }
   }
+
+  public static class HealthCheck {
+    private final boolean enabled;
+
+    private HealthCheck(Config cfg) {
+      enabled = cfg.getBoolean(HEALTH_CHECK_SECTION, ENABLE_KEY, DEFAULT_HEALTH_CHECK_ENABLED);
+    }
+
+    public boolean enabled() {
+      return enabled;
+    }
+  }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/HttpModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/HttpModule.java
index 57d1d91..3209b1b 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/HttpModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/HttpModule.java
@@ -15,6 +15,7 @@
 package com.ericsson.gerrit.plugins.highavailability;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.RestForwarderServletModule;
+import com.ericsson.gerrit.plugins.highavailability.health.HealthServletModule;
 import com.ericsson.gerrit.plugins.highavailability.websession.file.FileBasedWebsessionModule;
 import com.google.gerrit.httpd.plugins.HttpPluginModule;
 import com.google.inject.Inject;
@@ -30,6 +31,9 @@
   @Override
   protected void configureServlets() {
     install(new RestForwarderServletModule());
+    if (config.healthCheck().enabled()) {
+      install(new HealthServletModule());
+    }
     if (config.websession().synchronize()) {
       install(new FileBasedWebsessionModule());
     }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java
index 5b7cc60..ddd1ac2 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java
@@ -24,6 +24,7 @@
 import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
 import java.util.HashMap;
+import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 import javax.servlet.ServletException;
@@ -79,7 +80,7 @@
     rsp.setCharacterEncoding(UTF_8.name());
     String path = req.getPathInfo();
     T id = parse(path.substring(path.lastIndexOf('/') + 1));
-    logger.debug("{} {} {}", operation.name(), type, id);
+    logger.debug("{} {} {}", operation.name().toLowerCase(Locale.US), type, id);
     try {
       Context.setForwardedEvent(true);
       AtomicInteger idLock = getAndIncrementIdLock(id);
@@ -92,7 +93,7 @@
       rsp.setStatus(SC_NO_CONTENT);
     } catch (IOException e) {
       sendError(rsp, SC_CONFLICT, e.getMessage());
-      logger.error(String.format("Unable to update %s index", type), e);
+      logger.error("Unable to update {} index", type, e);
     } catch (OrmException e) {
       String msg = String.format("Error trying to find %s \n", type);
       sendError(rsp, SC_NOT_FOUND, msg);
@@ -127,7 +128,7 @@
     try {
       rsp.sendError(statusCode, message);
     } catch (IOException e) {
-      logger.error("Failed to send error messsage: " + e.getMessage(), e);
+      logger.error("Failed to send error messsage: {}", e.getMessage(), e);
     }
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServlet.java
index 253ff4a..86483c9 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServlet.java
@@ -61,7 +61,7 @@
       Cache<?, ?> cache = cacheMap.get(cacheKey.pluginName, cacheKey.cacheName);
       if (cache == null) {
         String msg = String.format("cache %s not found", cacheName);
-        logger.error("Failed to process eviction request: " + msg);
+        logger.error("Failed to process eviction request: {}", msg);
         sendError(rsp, SC_BAD_REQUEST, msg);
       } else {
         Context.setForwardedEvent(true);
@@ -69,7 +69,7 @@
         rsp.setStatus(SC_NO_CONTENT);
       }
     } catch (IOException e) {
-      logger.error("Failed to process eviction request: " + e.getMessage(), e);
+      logger.error("Failed to process eviction request: {}", e.getMessage(), e);
       sendError(rsp, SC_BAD_REQUEST, e.getMessage());
     } finally {
       Context.unsetForwardedEvent();
@@ -100,7 +100,7 @@
     try {
       rsp.sendError(statusCode, message);
     } catch (IOException e) {
-      logger.error("Failed to send error messsage: " + e.getMessage(), e);
+      logger.error("Failed to send error messsage: {}", e.getMessage(), e);
     }
   }
 
@@ -108,9 +108,10 @@
     if (Constants.PROJECT_LIST.equals(cacheName)) {
       // One key is holding the list of projects
       cache.invalidateAll();
+      logger.debug("Invalidated cache {}", cacheName);
     } else {
       cache.invalidate(key);
+      logger.debug("Invalidated cache {}[{}]", cacheName, key);
     }
-    logger.debug("Invalidated " + cacheName);
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java
index 92f3d0d..e25e7eb 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java
@@ -95,7 +95,7 @@
     try {
       rsp.sendError(statusCode, message);
     } catch (IOException e) {
-      logger.error("Failed to send error messsage: " + e.getMessage(), e);
+      logger.error("Failed to send error messsage: {}", e.getMessage(), e);
     }
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
index c1c46d9..01ea665 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
@@ -105,7 +105,7 @@
 
   @Override
   public boolean evict(final String cacheName, final Object key) {
-    return new Request("evict for cache " + cacheName + "[" + key + "]") {
+    return new Request("invalidate cache " + cacheName + "[" + key + "]") {
       @Override
       HttpResult send() throws IOException {
         String json = GsonParser.toJson(cacheName, key);
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/health/HealthServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/health/HealthServlet.java
new file mode 100644
index 0000000..d01382b
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/health/HealthServlet.java
@@ -0,0 +1,88 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.ericsson.gerrit.plugins.highavailability.health;
+
+import static com.google.gerrit.server.permissions.GlobalPermission.ADMINISTRATE_SERVER;
+import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
+import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
+import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
+
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+public class HealthServlet extends HttpServlet {
+  private static final Logger log = LoggerFactory.getLogger(HealthServlet.class);
+  private static final long serialVersionUID = -1L;
+
+  private final Provider<CurrentUser> currentUserProvider;
+  private final PermissionBackend permissionBackend;
+  private boolean healthy;
+
+  @Inject
+  HealthServlet(Provider<CurrentUser> currentUserProvider, PermissionBackend permissionBackend) {
+    this.currentUserProvider = currentUserProvider;
+    this.permissionBackend = permissionBackend;
+    this.healthy = true;
+  }
+
+  @Override
+  protected void doPost(HttpServletRequest req, HttpServletResponse rsp) {
+    if (!permissionBackend.user(currentUserProvider.get()).testOrFalse(ADMINISTRATE_SERVER)) {
+      sendError(rsp, SC_FORBIDDEN);
+      return;
+    }
+    this.healthy = true;
+    rsp.setStatus(SC_NO_CONTENT);
+  }
+
+  @Override
+  protected void doDelete(HttpServletRequest req, HttpServletResponse rsp) {
+    if (!permissionBackend.user(currentUserProvider.get()).testOrFalse(ADMINISTRATE_SERVER)) {
+      sendError(rsp, SC_FORBIDDEN);
+      return;
+    }
+    this.healthy = false;
+    rsp.setStatus(SC_NO_CONTENT);
+  }
+
+  @Override
+  protected void doGet(HttpServletRequest req, HttpServletResponse rsp) {
+    if (healthy) {
+      rsp.setStatus(SC_NO_CONTENT);
+    } else {
+      sendError(rsp, SC_SERVICE_UNAVAILABLE);
+    }
+  }
+
+  private void sendError(HttpServletResponse rsp, int statusCode) {
+    try {
+      rsp.sendError(statusCode);
+    } catch (IOException e) {
+      rsp.setStatus(SC_INTERNAL_SERVER_ERROR);
+      log.error("Failed to send error response", e);
+    }
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/health/HealthServletModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/health/HealthServletModule.java
new file mode 100644
index 0000000..f3318a9
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/health/HealthServletModule.java
@@ -0,0 +1,24 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.ericsson.gerrit.plugins.highavailability.health;
+
+import com.google.gerrit.httpd.plugins.HttpPluginModule;
+
+public class HealthServletModule extends HttpPluginModule {
+  @Override
+  protected void configureServlets() {
+    serve("/health").with(HealthServlet.class);
+  }
+}
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 42ce673..2289c83 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -30,6 +30,8 @@
 [http]
 :  user = username
 :  password = password
+[healthcheck]
+:  enable = true
 
 main.sharedDirectory
 :   Path to a directory accessible from both master instances.
@@ -152,3 +154,6 @@
 * y, year, years (`1 year` is treated as `365 days`)
 If a time unit suffix is not specified, `hours` is assumed.
 Defaults to 24 hours.
+
+healthcheck.enable
+:   Whether to enable the health check endpoint. Defaults to 'true'.
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
index e9b39c7..42102c7 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
@@ -20,6 +20,7 @@
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.CONNECTION_TIMEOUT_KEY;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_CLEANUP_INTERVAL_MS;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_CLUSTER_NAME;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_HEALTH_CHECK_ENABLED;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_MAX_TRIES;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_PEER_INFO_STRATEGY;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_RETRY_INTERVAL;
@@ -27,7 +28,9 @@
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_SYNCHRONIZE;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_THREAD_POOL_SIZE;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_TIMEOUT_MS;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.ENABLE_KEY;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.EVENT_SECTION;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.HEALTH_CHECK_SECTION;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.HTTP_SECTION;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.INDEX_SECTION;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.JGROUPS_SECTION;
@@ -533,4 +536,12 @@
       assertThat(matcher.matches(cache)).isFalse();
     }
   }
+
+  @Test
+  public void testHealthCheckEnabled() throws Exception {
+    when(configMock.getBoolean(HEALTH_CHECK_SECTION, ENABLE_KEY, DEFAULT_HEALTH_CHECK_ENABLED))
+        .thenReturn(false);
+    initializeConfiguration();
+    assertThat(configuration.healthCheck().enabled()).isFalse();
+  }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/health/HealthServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/health/HealthServletTest.java
new file mode 100644
index 0000000..b8a69cb
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/health/HealthServletTest.java
@@ -0,0 +1,145 @@
+// Copyright (C) 2017 Ericsson
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.ericsson.gerrit.plugins.highavailability.health;
+
+import static com.google.gerrit.server.permissions.GlobalPermission.ADMINISTRATE_SERVER;
+import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
+import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
+import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackend.WithUser;
+import com.google.inject.Provider;
+import java.io.IOException;
+import javax.servlet.http.HttpServletResponse;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class HealthServletTest {
+
+  @Mock private Provider<CurrentUser> currentUserProviderMock;
+  @Mock private CurrentUser currentUserMock;
+  @Mock private PermissionBackend permissionBackendMock;
+  @Mock private WithUser withUserMock;
+
+  private HealthServlet servlet;
+
+  @Before
+  public void setUp() throws Exception {
+    when(currentUserProviderMock.get()).thenReturn(currentUserMock);
+    when(permissionBackendMock.user(currentUserMock)).thenReturn(withUserMock);
+    when(withUserMock.testOrFalse(ADMINISTRATE_SERVER)).thenReturn(true);
+    servlet = new HealthServlet(currentUserProviderMock, permissionBackendMock);
+  }
+
+  @Test
+  public void shouldBeHealthyByDefault() {
+    assertIsHealthy();
+  }
+
+  @Test
+  public void testTransitionToUnhealthy() throws IOException {
+    assertIsHealthy();
+
+    // transition from healthy to unhealthy
+    HttpServletResponse responseMock = mock(HttpServletResponse.class);
+    servlet.doDelete(null, responseMock);
+    verify(responseMock).setStatus(SC_NO_CONTENT);
+    assertIsUnhealthy();
+
+    // setting to unhealthy again should not change anything
+    responseMock = mock(HttpServletResponse.class);
+    servlet.doDelete(null, responseMock);
+    verify(responseMock).setStatus(SC_NO_CONTENT);
+    assertIsUnhealthy();
+  }
+
+  @Test
+  public void testTransitionToUnhealthyByNonAdmins() throws IOException {
+    assertIsHealthy();
+
+    when(withUserMock.testOrFalse(ADMINISTRATE_SERVER)).thenReturn(false);
+    HttpServletResponse responseMock = mock(HttpServletResponse.class);
+    servlet.doDelete(null, responseMock);
+    verify(responseMock).sendError(SC_FORBIDDEN);
+    assertIsHealthy();
+  }
+
+  @Test
+  public void testTransitionToHealty() throws IOException {
+    // first, mark as unhealthy
+    servlet.doDelete(null, mock(HttpServletResponse.class));
+    assertIsUnhealthy();
+
+    // transition from unhealthy to healthy
+    HttpServletResponse responseMock = mock(HttpServletResponse.class);
+    servlet.doPost(null, responseMock);
+    verify(responseMock).setStatus(SC_NO_CONTENT);
+    assertIsHealthy();
+
+    // setting to healthy again should not change anything
+    responseMock = mock(HttpServletResponse.class);
+    servlet.doPost(null, responseMock);
+    verify(responseMock).setStatus(SC_NO_CONTENT);
+    assertIsHealthy();
+  }
+
+  @Test
+  public void testTransitionToHealthyByNonAdmins() throws IOException {
+    // first, mark as unhealthy
+    servlet.doDelete(null, mock(HttpServletResponse.class));
+    assertIsUnhealthy();
+
+    when(withUserMock.testOrFalse(ADMINISTRATE_SERVER)).thenReturn(false);
+    HttpServletResponse responseMock = mock(HttpServletResponse.class);
+    servlet.doPost(null, responseMock);
+    verify(responseMock).sendError(SC_FORBIDDEN);
+    assertIsUnhealthy();
+  }
+
+  @Test
+  public void testErrorWhileSendingUnhealthyResponse() throws IOException {
+    HttpServletResponse responseMock = mock(HttpServletResponse.class);
+    servlet.doDelete(null, responseMock);
+    verify(responseMock).setStatus(SC_NO_CONTENT);
+
+    responseMock = mock(HttpServletResponse.class);
+    doThrow(new IOException("someError")).when(responseMock).sendError(SC_SERVICE_UNAVAILABLE);
+    servlet.doGet(null, responseMock);
+    verify(responseMock).setStatus(SC_INTERNAL_SERVER_ERROR);
+  }
+
+  private void assertIsHealthy() {
+    HttpServletResponse responseMock = mock(HttpServletResponse.class);
+    servlet.doGet(null, responseMock);
+    verify(responseMock).setStatus(SC_NO_CONTENT);
+  }
+
+  private void assertIsUnhealthy() throws IOException {
+    HttpServletResponse responseMock = mock(HttpServletResponse.class);
+    servlet.doGet(null, responseMock);
+    verify(responseMock).sendError(SC_SERVICE_UNAVAILABLE);
+  }
+}