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 dad7e2c  ISSUE #612: Make bookie recovery work with recovering 
multiple bookies
dad7e2c is described below

commit dad7e2ce0d1cf8f04c7e2f128900ea69792c948d
Author: Sijie Guo <[email protected]>
AuthorDate: Sun Nov 12 15:13:56 2017 +0800

    ISSUE #612: Make bookie recovery work with recovering multiple bookies
    
    Descriptions of the changes in this PR:
    
    This is a change ported from twitter branch.
    
    - Enable recovering multiple bookies in bookie shell
    - Add a unit test for BookieShell.
    
    Author: Sijie Guo <[email protected]>
    
    Reviewers: Enrico Olivelli <[email protected]>, Jia Zhai <None>
    
    This closes #714 from sijie/recover_command_fix, closes #612
---
 .../org/apache/bookkeeper/bookie/BookieShell.java  | 188 +++++++++++---
 .../apache/bookkeeper/client/BookKeeperAdmin.java  | 129 +++++++++-
 .../apache/bookkeeper/client/LedgerMetadata.java   |  33 ++-
 .../bookkeeper/http/RecoveryBookieService.java     |  15 +-
 .../apache/bookkeeper/bookie/BookieShellTest.java  | 281 +++++++++++++++++++++
 .../bookkeeper/client/BookKeeperCloseTest.java     |   3 +-
 .../bookkeeper/client/BookieRecoveryTest.java      |  25 +-
 .../org/apache/bookkeeper/client/TestFencing.java  |   4 +-
 .../apache/bookkeeper/http/TestHttpService.java    |  15 +-
 .../tests/backward/TestBookieRecovery.java         |   6 +-
 10 files changed, 601 insertions(+), 98 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 2d26145..d564254 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
@@ -20,6 +20,7 @@ package org.apache.bookkeeper.bookie;
 
 import static com.google.common.base.Charsets.UTF_8;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.AbstractFuture;
 import java.io.File;
@@ -42,9 +43,13 @@ import java.util.Comparator;
 import java.util.Enumeration;
 import java.util.Formatter;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Predicate;
 import org.apache.bookkeeper.bookie.BookieException.CookieNotFoundException;
