package org.apache.bookkeeper.tools;
/*
*
* 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.
*
*/
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.bookkeeper.client.BKException;
import org.apache.bookkeeper.client.BookKeeper;
import org.apache.bookkeeper.client.LedgerEntry;
import org.apache.bookkeeper.client.LedgerHandle;
import org.apache.bookkeeper.client.AsyncCallback.OpenCallback;
import org.apache.bookkeeper.client.AsyncCallback.ReadCallback;
import org.apache.bookkeeper.client.AsyncCallback.RecoverCallback;
import org.apache.bookkeeper.client.BookKeeper.DigestType;
import org.apache.bookkeeper.proto.BookkeeperInternalCallbacks.WriteCallback;
import org.apache.log4j.Logger;
import org.apache.zookeeper.AsyncCallback;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.KeeperException.Code;
import org.apache.zookeeper.data.Stat;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* Provides Admin Tools to manage the BookKeeper cluster.
*
*/
public class BookKeeperTools {
private static Logger LOG = Logger.getLogger(BookKeeperTools.class);
// ZK client instance
private ZooKeeper zk;
// ZK ledgers related String constants
static final String LEDGERS_PATH = "/ledgers";
static final String LEDGER_NODE_PREFIX = "L";
static final String AVAILABLE_NODE = "available";
static final String BOOKIES_PATH = LEDGERS_PATH + "/" + AVAILABLE_NODE;
static final String COLON = ":";
// BookKeeper client instance
private BookKeeper bkc;
/*
* Random number generator used to choose an available bookie server to
* replicate data from a dead bookie.
*/
private Random rand = new Random();
/*
* For now, assume that all ledgers were created with the same DigestType
* and password. In the future, this admin tool will need to know for each
* ledger, what was the DigestType and password used to create it before it
* can open it. These values will come from System properties, though hard
* coded defaults are defined here.
*/
private DigestType DIGEST_TYPE = DigestType.valueOf(System.getProperty("digestType", DigestType.CRC32.toString()));
private byte[] PASSWD = System.getProperty("passwd", "").getBytes();
/**
* Constructor that takes in a ZooKeeper servers connect string so we know
* how to connect to ZooKeeper to retrieve information about the BookKeeper
* cluster. We need this before we can do any type of admin operations on
* the BookKeeper cluster.
*
* @param zkServers
* Comma separated list of hostname:port pairs for the ZooKeeper
* servers cluster.
* @throws IOException
* Throws this exception if there is an error instantiating the
* ZooKeeper client.
* @throws InterruptedException
* Throws this exception if there is an error instantiating the
* BookKeeper client.
* @throws KeeperException
* Throws this exception if there is an error instantiating the
* BookKeeper client.
*/
public BookKeeperTools(String zkServers) throws IOException, InterruptedException, KeeperException {
// Create the ZooKeeper client instance
zk = new ZooKeeper(zkServers, 10000, new Watcher() {
@Override
public void process(WatchedEvent event) {
if (LOG.isDebugEnabled()) {
LOG.debug("Process: " + event.getType() + " " + event.getPath());
}
}
});
// Create the BookKeeper client instance
bkc = new BookKeeper(zk);
}
/**
* Shutdown method to gracefully release resources that this class uses.
*
* @throws InterruptedException
* if there is an error shutting down the clients that this
* class uses.
*/
public void shutdown() throws InterruptedException {
bkc.halt();
zk.close();
}
/**
* This is a multi callback object for bookie recovery that waits for all of
* the multiple async operations to complete. If any fail, then we invoke
* the final callback with a BK LedgerRecoveryException.
*/
class MultiCallback implements AsyncCallback.VoidCallback {
// Number of expected callbacks
final int expected;
// Final callback and the corresponding context to invoke
final AsyncCallback.VoidCallback cb;
final Object context;
// This keeps track of how many operations have completed
final AtomicInteger done = new AtomicInteger();
// List of the exceptions from operations that completed unsuccessfully
final LinkedBlockingQueue<Integer> exceptions = new LinkedBlockingQueue<Integer>();
MultiCallback(int expected, AsyncCallback.VoidCallback cb, Object context) {
this.expected = expected;
this.cb = cb;
this.context = context;
if (expected == 0) {
cb.processResult(Code.OK.intValue(), null, context);
}
}
private void tick() {
if (done.incrementAndGet() == expected) {
if (exceptions.isEmpty()) {
cb.processResult(Code.OK.intValue(), null, context);
} else {
cb.processResult(BKException.Code.LedgerRecoveryException, null, context);
}
}
}
@Override
public void processResult(int rc, String path, Object ctx) {
if (rc != Code.OK.intValue()) {
LOG.error("BK error recovering ledger data", BKException.create(rc));
exceptions.add(rc);
}
tick();
}
}
/**
* Method to get the input ledger's digest type. For now, this is just a
* placeholder function since there is no way we can get this information
* easily. In the future, BookKeeper should store this ledger metadata
* somewhere such that an admin tool can access it.
*
* @param ledgerId
* LedgerId we are retrieving the digestType for.
* @return DigestType for the input ledger
*/
private DigestType getLedgerDigestType(long ledgerId) {
return DIGEST_TYPE;
}
/**
* Method to get the input ledger's password. For now, this is just a
* placeholder function since there is no way we can get this information
* easily. In the future, BookKeeper should store this ledger metadata
* somewhere such that an admin tool can access it.
*
* @param ledgerId
* LedgerId we are retrieving the password for.
* @return Password for the input ledger
*/
private byte[] getLedgerPasswd(long ledgerId) {
return PASSWD;
}
// Object used for calling async methods and waiting for them to complete.
class SyncObject {
boolean value;
public SyncObject() {
value = false;
}
}
/**
* Synchronous method to rebuild and recover the ledger fragments data that
* was stored on the source bookie. That bookie could have failed completely
* and now the ledger data that was stored on it is under replicated. An
* optional destination bookie server could be given if we want to copy all
* of the ledger fragments data on the failed source bookie to it.
* Otherwise, we will just randomly distribute the ledger fragments to the
* active set of bookies, perhaps based on load. All ZooKeeper ledger
* metadata will be updated to point to the new bookie(s) that contain the
* replicated ledger fragments.
*
* @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 InetSocketAddress bookieSrc, final InetSocketAddress bookieDest)
throws InterruptedException {
SyncObject sync = new SyncObject();
// Call the async method to recover bookie data.
asyncRecoverBookieData(bookieSrc, bookieDest, new RecoverCallback() {
@Override
public void recoverComplete(int rc, Object ctx) {
LOG.info("Recover bookie operation completed with rc: " + rc);
SyncObject syncObj = (SyncObject) ctx;
synchronized (syncObj) {
syncObj.value = true;
syncObj.notify();
}
}
}, sync);
// Wait for the async method to complete.
synchronized (sync) {
while (sync.value == false) {
sync.wait();
}
}
}
/**
* Async method to rebuild and recover the ledger fragments data that was
* stored on the source bookie. That bookie could have failed completely and
* now the ledger data that was stored on it is under replicated. An
* optional destination bookie server could be given if we want to copy all
* of the ledger fragments data on the failed source bookie to it.
* Otherwise, we will just randomly distribute the ledger fragments to the
* active set of bookies, perhaps based on load. All ZooKeeper ledger
* metadata will be updated to point to the new bookie(s) that contain the
* replicated ledger fragments.
*
* @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.
* @param cb
* 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(final InetSocketAddress bookieSrc, final InetSocketAddress bookieDest,
final RecoverCallback cb, final Object context) {
// Sync ZK to make sure we're reading the latest bookie/ledger data.
zk.sync(LEDGERS_PATH, new AsyncCallback.VoidCallback() {
@Override
public void processResult(int rc, String path, Object ctx) {
if (rc != Code.OK.intValue()) {
LOG.error("ZK error syncing: ", KeeperException.create(KeeperException.Code.get(rc), path));
cb.recoverComplete(BKException.Code.ZKException, context);
return;
}
getAvailableBookies(bookieSrc, bookieDest, cb, context);
};
}, null);
}
/**
* This method asynchronously gets the set of available Bookies that the
* dead input bookie's data will be copied over into. If the user passed in
* a specific destination bookie, then just use that one. Otherwise, we'll
* randomly pick one of the other available bookies to use for each ledger
* fragment we are replicating.
*
* @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.
* @param cb
* 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.
*/
private void getAvailableBookies(final InetSocketAddress bookieSrc, final InetSocketAddress bookieDest,
final RecoverCallback cb, final Object context) {
final List<InetSocketAddress> availableBookies = new LinkedList<InetSocketAddress>();
if (bookieDest != null) {
availableBookies.add(bookieDest);
// Now poll ZK to get the active ledgers
getActiveLedgers(bookieSrc, bookieDest, cb, context, availableBookies);
} else {
zk.getChildren(BOOKIES_PATH, null, new AsyncCallback.ChildrenCallback() {
@Override
public void processResult(int rc, String path, Object ctx, List<String> children) {
if (rc != Code.OK.intValue()) {
LOG.error("ZK error getting bookie nodes: ", KeeperException.create(KeeperException.Code
.get(rc), path));
cb.recoverComplete(BKException.Code.ZKException, context);
return;
}
for (String bookieNode : children) {
String parts[] = bookieNode.split(COLON);
if (parts.length < 2) {
LOG.error("Bookie Node retrieved from ZK has invalid name format: " + bookieNode);
cb.recoverComplete(BKException.Code.ZKException, context);
return;
}
availableBookies.add(new InetSocketAddress(parts[0], Integer.parseInt(parts[1])));
}
// Now poll ZK to get the active ledgers
getActiveLedgers(bookieSrc, bookieDest, cb, context, availableBookies);
}
}, null);
}
}
/**
* 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
* input bookie.
*
* @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.
* @param cb
* 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.
* @param availableBookies
* List of Bookie Servers that are available to use for
* replicating data on the failed bookie. This could contain a
* single bookie server if the user explicitly chose a bookie
* server to replicate data to.
*/
private void getActiveLedgers(final InetSocketAddress bookieSrc, final InetSocketAddress bookieDest,
final RecoverCallback cb, final Object context, final List<InetSocketAddress> availableBookies) {
zk.getChildren(LEDGERS_PATH, null, new AsyncCallback.ChildrenCallback() {
@Override
public void processResult(int rc, String path, Object ctx, List<String> children) {
if (rc != Code.OK.intValue()) {
LOG.error("ZK error getting ledger nodes: ", KeeperException.create(KeeperException.Code.get(rc),
path));
cb.recoverComplete(BKException.Code.ZKException, context);
return;
}
// Wrapper class around the RecoverCallback so it can be used
// as the final VoidCallback to invoke within the MultiCallback.
class RecoverCallbackWrapper implements AsyncCallback.VoidCallback {
final RecoverCallback cb;
RecoverCallbackWrapper(RecoverCallback cb) {
this.cb = cb;
}
@Override
public void processResult(int rc, String path, Object ctx) {
cb.recoverComplete(rc, ctx);
}
}
// Recover each of the ledgers asynchronously
MultiCallback ledgerMcb = new MultiCallback(children.size(), new RecoverCallbackWrapper(cb), context);
for (final String ledgerNode : children) {
recoverLedger(bookieSrc, ledgerNode, ledgerMcb, availableBookies);
}
}
}, null);
}
/**
* This method asynchronously recovers a given ledger if any of the ledger
* entries were stored on the failed bookie.
*
* @param bookieSrc
* Source bookie that had a failure. We want to replicate the
* ledger fragments that were stored there.
* @param ledgerNode
* Ledger Node name as retrieved from ZooKeeper we want to
* recover.
* @param ledgerMcb
* MultiCallback to invoke once we've recovered the current
* ledger.
* @param availableBookies
* List of Bookie Servers that are available to use for
* replicating data on the failed bookie. This could contain a
* single bookie server if the user explicitly chose a bookie
* server to replicate data to.
*/
private void recoverLedger(final InetSocketAddress bookieSrc, final String ledgerNode,
final MultiCallback ledgerMcb, final List<InetSocketAddress> availableBookies) {
/*
* The available node is also stored in this path so ignore that. That
* node is the path for the set of available Bookie Servers.
*/
if (ledgerNode.equals(AVAILABLE_NODE)) {
ledgerMcb.processResult(BKException.Code.OK, null, null);
return;
}
// Parse out the ledgerId from the ZK ledger node.
String parts[] = ledgerNode.split(LEDGER_NODE_PREFIX);
if (parts.length < 2) {
LOG.error("Ledger Node retrieved from ZK has invalid name format: " + ledgerNode);
ledgerMcb.processResult(BKException.Code.ZKException, null, null);
return;
}
final long lId;
try {
lId = Long.parseLong(parts[parts.length - 1]);
} catch (NumberFormatException e) {
LOG.error("Error retrieving ledgerId from ledgerNode: " + ledgerNode, e);
ledgerMcb.processResult(BKException.Code.ZKException, null, null);
return;
}
/*
* For the current ledger, open it to retrieve the LedgerHandle. This
* will contain the LedgerMetadata indicating which bookie servers the
* ledger fragments are stored on. Check if any of the ledger fragments
* for the current ledger are stored on the input dead bookie.
*/
DigestType digestType = getLedgerDigestType(lId);
byte[] passwd = getLedgerPasswd(lId);
bkc.asyncOpenLedger(lId, digestType, passwd, new OpenCallback() {
@Override
public void openComplete(int rc, final LedgerHandle lh, Object ctx) {
if (rc != Code.OK.intValue()) {
LOG.error("BK error opening ledger: " + lId, BKException.create(rc));
ledgerMcb.processResult(rc, null, null);
return;
}
/*
* This List stores the ledger fragments to recover indexed by
* the start entry ID for the range. The ensembles TreeMap is
* keyed off this.
*/
final List<Long> ledgerFragmentsToRecover = new LinkedList<Long>();
/*
* This Map will store the start and end entry ID values for
* each of the ledger fragment ranges. The only exception is the
* current active fragment since it has no end yet. In the event
* of a bookie failure, a new ensemble is created so the current
* ensemble should not contain the dead bookie we are trying to
* recover.
*/
Map<Long, Long> ledgerFragmentsRange = new HashMap<Long, Long>();
Long curEntryId = null;
for (Map.Entry<Long, ArrayList<InetSocketAddress>> entry : lh.getLedgerMetadata().getEnsembles()
.entrySet()) {
if (curEntryId != null)
ledgerFragmentsRange.put(curEntryId, entry.getKey() - 1);
curEntryId = entry.getKey();
if (entry.getValue().contains(bookieSrc)) {
/*
* Current ledger fragment has entries stored on the
* dead bookie so we'll need to recover them.
*/
ledgerFragmentsToRecover.add(entry.getKey());
}
}
/*
* See if this current ledger contains any ledger fragment that
* needs to be re-replicated. If not, then just invoke the
* multiCallback and return.
*/
if (ledgerFragmentsToRecover.size() == 0) {
ledgerMcb.processResult(BKException.Code.OK, null, null);
return;
}
/*
* We have ledger fragments that need to be re-replicated to a
* new bookie. Choose one randomly from the available set of
* bookies.
*/
final InetSocketAddress newBookie = availableBookies.get(rand.nextInt(availableBookies.size()));
/*
* Wrapper class around the ledger MultiCallback. Once all
* ledger fragments for the ledger have been replicated to a new
* bookie, we need to update ZK with this new metadata to point
* to the new bookie instead of the old dead one. That should be
* done at the end prior to invoking the ledger MultiCallback.
*/
class LedgerMultiCallbackWrapper implements AsyncCallback.VoidCallback {
final MultiCallback ledgerMcb;
LedgerMultiCallbackWrapper(MultiCallback ledgerMcb) {
this.ledgerMcb = ledgerMcb;
}
@Override
public void processResult(int rc, String path, Object ctx) {
if (rc != Code.OK.intValue()) {
LOG.error("BK error replicating ledger fragments for ledger: " + lId, BKException
.create(rc));
ledgerMcb.processResult(rc, null, null);
return;
}
/*
* Update the ledger metadata's ensemble info to point
* to the new bookie.
*/
for (final Long startEntryId : ledgerFragmentsToRecover) {
ArrayList<InetSocketAddress> ensemble = lh.getLedgerMetadata().getEnsembles().get(
startEntryId);
int deadBookieIndex = ensemble.indexOf(bookieSrc);
ensemble.remove(deadBookieIndex);
ensemble.add(deadBookieIndex, newBookie);
}
lh.writeLedgerConfig(new AsyncCallback.StatCallback() {
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
if (rc != Code.OK.intValue()) {
LOG.error("ZK error updating ledger config metadata for ledgerId: " + lh.getId(),
KeeperException.create(KeeperException.Code.get(rc), path));
} else {
LOG.info("Updated ZK for ledgerId: (" + lh.getId()
+ ") to point ledger fragments from old dead bookie: (" + bookieSrc
+ ") to new bookie: (" + newBookie + ")");
}
/*
* Pass the return code result up the chain with
* the parent callback.
*/
ledgerMcb.processResult(rc, null, null);
}
}, null);
}
}
/*
* Now recover all of the necessary ledger fragments
* asynchronously using a MultiCallback for every fragment.
*/
MultiCallback ledgerFragmentMcb = new MultiCallback(ledgerFragmentsToRecover.size(),
new LedgerMultiCallbackWrapper(ledgerMcb), null);
for (final Long startEntryId : ledgerFragmentsToRecover) {
Long endEntryId = ledgerFragmentsRange.get(startEntryId);
recoverLedgerFragment(bookieSrc, lh, startEntryId, endEntryId, ledgerFragmentMcb, newBookie);
}
}
}, null);
}
/**
* This method asynchronously recovers a ledger fragment which is a
* contiguous portion of a ledger that was stored in an ensemble that
* included the failed bookie.
*
* @param bookieSrc
* Source bookie that had a failure. We want to replicate the
* ledger fragments that were stored there.
* @param lh
* LedgerHandle for the ledger
* @param startEntryId
* Start entry Id for the ledger fragment
* @param endEntryId
* End entry Id for the ledger fragment
* @param ledgerFragmentMcb
* MultiCallback to invoke once we've recovered the current
* ledger fragment.
* @param newBookie
* New bookie we want to use to recover and replicate the ledger
* entries that were stored on the failed bookie.
*/
private void recoverLedgerFragment(final InetSocketAddress bookieSrc, final LedgerHandle lh,
final Long startEntryId, final Long endEntryId, final MultiCallback ledgerFragmentMcb,
final InetSocketAddress newBookie) {
if (endEntryId == null) {
/*
* Ideally this should never happen if bookie failure is taken care
* of properly. Nothing we can do though in this case.
*/
LOG.warn("Dead bookie (" + bookieSrc + ") is still part of the current active ensemble for ledgerId: "
+ lh.getId());
ledgerFragmentMcb.processResult(BKException.Code.OK, null, null);
return;
}
ArrayList<InetSocketAddress> curEnsemble = lh.getLedgerMetadata().getEnsembles().get(startEntryId);
int bookieIndex = 0;
for (int i = 0; i < curEnsemble.size(); i++) {
if (curEnsemble.get(i).equals(bookieSrc)) {
bookieIndex = i;
break;
}
}
/*
* Loop through all entries in the current ledger fragment range and
* find the ones that were stored on the dead bookie.
*/
List<Long> entriesToReplicate = new LinkedList<Long>();
for (long i = startEntryId; i <= endEntryId; i++) {
if (lh.getDistributionSchedule().getReplicaIndex(i, bookieIndex) >= 0) {
/*
* Current entry is stored on the dead bookie so we'll need to
* read it and replicate it to a new bookie.
*/
entriesToReplicate.add(i);
}
}
/*
* Now asynchronously replicate all of the entries for the ledger
* fragment that were on the dead bookie.
*/
MultiCallback ledgerFragmentEntryMcb = new MultiCallback(entriesToReplicate.size(), ledgerFragmentMcb, null);
for (final Long entryId : entriesToReplicate) {
recoverLedgerFragmentEntry(entryId, lh, ledgerFragmentEntryMcb, newBookie);
}
}
/**
* This method asynchronously recovers a specific ledger entry by reading
* the values via the BookKeeper Client (which would read it from the other
* replicas) and then writing it to the chosen new bookie.
*
* @param entryId
* Ledger Entry ID to recover.
* @param lh
* LedgerHandle for the ledger
* @param ledgerFragmentEntryMcb
* MultiCallback to invoke once we've recovered the current
* ledger entry.
* @param newBookie
* New bookie we want to use to recover and replicate the ledger
* entries that were stored on the failed bookie.
*/
private void recoverLedgerFragmentEntry(final Long entryId, final LedgerHandle lh,
final MultiCallback ledgerFragmentEntryMcb, final InetSocketAddress newBookie) {
/*
* Read the ledger entry using the LedgerHandle. This will allow us to
* read the entry from one of the other replicated bookies other than
* the dead one.
*/
lh.asyncReadEntries(entryId, entryId, new ReadCallback() {
@Override
public void readComplete(int rc, LedgerHandle lh, Enumeration<LedgerEntry> seq, Object ctx) {
if (rc != Code.OK.intValue()) {
LOG.error("BK error reading ledger entry: " + entryId, BKException.create(rc));
ledgerFragmentEntryMcb.processResult(rc, null, null);
return;
}
/*
* Now that we've read the ledger entry, write it to the new
* bookie we've selected.
*/
LedgerEntry entry = seq.nextElement();
ChannelBuffer toSend = lh.getDigestManager().computeDigestAndPackageForSending(entryId,
lh.getLastAddConfirmed(), entry.getLength(), entry.getEntry());
bkc.getBookieClient().addEntry(newBookie, lh.getId(), lh.getLedgerKey(), entryId, toSend,
new WriteCallback() {
@Override
public void writeComplete(int rc, long ledgerId, long entryId, InetSocketAddress addr,
Object ctx) {
if (rc != Code.OK.intValue()) {
LOG.error("BK error writing entry for ledgerId: " + ledgerId + ", entryId: "
+ entryId + ", bookie: " + addr, BKException.create(rc));
} else {
LOG.debug("Success writing ledger entry to a new bookie!");
}
/*
* Pass the return code result up the chain with
* the parent callback.
*/
ledgerFragmentEntryMcb.processResult(rc, null, null);
}
}, null);
}
}, null);
}
/**
* Main method so we can invoke the bookie recovery via command line.
*
* @param args
* Arguments to BookKeeperTools. 2 are required and the third is
* optional. The first is a comma separated list of ZK server
* host:port pairs. The second is the host:port socket address
* for the bookie we are trying to recover. The third is the
* host:port socket address of the optional destination bookie
* server we want to replicate the data over to.
* @throws InterruptedException
* @throws IOException
* @throws KeeperException
*/
public static void main(String[] args) throws InterruptedException, IOException, KeeperException {
// Validate the inputs
if (args.length < 2) {
System.err.println("USAGE: BookKeeperTools zkServers bookieSrc [bookieDest]");
return;
}
// Parse out the input arguments
String zkServers = args[0];
String bookieSrcString[] = args[1].split(COLON);
if (bookieSrcString.length < 2) {
System.err.println("BookieSrc inputted has invalid name format (host:port expected): " + bookieSrcString);
return;
}
final InetSocketAddress bookieSrc = new InetSocketAddress(bookieSrcString[0], Integer
.parseInt(bookieSrcString[1]));
InetSocketAddress bookieDest = null;
if (args.length < 3) {
String bookieDestString[] = args[2].split(COLON);
if (bookieDestString.length < 2) {
System.err.println("BookieDest inputted has invalid name format (host:port expected): "
+ bookieDestString);
return;
}
bookieDest = new InetSocketAddress(bookieDestString[0], Integer.parseInt(bookieDestString[1]));
}
// Create the BookKeeperTools instance and perform the bookie recovery
// synchronously.
BookKeeperTools bkTools = new BookKeeperTools(zkServers);
bkTools.recoverBookieData(bookieSrc, bookieDest);
// Shutdown the resources used in the BookKeeperTools instance.
bkTools.shutdown();
}
}