You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@curator.apache.org by ra...@apache.org on 2017/04/25 14:52:13 UTC
[05/16] curator git commit: initial test and doc updates
initial test and doc updates
Project: http://git-wip-us.apache.org/repos/asf/curator/repo
Commit: http://git-wip-us.apache.org/repos/asf/curator/commit/994778c3
Tree: http://git-wip-us.apache.org/repos/asf/curator/tree/994778c3
Diff: http://git-wip-us.apache.org/repos/asf/curator/diff/994778c3
Branch: refs/heads/CURATOR-3.0
Commit: 994778c31242942c43da741c5124d8c0f2320ea0
Parents: d029966
Author: randgalt <ra...@apache.org>
Authored: Fri Dec 9 14:39:35 2016 +0100
Committer: randgalt <ra...@apache.org>
Committed: Fri Dec 9 14:39:35 2016 +0100
----------------------------------------------------------------------
.../org/apache/curator/utils/ThreadUtils.java | 4 +-
.../framework/recipes/nodes/PersistentNode.java | 2 +-
.../recipes/nodes/PersistentTtlNode.java | 190 +++++++++++++++++++
.../src/site/confluence/index.confluence | 3 +-
.../persistent-ephemeral-node.confluence | 2 +-
.../confluence/persistent-ttl-node.confluence | 39 ++++
.../recipes/nodes/TestPersistentTtlNode.java | 58 ++++++
7 files changed, 294 insertions(+), 4 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/curator/blob/994778c3/curator-client/src/main/java/org/apache/curator/utils/ThreadUtils.java
----------------------------------------------------------------------
diff --git a/curator-client/src/main/java/org/apache/curator/utils/ThreadUtils.java b/curator-client/src/main/java/org/apache/curator/utils/ThreadUtils.java
index 74b4e40..bc93604 100644
--- a/curator-client/src/main/java/org/apache/curator/utils/ThreadUtils.java
+++ b/curator-client/src/main/java/org/apache/curator/utils/ThreadUtils.java
@@ -31,12 +31,14 @@ public class ThreadUtils
{
private static final Logger log = LoggerFactory.getLogger(ThreadUtils.class);
- public static void checkInterrupted(Throwable e)
+ public static boolean checkInterrupted(Throwable e)
{
if ( e instanceof InterruptedException )
{
Thread.currentThread().interrupt();
+ return true;
}
+ return false;
}
public static ExecutorService newSingleThreadExecutor(String processName)
http://git-wip-us.apache.org/repos/asf/curator/blob/994778c3/curator-recipes/src/main/java/org/apache/curator/framework/recipes/nodes/PersistentNode.java
----------------------------------------------------------------------
diff --git a/curator-recipes/src/main/java/org/apache/curator/framework/recipes/nodes/PersistentNode.java b/curator-recipes/src/main/java/org/apache/curator/framework/recipes/nodes/PersistentNode.java
index eaa91b7..e956266 100644
--- a/curator-recipes/src/main/java/org/apache/curator/framework/recipes/nodes/PersistentNode.java
+++ b/curator-recipes/src/main/java/org/apache/curator/framework/recipes/nodes/PersistentNode.java
@@ -386,7 +386,7 @@ public class PersistentNode implements Closeable
return this.data.get();
}
- private void deleteNode() throws Exception
+ protected void deleteNode() throws Exception
{
String localNodePath = nodePath.getAndSet(null);
if ( localNodePath != null )
http://git-wip-us.apache.org/repos/asf/curator/blob/994778c3/curator-recipes/src/main/java/org/apache/curator/framework/recipes/nodes/PersistentTtlNode.java
----------------------------------------------------------------------
diff --git a/curator-recipes/src/main/java/org/apache/curator/framework/recipes/nodes/PersistentTtlNode.java b/curator-recipes/src/main/java/org/apache/curator/framework/recipes/nodes/PersistentTtlNode.java
new file mode 100644
index 0000000..3eda9e2
--- /dev/null
+++ b/curator-recipes/src/main/java/org/apache/curator/framework/recipes/nodes/PersistentTtlNode.java
@@ -0,0 +1,190 @@
+package org.apache.curator.framework.recipes.nodes;
+
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.utils.ThreadUtils;
+import org.apache.curator.utils.ZKPaths;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.KeeperException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Objects;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * <p>
+ * Manages a {@link PersistentNode} that uses {@link CreateMode#CONTAINER}. Asynchronously
+ * it creates or updates a child on the persistent node that is marked with a provided TTL.
+ * </p>
+ *
+ * <p>
+ * The effect of this is to have a node that can be watched, etc. The child node serves as
+ * a method of having the parent node deleted if the TTL expires. i.e. if the process
+ * that is running the PersistentTtlNode crashes and the TTL elapses, first the child node
+ * will be deleted due to the TTL expiration and then the parent node will be deleted as it's
+ * a container node with no children.
+ * </p>
+ *
+ * <p>
+ * PersistentTtlNode is useful when you need to create a TTL node but don't want to keep
+ * it alive manually by periodically setting data - PersistentTtlNode does that for you. Further
+ * the keep-alive is done in a way that does not generate watch triggers on the parent node.
+ * </p>
+ */
+public class PersistentTtlNode implements Closeable
+{
+ public static final String DEFAULT_CHILD_NODE_NAME = "touch";
+ public static final int DEFAULT_TOUCH_SCHEDULE_FACTOR = 2;
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+ private final PersistentNode node;
+ private final CuratorFramework client;
+ private final long ttlMs;
+ private final int touchScheduleFactor;
+ private final ScheduledExecutorService executorService;
+ private final AtomicReference<Future<?>> futureRef = new AtomicReference<>();
+ private final String childPath;
+
+ /**
+ * @param client the client
+ * @param path path for the parent ZNode
+ * @param ttlMs max ttl for the node in milliseconds
+ * @param initData data for the node
+ */
+ public PersistentTtlNode(CuratorFramework client, String path, long ttlMs, byte[] initData)
+ {
+ this(client, Executors.newSingleThreadScheduledExecutor(ThreadUtils.newThreadFactory("PersistentTtlNode")), path, ttlMs, initData, DEFAULT_CHILD_NODE_NAME, DEFAULT_TOUCH_SCHEDULE_FACTOR);
+ }
+
+ /**
+ * @param client the client
+ * @param path path for the parent ZNode
+ * @param ttlMs max ttl for the node in milliseconds
+ * @param initData data for the node
+ * @param childNodeName name to use for the child node of the node created at <code>path</code>
+ * @param touchScheduleFactor how ofter to set/create the child node as a factor of the ttlMs. i.e.
+ * the child is touched every <code>(ttlMs / touchScheduleFactor)</code>
+ */
+ public PersistentTtlNode(CuratorFramework client, ScheduledExecutorService executorService, String path, long ttlMs, byte[] initData, String childNodeName, int touchScheduleFactor)
+ {
+ this.client = Objects.requireNonNull(client, "client cannot be null");
+ this.ttlMs = ttlMs;
+ this.touchScheduleFactor = touchScheduleFactor;
+ node = new PersistentNode(client, CreateMode.CONTAINER, false, path, initData)
+ {
+ @Override
+ protected void deleteNode()
+ {
+ // NOP
+ }
+ };
+ this.executorService = Objects.requireNonNull(executorService, "executorService cannot be null");
+ childPath = ZKPaths.makePath(Objects.requireNonNull(path, "path cannot be null"), childNodeName);
+ }
+
+ /**
+ * You must call start() to initiate the persistent ttl node
+ */
+ public void start()
+ {
+ node.start();
+
+ Runnable touchTask = new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ try
+ {
+ client.setData().forPath(childPath);
+ }
+ catch ( KeeperException.NoNodeException e )
+ {
+ client.create().orSetData().withTtl(ttlMs).withMode(CreateMode.PERSISTENT_WITH_TTL).forPath(childPath);
+ }
+ }
+ catch ( KeeperException.NoNodeException ignore )
+ {
+ // ignore
+ }
+ catch ( Exception e )
+ {
+ if ( !ThreadUtils.checkInterrupted(e) )
+ {
+ log.debug("Could not touch child node", e);
+ }
+ }
+ }
+ };
+ Future<?> future = executorService.scheduleAtFixedRate(touchTask, ttlMs / touchScheduleFactor, ttlMs / touchScheduleFactor, TimeUnit.MILLISECONDS);
+ futureRef.set(future);
+ }
+
+ /**
+ * Block until the either initial node creation initiated by {@link #start()} succeeds or
+ * the timeout elapses.
+ *
+ * @param timeout the maximum time to wait
+ * @param unit time unit
+ * @return if the node was created before timeout
+ * @throws InterruptedException if the thread is interrupted
+ */
+ public boolean waitForInitialCreate(long timeout, TimeUnit unit) throws InterruptedException
+ {
+ return node.waitForInitialCreate(timeout, unit);
+ }
+
+ /**
+ * Set data that node should set in ZK also writes the data to the node. NOTE: it
+ * is an error to call this method after {@link #start()} but before the initial create
+ * has completed. Use {@link #waitForInitialCreate(long, TimeUnit)} to ensure initial
+ * creation.
+ *
+ * @param data new data value
+ * @throws Exception errors
+ */
+ public void setData(byte[] data) throws Exception
+ {
+ node.setData(data);
+ }
+
+ /**
+ * Return the current value of our data
+ *
+ * @return our data
+ */
+ public byte[] getData()
+ {
+ return node.getData();
+ }
+
+ /**
+ * Call when you are done with the PersistentTtlNode. Note: the ZNode is <em>not</em> immediately
+ * deleted. However, if no other PersistentTtlNode with the same path is running the node will get deleted
+ * based on the ttl.
+ */
+ @Override
+ public void close()
+ {
+ Future<?> future = futureRef.getAndSet(null);
+ if ( future != null )
+ {
+ future.cancel(true);
+ }
+ try
+ {
+ node.close();
+ }
+ catch ( IOException e )
+ {
+ throw new RuntimeException(e);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/curator/blob/994778c3/curator-recipes/src/site/confluence/index.confluence
----------------------------------------------------------------------
diff --git a/curator-recipes/src/site/confluence/index.confluence b/curator-recipes/src/site/confluence/index.confluence
index 01dfc7e..4cf2cde 100644
--- a/curator-recipes/src/site/confluence/index.confluence
+++ b/curator-recipes/src/site/confluence/index.confluence
@@ -29,7 +29,8 @@ regarding "Curator Recipes Own Their ZNode/Paths".
|[[Tree Cache|tree-cache.html]] \- A utility that attempts to keep all data from all children of a ZK path locally cached. This class will watch the ZK path, respond to update/create/delete events, pull down the data, etc. You can register a listener that will get notified when changes occur.|
||Nodes||
-|[[Persistent Ephemeral Node|persistent-ephemeral-node.html]] \- An ephemeral node that attempts to stay present in ZooKeeper, even through connection and session interruptions.|
+|[[Persistent Node|persistent-ephemeral-node.html]] \- A node that attempts to stay present in ZooKeeper, even through connection and session interruptions.|
+|[[Persistent TTL Node|persistent-ttl-node.html]] \- Useful when you need to create a TTL node but don't want to keep it alive manually by periodically setting data.|
|[Group Member|group-member.html]] \- Group membership management. Adds this instance into a group and keeps a cache of members in the group.|
||Queues||
http://git-wip-us.apache.org/repos/asf/curator/blob/994778c3/curator-recipes/src/site/confluence/persistent-ephemeral-node.confluence
----------------------------------------------------------------------
diff --git a/curator-recipes/src/site/confluence/persistent-ephemeral-node.confluence b/curator-recipes/src/site/confluence/persistent-ephemeral-node.confluence
index aeb9e10..5baca09 100644
--- a/curator-recipes/src/site/confluence/persistent-ephemeral-node.confluence
+++ b/curator-recipes/src/site/confluence/persistent-ephemeral-node.confluence
@@ -1,4 +1,4 @@
-h1. Persistent Ephemeral Node
+h1. Persistent Node
h2. Description
A persistent node is a node that attempts to stay present in ZooKeeper, even through connection and session interruptions.
http://git-wip-us.apache.org/repos/asf/curator/blob/994778c3/curator-recipes/src/site/confluence/persistent-ttl-node.confluence
----------------------------------------------------------------------
diff --git a/curator-recipes/src/site/confluence/persistent-ttl-node.confluence b/curator-recipes/src/site/confluence/persistent-ttl-node.confluence
new file mode 100644
index 0000000..acd2d33
--- /dev/null
+++ b/curator-recipes/src/site/confluence/persistent-ttl-node.confluence
@@ -0,0 +1,39 @@
+h1. Persistent TTL Node
+
+h2. Description
+PersistentTtlNode is useful when you need to create a TTL node but don't want to keep it alive manually by periodically setting data \-
+PersistentTtlNode does that for you. Further the keep\-alive is done in a way that does not generate watch triggers on the parent node.
+
+h2. Participating Classes
+* PersistentNode
+* PersistentTtlNode
+
+h2. Usage
+h3. Creating a PersistentTtlNode
+{code}
+public PersistentTtlNode(CuratorFramework client,
+ String path,
+ long ttlMs,
+ byte[] initData)
+Parameters:
+client - client instance
+path path for the parent ZNode
+ttlMs max ttl for the node in milliseconds
+initData - initData for the node
+{code}
+
+h3. General Usage
+PersistentTtlNode must be started:
+{code}
+node.start();
+{code}
+
+When you are through with the PersistentTtlNode instance, you should call close:
+{code}
+node.close();
+{code}
+
+NOTE: this will NOT delete the node immediately. The node will get deleted based on the ttl.
+
+h2. Error Handling
+PersistentTtlNode instances internally handle all error states recreating the node as necessary.
http://git-wip-us.apache.org/repos/asf/curator/blob/994778c3/curator-recipes/src/test/java/org/apache/curator/framework/recipes/nodes/TestPersistentTtlNode.java
----------------------------------------------------------------------
diff --git a/curator-recipes/src/test/java/org/apache/curator/framework/recipes/nodes/TestPersistentTtlNode.java b/curator-recipes/src/test/java/org/apache/curator/framework/recipes/nodes/TestPersistentTtlNode.java
new file mode 100644
index 0000000..9ae8df9
--- /dev/null
+++ b/curator-recipes/src/test/java/org/apache/curator/framework/recipes/nodes/TestPersistentTtlNode.java
@@ -0,0 +1,58 @@
+package org.apache.curator.framework.recipes.nodes;
+
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.framework.CuratorFrameworkFactory;
+import org.apache.curator.retry.RetryOneTime;
+import org.apache.curator.test.BaseClassForTests;
+import org.apache.curator.test.Timing;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import java.util.concurrent.TimeUnit;
+
+public class TestPersistentTtlNode extends BaseClassForTests
+{
+ private final Timing timing = new Timing();
+
+ @BeforeMethod
+ @Override
+ public void setup() throws Exception
+ {
+ System.setProperty("znode.container.checkIntervalMs", "1");
+ super.setup();
+ }
+
+ @AfterMethod
+ @Override
+ public void teardown() throws Exception
+ {
+ System.clearProperty("znode.container.checkIntervalMs");
+ super.teardown();
+ }
+
+ @Test
+ public void testBasic() throws Exception
+ {
+ try (CuratorFramework client = CuratorFrameworkFactory.newClient(server.getConnectString(), new RetryOneTime(1)))
+ {
+ client.start();
+
+ try (PersistentTtlNode node = new PersistentTtlNode(client, "/test", 10, new byte[0]))
+ {
+ node.start();
+ node.waitForInitialCreate(timing.session(), TimeUnit.MILLISECONDS);
+
+ for ( int i = 0; i < 10; ++i )
+ {
+ Thread.sleep(10);
+ Assert.assertNotNull(client.checkExists().forPath("/test"));
+ }
+ }
+
+ timing.sleepABit();
+
+ Assert.assertNull(client.checkExists().forPath("/test"));
+ }
+ }
+}