package org.apache.bookkeeper.client;
/*
*
* 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.util.concurrent.Executors;
import org.apache.bookkeeper.client.BKException;
import org.apache.bookkeeper.client.AsyncCallback.CreateCallback;
import org.apache.bookkeeper.client.AsyncCallback.OpenCallback;
import org.apache.bookkeeper.client.BKException.Code;
import org.apache.bookkeeper.client.SyncCounter;
import org.apache.bookkeeper.proto.BookieClient;
import org.apache.bookkeeper.util.OrderedSafeExecutor;
import org.apache.log4j.Logger;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.jboss.netty.channel.socket.ClientSocketChannelFactory;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
/**
* BookKeeper client. We assume there is one single writer to a ledger at any
* time.
*
* There are three possible operations: start a new ledger, write to a ledger,
* and read from a ledger.
*
* The exceptions resulting from synchronous calls and error code resulting from
* asynchronous calls can be found in the class {@link BKException}.
*
*
*/
public class BookKeeper implements OpenCallback, CreateCallback {
static final Logger LOG = Logger.getLogger(BookKeeper.class);
ZooKeeper zk = null;
// whether the zk handle is one we created, or is owned by whoever
// instantiated us
boolean ownZKHandle = false;
ClientSocketChannelFactory channelFactory;
// whether the socket factory is one we created, or is owned by whoever
// instantiated us
boolean ownChannelFactory = false;
BookieClient bookieClient;
BookieWatcher bookieWatcher;
OrderedSafeExecutor callbackWorker = new OrderedSafeExecutor(Runtime
.getRuntime().availableProcessors());
OrderedSafeExecutor mainWorkerPool = new OrderedSafeExecutor(Runtime
.getRuntime().availableProcessors());
/**
* Create a bookkeeper client. A zookeeper client and a client socket factory
* will be instantiated as part of this constructor.
*
* @param servers
* A list of one of more servers on which zookeeper is running. The
* client assumes that the running bookies have been registered with
* zookeeper under the path
* {@link BookieWatcher#BOOKIE_REGISTRATION_PATH}
* @throws IOException
* @throws InterruptedException
* @throws KeeperException
*/
public BookKeeper(String servers) throws IOException, InterruptedException,
KeeperException {
this(new ZooKeeper(servers, 10000, new Watcher() {
@Override
public void process(WatchedEvent event) {
// TODO: handle session disconnects and expires
if (LOG.isDebugEnabled()) {
LOG.debug("Process: " + event.getType() + " " + event.getPath());
}
}
}), new NioClientSocketChannelFactory(Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
ownZKHandle = true;
ownChannelFactory = true;
}
/**
* Create a bookkeeper client but use the passed in zookeeper client instead
* of instantiating one.
*
* @param zk
* Zookeeper client instance connected to the zookeeper with which
* the bookies have registered
* @throws InterruptedException
* @throws KeeperException
*/
public BookKeeper(ZooKeeper zk) throws InterruptedException, KeeperException {
this(zk, new NioClientSocketChannelFactory(Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
ownChannelFactory = true;
}
/**
* Create a bookkeeper client but use the passed in zookeeper client and
* client socket channel factory instead of instantiating those.
*
* @param zk
* Zookeeper client instance connected to the zookeeper with which
* the bookies have registered
* @param channelFactory
* A factory that will be used to create connections to the bookies
* @throws InterruptedException
* @throws KeeperException
*/
public BookKeeper(ZooKeeper zk, ClientSocketChannelFactory channelFactory)
throws InterruptedException, KeeperException {
if (zk == null || channelFactory == null) {
throw new NullPointerException();
}
this.zk = zk;
this.channelFactory = channelFactory;
bookieWatcher = new BookieWatcher(this);
bookieWatcher.readBookiesBlocking();
bookieClient = new BookieClient(channelFactory, mainWorkerPool);
}
/**
* There are 2 digest types that can be used for verification. The CRC32 is
* cheap to compute but does not protect against byzantine bookies (i.e., a
* bookie might report fake bytes and a matching CRC32). The MAC code is more
* expensive to compute, but is protected by a password, i.e., a bookie can't
* report fake bytes with a mathching MAC unless it knows the password
*/
public enum DigestType {
MAC, CRC32
};
public ZooKeeper getZkHandle() {
return zk;
}
/**
* Get the BookieClient, currently used for doing bookie recovery.
*
* @return BookieClient for the BookKeeper instance.
*/
public BookieClient getBookieClient() {
return bookieClient;
}
/**
* Creates a new ledger asynchronously. To create a ledger, we need to specify
* the ensemble size, the quorum size, the digest type, a password, a callback
* implementation, and an optional control object. The ensemble size is how
* many bookies the entries should be striped among and the quorum size is the
* degree of replication of each entry. The digest type is either a MAC or a
* CRC. Note that the CRC option is not able to protect a client against a
* bookie that replaces an entry. The password is used not only to
* authenticate access to a ledger, but also to verify entries in ledgers.
*
* @param ensSize
* ensemble size
* @param qSize
* quorum size
* @param digestType
* digest type, either MAC or CRC32
* @param passwd
* password
* @param cb
* createCallback implementation
* @param ctx
* optional control object
*/
public void asyncCreateLedger(int ensSize, int qSize, DigestType digestType,
byte[] passwd, CreateCallback cb, Object ctx) {
new LedgerCreateOp(this, ensSize, qSize, digestType, passwd, cb, ctx)
.initiate();
}
/**
* Create callback implementation for synchronous create call.
*
* @param rc
* return code
* @param lh
* ledger handle object
* @param ctx
* optional control object
*/
public void createComplete(int rc, LedgerHandle lh, Object ctx) {
SyncCounter counter = (SyncCounter) ctx;
counter.setLh(lh);
counter.setrc(rc);
counter.dec();
}
/**
* Creates a new ledger. Default of 3 servers, and quorum of 2 servers.
*
* @param digestType
* digest type, either MAC or CRC32
* @param passwd
* password
* @return
* @throws KeeperException
* @throws InterruptedException
* @throws BKException
*/
public LedgerHandle createLedger(DigestType digestType, byte passwd[])
throws KeeperException, BKException, InterruptedException, IOException {
return createLedger(3, 2, digestType, passwd);
}
/**
* Synchronous call to create ledger. Parameters match those of
* {@link #asyncCreateLedger(int, int, DigestType, byte[], CreateCallback, Object)}
*
* @param ensSize
* @param qSize
* @param digestType
* @param passwd
* @return
* @throws KeeperException
* @throws InterruptedException
* @throws IOException
* @throws BKException
*/
public LedgerHandle createLedger(int ensSize, int qSize,
DigestType digestType, byte passwd[]) throws KeeperException,
InterruptedException, IOException, BKException {
SyncCounter counter = new SyncCounter();
counter.inc();
/*
* Calls asynchronous version
*/
asyncCreateLedger(ensSize, qSize, digestType, passwd, this, counter);
/*
* Wait
*/
counter.block(0);
if (counter.getLh() == null) {
LOG.error("ZooKeeper error: " + counter.getrc());
throw BKException.create(Code.ZKException);
}
return counter.getLh();
}
/**
* Open existing ledger asynchronously for reading.
*
* @param lId
* ledger identifier
* @param digestType
* digest type, either MAC or CRC32
* @param passwd
* password
* @param ctx
* optional control object
*/
public void asyncOpenLedger(long lId, DigestType digestType, byte passwd[],
OpenCallback cb, Object ctx) {
new LedgerOpenOp(this, lId, digestType, passwd, cb, ctx).initiate();
}
/**
* Callback method for synchronous open operation
*
* @param rc
* return code
* @param lh
* ledger handle
* @param ctx
* optional control object
*/
public void openComplete(int rc, LedgerHandle lh, Object ctx) {
SyncCounter counter = (SyncCounter) ctx;
counter.setLh(lh);
LOG.debug("Open complete: " + rc);
counter.setrc(rc);
counter.dec();
}
/**
* Synchronous open ledger call
*
* @param lId
* ledger identifier
* @param digestType
* digest type, either MAC or CRC32
* @param passwd
* password
* @return
* @throws InterruptedException
* @throws BKException
*/
public LedgerHandle openLedger(long lId, DigestType digestType, byte passwd[])
throws BKException, InterruptedException {
SyncCounter counter = new SyncCounter();
counter.inc();
/*
* Calls async open ledger
*/
asyncOpenLedger(lId, digestType, passwd, this, counter);
/*
* Wait
*/
counter.block(0);
if (counter.getrc() != BKException.Code.OK)
throw BKException.create(counter.getrc());
return counter.getLh();
}
/**
* Shuts down client.
*
*/
public void halt() throws InterruptedException {
bookieClient.close();
bookieWatcher.halt();
if (ownChannelFactory) {
channelFactory.releaseExternalResources();
}
if (ownZKHandle) {
zk.close();
}
callbackWorker.shutdown();
mainWorkerPool.shutdown();
}
}