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,