This is an automated email from the ASF dual-hosted git repository.

ayegorov pushed a commit to branch branch-4.14
in repository https://gitbox.apache.org/repos/asf/bookkeeper.git


The following commit(s) were added to refs/heads/branch-4.14 by this push:
     new c4f2951  Add a REST API to get or update bookie readOnly state
c4f2951 is described below

commit c4f2951f1b7ec9d8c1c7065bd536046da683d5be
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
    
    (cherry picked from commit 9ee04b3133c84979661a3babf1b629d72a5b9f4e)
    Signed-off-by: Andrey Yegorov <[email protected]>
---
 .../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 a9426de..35f4e25 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 76705b9..1cafbc2 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
@@ -42,6 +42,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;
@@ -208,6 +209,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 92a575d..152bd09 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;
 
@@ -52,6 +53,7 @@ import 
org.apache.bookkeeper.meta.LedgerUnderreplicationManager;
 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.test.BookKeeperClusterTestCase;
 import org.junit.Before;
@@ -886,4 +888,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());
+    }
 }

Reply via email to