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/22 21:22:33 UTC

[bookkeeper] branch master updated: [FEATURE] Added api/v1/bookie/cluster_info REST API

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 032aef7e75 [FEATURE] Added api/v1/bookie/cluster_info REST API
032aef7e75 is described below

commit 032aef7e75f6b3487a6611a2b8af8306b9a3f7bb
Author: Andrey Yegorov <86...@users.noreply.github.com>
AuthorDate: Thu Dec 22 13:22:26 2022 -0800

    [FEATURE] Added api/v1/bookie/cluster_info REST API
    
    Descriptions of the changes in this PR:
    
    ### Motivation
    
    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.
    
    ### Changes
    
     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
---
 .../org/apache/bookkeeper/http/HttpRouter.java     |   2 +
 .../org/apache/bookkeeper/http/HttpServer.java     |   1 +
 .../server/http/BKHttpServiceProvider.java         |   3 +
 .../server/http/service/ClusterInfoService.java    | 137 +++++++++++++++++++++
 .../bookkeeper/server/http/TestHttpService.java    |  29 +++++
 site3/website/docs/admin/http.md                   |  31 +++++
 .../bookkeeper/stream/server/StorageServer.java    |  13 ++
 7 files changed, 216 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 0fc4fec308..004c05dc61 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
@@ -54,6 +54,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";
@@ -91,6 +92,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));
         this.endpointHandlers.put(SUSPEND_GC_COMPACTION,
             handlerFactory.newHandler(HttpServer.ApiType.SUSPEND_GC_COMPACTION));
         this.endpointHandlers.put(RESUME_GC_COMPACTION,
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 f413ae2b65..69dc96ab0d 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
@@ -86,6 +86,7 @@ public interface HttpServer {
         BOOKIE_STATE_READONLY,
         BOOKIE_IS_READY,
         BOOKIE_INFO,
+        CLUSTER_INFO,
         RESUME_GC_COMPACTION,
         SUSPEND_GC_COMPACTION,
         // autorecovery
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 eba32d44ec..4c7b787405 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.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.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;
@@ -228,6 +229,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, ledgerManagerFactory);
             case SUSPEND_GC_COMPACTION:
                 return new SuspendCompactionService(bookieServer);
             case RESUME_GC_COMPACTION:
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..99665eae3f
--- /dev/null
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/ClusterInfoService.java
@@ -0,0 +1,137 @@
+/*
+ * 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;
+
+/**
+ * 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 LedgerManagerFactory ledgerManagerFactory;
+
+    /**
+     * 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) {
+        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 076cfbf539..5fb1679810 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
@@ -56,6 +56,7 @@ 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.server.http.service.ClusterInfoService;
 import org.apache.bookkeeper.stats.NullStatsLogger;
 import org.apache.bookkeeper.test.BookKeeperClusterTestCase;
 import org.junit.Before;
@@ -920,6 +921,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/site3/website/docs/admin/http.md b/site3/website/docs/admin/http.md
index 14358f8f37..e25f30b375 100644
--- a/site3/website/docs/admin/http.md
+++ b/site3/website/docs/admin/http.md
@@ -234,6 +234,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.
diff --git a/stream/server/src/main/java/org/apache/bookkeeper/stream/server/StorageServer.java b/stream/server/src/main/java/org/apache/bookkeeper/stream/server/StorageServer.java
index 673c808045..99e70188a6 100644
--- a/stream/server/src/main/java/org/apache/bookkeeper/stream/server/StorageServer.java
+++ b/stream/server/src/main/java/org/apache/bookkeeper/stream/server/StorageServer.java
@@ -28,15 +28,19 @@ import java.util.concurrent.ExecutionException;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.bookkeeper.bookie.BookieResources;
 import org.apache.bookkeeper.clients.config.StorageClientSettings;
 import org.apache.bookkeeper.clients.impl.channel.StorageServerChannel;
 import org.apache.bookkeeper.clients.impl.internal.StorageServerClientManagerImpl;
+import org.apache.bookkeeper.common.component.AutoCloseableLifecycleComponent;
 import org.apache.bookkeeper.common.component.ComponentInfoPublisher;
 import org.apache.bookkeeper.common.component.ComponentStarter;
 import org.apache.bookkeeper.common.component.LifecycleComponent;
 import org.apache.bookkeeper.common.component.LifecycleComponentStack;
 import org.apache.bookkeeper.conf.ServerConfiguration;
 import org.apache.bookkeeper.discover.BookieServiceInfo;
+import org.apache.bookkeeper.meta.LedgerManagerFactory;
+import org.apache.bookkeeper.meta.MetadataBookieDriver;
 import org.apache.bookkeeper.meta.zk.ZKMetadataDriverBase;
 import org.apache.bookkeeper.server.http.BKHttpServiceProvider;
 import org.apache.bookkeeper.server.service.HttpService;
@@ -256,10 +260,19 @@ public class StorageServer {
 
             // Build http service
             if (bkServerConf.isHttpServerEnabled()) {
+                MetadataBookieDriver metadataDriver = BookieResources.createMetadataDriver(bkServerConf,
+                        rootStatsLogger);
+                serverBuilder.addComponent(new AutoCloseableLifecycleComponent("metadataDriver",
+                        metadataDriver));
+                LedgerManagerFactory ledgerManagerFactory = metadataDriver.getLedgerManagerFactory();
+                serverBuilder.addComponent(new AutoCloseableLifecycleComponent("lmFactory",
+                        ledgerManagerFactory));
+
                 BKHttpServiceProvider provider = new BKHttpServiceProvider.Builder()
                         .setBookieServer(bookieService.getServer())
                         .setServerConfiguration(bkServerConf)
                         .setStatsProvider(statsProviderService.getStatsProvider())
+                        .setLedgerManagerFactory(ledgerManagerFactory)
                         .build();
                 HttpService httpService =
                         new HttpService(provider,