This is an automated email from the ASF dual-hosted git repository.
sijie 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 1246826 Migrate command `recover`
1246826 is described below
commit 1246826ba749f57d3d428deb7606dd582836ca69
Author: Yong Zhang <[email protected]>
AuthorDate: Mon Apr 8 12:29:48 2019 +0800
Migrate command `recover`
Descriptions of the changes in this PR:
#2055
Reviewers: Jia Zhai <[email protected]>, Sijie Guo <[email protected]>
This closes #2056 from zymap/command-recover
---
.../org/apache/bookkeeper/bookie/BookieShell.java | 165 +------------
.../tools/cli/commands/bookies/RecoverCommand.java | 271 +++++++++++++++++++++
.../apache/bookkeeper/bookie/BookieShellTest.java | 3 +-
.../tools/cli/commands/BookiesCommandGroup.java | 2 +
.../cli/commands/bookies/RecoverCommandTest.java | 200 +++++++++++++++
5 files changed, 487 insertions(+), 154 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 9d2bba6..4446903 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
@@ -44,21 +44,16 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.apache.bookkeeper.bookie.BookieException.CookieNotFoundException;
import org.apache.bookkeeper.bookie.BookieException.InvalidCookieException;
import org.apache.bookkeeper.bookie.storage.ldb.LocationsIndexRebuildOp;
-import org.apache.bookkeeper.client.BKException;
-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;
@@ -67,7 +62,6 @@ import org.apache.bookkeeper.client.api.LedgerMetadata;
import org.apache.bookkeeper.common.annotation.InterfaceAudience.Private;
import org.apache.bookkeeper.conf.ClientConfiguration;
import org.apache.bookkeeper.conf.ServerConfiguration;
-import org.apache.bookkeeper.discover.RegistrationManager;
import org.apache.bookkeeper.meta.LedgerManager;
import org.apache.bookkeeper.meta.LedgerMetadataSerDe;
import org.apache.bookkeeper.meta.LedgerUnderreplicationManager;
@@ -97,6 +91,7 @@ import
org.apache.bookkeeper.tools.cli.commands.bookies.ListBookiesCommand;
import org.apache.bookkeeper.tools.cli.commands.bookies.MetaFormatCommand;
import
org.apache.bookkeeper.tools.cli.commands.bookies.NukeExistingClusterCommand;
import
org.apache.bookkeeper.tools.cli.commands.bookies.NukeExistingClusterCommand.NukeExistingClusterFlags;
+import org.apache.bookkeeper.tools.cli.commands.bookies.RecoverCommand;
import org.apache.bookkeeper.tools.cli.commands.client.DeleteLedgerCommand;
import org.apache.bookkeeper.tools.cli.commands.client.SimpleTestCommand;
import org.apache.bookkeeper.tools.cli.commands.cookie.CreateCookieCommand;
@@ -530,154 +525,18 @@ public class BookieShell implements Tool {
Long ledgerId = getOptionLedgerIdValue(cmdLine, "ledger", -1);
- // Get bookies list
- final String[] bookieStrs = args[0].split(",");
- final Set<BookieSocketAddress> bookieAddrs = new HashSet<>();
- for (String bookieStr : bookieStrs) {
- final String bookieStrParts[] = bookieStr.split(":");
- if (bookieStrParts.length != 2) {
- System.err.println("BookieSrcs has invalid bookie address
format (host:port expected) : "
- + bookieStr);
- return -1;
- }
- bookieAddrs.add(new BookieSocketAddress(bookieStrParts[0],
- Integer.parseInt(bookieStrParts[1])));
- }
-
- if (!force) {
- System.err.println("Bookies : " + bookieAddrs);
- if (!IOUtils.confirmPrompt("Are you sure to recover them :
(Y/N)")) {
- System.err.println("Give up!");
- return -1;
- }
- }
-
- LOG.info("Constructing admin");
- ClientConfiguration adminConf = new ClientConfiguration(bkConf);
- BookKeeperAdmin admin = new BookKeeperAdmin(adminConf);
- LOG.info("Construct admin : {}", admin);
- try {
- if (query) {
- return bkQuery(admin, bookieAddrs);
- }
- if (-1 != ledgerId) {
- return bkRecoveryLedger(admin, ledgerId, bookieAddrs,
dryrun, skipOpenLedgers, removeCookies);
- }
- return bkRecovery(admin, bookieAddrs, dryrun, skipOpenLedgers,
removeCookies);
- } finally {
- admin.close();
- }
- }
-
- private int bkQuery(BookKeeperAdmin bkAdmin, Set<BookieSocketAddress>
bookieAddrs)
- throws InterruptedException, BKException {
- SortedMap<Long, LedgerMetadata> ledgersContainBookies =
- bkAdmin.getLedgersContainBookies(bookieAddrs);
- System.err.println("NOTE: Bookies in inspection list are marked
with '*'.");
- for (Map.Entry<Long, LedgerMetadata> ledger :
ledgersContainBookies.entrySet()) {
- System.out.println("ledger " + ledger.getKey() + " : " +
ledger.getValue().getState());
- Map<Long, Integer> numBookiesToReplacePerEnsemble =
- inspectLedger(ledger.getValue(), bookieAddrs);
- System.out.print("summary: [");
- for (Map.Entry<Long, Integer> entry :
numBookiesToReplacePerEnsemble.entrySet()) {
- System.out.print(entry.getKey() + "=" + entry.getValue() +
", ");
- }
- System.out.println("]");
- System.out.println();
- }
- System.err.println("Done");
- return 0;
- }
-
- private Map<Long, Integer> inspectLedger(LedgerMetadata metadata,
Set<BookieSocketAddress> bookiesToInspect) {
- Map<Long, Integer> numBookiesToReplacePerEnsemble = new
TreeMap<Long, Integer>();
- for (Map.Entry<Long, ? extends List<BookieSocketAddress>> ensemble
:
- metadata.getAllEnsembles().entrySet()) {
- List<BookieSocketAddress> bookieList = ensemble.getValue();
- System.out.print(ensemble.getKey() + ":\t");
- int numBookiesToReplace = 0;
- for (BookieSocketAddress bookie : bookieList) {
- System.out.print(bookie);
- if (bookiesToInspect.contains(bookie)) {
- System.out.print("*");
- ++numBookiesToReplace;
- } else {
- System.out.print(" ");
- }
- System.out.print(" ");
- }
- System.out.println();
- numBookiesToReplacePerEnsemble.put(ensemble.getKey(),
numBookiesToReplace);
- }
- return numBookiesToReplacePerEnsemble;
- }
-
- private int bkRecoveryLedger(BookKeeperAdmin bkAdmin,
- long lid,
- Set<BookieSocketAddress> bookieAddrs,
- boolean dryrun,
- boolean skipOpenLedgers,
- boolean removeCookies)
- throws InterruptedException, BKException, KeeperException {
- bkAdmin.recoverBookieData(lid, bookieAddrs, dryrun,
skipOpenLedgers);
- if (removeCookies) {
- deleteCookies(bkAdmin.getConf(), bookieAddrs);
- }
- return 0;
- }
-
- private int bkRecovery(BookKeeperAdmin bkAdmin,
- Set<BookieSocketAddress> bookieAddrs,
- boolean dryrun,
- boolean skipOpenLedgers,
- boolean removeCookies)
- throws InterruptedException, BKException, KeeperException {
- bkAdmin.recoverBookieData(bookieAddrs, dryrun, skipOpenLedgers);
- if (removeCookies) {
- deleteCookies(bkAdmin.getConf(), bookieAddrs);
- }
- return 0;
- }
-
- private void deleteCookies(ClientConfiguration conf,
- Set<BookieSocketAddress> bookieAddrs)
throws BKException {
- ServerConfiguration serverConf = new ServerConfiguration(conf);
- try {
- runFunctionWithRegistrationManager(serverConf, rm -> {
- try {
- for (BookieSocketAddress addr : bookieAddrs) {
- deleteCookie(rm, addr);
- }
- } catch (Exception e) {
- throw new UncheckedExecutionException(e);
- }
- return null;
- });
- } catch (Exception e) {
- Throwable cause = e;
- if (e instanceof UncheckedExecutionException) {
- cause = e.getCause();
- }
- if (cause instanceof BKException) {
- throw (BKException) cause;
- } else {
- BKException bke = new MetaStoreException();
- bke.initCause(bke);
- throw bke;
- }
- }
- }
-
- private void deleteCookie(RegistrationManager rm,
- BookieSocketAddress bookieSrc) throws
BookieException {
- try {
- Versioned<Cookie> cookie =
Cookie.readFromRegistrationManager(rm, bookieSrc);
- cookie.getValue().deleteFromRegistrationManager(rm, bookieSrc,
cookie.getVersion());
- } catch (CookieNotFoundException nne) {
- LOG.warn("No cookie to remove for {} : ", bookieSrc, nne);
- }
+ RecoverCommand cmd = new RecoverCommand();
+ RecoverCommand.RecoverFlags flags = new
RecoverCommand.RecoverFlags();
+ flags.bookieAddress(args[0]);
+ flags.deleteCookie(removeCookies);
+ flags.dryRun(dryrun);
+ flags.force(force);
+ flags.ledger(ledgerId);
+ flags.skipOpenLedgers(skipOpenLedgers);
+ flags.query(query);
+ boolean result = cmd.apply(bkConf, flags);
+ return (result) ? 0 : -1;
}
-
}
/**
diff --git
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/bookies/RecoverCommand.java
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/bookies/RecoverCommand.java
new file mode 100644
index 0000000..ba4acf5
--- /dev/null
+++
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/bookies/RecoverCommand.java
@@ -0,0 +1,271 @@
+/*
+ * 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.bookies;
+
+import static
org.apache.bookkeeper.meta.MetadataDrivers.runFunctionWithRegistrationManager;
+
+import com.beust.jcommander.Parameter;
+import com.google.common.util.concurrent.UncheckedExecutionException;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import org.apache.bookkeeper.bookie.BookieException;
+import org.apache.bookkeeper.bookie.Cookie;
+import org.apache.bookkeeper.client.BKException;
+import org.apache.bookkeeper.client.BookKeeperAdmin;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
+import org.apache.bookkeeper.conf.ClientConfiguration;
+import org.apache.bookkeeper.conf.ServerConfiguration;
+import org.apache.bookkeeper.discover.RegistrationManager;
+import org.apache.bookkeeper.net.BookieSocketAddress;
+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.IOUtils;
+import org.apache.bookkeeper.versioning.Versioned;
+import org.apache.zookeeper.KeeperException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Command to ledger data recovery for failed bookie.
+ */
+public class RecoverCommand extends BookieCommand<RecoverCommand.RecoverFlags>
{
+
+ static final Logger LOG = LoggerFactory.getLogger(RecoverCommand.class);
+
+ private static final String NAME = "recover";
+ private static final String DESC = "Recover the ledger data for failed
bookie";
+
+ private static final long DEFAULT_ID = -1L;
+
+ public RecoverCommand() {
+ this(new RecoverFlags());
+ }
+
+ private RecoverCommand(RecoverFlags flags) {
+ super(CliSpec.<RecoverFlags>newBuilder()
+ .withName(NAME)
+ .withDescription(DESC)
+ .withFlags(flags)
+ .build());
+ }
+
+ /**
+ * Flags for recover command.
+ */
+ @Accessors(fluent = true)
+ @Setter
+ public static class RecoverFlags extends CliFlags{
+
+ @Parameter(names = { "-l", "--ledger" }, description = "Recover a
specific ledger")
+ private long ledger = DEFAULT_ID;
+
+ @Parameter(names = { "-f", "--force" }, description = "Force recovery
without confirmation")
+ private boolean force;
+
+ @Parameter(names = { "-q", "--query" }, description = "Query the
ledgers that contain given bookies")
+ private boolean query;
+
+ @Parameter(names = { "-dr", "--drarun" }, description = "Printing the
recovery plan w/o doing actual recovery")
+ private boolean dryRun;
+
+ @Parameter(names = {"-sk", "--skipopenledgers"}, description = "Skip
recovering open ledgers")
+ private boolean skipOpenLedgers;
+
+ @Parameter(names = { "-d", "--deletecookie" }, description = "Delete
cookie node for the bookie")
+ private boolean deleteCookie;
+
+ @Parameter(names = { "-bs", "--bokiesrc" }, description = "Bookie
address")
+ private String bookieAddress;
+ }
+
+ @Override
+ public boolean apply(ServerConfiguration conf, RecoverFlags cmdFlags) {
+ try {
+ return recover(conf, cmdFlags);
+ } catch (Exception e) {
+ throw new UncheckedExecutionException(e.getMessage(), e);
+ }
+ }
+
+ private boolean recover(ServerConfiguration conf, RecoverFlags flags)
+ throws IOException, BKException, InterruptedException, KeeperException
{
+ boolean query = flags.query;
+ boolean dryrun = flags.dryRun;
+ boolean force = flags.force;
+ boolean skipOpenLedgers = flags.skipOpenLedgers;
+ boolean removeCookies = !dryrun && flags.deleteCookie;
+
+ Long ledgerId = flags.ledger;
+
+ // Get bookies list
+ final String[] bookieStrs = flags.bookieAddress.split(",");
+ final Set<BookieSocketAddress> bookieAddrs = new HashSet<>();
+ for (String bookieStr : bookieStrs) {
+ final String bookieStrParts[] = bookieStr.split(":");
+ if (bookieStrParts.length != 2) {
+ System.err.println("BookieSrcs has invalid bookie address
format (host:port expected) : "
+ + bookieStr);
+ return false;
+ }
+ bookieAddrs.add(new BookieSocketAddress(bookieStrParts[0],
+
Integer.parseInt(bookieStrParts[1])));
+ }
+
+ if (!force) {
+ System.err.println("Bookies : " + bookieAddrs);
+ if (!IOUtils.confirmPrompt("Are you sure to recover them :
(Y/N)")) {
+ System.err.println("Give up!");
+ return false;
+ }
+ }
+
+ LOG.info("Constructing admin");
+ ClientConfiguration adminConf = new ClientConfiguration(conf);
+ BookKeeperAdmin admin = new BookKeeperAdmin(adminConf);
+ LOG.info("Construct admin : {}", admin);
+ try {
+ if (query) {
+ return bkQuery(admin, bookieAddrs);
+ }
+ if (DEFAULT_ID != ledgerId) {
+ return bkRecoveryLedger(admin, ledgerId, bookieAddrs, dryrun,
skipOpenLedgers, removeCookies);
+ }
+ return bkRecovery(admin, bookieAddrs, dryrun, skipOpenLedgers,
removeCookies);
+ } finally {
+ admin.close();
+ }
+ }
+
+ private boolean bkQuery(BookKeeperAdmin bkAdmin, Set<BookieSocketAddress>
bookieAddrs)
+ throws InterruptedException, BKException {
+ SortedMap<Long, LedgerMetadata> ledgersContainBookies =
+ bkAdmin.getLedgersContainBookies(bookieAddrs);
+ System.err.println("NOTE: Bookies in inspection list are marked with
'*'.");
+ for (Map.Entry<Long, LedgerMetadata> ledger :
ledgersContainBookies.entrySet()) {
+ System.out.println("ledger " + ledger.getKey() + " : " +
ledger.getValue().getState());
+ Map<Long, Integer> numBookiesToReplacePerEnsemble =
+ inspectLedger(ledger.getValue(), bookieAddrs);
+ System.out.print("summary: [");
+ for (Map.Entry<Long, Integer> entry :
numBookiesToReplacePerEnsemble.entrySet()) {
+ System.out.print(entry.getKey() + "=" + entry.getValue() + ",
");
+ }
+ System.out.println("]");
+ System.out.println();
+ }
+ System.err.println("Done");
+ return true;
+ }
+
+ private Map<Long, Integer> inspectLedger(LedgerMetadata metadata,
Set<BookieSocketAddress> bookiesToInspect) {
+ Map<Long, Integer> numBookiesToReplacePerEnsemble = new TreeMap<Long,
Integer>();
+ for (Map.Entry<Long, ? extends List<BookieSocketAddress>> ensemble :
+ metadata.getAllEnsembles().entrySet()) {
+ List<BookieSocketAddress> bookieList = ensemble.getValue();
+ System.out.print(ensemble.getKey() + ":\t");
+ int numBookiesToReplace = 0;
+ for (BookieSocketAddress bookie : bookieList) {
+ System.out.print(bookie);
+ if (bookiesToInspect.contains(bookie)) {
+ System.out.print("*");
+ ++numBookiesToReplace;
+ } else {
+ System.out.print(" ");
+ }
+ System.out.print(" ");
+ }
+ System.out.println();
+ numBookiesToReplacePerEnsemble.put(ensemble.getKey(),
numBookiesToReplace);
+ }
+ return numBookiesToReplacePerEnsemble;
+ }
+
+ private boolean bkRecoveryLedger(BookKeeperAdmin bkAdmin,
+ long lid,
+ Set<BookieSocketAddress> bookieAddrs,
+ boolean dryrun,
+ boolean skipOpenLedgers,
+ boolean removeCookies)
+ throws InterruptedException, BKException {
+ bkAdmin.recoverBookieData(lid, bookieAddrs, dryrun, skipOpenLedgers);
+ if (removeCookies) {
+ deleteCookies(bkAdmin.getConf(), bookieAddrs);
+ }
+ return true;
+ }
+
+ private void deleteCookies(ClientConfiguration conf,
+ Set<BookieSocketAddress> bookieAddrs) throws
BKException {
+ ServerConfiguration serverConf = new ServerConfiguration(conf);
+ try {
+ runFunctionWithRegistrationManager(serverConf, rm -> {
+ try {
+ for (BookieSocketAddress addr : bookieAddrs) {
+ deleteCookie(rm, addr);
+ }
+ } catch (Exception e) {
+ throw new UncheckedExecutionException(e);
+ }
+ return null;
+ });
+ } catch (Exception e) {
+ Throwable cause = e;
+ if (e instanceof UncheckedExecutionException) {
+ cause = e.getCause();
+ }
+ if (cause instanceof BKException) {
+ throw (BKException) cause;
+ } else {
+ BKException bke = new BKException.MetaStoreException();
+ bke.initCause(bke);
+ throw bke;
+ }
+ }
+
+ }
+
+ private void deleteCookie(RegistrationManager rm, BookieSocketAddress
bookieSrc) throws BookieException {
+ try {
+ Versioned<Cookie> cookie = Cookie.readFromRegistrationManager(rm,
bookieSrc);
+ cookie.getValue().deleteFromRegistrationManager(rm, bookieSrc,
cookie.getVersion());
+ } catch (BookieException.CookieNotFoundException nne) {
+ LOG.warn("No cookie to remove for {} : ", bookieSrc, nne);
+ }
+ }
+
+ private boolean bkRecovery(BookKeeperAdmin bkAdmin,
+ Set<BookieSocketAddress> bookieAddrs,
+ boolean dryrun,
+ boolean skipOpenLedgers,
+ boolean removeCookies)
+ throws InterruptedException, BKException {
+ bkAdmin.recoverBookieData(bookieAddrs, dryrun, skipOpenLedgers);
+ if (removeCookies) {
+ deleteCookies(bkAdmin.getConf(), bookieAddrs);
+ }
+ return true;
+ }
+}
diff --git
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieShellTest.java
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieShellTest.java
index 3867466..9fcb2fb 100644
---
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieShellTest.java
+++
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieShellTest.java
@@ -51,6 +51,7 @@ import org.apache.bookkeeper.meta.MetadataBookieDriver;
import org.apache.bookkeeper.meta.MetadataDrivers;
import org.apache.bookkeeper.tools.cli.commands.bookie.LastMarkCommand;
import org.apache.bookkeeper.tools.cli.commands.bookies.ListBookiesCommand;
+import org.apache.bookkeeper.tools.cli.commands.bookies.RecoverCommand;
import org.apache.bookkeeper.tools.cli.commands.client.SimpleTestCommand;
import org.apache.bookkeeper.tools.framework.CliFlags;
import org.apache.bookkeeper.util.EntryFormatter;
@@ -73,7 +74,7 @@ import org.powermock.modules.junit4.PowerMockRunner;
* Unit test for {@link BookieShell}.
*/
@RunWith(PowerMockRunner.class)
-@PrepareForTest({ BookieShell.class, MetadataDrivers.class })
+@PrepareForTest({ BookieShell.class, MetadataDrivers.class,
RecoverCommand.class })
public class BookieShellTest {
private ClientConfiguration clientConf;
diff --git
a/tools/ledger/src/main/java/org/apache/bookkeeper/tools/cli/commands/BookiesCommandGroup.java
b/tools/ledger/src/main/java/org/apache/bookkeeper/tools/cli/commands/BookiesCommandGroup.java
index 1f4d4cf..cb161f5 100644
---
a/tools/ledger/src/main/java/org/apache/bookkeeper/tools/cli/commands/BookiesCommandGroup.java
+++
b/tools/ledger/src/main/java/org/apache/bookkeeper/tools/cli/commands/BookiesCommandGroup.java
@@ -27,6 +27,7 @@ import
org.apache.bookkeeper.tools.cli.commands.bookies.InitCommand;
import org.apache.bookkeeper.tools.cli.commands.bookies.ListBookiesCommand;
import org.apache.bookkeeper.tools.cli.commands.bookies.MetaFormatCommand;
import
org.apache.bookkeeper.tools.cli.commands.bookies.NukeExistingClusterCommand;
+import org.apache.bookkeeper.tools.cli.commands.bookies.RecoverCommand;
import org.apache.bookkeeper.tools.common.BKFlags;
import org.apache.bookkeeper.tools.framework.CliCommandGroup;
import org.apache.bookkeeper.tools.framework.CliSpec;
@@ -50,6 +51,7 @@ public class BookiesCommandGroup extends
CliCommandGroup<BKFlags> {
.addCommand(new MetaFormatCommand())
.addCommand(new DecommissionCommand())
.addCommand(new InitCommand())
+ .addCommand(new RecoverCommand())
.build();
public BookiesCommandGroup() {
diff --git
a/tools/ledger/src/test/java/org/apache/bookkeeper/tools/cli/commands/bookies/RecoverCommandTest.java
b/tools/ledger/src/test/java/org/apache/bookkeeper/tools/cli/commands/bookies/RecoverCommandTest.java
new file mode 100644
index 0000000..12f336b
--- /dev/null
+++
b/tools/ledger/src/test/java/org/apache/bookkeeper/tools/cli/commands/bookies/RecoverCommandTest.java
@@ -0,0 +1,200 @@
+/*
+ * 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.bookies;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+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.doReturn;
+import static org.mockito.Mockito.verify;
+import static org.mockito.internal.verification.VerificationModeFactory.times;
+import static org.powermock.api.mockito.PowerMockito.doNothing;
+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 com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.function.Function;
+import org.apache.bookkeeper.bookie.BookieException;
+import org.apache.bookkeeper.bookie.Cookie;
+import org.apache.bookkeeper.client.BKException;
+import org.apache.bookkeeper.client.BookKeeperAdmin;
+import org.apache.bookkeeper.client.api.LedgerMetadata;
+import org.apache.bookkeeper.conf.AbstractConfiguration;
+import org.apache.bookkeeper.conf.ClientConfiguration;
+import org.apache.bookkeeper.conf.ServerConfiguration;
+import org.apache.bookkeeper.discover.RegistrationManager;
+import org.apache.bookkeeper.meta.MetadataDrivers;
+import org.apache.bookkeeper.net.BookieSocketAddress;
+import org.apache.bookkeeper.tools.cli.helpers.BookieCommandTestBase;
+import org.apache.bookkeeper.versioning.Version;
+import org.apache.bookkeeper.versioning.Versioned;
+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 RecoverCommand}.
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ RecoverCommand.class, MetadataDrivers.class, Cookie.class })
+public class RecoverCommandTest extends BookieCommandTestBase {
+
+ @Mock
+ private BookieSocketAddress bookieSocketAddress;
+
+ @Mock
+ private ClientConfiguration clientConfiguration;
+
+ @Mock
+ private BookKeeperAdmin bookKeeperAdmin;
+
+ @Mock
+ private LedgerMetadata ledgerMetadata;
+
+ @Mock
+ private ServerConfiguration serverConfiguration;
+
+ @Mock
+ private RegistrationManager registrationManager;
+
+ @Mock
+ private Versioned<Cookie> cookieVersioned;
+
+ public RecoverCommandTest() {
+ super(3, 0);
+ }
+
+ @Override
+ public void setup() throws Exception {
+ super.setup();
+
+
PowerMockito.whenNew(ServerConfiguration.class).withNoArguments().thenReturn(conf);
+
PowerMockito.whenNew(ServerConfiguration.class).withParameterTypes(AbstractConfiguration.class)
+ .withArguments(eq(clientConfiguration)).thenReturn(conf);
+
PowerMockito.whenNew(BookieSocketAddress.class).withArguments(anyString(),
anyInt())
+ .thenReturn(bookieSocketAddress);
+
PowerMockito.whenNew(ClientConfiguration.class).withParameterTypes(AbstractConfiguration.class)
+ .withArguments(eq(conf)).thenReturn(clientConfiguration);
+
PowerMockito.whenNew(BookKeeperAdmin.class).withParameterTypes(ClientConfiguration.class)
+
.withArguments(eq(clientConfiguration)).thenReturn(bookKeeperAdmin);
+
+ mockBkQuery();
+ mockDeleteCookie();
+ mockDeleteCookies();
+ mockBkRecovery();
+
+ }
+
+ private void mockBkQuery() throws BKException, InterruptedException {
+ SortedMap<Long, LedgerMetadata> ledgerMetadataSortedMap = new
TreeMap<>();
+ ledgerMetadataSortedMap.put(1L, ledgerMetadata);
+
when(bookKeeperAdmin.getLedgersContainBookies(any())).thenReturn(ledgerMetadataSortedMap);
+ ArrayList<BookieSocketAddress> arrayList = new ArrayList<>();
+ arrayList.add(bookieSocketAddress);
+ Map<Long, List<BookieSocketAddress>> map = new HashMap<>();
+ map.put(1L, arrayList);
+ NavigableMap<Long, ImmutableList<BookieSocketAddress>> navigableMap =
Collections.unmodifiableNavigableMap(
+ map.entrySet().stream()
+ .collect(TreeMap::new, (m, e) -> m.put(e.getKey(),
ImmutableList.copyOf(e.getValue())),
+ TreeMap::putAll));
+ doReturn(navigableMap).when(ledgerMetadata).getAllEnsembles();
+ }
+
+
+
+ private void mockDeleteCookies() throws Exception {
+ PowerMockito.mockStatic(MetadataDrivers.class);
+ PowerMockito.doAnswer(invocationOnMock -> {
+ Function<RegistrationManager, ?> function =
invocationOnMock.getArgument(1);
+ function.apply(registrationManager);
+ return null;
+ }).when(MetadataDrivers.class, "runFunctionWithRegistrationManager",
any(ServerConfiguration.class),
+ any(Function.class));
+ }
+
+ private void mockDeleteCookie() throws BookieException {
+ PowerMockito.mockStatic(Cookie.class);
+ when(Cookie.readFromRegistrationManager(eq(registrationManager),
eq(bookieSocketAddress)))
+ .thenReturn(cookieVersioned);
+ Cookie cookie = mock(Cookie.class);
+ when(cookieVersioned.getValue()).thenReturn(cookie);
+ Version version = mock(Version.class);
+ when(cookieVersioned.getVersion()).thenReturn(version);
+ doNothing().when(cookie)
+ .deleteFromRegistrationManager(eq(registrationManager),
eq(bookieSocketAddress), eq(version));
+ }
+
+ private void mockBkRecovery() throws BKException, InterruptedException {
+ doNothing().when(bookKeeperAdmin).recoverBookieData(any(),
anyBoolean(), anyBoolean());
+ when(bookKeeperAdmin.getConf()).thenReturn(clientConfiguration);
+ }
+
+ @Test
+ public void testBookieListCheck() {
+ RecoverCommand cmd = new RecoverCommand();
+ Assert.assertFalse(cmd.apply(bkFlags, new String[] { "-bs",
"127.0.0.1:8000,8001" }));
+ }
+
+ @Test
+ public void testQuery() throws Exception {
+ RecoverCommand cmd = new RecoverCommand();
+ Assert.assertTrue(cmd.apply(bkFlags, new String[] { "-q", "-bs",
"127.0.0.1:8000", "-f" }));
+ verifyNew(ClientConfiguration.class, times(1)).withArguments(eq(conf));
+ verifyNew(BookKeeperAdmin.class,
times(1)).withArguments(eq(clientConfiguration));
+ verify(bookKeeperAdmin, times(1)).getLedgersContainBookies(any());
+ }
+
+ @Test
+ public void testLedgerId() throws Exception {
+ RecoverCommand cmd = new RecoverCommand();
+ Assert.assertTrue(cmd.apply(bkFlags, new String[] { "-bs",
"127.0.0.1:8000", "-f", "-l", "1" }));
+ verifyNew(ClientConfiguration.class, times(1)).withArguments(eq(conf));
+ verifyNew(BookKeeperAdmin.class,
times(1)).withArguments(eq(clientConfiguration));
+ verify(bookKeeperAdmin, times(1))
+ .recoverBookieData(anyLong(), any(), anyBoolean(), anyBoolean());
+ }
+
+ @Test
+ public void testWithLedgerIdAndRemoveCookies() throws Exception {
+ RecoverCommand cmd = new RecoverCommand();
+ Assert.assertTrue(cmd.apply(bkFlags, new String[] { "-bs",
"127.0.0.1:8000", "-f", "-l", "1", "-d" }));
+ verifyNew(ClientConfiguration.class, times(1)).withArguments(eq(conf));
+ verifyNew(BookKeeperAdmin.class,
times(1)).withArguments(eq(clientConfiguration));
+ verify(bookKeeperAdmin, times(1)).recoverBookieData(anyLong(), any(),
anyBoolean(), anyBoolean());
+ verify(bookKeeperAdmin, times(1)).getConf();
+ verifyNew(ServerConfiguration.class,
times(1)).withArguments(eq(clientConfiguration));
+ verify(cookieVersioned, times(1)).getValue();
+ }
+}