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();
+    }
+}

Reply via email to