You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bookkeeper.apache.org by ay...@apache.org on 2022/12/27 17:24:32 UTC

[bookkeeper] branch branch-4.14 updated: [FEATURE] Added api/v1/bookie/cluster_info REST API (#3714)

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 6b1b1361fa [FEATURE] Added api/v1/bookie/cluster_info REST API (#3714)
6b1b1361fa is described below

commit 6b1b1361fac8c424b3214ab462af9e8388056bd9
Author: Andrey Yegorov <86...@users.noreply.github.com>
AuthorDate: Tue Dec 27 09:24:27 2022 -0800

    [FEATURE] Added api/v1/bookie/cluster_info REST API (#3714)
    
    Descriptions of the changes in this PR:
    
    Information provided by current REST API is not enough (and cumbersome to combine) to answer such question as "is any data in danger if I shut down one more bookie".
    E.g. getting list of underreplicated ledgers can get some info but it is either fast (no ledgers) or can be super slow on large cluster with some bookies lost (it retrieves full list of ledgers).
    Even if there are no UR ledgers it still possible that the problem is that Auditor is down etc.
    
     Added api/v1/bookie/cluster_info REST API
    
    ```
    curl -s 127.0.0.1:8080/api/v1/bookie/cluster_info
    {
      "auditorElected" : false,
      "auditorId" : "",
      "clusterUnderReplicated" : false,
      "ledgerReplicationEnabled" : true,
      "totalBookiesCount" : 1,
      "writableBookiesCount" : 1,
      "readonlyBookiesCount" : 0,
      "unavailableBookiesCount" : 0
    }%
    ```
    
    Side-fix:
    `org.apache.bookkeeper.stream.cluster.StandaloneStarter` (used by bookie standalone) did not pass `LedgerManagerFactory` to the http server thus REST calls that needed it didn't work.
    
    Reviewers: Nicolò Boschi <bo...@gmail.com>, Enrico Olivelli <eo...@gmail.com>
    
    This closes #3710 from dlg99/rest-cluster-info
    
    (cherry picked from commit 032aef7e75f6b3487a6611a2b8af8306b9a3f7bb)
---
 .../org/apache/bookkeeper/http/HttpRouter.java     |   2 +
 .../org/apache/bookkeeper/http/HttpServer.java     |   2 +-
 .../server/http/BKHttpServiceProvider.java         |   3 +
 .../server/http/service/ClusterInfoService.java    | 139 +++++++++++++++++++++
 .../bookkeeper/server/http/TestHttpService.java    |  29 +++++
 site/docs/4.11.0/admin/http.md                     |  31 +++++
 6 files changed, 205 insertions(+), 1 deletion(-)

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 e4dbf299e0..fb8c1a4f05 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
@@ -51,6 +51,7 @@ public abstract class HttpRouter<Handler> {
     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";
+    public static final String CLUSTER_INFO                  = "/api/v1/bookie/cluster_info";
     // autorecovery
     public static final String AUTORECOVERY_STATUS          = "/api/v1/autorecovery/status";
     public static final String RECOVERY_BOOKIE              = "/api/v1/autorecovery/bookie";
@@ -87,6 +88,7 @@ public abstract class HttpRouter<Handler> {
                 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));
+        this.endpointHandlers.put(CLUSTER_INFO, handlerFactory.newHandler(HttpServer.ApiType.CLUSTER_INFO));
 
         // autorecovery
         this.endpointHandlers.put(AUTORECOVERY_STATUS, handlerFactory
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 35f4e25814..fcebd9237b 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
@@ -85,7 +85,7 @@ public interface HttpServer {
         BOOKIE_STATE_READONLY,
         BOOKIE_IS_READY,
         BOOKIE_INFO,
-
+        CLUSTER_INFO,
         // autorecovery
         AUTORECOVERY_STATUS,
         RECOVERY_BOOKIE,
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 1cafbc2f61..d2fb7c3317 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
@@ -44,6 +44,7 @@ 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.ClusterInfoService;
 import org.apache.bookkeeper.server.http.service.ConfigurationService;
 import org.apache.bookkeeper.server.http.service.DecommissionService;
 import org.apache.bookkeeper.server.http.service.DeleteLedgerService;
@@ -215,6 +216,8 @@ public class BKHttpServiceProvider implements HttpServiceProvider {
                 return new BookieIsReadyService(bookieServer.getBookie());
             case BOOKIE_INFO:
                 return new BookieInfoService(bookieServer.getBookie());
+            case CLUSTER_INFO:
+                return new ClusterInfoService(bka, bookieServer);
 
             // autorecovery
             case AUTORECOVERY_STATUS:
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/ClusterInfoService.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/ClusterInfoService.java
new file mode 100644
index 0000000000..a7d8290974
--- /dev/null
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/ClusterInfoService.java
@@ -0,0 +1,139 @@
+/*
+ * 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 java.util.Iterator;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NonNull;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.bookkeeper.client.BookKeeperAdmin;
+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;
+import org.apache.bookkeeper.meta.LedgerManagerFactory;
+import org.apache.bookkeeper.meta.LedgerUnderreplicationManager;
+import org.apache.bookkeeper.meta.UnderreplicatedLedger;
+import org.apache.bookkeeper.net.BookieId;
+import org.apache.bookkeeper.proto.BookieServer;
+
+/**
+ * HttpEndpointService that exposes the current info about the cluster of bookies.
+ *
+ * <pre>
+ * <code>
+ * {
+ *  "hasAuditorElected" : true,
+ *  "auditorId" : "blah",
+ *  "hasUnderReplicatedLedgers": false,
+ *  "isLedgerReplicationEnabled": true,
+ *  "totalBookiesCount": 10,
+ *  "writableBookiesCount": 6,
+ *  "readonlyBookiesCount": 3,
+ *  "unavailableBookiesCount": 1
+ * }
+ * </code>
+ * </pre>
+ */
+@AllArgsConstructor
+@Slf4j
+public class ClusterInfoService implements HttpEndpointService {
+
+    @NonNull
+    private final BookKeeperAdmin bka;
+    @NonNull
+    private final BookieServer bookieServer;
+
+    /**
+     * POJO definition for the cluster info response.
+     */
+    @Data
+    public static class ClusterInfo {
+        private boolean auditorElected;
+        private String auditorId;
+        private boolean clusterUnderReplicated;
+        private boolean ledgerReplicationEnabled;
+        private int totalBookiesCount;
+        private int writableBookiesCount;
+        private int readonlyBookiesCount;
+        private int unavailableBookiesCount;
+    }
+
+    @Override
+    public HttpServiceResponse handle(HttpServiceRequest request) throws Exception {
+        final HttpServiceResponse response = new HttpServiceResponse();
+
+        if (HttpServer.Method.GET != request.getMethod()) {
+            response.setCode(HttpServer.StatusCode.NOT_FOUND);
+            response.setBody("Only GET is supported.");
+            return response;
+        }
+
+        final ClusterInfo info = new ClusterInfo();
+        fillUReplicatedInfo(info);
+        fillAuditorInfo(info);
+        fillBookiesInfo(info);
+
+        String jsonResponse = JsonUtil.toJson(info);
+        response.setBody(jsonResponse);
+        response.setCode(HttpServer.StatusCode.OK);
+        return response;
+    }
+
+    @SneakyThrows
+    private void fillBookiesInfo(ClusterInfo info) {
+        int totalBookiesCount = bka.getAllBookies().size();
+        int writableBookiesCount = bka.getAvailableBookies().size();
+        int readonlyBookiesCount = bka.getReadOnlyBookies().size();
+        int unavailableBookiesCount = totalBookiesCount - writableBookiesCount - readonlyBookiesCount;
+
+        info.setTotalBookiesCount(totalBookiesCount);
+        info.setWritableBookiesCount(writableBookiesCount);
+        info.setReadonlyBookiesCount(readonlyBookiesCount);
+        info.setUnavailableBookiesCount(unavailableBookiesCount);
+    }
+
+    private void fillAuditorInfo(ClusterInfo info) {
+        try {
+            BookieId currentAuditor = bka.getCurrentAuditor();
+            info.setAuditorElected(currentAuditor != null);
+            info.setAuditorId(currentAuditor == null ? "" : currentAuditor.getId());
+        } catch (Exception e) {
+            log.error("Could not get Auditor info", e);
+            info.setAuditorElected(false);
+            info.setAuditorId("");
+        }
+    }
+
+    @SneakyThrows
+    private void fillUReplicatedInfo(ClusterInfo info) {
+        LedgerManagerFactory ledgerManagerFactory = bookieServer.getBookie().getLedgerManagerFactory();
+        try (LedgerUnderreplicationManager underreplicationManager =
+                ledgerManagerFactory.newLedgerUnderreplicationManager()) {
+            Iterator<UnderreplicatedLedger> iter = underreplicationManager.listLedgersToRereplicate(null);
+
+            info.setClusterUnderReplicated(iter.hasNext());
+            info.setLedgerReplicationEnabled(underreplicationManager.isLedgerReplicationEnabled());
+        }
+    }
+
+}
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 152bd09412..ece5e1be0f 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
@@ -55,6 +55,7 @@ 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.server.http.service.ClusterInfoService;
 import org.apache.bookkeeper.test.BookKeeperClusterTestCase;
 import org.junit.Before;
 import org.junit.Test;
@@ -889,6 +890,34 @@ public class TestHttpService extends BookKeeperClusterTestCase {
         assertEquals(HttpServer.StatusCode.NOT_FOUND.getValue(), response2.getStatusCode());
     }
 
+    @Test
+    public void testGetClusterInfo() throws Exception {
+        HttpEndpointService clusterInfoServer = bkHttpServiceProvider
+                .provideHttpEndpointService(HttpServer.ApiType.CLUSTER_INFO);
+
+        HttpServiceRequest request1 = new HttpServiceRequest(null, HttpServer.Method.GET, null);
+        HttpServiceResponse response1 = clusterInfoServer.handle(request1);
+        assertEquals(HttpServer.StatusCode.OK.getValue(), response1.getStatusCode());
+        LOG.info("Get response: {}", response1.getBody());
+
+        ClusterInfoService.ClusterInfo info = JsonUtil.fromJson(response1.getBody(),
+                ClusterInfoService.ClusterInfo.class);
+        assertFalse(info.isAuditorElected());
+        assertTrue(info.getAuditorId().length() == 0);
+        assertFalse(info.isClusterUnderReplicated());
+        assertTrue(info.isLedgerReplicationEnabled());
+        assertTrue(info.getTotalBookiesCount() > 0);
+        assertTrue(info.getWritableBookiesCount() > 0);
+        assertTrue(info.getReadonlyBookiesCount() == 0);
+        assertTrue(info.getUnavailableBookiesCount() == 0);
+        assertTrue(info.getTotalBookiesCount() == info.getWritableBookiesCount());
+
+        // Try using POST instead of GET
+        HttpServiceRequest request2 = new HttpServiceRequest(null, HttpServer.Method.POST, null);
+        HttpServiceResponse response2 = clusterInfoServer.handle(request2);
+        assertEquals(HttpServer.StatusCode.NOT_FOUND.getValue(), response2.getStatusCode());
+    }
+
     @Test
     public void testBookieReadOnlyState() throws Exception {
         HttpEndpointService bookieStateServer = bkHttpServiceProvider
diff --git a/site/docs/4.11.0/admin/http.md b/site/docs/4.11.0/admin/http.md
index 50c3a7db01..bee78c82a4 100644
--- a/site/docs/4.11.0/admin/http.md
+++ b/site/docs/4.11.0/admin/http.md
@@ -233,6 +233,37 @@ Currently all the HTTP endpoints could be divided into these 5 components:
         }
         ```    
 
+### Endpoint: /api/v1/bookie/cluster_info
+1. Method: GET
+    * Description:  Get top-level info of this cluster.
+    * Response:
+
+      | Code   | Description |
+              |:-------|:------------|
+      |200 | Successful operation |
+      |403 | Permission denied |
+      |404 | Not found |
+    * Response Body format:
+
+        ```json
+        {
+          "auditorElected" : false,
+          "auditorId" : "",
+          "clusterUnderReplicated" : false,
+          "ledgerReplicationEnabled" : true,
+          "totalBookiesCount" : 1,
+          "writableBookiesCount" : 1,
+          "readonlyBookiesCount" : 0,
+          "unavailableBookiesCount" : 0
+        }
+        ```    
+   `clusterUnderReplicated` is true if there is any underreplicated ledger known currently. 
+    Trigger audit to increase precision. Audit might not be possible if `auditorElected` is false or
+    `ledgerReplicationEnabled` is false.
+
+   `totalBookiesCount` = `writableBookiesCount` + `readonlyBookiesCount` + `unavailableBookiesCount`.
+
+
 ### Endpoint: /api/v1/bookie/last_log_mark
 1. Method: GET
     * Description:  Get the last log marker.