This is an automated email from the ASF dual-hosted git repository.
ayegorov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/bookkeeper.git
The following commit(s) were added to refs/heads/master by this push:
new 9ee04b3 Add a REST API to get or update bookie readOnly state
9ee04b3 is described below
commit 9ee04b3133c84979661a3babf1b629d72a5b9f4e
Author: Yang Yang <[email protected]>
AuthorDate: Sat Mar 12 03:57:51 2022 +0800
Add a REST API to get or update bookie readOnly state
### Motivation
This PR is a part of the work to improve the process of removing bookies
from the cluster. Specifically, it implements the `readOnly` API described in
the
[mail](http://mail-archives.apache.org/mod_mbox/bookkeeper-dev/202109.mbox/raw/%3CCAJdLeK03g8K0h6swn%3D9yVP1Ze2zHxe8TDobK6a-zpTdABkeQEA%40mail.gmail.com%3E).
### Changes
- Add an REST API at `/api/v1/bookie/state/readonly`
- The `GET` method returns the current `readOnly` status
- The `PUT` method updates the `readOnly` status if needed.
### TODOs
- Update the document once the PR is accepted.
- Update the `BookieStateManager` & `BookieImpl` to persist the information
that the state change is triggered by the external API request and do not
change the state based on the notification from the dirs monitoring service.
Reviewers: Yong Zhang <[email protected]>, Enrico Olivelli
<[email protected]>, Andrey Yegorov <None>
This closes #2799 from fantapsody/readonly-api
---
.../org/apache/bookkeeper/http/HttpRouter.java | 3 +
.../org/apache/bookkeeper/http/HttpServer.java | 1 +
.../server/http/BKHttpServiceProvider.java | 3 +
.../http/service/BookieStateReadOnlyService.java | 82 ++++++++++++++++++++++
.../bookkeeper/server/http/TestHttpService.java | 70 ++++++++++++++++++
5 files changed, 159 insertions(+)
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 f6e2c58..e4dbf29 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
@@ -48,6 +48,7 @@ public abstract class HttpRouter<Handler> {
public static final String GC =
"/api/v1/bookie/gc";
public static final String GC_DETAILS =
"/api/v1/bookie/gc_details";
public static final String BOOKIE_STATE =
"/api/v1/bookie/state";
+ public static final String BOOKIE_STATE_READONLY =
"/api/v1/bookie/state/readonly";
public static final String BOOKIE_IS_READY =
"/api/v1/bookie/is_ready";
public static final String BOOKIE_INFO =
"/api/v1/bookie/info";
// autorecovery
@@ -82,6 +83,8 @@ public abstract class HttpRouter<Handler> {
this.endpointHandlers.put(GC,
handlerFactory.newHandler(HttpServer.ApiType.GC));
this.endpointHandlers.put(GC_DETAILS,
handlerFactory.newHandler(HttpServer.ApiType.GC_DETAILS));
this.endpointHandlers.put(BOOKIE_STATE,
handlerFactory.newHandler(HttpServer.ApiType.BOOKIE_STATE));
+ this.endpointHandlers.put(BOOKIE_STATE_READONLY,
+
handlerFactory.newHandler(HttpServer.ApiType.BOOKIE_STATE_READONLY));
this.endpointHandlers.put(BOOKIE_IS_READY,
handlerFactory.newHandler(HttpServer.ApiType.BOOKIE_IS_READY));
this.endpointHandlers.put(BOOKIE_INFO,
handlerFactory.newHandler(HttpServer.ApiType.BOOKIE_INFO));
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 486ecc8..902194a 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
@@ -82,6 +82,7 @@ public interface HttpServer {
GC,
GC_DETAILS,
BOOKIE_STATE,
+ BOOKIE_STATE_READONLY,
BOOKIE_IS_READY,
BOOKIE_INFO,
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 588d432..2db8e3e 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
@@ -43,6 +43,7 @@ import org.apache.bookkeeper.replication.AutoRecoveryMain;
import org.apache.bookkeeper.server.http.service.AutoRecoveryStatusService;
import org.apache.bookkeeper.server.http.service.BookieInfoService;
import org.apache.bookkeeper.server.http.service.BookieIsReadyService;
+import org.apache.bookkeeper.server.http.service.BookieStateReadOnlyService;
import org.apache.bookkeeper.server.http.service.BookieStateService;
import org.apache.bookkeeper.server.http.service.ConfigurationService;
import org.apache.bookkeeper.server.http.service.DecommissionService;
@@ -219,6 +220,8 @@ public class BKHttpServiceProvider implements
HttpServiceProvider {
return new GCDetailsService(configuration, bookieServer);
case BOOKIE_STATE:
return new BookieStateService(bookieServer.getBookie());
+ case BOOKIE_STATE_READONLY:
+ return new
BookieStateReadOnlyService(bookieServer.getBookie());
case BOOKIE_IS_READY:
return new BookieIsReadyService(bookieServer.getBookie());
case BOOKIE_INFO:
diff --git
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/BookieStateReadOnlyService.java
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/BookieStateReadOnlyService.java
new file mode 100644
index 0000000..2fe3177
--- /dev/null
+++
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/BookieStateReadOnlyService.java
@@ -0,0 +1,82 @@
+/*
+ * 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 com.google.common.base.Preconditions.checkNotNull;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.apache.bookkeeper.bookie.Bookie;
+import org.apache.bookkeeper.bookie.StateManager;
+import org.apache.bookkeeper.common.util.JsonUtil;
+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;
+
+/**
+ * HttpEndpointService that handles readOnly state related http requests.
+ * The GET method will get the current readOnly state of the bookie.
+ * The PUT method will change the current readOnly state of the bookie if the
desired state is
+ * different from the current. The request body could be
{"readOnly":true/false}. The current
+ * or the updated state will be included in the response.
+ */
+public class BookieStateReadOnlyService implements HttpEndpointService {
+ private final Bookie bookie;
+
+ public BookieStateReadOnlyService(Bookie bookie) {
+ this.bookie = checkNotNull(bookie);
+ }
+
+ @Override
+ public HttpServiceResponse handle(HttpServiceRequest request) throws
Exception {
+ HttpServiceResponse response = new HttpServiceResponse();
+ StateManager stateManager = this.bookie.getStateManager();
+
+ if (HttpServer.Method.PUT.equals(request.getMethod())) {
+ ReadOnlyState inState = JsonUtil.fromJson(request.getBody(),
ReadOnlyState.class);
+ if (stateManager.isReadOnly() && !inState.isReadOnly()) {
+ stateManager.transitionToWritableMode().get();
+ } else if (!stateManager.isReadOnly() && inState.isReadOnly()) {
+ stateManager.transitionToReadOnlyMode().get();
+ }
+ } else if (!HttpServer.Method.GET.equals(request.getMethod())) {
+ response.setCode(HttpServer.StatusCode.NOT_FOUND);
+ response.setBody("Unsupported method. Should be GET or PUT
method");
+ return response;
+ }
+
+ ReadOnlyState outState = new ReadOnlyState(stateManager.isReadOnly());
+ response.setBody(JsonUtil.toJson(outState));
+ response.setCode(HttpServer.StatusCode.OK);
+
+ return response;
+ }
+
+ /**
+ * The object represent the readOnly state.
+ */
+ @AllArgsConstructor
+ @NoArgsConstructor
+ @Data
+ public static class ReadOnlyState {
+ private boolean readOnly;
+ }
+}
diff --git
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/server/http/TestHttpService.java
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/server/http/TestHttpService.java
index a00b1cd..863b822 100644
---
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/server/http/TestHttpService.java
+++
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/server/http/TestHttpService.java
@@ -20,6 +20,7 @@ package org.apache.bookkeeper.server.http;
import static
org.apache.bookkeeper.meta.MetadataDrivers.runFunctionWithLedgerManagerFactory;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -54,6 +55,7 @@ import org.apache.bookkeeper.meta.MetadataBookieDriver;
import org.apache.bookkeeper.net.BookieSocketAddress;
import org.apache.bookkeeper.replication.AuditorElector;
import org.apache.bookkeeper.server.http.service.BookieInfoService;
+import
org.apache.bookkeeper.server.http.service.BookieStateReadOnlyService.ReadOnlyState;
import
org.apache.bookkeeper.server.http.service.BookieStateService.BookieState;
import org.apache.bookkeeper.stats.NullStatsLogger;
import org.apache.bookkeeper.test.BookKeeperClusterTestCase;
@@ -897,4 +899,72 @@ public class TestHttpService extends
BookKeeperClusterTestCase {
HttpServiceResponse response2 = bookieStateServer.handle(request2);
assertEquals(HttpServer.StatusCode.NOT_FOUND.getValue(),
response2.getStatusCode());
}
+
+ @Test
+ public void testBookieReadOnlyState() throws Exception {
+ HttpEndpointService bookieStateServer = bkHttpServiceProvider
+ .provideHttpEndpointService(HttpServer.ApiType.BOOKIE_STATE);
+ HttpEndpointService bookieReadOnlyService = bkHttpServiceProvider
+
.provideHttpEndpointService(HttpServer.ApiType.BOOKIE_STATE_READONLY);
+
+ // responses from both endpoints should indicate the bookie is not
read only
+ HttpServiceRequest request = new HttpServiceRequest(null,
HttpServer.Method.GET, null);
+ HttpServiceResponse response = bookieStateServer.handle(request);
+ assertEquals(HttpServer.StatusCode.OK.getValue(),
response.getStatusCode());
+
+ BookieState bs = JsonUtil.fromJson(response.getBody(),
BookieState.class);
+ assertTrue(bs.isRunning());
+ assertFalse(bs.isReadOnly());
+ assertTrue(bs.isAvailableForHighPriorityWrites());
+ assertFalse(bs.isShuttingDown());
+
+ request = new HttpServiceRequest(null, HttpServer.Method.GET, null);
+ response = bookieReadOnlyService.handle(request);
+ ReadOnlyState readOnlyState = JsonUtil.fromJson(response.getBody(),
ReadOnlyState.class);
+ assertFalse(readOnlyState.isReadOnly());
+
+ // update the state to read only
+ request = new HttpServiceRequest(JsonUtil.toJson(new
ReadOnlyState(true)), HttpServer.Method.PUT, null);
+ response = bookieReadOnlyService.handle(request);
+ readOnlyState = JsonUtil.fromJson(response.getBody(),
ReadOnlyState.class);
+ assertTrue(readOnlyState.isReadOnly());
+
+ // responses from both endpoints should indicate the bookie is read
only
+ request = new HttpServiceRequest(null, HttpServer.Method.GET, null);
+ response = bookieStateServer.handle(request);
+ assertEquals(HttpServer.StatusCode.OK.getValue(),
response.getStatusCode());
+
+ bs = JsonUtil.fromJson(response.getBody(), BookieState.class);
+ assertTrue(bs.isRunning());
+ assertTrue(bs.isReadOnly());
+ assertTrue(bs.isAvailableForHighPriorityWrites());
+ assertFalse(bs.isShuttingDown());
+
+ request = new HttpServiceRequest(null, HttpServer.Method.GET, null);
+ response = bookieReadOnlyService.handle(request);
+ readOnlyState = JsonUtil.fromJson(response.getBody(),
ReadOnlyState.class);
+ assertTrue(readOnlyState.isReadOnly());
+
+ // should be able to update the state to writable again
+ request = new HttpServiceRequest(JsonUtil.toJson(new
ReadOnlyState(false)), HttpServer.Method.PUT, null);
+ response = bookieReadOnlyService.handle(request);
+ readOnlyState = JsonUtil.fromJson(response.getBody(),
ReadOnlyState.class);
+ assertFalse(readOnlyState.isReadOnly());
+
+ // responses from both endpoints should indicate the bookie is writable
+ request = new HttpServiceRequest(null, HttpServer.Method.GET, null);
+ response = bookieStateServer.handle(request);
+ assertEquals(HttpServer.StatusCode.OK.getValue(),
response.getStatusCode());
+
+ bs = JsonUtil.fromJson(response.getBody(), BookieState.class);
+ assertTrue(bs.isRunning());
+ assertFalse(bs.isReadOnly());
+ assertTrue(bs.isAvailableForHighPriorityWrites());
+ assertFalse(bs.isShuttingDown());
+
+ request = new HttpServiceRequest(null, HttpServer.Method.GET, null);
+ response = bookieReadOnlyService.handle(request);
+ readOnlyState = JsonUtil.fromJson(response.getBody(),
ReadOnlyState.class);
+ assertFalse(readOnlyState.isReadOnly());
+ }
}