@@ -92,6 +97,7 @@ import 
org.apache.commons.configuration.PropertiesConfiguration;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang.mutable.MutableBoolean;
+import org.apache.zookeeper.KeeperException;
 import org.apache.zookeeper.ZooKeeper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -287,6 +293,11 @@ public class BookieShell implements Tool {
 
         public RecoverCmd() {
             super(CMD_RECOVER);
+            opts.addOption("q", "query", false, "Query the ledgers that 
contain given bookies");
+            opts.addOption("dr", "dryrun", false, "Printing the recovery plan 
w/o doing actual recovery");
+            opts.addOption("f", "force", false, "Force recovery without 
confirmation");
+            opts.addOption("l", "ledger", true, "Recover a specific ledger");
+            opts.addOption("sk", "skipOpenLedgers", false, "Skip recovering 
open ledgers");
             opts.addOption("d", "deleteCookie", false, "Delete cookie node for 
the bookie.");
         }
 
@@ -302,7 +313,7 @@ public class BookieShell implements Tool {
 
         @Override
         String getUsage() {
-            return "recover [-deleteCookie] <bookieSrc> [bookieDest]";
+            return "recover [-deleteCookie] <bookieSrc[:bookieSrc]>";
         }
 
         @Override
@@ -312,54 +323,160 @@ public class BookieShell implements Tool {
                 throw new MissingArgumentException(
                         "'bookieSrc' argument required");
             }
+            if (args.length > 1) {
+                System.err.println("The provided bookie dest " + args[1] + " 
will be ignored!");
+            }
+            boolean query = cmdLine.hasOption("q");
+            boolean dryrun = cmdLine.hasOption("dr");
+            boolean force = cmdLine.hasOption("f");
+            boolean skipOpenLedgers = cmdLine.hasOption("sk");
+            boolean removeCookies = !dryrun && cmdLine.hasOption("d");
+
+            Long ledgerId = null;
+            if (cmdLine.hasOption("l")) {
+                try {
+                    ledgerId = Long.parseLong(cmdLine.getOptionValue("l"));
+                } catch (NumberFormatException nfe) {
+                    throw new IOException("Invalid ledger id provided : " + 
cmdLine.getOptionValue("l"));
+                }
+            }
+
+            // 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 {
-                return bkRecovery(adminConf, admin, args, 
cmdLine.hasOption("d"));
+                if (query) {
+                    return bkQuery(admin, bookieAddrs);
+                }
+                if (null != ledgerId) {
+                    return bkRecoveryLedger(admin, ledgerId, bookieAddrs, 
dryrun, skipOpenLedgers, removeCookies);
+                }
+                return bkRecovery(admin, bookieAddrs, dryrun, skipOpenLedgers, 
removeCookies);
             } finally {
                 admin.close();
             }
         }
 
-        private int bkRecovery(ClientConfiguration conf, BookKeeperAdmin 
bkAdmin,
-                               String[] args, boolean deleteCookie)
-                throws InterruptedException, BKException, BookieException, 
IOException {
-            final String bookieSrcString[] = args[0].split(":");
-            if (bookieSrcString.length != 2) {
-                System.err.println("BookieSrc inputted has invalid format"
-                        + "(host:port expected): " + args[0]);
-                return -1;
-            }
-            final BookieSocketAddress bookieSrc = new BookieSocketAddress(
-                    bookieSrcString[0], Integer.parseInt(bookieSrcString[1]));
-            BookieSocketAddress bookieDest = null;
-            if (args.length >= 2) {
-                final String bookieDestString[] = args[1].split(":");
-                if (bookieDestString.length < 2) {
-                    System.err.println("BookieDest inputted has invalid format"
-                            + "(host:port expected): " + args[1]);
-                    return -1;
+        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() + 
", ");
                 }
-                bookieDest = new BookieSocketAddress(bookieDestString[0],
-                        Integer.parseInt(bookieDestString[1]));
+                System.out.println("]");
+                System.out.println();
             }
+            System.err.println("Done");
+            return 0;
+        }
 
-            bkAdmin.recoverBookieData(bookieSrc, bookieDest);
-            if (deleteCookie) {
-                ServerConfiguration serverConf = new ServerConfiguration();
-                serverConf.addConfiguration(conf);
-                RegistrationManager rm = new ZKRegistrationManager();
-                try {
-                    rm.initialize(serverConf, () -> {}, 
NullStatsLogger.INSTANCE);
-                    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);
+        private Map<Long, Integer> inspectLedger(LedgerMetadata metadata, 
Set<BookieSocketAddress> bookiesToInspect) {
+            Map<Long, Integer> numBookiesToReplacePerEnsemble = new 
TreeMap<Long, Integer>();
+            for (Map.Entry<Long, ArrayList<BookieSocketAddress>> ensemble : 
metadata.getEnsembles().entrySet()) {
+                ArrayList<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);
+            RegistrationManager rm = new ZKRegistrationManager();
+            try {
+                rm.initialize(serverConf, () -> {}, NullStatsLogger.INSTANCE);
+                for (BookieSocketAddress addr : bookieAddrs) {
+                    deleteCookie(rm, addr);
+                }
+            } catch (BookieException be) {
+                BKException bke = new BKException.MetaStoreException();
+                bke.initCause(be);
+                throw bke;
+            } finally {
+                rm.close();
+            }
+        }
+
+        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);
+            }
+        }
+
     }
 
     /**
@@ -2058,6 +2175,11 @@ public class BookieShell implements Tool {
         }
     }
 
+    @VisibleForTesting
+    public int execute(String... args) throws Exception {
+        return run(args);
+    }
+
     @Override
     public int run(String[] args) throws Exception {
         if (args.length <= 0) {
diff --git 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeperAdmin.java
 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeperAdmin.java
index aae36b8..e761222 100644
--- 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeperAdmin.java
+++ 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeperAdmin.java
@@ -42,10 +42,13 @@ import java.util.NoSuchElementException;
 import java.util.Optional;
 import java.util.Random;
 import java.util.Set;
+import java.util.SortedMap;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentSkipListMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Predicate;
 import org.apache.bookkeeper.client.AsyncCallback.OpenCallback;
 import org.apache.bookkeeper.client.AsyncCallback.RecoverCallback;
@@ -182,6 +185,10 @@ public class BookKeeperAdmin implements AutoCloseable {
         this(bkc, NullStatsLogger.INSTANCE);
     }
 
+    public ClientConfiguration getConf() {
+        return bkc.getConf();
+    }
+
     /**
      * Gracefully release resources that this client uses.
      *
@@ -442,6 +449,69 @@ public class BookKeeperAdmin implements AutoCloseable {
         }
     }
 
+    public SortedMap<Long, LedgerMetadata> 
getLedgersContainBookies(Set<BookieSocketAddress> bookies)
+            throws InterruptedException, BKException {
+        final SyncObject sync = new SyncObject();
+        final AtomicReference<SortedMap<Long, LedgerMetadata>> resultHolder =
+                new AtomicReference<SortedMap<Long, LedgerMetadata>>(null);
+        asyncGetLedgersContainBookies(bookies, new 
GenericCallback<SortedMap<Long, LedgerMetadata>>() {
+            @Override
+            public void operationComplete(int rc, SortedMap<Long, 
LedgerMetadata> result) {
+                LOG.info("GetLedgersContainBookies completed with rc : {}", 
rc);
+                synchronized (sync) {
+                    sync.rc = rc;
+                    sync.value = true;
+                    resultHolder.set(result);
+                    sync.notify();
+                }
+            }
+        });
+        synchronized (sync) {
+            while (sync.value == false) {
+                sync.wait();
+            }
+        }
+        if (sync.rc != BKException.Code.OK) {
+            throw BKException.create(sync.rc);
+        }
+        return resultHolder.get();
+    }
+
+    public void asyncGetLedgersContainBookies(final Set<BookieSocketAddress> 
bookies,
+                                              final 
GenericCallback<SortedMap<Long, LedgerMetadata>> callback) {
+        final SortedMap<Long, LedgerMetadata> ledgers = new 
ConcurrentSkipListMap<Long, LedgerMetadata>();
+        bkc.getLedgerManager().asyncProcessLedgers(new Processor<Long>() {
+            @Override
+            public void process(final Long lid, final 
AsyncCallback.VoidCallback cb) {
+                bkc.getLedgerManager().readLedgerMetadata(lid, new 
GenericCallback<LedgerMetadata>() {
+                    @Override
+                    public void operationComplete(int rc, LedgerMetadata 
metadata) {
+                        if (BKException.Code.NoSuchLedgerExistsException == 
rc) {
+                            // the ledger was deleted during this iteration.
+                            cb.processResult(BKException.Code.OK, null, null);
+                            return;
+                        } else if (BKException.Code.OK != rc) {
+                            cb.processResult(rc, null, null);
+                            return;
+                        }
+                        Set<BookieSocketAddress> bookiesInLedger = 
metadata.getBookiesInThisLedger();
+                        Sets.SetView<BookieSocketAddress> intersection =
+                                Sets.intersection(bookiesInLedger, bookies);
+                        if (!intersection.isEmpty()) {
+                            ledgers.put(lid, metadata);
+                        }
+                        cb.processResult(BKException.Code.OK, null, null);
+                    }
+                });
+            }
+        }, new AsyncCallback.VoidCallback() {
+            @Override
+            public void processResult(int rc, String path, Object ctx) {
+                callback.operationComplete(rc, ledgers);
+            }
+        }, null, BKException.Code.OK, BKException.Code.MetaStoreException);
+    }
+
     /**
      * Synchronous method to rebuild and recover the ledger fragments data that
      * was stored on the source bookie. That bookie could have failed 
completely
@@ -456,11 +526,8 @@ public class BookKeeperAdmin implements AutoCloseable {
      * @param bookieSrc
      *            Source bookie that had a failure. We want to replicate the
      *            ledger fragments that were stored there.
-     * @param bookieDest
-     *            Optional destination bookie that if passed, we will copy all
-     *            of the ledger fragments from the source bookie over to it.
      */
-    public void recoverBookieData(final BookieSocketAddress bookieSrc, final 
BookieSocketAddress bookieDest)
+    public void recoverBookieData(final BookieSocketAddress bookieSrc)
             throws InterruptedException, BKException {
         Set<BookieSocketAddress> bookiesSrc = Sets.newHashSet(bookieSrc);
         recoverBookieData(bookiesSrc);
@@ -490,6 +557,34 @@ public class BookKeeperAdmin implements AutoCloseable {
 
         // Wait for the async method to complete.
         synchronized (sync) {
+            while (sync.value == false) {
+                sync.wait();
+            }
+        }
+        if (sync.rc != BKException.Code.OK) {
+            throw BKException.create(sync.rc);
+        }
+    }
+
+    public void recoverBookieData(final long lid,
+                                  final Set<BookieSocketAddress> bookiesSrc,
+                                  boolean dryrun,
+                                  boolean skipOpenLedgers)
+            throws InterruptedException, BKException {
+        SyncObject sync = new SyncObject();
+        // Call the async method to recover bookie data.
+        asyncRecoverBookieData(lid, bookiesSrc, dryrun, skipOpenLedgers, (rc, 
ctx) -> {
+            LOG.info("Recover bookie for {} completed with rc : {}", lid, rc);
+            SyncObject syncObject = (SyncObject) ctx;
+            synchronized (syncObject) {
+                syncObject.rc = rc;
+                syncObject.value = true;
+                syncObject.notify();
+            }
+        }, sync);
+
+        // Wait for the async method to complete.
+        synchronized (sync) {
             while (!sync.value) {
                 sync.wait();
             }
@@ -498,7 +593,7 @@ public class BookKeeperAdmin implements AutoCloseable {
             throw BKException.create(sync.rc);
         }
     }
-       
+
     /**
      * Async method to rebuild and recover the ledger fragments data that was
      * stored on the source bookie. That bookie could have failed completely 
and
@@ -536,6 +631,30 @@ public class BookKeeperAdmin implements AutoCloseable {
     }
 
     /**
+     * Recover a specific ledger.
+     *
+     * @param lid
+     *          ledger to recover
+     * @param bookieSrc
+     *          Source bookies that had a failure. We want to replicate the 
ledger fragments that were stored there.
+     * @param dryrun
+     *          dryrun the recover procedure.
+     * @param skipOpenLedgers
+     *            Skip recovering open ledgers.
+     * @param callback
+     *          RecoverCallback to invoke once all of the data on the dead
+     *          bookie has been recovered and replicated.
+     * @param context
+     *          Context for the RecoverCallback to call.
+     */
+    public void asyncRecoverBookieData(long lid, final 
Set<BookieSocketAddress> bookieSrc, boolean dryrun,
+                                       boolean skipOpenLedgers, final 
RecoverCallback callback, final Object context) {
+        AsyncCallback.VoidCallback callbackWrapper = (rc, path, ctx)
+            -> callback.recoverComplete(bkc.getReturnRc(rc), context);
+        recoverLedger(bookieSrc, lid, dryrun, skipOpenLedgers, 
callbackWrapper);
+    }
+
+    /**
      * This method asynchronously polls ZK to get the current set of active
      * ledgers. From this, we can open each ledger and look at the metadata to
      * determine if any of the ledger fragments for it were stored at the dead
diff --git 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerMetadata.java
 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerMetadata.java
index 0a0b87c..d757542 100644
--- 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerMetadata.java
+++ 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerMetadata.java
@@ -17,33 +17,32 @@
  */
 package org.apache.bookkeeper.client;
 
+import static com.google.common.base.Charsets.UTF_8;
+
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Optional;
+import com.google.common.collect.Maps;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.TextFormat;
-
-import org.apache.bookkeeper.net.BookieSocketAddress;
-import org.apache.bookkeeper.proto.DataFormats.LedgerMetadataFormat;
-import org.apache.bookkeeper.versioning.Version;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.StringReader;
 import java.nio.CharBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
-
-import static com.google.common.base.Charsets.UTF_8;
-
-import com.google.common.base.Optional;
-import com.google.common.collect.Maps;
+import org.apache.bookkeeper.net.BookieSocketAddress;
+import org.apache.bookkeeper.proto.DataFormats.LedgerMetadataFormat;
+import org.apache.bookkeeper.versioning.Version;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * This class encapsulates all the ledger metadata that is persistently stored
@@ -234,7 +233,7 @@ public class LedgerMetadata {
         return state == LedgerMetadataFormat.State.IN_RECOVERY;
     }
 
-    LedgerMetadataFormat.State getState() {
+    public LedgerMetadataFormat.State getState() {
         return state;
     }
 
@@ -648,4 +647,12 @@ public class LedgerMetadata {
         }
     }
 
+    Set<BookieSocketAddress> getBookiesInThisLedger() {
+        Set<BookieSocketAddress> bookies = new HashSet<BookieSocketAddress>();
+        for (ArrayList<BookieSocketAddress> ensemble : ensembles.values()) {
+            bookies.addAll(ensemble);
+        }
+        return bookies;
+    }
+
 }
diff --git 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/http/RecoveryBookieService.java
 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/http/RecoveryBookieService.java
index b079f1b..cfbb921 100644
--- 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/http/RecoveryBookieService.java
+++ 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/http/RecoveryBookieService.java
@@ -25,7 +25,6 @@ import java.util.List;
 import java.util.concurrent.ExecutorService;
 import org.apache.bookkeeper.bookie.Cookie;
 import org.apache.bookkeeper.client.BookKeeperAdmin;
-import org.apache.bookkeeper.conf.ClientConfiguration;
 import org.apache.bookkeeper.conf.ServerConfiguration;
 import org.apache.bookkeeper.discover.RegistrationManager;
 import org.apache.bookkeeper.http.service.HttpEndpointService;
@@ -69,13 +68,11 @@ public class RecoveryBookieService implements 
HttpEndpointService {
      * Example body as this:
      * {
      *   "bookie_src": [ "bookie_src1", "bookie_src2"... ],
-     *   "bookie_dest": [ "bookie_dest1", "bookie_dest2"... ],
      *   "delete_cookie": <bool_value>
      * }
      */
     static class RecoveryRequestJsonBody {
         public List<String> bookie_src;
-        public List<String> bookie_dest;
         public boolean delete_cookie;
     }
 
@@ -94,8 +91,6 @@ public class RecoveryBookieService implements 
HttpEndpointService {
         try {
             requestJsonBody = JsonUtil.fromJson(requestBody, 
RecoveryRequestJsonBody.class);
             LOG.debug("bookie_src: [" + requestJsonBody.bookie_src.get(0)
-                + "],  bookie_dest: ["
-                + ((requestJsonBody.bookie_dest == null) ? "null" : 
requestJsonBody.bookie_dest.get(0))
                 + "],  delete_cookie: [" + requestJsonBody.delete_cookie + 
"]");
         } catch (JsonUtil.ParseJsonException e) {
             LOG.error("Meet Exception: ", e);
@@ -114,19 +109,11 @@ public class RecoveryBookieService implements 
HttpEndpointService {
             String bookieSrcString[] = 
requestJsonBody.bookie_src.get(0).split(":");
             BookieSocketAddress bookieSrc = new BookieSocketAddress(
               bookieSrcString[0], Integer.parseInt(bookieSrcString[1]));
-            final BookieSocketAddress bookieDest;
-            if ((requestJsonBody.bookie_dest != null) && 
!requestJsonBody.bookie_dest.isEmpty()) {
-                String bookieDestString[] = 
requestJsonBody.bookie_dest.get(0).split(":");
-                bookieDest = new BookieSocketAddress(bookieDestString[0],
-                  Integer.parseInt(bookieDestString[1]));
-            } else {
-                bookieDest = null;
-            }
             boolean deleteCookie = requestJsonBody.delete_cookie;
             executor.execute(() -> {
                 try {
                     LOG.info("Start recovering bookie.");
-                    bka.recoverBookieData(bookieSrc, bookieDest);
+                    bka.recoverBookieData(bookieSrc);
                     if (deleteCookie) {
                         Versioned<Cookie> cookie = 
Cookie.readFromRegistrationManager(rm, bookieSrc);
                         cookie.getValue().deleteFromRegistrationManager(rm, 
bookieSrc, cookie.getVersion());
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
new file mode 100644
index 0000000..f42ec9e
--- /dev/null
+++ 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/BookieShellTest.java
@@ -0,0 +1,281 @@
+/*
+ * 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.bookie;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.powermock.api.mockito.PowerMockito.whenNew;
+
+import com.google.common.collect.Maps;
+import java.util.Set;
+import java.util.SortedMap;
+import org.apache.bookkeeper.bookie.BookieShell.MyCommand;
+import org.apache.bookkeeper.bookie.BookieShell.RecoverCmd;
+import org.apache.bookkeeper.client.BookKeeperAdmin;
+import org.apache.bookkeeper.client.LedgerMetadata;
+import org.apache.bookkeeper.conf.ClientConfiguration;
+import org.apache.bookkeeper.conf.ServerConfiguration;
+import org.apache.bookkeeper.discover.RegistrationManager.RegistrationListener;
+import org.apache.bookkeeper.discover.ZKRegistrationManager;
+import org.apache.bookkeeper.stats.NullStatsLogger;
+import org.apache.bookkeeper.versioning.LongVersion;
+import org.apache.bookkeeper.versioning.Version;
+import org.apache.bookkeeper.versioning.Versioned;
+import org.apache.commons.cli.BasicParser;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.MissingArgumentException;
+import org.apache.commons.cli.ParseException;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+/**
+ * Unit test for {@link BookieShell}.
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(BookieShell.class)
+public class BookieShellTest {
+
+    private ClientConfiguration clientConf;
+    private BookieShell shell;
+    private BookKeeperAdmin admin;
+    private ZKRegistrationManager rm;
+    private Cookie cookie;
+    private Version version;
+
+    @Before
+    public void setup() throws Exception {
+        this.shell = new BookieShell();
+        this.admin = PowerMockito.mock(BookKeeperAdmin.class);
+        whenNew(BookKeeperAdmin.class)
+            .withParameterTypes(ClientConfiguration.class)
+            .withArguments(any(ClientConfiguration.class))
+            .thenReturn(admin);
+        this.clientConf = new ClientConfiguration();
+        when(admin.getConf()).thenReturn(this.clientConf);
+        this.rm = PowerMockito.mock(ZKRegistrationManager.class);
+        this.cookie = Cookie.newBuilder()
+            .setBookieHost("127.0.0.1:3181")
+            .setInstanceId("xyz")
+            .setJournalDirs("/path/to/journal/dir")
+            .setLedgerDirs("/path/to/journal/dir")
+            .setLayoutVersion(Cookie.CURRENT_COOKIE_LAYOUT_VERSION)
+            .build();
+        this.version = new LongVersion(1L);
+        when(rm.readCookie(anyString()))
+            .thenReturn(new Versioned<>(cookie.toString().getBytes(UTF_8), 
version));
+        whenNew(ZKRegistrationManager.class)
+            .withNoArguments()
+            .thenReturn(rm);
+    }
+
+    private static CommandLine parseCommandLine(MyCommand cmd, String... args) 
throws ParseException {
+        BasicParser parser = new BasicParser();
+        return parser.parse(cmd.getOptions(), args);
+    }
+
+    @Test
+    public void testRecoverCmdMissingArgument() throws Exception {
+        RecoverCmd cmd = (RecoverCmd) shell.commands.get("recover");
+        CommandLine cmdLine = parseCommandLine(cmd);
+        try {
+            cmd.runCmd(cmdLine);
+            fail("should fail running command when the arguments are missing");
+        } catch (MissingArgumentException e) {
+            // expected
+        }
+        PowerMockito.verifyNew(BookKeeperAdmin.class, 
never()).withArguments(any(ClientConfiguration.class));
+    }
+
+    @Test
+    public void testRecoverCmdInvalidBookieAddress() throws Exception {
+        RecoverCmd cmd = (RecoverCmd) shell.commands.get("recover");
+        CommandLine cmdLine = parseCommandLine(cmd, "127.0.0.1");
+        assertEquals(-1, cmd.runCmd(cmdLine));
+        PowerMockito.verifyNew(BookKeeperAdmin.class, 
never()).withArguments(any(ClientConfiguration.class));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testRecoverCmdQuery() throws Exception {
+        SortedMap<Long, LedgerMetadata> ledgersContainBookies = 
Maps.newTreeMap();
+        when(admin.getLedgersContainBookies(any(Set.class)))
+            .thenReturn(ledgersContainBookies);
+
+        RecoverCmd cmd = (RecoverCmd) shell.commands.get("recover");
+        CommandLine cmdLine = parseCommandLine(cmd, "-force", "-q", 
"127.0.0.1:3181");
+        assertEquals(0, cmd.runCmd(cmdLine));
+        PowerMockito
+            .verifyNew(BookKeeperAdmin.class, times(1))
+            .withArguments(any(ClientConfiguration.class));
+        verify(admin, times(1)).getLedgersContainBookies(any(Set.class));
+        verify(admin, times(1)).close();
+    }
+
+    @Test
+    public void testRecoverCmdRecoverLedgerDefault() throws Exception {
+        // default behavior
+        testRecoverCmdRecoverLedger(
+            12345, false, false, false,
+            "-force", "-l", "12345", "127.0.0.1:3181");
+    }
+
+    @Test
+    public void testRecoverCmdRecoverLedgerDeleteCookie() throws Exception {
+        // dryrun
+        testRecoverCmdRecoverLedger(
+            12345, false, false, true,
+            "-force", "-l", "12345", "-deleteCookie", "127.0.0.1:3181");
+    }
+
+    @Test
+    public void testRecoverCmdRecoverLedgerSkipOpenLedgersDeleteCookie() 
throws Exception {
+        // dryrun
+        testRecoverCmdRecoverLedger(
+            12345, false, true, true,
+            "-force", "-l", "12345", "-deleteCookie", "-skipOpenLedgers", 
"127.0.0.1:3181");
+    }
+
+    @Test
+    public void testRecoverCmdRecoverLedgerDryrun() throws Exception {
+        // dryrun
+        testRecoverCmdRecoverLedger(
+            12345, true, false, false,
+            "-force", "-l", "12345", "-dryrun", "127.0.0.1:3181");
+    }
+
+    @Test
+    public void testRecoverCmdRecoverLedgerDryrunDeleteCookie() throws 
Exception {
+        // dryrun & removeCookie : removeCookie should be false
+        testRecoverCmdRecoverLedger(
+            12345, true, false, false,
+            "-force", "-l", "12345", "-dryrun", "-deleteCookie", 
"127.0.0.1:3181");
+    }
+
+    @SuppressWarnings("unchecked")
+    void testRecoverCmdRecoverLedger(long ledgerId,
+                                     boolean dryrun,
+                                     boolean skipOpenLedgers,
+                                     boolean removeCookies,
+                                     String... args) throws Exception {
+        RecoverCmd cmd = (RecoverCmd) shell.commands.get("recover");
+        CommandLine cmdLine = parseCommandLine(cmd, args);
+        assertEquals(0, cmd.runCmd(cmdLine));
+        PowerMockito
+            .verifyNew(BookKeeperAdmin.class, times(1))
+            .withArguments(any(ClientConfiguration.class));
+        verify(admin, times(1))
+            .recoverBookieData(eq(ledgerId), any(Set.class), eq(dryrun), 
eq(skipOpenLedgers));
+        verify(admin, times(1)).close();
+        if (removeCookies) {
+            PowerMockito
+                .verifyNew(ZKRegistrationManager.class, times(1))
+                .withNoArguments();
+            verify(rm, times(1)).initialize(
+                any(ServerConfiguration.class), 
any(RegistrationListener.class), eq(NullStatsLogger.INSTANCE));
+            verify(rm, times(1)).readCookie(anyString());
+            verify(rm, times(1)).removeCookie(anyString(), eq(version));
+        } else {
+            PowerMockito
+                .verifyNew(ZKRegistrationManager.class, never())
+                .withNoArguments();
+        }
+    }
+
+    @Test
+    public void testRecoverCmdRecoverDefault() throws Exception {
+        // default behavior
+        testRecoverCmdRecover(
+            false, false, false,
+            "-force", "127.0.0.1:3181");
+    }
+
+    @Test
+    public void testRecoverCmdRecoverDeleteCookie() throws Exception {
+        // dryrun
+        testRecoverCmdRecover(
+            false, false, true,
+            "-force", "-deleteCookie", "127.0.0.1:3181");
+    }
+
+    @Test
+    public void testRecoverCmdRecoverSkipOpenLedgersDeleteCookie() throws 
Exception {
+        // dryrun
+        testRecoverCmdRecover(
+            false, true, true,
+            "-force", "-deleteCookie", "-skipOpenLedgers", "127.0.0.1:3181");
+    }
+
+    @Test
+    public void testRecoverCmdRecoverDryrun() throws Exception {
+        // dryrun
+        testRecoverCmdRecover(
+            true, false, false,
+            "-force", "-dryrun", "127.0.0.1:3181");
+    }
+
+    @Test
+    public void testRecoverCmdRecoverDryrunDeleteCookie() throws Exception {
+        // dryrun & removeCookie : removeCookie should be false
+        testRecoverCmdRecover(
+            true, false, false,
+            "-force", "-dryrun", "-deleteCookie", "127.0.0.1:3181");
+    }
+
+    @SuppressWarnings("unchecked")
+    void testRecoverCmdRecover(boolean dryrun,
+                               boolean skipOpenLedgers,
+                               boolean removeCookies,
+                               String... args) throws Exception {
+        RecoverCmd cmd = (RecoverCmd) shell.commands.get("recover");
+        CommandLine cmdLine = parseCommandLine(cmd, args);
+        assertEquals(0, cmd.runCmd(cmdLine));
+        PowerMockito
+            .verifyNew(BookKeeperAdmin.class, times(1))
+            .withArguments(any(ClientConfiguration.class));
+        verify(admin, times(1))
+            .recoverBookieData(any(Set.class), eq(dryrun), 
eq(skipOpenLedgers));
+        verify(admin, times(1)).close();
+        if (removeCookies) {
+            PowerMockito
+                .verifyNew(ZKRegistrationManager.class, times(1))
+                .withNoArguments();
+            verify(rm, times(1)).initialize(
+                any(ServerConfiguration.class), 
any(RegistrationListener.class), eq(NullStatsLogger.INSTANCE));
+            verify(rm, times(1)).readCookie(anyString());
+            verify(rm, times(1)).removeCookie(anyString(), eq(version));
+        } else {
+            PowerMockito
+                .verifyNew(ZKRegistrationManager.class, never())
+                .withNoArguments();
+        }
+    }
+}
diff --git 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/BookKeeperCloseTest.java
 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/BookKeeperCloseTest.java
index f415152..e4c5dcc 100644
--- 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/BookKeeperCloseTest.java
+++ 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/BookKeeperCloseTest.java
@@ -476,7 +476,6 @@ public class BookKeeperCloseTest extends 
BookKeeperClusterTestCase {
             BookieSocketAddress bookieToKill = getBookie(0);
             killBookie(bookieToKill);
             startNewBookie();
-            BookieSocketAddress newBookie = getBookie(2);
 
             CheckerCb checkercb = new CheckerCb();
             LedgerChecker lc = new LedgerChecker(bk);
@@ -507,7 +506,7 @@ public class BookKeeperCloseTest extends 
BookKeeperClusterTestCase {
             }
 
             try {
-                bkadmin.recoverBookieData(bookieToKill, newBookie);
+                bkadmin.recoverBookieData(bookieToKill);
                 fail("Shouldn't be able to recover with a closed client");
             } catch (BKException.BKClientClosedException cce) {
                 // correct behaviour
diff --git 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/BookieRecoveryTest.java
 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/BookieRecoveryTest.java
index a9ccf89..1a4a3ef 100644
--- 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/BookieRecoveryTest.java
+++ 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/BookieRecoveryTest.java
@@ -271,7 +271,7 @@ public class BookieRecoveryTest extends 
BookKeeperClusterTestCase {
         for (int i = 0; i < numEntries; i++) {
             lh.addEntry(data);
         }
-        bkAdmin.recoverBookieData(bookieToKill, null);
+        bkAdmin.recoverBookieData(bookieToKill);
         // fail another bookie to cause ensemble change again
         bookieToKill = lh.getLedgerMetadata().getEnsemble(2 * numEntries - 
1).get(1);
         ServerConfiguration confOfKilledBookie = killBookie(bookieToKill);
@@ -433,7 +433,7 @@ public class BookieRecoveryTest extends 
BookKeeperClusterTestCase {
           newBookiePort);
         LOG.info("Now recover the data on the killed bookie (" + bookieSrc + 
") and replicate it to the new one ("
           + bookieDest + ")");
-        bkAdmin.recoverBookieData(bookieSrc, bookieDest);
+        bkAdmin.recoverBookieData(bookieSrc);
 
         // Verify the recovered ledger entries are okay.
         verifyRecoveredLedgers(lhs, 0, 2 * numMsgs - 1);
@@ -477,10 +477,9 @@ public class BookieRecoveryTest extends 
BookKeeperClusterTestCase {
         // Call the sync recover bookie method.
         BookieSocketAddress bookieSrc = new 
BookieSocketAddress(InetAddress.getLocalHost().getHostAddress(),
           initialPort);
-        BookieSocketAddress bookieDest = null;
         LOG.info("Now recover the data on the killed bookie (" + bookieSrc
           + ") and replicate it to a random available one");
-        bkAdmin.recoverBookieData(bookieSrc, bookieDest);
+        bkAdmin.recoverBookieData(bookieSrc);
 
         // Verify the recovered ledger entries are okay.
         verifyRecoveredLedgers(lhs, 0, 2 * numMsgs - 1);
@@ -639,11 +638,10 @@ public class BookieRecoveryTest extends 
BookKeeperClusterTestCase {
         // start a new bookie
         startNewBookie();
 
-        BookieSocketAddress bookieDest = null;
         LOG.info("Now recover the data on the killed bookie (" + bookieToKill
           + ") and replicate it to a random available one");
 
-        bkAdmin.recoverBookieData(bookieToKill, bookieDest);
+        bkAdmin.recoverBookieData(bookieToKill);
         for (LedgerHandle lh : lhs) {
             assertTrue("Not fully replicated", verifyFullyReplicated(lh, 
numMsgs));
             lh.close();
@@ -669,11 +667,10 @@ public class BookieRecoveryTest extends 
BookKeeperClusterTestCase {
         // start a new bookie
         startNewBookie();
 
-        BookieSocketAddress bookieDest = null;
         LOG.info("Now recover the data on the killed bookie (" + bookieToKill
           + ") and replicate it to a random available one");
 
-        bkAdmin.recoverBookieData(bookieToKill, bookieDest);
+        bkAdmin.recoverBookieData(bookieToKill);
 
         for (LedgerHandle lh : lhs) {
             assertTrue("Not fully replicated", verifyFullyReplicated(lh, 
numMsgs));
@@ -720,7 +717,7 @@ public class BookieRecoveryTest extends 
BookKeeperClusterTestCase {
         }
 
         try {
-            bkAdmin.recoverBookieData(bookieToKill, null);
+            bkAdmin.recoverBookieData(bookieToKill);
             fail("Should have thrown exception");
         } catch (BKException.BKLedgerRecoveryException bke) {
             // correct behaviour
@@ -731,7 +728,7 @@ public class BookieRecoveryTest extends 
BookKeeperClusterTestCase {
         bsConfs.add(conf2);
 
         // recover them
-        bkAdmin.recoverBookieData(bookieToKill, null);
+        bkAdmin.recoverBookieData(bookieToKill);
 
         for (LedgerHandle lh : lhs) {
             assertTrue("Not fully replicated", verifyFullyReplicated(lh, 
numMsgs));
@@ -773,7 +770,7 @@ public class BookieRecoveryTest extends 
BookKeeperClusterTestCase {
         // Initiate the sync object
         sync.value = false;
         try {
-            bkAdmin.recoverBookieData(bookieSrc, null);
+            bkAdmin.recoverBookieData(bookieSrc);
             fail("Should have thrown exception");
         } catch (BKException.BKLedgerRecoveryException bke) {
             // correct behaviour
@@ -811,7 +808,7 @@ public class BookieRecoveryTest extends 
BookKeeperClusterTestCase {
           + ") and replicate it to a random available one");
         // Initiate the sync object
         sync.value = false;
-        bkAdmin.recoverBookieData(bookieSrc, null);
+        bkAdmin.recoverBookieData(bookieSrc);
 
         assertFalse("Dupes exist in ensembles", findDupesInEnsembles(lhs));
 
@@ -857,7 +854,7 @@ public class BookieRecoveryTest extends 
BookKeeperClusterTestCase {
         setMetastoreImplClass(adminConf);
 
         BookKeeperAdmin bka = new BookKeeperAdmin(adminConf);
-        bka.recoverBookieData(bookieSrc, null);
+        bka.recoverBookieData(bookieSrc);
         bka.close();
 
         lh = bkc.openLedgerNoRecovery(ledgerId, digestCorrect, passwdCorrect);
@@ -880,7 +877,7 @@ public class BookieRecoveryTest extends 
BookKeeperClusterTestCase {
         setMetastoreImplClass(adminConf);
 
         bka = new BookKeeperAdmin(adminConf);
-        bka.recoverBookieData(bookieSrc, null);
+        bka.recoverBookieData(bookieSrc);
         bka.close();
 
         lh = bkc.openLedgerNoRecovery(ledgerId, digestCorrect, passwdCorrect);
diff --git 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestFencing.java 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestFencing.java
index ea6205f..53cd87e 100644
--- 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestFencing.java
+++ 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/TestFencing.java
@@ -274,7 +274,7 @@ public class TestFencing extends BookKeeperClusterTestCase {
             writelh.addEntry(tmp.getBytes());
         }
 
-        admin.recoverBookieData(bookieToKill, null);
+        admin.recoverBookieData(bookieToKill);
 
         for (int i = 0; i < numEntries; i++) {
             writelh.addEntry(tmp.getBytes());
@@ -321,7 +321,7 @@ public class TestFencing extends BookKeeperClusterTestCase {
         BookieSocketAddress bookieToKill
             = writelh.getLedgerMetadata().getEnsemble(numEntries).get(0);
         killBookie(bookieToKill);
-        admin.recoverBookieData(bookieToKill, null);
+        admin.recoverBookieData(bookieToKill);
 
         try {
             writelh.addEntry(tmp.getBytes());
diff --git 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/http/TestHttpService.java
 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/http/TestHttpService.java
index 7a49b30..5be680f 100644
--- 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/http/TestHttpService.java
+++ 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/http/TestHttpService.java
@@ -575,21 +575,12 @@ public class TestHttpService extends 
BookKeeperClusterTestCase {
 
         //3, body with bookie_src, bookie_dest and delete_cookie of PUT, 
should success.
         String bookieSrc = getBookie(0).toString();
-        String bookieDest = getBookie(1).toString();
-        String putBody = "{\"bookie_src\": [ \"" + bookieSrc + "\" ],"
-          + "\"bookie_dest\": [ \"" + bookieDest + "\" ],"
-          + "\"delete_cookie\": true }";
-        HttpServiceRequest request3 = new HttpServiceRequest(putBody, 
HttpServer.Method.PUT, null);
+        String putBody3 = "{\"bookie_src\": [ \"" + bookieSrc + "\" ],"
+          + "\"delete_cookie\": false }";
+        HttpServiceRequest request3 = new HttpServiceRequest(putBody3, 
HttpServer.Method.PUT, null);
         HttpServiceResponse response3 = recoveryBookieService.handle(request3);
         assertEquals(HttpServer.StatusCode.OK.getValue(), 
response3.getStatusCode());
 
-        //4, body with bookie_src, and delete_cookie of PUT, should success.
-        String putBody4 = "{\"bookie_src\": [ \"" + bookieSrc + "\" ],"
-          + "\"delete_cookie\": false }";
-        HttpServiceRequest request4 = new HttpServiceRequest(putBody4, 
HttpServer.Method.PUT, null);
-        HttpServiceResponse response4 = recoveryBookieService.handle(request4);
-        assertEquals(HttpServer.StatusCode.OK.getValue(), 
response4.getStatusCode());
-
         //5, body with bookie_src of PUT, should success.
         String putBody5 = "{\"bookie_src\": [ \"" + bookieSrc + "\" ] }";
         HttpServiceRequest request5 = new HttpServiceRequest(putBody5, 
HttpServer.Method.PUT, null);
diff --git 
a/tests/backward/src/test/java/org/apache/bookkeeper/tests/backward/TestBookieRecovery.java
 
b/tests/backward/src/test/java/org/apache/bookkeeper/tests/backward/TestBookieRecovery.java
index a009154..64b42bb 100644
--- 
a/tests/backward/src/test/java/org/apache/bookkeeper/tests/backward/TestBookieRecovery.java
+++ 
b/tests/backward/src/test/java/org/apache/bookkeeper/tests/backward/TestBookieRecovery.java
@@ -318,7 +318,7 @@ public class TestBookieRecovery extends 
BookKeeperClusterTestCase {
 
             bka = new BookKeeperAdmin(adminConf);
             try {
-                bka.recoverBookieData(bookieSrc, null);
+                bka.recoverBookieData(bookieSrc);
                 fail("Shouldn't be able to recover with wrong password");
             } catch (BKException bke) {
                 // correct behaviour
@@ -336,7 +336,7 @@ public class TestBookieRecovery extends 
BookKeeperClusterTestCase {
 
         bka = new BookKeeperAdmin(adminConf);
         try {
-            bka.recoverBookieData(bookieSrc, null);
+            bka.recoverBookieData(bookieSrc);
             fail("Shouldn't be able to recover with wrong digest");
         } catch (BKException bke) {
             // correct behaviour
@@ -353,7 +353,7 @@ public class TestBookieRecovery extends 
BookKeeperClusterTestCase {
         adminConf.setBookieRecoveryPasswd(passwdCorrect);
 
         bka = new BookKeeperAdmin(adminConf);
-        bka.recoverBookieData(bookieSrc, null);
+        bka.recoverBookieData(bookieSrc);
         bka.close();
 
         lh = bkc.openLedgerNoRecovery(ledgerId, digestCorrect, passwdCorrect);

-- 
To stop receiving notification emails like this one, please contact
['"[email protected]" <[email protected]>'].

Reply via email to