
com.spotify.helios.servicescommon.coordination.DefaultZooKeeperClient Maven / Gradle / Ivy
/*
* Copyright (c) 2014 Spotify AB.
*
* 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 com.spotify.helios.servicescommon.coordination;
import com.fasterxml.jackson.databind.JavaType;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.api.transaction.CuratorTransactionFinal;
import org.apache.curator.framework.api.transaction.CuratorTransactionResult;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.framework.listen.Listenable;
import org.apache.curator.framework.recipes.nodes.PersistentEphemeralNode;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.framework.state.ConnectionStateListener;
import org.apache.curator.utils.PathUtils;
import org.apache.curator.utils.ZKPaths;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.base.Throwables.propagateIfInstanceOf;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.newLinkedList;
import static com.google.common.collect.Lists.reverse;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
public class DefaultZooKeeperClient implements ZooKeeperClient {
private static final Logger log = LoggerFactory.getLogger(DefaultZooKeeperClient.class);
private final CuratorFramework client;
private final String clusterId;
private final AtomicBoolean clusterIdExists;
private final Watcher watcher;
private final ConnectionStateListener connectionStateListener;
public DefaultZooKeeperClient(final CuratorFramework client) {
this(client, null);
}
public DefaultZooKeeperClient(final CuratorFramework client, final String clusterId) {
this.client = client;
this.clusterId = clusterId;
if (clusterId == null) {
this.clusterIdExists = null;
this.watcher = null;
this.connectionStateListener = null;
return;
}
this.clusterIdExists = new AtomicBoolean(false);
this.watcher = new Watcher() {
@Override
public void process(WatchedEvent event) {
log.info("DefaultZooKeeperClient processing WatchedEvent - {}", event);
checkClusterIdExists(clusterId, "watcher");
}
};
connectionStateListener = new ConnectionStateListener() {
@Override
public void stateChanged(CuratorFramework client, ConnectionState newState) {
log.info("DefaultZooKeeperClient connection state change - {}", newState);
if (newState == ConnectionState.RECONNECTED) {
checkClusterIdExists(clusterId, "connectionStateListener");
}
}
};
}
@Override
public CuratorFramework getCuratorFramework() {
return client;
}
@Override
/** {@inheritDoc} */
public void ensurePath(final String path) throws KeeperException {
ensurePath(path, false);
}
@Override
/** {@inheritDoc} */
public void ensurePath(final String path, final boolean excludingLast) throws KeeperException {
PathUtils.validatePath(path);
assertClusterIdFlagTrue();
final String[] parts = path.substring(1).split(Pattern.quote("/"));
final int end = excludingLast ? parts.length - 1 : parts.length;
String current = "";
for (int i = 0; i < end; i++) {
current += "/" + parts[i];
if (exists(current) == null) {
create(current);
}
}
}
@Override
public byte[] getData(final String path) throws KeeperException {
assertClusterIdFlagTrue();
try {
return client.getData().forPath(path);
} catch (Exception e) {
propagateIfInstanceOf(e, KeeperException.class);
throw propagate(e);
}
}
@Override
public Node getNode(final String path) throws KeeperException {
assertClusterIdFlagTrue();
final Stat stat = new Stat();
try {
final byte[] bytes = client.getData().storingStatIn(stat).forPath(path);
return new Node(path, bytes, stat);
} catch (Exception e) {
propagateIfInstanceOf(e, KeeperException.class);
throw propagate(e);
}
}
@Override
public Stat exists(final String path) throws KeeperException {
return stat(path);
}
@Override
public void start() {
if (client.getState() != CuratorFrameworkState.STARTED) {
client.start();
if (clusterId != null) {
client.getConnectionStateListenable().addListener(connectionStateListener);
checkClusterIdExists(clusterId, "start");
}
}
}
@Override
public void close() {
if (clusterId != null) {
client.getConnectionStateListenable().removeListener(connectionStateListener);
}
client.close();
}
@Override
public PersistentEphemeralNode persistentEphemeralNode(final String path,
final PersistentEphemeralNode.Mode mode,
final byte[] data) {
assertClusterIdFlagTrue();
final PersistentEphemeralNode node = new PersistentEphemeralNode(client, mode, path, data);
// ugly hack to work around a problem with curator, wherein PersistentEphemeralNode creates
// all parent paths if they don't exist but applies the ACL for its own path to its parents
try {
final Field field = node.getClass().getDeclaredField("createMethod");
field.setAccessible(true);
field.set(node, client.create());
} catch (NoSuchFieldException | IllegalAccessException e) {
throw propagate(e);
}
return node;
}
@Override
public Listenable getConnectionStateListenable() {
return client.getConnectionStateListenable();
}
@Override
public ZooKeeper.States getState() throws KeeperException {
assertClusterIdFlagTrue();
try {
return client.getZookeeperClient().getZooKeeper().getState();
} catch (Exception e) {
propagateIfInstanceOf(e, KeeperException.class);
throw propagate(e);
}
}
@Override
public List getChildren(final String path) throws KeeperException {
assertClusterIdFlagTrue();
try {
return client.getChildren().forPath(path);
} catch (Exception e) {
propagateIfInstanceOf(e, KeeperException.class);
throw propagate(e);
}
}
@Override
public void deleteRecursive(final String path) throws KeeperException {
assertClusterIdFlagTrue();
try {
final List nodes = listRecursive(path);
if (nodes.isEmpty()) {
return;
}
final CuratorTransactionFinal t = client.inTransaction().check().forPath(path).and();
for (final String node : reverse(nodes)) {
t.delete().forPath(node).and();
}
t.commit();
} catch (Exception e) {
propagateIfInstanceOf(e, KeeperException.class);
throw propagate(e);
}
}
@Override
public List listRecursive(final String path) throws KeeperException {
assertClusterIdFlagTrue();
final Deque queue = newLinkedList();
final List tree = newArrayList();
queue.add(path);
tree.add(path);
while (!queue.isEmpty()) {
final String node = queue.pollFirst();
final List children = getChildren(node);
for (final String child : children) {
final String childPath = node.replaceAll("/$", "") + "/" + child;
queue.add(childPath);
tree.add(childPath);
}
}
return tree;
}
@Override
public void delete(final String path) throws KeeperException {
assertClusterIdFlagTrue();
try {
client.delete().forPath(path);
} catch (Exception e) {
propagateIfInstanceOf(e, KeeperException.class);
throw propagate(e);
}
}
@Override
public void delete(final String path, final int version) throws KeeperException {
assertClusterIdFlagTrue();
final String namespace = emptyToNull(client.getNamespace());
final String namespacedPath = ZKPaths.fixForNamespace(namespace, path);
try {
client.getZookeeperClient().getZooKeeper().delete(namespacedPath, version);
} catch (Exception e) {
propagateIfInstanceOf(e, KeeperException.class);
throw propagate(e);
}
}
@Override
public void createAndSetData(final String path, final byte[] data) throws KeeperException {
assertClusterIdFlagTrue();
try {
client.create().forPath(path, data);
} catch (Exception e) {
propagateIfInstanceOf(e, KeeperException.class);
throw propagate(e);
}
}
@Override
public void create(final String path) throws KeeperException {
assertClusterIdFlagTrue();
try {
client.create().forPath(path);
} catch (Exception e) {
propagateIfInstanceOf(e, KeeperException.class);
throw propagate(e);
}
}
@Override
public void createWithMode(final String path, final CreateMode mode) throws KeeperException {
assertClusterIdFlagTrue();
try {
client.create().withMode(mode).forPath(path);
} catch (Exception e) {
propagateIfInstanceOf(e, KeeperException.class);
throw propagate(e);
}
}
@Override
public void setData(final String path, final byte[] data) throws KeeperException {
assertClusterIdFlagTrue();
try {
client.setData().forPath(path, data);
} catch (Exception e) {
propagateIfInstanceOf(e, KeeperException.class);
throw propagate(e);
}
}
@Override
public Stat stat(final String path) throws KeeperException {
assertClusterIdFlagTrue();
try {
return client.checkExists().forPath(path);
} catch (Exception e) {
propagateIfInstanceOf(e, KeeperException.class);
throw propagate(e);
}
}
@Override
public PersistentPathChildrenCache pathChildrenCache(final String path,
final Path snapshotFile,
final JavaType valueType)
throws IOException, InterruptedException {
return new PersistentPathChildrenCache(client, path, clusterId, snapshotFile, valueType);
}
@Override
public Collection transaction(final List operations)
throws KeeperException {
assertClusterIdFlagTrue();
log.debug("transaction: {}", operations);
if (operations.isEmpty()) {
return emptyList();
}
// Assemble transaction
final CuratorTransactionFinal transaction = (CuratorTransactionFinal) client.inTransaction();
for (final ZooKeeperOperation operation : operations) {
try {
operation.register(transaction);
} catch (final Exception e) {
throw propagate(e);
}
}
// Commit
try {
return transaction.commit();
} catch (Exception e) {
propagateIfInstanceOf(e, KeeperException.class);
throw propagate(e);
}
}
@Override
public Collection transaction(final ZooKeeperOperation... operations)
throws KeeperException {
return transaction(asList(operations));
}
@Override
public void setAcl(final String path, final List aclList) throws KeeperException {
assertClusterIdFlagTrue();
try {
client.setACL().withACL(aclList).forPath(path);
} catch (Exception e) {
propagateIfInstanceOf(e, KeeperException.class);
throw propagate(e);
}
}
@Override
public List getAcl(final String path) throws KeeperException {
assertClusterIdFlagTrue();
try {
return client.getACL().forPath(path);
} catch (Exception e) {
propagateIfInstanceOf(e, KeeperException.class);
throw propagate(e);
}
}
private void assertClusterIdFlagTrue() {
if (clusterId != null && !clusterIdExists.get()) {
throw new IllegalStateException("ZooKeeper cluster ID does not exist");
}
}
private void checkClusterIdExists(final String id, final String checker) {
try {
final Stat stat = client.checkExists().usingWatcher(watcher).forPath(Paths.configId(id));
final boolean exists = stat != null;
clusterIdExists.set(exists);
log.info("Cluster ID {} {} when checked by {}", id, exists ? "exists" : "does not exist",
checker);
} catch (Exception e) {
clusterIdExists.set(false);
log.error("Exception while checking ZooKeeper cluster ID {}", clusterId, e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy