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

chenhang 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 c2e59e1411 Add Http-service to check bookie sanity state (#3630)
c2e59e1411 is described below

commit c2e59e14115d1e1b67e0fcbad4d44ee904be27cf
Author: Rajan Dhabalia <[email protected]>
AuthorDate: Thu Nov 24 21:23:08 2022 -0800

    Add Http-service to check bookie sanity state (#3630)
    
    ### Motivation
    
    Right now, if Bookie startup or process fails due to various reasons then 
bookie getState() rest API still gives a successful response even though bookie 
writes are not going through. Therefore. Bookie should have a REST API to check 
bookie sanity test and that can be used to check bookie liveliness in 
k8/helm-chart or to plot bookie health graph.
    
    ### Changes
    
    Add Bookie REST API to check bookie health to validate bookie liveliness
---
 .../org/apache/bookkeeper/http/HttpRouter.java     |   2 +
 .../org/apache/bookkeeper/http/HttpServer.java     |   1 +
 .../server/http/BKHttpServiceProvider.java         |   3 +
 .../server/http/service/BookieSanityService.java   | 113 +++++++++++++++++++++
 .../bookkeeper/server/http/TestHttpService.java    |  23 +++++
 site3/website/docs/admin/http.md                   |  19 ++++
 6 files changed, 161 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 ed460b73c9..0fc4fec308 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 abstract class HttpRouter<Handler> {
     public static final String SUSPEND_GC_COMPACTION        = 
"/api/v1/bookie/gc/suspend_compaction";
     public static final String RESUME_GC_COMPACTION         = 
"/api/v1/bookie/gc/resume_compaction";
     public static final String BOOKIE_STATE                 = 
"/api/v1/bookie/state";
+    public static final String BOOKIE_SANITY                 = 
"/api/v1/bookie/sanity";
     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";
@@ -85,6 +86,7 @@ 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_SANITY, 
handlerFactory.newHandler(HttpServer.ApiType.BOOKIE_SANITY));
         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));
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 7bfd62e975..f413ae2b65 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_SANITY,
         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 8ebf29e831..eba32d44ec 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.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.BookieSanityService;
 import org.apache.bookkeeper.server.http.service.BookieStateReadOnlyService;
 import org.apache.bookkeeper.server.http.service.BookieStateService;
 import org.apache.bookkeeper.server.http.service.ConfigurationService;
