ISSUE #2310: Adds the autorecovery status service.
Descriptions of the changes in this PR:
### Motivation
It would be convenient to support enable, disable, or check the status of autorecovery with REST API, just like what does in the toggle command.
### Changes
Adds an API to enable, disable, and get the current status of autorecovery at `/api/v1/autorecovery/status`.
Master Issue: #2310
Reviewers: Sijie Guo <None>, Enrico Olivelli <eolivelli@gmail.com>, Matteo Minardi <minardi.matteo@hotmail.it>
This closes #2313 from fantapsody/autorecovery_status, closes #2310
diff --git a/bookkeeper-http/http-server/src/main/java/org/apache/bookkeeper/http/HttpRouter.java b/bookkeeper-http/http-server/src/main/java/org/apache/bookkeeper/http/HttpRouter.java
index 99d5be6..91dcb88 100644
--- a/bookkeeper-http/http-server/src/main/java/org/apache/bookkeeper/http/HttpRouter.java
+++ b/bookkeeper-http/http-server/src/main/java/org/apache/bookkeeper/http/HttpRouter.java
@@ -50,6 +50,7 @@
public static final String BOOKIE_STATE = "/api/v1/bookie/state";
public static final String BOOKIE_IS_READY = "/api/v1/bookie/is_ready";
// autorecovery
+ public static final String AUTORECOVERY_STATUS = "/api/v1/autorecovery/status";
public static final String RECOVERY_BOOKIE = "/api/v1/autorecovery/bookie";
public static final String LIST_UNDER_REPLICATED_LEDGER = "/api/v1/autorecovery/list_under_replicated_ledger";
public static final String WHO_IS_AUDITOR = "/api/v1/autorecovery/who_is_auditor";
@@ -83,6 +84,8 @@
this.endpointHandlers.put(BOOKIE_IS_READY, handlerFactory.newHandler(HttpServer.ApiType.BOOKIE_IS_READY));
// autorecovery
+ this.endpointHandlers.put(AUTORECOVERY_STATUS, handlerFactory
+ .newHandler(HttpServer.ApiType.AUTORECOVERY_STATUS));
this.endpointHandlers.put(RECOVERY_BOOKIE, handlerFactory.newHandler(HttpServer.ApiType.RECOVERY_BOOKIE));
this.endpointHandlers.put(LIST_UNDER_REPLICATED_LEDGER,
handlerFactory.newHandler(HttpServer.ApiType.LIST_UNDER_REPLICATED_LEDGER));
diff --git a/bookkeeper-http/http-server/src/main/java/org/apache/bookkeeper/http/HttpServer.java b/bookkeeper-http/http-server/src/main/java/org/apache/bookkeeper/http/HttpServer.java
index c694a07..82c9c33 100644
--- a/bookkeeper-http/http-server/src/main/java/org/apache/bookkeeper/http/HttpServer.java
+++ b/bookkeeper-http/http-server/src/main/java/org/apache/bookkeeper/http/HttpServer.java
@@ -33,6 +33,7 @@
enum StatusCode {
OK(200),
REDIRECT(302),
+ BAD_REQUEST(400),
FORBIDDEN(403),
NOT_FOUND(404),
INTERNAL_ERROR(500),
@@ -84,6 +85,7 @@
BOOKIE_IS_READY,
// autorecovery
+ AUTORECOVERY_STATUS,
RECOVERY_BOOKIE,
LIST_UNDER_REPLICATED_LEDGER,
WHO_IS_AUDITOR,
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/BKHttpServiceProvider.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/BKHttpServiceProvider.java
index 24795e5..aeb4d83 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/BKHttpServiceProvider.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/BKHttpServiceProvider.java
@@ -40,6 +40,7 @@
import org.apache.bookkeeper.proto.BookieServer;
import org.apache.bookkeeper.replication.Auditor;
import org.apache.bookkeeper.replication.AutoRecoveryMain;
+import org.apache.bookkeeper.server.http.service.AutoRecoveryStatusService;
import org.apache.bookkeeper.server.http.service.BookieIsReadyService;
import org.apache.bookkeeper.server.http.service.BookieStateService;
import org.apache.bookkeeper.server.http.service.ConfigurationService;
@@ -225,6 +226,8 @@
return new BookieIsReadyService(bookieServer.getBookie());
// autorecovery
+ case AUTORECOVERY_STATUS:
+ return new AutoRecoveryStatusService(configuration);
case RECOVERY_BOOKIE:
return new RecoveryBookieService(configuration, bka, executor);
case LIST_UNDER_REPLICATED_LEDGER:
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/AutoRecoveryStatusService.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/AutoRecoveryStatusService.java
new file mode 100644
index 0000000..f9ab650
--- /dev/null
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/AutoRecoveryStatusService.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.bookkeeper.server.http.service;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.util.concurrent.UncheckedExecutionException;
+import java.util.Collections;
+import java.util.Map;
+import org.apache.bookkeeper.common.util.JsonUtil;
+import org.apache.bookkeeper.conf.ServerConfiguration;
+import org.apache.bookkeeper.http.HttpServer;
+import org.apache.bookkeeper.http.service.HttpEndpointService;
+import org.apache.bookkeeper.http.service.HttpServiceRequest;
+import org.apache.bookkeeper.http.service.HttpServiceResponse;
+import org.apache.bookkeeper.meta.LedgerUnderreplicationManager;
+import org.apache.bookkeeper.meta.MetadataDrivers;
+import org.apache.commons.lang3.ObjectUtils;
+
+/**
+ * HttpEndpointService that handles Autorecovery status related http requests.
+ *
+ * <p></p>The GET method returns the current status of Autorecovery. The output would be like {"enabled" : true}.
+ *
+ * <p>The PUT method requires a parameter 'enabled', and enables Autorecovery if its value is 'true',
+ * and disables Autorecovery otherwise. The behaviour is idempotent if Autorecovery status is already
+ * the same as desired. The output would be the current status after the action.
+ *
+ */
+public class AutoRecoveryStatusService implements HttpEndpointService {
+ protected final ServerConfiguration conf;
+
+ public AutoRecoveryStatusService(ServerConfiguration conf) {
+ this.conf = conf;
+ }
+
+ @Override
+ public HttpServiceResponse handle(HttpServiceRequest request) throws Exception {
+ return MetadataDrivers.runFunctionWithLedgerManagerFactory(conf,
+ ledgerManagerFactory -> {
+ try (LedgerUnderreplicationManager ledgerUnderreplicationManager = ledgerManagerFactory
+ .newLedgerUnderreplicationManager()) {
+ switch (request.getMethod()) {
+ case GET:
+ return handleGetStatus(ledgerUnderreplicationManager);
+ case PUT:
+ return handlePutStatus(request, ledgerUnderreplicationManager);
+ default:
+ return new HttpServiceResponse("Not found method. Should be GET or PUT method",
+ HttpServer.StatusCode.NOT_FOUND);
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new UncheckedExecutionException(e);
+ } catch (Exception e) {
+ throw new UncheckedExecutionException(e);
+ }
+ });
+ }
+
+ private HttpServiceResponse handleGetStatus(LedgerUnderreplicationManager ledgerUnderreplicationManager)
+ throws Exception {
+ String body = JsonUtil.toJson(ImmutableMap.of("enabled",
+ ledgerUnderreplicationManager.isLedgerReplicationEnabled()));
+ return new HttpServiceResponse(body, HttpServer.StatusCode.OK);
+ }
+
+ private HttpServiceResponse handlePutStatus(HttpServiceRequest request,
+ LedgerUnderreplicationManager ledgerUnderreplicationManager)
+ throws Exception {
+ Map<String, String> params = ObjectUtils.defaultIfNull(request.getParams(), Collections.emptyMap());
+ String enabled = params.get("enabled");
+ if (enabled == null) {
+ return new HttpServiceResponse("Param 'enabled' not found in " + params,
+ HttpServer.StatusCode.BAD_REQUEST);
+ }
+ if (Boolean.parseBoolean(enabled)) {
+ if (!ledgerUnderreplicationManager.isLedgerReplicationEnabled()) {
+ ledgerUnderreplicationManager.enableLedgerReplication();
+ }
+ } else {
+ if (ledgerUnderreplicationManager.isLedgerReplicationEnabled()) {
+ ledgerUnderreplicationManager.disableLedgerReplication();
+ }
+ }
+
+ // use the current status as the response
+ String body = JsonUtil.toJson(ImmutableMap.of("enabled",
+ ledgerUnderreplicationManager.isLedgerReplicationEnabled()));
+ return new HttpServiceResponse(body, HttpServer.StatusCode.OK);
+ }
+}
diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/server/http/service/AutoRecoveryStatusServiceTest.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/server/http/service/AutoRecoveryStatusServiceTest.java
new file mode 100644
index 0000000..69e851f
--- /dev/null
+++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/server/http/service/AutoRecoveryStatusServiceTest.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.bookkeeper.server.http.service;
+
+import static org.junit.Assert.assertEquals;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import org.apache.bookkeeper.http.HttpServer;
+import org.apache.bookkeeper.http.service.HttpServiceRequest;
+import org.apache.bookkeeper.http.service.HttpServiceResponse;
+import org.apache.bookkeeper.test.BookKeeperClusterTestCase;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link AutoRecoveryStatusService}.
+ */
+public class AutoRecoveryStatusServiceTest extends BookKeeperClusterTestCase {
+ private final ObjectMapper mapper = new ObjectMapper();
+ private AutoRecoveryStatusService autoRecoveryStatusService;
+ public AutoRecoveryStatusServiceTest() {
+ super(1);
+ }
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ autoRecoveryStatusService = new AutoRecoveryStatusService(baseConf);
+ }
+
+ @Test
+ public void testGetStatus() throws Exception {
+ HttpServiceRequest request = new HttpServiceRequest(null, HttpServer.Method.GET, null);
+ HttpServiceResponse response = autoRecoveryStatusService.handle(request);
+ assertEquals(HttpServer.StatusCode.OK.getValue(), response.getStatusCode());
+ JsonNode json = mapper.readTree(response.getBody());
+ assertEquals(Boolean.TRUE, json.get("enabled").asBoolean());
+ }
+
+ @Test
+ public void testEnableStatus() throws Exception {
+ Map<String, String> params = ImmutableMap.of("enabled", "true");
+ HttpServiceRequest request = new HttpServiceRequest(null, HttpServer.Method.PUT, params);
+ HttpServiceResponse response = autoRecoveryStatusService.handle(request);
+ assertEquals(HttpServer.StatusCode.OK.getValue(), response.getStatusCode());
+ JsonNode json = mapper.readTree(response.getBody());
+ assertEquals(Boolean.TRUE, json.get("enabled").asBoolean());
+
+ request = new HttpServiceRequest(null, HttpServer.Method.GET, params);
+ response = autoRecoveryStatusService.handle(request);
+ assertEquals(HttpServer.StatusCode.OK.getValue(), response.getStatusCode());
+ json = mapper.readTree(response.getBody());
+ assertEquals(Boolean.TRUE, json.get("enabled").asBoolean());
+ }
+
+ @Test
+ public void testDisableStatus() throws Exception {
+ Map<String, String> params = ImmutableMap.of("enabled", "false");
+ HttpServiceRequest request = new HttpServiceRequest(null, HttpServer.Method.PUT, params);
+ HttpServiceResponse response = autoRecoveryStatusService.handle(request);
+ assertEquals(HttpServer.StatusCode.OK.getValue(), response.getStatusCode());
+ JsonNode json = mapper.readTree(response.getBody());
+ assertEquals(Boolean.FALSE, json.get("enabled").asBoolean());
+
+ request = new HttpServiceRequest(null, HttpServer.Method.GET, params);
+ response = autoRecoveryStatusService.handle(request);
+ assertEquals(HttpServer.StatusCode.OK.getValue(), response.getStatusCode());
+ json = mapper.readTree(response.getBody());
+ assertEquals(Boolean.FALSE, json.get("enabled").asBoolean());
+ }
+
+ @Test
+ public void testInvalidParams() throws Exception {
+ Map<String, String> params = ImmutableMap.of("enable", "false");
+ HttpServiceRequest request = new HttpServiceRequest(null, HttpServer.Method.PUT, params);
+ HttpServiceResponse response = autoRecoveryStatusService.handle(request);
+ assertEquals(HttpServer.StatusCode.BAD_REQUEST.getValue(), response.getStatusCode());
+ }
+
+ @Test
+ public void testInvalidMethod() throws Exception {
+ HttpServiceRequest request = new HttpServiceRequest(null, HttpServer.Method.POST, null);
+ HttpServiceResponse response = autoRecoveryStatusService.handle(request);
+ assertEquals(HttpServer.StatusCode.NOT_FOUND.getValue(), response.getStatusCode());
+ }
+}