This is an automated email from the ASF dual-hosted git repository. eolivelli pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/curator.git
The following commit(s) were added to refs/heads/master by this push: new 4a11aae CURATOR-590: Add option to disable parent creation for PersistentNode 4a11aae is described below commit 4a11aaef8b190dc220d35b7a91df294bfa06250e Author: Paul Boutes <paul.bou...@elastic.co> AuthorDate: Tue Mar 9 08:11:49 2021 +0100 CURATOR-590: Add option to disable parent creation for PersistentNode Adds a `useParentCreation` boolean flag to control the parent creation. If this flag is set to `false`, the `PersistentNode` won't create the underlying znodes with the `createParentContainersIfNeeded()`, meaning that the parent znodes will have to exist beforehand in order for the `PersistentNode` to succeed its creation. The `useParentCreation` flag is set to `true` by default. https://issues.apache.org/jira/browse/CURATOR-590 Author: Paul Boutes <paul.bou...@elastic.co> Author: Paul Boutes <paul.bou...@gmail.com> Reviewers: Enrico Olivelli <eolive...@apache.org>, Cameron McKenzie <mckenzie....@gmail.com>, Zili Chen <wander4...@gmail.com>, Jordan Zimmerman <jor...@jordanzimmerman.com> Closes #380 from pboutes/CURATOR-590 --- .../framework/recipes/nodes/PersistentNode.java | 49 +++++++++++++++--- .../recipes/nodes/TestPersistentNode.java | 59 +++++++++++++++++++++- 2 files changed, 99 insertions(+), 9 deletions(-) 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 f7b4653..72db454 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 @@ -72,9 +72,11 @@ public class PersistentNode implements Closeable private final long ttl; private final AtomicReference<byte[]> data = new AtomicReference<byte[]>(); private final AtomicReference<State> state = new AtomicReference<State>(State.LATENT); - private final AtomicBoolean authFailure = new AtomicBoolean(false); + private volatile boolean authFailure; + private volatile boolean parentCreationFailure; private final BackgroundCallback backgroundCallback; private final boolean useProtection; + private final boolean useParentCreation; private final AtomicReference<CreateModable<ACLBackgroundPathAndBytesable<String>>> createMethod = new AtomicReference<CreateModable<ACLBackgroundPathAndBytesable<String>>>(null); private final StandardListenerManager<PersistentNodeListener> listeners = StandardListenerManager.standard(); private final CuratorWatcher watcher = new CuratorWatcher() @@ -140,7 +142,7 @@ public class PersistentNode implements Closeable else if ( event.getResultCode() == KeeperException.Code.NOAUTH.intValue() ) { log.warn("Client does not have authorisation to write node at path {}", event.getPath()); - authFailure.set(true); + authFailure = true; } } }; @@ -175,7 +177,19 @@ public class PersistentNode implements Closeable */ public PersistentNode(CuratorFramework givenClient, final CreateMode mode, boolean useProtection, final String basePath, byte[] initData) { - this(givenClient, mode, useProtection, basePath, initData, -1); + this(givenClient, mode, useProtection, basePath, initData, -1, true); + } + + /** + * @param givenClient client instance + * @param mode creation mode + * @param useProtection if true, call {@link CreateBuilder#withProtection()} + * @param basePath the base path for the node + * @param initData data for the node + * @param useParentCreation if true, call {@link CreateBuilder#creatingParentContainersIfNeeded()} + */ + public PersistentNode(CuratorFramework givenClient, final CreateMode mode, boolean useProtection, final String basePath, byte[] initData, boolean useParentCreation) { + this(givenClient, mode, useProtection, basePath, initData, -1, useParentCreation); } /** @@ -185,10 +199,12 @@ public class PersistentNode implements Closeable * @param basePath the base path for the node * @param initData data for the node * @param ttl for ttl modes, the ttl to use + * @param useParentCreation if true, call {@link CreateBuilder#creatingParentContainersIfNeeded()} */ - public PersistentNode(CuratorFramework givenClient, final CreateMode mode, boolean useProtection, final String basePath, byte[] initData, long ttl) + public PersistentNode(CuratorFramework givenClient, final CreateMode mode, boolean useProtection, final String basePath, byte[] initData, long ttl, boolean useParentCreation) { this.useProtection = useProtection; + this.useParentCreation = useParentCreation; this.client = Preconditions.checkNotNull(givenClient, "client cannot be null").newWatcherRemoveCuratorFramework(); this.basePath = PathUtils.validatePath(basePath); this.mode = Preconditions.checkNotNull(mode, "mode cannot be null"); @@ -255,12 +271,17 @@ public class PersistentNode implements Closeable else if ( event.getResultCode() == KeeperException.Code.NOAUTH.intValue() ) { log.warn("Client does not have authorisation to create node at path {}", event.getPath()); - authFailure.set(true); + authFailure = true; + return; + } else if ( event.getResultCode() == KeeperException.Code.NONODE.intValue() ) + { + log.warn("Client cannot create parent hierarchy for path {} with useParentCreation set to {}", event.getPath(), useParentCreation); + parentCreationFailure = true; return; } if ( path != null ) { - authFailure.set(false); + authFailure = false; nodePath.set(path); watchNode(); @@ -389,6 +410,7 @@ public class PersistentNode implements Closeable { data = Preconditions.checkNotNull(data, "data cannot be null"); Preconditions.checkState(nodePath.get() != null, "initial create has not been processed. Call waitForInitialCreate() to ensure."); + Preconditions.checkState(!parentCreationFailure, "Failed to create parent nodes."); this.data.set(Arrays.copyOf(data, data.length)); if ( isActive() ) { @@ -462,7 +484,12 @@ public class PersistentNode implements Closeable if ( localCreateMethod == null ) { CreateBuilderMain createBuilder = mode.isTTL() ? client.create().withTtl(ttl) : client.create(); - CreateModable<ACLBackgroundPathAndBytesable<String>> tempCreateMethod = useProtection ? createBuilder.creatingParentContainersIfNeeded().withProtection() : createBuilder.creatingParentContainersIfNeeded(); + CreateModable<ACLBackgroundPathAndBytesable<String>> tempCreateMethod; + if (useParentCreation) { + tempCreateMethod = useProtection ? createBuilder.creatingParentContainersIfNeeded().withProtection() : createBuilder.creatingParentContainersIfNeeded(); + } else { + tempCreateMethod = useProtection ? createBuilder.withProtection() : createBuilder; + } createMethod.compareAndSet(null, tempCreateMethod); localCreateMethod = createMethod.get(); } @@ -543,6 +570,12 @@ public class PersistentNode implements Closeable @VisibleForTesting boolean isAuthFailure() { - return authFailure.get(); + return authFailure; } + + @VisibleForTesting + boolean isParentCreationFailure() { + return parentCreationFailure; + } + } diff --git a/curator-recipes/src/test/java/org/apache/curator/framework/recipes/nodes/TestPersistentNode.java b/curator-recipes/src/test/java/org/apache/curator/framework/recipes/nodes/TestPersistentNode.java index 620c9cc..5ec20da 100644 --- a/curator-recipes/src/test/java/org/apache/curator/framework/recipes/nodes/TestPersistentNode.java +++ b/curator-recipes/src/test/java/org/apache/curator/framework/recipes/nodes/TestPersistentNode.java @@ -20,7 +20,9 @@ package org.apache.curator.framework.recipes.nodes; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -101,6 +103,61 @@ public class TestPersistentNode extends BaseClassForTests } @Test + public void testCreationWithParentCreationOff() throws Exception { + Timing2 timing = new Timing2(); + PersistentNode pen = null; + CuratorFramework client = CuratorFrameworkFactory.newClient(server.getConnectString(), timing.session(), timing.connection(), new RetryOneTime(1)); + + try { + client.start(); + pen = new PersistentNode(client, CreateMode.PERSISTENT, false, "/test/one/two", new byte[0], false); + pen.debugWaitMsForBackgroundBeforeClose.set(timing.forSleepingABit().milliseconds()); + pen.start(); + assertFalse(pen.waitForInitialCreate(timing.milliseconds(), TimeUnit.MILLISECONDS)); + assertTrue(pen.isParentCreationFailure()); + } finally { + CloseableUtils.closeQuietly(pen); + CloseableUtils.closeQuietly(client); + } + + } + + @Test + public void testRecreationWithParentCreationOff() throws Exception { + final byte[] TEST_DATA = "hey".getBytes(); + Timing2 timing = new Timing2(); + PersistentNode pen = null; + CuratorFramework client = CuratorFrameworkFactory.newClient(server.getConnectString(), timing.session(), timing.connection(), new RetryOneTime(1)); + + try { + client.start(); + client.create().creatingParentsIfNeeded().forPath("/test/one"); + pen = new PersistentNode(client, CreateMode.EPHEMERAL, false, "/test/one/two", TEST_DATA, false); + pen.debugWaitMsForBackgroundBeforeClose.set(timing.forSleepingABit().milliseconds()); + pen.start(); + assertTrue(pen.waitForInitialCreate(timing.milliseconds(), TimeUnit.MILLISECONDS)); + assertFalse(pen.isParentCreationFailure()); + client.delete().deletingChildrenIfNeeded().forPath("/test/one"); + timing.sleepABit(); + + // persistent node should not be able to recreate itself as the lazy parent creation is disabled + assertNull(client.checkExists().forPath("/test/one/two")); + assertTrue(pen.isParentCreationFailure()); + PersistentNode finalPen = pen; + assertThrows(IllegalStateException.class, () -> finalPen.setData(new byte[0])); + + // The persistent node data should still be the initial one + assertArrayEquals(TEST_DATA, pen.getData()); + + } finally { + CloseableUtils.closeQuietly(pen); + CloseableUtils.closeQuietly(client); + } + + + } + + @Test public void testQuickClose() throws Exception { Timing timing = new Timing(); @@ -145,7 +202,7 @@ public class TestPersistentNode extends BaseClassForTests CloseableUtils.closeQuietly(client); } } - + @Test public void testEphemeralSequentialWithProtectionReconnection() throws Exception {