/* * Copyright 2010 Outerthought bvba * * Licensed 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.lilyproject.util.zookeeper; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.KeeperException.NotEmptyException; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.ZooKeeper.States; import org.apache.zookeeper.data.Stat; /** * Various ZooKeeper utility methods. */ public class ZkUtil { private ZkUtil() { } public static ZooKeeperItf connect(String connectString, int sessionTimeout) throws ZkConnectException { ZooKeeperImpl zooKeeper; try { zooKeeper = new ZooKeeperImpl(connectString, sessionTimeout); } catch (IOException e) { throw new ZkConnectException("Failed to connect with Zookeeper @ '" + connectString + "'", e); } long waitUntil = System.currentTimeMillis() + sessionTimeout; boolean connected = (States.CONNECTED).equals(zooKeeper.getState()); while (!connected && waitUntil > System.currentTimeMillis()) { try { Thread.sleep(100); } catch (InterruptedException e) { connected = (States.CONNECTED).equals(zooKeeper.getState()); break; } connected = (States.CONNECTED).equals(zooKeeper.getState()); } if (!connected) { System.out.println("Failed to connect to Zookeeper within timeout: Dumping stack: "); Thread.dumpStack(); zooKeeper.close(); throw new ZkConnectException("Failed to connect with Zookeeper @ '" + connectString + "' within timeout " + sessionTimeout); } return zooKeeper; } public static void createPath(final ZooKeeperItf zk, final String path) throws InterruptedException, KeeperException { createPath(zk, path, null); } /** * Creates a persistent path on zookeeper if it does not exist yet, including any parents. * Keeps retrying in case of connection loss. * * <p>The supplied data is used for the last node in the path. If the path already exists, * the data is updated if necessary. */ public static void createPath(final ZooKeeperItf zk, final String path, final byte[] data) throws InterruptedException, KeeperException { if (!path.startsWith("/")) { throw new IllegalArgumentException("Path should start with a slash."); } if (path.endsWith("/")) { throw new IllegalArgumentException("Path should not end on a slash."); } String[] parts = path.substring(1).split("/"); final StringBuilder subPath = new StringBuilder(); boolean created = false; for (int i = 0; i < parts.length; i++) { String part = parts[i]; subPath.append("/").append(part); // Only use the supplied data for the last node in the path final byte[] newData = (i == parts.length - 1 ? data : null); created = zk.retryOperation(new ZooKeeperOperation<Boolean>() { @Override public Boolean execute() throws KeeperException, InterruptedException { if (zk.exists(subPath.toString(), false) == null) { try { zk.create(subPath.toString(), newData, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); return true; } catch (KeeperException.NodeExistsException e) { return false; } } return false; } }); } if (!created) { // The node already existed, update its data if necessary zk.retryOperation(new ZooKeeperOperation<Boolean>() { @Override public Boolean execute() throws KeeperException, InterruptedException { byte[] currentData = zk.getData(path, false, new Stat()); if (!Arrays.equals(currentData, data)) { zk.setData(path, data, -1); } return null; } }); } } /** * Updates data on a zookeeper node. * * <p> * The supplied data is used for the last node in the path. The path must * already exist. It is not checked if the data is changed or not. This will * cause the version of the node to be increased. * <p> * This operation is retried until it succeeds. */ public static void update(final ZooKeeperItf zk, final String path, final byte[] data, final int version) throws InterruptedException, KeeperException { zk.retryOperation(new ZooKeeperOperation<Boolean>() { @Override public Boolean execute() throws KeeperException, InterruptedException { zk.setData(path, data, version); return null; } }); } /** * Deletes a path (non-recursively) in ZooKeeper, if it exists. * <p> * If the path doesn't exist, the delete will fail silently. The delete operation is retried until it succeeds, or * until it fails with a non-recoverable error. * <p> * If the path has children, the operation will fail with the underlying {@link NotEmptyException}. * * @param zk Handle to the ZooKeeper where the delete will occur * @param path The path to be deleted */ public static void deleteNode(final ZooKeeperItf zk, final String path) throws InterruptedException, KeeperException { zk.retryOperation(new ZooKeeperOperation<Boolean>() { @Override public Boolean execute() throws KeeperException, InterruptedException { Stat stat = zk.exists(path, false); if (stat != null) { try { zk.delete(path, stat.getVersion()); } catch (KeeperException.NoNodeException nne) { // This is ok, the node is already gone } // We don't catch BadVersion or NotEmpty as these are probably signs that there is something // unexpected going on with the node that is to be deleted } return true; } }); } /** * Gets data from a zookeeper node. * <p> * This operation is retried until it succeeds. */ public static byte[] getData(final ZooKeeperItf zk, final String path, final Watcher watcher, final Stat stat) throws InterruptedException, KeeperException { final List<byte[]> data = new ArrayList<byte[]>(1); zk.retryOperation(new ZooKeeperOperation<Boolean>() { @Override public Boolean execute() throws KeeperException, InterruptedException { data.add(zk.getData(path, watcher, stat)); return null; } }); return data.get(0); } }