This is an automated email from the ASF dual-hosted git repository.
zhaijia 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 67f8362 Migrate command `readledger`
67f8362 is described below
commit 67f83620eb8ed93bf73322293deb1a1bde8d09c7
Author: Yong Zhang <[email protected]>
AuthorDate: Wed Apr 3 21:30:16 2019 +0800
Migrate command `readledger`
Descriptions of the changes in this PR:
#2040
- Using `bkctl` run `readledger`
```
Read a range of entries from a ledger.
Usage: bkctl bookie readledger [flags]
Flags:
-b, --bookie
Only read from a specific bookie
-ef, --entryformatter
Set entry formatter
-fe, --firstentryid
First Entry ID
-r, --force-recovery
Ensure the ledger is properly closed before reading
-le, --lastentryid
Last Entry ID
-l, --ledgerid
Ledger ID
-lf, --ledgeridformatter
Set ledger id formatter
-m, --msg
Print message body
-h, --help
Display help information
```
Reviewers: Jia Zhai <[email protected]>, Sijie Guo <[email protected]>
This closes #2041 from zymap/command-readledger and squashes the following
commits:
56b0a4581 [Yong Zhang] Remove unused import
30dafa85f [Yong Zhang] Merge branch 'master' into command-readledger
bfbd6b023 [Yong Zhang] Migrate command `decommission`
d40b8b69f [Yong Zhang] Migrate command `readlog`
95d145a15 [Yong Zhang] Migrate command `nukeexistingcluster`
e2b1dc7f3 [Yong Zhang] Migrate command `listunderreplicated`
c465c4761 [Yong Zhang] Remove unused import
0988e12c7 [bd2019us] ISSUE #2023: change cached thread pool to fixed thread
pool
6a6d7bbd9 [Yong Zhang] Migrate command `initnewcluster`
931df8c2c [Sijie Guo] Merge branch 'master' into command-readledger
c391fe58d [Yong Zhang] Migrate command `readlogmetadata`
120d67737 [Yong Zhang] Migrate command `lostbookierecoverydelay`
bf66235e5 [Yong Zhang] Migrate command `deleteledger`
87e6644f2 [Yong Zhang] Fix some conflict
5ae05f0d2 [Yong Zhang] Migrate command `readledger`
751e55fa4 [Arvin] ISSUE #2020: close db properly to avoid open RocksDB
failure at the second time
138a7ae85 [Yong Zhang] Migrate command `metadataformat`
b043d1694 [Yong Zhang] Migrate command `listledgers`
4573285db [Ivan Kelly] Docker autobuild hook
e3d807a32 [Like] Fix IDE complain as there are multi choices for error code
9524a9f4a [Yong Zhang] Migrate command `readjournal`
6c3f33f55 [Yong Zhang] Fix when met unexpect entry id crashed
e35a108c7 [Like] Fix error message for unrecognized number-of-bookies
5902ee27b [Boyang Jerry Peng] fix potential NPE when releasing entry that
is null
6aa73ce05 [Ivan Kelly] [RELEASE] Update website to include documentation
for 4.8.2
1448d12aa [Yong Zhang] Migrate command `listfilesondisk`
4de598379 [Yong Zhang] Issue #1987: Migrate command
`convert-to-interleaved-storage`
468743e7e [Matteo Merli] In DbLedgerStorage use default values when config
key is present but empty
f26a4cae0 [Ivan Kelly] Release notes for v4.8.2
ec2636cd2 [Yong Zhang] Issue #1985: Migrate command `convert-to-db-storage`
8cc7239ac [Yong Zhang] Issue #1982: Migrate command `bookiesanity`
fa90f0185 [Yong Zhang] Issue #1980: Migrate command `ledger` from shell to
bkctl
---
.../org/apache/bookkeeper/bookie/BookieShell.java | 104 ++-------
.../cli/commands/bookie/ReadLedgerCommand.java | 244 +++++++++++++++++++++
.../tools/cli/commands/BookieCommandGroup.java | 2 +
.../cli/commands/bookie/ReadLedgerCommandTest.java | 169 ++++++++++++++
4 files changed, 428 insertions(+), 91 deletions(-)
diff --git
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieShell.java
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieShell.java
index 870d31b..98f276c 100644
---
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieShell.java
+++
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieShell.java
@@ -27,11 +27,6 @@ import static
org.apache.bookkeeper.tools.cli.helpers.CommandHelpers.getBookieSo
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.UncheckedExecutionException;
-import io.netty.buffer.ByteBufUtil;
-import io.netty.buffer.UnpooledByteBufAllocator;
-import io.netty.channel.EventLoopGroup;
-import io.netty.channel.nio.NioEventLoopGroup;
-import io.netty.util.concurrent.DefaultThreadFactory;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
@@ -50,7 +45,6 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -58,13 +52,8 @@ import java.util.Optional;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
-import java.util.stream.LongStream;
-
import org.apache.bookkeeper.bookie.BookieException.CookieNotFoundException;
import org.apache.bookkeeper.bookie.BookieException.InvalidCookieException;
import org.apache.bookkeeper.bookie.storage.ldb.LocationsIndexRebuildOp;
@@ -73,11 +62,9 @@ import
org.apache.bookkeeper.client.BKException.MetaStoreException;
import org.apache.bookkeeper.client.BookKeeper;
import org.apache.bookkeeper.client.BookKeeperAdmin;
import org.apache.bookkeeper.client.LedgerEntry;
-import org.apache.bookkeeper.client.LedgerHandle;
import org.apache.bookkeeper.client.UpdateLedgerOp;
import org.apache.bookkeeper.client.api.LedgerMetadata;
import org.apache.bookkeeper.common.annotation.InterfaceAudience.Private;
-import org.apache.bookkeeper.common.util.OrderedExecutor;
import org.apache.bookkeeper.conf.ClientConfiguration;
import org.apache.bookkeeper.conf.ServerConfiguration;
import org.apache.bookkeeper.discover.RegistrationManager;
@@ -86,12 +73,8 @@ import org.apache.bookkeeper.meta.LedgerMetadataSerDe;
import org.apache.bookkeeper.meta.LedgerUnderreplicationManager;
import org.apache.bookkeeper.meta.zk.ZKMetadataDriverBase;
import org.apache.bookkeeper.net.BookieSocketAddress;
-import org.apache.bookkeeper.proto.BookieClient;
-import org.apache.bookkeeper.proto.BookieClientImpl;
-import org.apache.bookkeeper.proto.BookieProtocol;
import org.apache.bookkeeper.replication.AuditorElector;
import org.apache.bookkeeper.replication.ReplicationException;
-import org.apache.bookkeeper.stats.NullStatsLogger;
import
org.apache.bookkeeper.tools.cli.commands.autorecovery.ListUnderReplicatedCommand;
import
org.apache.bookkeeper.tools.cli.commands.autorecovery.LostBookieRecoveryDelayCommand;
import
org.apache.bookkeeper.tools.cli.commands.bookie.ConvertToDBStorageCommand;
@@ -103,6 +86,7 @@ import
org.apache.bookkeeper.tools.cli.commands.bookie.LedgerCommand;
import org.apache.bookkeeper.tools.cli.commands.bookie.ListFilesOnDiscCommand;
import org.apache.bookkeeper.tools.cli.commands.bookie.ListLedgersCommand;
import org.apache.bookkeeper.tools.cli.commands.bookie.ReadJournalCommand;
+import org.apache.bookkeeper.tools.cli.commands.bookie.ReadLedgerCommand;
import org.apache.bookkeeper.tools.cli.commands.bookie.ReadLogCommand;
import org.apache.bookkeeper.tools.cli.commands.bookie.ReadLogMetadataCommand;
import org.apache.bookkeeper.tools.cli.commands.bookie.SanityTestCommand;
@@ -772,91 +756,29 @@ public class BookieShell implements Tool {
@Override
int runCmd(CommandLine cmdLine) throws Exception {
final long ledgerId = getOptionLedgerIdValue(cmdLine, "ledgerid",
-1);
- if (ledgerId == -1) {
- System.err.println("Must specify a ledger id");
- return -1;
- }
-
final long firstEntry = getOptionLongValue(cmdLine,
"firstentryid", 0);
long lastEntry = getOptionLongValue(cmdLine, "lastentryid", -1);
boolean printMsg = cmdLine.hasOption("m");
boolean forceRecovery = cmdLine.hasOption("r");
final BookieSocketAddress bookie;
+ String bookieAddress;
if (cmdLine.hasOption("b")) {
// A particular bookie was specified
- bookie = new BookieSocketAddress(cmdLine.getOptionValue("b"));
+ bookieAddress = cmdLine.getOptionValue("b");
} else {
- bookie = null;
- }
-
- ClientConfiguration conf = new ClientConfiguration();
- conf.addConfiguration(bkConf);
-
- try (BookKeeperAdmin bk = new BookKeeperAdmin(conf)) {
- if (forceRecovery) {
- // Force the opening of the ledger to trigger recovery
- try (LedgerHandle lh = bk.openLedger(ledgerId)) {
- if (lastEntry == -1 || lastEntry >
lh.getLastAddConfirmed()) {
- lastEntry = lh.getLastAddConfirmed();
- }
- }
- }
-
- if (bookie == null) {
- // No bookie was specified, use normal bk client
- Iterator<LedgerEntry> entries = bk.readEntries(ledgerId,
firstEntry, lastEntry).iterator();
- while (entries.hasNext()) {
- LedgerEntry entry = entries.next();
- formatEntry(entry, printMsg);
- }
- } else {
- // Use BookieClient to target a specific bookie
- EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
- OrderedExecutor executor = OrderedExecutor.newBuilder()
- .numThreads(1)
- .name("BookieClientScheduler")
- .build();
-
- ScheduledExecutorService scheduler =
Executors.newSingleThreadScheduledExecutor(
- new
DefaultThreadFactory("BookKeeperClientSchedulerPool"));
-
- BookieClient bookieClient = new BookieClientImpl(conf,
eventLoopGroup,
- UnpooledByteBufAllocator.DEFAULT, executor,
scheduler, NullStatsLogger.INSTANCE);
-
- LongStream.range(firstEntry, lastEntry).forEach(entryId ->
{
- CompletableFuture<Void> future = new
CompletableFuture<>();
-
- bookieClient.readEntry(bookie, ledgerId, entryId,
- (rc, ledgerId1, entryId1, buffer, ctx) -> {
- if (rc != BKException.Code.OK) {
- LOG.error("Failed to read entry {} -- {}",
entryId1, BKException.getMessage(rc));
-
future.completeExceptionally(BKException.create(rc));
- return;
- }
-
- System.out.println("--------- Lid=" +
ledgerIdFormatter.formatLedgerId(ledgerId)
- + ", Eid=" + entryId + " ---------");
- if (printMsg) {
- System.out.println("Data: " +
ByteBufUtil.prettyHexDump(buffer));
- }
-
- future.complete(null);
- }, null, BookieProtocol.FLAG_NONE);
-
- try {
- future.get();
- } catch (Exception e) {
- LOG.error("Error future.get while reading entries
from ledger {}", ledgerId, e);
- }
- });
-
- eventLoopGroup.shutdownGracefully();
- executor.shutdown();
- bookieClient.close();
- }
+ bookieAddress = null;
}
+ ReadLedgerCommand cmd = new ReadLedgerCommand(entryFormatter,
ledgerIdFormatter);
+ ReadLedgerCommand.ReadLedgerFlags flags = new
ReadLedgerCommand.ReadLedgerFlags();
+ flags.bookieAddresss(bookieAddress);
+ flags.firstEntryId(firstEntry);
+ flags.forceRecovery(forceRecovery);
+ flags.lastEntryId(lastEntry);
+ flags.ledgerId(ledgerId);
+ flags.msg(printMsg);
+ cmd.apply(bkConf, flags);
return 0;
}
diff --git
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/bookie/ReadLedgerCommand.java
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/bookie/ReadLedgerCommand.java
new file mode 100644
index 0000000..98c1b11
--- /dev/null
+++
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/bookie/ReadLedgerCommand.java
@@ -0,0 +1,244 @@
+/*
+ * 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.tools.cli.commands.bookie;
+
+import com.beust.jcommander.Parameter;
+import com.google.common.util.concurrent.UncheckedExecutionException;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.UnpooledByteBufAllocator;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.util.concurrent.DefaultThreadFactory;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.stream.LongStream;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import org.apache.bookkeeper.client.BKException;
+import org.apache.bookkeeper.client.BookKeeperAdmin;
+import org.apache.bookkeeper.client.LedgerEntry;
+import org.apache.bookkeeper.client.LedgerHandle;
+import org.apache.bookkeeper.common.util.OrderedExecutor;
+import org.apache.bookkeeper.conf.ClientConfiguration;
+import org.apache.bookkeeper.conf.ServerConfiguration;
+import org.apache.bookkeeper.net.BookieSocketAddress;
+import org.apache.bookkeeper.proto.BookieClient;
+import org.apache.bookkeeper.proto.BookieClientImpl;
+import org.apache.bookkeeper.proto.BookieProtocol;
+import org.apache.bookkeeper.stats.NullStatsLogger;
+import org.apache.bookkeeper.tools.cli.helpers.BookieCommand;
+import org.apache.bookkeeper.tools.framework.CliFlags;
+import org.apache.bookkeeper.tools.framework.CliSpec;
+import org.apache.bookkeeper.util.EntryFormatter;
+import org.apache.bookkeeper.util.LedgerIdFormatter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Command to read ledger entries.
+ */
+public class ReadLedgerCommand extends
BookieCommand<ReadLedgerCommand.ReadLedgerFlags> {
+
+ static final Logger LOG = LoggerFactory.getLogger(ReadLedgerCommand.class);
+
+ private static final String NAME = "readledger";
+ private static final String DESC = "Read a range of entries from a
ledger.";
+
+ EntryFormatter entryFormatter;
+ LedgerIdFormatter ledgerIdFormatter;
+
+ public ReadLedgerCommand() {
+ this(new ReadLedgerFlags());
+ }
+
+ public ReadLedgerCommand(EntryFormatter entryFormatter, LedgerIdFormatter
ledgerIdFormatter) {
+ this(new ReadLedgerFlags());
+ this.ledgerIdFormatter = ledgerIdFormatter;
+ this.entryFormatter = entryFormatter;
+ }
+
+ private ReadLedgerCommand(ReadLedgerFlags flags) {
+ super(CliSpec.<ReadLedgerFlags>newBuilder()
+ .withName(NAME)
+ .withDescription(DESC)
+ .withFlags(flags)
+ .build());
+ }
+
+ /**
+ * Flags for read ledger command.
+ */
+ @Accessors(fluent = true)
+ @Setter
+ public static class ReadLedgerFlags extends CliFlags {
+
+ @Parameter(names = { "-m", "--msg" }, description = "Print message
body")
+ private boolean msg;
+
+ @Parameter(names = { "-l", "--ledgerid" }, description = "Ledger ID")
+ private long ledgerId = -1;
+
+ @Parameter(names = { "-fe", "--firstentryid" }, description = "First
Entry ID")
+ private long firstEntryId = -1;
+
+ @Parameter(names = { "-le", "--lastentryid" }, description = "Last
Entry ID")
+ private long lastEntryId = -1;
+
+ @Parameter(names = { "-r", "--force-recovery" },
+ description = "Ensure the ledger is properly closed before
reading")
+ private boolean forceRecovery;
+
+ @Parameter(names = { "-b", "--bookie" }, description = "Only read from
a specific bookie")
+ private String bookieAddresss;
+
+ @Parameter(names = { "-lf", "--ledgeridformatter" }, description =
"Set ledger id formatter")
+ private String ledgerIdFormatter;
+
+ @Parameter(names = { "-ef", "--entryformatter" }, description = "Set
entry formatter")
+ private String entryFormatter;
+ }
+
+ @Override
+ public boolean apply(ServerConfiguration conf, ReadLedgerFlags cmdFlags) {
+ if (cmdFlags.ledgerIdFormatter != null && ledgerIdFormatter == null) {
+ this.ledgerIdFormatter =
LedgerIdFormatter.newLedgerIdFormatter(cmdFlags.ledgerIdFormatter, conf);
+ } else if (ledgerIdFormatter == null) {
+ this.ledgerIdFormatter =
LedgerIdFormatter.newLedgerIdFormatter(conf);
+ }
+
+ if (cmdFlags.entryFormatter != null && entryFormatter == null) {
+ this.entryFormatter =
EntryFormatter.newEntryFormatter(cmdFlags.entryFormatter, conf);
+ } else if (entryFormatter == null) {
+ this.entryFormatter = EntryFormatter.newEntryFormatter(conf);
+ }
+
+ try {
+ return readledger(conf, cmdFlags);
+ } catch (Exception e) {
+ throw new UncheckedExecutionException(e.getMessage(), e);
+ }
+ }
+
+ private boolean readledger(ServerConfiguration serverConf, ReadLedgerFlags
flags)
+ throws InterruptedException, BKException, IOException {
+
+ long lastEntry = flags.lastEntryId;
+
+ final BookieSocketAddress bookie;
+ if (flags.bookieAddresss != null) {
+ // A particular bookie was specified
+ bookie = new BookieSocketAddress(flags.bookieAddresss);
+ } else {
+ bookie = null;
+ }
+
+ ClientConfiguration conf = new ClientConfiguration();
+ conf.addConfiguration(serverConf);
+
+ try (BookKeeperAdmin bk = new BookKeeperAdmin(conf)) {
+ if (flags.forceRecovery) {
+ // Force the opening of the ledger to trigger recovery
+ try (LedgerHandle lh = bk.openLedger(flags.ledgerId)) {
+ if (lastEntry == -1 || lastEntry >
lh.getLastAddConfirmed()) {
+ lastEntry = lh.getLastAddConfirmed();
+ }
+ }
+ }
+
+ if (bookie == null) {
+ // No bookie was specified, use normal bk client
+ Iterator<LedgerEntry> entries = bk.readEntries(flags.ledgerId,
flags.firstEntryId, lastEntry)
+ .iterator();
+ while (entries.hasNext()) {
+ LedgerEntry entry = entries.next();
+ formatEntry(entry, flags.msg);
+ }
+ } else {
+ // Use BookieClient to target a specific bookie
+ EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
+ OrderedExecutor executor = OrderedExecutor.newBuilder()
+ .numThreads(1)
+
.name("BookieClientScheduler")
+ .build();
+
+ ScheduledExecutorService scheduler =
Executors.newSingleThreadScheduledExecutor(
+ new DefaultThreadFactory("BookKeeperClientSchedulerPool"));
+
+ BookieClient bookieClient = new BookieClientImpl(conf,
eventLoopGroup, UnpooledByteBufAllocator.DEFAULT,
+ executor,
scheduler, NullStatsLogger.INSTANCE);
+
+ LongStream.range(flags.firstEntryId,
lastEntry).forEach(entryId -> {
+ CompletableFuture<Void> future = new CompletableFuture<>();
+
+ bookieClient.readEntry(bookie, flags.ledgerId, entryId,
+ (rc, ledgerId1, entryId1, buffer,
ctx) -> {
+ if (rc != BKException.Code.OK) {
+ LOG.error("Failed to read
entry {} -- {}", entryId1,
+
BKException.getMessage(rc));
+
future.completeExceptionally(BKException.create(rc));
+ return;
+ }
+
+ System.out.println(
+ "--------- Lid=" +
ledgerIdFormatter.formatLedgerId(flags.ledgerId)
+ + ", Eid=" + entryId + "
---------");
+ if (flags.msg) {
+ System.out.println("Data: "
+ ByteBufUtil.prettyHexDump(buffer));
+ }
+
+ future.complete(null);
+ }, null, BookieProtocol.FLAG_NONE);
+
+ try {
+ future.get();
+ } catch (Exception e) {
+ LOG.error("Error future.get while reading entries from
ledger {}", flags.ledgerId, e);
+ }
+ });
+
+ eventLoopGroup.shutdownGracefully();
+ executor.shutdown();
+ bookieClient.close();
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Format the entry into a readable format.
+ *
+ * @param entry
+ * ledgerentry to print
+ * @param printMsg
+ * Whether printing the message body
+ */
+ private void formatEntry(LedgerEntry entry, boolean printMsg) {
+ long ledgerId = entry.getLedgerId();
+ long entryId = entry.getEntryId();
+ long entrySize = entry.getLength();
+ System.out.println("--------- Lid=" +
ledgerIdFormatter.formatLedgerId(ledgerId) + ", Eid=" + entryId
+ + ", EntrySize=" + entrySize + " ---------");
+ if (printMsg) {
+ entryFormatter.formatEntry(entry.getEntry());
+ }
+ }
+}
diff --git
a/tools/ledger/src/main/java/org/apache/bookkeeper/tools/cli/commands/BookieCommandGroup.java
b/tools/ledger/src/main/java/org/apache/bookkeeper/tools/cli/commands/BookieCommandGroup.java
index 2970c8c..51e579f 100644
---
a/tools/ledger/src/main/java/org/apache/bookkeeper/tools/cli/commands/BookieCommandGroup.java
+++
b/tools/ledger/src/main/java/org/apache/bookkeeper/tools/cli/commands/BookieCommandGroup.java
@@ -30,6 +30,7 @@ import
org.apache.bookkeeper.tools.cli.commands.bookie.LedgerCommand;
import org.apache.bookkeeper.tools.cli.commands.bookie.ListFilesOnDiscCommand;
import org.apache.bookkeeper.tools.cli.commands.bookie.ListLedgersCommand;
import org.apache.bookkeeper.tools.cli.commands.bookie.ReadJournalCommand;
+import org.apache.bookkeeper.tools.cli.commands.bookie.ReadLedgerCommand;
import org.apache.bookkeeper.tools.cli.commands.bookie.ReadLogCommand;
import org.apache.bookkeeper.tools.cli.commands.bookie.ReadLogMetadataCommand;
import org.apache.bookkeeper.tools.cli.commands.bookie.SanityTestCommand;
@@ -60,6 +61,7 @@ public class BookieCommandGroup extends
CliCommandGroup<BKFlags> {
.addCommand(new ListLedgersCommand())
.addCommand(new ConvertToInterleavedStorageCommand())
.addCommand(new ReadJournalCommand())
+ .addCommand(new ReadLedgerCommand())
.addCommand(new ReadLogCommand())
.addCommand(new ReadLogMetadataCommand())
.build();
diff --git
a/tools/ledger/src/test/java/org/apache/bookkeeper/tools/cli/commands/bookie/ReadLedgerCommandTest.java
b/tools/ledger/src/test/java/org/apache/bookkeeper/tools/cli/commands/bookie/ReadLedgerCommandTest.java
new file mode 100644
index 0000000..7eb651d
--- /dev/null
+++
b/tools/ledger/src/test/java/org/apache/bookkeeper/tools/cli/commands/bookie/ReadLedgerCommandTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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.tools.cli.commands.bookie;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.powermock.api.mockito.PowerMockito.mock;
+import static org.powermock.api.mockito.PowerMockito.verifyNew;
+import static org.powermock.api.mockito.PowerMockito.when;
+
+import io.netty.buffer.UnpooledByteBufAllocator;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.util.concurrent.DefaultThreadFactory;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import org.apache.bookkeeper.client.BookKeeperAdmin;
+import org.apache.bookkeeper.client.LedgerEntry;
+import org.apache.bookkeeper.client.LedgerHandle;
+import org.apache.bookkeeper.common.util.OrderedExecutor;
+import org.apache.bookkeeper.conf.ClientConfiguration;
+import org.apache.bookkeeper.conf.ServerConfiguration;
+import org.apache.bookkeeper.net.BookieSocketAddress;
+import org.apache.bookkeeper.proto.BookieClientImpl;
+import org.apache.bookkeeper.stats.NullStatsLogger;
+import org.apache.bookkeeper.tools.cli.helpers.BookieCommandTestBase;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+/**
+ * Unit test for {@link ReadLedgerCommand}.
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ ReadLedgerCommand.class, BookKeeperAdmin.class,
BookieSocketAddress.class, ClientConfiguration.class,
+ LedgerHandle.class, LedgerEntry.class, OrderedExecutor.class })
+public class ReadLedgerCommandTest extends BookieCommandTestBase {
+
+ @Mock
+ private BookieSocketAddress bookieSocketAddress;
+
+ @Mock
+ private ClientConfiguration clientConfiguration;
+
+ @Mock
+ private BookKeeperAdmin bookKeeperAdmin;
+
+ @Mock
+ private LedgerHandle ledgerHandle;
+
+ @Mock
+ private LedgerEntry entry;
+
+ @Mock
+ private NioEventLoopGroup nioEventLoopGroup;
+
+ @Mock
+ private OrderedExecutor orderedExecutor;
+
+ @Mock
+ private ScheduledExecutorService scheduledExecutorService;
+
+ @Mock
+ private DefaultThreadFactory defaultThreadFactory;
+
+ @Mock
+ private BookieClientImpl bookieClient;
+
+ public ReadLedgerCommandTest() {
+ super(3, 0);
+ }
+
+ @Override
+ public void setup() throws Exception {
+ super.setup();
+
+
PowerMockito.whenNew(ServerConfiguration.class).withNoArguments().thenReturn(conf);
+
PowerMockito.whenNew(BookieSocketAddress.class).withArguments(anyString()).thenReturn(bookieSocketAddress);
+
PowerMockito.whenNew(ClientConfiguration.class).withNoArguments().thenReturn(clientConfiguration);
+
PowerMockito.whenNew(BookKeeperAdmin.class).withParameterTypes(ClientConfiguration.class)
+
.withArguments(eq(clientConfiguration)).thenReturn(bookKeeperAdmin);
+
+ when(bookKeeperAdmin.openLedger(anyLong())).thenReturn(ledgerHandle);
+ when(ledgerHandle.getLastAddConfirmed()).thenReturn(1L);
+
+ List<LedgerEntry> entries = new LinkedList<>();
+ entries.add(entry);
+ when(entry.getLedgerId()).thenReturn(1L);
+ when(entry.getEntryId()).thenReturn(1L);
+ when(entry.getLength()).thenReturn(1L);
+
+ when(bookKeeperAdmin.readEntries(anyLong(), anyLong(),
anyLong())).thenReturn(entries);
+
+
PowerMockito.whenNew(NioEventLoopGroup.class).withNoArguments().thenReturn(nioEventLoopGroup);
+
+ PowerMockito.mockStatic(OrderedExecutor.class);
+ OrderedExecutor.Builder builder = mock(OrderedExecutor.Builder.class);
+ when(OrderedExecutor.newBuilder()).thenReturn(builder);
+ when(builder.numThreads(anyInt())).thenCallRealMethod();
+ when(builder.name(anyString())).thenCallRealMethod();
+ when(builder.build()).thenReturn(orderedExecutor);
+
+ PowerMockito.mockStatic(Executors.class);
+
PowerMockito.whenNew(DefaultThreadFactory.class).withArguments(anyString()).thenReturn(defaultThreadFactory);
+
when(Executors.newSingleThreadScheduledExecutor(eq(defaultThreadFactory))).thenReturn(scheduledExecutorService);
+
+ PowerMockito.whenNew(BookieClientImpl.class)
+ .withArguments(eq(clientConfiguration),
eq(nioEventLoopGroup), eq(UnpooledByteBufAllocator.DEFAULT),
+ eq(orderedExecutor),
eq(scheduledExecutorService), eq(NullStatsLogger.INSTANCE))
+ .thenReturn(bookieClient);
+
+
+ }
+
+ @Test
+ public void testWithoutBookieAddress() throws Exception {
+ ReadLedgerCommand cmd = new ReadLedgerCommand();
+ Assert.assertTrue(cmd.apply(bkFlags, new String[] { "-r" }));
+ verifyNew(ClientConfiguration.class, times(1)).withNoArguments();
+ verify(clientConfiguration, times(1)).addConfiguration(eq(conf));
+ verifyNew(BookKeeperAdmin.class,
times(1)).withArguments(eq(clientConfiguration));
+ verify(bookKeeperAdmin, times(1)).openLedger(anyLong());
+ verify(ledgerHandle, times(1)).getLastAddConfirmed();
+ verify(bookKeeperAdmin, times(1)).readEntries(anyLong(), anyLong(),
anyLong());
+ verify(entry, times(1)).getLedgerId();
+ verify(entry, times(1)).getEntryId();
+ verify(entry, times(1)).getLength();
+ }
+
+ @Test
+ public void testWithBookieAddress() throws Exception {
+ ReadLedgerCommand cmd = new ReadLedgerCommand();
+ Assert.assertTrue(cmd.apply(bkFlags, new String[] { "-b",
"localhost:9000" }));
+ verifyNew(NioEventLoopGroup.class, times(1)).withNoArguments();
+ verifyNew(DefaultThreadFactory.class,
times(1)).withArguments(anyString());
+ verifyNew(BookieClientImpl.class, times(1))
+ .withArguments(eq(clientConfiguration), eq(nioEventLoopGroup),
eq(UnpooledByteBufAllocator.DEFAULT),
+ eq(orderedExecutor), eq(scheduledExecutorService),
eq(NullStatsLogger.INSTANCE));
+ verify(nioEventLoopGroup, times(1)).shutdownGracefully();
+ verify(orderedExecutor, times(1)).shutdown();
+ verify(bookieClient, times(1)).close();
+ }
+
+}