You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zookeeper.apache.org by rg...@apache.org on 2015/06/06 23:38:48 UTC

svn commit: r1683960 - in /zookeeper/branches/branch-3.5: ./ src/ src/docs/src/documentation/content/xdocs/ src/java/main/org/apache/zookeeper/ src/java/main/org/apache/zookeeper/cli/ src/java/main/org/apache/zookeeper/server/ src/java/main/org/apache/...

Author: rgs
Date: Sat Jun  6 21:38:47 2015
New Revision: 1683960

URL: http://svn.apache.org/r1683960
Log:
ZOOKEEPER-2163: Introduce new ZNode type: container
(Jordan Zimmerman via rgs)

Added:
    zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/ContainerManager.java
    zookeeper/branches/branch-3.5/src/java/test/org/apache/zookeeper/server/CreateContainerTest.java
Modified:
    zookeeper/branches/branch-3.5/CHANGES.txt
    zookeeper/branches/branch-3.5/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml
    zookeeper/branches/branch-3.5/src/docs/src/documentation/content/xdocs/zookeeperProgrammers.xml
    zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/CreateMode.java
    zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/MultiTransactionRecord.java
    zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/Op.java
    zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/ZooDefs.java
    zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/ZooKeeper.java
    zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/cli/CreateCommand.java
    zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/DataNode.java
    zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/DataTree.java
    zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java
    zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/PrepRequestProcessor.java
    zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/Request.java
    zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/TraceFormatter.java
    zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/ZooKeeperServerMain.java
    zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/CommitProcessor.java
    zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/FollowerRequestProcessor.java
    zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/LeaderZooKeeperServer.java
    zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/ObserverRequestProcessor.java
    zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/ReadOnlyRequestProcessor.java
    zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/util/SerializeUtils.java
    zookeeper/branches/branch-3.5/src/java/test/org/apache/zookeeper/test/CreateModeTest.java
    zookeeper/branches/branch-3.5/src/zookeeper.jute

Modified: zookeeper/branches/branch-3.5/CHANGES.txt
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/CHANGES.txt?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/CHANGES.txt (original)
+++ zookeeper/branches/branch-3.5/CHANGES.txt Sat Jun  6 21:38:47 2015
@@ -10,6 +10,8 @@ NEW FEATURES:
   ZOOKEEPER-2123 Provide implementation of X509 AuthenticationProvider
   (Ian Dimayuga via rakeshr)
 
+  ZOOKEEPER-2163: Introduce new ZNode type: container (Jordan Zimmerman via rgs)
+
 BUGFIXES:
   ZOOKEEPER-1784 wrong check for COMMITANDACTIVATE in observer code, Learner.java (rgs via shralex).
 

Modified: zookeeper/branches/branch-3.5/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml (original)
+++ zookeeper/branches/branch-3.5/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml Sat Jun  6 21:38:47 2015
@@ -1383,6 +1383,30 @@ server.3=zoo3:2888:3888</programlisting>
             </listitem>
           </varlistentry>
 
+          <varlistentry>
+            <term>znode.container.checkIntervalMs</term>
+
+            <listitem>
+              <para>(Java system property only)</para>
+
+              <para><emphasis role="bold">New in 3.6.0:</emphasis> The
+                time interval in milliseconds for each check of candidate container
+                nodes. Default is "60000".</para>
+            </listitem>
+          </varlistentry>
+
+          <varlistentry>
+            <term>znode.container.maxPerMinute</term>
+
+            <listitem>
+              <para>(Java system property only)</para>
+
+              <para><emphasis role="bold">New in 3.6.0:</emphasis> The
+                maximum number of container nodes that can be deleted per
+                minute. This prevents herding during container deletion.
+                Default is "10000".</para>
+            </listitem>
+          </varlistentry>
         </variablelist>
       </section>
 

Modified: zookeeper/branches/branch-3.5/src/docs/src/documentation/content/xdocs/zookeeperProgrammers.xml
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/docs/src/documentation/content/xdocs/zookeeperProgrammers.xml?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/docs/src/documentation/content/xdocs/zookeeperProgrammers.xml (original)
+++ zookeeper/branches/branch-3.5/src/docs/src/documentation/content/xdocs/zookeeperProgrammers.xml Sat Jun  6 21:38:47 2015
@@ -243,6 +243,23 @@
         overflow when incremented beyond 2147483647 (resulting in a
         name "&lt;path&gt;-2147483647").</para>
       </section>
+
+      <section>
+        <title>Container Nodes</title>
+
+        <para><emphasis role="bold">Added in 3.6.0</emphasis></para>
+
+        <para>ZooKeeper has the notion of container nodes. Container nodes are
+          special purpose nodes useful for recipes such as leader, lock, etc.
+          When the last child of a container is deleted, the container becomes
+          a candidate to be deleted by the server at some point in the future.</para>
+
+        <para>Given this property, you should be prepared to get
+          KeeperException.NoNodeException when creating children inside of
+          container nodes. i.e. when creating child nodes inside of container nodes
+          always check for KeeperException.NoNodeException and recreate the container
+          node when it occurs.</para>
+      </section>
     </section>
 
     <section id="sc_timeInZk">

Modified: zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/CreateMode.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/CreateMode.java?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/CreateMode.java (original)
+++ zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/CreateMode.java Sat Jun  6 21:38:47 2015
@@ -19,7 +19,6 @@ package org.apache.zookeeper;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.apache.zookeeper.KeeperException;
 
 /***
  *  CreateMode value determines how the znode is created on ZooKeeper.
@@ -29,32 +28,45 @@ public enum CreateMode {
     /**
      * The znode will not be automatically deleted upon client's disconnect.
      */
-    PERSISTENT (0, false, false),
+    PERSISTENT (0, false, false, false),
     /**
     * The znode will not be automatically deleted upon client's disconnect,
     * and its name will be appended with a monotonically increasing number.
     */
-    PERSISTENT_SEQUENTIAL (2, false, true),
+    PERSISTENT_SEQUENTIAL (2, false, true, false),
     /**
      * The znode will be deleted upon the client's disconnect.
      */
-    EPHEMERAL (1, true, false),
+    EPHEMERAL (1, true, false, false),
     /**
      * The znode will be deleted upon the client's disconnect, and its name
      * will be appended with a monotonically increasing number.
      */
-    EPHEMERAL_SEQUENTIAL (3, true, true);
+    EPHEMERAL_SEQUENTIAL (3, true, true, false),
+    /**
+     * The znode will be a container node. Container
+     * nodes are special purpose nodes useful for recipes such as leader, lock,
+     * etc. When the last child of a container is deleted, the container becomes
+     * a candidate to be deleted by the server at some point in the future.
+     * Given this property, you should be prepared to get
+     * {@link org.apache.zookeeper.KeeperException.NoNodeException}
+     * when creating children inside of this container node.
+     */
+    CONTAINER (4, false, false, true);
 
     private static final Logger LOG = LoggerFactory.getLogger(CreateMode.class);
 
     private boolean ephemeral;
     private boolean sequential;
+    private final boolean isContainer;
     private int flag;
 
