// =================================================================================================
// Copyright 2011 Twitter, Inc.
// -------------------------------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this work except in compliance with the License.
// You may obtain a copy of the License in the LICENSE file, or 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 com.twitter.common.zookeeper;
import java.util.List;
import java.util.logging.Logger;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.data.ACL;
import com.twitter.common.quantity.Amount;
import com.twitter.common.quantity.Time;
import com.twitter.common.zookeeper.ZooKeeperClient.ZooKeeperConnectionException;
/**
* Utilities for dealing with zoo keeper.
*
* @author John Sirois
*/
public final class ZooKeeperUtils {
private static final Logger LOG = Logger.getLogger(ZooKeeperUtils.class.getName());
/**
* An appropriate default session timeout for Twitter ZooKeeper clusters.
*/
public static final Amount<Integer,Time> DEFAULT_ZK_SESSION_TIMEOUT = Amount.of(3, Time.SECONDS);
/**
* The magic version number that allows any mutation to always succeed regardless of actual
* version number.
*/
public static final int ANY_VERSION = -1;
/**
* An ACL that gives all permissions to node creators and read permissions only to everyone else.
*/
public static final ImmutableList<ACL> EVERYONE_READ_CREATOR_ALL =
ImmutableList.<ACL>builder()
.addAll(Ids.CREATOR_ALL_ACL)
.addAll(Ids.READ_ACL_UNSAFE)
.build();
/**
* Returns true if the given exception indicates an error that can be resolved by retrying the
* operation without modification.
*
* @param e the exception to check
* @return true if the causing operation is strictly retryable
*/
public static boolean isRetryable(KeeperException e) {
Preconditions.checkNotNull(e);
switch (e.code()) {
case CONNECTIONLOSS:
case SESSIONEXPIRED:
case SESSIONMOVED:
case OPERATIONTIMEOUT:
return true;
case RUNTIMEINCONSISTENCY:
case DATAINCONSISTENCY:
case MARSHALLINGERROR:
case BADARGUMENTS:
case NONODE:
case NOAUTH:
case BADVERSION:
case NOCHILDRENFOREPHEMERALS:
case NODEEXISTS:
case NOTEMPTY:
case INVALIDCALLBACK:
case INVALIDACL:
case AUTHFAILED:
case UNIMPLEMENTED:
// These two should not be encountered - they are used internally by ZK to specify ranges
case SYSTEMERROR:
case APIERROR:
case OK: // This is actually an invalid ZK exception code
default:
return false;
}
}
/**
* Ensures the given {@code path} exists in the ZK cluster accessed by {@code zkClient}. If the
* path already exists, nothing is done; however if any portion of the path is missing, it will be
* created with the given {@code acl} as a persistent zookeeper node. The given {@code path} must
* be a valid zookeeper absolute path.
*
* @param zkClient the client to use to access the ZK cluster
* @param acl the acl to use if creating path nodes
* @param path the path to ensure exists
* @throws ZooKeeperConnectionException if there was a problem accessing the ZK cluster
* @throws InterruptedException if we were interrupted attempting to connect to the ZK cluster
* @throws KeeperException if there was a problem in ZK
*/
public static void ensurePath(ZooKeeperClient zkClient, List<ACL> acl, String path)
throws ZooKeeperConnectionException, InterruptedException, KeeperException {
Preconditions.checkNotNull(zkClient);
Preconditions.checkNotNull(path);
Preconditions.checkArgument(path.startsWith("/"));
ensurePathInternal(zkClient, acl, path);
}
private static void ensurePathInternal(ZooKeeperClient zkClient, List<ACL> acl, String path)
throws ZooKeeperConnectionException, InterruptedException, KeeperException {
if (zkClient.get().exists(path, false) == null) {
// The current path does not exist; so back up a level and ensure the parent path exists
// unless we're already a root-level path.
int lastPathIndex = path.lastIndexOf('/');
if (lastPathIndex > 0) {
ensurePathInternal(zkClient, acl, path.substring(0, lastPathIndex));
}
// We've ensured our parent path (if any) exists so we can proceed to create our path.
try {
zkClient.get().create(path, null, acl, CreateMode.PERSISTENT);
} catch (KeeperException.NodeExistsException e) {
// This ensures we don't die if a race condition was met between checking existence and
// trying to create the node.
LOG.info("Node existed when trying to ensure path " + path + ", somebody beat us to it?");
}
}
}
private ZooKeeperUtils() {
// utility
}
}