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

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


The following commit(s) were added to refs/heads/branch-4.15 by this push:
     new c43a7a4c5e [FEATURE] Added api/v1/bookie/cluster_info REST API (#3713)
c43a7a4c5e is described below

commit c43a7a4c5e48ee5a2cc82ce6a331256d1bbee3e3
Author: Andrey Yegorov <[email protected]>
AuthorDate: Tue Dec 27 09:24:13 2022 -0800

    [FEATURE] Added api/v1/bookie/cluster_info REST API (#3713)
    
    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 <[email protected]>, Enrico Olivelli 
<[email protected]>
    
    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    | 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(+), 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 902194a0ec..e614b1e712 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 2db8e3e5af..34b35233b2 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
@@ -45,6 +45,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;
@@ -226,6 +227,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);
 
             // 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..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 863b822773..3aac99c3f9 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
@@ -57,6 +57,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.stats.NullStatsLogger;
 import org.apache.bookkeeper.test.BookKeeperClusterTestCase;
 import org.junit.Before;
@@ -900,6 +901,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 d827720856..1e235f8b9f 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,

Reply via email to