-    CreateMode(int flag, boolean ephemeral, boolean sequential) {
+    CreateMode(int flag, boolean ephemeral, boolean sequential,
+               boolean isContainer) {
         this.flag = flag;
         this.ephemeral = ephemeral;
         this.sequential = sequential;
+        this.isContainer = isContainer;
     }
 
     public boolean isEphemeral() { 
@@ -65,6 +77,10 @@ public enum CreateMode {
         return sequential;
     }
 
+    public boolean isContainer() {
+        return isContainer;
+    }
+
     public int toFlag() {
         return flag;
     }
@@ -82,6 +98,8 @@ public enum CreateMode {
 
         case 3: return CreateMode.EPHEMERAL_SEQUENTIAL ;
 
+        case 4: return CreateMode.CONTAINER;
+
         default:
             String errMsg = "Received an invalid flag value: " + flag
                     + " to convert to a CreateMode";
@@ -89,4 +107,29 @@ public enum CreateMode {
             throw new KeeperException.BadArgumentsException(errMsg);
         }
     }
+
+    /**
+     * Map an integer value to a CreateMode value
+     */
+    static public CreateMode fromFlag(int flag, CreateMode defaultMode) {
+        switch(flag) {
+            case 0:
+                return CreateMode.PERSISTENT;
+
+            case 1:
+                return CreateMode.EPHEMERAL;
+
+            case 2:
+                return CreateMode.PERSISTENT_SEQUENTIAL;
+
+            case 3:
+                return CreateMode.EPHEMERAL_SEQUENTIAL;
+
+            case 4:
+                return CreateMode.CONTAINER;
+
+            default:
+                return defaultMode;
+        }
+    }
 }

Modified: zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/MultiTransactionRecord.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/MultiTransactionRecord.java?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/MultiTransactionRecord.java (original)
+++ zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/MultiTransactionRecord.java Sat Jun  6 21:38:47 2015
@@ -68,6 +68,7 @@ public class MultiTransactionRecord impl
             switch (op.getType()) {
                 case ZooDefs.OpCode.create:
                 case ZooDefs.OpCode.create2:
+                case ZooDefs.OpCode.createContainer:
                 case ZooDefs.OpCode.delete:
                 case ZooDefs.OpCode.setData:
                 case ZooDefs.OpCode.check:
@@ -89,8 +90,9 @@ public class MultiTransactionRecord impl
 
         while (!h.getDone()) {
             switch (h.getType()) {
-               case ZooDefs.OpCode.create:
-               case ZooDefs.OpCode.create2:
+                case ZooDefs.OpCode.create:
+                case ZooDefs.OpCode.create2:
+                case ZooDefs.OpCode.createContainer:
                     CreateRequest cr = new CreateRequest();
                     cr.deserialize(archive, tag);
                     add(Op.create(cr.getPath(), cr.getData(), cr.getAcl(), cr.getFlags()));

Modified: zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/Op.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/Op.java?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/Op.java (original)
+++ zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/Op.java Sat Jun  6 21:38:47 2015
@@ -183,14 +183,18 @@ public abstract class Op {
         private int flags;
 
         private Create(String path, byte[] data, List<ACL> acl, int flags) {
-            super(ZooDefs.OpCode.create, path);
+            super(getOpcode(CreateMode.fromFlag(flags, CreateMode.PERSISTENT)), path);
             this.data = data;
             this.acl = acl;
             this.flags = flags;
         }
 
+        private static int getOpcode(CreateMode createMode) {
+            return createMode.isContainer() ? ZooDefs.OpCode.createContainer : ZooDefs.OpCode.create;
+        }
+
         private Create(String path, byte[] data, List<ACL> acl, CreateMode createMode) {
-            super(ZooDefs.OpCode.create, path);
+            super(getOpcode(createMode), path);
             this.data = data;
             this.acl = acl;
             this.flags = createMode.toFlag();

Modified: zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/ZooDefs.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/ZooDefs.java?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/ZooDefs.java (original)
+++ zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/ZooDefs.java Sat Jun  6 21:38:47 2015
@@ -65,6 +65,10 @@ public class ZooDefs {
 
         public final int removeWatches = 18;
 
+        public final int createContainer = 19;
+
+        public final int deleteContainer = 20;
+
         public final int auth = 100;
 
         public final int setWatches = 101;

Modified: zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/ZooKeeper.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/ZooKeeper.java?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/ZooKeeper.java (original)
+++ zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/ZooKeeper.java Sat Jun  6 21:38:47 2015
@@ -1194,7 +1194,7 @@ public class ZooKeeper {
         final String serverPath = prependChroot(clientPath);
 
         RequestHeader h = new RequestHeader();
-        h.setType(ZooDefs.OpCode.create);
+        h.setType(createMode.isContainer() ? ZooDefs.OpCode.createContainer : ZooDefs.OpCode.create);
         CreateRequest request = new CreateRequest();
         CreateResponse response = new CreateResponse();
         request.setData(data);
@@ -1282,7 +1282,7 @@ public class ZooKeeper {
         final String serverPath = prependChroot(clientPath);
 
         RequestHeader h = new RequestHeader();
-        h.setType(ZooDefs.OpCode.create2);
+        h.setType(createMode.isContainer() ? ZooDefs.OpCode.createContainer : ZooDefs.OpCode.create2);
         CreateRequest request = new CreateRequest();
         Create2Response response = new Create2Response();
         request.setData(data);
@@ -1321,7 +1321,7 @@ public class ZooKeeper {
         final String serverPath = prependChroot(clientPath);
 
         RequestHeader h = new RequestHeader();
-        h.setType(ZooDefs.OpCode.create);
+        h.setType(createMode.isContainer() ? ZooDefs.OpCode.createContainer : ZooDefs.OpCode.create);
         CreateRequest request = new CreateRequest();
         CreateResponse response = new CreateResponse();
         ReplyHeader r = new ReplyHeader();
@@ -1347,7 +1347,7 @@ public class ZooKeeper {
         final String serverPath = prependChroot(clientPath);
 
         RequestHeader h = new RequestHeader();
-        h.setType(ZooDefs.OpCode.create2);
+        h.setType(createMode.isContainer() ? ZooDefs.OpCode.createContainer : ZooDefs.OpCode.create2);
         CreateRequest request = new CreateRequest();
         Create2Response response = new Create2Response();
         ReplyHeader r = new ReplyHeader();

Modified: zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/cli/CreateCommand.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/cli/CreateCommand.java?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/cli/CreateCommand.java (original)
+++ zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/cli/CreateCommand.java Sat Jun  6 21:38:47 2015
@@ -36,10 +36,11 @@ public class CreateCommand extends CliCo
     {
         options.addOption(new Option("e", false, "ephemeral"));
         options.addOption(new Option("s", false, "sequential"));
+        options.addOption(new Option("c", false, "container"));
     }
 
     public CreateCommand() {
-        super("create", "[-s] [-e] path [data] [acl]");
+        super("create", "[-s] [-e] [-c] path [data] [acl]");
     }
 
 
@@ -58,12 +59,22 @@ public class CreateCommand extends CliCo
     @Override
     public boolean exec() throws KeeperException, InterruptedException {
         CreateMode flags = CreateMode.PERSISTENT;
-        if(cl.hasOption("e") && cl.hasOption("s")) {
+        boolean hasE = cl.hasOption("e");
+        boolean hasS = cl.hasOption("s");
+        boolean hasC = cl.hasOption("c");
+        if (hasC && (hasE || hasS)) {
+            err.println("-c cannot be combined with -s or -e. Containers cannot be ephemeral or sequential.");
+            return false;
+        }
+
+        if(hasE && hasS) {
             flags = CreateMode.EPHEMERAL_SEQUENTIAL;
-        } else if (cl.hasOption("e")) {
+        } else if (hasE) {
             flags = CreateMode.EPHEMERAL;
-        } else if (cl.hasOption("s")) {
+        } else if (hasS) {
             flags = CreateMode.PERSISTENT_SEQUENTIAL;
+        } else if (hasC) {
+            flags = CreateMode.CONTAINER;
         }
         String path = args[1];
         byte[] data = null;

Added: zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/ContainerManager.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/ContainerManager.java?rev=1683960&view=auto
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/ContainerManager.java (added)
+++ zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/ContainerManager.java Sat Jun  6 21:38:47 2015
@@ -0,0 +1,157 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 org.apache.zookeeper.server;
+
+import org.apache.zookeeper.ZooDefs;
+import org.apache.zookeeper.common.Time;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Manages cleanup of container ZNodes. This class is meant to only
+ * be run from the leader. There's no harm in running from followers/observers
+ * but that will be extra work that's not needed. Once started, it periodically
+ * checks container nodes that have a cversion > 0 and have no children. A
+ * delete is attempted on the node. The result of the delete is unimportant.
+ * If the proposal fails or the container node is not empty there's no harm.
+ */
+public class ContainerManager {
+    private static final Logger LOG = LoggerFactory.getLogger(ContainerManager.class);
+    private final ZKDatabase zkDb;
+    private final RequestProcessor requestProcessor;
+    private final int checkIntervalMs;
+    private final int maxPerMinute;
+    private final Timer timer;
+    private final AtomicReference<TimerTask> task = new AtomicReference<TimerTask>(null);
+
+    /**
+     * @param zkDb the ZK database
+     * @param requestProcessor request processer - used to inject delete
+     *                         container requests
+     * @param checkIntervalMs how often to check containers in milliseconds
+     * @param maxPerMinute the max containers to delete per second - avoids
+     *                     herding of container deletions
+     */
+    public ContainerManager(ZKDatabase zkDb, RequestProcessor requestProcessor,
+                            int checkIntervalMs, int maxPerMinute) {
+        this.zkDb = zkDb;
+        this.requestProcessor = requestProcessor;
+        this.checkIntervalMs = checkIntervalMs;
+        this.maxPerMinute = maxPerMinute;
+        timer = new Timer("ContainerManagerTask", true);
+
+        LOG.info(String.format("Using checkIntervalMs=%d maxPerMinute=%d",
+                checkIntervalMs, maxPerMinute));
+    }
+
+    /**
+     * start/restart the timer the runs the check. Can safely be called
+     * multiple times.
+     */
+    public void start() {
+        if (task.get() == null) {
+            TimerTask timerTask = new TimerTask() {
+                @Override
+                public void run() {
+                    try {
+                        checkContainers();
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                        LOG.info("interrupted");
+                        cancel();
+                    } catch ( Throwable e ) {
+                        LOG.error("Error checking containers", e);
+                    }
+                }
+            };
+            if (task.compareAndSet(null, timerTask)) {
+                timer.scheduleAtFixedRate(timerTask, checkIntervalMs,
+                        checkIntervalMs);
+            }
+        }
+    }
+
+    /**
+     * stop the timer if necessary. Can safely be called multiple times.
+     */
+    public void stop() {
+        TimerTask timerTask = task.getAndSet(null);
+        if (timerTask != null) {
+            timerTask.cancel();
+        }
+    }
+
+    /**
+     * Manually check the containers. Not normally used directly
+     */
+    public void checkContainers()
+            throws InterruptedException {
+        long minIntervalMs = getMinIntervalMs();
+        for (String containerPath : getCandidates()) {
+            long startMs = Time.currentElapsedTime();
+
+            ByteBuffer path = ByteBuffer.wrap(containerPath.getBytes());
+            Request request = new Request(null, 0, 0,
+                    ZooDefs.OpCode.deleteContainer, path, null);
+            try {
+                LOG.info("Attempting to delete candidate container: %s",
+                        containerPath);
+                requestProcessor.processRequest(request);
+            } catch (Exception e) {
+                LOG.error(String.format("Could not delete container: %s" ,
+                        containerPath), e);
+            }
+
+            long elapsedMs = Time.currentElapsedTime() - startMs;
+            long waitMs = minIntervalMs - elapsedMs;
+            if (waitMs > 0) {
+                Thread.sleep(waitMs);
+            }
+        }
+    }
+
+    // VisibleForTesting
+    protected long getMinIntervalMs() {
+        return TimeUnit.MINUTES.toMillis(1) / maxPerMinute;
+    }
+
+    // VisibleForTesting
+    protected Collection<String> getCandidates() {
+        Set<String> candidates = new HashSet<String>();
+        for (String containerPath : zkDb.getDataTree().getContainers()) {
+            DataNode node = zkDb.getDataTree().getNode(containerPath);
+            /*
+                cversion > 0: keep newly created containers from being deleted
+                before any children have been added. If you were to create the
+                container just before a container cleaning period the container
+                would be immediately be deleted.
+             */
+            if ((node != null) && (node.stat.getCversion() > 0) &&
+                    (node.getChildren().size() == 0)) {
+                candidates.add(containerPath);
+            }
+        }
+        return candidates;
+    }
+}

Modified: zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/DataNode.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/DataNode.java?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/DataNode.java (original)
+++ zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/DataNode.java Sat Jun  6 21:38:47 2015
@@ -145,7 +145,7 @@ public class DataNode implements Record
         to.setMzxid(stat.getMzxid());
         to.setPzxid(stat.getPzxid());
         to.setVersion(stat.getVersion());
-        to.setEphemeralOwner(stat.getEphemeralOwner());
+        to.setEphemeralOwner(getClientEphemeralOwner(stat));
         to.setDataLength(data == null ? 0 : data.length);
         int numChildren = 0;
         if (this.children != null) {
@@ -158,6 +158,11 @@ public class DataNode implements Record
         to.setNumChildren(numChildren);
     }
 
+    private static long getClientEphemeralOwner(StatPersisted stat) {
+        return (stat.getEphemeralOwner() == DataTree.CONTAINER_EPHEMERAL_OWNER)
+                ? 0 : stat.getEphemeralOwner();
+    }
+
     synchronized public void deserialize(InputArchive archive, String tag)
             throws IOException {
         archive.startRecord("node");

Modified: zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/DataTree.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/DataTree.java?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/DataTree.java (original)
+++ zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/DataTree.java Sat Jun  6 21:38:47 2015
@@ -18,19 +18,6 @@
 
 package org.apache.zookeeper.server;
 
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
 import org.apache.jute.Index;
 import org.apache.jute.InputArchive;
 import org.apache.jute.OutputArchive;
@@ -43,11 +30,11 @@ import org.apache.zookeeper.Quotas;
 import org.apache.zookeeper.StatsTrack;
 import org.apache.zookeeper.WatchedEvent;
 import org.apache.zookeeper.Watcher;
-import org.apache.zookeeper.Watcher.WatcherType;
-import org.apache.zookeeper.ZooDefs;
 import org.apache.zookeeper.Watcher.Event;
 import org.apache.zookeeper.Watcher.Event.EventType;
 import org.apache.zookeeper.Watcher.Event.KeeperState;
+import org.apache.zookeeper.Watcher.WatcherType;
+import org.apache.zookeeper.ZooDefs;
 import org.apache.zookeeper.ZooDefs.Ids;
 import org.apache.zookeeper.ZooDefs.OpCode;
 import org.apache.zookeeper.common.PathTrie;
@@ -55,6 +42,7 @@ import org.apache.zookeeper.data.ACL;
 import org.apache.zookeeper.data.Stat;
 import org.apache.zookeeper.data.StatPersisted;
 import org.apache.zookeeper.txn.CheckVersionTxn;
+import org.apache.zookeeper.txn.CreateContainerTxn;
 import org.apache.zookeeper.txn.CreateTxn;
 import org.apache.zookeeper.txn.DeleteTxn;
 import org.apache.zookeeper.txn.ErrorTxn;
@@ -66,6 +54,20 @@ import org.apache.zookeeper.txn.TxnHeade
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
 /**
  * This class maintains the tree data structure. It doesn't have any networking
  * or client connection code in it so that it can be tested in a stand alone
@@ -78,6 +80,8 @@ import org.slf4j.LoggerFactory;
 public class DataTree {
     private static final Logger LOG = LoggerFactory.getLogger(DataTree.class);
 
+    public static final long CONTAINER_EPHEMERAL_OWNER = Long.MIN_VALUE;
+
     /**
      * This hashtable provides a fast lookup to the datanodes. The tree is the
      * source of truth and is where all the locking occurs
@@ -130,6 +134,12 @@ public class DataTree {
         new ConcurrentHashMap<Long, HashSet<String>>();
 
     /**
+     * This set contains the paths of all container nodes
+     */
+    private final Set<String> containers =
+            Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
+
+    /**
      * this is map from longs to acl's. It saves acl's being stored for each
      * datanode.
      */
@@ -160,6 +170,10 @@ public class DataTree {
         return cloned;
     }
 
+    public Set<String> getContainers() {
+        return new HashSet<String>(containers);
+    }
+
     int getAclSize() {
         return longKeyMap.size();
     }
@@ -507,7 +521,9 @@ public class DataTree {
             DataNode child = new DataNode(data, longval, stat);
             parent.addChild(childName);
             nodes.put(path, child);
-            if (ephemeralOwner != 0) {
+            if (ephemeralOwner == CONTAINER_EPHEMERAL_OWNER) {
+                containers.add(path);
+            } else if (ephemeralOwner != 0) {
                 HashSet<String> list = ephemerals.get(ephemeralOwner);
                 if (list == null) {
                     list = new HashSet<String>();
@@ -573,7 +589,9 @@ public class DataTree {
             parent.removeChild(childName);
             parent.stat.setPzxid(zxid);
             long eowner = node.stat.getEphemeralOwner();
-            if (eowner != 0) {
+            if (eowner == CONTAINER_EPHEMERAL_OWNER) {
+                containers.remove(path);
+            } else if (eowner != 0) {
                 HashSet<String> nodes = ephemerals.get(eowner);
                 if (nodes != null) {
                     synchronized (nodes) {
@@ -824,7 +842,21 @@ public class DataTree {
                             header.getZxid(), header.getTime(), stat);
                     rc.stat = stat;
                     break;
+                case OpCode.createContainer:
+                    CreateContainerTxn createContainerTxn = (CreateContainerTxn) txn;
+                    rc.path = createContainerTxn.getPath();
+                    stat = new Stat();
+                    createNode(
+                            createContainerTxn.getPath(),
+                            createContainerTxn.getData(),
+                            createContainerTxn.getAcl(),
+                            CONTAINER_EPHEMERAL_OWNER,
+                            createContainerTxn.getParentCVersion(),
+                            header.getZxid(), header.getTime(), stat);
+                    rc.stat = stat;
+                    break;
                 case OpCode.delete:
+                case OpCode.deleteContainer:
                     DeleteTxn deleteTxn = (DeleteTxn) txn;
                     rc.path = deleteTxn.getPath();
                     deleteNode(deleteTxn.getPath(), header.getZxid());
@@ -874,7 +906,11 @@ public class DataTree {
                             case OpCode.create:
                                 record = new CreateTxn();
                                 break;
+                            case OpCode.createContainer:
+                                record = new CreateContainerTxn();
+                                break;
                             case OpCode.delete:
+                            case OpCode.deleteContainer:
                                 record = new DeleteTxn();
                                 break;
                             case OpCode.setData:
@@ -1234,7 +1270,9 @@ public class DataTree {
                 }
                 parent.addChild(path.substring(lastSlash + 1));
                 long eowner = node.stat.getEphemeralOwner();
-                if (eowner != 0) {
+                if (eowner == CONTAINER_EPHEMERAL_OWNER) {
+                    containers.add(path);
+                } else if (eowner != 0) {
                     HashSet<String> list = ephemerals.get(eowner);
                     if (list == null) {
                         list = new HashSet<String>();

Modified: zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java (original)
+++ zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java Sat Jun  6 21:38:47 2015
@@ -18,22 +18,21 @@
 
 package org.apache.zookeeper.server;
 
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.List;
-import java.util.Locale;
-
 import org.apache.jute.Record;
-import org.apache.zookeeper.common.Time;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.KeeperException.Code;
+import org.apache.zookeeper.KeeperException.SessionMovedException;
 import org.apache.zookeeper.MultiResponse;
+import org.apache.zookeeper.OpResult;
+import org.apache.zookeeper.OpResult.CheckResult;
+import org.apache.zookeeper.OpResult.CreateResult;
+import org.apache.zookeeper.OpResult.DeleteResult;
+import org.apache.zookeeper.OpResult.ErrorResult;
+import org.apache.zookeeper.OpResult.SetDataResult;
 import org.apache.zookeeper.Watcher.WatcherType;
 import org.apache.zookeeper.ZooDefs;
-import org.apache.zookeeper.KeeperException.Code;
-import org.apache.zookeeper.KeeperException.SessionMovedException;
 import org.apache.zookeeper.ZooDefs.OpCode;
+import org.apache.zookeeper.common.Time;
 import org.apache.zookeeper.data.ACL;
 import org.apache.zookeeper.data.Stat;
 import org.apache.zookeeper.proto.CheckWatchesRequest;
@@ -61,13 +60,13 @@ import org.apache.zookeeper.server.ZooKe
 import org.apache.zookeeper.server.quorum.QuorumZooKeeperServer;
 import org.apache.zookeeper.txn.ErrorTxn;
 import org.apache.zookeeper.txn.TxnHeader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-import org.apache.zookeeper.OpResult;
-import org.apache.zookeeper.OpResult.CheckResult;
-import org.apache.zookeeper.OpResult.CreateResult;
-import org.apache.zookeeper.OpResult.DeleteResult;
-import org.apache.zookeeper.OpResult.SetDataResult;
-import org.apache.zookeeper.OpResult.ErrorResult;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Locale;
 
 /**
  * This Request processor actually applies any transaction associated with a
@@ -216,9 +215,11 @@ public class FinalRequestProcessor imple
                             subResult = new CreateResult(subTxnResult.path);
                             break;
                         case OpCode.create2:
+                        case OpCode.createContainer:
                             subResult = new CreateResult(subTxnResult.path, subTxnResult.stat);
                             break;
                         case OpCode.delete:
+                        case OpCode.deleteContainer:
                             subResult = new DeleteResult();
                             break;
                         case OpCode.setData:
@@ -242,13 +243,15 @@ public class FinalRequestProcessor imple
                 err = Code.get(rc.err);
                 break;
             }
-            case OpCode.create2: {
+            case OpCode.create2:
+            case OpCode.createContainer: {
                 lastOp = "CREA";
                 rsp = new Create2Response(rc.path, rc.stat);
                 err = Code.get(rc.err);
                 break;
             }
-            case OpCode.delete: {
+            case OpCode.delete:
+            case OpCode.deleteContainer: {
                 lastOp = "DELE";
                 err = Code.get(rc.err);
                 break;

Modified: zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/PrepRequestProcessor.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/PrepRequestProcessor.java?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/PrepRequestProcessor.java (original)
+++ zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/PrepRequestProcessor.java Sat Jun  6 21:38:47 2015
@@ -18,67 +18,67 @@
 
 package org.apache.zookeeper.server;
 
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.StringReader;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Locale;
-import java.util.Set;
-import java.util.concurrent.LinkedBlockingQueue;
-
-import org.apache.jute.Record;
 import org.apache.jute.BinaryOutputArchive;
-
-import org.apache.zookeeper.common.Time;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.apache.jute.Record;
 import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.KeeperException;
 import org.apache.zookeeper.KeeperException.BadArgumentsException;
+import org.apache.zookeeper.KeeperException.Code;
 import org.apache.zookeeper.MultiTransactionRecord;
 import org.apache.zookeeper.Op;
 import org.apache.zookeeper.ZooDefs;
-import org.apache.zookeeper.KeeperException.Code;
 import org.apache.zookeeper.ZooDefs.OpCode;
 import org.apache.zookeeper.common.PathUtils;
 import org.apache.zookeeper.common.StringUtils;
+import org.apache.zookeeper.common.Time;
 import org.apache.zookeeper.data.ACL;
 import org.apache.zookeeper.data.Id;
 import org.apache.zookeeper.data.StatPersisted;
+import org.apache.zookeeper.proto.CheckVersionRequest;
 import org.apache.zookeeper.proto.CreateRequest;
 import org.apache.zookeeper.proto.DeleteRequest;
 import org.apache.zookeeper.proto.ReconfigRequest;
 import org.apache.zookeeper.proto.SetACLRequest;
 import org.apache.zookeeper.proto.SetDataRequest;
-import org.apache.zookeeper.proto.CheckVersionRequest;
 import org.apache.zookeeper.server.ZooKeeperServer.ChangeRecord;
 import org.apache.zookeeper.server.auth.AuthenticationProvider;
 import org.apache.zookeeper.server.auth.ProviderRegistry;
+import org.apache.zookeeper.server.quorum.Leader.XidRolloverException;
 import org.apache.zookeeper.server.quorum.LeaderZooKeeperServer;
 import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer;
 import org.apache.zookeeper.server.quorum.QuorumPeerConfig;
 import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException;
 import org.apache.zookeeper.server.quorum.flexible.QuorumMaj;
 import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier;
-import org.apache.zookeeper.server.quorum.Leader.XidRolloverException;
+import org.apache.zookeeper.txn.CheckVersionTxn;
+import org.apache.zookeeper.txn.CreateContainerTxn;
 import org.apache.zookeeper.txn.CreateSessionTxn;
 import org.apache.zookeeper.txn.CreateTxn;
 import org.apache.zookeeper.txn.DeleteTxn;
 import org.apache.zookeeper.txn.ErrorTxn;
+import org.apache.zookeeper.txn.MultiTxn;
 import org.apache.zookeeper.txn.SetACLTxn;
 import org.apache.zookeeper.txn.SetDataTxn;
-import org.apache.zookeeper.txn.CheckVersionTxn;
 import org.apache.zookeeper.txn.Txn;
-import org.apache.zookeeper.txn.MultiTxn;
 import org.apache.zookeeper.txn.TxnHeader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.StringReader;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.LinkedBlockingQueue;
 
 /**
  * This request processor is generally at the start of a RequestProcessor
@@ -340,8 +340,8 @@ public class PrepRequestProcessor extend
             throws BadArgumentsException {
         int lastSlash = path.lastIndexOf('/');
         if (lastSlash == -1 || path.indexOf('\0') != -1 || failCreate) {
-            LOG.info("Invalid path " + path + " with session 0x" +
-                    Long.toHexString(sessionId));
+            LOG.info("Invalid path %s with session 0x%s",
+                    path, Long.toHexString(sessionId));
             throw new KeeperException.BadArgumentsException(path);
         }
         return path.substring(0, lastSlash);
@@ -365,7 +365,8 @@ public class PrepRequestProcessor extend
 
         switch (type) {
             case OpCode.create:
-            case OpCode.create2: {
+            case OpCode.create2:
+            case OpCode.createContainer: {
                 CreateRequest createRequest = (CreateRequest)record;
                 if (deserialize) {
                     ByteBufferInputStream.byteBuffer2Record(request.request, createRequest);
@@ -397,13 +398,18 @@ public class PrepRequestProcessor extend
                 } catch (KeeperException.NoNodeException e) {
                     // ignore this one
                 }
-                boolean ephemeralParent = parentRecord.stat.getEphemeralOwner() != 0;
+                boolean ephemeralParent = (parentRecord.stat.getEphemeralOwner() != 0) &&
+                        (parentRecord.stat.getEphemeralOwner() != DataTree.CONTAINER_EPHEMERAL_OWNER);
                 if (ephemeralParent) {
                     throw new KeeperException.NoChildrenForEphemeralsException(path);
                 }
                 int newCversion = parentRecord.stat.getCversion()+1;
-                request.setTxn(new CreateTxn(path, createRequest.getData(), listACL, createMode.isEphemeral(),
-                        newCversion));
+                if (type == OpCode.createContainer) {
+                    request.setTxn(new CreateContainerTxn(path, createRequest.getData(), listACL, newCversion));
+                } else {
+                    request.setTxn(new CreateTxn(path, createRequest.getData(), listACL, createMode.isEphemeral(),
+                            newCversion));
+                }
                 StatPersisted s = new StatPersisted();
                 if (createMode.isEphemeral()) {
                     s.setEphemeralOwner(request.sessionId);
@@ -415,18 +421,31 @@ public class PrepRequestProcessor extend
                 addChangeRecord(new ChangeRecord(request.getHdr().getZxid(), path, s, 0, listACL));
                 break;
             }
+            case OpCode.deleteContainer: {
+                String path = new String(request.request.array());
+                String parentPath = getParentPathAndValidate(path);
+                ChangeRecord parentRecord = getRecordForPath(parentPath);
+                ChangeRecord nodeRecord = getRecordForPath(path);
+                if (nodeRecord.childCount > 0) {
+                    throw new KeeperException.NotEmptyException(path);
+                }
+                if (nodeRecord.stat.getEphemeralOwner() != DataTree.CONTAINER_EPHEMERAL_OWNER) {
+                    throw new KeeperException.BadVersionException(path);
+                }
+                request.setTxn(new DeleteTxn(path));
+                parentRecord = parentRecord.duplicate(request.getHdr().getZxid());
+                parentRecord.childCount--;
+                addChangeRecord(parentRecord);
+                addChangeRecord(new ChangeRecord(request.getHdr().getZxid(), path, null, -1, null));
+                break;
+            }
             case OpCode.delete:
                 zks.sessionTracker.checkSession(request.sessionId, request.getOwner());
                 DeleteRequest deleteRequest = (DeleteRequest)record;
                 if(deserialize)
                     ByteBufferInputStream.byteBuffer2Record(request.request, deleteRequest);
                 String path = deleteRequest.getPath();
-                int lastSlash = path.lastIndexOf('/');
-                if (lastSlash == -1 || path.indexOf('\0') != -1
-                        || zks.getZKDatabase().isSpecialPath(path)) {
-                    throw new KeeperException.BadArgumentsException(path);
-                }
-                String parentPath = path.substring(0, lastSlash);
+                String parentPath = getParentPathAndValidate(path);
                 ChangeRecord parentRecord = getRecordForPath(parentPath);
                 ChangeRecord nodeRecord = getRecordForPath(path);
                 checkACL(zks, parentRecord.acl, ZooDefs.Perms.DELETE, request.authInfo);
@@ -642,6 +661,16 @@ public class PrepRequestProcessor extend
         }
     }
 
+    private String getParentPathAndValidate(String path)
+            throws BadArgumentsException {
+        int lastSlash = path.lastIndexOf('/');
+        if (lastSlash == -1 || path.indexOf('\0') != -1
+                || zks.getZKDatabase().isSpecialPath(path)) {
+            throw new BadArgumentsException(path);
+        }
+        return path.substring(0, lastSlash);
+    }
+
     private static int checkAndIncVersion(int currentVersion, int expectedVersion, String path)
             throws KeeperException.BadVersionException {
         if (expectedVersion != -1 && expectedVersion != currentVersion) {
@@ -664,13 +693,15 @@ public class PrepRequestProcessor extend
 
         try {
             switch (request.type) {
+            case OpCode.createContainer:
             case OpCode.create:
             case OpCode.create2:
                 CreateRequest create2Request = new CreateRequest();
                 pRequest2Txn(request.type, zks.getNextZxid(), request, create2Request, true);
                 break;
+            case OpCode.deleteContainer:
             case OpCode.delete:
-                DeleteRequest deleteRequest = new DeleteRequest();               
+                DeleteRequest deleteRequest = new DeleteRequest();
                 pRequest2Txn(request.type, zks.getNextZxid(), request, deleteRequest, true);
                 break;
             case OpCode.setData:

Modified: zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/Request.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/Request.java?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/Request.java (original)
+++ zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/Request.java Sat Jun  6 21:38:47 2015
@@ -137,8 +137,10 @@ public class Request {
         case OpCode.closeSession:
         case OpCode.create:
         case OpCode.create2:
+        case OpCode.createContainer:
         case OpCode.createSession:
         case OpCode.delete:
+        case OpCode.deleteContainer:
         case OpCode.exists:
         case OpCode.getACL:
         case OpCode.getChildren:
@@ -169,8 +171,10 @@ public class Request {
             return false;
         case OpCode.create:
         case OpCode.create2:
+        case OpCode.createContainer:
         case OpCode.error:
         case OpCode.delete:
+        case OpCode.deleteContainer:
         case OpCode.setACL:
         case OpCode.setData:
         case OpCode.check:
@@ -193,10 +197,14 @@ public class Request {
             return "create";
         case OpCode.create2:
             return "create2";
+        case OpCode.createContainer:
+            return "createContainer";
         case OpCode.setWatches:
             return "setWatches";
         case OpCode.delete:
             return "delete";
+        case OpCode.deleteContainer:
+            return "deleteContainer";
         case OpCode.exists:
             return "exists";
         case OpCode.getData:

Modified: zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/TraceFormatter.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/TraceFormatter.java?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/TraceFormatter.java (original)
+++ zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/TraceFormatter.java Sat Jun  6 21:38:47 2015
@@ -37,8 +37,12 @@ public class TraceFormatter {
             return "create";
         case OpCode.create2:
             return "create2";
+        case OpCode.createContainer:
+            return "createContainer";
         case OpCode.delete:
             return "delete";
+        case OpCode.deleteContainer:
+            return "deleteContainer";
         case OpCode.exists:
             return "exists";
         case OpCode.getData:

Modified: zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/ZooKeeperServerMain.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/ZooKeeperServerMain.java?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/ZooKeeperServerMain.java (original)
+++ zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/ZooKeeperServerMain.java Sat Jun  6 21:38:47 2015
@@ -19,6 +19,7 @@
 package org.apache.zookeeper.server;
 
 import java.io.IOException;
+import java.util.concurrent.TimeUnit;
 
 import javax.management.JMException;
 
@@ -45,6 +46,7 @@ public class ZooKeeperServerMain {
     // ZooKeeper server supports two kinds of connection: unencrypted and encrypted.
     private ServerCnxnFactory cnxnFactory;
     private ServerCnxnFactory secureCnxnFactory;
+    private ContainerManager containerManager;
 
     private AdminServer adminServer;
 
@@ -138,6 +140,12 @@ public class ZooKeeperServerMain {
                 secureCnxnFactory.startup(zkServer, needStartZKServer);
             }
 
+            containerManager = new ContainerManager(zkServer.getZKDatabase(), zkServer.firstProcessor,
+                    Integer.getInteger("znode.container.checkIntervalMs", (int) TimeUnit.MINUTES.toMillis(1)),
+                    Integer.getInteger("znode.container.maxPerMinute", 10000)
+            );
+            containerManager.start();
+
             if (cnxnFactory != null) {
                 cnxnFactory.join();
             }
@@ -162,6 +170,9 @@ public class ZooKeeperServerMain {
      * Shutdown the serving instance
      */
     protected void shutdown() {
+        if (containerManager != null) {
+            containerManager.stop();
+        }
         if (cnxnFactory != null) {
             cnxnFactory.shutdown();
         }

Modified: zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/CommitProcessor.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/CommitProcessor.java?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/CommitProcessor.java (original)
+++ zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/CommitProcessor.java Sat Jun  6 21:38:47 2015
@@ -133,7 +133,9 @@ public class CommitProcessor extends Zoo
         switch (request.type) {
             case OpCode.create:
             case OpCode.create2:
+            case OpCode.createContainer:
             case OpCode.delete:
+            case OpCode.deleteContainer:
             case OpCode.setData:
             case OpCode.reconfig:
             case OpCode.multi:

Modified: zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/FollowerRequestProcessor.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/FollowerRequestProcessor.java?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/FollowerRequestProcessor.java (original)
+++ zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/FollowerRequestProcessor.java Sat Jun  6 21:38:47 2015
@@ -84,7 +84,9 @@ public class FollowerRequestProcessor ex
                     break;
                 case OpCode.create:
                 case OpCode.create2:
+                case OpCode.createContainer:
                 case OpCode.delete:
+                case OpCode.deleteContainer:
                 case OpCode.setData:
                 case OpCode.reconfig:
                 case OpCode.setACL:

Modified: zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/LeaderZooKeeperServer.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/LeaderZooKeeperServer.java?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/LeaderZooKeeperServer.java (original)
+++ zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/LeaderZooKeeperServer.java Sat Jun  6 21:38:47 2015
@@ -18,10 +18,9 @@
 
 package org.apache.zookeeper.server.quorum;
 
-import java.io.IOException;
-
 import org.apache.zookeeper.KeeperException.SessionExpiredException;
 import org.apache.zookeeper.jmx.MBeanRegistry;
+import org.apache.zookeeper.server.ContainerManager;
 import org.apache.zookeeper.server.DataTreeBean;
 import org.apache.zookeeper.server.FinalRequestProcessor;
 import org.apache.zookeeper.server.PrepRequestProcessor;
@@ -31,6 +30,9 @@ import org.apache.zookeeper.server.Serve
 import org.apache.zookeeper.server.ZKDatabase;
 import org.apache.zookeeper.server.persistence.FileTxnSnapLog;
 
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
 /**
  *
  * Just like the standard ZooKeeperServer. We just replace the request
@@ -39,6 +41,8 @@ import org.apache.zookeeper.server.persi
  * FinalRequestProcessor
  */
 public class LeaderZooKeeperServer extends QuorumZooKeeperServer {
+    private ContainerManager containerManager;  // guarded by sync
+
 
     CommitProcessor commitProcessor;
 
@@ -71,6 +75,31 @@ public class LeaderZooKeeperServer exten
         prepRequestProcessor = new PrepRequestProcessor(this, proposalProcessor);
         prepRequestProcessor.start();
         firstProcessor = new LeaderRequestProcessor(this, prepRequestProcessor);
+
+        setupContainerManager();
+    }
+
+    private synchronized void setupContainerManager() {
+        containerManager = new ContainerManager(getZKDatabase(), prepRequestProcessor,
+                Integer.getInteger("znode.container.checkIntervalMs", (int) TimeUnit.MINUTES.toMillis(1)),
+                Integer.getInteger("znode.container.maxPerMinute", 10000)
+                );
+    }
+
+    @Override
+    public synchronized void startup() {
+        super.startup();
+        if (containerManager != null) {
+            containerManager.start();
+        }
+    }
+
+    @Override
+    public synchronized void shutdown() {
+        if (containerManager != null) {
+            containerManager.stop();
+        }
+        super.shutdown();
     }
 
     @Override

Modified: zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/ObserverRequestProcessor.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/ObserverRequestProcessor.java?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/ObserverRequestProcessor.java (original)
+++ zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/ObserverRequestProcessor.java Sat Jun  6 21:38:47 2015
@@ -93,7 +93,9 @@ public class ObserverRequestProcessor ex
                     break;
                 case OpCode.create:
                 case OpCode.create2:
+                case OpCode.createContainer:
                 case OpCode.delete:
+                case OpCode.deleteContainer:
                 case OpCode.setData:
                 case OpCode.reconfig:
                 case OpCode.setACL:

Modified: zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/ReadOnlyRequestProcessor.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/ReadOnlyRequestProcessor.java?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/ReadOnlyRequestProcessor.java (original)
+++ zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/quorum/ReadOnlyRequestProcessor.java Sat Jun  6 21:38:47 2015
@@ -82,7 +82,9 @@ public class ReadOnlyRequestProcessor ex
                 case OpCode.sync:
                 case OpCode.create:
                 case OpCode.create2:
+                case OpCode.createContainer:
                 case OpCode.delete:
+                case OpCode.deleteContainer:
                 case OpCode.setData:
                 case OpCode.reconfig:
                 case OpCode.setACL:

Modified: zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/util/SerializeUtils.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/util/SerializeUtils.java?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/util/SerializeUtils.java (original)
+++ zookeeper/branches/branch-3.5/src/java/main/org/apache/zookeeper/server/util/SerializeUtils.java Sat Jun  6 21:38:47 2015
@@ -18,16 +18,6 @@
 
 package org.apache.zookeeper.server.util;
 
-import java.io.ByteArrayInputStream;
-import java.io.EOFException;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import org.apache.jute.BinaryInputArchive;
 import org.apache.jute.InputArchive;
 import org.apache.jute.OutputArchive;
@@ -35,15 +25,25 @@ import org.apache.jute.Record;
 import org.apache.zookeeper.ZooDefs.OpCode;
 import org.apache.zookeeper.server.DataTree;
 import org.apache.zookeeper.server.ZooTrace;
+import org.apache.zookeeper.txn.CreateContainerTxn;
 import org.apache.zookeeper.txn.CreateSessionTxn;
 import org.apache.zookeeper.txn.CreateTxn;
 import org.apache.zookeeper.txn.CreateTxnV0;
 import org.apache.zookeeper.txn.DeleteTxn;
 import org.apache.zookeeper.txn.ErrorTxn;
+import org.apache.zookeeper.txn.MultiTxn;
 import org.apache.zookeeper.txn.SetACLTxn;
 import org.apache.zookeeper.txn.SetDataTxn;
 import org.apache.zookeeper.txn.TxnHeader;
-import org.apache.zookeeper.txn.MultiTxn;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
 
 public class SerializeUtils {
     private static final Logger LOG = LoggerFactory.getLogger(SerializeUtils.class);
@@ -68,7 +68,11 @@ public class SerializeUtils {
         case OpCode.create2:
             txn = new CreateTxn();
             break;
+        case OpCode.createContainer:
+            txn = new CreateContainerTxn();
+            break;
         case OpCode.delete:
+        case OpCode.deleteContainer:
             txn = new DeleteTxn();
             break;
         case OpCode.reconfig:

Added: zookeeper/branches/branch-3.5/src/java/test/org/apache/zookeeper/server/CreateContainerTest.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/test/org/apache/zookeeper/server/CreateContainerTest.java?rev=1683960&view=auto
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/test/org/apache/zookeeper/server/CreateContainerTest.java (added)
+++ zookeeper/branches/branch-3.5/src/java/test/org/apache/zookeeper/server/CreateContainerTest.java Sat Jun  6 21:38:47 2015
@@ -0,0 +1,282 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.zookeeper.server;
+
+import org.apache.zookeeper.*;
+import org.apache.zookeeper.data.Stat;
+import org.apache.zookeeper.test.ClientBase;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.*;
+
+public class CreateContainerTest extends ClientBase {
+    private ZooKeeper zk;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        zk = createClient();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        zk.close();
+    }
+
+    @Test(timeout = 30000)
+    public void testCreate()
+            throws IOException, KeeperException, InterruptedException {
+        createNoStatVerifyResult("/foo");
+        createNoStatVerifyResult("/foo/child");
+    }
+
+    @Test(timeout = 30000)
+    public void testCreateWithStat()
+            throws IOException, KeeperException, InterruptedException {
+        Stat stat = createWithStatVerifyResult("/foo");
+        Stat childStat = createWithStatVerifyResult("/foo/child");
+        // Don't expect to get the same stats for different creates.
+        Assert.assertFalse(stat.equals(childStat));
+    }
+
+    @SuppressWarnings("ConstantConditions")
+    @Test(timeout = 30000)
+    public void testCreateWithNullStat()
+            throws IOException, KeeperException, InterruptedException {
+        final String name = "/foo";
+        Assert.assertNull(zk.exists(name, false));
+
+        Stat stat = null;
+        // If a null Stat object is passed the create should still
+        // succeed, but no Stat info will be returned.
+        zk.create(name, name.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER, stat);
+        Assert.assertNull(stat);
+        Assert.assertNotNull(zk.exists(name, false));
+    }
+
+    @Test(timeout = 30000)
+    public void testSimpleDeletion()
+            throws IOException, KeeperException, InterruptedException {
+        zk.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER);
+        zk.create("/foo/bar", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
+        zk.delete("/foo/bar", -1);  // should cause "/foo" to get deleted when checkContainers() is called
+
+        ContainerManager containerManager = new ContainerManager(serverFactory.getZooKeeperServer()
+                .getZKDatabase(), serverFactory.getZooKeeperServer().firstProcessor, 1, 100);
+        containerManager.checkContainers();
+
+        Thread.sleep(1000);
+
+        Assert.assertNull("Container should have been deleted", zk.exists("/foo", false));
+    }
+
+    @Test(timeout = 30000)
+    public void testMultiWithContainerSimple()
+            throws IOException, KeeperException, InterruptedException {
+        Op createContainer = Op.create("/foo", new byte[0],
+                ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER);
+        zk.multi(Collections.singletonList(createContainer));
+
+        DataTree dataTree = serverFactory.getZooKeeperServer().getZKDatabase().getDataTree();
+        Assert.assertEquals(dataTree.getContainers().size(), 1);
+    }
+
+    @Test(timeout = 30000)
+    public void testMultiWithContainer()
+            throws IOException, KeeperException, InterruptedException {
+        Op createContainer = Op.create("/foo", new byte[0],
+                ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER);
+        Op createChild = Op.create("/foo/bar", new byte[0],
+                ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
+        zk.multi(Arrays.asList(createContainer, createChild));
+
+        DataTree dataTree = serverFactory.getZooKeeperServer().getZKDatabase().getDataTree();
+        Assert.assertEquals(dataTree.getContainers().size(), 1);
+
+        zk.delete("/foo/bar", -1);  // should cause "/foo" to get deleted when checkContainers() is called
+
+        ContainerManager containerManager = new ContainerManager(serverFactory.getZooKeeperServer()
+                .getZKDatabase(), serverFactory.getZooKeeperServer().firstProcessor, 1, 100);
+        containerManager.checkContainers();
+
+        Thread.sleep(1000);
+
+        Assert.assertNull("Container should have been deleted", zk.exists("/foo", false));
+
+        createContainer = Op.create("/foo", new byte[0],
+                ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER);
+        createChild = Op.create("/foo/bar", new byte[0],
+                ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
+        Op deleteChild = Op.delete("/foo/bar", -1);
+        zk.multi(Arrays.asList(createContainer, createChild, deleteChild));
+
+        containerManager.checkContainers();
+
+        Thread.sleep(1000);
+
+        Assert.assertNull("Container should have been deleted", zk.exists("/foo", false));
+    }
+
+    @Test(timeout = 30000)
+    public void testSimpleDeletionAsync()
+            throws IOException, KeeperException, InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        AsyncCallback.Create2Callback cb = new AsyncCallback.Create2Callback() {
+            @Override
+            public void processResult(int rc, String path, Object ctx, String name, Stat stat) {
+                Assert.assertEquals(ctx, "context");
+                latch.countDown();
+            }
+        };
+        zk.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER, cb, "context");
+        Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+        zk.create("/foo/bar", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
+        zk.delete("/foo/bar", -1);  // should cause "/foo" to get deleted when checkContainers() is called
+
+        ContainerManager containerManager = new ContainerManager(serverFactory.getZooKeeperServer()
+                .getZKDatabase(), serverFactory.getZooKeeperServer().firstProcessor, 1, 100);
+        containerManager.checkContainers();
+
+        Thread.sleep(1000);
+
+        Assert.assertNull("Container should have been deleted", zk.exists("/foo", false));
+    }
+
+    @Test(timeout = 30000)
+    public void testCascadingDeletion()
+            throws IOException, KeeperException, InterruptedException {
+        zk.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER);
+        zk.create("/foo/bar", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER);
+        zk.create("/foo/bar/one", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
+        zk.delete("/foo/bar/one", -1);  // should cause "/foo/bar" and "/foo" to get deleted when checkContainers() is called
+
+        ContainerManager containerManager = new ContainerManager(serverFactory.getZooKeeperServer()
+                .getZKDatabase(), serverFactory.getZooKeeperServer().firstProcessor, 1, 100);
+        containerManager.checkContainers();
+        Thread.sleep(1000);
+        containerManager
+                .checkContainers();
+        Thread.sleep(1000);
+
+        Assert.assertNull("Container should have been deleted", zk.exists("/foo/bar", false));
+        Assert.assertNull("Container should have been deleted", zk.exists("/foo", false));
+    }
+
+    @Test(timeout = 30000)
+    public void testFalseEmpty()
+            throws IOException, KeeperException, InterruptedException {
+        zk.create("/foo", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER);
+        zk.create("/foo/bar", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
+
+        ContainerManager containerManager = new ContainerManager(serverFactory.getZooKeeperServer()
+                .getZKDatabase(), serverFactory.getZooKeeperServer().firstProcessor, 1, 100) {
+            @Override
+            protected Collection<String> getCandidates() {
+                return Collections.singletonList("/foo");
+            }
+        };
+        containerManager.checkContainers();
+        Thread.sleep(1000);
+
+        Assert.assertNotNull("Container should have not been deleted", zk.exists("/foo", false));
+    }
+
+    @Test(timeout = 30000)
+    public void testMaxPerMinute()
+            throws IOException, KeeperException, InterruptedException {
+        final BlockingQueue<String> queue = new LinkedBlockingQueue<String>();
+        RequestProcessor processor = new RequestProcessor() {
+            @Override
+            public void processRequest(Request request) throws RequestProcessorException {
+                queue.add(new String(request.request.array()));
+            }
+
+            @Override
+            public void shutdown() {
+            }
+        };
+        final ContainerManager containerManager = new ContainerManager(serverFactory.getZooKeeperServer()
+                .getZKDatabase(), processor, 1, 2) {
+            @Override
+            protected long getMinIntervalMs() {
+                return 1000;
+            }
+
+            @Override
+            protected Collection<String> getCandidates() {
+                return Arrays.asList("/one", "/two", "/three", "/four");
+            }
+        };
+        Executors.newSingleThreadExecutor().submit(new Callable<Void>() {
+            @Override
+            public Void call() throws Exception {
+                containerManager.checkContainers();
+                return null;
+            }
+        });
+        Assert.assertEquals(queue.poll(5, TimeUnit.SECONDS), "/one");
+        Assert.assertEquals(queue.poll(5, TimeUnit.SECONDS), "/two");
+        Assert.assertEquals(queue.size(), 0);
+        Thread.sleep(500);
+        Assert.assertEquals(queue.size(), 0);
+
+        Assert.assertEquals(queue.poll(5, TimeUnit.SECONDS), "/three");
+        Assert.assertEquals(queue.poll(5, TimeUnit.SECONDS), "/four");
+    }
+
+    private void createNoStatVerifyResult(String newName)
+            throws KeeperException, InterruptedException {
+        Assert.assertNull("Node existed before created", zk.exists(newName, false));
+        zk.create(newName, newName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER);
+        Assert.assertNotNull("Node was not created as expected",
+                zk.exists(newName, false));
+    }
+
+    private Stat createWithStatVerifyResult(String newName)
+            throws KeeperException, InterruptedException {
+        Assert.assertNull("Node existed before created", zk.exists(newName, false));
+        Stat stat = new Stat();
+        zk.create(newName, newName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.CONTAINER, stat);
+        validateCreateStat(stat, newName);
+
+        Stat referenceStat = zk.exists(newName, false);
+        Assert.assertNotNull("Node was not created as expected", referenceStat);
+        Assert.assertEquals(referenceStat, stat);
+
+        return stat;
+    }
+
+    private void validateCreateStat(Stat stat, String name) {
+        Assert.assertEquals(stat.getCzxid(), stat.getMzxid());
+        Assert.assertEquals(stat.getCzxid(), stat.getPzxid());
+        Assert.assertEquals(stat.getCtime(), stat.getMtime());
+        Assert.assertEquals(0, stat.getCversion());
+        Assert.assertEquals(0, stat.getVersion());
+        Assert.assertEquals(0, stat.getAversion());
+        Assert.assertEquals(0, stat.getEphemeralOwner());
+        Assert.assertEquals(name.length(), stat.getDataLength());
+        Assert.assertEquals(0, stat.getNumChildren());
+    }
+}

Modified: zookeeper/branches/branch-3.5/src/java/test/org/apache/zookeeper/test/CreateModeTest.java
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/java/test/org/apache/zookeeper/test/CreateModeTest.java?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/java/test/org/apache/zookeeper/test/CreateModeTest.java (original)
+++ zookeeper/branches/branch-3.5/src/java/test/org/apache/zookeeper/test/CreateModeTest.java Sat Jun  6 21:38:47 2015
@@ -35,21 +35,31 @@ public class CreateModeTest extends ZKTe
         Assert.assertEquals(cm.toFlag(), 0);
         Assert.assertFalse(cm.isEphemeral());
         Assert.assertFalse(cm.isSequential());
-        
+        Assert.assertFalse(cm.isContainer());
+
         cm = CreateMode.EPHEMERAL;
         Assert.assertEquals(cm.toFlag(), 1);
         Assert.assertTrue(cm.isEphemeral());
         Assert.assertFalse(cm.isSequential());
-        
+        Assert.assertFalse(cm.isContainer());
+
         cm = CreateMode.PERSISTENT_SEQUENTIAL;
         Assert.assertEquals(cm.toFlag(), 2);
         Assert.assertFalse(cm.isEphemeral());
         Assert.assertTrue(cm.isSequential());
-        
+        Assert.assertFalse(cm.isContainer());
+
         cm = CreateMode.EPHEMERAL_SEQUENTIAL;
         Assert.assertEquals(cm.toFlag(), 3);
         Assert.assertTrue(cm.isEphemeral());
         Assert.assertTrue(cm.isSequential());
+        Assert.assertFalse(cm.isContainer());
+
+        cm = CreateMode.CONTAINER;
+        Assert.assertEquals(cm.toFlag(), 4);
+        Assert.assertFalse(cm.isEphemeral());
+        Assert.assertFalse(cm.isSequential());
+        Assert.assertTrue(cm.isContainer());
     }
     
     @Test

Modified: zookeeper/branches/branch-3.5/src/zookeeper.jute
URL: http://svn.apache.org/viewvc/zookeeper/branches/branch-3.5/src/zookeeper.jute?rev=1683960&r1=1683959&r2=1683960&view=diff
==============================================================================
--- zookeeper/branches/branch-3.5/src/zookeeper.jute (original)
+++ zookeeper/branches/branch-3.5/src/zookeeper.jute Sat Jun  6 21:38:47 2015
@@ -260,6 +260,12 @@ module org.apache.zookeeper.txn {
         boolean ephemeral;
         int parentCVersion;
     }
+    class CreateContainerTxn {
+        ustring path;
+        buffer data;
+        vector<org.apache.zookeeper.data.ACL> acl;
+        int parentCVersion;
+    }
     class DeleteTxn {
         ustring path;
     }