From 75f6fb8b7d89698f904fa9d68950047c7d7c8325 Mon Sep 17 00:00:00 2001 From: nicklixinyang Date: Tue, 7 Mar 2023 16:48:57 +0800 Subject: [PATCH] Add trigger entry location index rocksDB compact interface. --- .../apache/bookkeeper/http/HttpRouter.java | 3 + .../apache/bookkeeper/http/HttpServer.java | 2 + .../bookkeeper/bookie/LedgerStorage.java | 21 +++ .../bookie/storage/ldb/DbLedgerStorage.java | 44 ++++++ .../storage/ldb/EntryLocationIndex.java | 18 +++ .../bookie/storage/ldb/KeyValueStorage.java | 10 ++ .../storage/ldb/KeyValueStorageRocksDB.java | 50 +++++- .../ldb/SingleDirectoryDbLedgerStorage.java | 28 ++++ .../server/http/BKHttpServiceProvider.java | 3 + .../TriggerLocationCompactService.java | 143 ++++++++++++++++++ .../server/http/TestHttpService.java | 81 ++++++++++ site3/website/docs/admin/http.md | 41 +++++ 12 files changed, 438 insertions(+), 6 deletions(-) create mode 100644 bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/TriggerLocationCompactService.java 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 ddbe46b6b2f..296201e9e8b 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 @@ -55,6 +55,7 @@ public abstract class HttpRouter { 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"; + public static final String ENTRY_LOCATION_COMPACT = "/api/v1/bookie/entry_location_compact"; // autorecovery public static final String AUTORECOVERY_STATUS = "/api/v1/autorecovery/status"; public static final String RECOVERY_BOOKIE = "/api/v1/autorecovery/bookie"; @@ -97,6 +98,8 @@ public HttpRouter(AbstractHttpHandlerFactory handlerFactory) { handlerFactory.newHandler(HttpServer.ApiType.SUSPEND_GC_COMPACTION)); this.endpointHandlers.put(RESUME_GC_COMPACTION, handlerFactory.newHandler(HttpServer.ApiType.RESUME_GC_COMPACTION)); + this.endpointHandlers.put(ENTRY_LOCATION_COMPACT, + handlerFactory.newHandler(HttpServer.ApiType.TRIGGER_ENTRY_LOCATION_COMPACT)); // 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 afadba87c78..71d597d5ffa 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 @@ -36,6 +36,7 @@ enum StatusCode { BAD_REQUEST(400), FORBIDDEN(403), NOT_FOUND(404), + METHOD_NOT_ALLOWED(405), INTERNAL_ERROR(500), SERVICE_UNAVAILABLE(503); @@ -89,6 +90,7 @@ enum ApiType { CLUSTER_INFO, RESUME_GC_COMPACTION, SUSPEND_GC_COMPACTION, + TRIGGER_ENTRY_LOCATION_COMPACT, // autorecovery AUTORECOVERY_STATUS, RECOVERY_BOOKIE, diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/LedgerStorage.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/LedgerStorage.java index bedc7d26814..92f0d38c6ea 100644 --- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/LedgerStorage.java +++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/LedgerStorage.java @@ -29,6 +29,7 @@ import java.util.Collections; import java.util.EnumSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.PrimitiveIterator; import org.apache.bookkeeper.bookie.CheckpointSource.Checkpoint; @@ -259,6 +260,26 @@ default boolean isMinorGcSuspended() { return false; } + default void entryLocationCompact() { + return; + } + + default void entryLocationCompact(List locations) { + return; + } + + default boolean isEntryLocationCompacting() { + return false; + } + + default Map isEntryLocationCompacting(List locations) { + return Collections.emptyMap(); + } + + default List getEntryLocationDBPath() { + return Collections.emptyList(); + } + /** * Class for describing location of a generic inconsistency. Implementations should * ensure that detail is populated with an exception which adequately describes the diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/storage/ldb/DbLedgerStorage.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/storage/ldb/DbLedgerStorage.java index e02615df63d..ce9e8370ea5 100644 --- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/storage/ldb/DbLedgerStorage.java +++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/storage/ldb/DbLedgerStorage.java @@ -26,6 +26,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.util.concurrent.DefaultThreadFactory; @@ -34,7 +35,9 @@ import java.io.IOException; import java.util.ArrayList; import java.util.EnumSet; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.PrimitiveIterator.OfLong; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -542,6 +545,47 @@ public boolean isMinorGcSuspended() { return ledgerStorageList.stream().allMatch(SingleDirectoryDbLedgerStorage::isMinorGcSuspended); } + @Override + public void entryLocationCompact() { + ledgerStorageList.forEach(SingleDirectoryDbLedgerStorage::entryLocationCompact); + } + + @Override + public void entryLocationCompact(List locations) { + for (SingleDirectoryDbLedgerStorage ledgerStorage : ledgerStorageList) { + String entryLocation = ledgerStorage.getEntryLocationDBPath().get(0); + if (locations.contains(entryLocation)) { + ledgerStorage.entryLocationCompact(); + } + } + } + + @Override + public boolean isEntryLocationCompacting() { + return ledgerStorageList.stream().anyMatch(SingleDirectoryDbLedgerStorage::isEntryLocationCompacting); + } + + @Override + public Map isEntryLocationCompacting(List locations) { + HashMap isCompacting = Maps.newHashMap(); + for (SingleDirectoryDbLedgerStorage ledgerStorage : ledgerStorageList) { + String entryLocation = ledgerStorage.getEntryLocationDBPath().get(0); + if (locations.contains(entryLocation)) { + isCompacting.put(entryLocation, ledgerStorage.isEntryLocationCompacting()); + } + } + return isCompacting; + } + + @Override + public List getEntryLocationDBPath() { + List allEntryLocationDBPath = Lists.newArrayList(); + for (SingleDirectoryDbLedgerStorage ledgerStorage : ledgerStorageList) { + allEntryLocationDBPath.addAll(ledgerStorage.getEntryLocationDBPath()); + } + return allEntryLocationDBPath; + } + @Override public List getGarbageCollectionStatus() { return ledgerStorageList.stream() diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/storage/ldb/EntryLocationIndex.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/storage/ldb/EntryLocationIndex.java index 3f6d1ae55b2..335609aa860 100644 --- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/storage/ldb/EntryLocationIndex.java +++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/storage/ldb/EntryLocationIndex.java @@ -48,6 +48,7 @@ public class EntryLocationIndex implements Closeable { private final KeyValueStorage locationsDb; private final ConcurrentLongHashSet deletedLedgers = ConcurrentLongHashSet.newBuilder().build(); private final EntryLocationIndexStats stats; + private boolean isCompacting; public EntryLocationIndex(ServerConfiguration conf, KeyValueStorageFactory storageFactory, String basePath, StatsLogger stats) throws IOException { @@ -189,6 +190,23 @@ public void delete(long ledgerId) throws IOException { deletedLedgers.add(ledgerId); } + public String getEntryLocationDBPath() { + return locationsDb.getDBPath(); + } + + public void compact() throws IOException { + try { + isCompacting = true; + locationsDb.compact(); + } finally { + isCompacting = false; + } + } + + public boolean isCompacting() { + return isCompacting; + } + public void removeOffsetFromDeletedLedgers() throws IOException { LongPairWrapper firstKeyWrapper = LongPairWrapper.get(-1, -1); LongPairWrapper lastKeyWrapper = LongPairWrapper.get(-1, -1); diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/storage/ldb/KeyValueStorage.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/storage/ldb/KeyValueStorage.java index 08e7ad7396f..ab724d73bb5 100644 --- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/storage/ldb/KeyValueStorage.java +++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/storage/ldb/KeyValueStorage.java @@ -106,6 +106,16 @@ public interface KeyValueStorage extends Closeable { */ default void compact(byte[] firstKey, byte[] lastKey) throws IOException {} + /** + * Compact storage full range. + */ + default void compact() throws IOException {} + + /** + * Get storage path. + */ + String getDBPath(); + /** * Get an iterator over to scan sequentially through all the keys in the * database. diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/storage/ldb/KeyValueStorageRocksDB.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/storage/ldb/KeyValueStorageRocksDB.java index d402e66e118..fd870506939 100644 --- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/storage/ldb/KeyValueStorageRocksDB.java +++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/storage/ldb/KeyValueStorageRocksDB.java @@ -51,6 +51,7 @@ import org.rocksdb.Env; import org.rocksdb.InfoLogLevel; import org.rocksdb.LRUCache; +import org.rocksdb.LiveFileMetaData; import org.rocksdb.Options; import org.rocksdb.OptionsUtil; import org.rocksdb.ReadOptions; @@ -84,6 +85,8 @@ public class KeyValueStorageRocksDB implements KeyValueStorage { private final ReadOptions optionDontCache; private final WriteBatch emptyBatch; + private String dbPath; + private static final String ROCKSDB_LOG_PATH = "dbStorage_rocksDB_logPath"; private static final String ROCKSDB_LOG_LEVEL = "dbStorage_rocksDB_logLevel"; private static final String ROCKSDB_LZ4_COMPRESSION_ENABLED = "dbStorage_rocksDB_lz4CompressionEnabled"; @@ -158,13 +161,13 @@ private RocksDB initializeRocksDBWithConfFile(String basePath, String subPath, D log.info("RocksDB<{}> log path: {}", subPath, logPathSetting); dbOptions.setDbLogDir(logPathSetting.toString()); } - String path = FileSystems.getDefault().getPath(basePath, subPath).toFile().toString(); + this.dbPath = FileSystems.getDefault().getPath(basePath, subPath).toFile().toString(); this.options = dbOptions; this.columnFamilyDescriptors = cfDescs; if (readOnly) { - return RocksDB.openReadOnly(dbOptions, path, cfDescs, cfHandles); + return RocksDB.openReadOnly(dbOptions, dbPath, cfDescs, cfHandles); } else { - return RocksDB.open(dbOptions, path, cfDescs, cfHandles); + return RocksDB.open(dbOptions, dbPath, cfDescs, cfHandles); } } catch (RocksDBException e) { throw new IOException("Error open RocksDB database", e); @@ -241,7 +244,7 @@ private RocksDB initializeRocksDBWithBookieConf(String basePath, String subPath, log.info("RocksDB<{}> log path: {}", subPath, logPathSetting); options.setDbLogDir(logPathSetting.toString()); } - String path = FileSystems.getDefault().getPath(basePath, subPath).toFile().toString(); + this.dbPath = FileSystems.getDefault().getPath(basePath, subPath).toFile().toString(); // Configure log level String logLevel = conf.getString(ROCKSDB_LOG_LEVEL, "info"); @@ -268,9 +271,9 @@ private RocksDB initializeRocksDBWithBookieConf(String basePath, String subPath, this.options = options; try { if (readOnly) { - return RocksDB.openReadOnly(options, path); + return RocksDB.openReadOnly(options, dbPath); } else { - return RocksDB.open(options, path); + return RocksDB.open(options, dbPath); } } catch (RocksDBException e) { throw new IOException("Error open RocksDB database", e); @@ -365,6 +368,11 @@ public void delete(byte[] key) throws IOException { } } + @Override + public String getDBPath() { + return dbPath; + } + @Override public void compact(byte[] firstKey, byte[] lastKey) throws IOException { try { @@ -374,6 +382,36 @@ public void compact(byte[] firstKey, byte[] lastKey) throws IOException { } } + @Override + public void compact() throws IOException { + try { + final long start = System.currentTimeMillis(); + final int oriRocksDBFileCount = db.getLiveFilesMetaData().size(); + final long oriRocksDBSize = getRocksDBSize(); + log.info("Starting RocksDB {} compact, current RocksDB hold {} files and {} Bytes.", + db.getName(), oriRocksDBFileCount, oriRocksDBSize); + + db.compactRange(); + + final long end = System.currentTimeMillis(); + final int rocksDBFileCount = db.getLiveFilesMetaData().size(); + final long rocksDBSize = getRocksDBSize(); + log.info("RocksDB {} compact finished {} ms, space reduced {} Bytes, current hold {} files and {} Bytes.", + db.getName(), end - start, oriRocksDBSize - rocksDBSize, rocksDBFileCount, rocksDBSize); + } catch (RocksDBException e) { + throw new IOException("Error in RocksDB compact", e); + } + } + + private long getRocksDBSize() { + List liveFilesMetaData = db.getLiveFilesMetaData(); + long rocksDBFileSize = 0L; + for (LiveFileMetaData fileMetaData : liveFilesMetaData) { + rocksDBFileSize += fileMetaData.size(); + } + return rocksDBFileSize; + } + @Override public void sync() throws IOException { try { diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/storage/ldb/SingleDirectoryDbLedgerStorage.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/storage/ldb/SingleDirectoryDbLedgerStorage.java index 565732d4284..635a2727f9d 100644 --- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/storage/ldb/SingleDirectoryDbLedgerStorage.java +++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/storage/ldb/SingleDirectoryDbLedgerStorage.java @@ -298,6 +298,34 @@ public boolean isMinorGcSuspended() { return gcThread.isMinorGcSuspend(); } + @Override + public void entryLocationCompact() { + if (entryLocationIndex.isCompacting()) { + // RocksDB already running compact. + return; + } + cleanupExecutor.execute(() -> { + // There can only be one single cleanup task running because the cleanupExecutor + // is single-threaded + try { + log.info("Trigger entry location index RocksDB compact."); + entryLocationIndex.compact(); + } catch (Throwable t) { + log.warn("Failed to trigger entry location index RocksDB compact", t); + } + }); + } + + @Override + public boolean isEntryLocationCompacting() { + return entryLocationIndex.isCompacting(); + } + + @Override + public List getEntryLocationDBPath() { + return Lists.newArrayList(entryLocationIndex.getEntryLocationDBPath()); + } + @Override public void shutdown() throws InterruptedException { try { 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 4c7b787405e..823cf486524 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 @@ -64,6 +64,7 @@ import org.apache.bookkeeper.server.http.service.SuspendCompactionService; import org.apache.bookkeeper.server.http.service.TriggerAuditService; import org.apache.bookkeeper.server.http.service.TriggerGCService; +import org.apache.bookkeeper.server.http.service.TriggerLocationCompactService; import org.apache.bookkeeper.server.http.service.WhoIsAuditorService; import org.apache.bookkeeper.stats.StatsProvider; import org.apache.zookeeper.KeeperException; @@ -235,6 +236,8 @@ public HttpEndpointService provideHttpEndpointService(ApiType type) { return new SuspendCompactionService(bookieServer); case RESUME_GC_COMPACTION: return new ResumeCompactionService(bookieServer); + case TRIGGER_ENTRY_LOCATION_COMPACT: + return new TriggerLocationCompactService(bookieServer); // autorecovery case AUTORECOVERY_STATUS: diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/TriggerLocationCompactService.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/TriggerLocationCompactService.java new file mode 100644 index 00000000000..95b7f277506 --- /dev/null +++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/server/http/service/TriggerLocationCompactService.java @@ -0,0 +1,143 @@ +/* + * 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 com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.bookkeeper.bookie.LedgerStorage; +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.proto.BookieServer; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * HttpEndpointService that handle force trigger entry location compact requests. + * + *

The PUT method will trigger entry location compact on current bookie. + * + *

The GET method will get the entry location compact running or not. + * Output would be like: + * { + * "/data1/bookkeeper/ledgers/current/locations" : "false", + * "/data2/bookkeeper/ledgers/current/locations" : "true", + * } + */ + +public class TriggerLocationCompactService implements HttpEndpointService { + + static final Logger LOG = LoggerFactory.getLogger(TriggerLocationCompactService.class); + + private final BookieServer bookieServer; + private final List entryLocationDBPath; + + public TriggerLocationCompactService(BookieServer bookieServer) { + this.bookieServer = checkNotNull(bookieServer); + this.entryLocationDBPath = bookieServer.getBookie().getLedgerStorage().getEntryLocationDBPath(); + } + + @Override + public HttpServiceResponse handle(HttpServiceRequest request) throws Exception { + HttpServiceResponse response = new HttpServiceResponse(); + LedgerStorage ledgerStorage = bookieServer.getBookie().getLedgerStorage(); + + if (HttpServer.Method.PUT.equals(request.getMethod())) { + String requestBody = request.getBody(); + String output = "Not trigger Entry Location RocksDB compact."; + + if (StringUtils.isBlank(requestBody)) { + output = "Empty request body"; + response.setBody(output); + response.setCode(HttpServer.StatusCode.BAD_REQUEST); + return response; + } + + try { + @SuppressWarnings("unchecked") + Map configMap = JsonUtil.fromJson(requestBody, HashMap.class); + Boolean isEntryLocationCompact = (Boolean) configMap + .getOrDefault("entryLocationRocksDBCompact", false); + String entryLocations = (String) configMap.getOrDefault("entryLocations", ""); + + if (!isEntryLocationCompact) { + // If entryLocationRocksDBCompact is false, doing nothing. + response.setBody(output); + response.setCode(HttpServer.StatusCode.OK); + return response; + } + if (StringUtils.isNotBlank(entryLocations)) { + // Specified trigger RocksDB compact entryLocations. + Set locations = Sets.newHashSet(entryLocations.trim().split(",")); + if (CollectionUtils.isSubCollection(locations, entryLocationDBPath)) { + ledgerStorage.entryLocationCompact(Lists.newArrayList(locations)); + output = String.format("Triggered entry Location RocksDB: %s compact on bookie:%s.", + entryLocations, bookieServer.getBookieId()); + response.setCode(HttpServer.StatusCode.OK); + } else { + output = String.format("Specified trigger compact entryLocations: %s is invalid. " + + "Bookie entry location RocksDB path: %s.", entryLocations, entryLocationDBPath); + response.setCode(HttpServer.StatusCode.BAD_REQUEST); + } + } else { + // Not specified trigger compact entryLocations, trigger compact for all entry location. + ledgerStorage.entryLocationCompact(); + output = "Triggered entry Location RocksDB compact on bookie:" + bookieServer.getBookieId(); + response.setCode(HttpServer.StatusCode.OK); + } + } catch (JsonUtil.ParseJsonException ex) { + output = ex.getMessage(); + response.setCode(HttpServer.StatusCode.BAD_REQUEST); + LOG.warn("Trigger entry location index RocksDB compact failed, caused by: " + ex.getMessage()); + } + + String jsonResponse = JsonUtil.toJson(output); + if (LOG.isDebugEnabled()) { + LOG.debug("output body:" + jsonResponse); + } + response.setBody(jsonResponse); + return response; + } else if (HttpServer.Method.GET == request.getMethod()) { + Map compactStatus = ledgerStorage.isEntryLocationCompacting(entryLocationDBPath); + String jsonResponse = JsonUtil.toJson(compactStatus); + if (LOG.isDebugEnabled()) { + LOG.debug("output body:" + jsonResponse); + } + response.setBody(jsonResponse); + response.setCode(HttpServer.StatusCode.OK); + return response; + } else { + response.setCode(HttpServer.StatusCode.METHOD_NOT_ALLOWED); + response.setBody("Not found method. Should be PUT to trigger entry location compact," + + " Or GET to get entry location compact state."); + return response; + } + } +} \ No newline at end of file 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 5fb1679810a..fdab2f8f9ad 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 @@ -23,17 +23,22 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; +import static org.powermock.api.mockito.PowerMockito.spy; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.util.concurrent.UncheckedExecutionException; import java.io.File; +import java.lang.reflect.Field; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.Future; import lombok.Cleanup; import org.apache.bookkeeper.bookie.BookieResources; +import org.apache.bookkeeper.bookie.LedgerStorage; import org.apache.bookkeeper.client.BookKeeper; import org.apache.bookkeeper.client.ClientUtil; import org.apache.bookkeeper.client.LedgerHandle; @@ -50,6 +55,7 @@ import org.apache.bookkeeper.meta.LedgerUnderreplicationManager; import org.apache.bookkeeper.meta.MetadataBookieDriver; import org.apache.bookkeeper.net.BookieSocketAddress; +import org.apache.bookkeeper.proto.BookieServer; import org.apache.bookkeeper.replication.AuditorElector; import org.apache.bookkeeper.server.http.service.BookieInfoService; import org.apache.bookkeeper.server.http.service.BookieSanityService; @@ -1087,4 +1093,79 @@ public void testSuspendCompaction() throws Exception { assertEquals(responseMap7.get("isMajorGcSuspended"), "false"); assertEquals(responseMap7.get("isMinorGcSuspended"), "false"); } + + @Test + public void testTriggerEntryLocationCompactService() throws Exception { + BookieServer bookieServer = serverByIndex(numberOfBookies - 1); + LedgerStorage spyLedgerStorage = spy(bookieServer.getBookie().getLedgerStorage()); + List dbLocationPath = Lists.newArrayList("/data1/bookkeeper/ledgers/current/locations", + "/data2/bookkeeper/ledgers/current/locations"); + when(spyLedgerStorage.getEntryLocationDBPath()) + .thenReturn(dbLocationPath); + + HashMap statusMap = Maps.newHashMap(); + statusMap.put("/data1/bookkeeper/ledgers/current/locations", false); + statusMap.put("/data2/bookkeeper/ledgers/current/locations", true); + when(spyLedgerStorage.isEntryLocationCompacting(dbLocationPath)) + .thenReturn(statusMap); + + Field ledgerStorageField = bookieServer.getBookie().getClass().getDeclaredField("ledgerStorage"); + ledgerStorageField.setAccessible(true); + ledgerStorageField.set(bookieServer.getBookie(), spyLedgerStorage); + + HttpEndpointService triggerEntryLocationCompactService = bkHttpServiceProvider + .provideHttpEndpointService(HttpServer.ApiType.TRIGGER_ENTRY_LOCATION_COMPACT); + + // 1. Put + // 1.1 Trigger all entry location rocksDB compact, should return OK + HttpServiceRequest request1 = new HttpServiceRequest("{\"entryLocationRocksDBCompact\":true}", + HttpServer.Method.PUT, null); + HttpServiceResponse response1 = triggerEntryLocationCompactService.handle(request1); + assertEquals(HttpServer.StatusCode.OK.getValue(), response1.getStatusCode()); + LOG.info("Get response: {}", response1.getBody()); + + // 1.2 Specified trigger entry location rocksDB compact, should return OK + String body2 = "{\"entryLocationRocksDBCompact\":true,\"entryLocations\"" + + ":\"/data1/bookkeeper/ledgers/current/locations\"}"; + HttpServiceRequest request2 = new HttpServiceRequest(body2, HttpServer.Method.PUT, null); + HttpServiceResponse response2 = triggerEntryLocationCompactService.handle(request2); + assertEquals(HttpServer.StatusCode.OK.getValue(), response2.getStatusCode()); + LOG.info("Get response: {}", response2.getBody()); + assertTrue(response2.getBody().contains("Triggered entry Location RocksDB")); + + // 1.3 Specified invalid entry location rocksDB compact, should return BAD_REQUEST + String body3 = "{\"entryLocationRocksDBCompact\":true,\"entryLocations\"" + + ":\"/invalid1/locations,/data2/bookkeeper/ledgers/current/locations\"}"; + HttpServiceRequest request3 = new HttpServiceRequest(body3, HttpServer.Method.PUT, null); + HttpServiceResponse response3 = triggerEntryLocationCompactService.handle(request3); + assertEquals(HttpServer.StatusCode.BAD_REQUEST.getValue(), response3.getStatusCode()); + LOG.info("Get response: {}", response3.getBody()); + assertTrue(response3.getBody().contains("is invalid")); + + // 1.4 Some rocksDB is running compact, should return OK + String body4 = "{\"entryLocationRocksDBCompact\":true,\"entryLocations\"" + + ":\"/data1/bookkeeper/ledgers/current/locations,/data2/bookkeeper/ledgers/current/locations\"}"; + HttpServiceRequest request4 = new HttpServiceRequest(body4, HttpServer.Method.PUT, null); + HttpServiceResponse response4 = triggerEntryLocationCompactService.handle(request4); + assertEquals(HttpServer.StatusCode.OK.getValue(), response4.getStatusCode()); + LOG.info("Get response: {}", response4.getBody()); + + // 1.5 Put, empty body, should return BAD_REQUEST + HttpServiceRequest request5 = new HttpServiceRequest(null, HttpServer.Method.PUT, null); + HttpServiceResponse response5 = triggerEntryLocationCompactService.handle(request5); + assertEquals(HttpServer.StatusCode.BAD_REQUEST.getValue(), response5.getStatusCode()); + LOG.info("Get response: {}", response5.getBody()); + + // 2. GET, should return OK + HttpServiceRequest request6 = new HttpServiceRequest(null, HttpServer.Method.GET, null); + HttpServiceResponse response6 = triggerEntryLocationCompactService.handle(request6); + assertEquals(HttpServer.StatusCode.OK.getValue(), response6.getStatusCode()); + assertTrue(response6.getBody().contains("\"/data2/bookkeeper/ledgers/current/locations\" : true")); + assertTrue(response6.getBody().contains("\"/data1/bookkeeper/ledgers/current/locations\" : false")); + + // 3. POST, should return NOT_FOUND + HttpServiceRequest request7 = new HttpServiceRequest(null, HttpServer.Method.POST, null); + HttpServiceResponse response7 = triggerEntryLocationCompactService.handle(request7); + assertEquals(HttpServer.StatusCode.METHOD_NOT_ALLOWED.getValue(), response7.getStatusCode()); + } } diff --git a/site3/website/docs/admin/http.md b/site3/website/docs/admin/http.md index c5385d3fefb..73777ac1287 100644 --- a/site3/website/docs/admin/http.md +++ b/site3/website/docs/admin/http.md @@ -476,6 +476,47 @@ Currently all the HTTP endpoints could be divided into these 5 components: |503 | Bookie is not ready | * Body: <empty> +### Endpoint: /api/v1/bookie/entry_location_compact +1. Method: PUT + * Description: trigger entry location index rocksDB compact. Trigger all entry location rocksDB compact, if entryLocations not be specified. + * Parameters: + + | Name | Type | Required | Description | + |:-----|:-----|:---------|:------------| + |entryLocationRocksDBCompact | String | Yes | Configuration name(key) | + |entryLocations | String | no | entry location rocksDB path | + * Body: + ```json + { + "entryLocationRocksDBCompact": "true", + "entryLocations":"/data1/bookkeeper/ledgers/current/locations,/data2/bookkeeper/ledgers/current/locations" + } + ``` + * Response: + + | Code | Description | + |:-------|:------------| + |200 | Successful operation | + |403 | Permission denied | + |405 | Method Not Allowed | + +2. Method: GET + * Description: All entry location index rocksDB compact status on bookie. true for is running. + * Response: + + | Code | Description | + |:-------|:------------| + |200 | Successful operation | + |403 | Permission denied | + |405 | Method Not Allowed | + * Body: + ```json + { + "/data1/bookkeeper/ledgers/current/locations" : true, + "/data2/bookkeeper/ledgers/current/locations" : false + } + ``` + ## Auto recovery