@@ -219,6 +220,8 @@ public class BKHttpServiceProvider implements 
HttpServiceProvider {
                 return new GCDetailsService(configuration, bookieServer);
             case BOOKIE_STATE:
                 return new BookieStateService(bookieServer.getBookie());
+            case BOOKIE_SANITY:
+                return new BookieSanityService(configuration);
             case BOOKIE_STATE_READONLY:
                 return new 
BookieStateReadOnlyService(bookieServer.getBookie());
             case BOOKIE_IS_READY:
diff --git 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/BookieSanityService.java
 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/BookieSanityService.java
new file mode 100644
index 0000000000..9d786425a3
--- /dev/null
+++ 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/BookieSanityService.java
@@ -0,0 +1,113 @@
+/*
+ * 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 java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+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.tools.cli.commands.bookie.SanityTestCommand;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * HttpEndpointService that exposes the bookie sanity state.
+ *
+ * <p>
+ * Get the current bookie sanity response:
+ *
+ * <pre>
+ * <code>
+ * {
+ *  "passed" : true,
+ *  "readOnly" : false
+ *}
+ * </code>
+ * </pre>
+ */
+public class BookieSanityService implements HttpEndpointService {
+
+    static final Logger LOG = 
LoggerFactory.getLogger(BookieSanityService.class);
+    private final ServerConfiguration config;
+    private Semaphore lock = new Semaphore(1);
+    private static final int TIMEOUT_MS = 5000;
+    private static final int MAX_CONCURRENT_REQUESTS = 1;
+
+    public BookieSanityService(ServerConfiguration config) {
+        this.config = checkNotNull(config);
+    }
+
+    /**
+     * POJO definition for the bookie sanity response.
+     */
+    @Data
+    @NoArgsConstructor
+    public static class BookieSanity {
+        private boolean passed;
+        private boolean readOnly;
+    }
+
+    @Override
+    public HttpServiceResponse handle(HttpServiceRequest request) throws 
Exception {
+        HttpServiceResponse response = new HttpServiceResponse();
+
+        if (HttpServer.Method.GET != request.getMethod()) {
+            response.setCode(HttpServer.StatusCode.NOT_FOUND);
+            response.setBody("Only support GET method to retrieve bookie 
sanity state.");
+            return response;
+        }
+
+        BookieSanity bs = new BookieSanity();
+        if (config.isForceReadOnlyBookie()) {
+            bs.readOnly = true;
+        } else {
+            try {
+                // allow max concurrent request as sanity-test check relatively
+                // longer time to complete
+                try {
+                    lock.tryAcquire(MAX_CONCURRENT_REQUESTS, TIMEOUT_MS, 
TimeUnit.MILLISECONDS);
+                } catch (InterruptedException e) {
+                    LOG.error("Timing out due to max {} of sanity request are 
running concurrently",
+                            MAX_CONCURRENT_REQUESTS);
+                    response.setCode(HttpServer.StatusCode.INTERNAL_ERROR);
+                    response.setBody("Timing out due to max number of sanity 
request are running concurrently");
+                    return response;
+                }
+                SanityTestCommand sanity = new SanityTestCommand();
+                bs.passed = sanity.apply(config, new 
SanityTestCommand.SanityFlags());
+            } finally {
+                lock.release();
+            }
+        }
+        String jsonResponse = JsonUtil.toJson(bs);
+        response.setBody(jsonResponse);
+        response.setCode(HttpServer.StatusCode.OK);
+        return response;
+    }
+}
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 cdb9c4fdd4..076cfbf539 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
@@ -52,6 +52,8 @@ 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.BookieSanityService;
+import 
org.apache.bookkeeper.server.http.service.BookieSanityService.BookieSanity;
 import 
org.apache.bookkeeper.server.http.service.BookieStateReadOnlyService.ReadOnlyState;
 import 
org.apache.bookkeeper.server.http.service.BookieStateService.BookieState;
 import org.apache.bookkeeper.stats.NullStatsLogger;
@@ -853,6 +855,27 @@ public class TestHttpService extends 
BookKeeperClusterTestCase {
         assertEquals(false, bs.isShuttingDown());
     }
 
+    @Test
+    public void testGetBookieSanity() throws Exception {
+        HttpEndpointService bookieStateServer = bkHttpServiceProvider
+                .provideHttpEndpointService(HttpServer.ApiType.BOOKIE_SANITY);
+
+        HttpServiceRequest request1 = new HttpServiceRequest(null, 
HttpServer.Method.GET, null);
+        ServerConfiguration conf = servers.get(0).getConfiguration();
+        BookieSanityService service = new BookieSanityService(conf);
+        HttpServiceResponse response1 = service.handle(request1);
+        assertEquals(HttpServer.StatusCode.OK.getValue(), 
response1.getStatusCode());
+        // run multiple iteration to validate any server side throttling 
doesn't
+        // fail sequential requests.
+        for (int i = 0; i < 3; i++) {
+            BookieSanity bs = JsonUtil.fromJson(response1.getBody(), 
BookieSanity.class);
+            assertEquals(true, bs.isPassed());
+            assertEquals(false, bs.isReadOnly());
+        }
+        HttpServiceResponse response2 = bookieStateServer.handle(request1);
+        assertEquals(HttpServer.StatusCode.OK.getValue(), 
response2.getStatusCode());
+    }
+
     @Test
     public void testGetBookieIsReady() throws Exception {
         HttpEndpointService bookieStateServer = bkHttpServiceProvider
diff --git a/site3/website/docs/admin/http.md b/site3/website/docs/admin/http.md
index 6c71c09740..14358f8f37 100644
--- a/site3/website/docs/admin/http.md
+++ b/site3/website/docs/admin/http.md
@@ -413,6 +413,25 @@ Currently all the HTTP endpoints could be divided into 
these 5 components:
        }
       ```
 
+### Endpoint: /api/v1/bookie/sanity
+1. Method: GET
+   * Description:  Exposes the bookie sanity state
+   * Response:
+
+        | Code   | Description |
+        |:-------|:------------|
+        |200 | Successful operation |
+        |403 | Permission denied |
+        |404 | Not found |
+   * Body:
+      ```json
+      {
+         "passed" : true,
+         "readOnly" : false
+       }
+      ```
+
+
 ### Endpoint: /api/v1/bookie/is_ready
 1. Method: GET
    * Description:  Return true if the bookie is ready

Reply via email to