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
     {

Reply via email to