
com.linkedin.parseq.zk.client.ZKClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of parseq-zk-client Show documentation
Show all versions of parseq-zk-client Show documentation
Integrates ParSeq with Apache ZooKeeper Library
/*
* Copyright 2016 LinkedIn Corp.
*
* 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.linkedin.parseq.zk.client;
import com.linkedin.parseq.Context;
import com.linkedin.parseq.Engine;
import com.linkedin.parseq.MultiException;
import com.linkedin.parseq.Task;
import com.linkedin.parseq.promise.Promise;
import com.linkedin.parseq.promise.PromiseListener;
import com.linkedin.parseq.promise.Promises;
import com.linkedin.parseq.promise.SettablePromise;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.Op;
import org.apache.zookeeper.OpResult;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs;
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;
/**
* A thin wrapper around vanilla zookeeper client to facilitate the usage of
* Parseq.
*
* @author Ang Xu
*/
public class ZKClient {
private static final Logger LOG = LoggerFactory.getLogger(ZKClient.class);
private volatile ZooKeeper _zkClient;
private volatile Reaper _reaper;
private final String _connectionString;
private final int _sessionTimeout;
private final Engine _engine;
private final Watcher _defaultWatcher = new DefaultWatcher();
private final Object _mutex = new Object();
private final StateListener _listener = new StateListener();
private KeeperState _currentState = null;
/**
* Constructs a {@link ZKClient} with the given parameters.
*
* @param connectionString comma separated host:port pairs, each corresponding to
* a zk server.
* @param sessionTimeout session timeout in milliseconds.
* @param engine a parseq {@link Engine} to schedule background tasks.
*/
public ZKClient(String connectionString, int sessionTimeout, Engine engine) {
_connectionString = connectionString;
_sessionTimeout = sessionTimeout;
_engine = engine;
}
/**
* Starts the zookeeper connection. The returned promise will be
* resolved once the connection is established.
*
* @return promise
*/
public Promise start() {
AtomicBoolean committed = new AtomicBoolean(false);
SettablePromise promise = Promises.settable();
_listener.subscribe(KeeperState.SyncConnected,
(Promise p) -> {
if (committed.compareAndSet(false, true)) {
if (p.isFailed()) {
promise.fail(p.getError());
} else {
promise.done(null);
}
}
}
);
try {
_zkClient = new ZooKeeper(_connectionString, _sessionTimeout, _defaultWatcher);
_reaper = new Reaper(_engine);
} catch (IOException ex) {
if (committed.compareAndSet(false, true)) {
promise.fail(ex);
}
}
return promise;
}
/**
* Shuts down the zookeeper connection.
* @throws InterruptedException
*/
public void shutdown() throws InterruptedException {
if (_zkClient != null) {
_zkClient.close();
}
}
public ZooKeeper getZookeeper() {
return _zkClient;
}
/**
* Returns task that will create znode for the given path with the given data and acl.
*
* @param path path of the znode.
* @param data data of the znode.
* @param acl acl of the znode.
* @param createMode create mode which specifies whether the znode is persistent
* or ephemeral.
* @return task to create znode.
*/
public Task create(String path, byte[] data, List acl, CreateMode createMode) {
return Task.async("zkCreate: " + path, () -> {
SettablePromise promise = Promises.settable();
_zkClient.create(path, data, acl, createMode,
(int rc, String p, Object ctx, String name) -> {
KeeperException.Code code = KeeperException.Code.get(rc);
switch (code) {
case OK:
promise.done(name);
break;
default:
promise.fail(KeeperException.create(code, p));
}
}, null);
return promise;
});
}
/**
* Returns task that will get data of the znode of the given path.
*
* @param path path of the znode.
* @return task to get data.
*/
public WatchableTask getData(String path) {
return new WatchableTask("zkGetData: " + path) {
@Override
protected Promise extends ZKData> run(Context context)
throws Throwable {
SettablePromise promise = Promises.settable();
_zkClient.getData(path, _watcher,
(int rc, String p, Object ctx, byte[] data, Stat stat) -> {
KeeperException.Code code = KeeperException.Code.get(rc);
switch (code) {
case OK:
promise.done(new ZKData(p, data, stat));
break;
default:
promise.fail(KeeperException.create(code, p));
}
}, null);
return promise;
}
};
}
/**
* Returns task that will set the data for the node of the given path if
* such a node exists and the given version matches the version of the node
* (if the given version is -1, it matches any node's versions).
*
* @param path path of the znode.
* @param data znode data to set.
* @param version expected matching version.
* @return task to set data.
*/
public Task setData(String path, byte[] data, int version) {
return Task.async("zkSetData: " + path, () -> {
SettablePromise promise = Promises.settable();
_zkClient.setData(path, data, version,
(int rc, String p, Object ctx, Stat stat) -> {
KeeperException.Code code = KeeperException.Code.get(rc);
switch (code) {
case OK:
promise.done(stat);
break;
default:
promise.fail(KeeperException.create(code, p));
}
}, null);
return promise;
});
}
/**
* Returns task that will get children for the znode of the given path.
*
* @param path path to the znode.
* @return task to get children.
*/
public WatchableTask> getChildren(String path) {
return new WatchableTask>("zkGetChildren: " + path) {
@Override
protected Promise run(Context context) throws Throwable {
SettablePromise> promise = Promises.settable();
_zkClient.getChildren(path, _watcher, (int rc, String p, Object ctx, List children) -> {
KeeperException.Code code = KeeperException.Code.get(rc);
switch (code) {
case OK:
promise.done(children);
break;
default:
promise.fail(KeeperException.create(code, p));
}
}, null);
return promise;
}
};
}
/**
* Returns task that will test whether the znode of the given path exists
* or not. If exists, the {@link Stat} of the znode is returned.
*
* @param path path to the znode.
* @return task to test existence of the znode.
*/
public WatchableTask> exists(String path) {
return new WatchableTask>("zkExists: " + path) {
@Override
protected Promise run(Context context) throws Throwable {
SettablePromise> promise = Promises.settable();
_zkClient.exists(path, _watcher, (int rc, String p, Object ctx, Stat stat) -> {
KeeperException.Code code = KeeperException.Code.get(rc);
switch (code) {
case OK:
promise.done(Optional.of(stat));
break;
case NONODE:
promise.done(Optional.empty());
break;
default:
promise.fail(KeeperException.create(code, p));
}
}, null);
return promise;
}
};
}
/**
* Returns task to delete znode of the given path if such a node exists
* and the given version matches the version of the node (if the given
* version is -1, it matches any node's versions).
*
* @param path path to the znode.
* @param version expected matching version.
* @return task to delete znode.
*/
public Task delete(String path, int version) {
return Task.async("zkDelete: " + path, () -> {
SettablePromise promise = Promises.settable();
_zkClient.delete(path, version, (int rc, String p, Object ctx) -> {
KeeperException.Code code = KeeperException.Code.get(rc);
switch (code) {
case OK:
promise.done(null);
break;
default:
promise.fail(KeeperException.create(code, p));
}
}, null);
return promise;
});
}
/**
* Returns task that will execute all the given {@link Op operation}s in
* an atomic manner.
*
* @param ops operations to execute.
* @param executor {@code Executor} that will be used to run the operations.
* @return task to execute multiple operations.
*/
public Task> multi(List ops, Executor executor) {
return Task.blocking(() -> _zkClient.multi(ops), executor);
}
/**
* Returns task that will wait for the given {@link KeeperState} to fulfill.
* The task will be failed if the underlying zookeeper session expires.
*
* @param state keeper state to wait for.
* @return task that waits for a certain keeper state.
*/
public Task waitFor(KeeperState state) {
return Task.async("waitFor " + state.toString(), () -> {
SettablePromise promise = Promises.settable();
synchronized (_mutex) {
if (_currentState == state) {
return Promises.VOID;
} else {
_listener.subscribe(state, (p) -> {
if (p.isFailed()) {
promise.fail(p.getError());
} else {
promise.done(null);
}
});
}
}
return promise;
});
}
public Task waitFor(KeeperState state, long deadline) {
return waitFor(state).withTimeout(deadline - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
public Task ensurePathExists(String path) {
return exists(path).flatMap(stat -> {
if (!stat.isPresent()) {
Task createIfAbsent = create(path, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT)
.recoverWith("recover from NodeExistsException", e -> {
if (e instanceof KeeperException.NodeExistsException) {
return Task.value(path);
} else {
return Task.failure(e);
}
});
String parent = path.substring(0, path.lastIndexOf('/'));
if (parent.isEmpty()) { // parent is root
return createIfAbsent;
} else {
return ensurePathExists(parent).flatMap(unused -> createIfAbsent);
}
} else {
return Task.value(path);
}
});
}
public void deleteNode(String node) {
_reaper.submit(() -> delete(node, -1));
}
public void deleteNodeHasUUID(String path, String uuid) {
_reaper.submit(() -> getChildren(path).map(children -> ZKUtil.findNodeWithUUID(children, uuid)).flatMap(node -> {
if (node != null) {
return delete(node, -1);
} else {
return Task.value(null);
}
}));
}
private class DefaultWatcher implements Watcher {
@Override
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getType() == Event.EventType.None) {
Event.KeeperState state = watchedEvent.getState();
switch (state) {
case Disconnected:
case SyncConnected:
case AuthFailed:
case ConnectedReadOnly:
case SaslAuthenticated:
synchronized (_mutex) {
_currentState = state;
_listener.done(state);
}
break;
case Expired:
synchronized (_mutex) {
_currentState = state;
for (KeeperState ks : KeeperState.values()) {
if (ks == KeeperState.Expired) {
_listener.done(KeeperState.Expired);
} else {
_listener.fail(ks, KeeperException.create(KeeperException.Code.SESSIONEXPIRED));
}
}
}
break;
default:
LOG.warn("Received unknown state {} for session 0x{}", state, Long.toHexString(_zkClient.getSessionId()));
}
}
}
}
/**
* A collection of listeners listen to Zookeeper {@link KeeperState state}.
*/
private static class StateListener {
private final Map> _listeners;
public StateListener() {
_listeners = new EnumMap<>(KeeperState.class);
for (KeeperState state : KeeperState.values()) {
_listeners.put(state, Promises.settable());
}
}
public void done(KeeperState state) {
SettablePromise promise = _listeners.put(state, Promises.settable());
promise.done(null);
}
public void fail(KeeperState state, Exception e) {
SettablePromise promise = _listeners.put(state, Promises.settable());
promise.fail(e);
}
public void subscribe(KeeperState state, PromiseListener listener) {
_listeners.get(state).addListener(listener);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy