You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@curator.apache.org by ra...@apache.org on 2017/07/14 04:53:49 UTC

[1/2] curator git commit: initial work on migrations done. Needs lots of testing and doc. However, found bug CURATOR-423 and will need to fix that first

Repository: curator
Updated Branches:
  refs/heads/CURATOR-421 [created] 1784a7c68


initial work on migrations done. Needs lots of testing and doc. However, found bug CURATOR-423 and will need to fix that first


Project: http://git-wip-us.apache.org/repos/asf/curator/repo
Commit: http://git-wip-us.apache.org/repos/asf/curator/commit/4cd731c9
Tree: http://git-wip-us.apache.org/repos/asf/curator/tree/4cd731c9
Diff: http://git-wip-us.apache.org/repos/asf/curator/diff/4cd731c9

Branch: refs/heads/CURATOR-421
Commit: 4cd731c92d34a075d9d5a9610ec2610d69aa7792
Parents: 716fb4a
Author: randgalt <ra...@apache.org>
Authored: Thu Jul 13 23:44:59 2017 -0500
Committer: randgalt <ra...@apache.org>
Committed: Thu Jul 13 23:44:59 2017 -0500

----------------------------------------------------------------------
 .../apache/curator/x/async/AsyncWrappers.java   |  38 ++--
 .../x/async/modeled/ModelSerializer.java        |  18 ++
 .../async/modeled/ModeledFrameworkBuilder.java  |  20 +-
 .../curator/x/async/modeled/ModeledOptions.java |  29 +++
 .../modeled/details/ModeledFrameworkImpl.java   |  33 +++-
 .../InvalidMigrationSetException.java           |  37 ++++
 .../x/async/modeled/migrations/MetaData.java    |  84 +++++++++
 .../x/async/modeled/migrations/Migration.java   |  57 ++++++
 .../modeled/migrations/MigrationManager.java    |  37 ++++
 .../migrations/MigrationManagerBuilder.java     |  68 +++++++
 .../migrations/MigrationManagerImpl.java        | 181 +++++++++++++++++++
 .../async/modeled/migrations/MigrationSet.java  |  69 +++++++
 .../x/async/modeled/migrations/Result.java      |  23 +++
 .../x/async/CompletableBaseClassForTests.java   |   8 +-
 .../migrations/TestMigrationManager.java        | 129 +++++++++++++
 .../modeled/migrations/models/ModelV1.java      |  39 ++++
 .../modeled/migrations/models/ModelV2.java      |  46 +++++
 .../modeled/migrations/models/ModelV3.java      |  53 ++++++
 18 files changed, 934 insertions(+), 35 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/AsyncWrappers.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/AsyncWrappers.java b/curator-x-async/src/main/java/org/apache/curator/x/async/AsyncWrappers.java
index e982cf2..7da82fc 100644
--- a/curator-x-async/src/main/java/org/apache/curator/x/async/AsyncWrappers.java
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/AsyncWrappers.java
@@ -20,7 +20,10 @@ package org.apache.curator.x.async;
 
 import org.apache.curator.framework.recipes.locks.InterProcessLock;
 import org.apache.curator.utils.ThreadUtils;
+import org.apache.curator.x.async.api.ExistsOption;
 import org.apache.curator.x.async.modeled.ZPath;
+import java.util.Collections;
+import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
 import java.util.concurrent.Executor;
@@ -67,38 +70,21 @@ public class AsyncWrappers
 {
     /**
      * Asynchronously call {@link org.apache.curator.framework.CuratorFramework#createContainers(String)} using
-     * the {@link java.util.concurrent.ForkJoinPool#commonPool()}.
-     *
-     * @param client client
-     * @param path path to ensure
-     * @return stage
-     */
-    public static CompletionStage<Void> asyncEnsureContainers(AsyncCuratorFramework client, ZPath path)
-    {
-        return asyncEnsureContainers(client, path, null);
-    }
-
-    /**
-     * Asynchronously call {@link org.apache.curator.framework.CuratorFramework#createContainers(String)} using
      * the given executor
      *
      * @param client client
      * @param path path to ensure
      * @return stage
      */
-    public static CompletionStage<Void> asyncEnsureContainers(AsyncCuratorFramework client, ZPath path, Executor executor)
+    public static CompletionStage<Void> asyncEnsureContainers(AsyncCuratorFramework client, ZPath path)
     {
-        Runnable proc = () -> {
-            try
-            {
-                client.unwrap().createContainers(path.fullPath());
-            }
-            catch ( Exception e )
-            {
-                throw new RuntimeException(e);
-            }
-        };
-        return (executor != null) ? CompletableFuture.runAsync(proc, executor) : CompletableFuture.runAsync(proc);
+        Set<ExistsOption> options = Collections.singleton(ExistsOption.createParentsAsContainers);
+        return client
+            .checkExists()
+            .withOptions(options)
+            .forPath(path.child("foo").fullPath())
+            .thenApply(__ -> null)
+            ;
     }
 
     /**
@@ -284,7 +270,7 @@ public class AsyncWrappers
                 future.complete(null);
             }
         }
-        catch ( Exception e )
+        catch ( Throwable e )
         {
             ThreadUtils.checkInterrupted(e);
             future.completeExceptionally(e);

http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSerializer.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSerializer.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSerializer.java
index 428096e..476f314 100644
--- a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSerializer.java
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSerializer.java
@@ -40,4 +40,22 @@ public interface ModelSerializer<T>
      * @throws RuntimeException if <code>bytes</code> is invalid or there was an error deserializing
      */
     T deserialize(byte[] bytes);
+
+    /**
+     * A pass through serializer
+     */
+    ModelSerializer<byte[]> raw = new ModelSerializer<byte[]>()
+    {
+        @Override
+        public byte[] serialize(byte[] model)
+        {
+            return model;
+        }
+
+        @Override
+        public byte[] deserialize(byte[] bytes)
+        {
+            return bytes;
+        }
+    };
 }

http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledFrameworkBuilder.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledFrameworkBuilder.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledFrameworkBuilder.java
index 2e8bec3..1df68e6 100644
--- a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledFrameworkBuilder.java
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledFrameworkBuilder.java
@@ -18,13 +18,16 @@
  */
 package org.apache.curator.x.async.modeled;
 
+import com.google.common.collect.ImmutableSet;
 import org.apache.curator.framework.api.CuratorEvent;
 import org.apache.curator.framework.api.UnhandledErrorListener;
 import org.apache.curator.x.async.AsyncCuratorFramework;
 import org.apache.curator.x.async.WatchMode;
 import org.apache.curator.x.async.modeled.details.ModeledFrameworkImpl;
 import org.apache.zookeeper.WatchedEvent;
+import java.util.Collections;
 import java.util.Objects;
+import java.util.Set;
 import java.util.function.UnaryOperator;
 
 public class ModeledFrameworkBuilder<T>
@@ -35,6 +38,7 @@ public class ModeledFrameworkBuilder<T>
     private UnaryOperator<WatchedEvent> watcherFilter;
     private UnhandledErrorListener unhandledErrorListener;
     private UnaryOperator<CuratorEvent> resultFilter;
+    private Set<ModeledOptions> modeledOptions;
 
     /**
      * Build a new ModeledFramework instance
@@ -49,7 +53,8 @@ public class ModeledFrameworkBuilder<T>
             watchMode,
             watcherFilter,
             unhandledErrorListener,
-            resultFilter
+            resultFilter,
+            modeledOptions
         );
     }
 
@@ -142,6 +147,18 @@ public class ModeledFrameworkBuilder<T>
         return this;
     }
 
+    /**
+     * Change the modeled options
+     *
+     * @param modeledOptions new options set
+     * @return this for chaining
+     */
+    public ModeledFrameworkBuilder<T> withOptions(Set<ModeledOptions> modeledOptions)
+    {
+        this.modeledOptions = ImmutableSet.copyOf(Objects.requireNonNull(modeledOptions, "client cannot be null"));
+        return this;
+    }
+
     ModeledFrameworkBuilder()
     {
     }
@@ -150,5 +167,6 @@ public class ModeledFrameworkBuilder<T>
     {
         this.client = Objects.requireNonNull(client, "client cannot be null");
         this.modelSpec = Objects.requireNonNull(modelSpec, "modelSpec cannot be null");
+        modeledOptions = Collections.singleton(ModeledOptions.ignoreMissingNodesForChildren);
     }
 }

http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledOptions.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledOptions.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledOptions.java
new file mode 100644
index 0000000..434894b
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledOptions.java
@@ -0,0 +1,29 @@
+/**
+ * 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.curator.x.async.modeled;
+
+public enum ModeledOptions
+{
+    /**
+     * Causes {@link ModeledFramework#children()} and {@link ModeledFramework#childrenAsZNodes()}
+     * to ignore {@link org.apache.zookeeper.KeeperException.NoNodeException} and merely return
+     * an empty list
+     */
+    ignoreMissingNodesForChildren
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModeledFrameworkImpl.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModeledFrameworkImpl.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModeledFrameworkImpl.java
index c1d19c4..44011ee 100644
--- a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModeledFrameworkImpl.java
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModeledFrameworkImpl.java
@@ -19,6 +19,8 @@
 package org.apache.curator.x.async.modeled.details;
 
 import com.google.common.base.Preconditions;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import org.apache.curator.framework.api.CuratorEvent;
 import org.apache.curator.framework.api.UnhandledErrorListener;
@@ -36,13 +38,16 @@ import org.apache.curator.x.async.api.CreateOption;
 import org.apache.curator.x.async.api.WatchableAsyncCuratorFramework;
 import org.apache.curator.x.async.modeled.ModelSpec;
 import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.ModeledOptions;
 import org.apache.curator.x.async.modeled.ZNode;
 import org.apache.curator.x.async.modeled.ZPath;
 import org.apache.curator.x.async.modeled.cached.CachedModeledFramework;
 import org.apache.curator.x.async.modeled.versioned.VersionedModeledFramework;
+import org.apache.zookeeper.KeeperException;
 import org.apache.zookeeper.WatchedEvent;
 import org.apache.zookeeper.data.ACL;
 import org.apache.zookeeper.data.Stat;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -62,13 +67,15 @@ public class ModeledFrameworkImpl<T> implements ModeledFramework<T>
     private final UnaryOperator<CuratorEvent> resultFilter;
     private final AsyncCuratorFrameworkDsl dslClient;
     private final boolean isWatched;
+    private final Set<ModeledOptions> modeledOptions;
 
-    public static <T> ModeledFrameworkImpl<T> build(AsyncCuratorFramework client, ModelSpec<T> model, WatchMode watchMode, UnaryOperator<WatchedEvent> watcherFilter, UnhandledErrorListener unhandledErrorListener, UnaryOperator<CuratorEvent> resultFilter)
+    public static <T> ModeledFrameworkImpl<T> build(AsyncCuratorFramework client, ModelSpec<T> model, WatchMode watchMode, UnaryOperator<WatchedEvent> watcherFilter, UnhandledErrorListener unhandledErrorListener, UnaryOperator<CuratorEvent> resultFilter, Set<ModeledOptions> modeledOptions)
     {
         boolean isWatched = (watchMode != null);
 
         Objects.requireNonNull(client, "client cannot be null");
         Objects.requireNonNull(model, "model cannot be null");
+        modeledOptions = ImmutableSet.copyOf(Objects.requireNonNull(modeledOptions, "modeledOptions cannot be null"));
 
         watchMode = (watchMode != null) ? watchMode : WatchMode.stateChangeAndSuccess;
 
@@ -84,11 +91,12 @@ public class ModeledFrameworkImpl<T> implements ModeledFramework<T>
             watcherFilter,
             unhandledErrorListener,
             resultFilter,
-            isWatched
+            isWatched,
+            modeledOptions
         );
     }
 
-    private ModeledFrameworkImpl(AsyncCuratorFramework client, AsyncCuratorFrameworkDsl dslClient, WatchableAsyncCuratorFramework watchableClient, ModelSpec<T> modelSpec, WatchMode watchMode, UnaryOperator<WatchedEvent> watcherFilter, UnhandledErrorListener unhandledErrorListener, UnaryOperator<CuratorEvent> resultFilter, boolean isWatched)
+    private ModeledFrameworkImpl(AsyncCuratorFramework client, AsyncCuratorFrameworkDsl dslClient, WatchableAsyncCuratorFramework watchableClient, ModelSpec<T> modelSpec, WatchMode watchMode, UnaryOperator<WatchedEvent> watcherFilter, UnhandledErrorListener unhandledErrorListener, UnaryOperator<CuratorEvent> resultFilter, boolean isWatched, Set<ModeledOptions> modeledOptions)
     {
         this.client = client;
         this.dslClient = dslClient;
@@ -99,6 +107,7 @@ public class ModeledFrameworkImpl<T> implements ModeledFramework<T>
         this.unhandledErrorListener = unhandledErrorListener;
         this.resultFilter = resultFilter;
         this.isWatched = isWatched;
+        this.modeledOptions = modeledOptions;
     }
 
     @Override
@@ -280,7 +289,14 @@ public class ModeledFrameworkImpl<T> implements ModeledFramework<T>
         asyncStage.whenComplete((children, e) -> {
             if ( e != null )
             {
-                modelStage.completeExceptionally(e);
+                if ( modeledOptions.contains(ModeledOptions.ignoreMissingNodesForChildren) && (Throwables.getRootCause(e) instanceof KeeperException.NoNodeException) )
+                {
+                    modelStage.complete(Collections.emptyList());
+                }
+                else
+                {
+                    modelStage.completeExceptionally(e);
+                }
             }
             else
             {
@@ -303,7 +319,8 @@ public class ModeledFrameworkImpl<T> implements ModeledFramework<T>
             watcherFilter,
             unhandledErrorListener,
             resultFilter,
-            isWatched
+            isWatched,
+            modeledOptions
         );
     }
 
@@ -320,7 +337,8 @@ public class ModeledFrameworkImpl<T> implements ModeledFramework<T>
             watcherFilter,
             unhandledErrorListener,
             resultFilter,
-            isWatched
+            isWatched,
+            modeledOptions
         );
     }
 
@@ -337,7 +355,8 @@ public class ModeledFrameworkImpl<T> implements ModeledFramework<T>
             watcherFilter,
             unhandledErrorListener,
             resultFilter,
-            isWatched
+            isWatched,
+            modeledOptions
         );
     }
 

http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/InvalidMigrationSetException.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/InvalidMigrationSetException.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/InvalidMigrationSetException.java
new file mode 100644
index 0000000..84b21bf
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/InvalidMigrationSetException.java
@@ -0,0 +1,37 @@
+/**
+ * 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.curator.x.async.modeled.migrations;
+
+import java.util.Objects;
+
+public class InvalidMigrationSetException extends RuntimeException
+{
+    private final String migrationId;
+
+    public InvalidMigrationSetException(String migrationId, String message)
+    {
+        super(message);
+        this.migrationId = Objects.requireNonNull(migrationId, "migrationId cannot be null");
+    }
+
+    public String getMigrationId()
+    {
+        return migrationId;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MetaData.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MetaData.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MetaData.java
new file mode 100644
index 0000000..c2878e2
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MetaData.java
@@ -0,0 +1,84 @@
+/**
+ * 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.curator.x.async.modeled.migrations;
+
+import java.util.Objects;
+
+public class MetaData
+{
+    private final String migrationId;
+    private final int migrationVersion;
+
+    public MetaData()
+    {
+        this("", 0);
+    }
+
+    public MetaData(String migrationId, int migrationVersion)
+    {
+        this.migrationId = Objects.requireNonNull(migrationId, "migrationId cannot be null");
+        this.migrationVersion = migrationVersion;
+    }
+
+    public String getMigrationId()
+    {
+        return migrationId;
+    }
+
+    public int getMigrationVersion()
+    {
+        return migrationVersion;
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if ( this == o )
+        {
+            return true;
+        }
+        if ( o == null || getClass() != o.getClass() )
+        {
+            return false;
+        }
+
+        MetaData metaData = (MetaData)o;
+
+        //noinspection SimplifiableIfStatement
+        if ( migrationVersion != metaData.migrationVersion )
+        {
+            return false;
+        }
+        return migrationId.equals(metaData.migrationId);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = migrationId.hashCode();
+        result = 31 * result + migrationVersion;
+        return result;
+    }
+
+    @Override
+    public String toString()
+    {
+        return "MetaData{" + "migrationId='" + migrationId + '\'' + ", migrationVersion=" + migrationVersion + '}';
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/Migration.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/Migration.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/Migration.java
new file mode 100644
index 0000000..b3919d1
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/Migration.java
@@ -0,0 +1,57 @@
+/**
+ * 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.curator.x.async.modeled.migrations;
+
+import java.util.Objects;
+import java.util.function.UnaryOperator;
+
+public interface Migration
+{
+    String id();
+
+    int version();
+
+    byte[] migrate(byte[] previousBytes);
+
+    static Migration build(String id, int version, UnaryOperator<byte[]> migrateProc)
+    {
+        Objects.requireNonNull(id, "id cannot be null");
+        Objects.requireNonNull(migrateProc, "migrateProc cannot be null");
+        return new Migration()
+        {
+            @Override
+            public String id()
+            {
+                return id;
+            }
+
+            @Override
+            public int version()
+            {
+                return version;
+            }
+
+            @Override
+            public byte[] migrate(byte[] previousBytes)
+            {
+                return migrateProc.apply(previousBytes);
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManager.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManager.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManager.java
new file mode 100644
index 0000000..2d5f39f
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManager.java
@@ -0,0 +1,37 @@
+/**
+ * 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.curator.x.async.modeled.migrations;
+
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.modeled.ModelSerializer;
+import org.apache.curator.x.async.modeled.ZPath;
+import java.util.List;
+import java.util.concurrent.CompletionStage;
+
+public interface MigrationManager
+{
+    CompletionStage<List<MetaData>> metaData(ZPath metaDataPath);
+
+    CompletionStage<Void> run();
+
+    static MigrationManagerBuilder builder(AsyncCuratorFramework client, ZPath lockPath, ModelSerializer<MetaData> metaDataSerializer)
+    {
+        return new MigrationManagerBuilder(client, lockPath, metaDataSerializer);
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManagerBuilder.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManagerBuilder.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManagerBuilder.java
new file mode 100644
index 0000000..ed48242
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManagerBuilder.java
@@ -0,0 +1,68 @@
+/**
+ * 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.curator.x.async.modeled.migrations;
+
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.modeled.ModelSerializer;
+import org.apache.curator.x.async.modeled.ZPath;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+public class MigrationManagerBuilder
+{
+    private final AsyncCuratorFramework client;
+    private final ZPath lockPath;
+    private final ModelSerializer<MetaData> metaDataSerializer;
+    private final List<MigrationSet> sets = new ArrayList<>();
+    private Executor executor = Runnable::run;
+    private Duration lockMax = Duration.ofSeconds(15);
+
+    MigrationManagerBuilder(AsyncCuratorFramework client, ZPath lockPath, ModelSerializer<MetaData> metaDataSerializer)
+    {
+        this.client = Objects.requireNonNull(client, "client cannot be null");
+        this.lockPath = Objects.requireNonNull(lockPath, "lockPath cannot be null");
+        this.metaDataSerializer = Objects.requireNonNull(metaDataSerializer, "metaDataSerializer cannot be null");
+    }
+
+    public MigrationManager build()
+    {
+        return new MigrationManagerImpl(client, lockPath, metaDataSerializer, executor, lockMax, sets);
+    }
+
+    public MigrationManagerBuilder withExecutor(Executor executor)
+    {
+        this.executor = Objects.requireNonNull(executor, "executor cannot be null");
+        return this;
+    }
+
+    public MigrationManagerBuilder withLockMax(Duration lockMax)
+    {
+        this.lockMax = Objects.requireNonNull(lockMax, "lockMax cannot be null");
+        return this;
+    }
+
+    public MigrationManagerBuilder adding(MigrationSet set)
+    {
+        sets.add(Objects.requireNonNull(set, "set cannot be null"));
+        return this;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManagerImpl.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManagerImpl.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManagerImpl.java
new file mode 100644
index 0000000..15c61b2
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManagerImpl.java
@@ -0,0 +1,181 @@
+/**
+ * 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.curator.x.async.modeled.migrations;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import org.apache.curator.framework.api.transaction.CuratorOp;
+import org.apache.curator.framework.recipes.locks.InterProcessLock;
+import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex;
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.modeled.ModelSerializer;
+import org.apache.curator.x.async.modeled.ModelSpec;
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.ZNode;
+import org.apache.curator.x.async.modeled.ZPath;
+import org.apache.zookeeper.CreateMode;
+import java.time.Duration;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import static org.apache.curator.x.async.AsyncWrappers.*;
+
+class MigrationManagerImpl implements MigrationManager
+{
+    private final AsyncCuratorFramework client;
+    private final ZPath lockPath;
+    private final ModelSerializer<MetaData> metaDataSerializer;
+    private final Executor executor;
+    private final Duration lockMax;
+    private final List<MigrationSet> sets;
+
+    private static final String META_DATA_NODE_NAME = "meta-";
+
+    MigrationManagerImpl(AsyncCuratorFramework client, ZPath lockPath, ModelSerializer<MetaData> metaDataSerializer, Executor executor, Duration lockMax, List<MigrationSet> sets)
+    {
+        this.client = client;
+        this.lockPath = lockPath;
+        this.metaDataSerializer = metaDataSerializer;
+        this.executor = executor;
+        this.lockMax = lockMax;
+        this.sets = ImmutableList.copyOf(sets);
+    }
+
+    @Override
+    public CompletionStage<List<MetaData>> metaData(ZPath metaDataPath)
+    {
+        ModeledFramework<MetaData> modeled = getMetaDataClient(metaDataPath);
+        return ZNode.models(modeled.childrenAsZNodes());
+    }
+
+    @Override
+    public CompletionStage<Void> run()
+    {
+        Map<String, CompletableFuture<Void>> futures = sets
+            .stream()
+            .map(m -> new AbstractMap.SimpleEntry<>(m.id(), runMigration(m).toCompletableFuture()))
+            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+        return CompletableFuture.allOf(futures.values().toArray(new CompletableFuture[futures.size()]));
+    }
+
+    private CompletionStage<Void> runMigration(MigrationSet set)
+    {
+        String lockPath = this.lockPath.child(set.id()).fullPath();
+        InterProcessLock lock = new InterProcessSemaphoreMutex(client.unwrap(), lockPath);
+        CompletionStage<Void> lockStage = lockAsync(lock, lockMax.toMillis(), TimeUnit.MILLISECONDS, executor);
+        return lockStage.thenCompose(__ -> runMigrationInLock(lock, set));
+    }
+
+    private CompletionStage<Void> runMigrationInLock(InterProcessLock lock, MigrationSet set)
+    {
+        ModeledFramework<MetaData> modeled = getMetaDataClient(set.metaDataPath());
+        return modeled.childrenAsZNodes()
+            .thenCompose(metaData -> applyMetaData(set, modeled, metaData))
+            .handle((v, e) -> {
+                release(lock, true);
+                if ( e != null )
+                {
+                    Throwables.propagate(e);
+                }
+                return v;
+            }
+        );
+    }
+
+    private ModeledFramework<MetaData> getMetaDataClient(ZPath metaDataPath)
+    {
+        ModelSpec<MetaData> modelSpec = ModelSpec.builder(metaDataPath, metaDataSerializer).withCreateMode(CreateMode.PERSISTENT_SEQUENTIAL).build();
+        return ModeledFramework.wrap(client, modelSpec);
+    }
+
+    protected void checkIsValid(MigrationSet set, List<MetaData> sortedMetaData) throws InvalidMigrationSetException
+    {
+        if ( sortedMetaData.size() > set.migrations().size() )
+        {
+            throw new InvalidMigrationSetException(set.id(), String.format("More metadata than migrations. Migration ID: %s - MetaData: %s", set.id(), sortedMetaData));
+        }
+
+        int compareSize = Math.min(set.migrations().size(), sortedMetaData.size());
+        List<MetaData> compareMigrations = set.migrations().subList(0, compareSize)
+            .stream()
+            .map(m -> new MetaData(m.id(), m.version()))
+            .collect(Collectors.toList());
+        if ( !compareMigrations.equals(sortedMetaData) )
+        {
+            throw new InvalidMigrationSetException(set.id(), String.format("Metadata mismatch. Migration ID: %s - MetaData: %s", set.id(), sortedMetaData));
+        }
+    }
+
+    private CompletionStage<Void> applyMetaData(MigrationSet set, ModeledFramework<MetaData> metaDataClient, List<ZNode<MetaData>> metaDataNodes)
+    {
+        List<MetaData> sortedMetaData = metaDataNodes
+            .stream()
+            .sorted(Comparator.comparing(m -> m.path().fullPath()))
+            .map(ZNode::model)
+            .collect(Collectors.toList());
+        try
+        {
+            checkIsValid(set, sortedMetaData);
+        }
+        catch ( InvalidMigrationSetException e )
+        {
+            CompletableFuture<Void> future = new CompletableFuture<>();
+            future.completeExceptionally(e);
+            return future;
+        }
+
+        List<Migration> toBeApplied = set.migrations().subList(sortedMetaData.size(), set.migrations().size());
+        if ( toBeApplied.size() == 0 )
+        {
+            return CompletableFuture.completedFuture(null);
+        }
+
+        return asyncEnsureContainers(client, metaDataClient.modelSpec().path())
+            .thenCompose(__ -> applyMetaDataAfterEnsure(set, toBeApplied, metaDataClient));
+    }
+
+    private CompletionStage<Void> applyMetaDataAfterEnsure(MigrationSet set, List<Migration> toBeApplied, ModeledFramework<MetaData> metaDataClient)
+    {
+        ModelSpec<byte[]> modelSpec = ModelSpec.builder(set.path(), ModelSerializer.raw).build();
+        ModeledFramework<byte[]> modeled = ModeledFramework.wrap(client, modelSpec);
+        return modeled.childrenAsZNodes().thenCompose(nodes -> {
+            List<CuratorOp> operations = new ArrayList<>();
+            for ( ZNode<byte[]> node : nodes )
+            {
+                byte[] currentBytes = node.model();
+                for ( Migration migration : toBeApplied )
+                {
+                    currentBytes = migration.migrate(currentBytes);
+                    MetaData thisMetaData = new MetaData(migration.id(), migration.version());
+                    operations.add(metaDataClient.child(META_DATA_NODE_NAME).createOp(thisMetaData));
+                }
+                operations.add(modeled.child(node.path().nodeName()).updateOp(currentBytes, node.stat().getVersion()));
+            }
+            return client.transaction().forOperations(operations).thenApply(__ -> null);
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationSet.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationSet.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationSet.java
new file mode 100644
index 0000000..9e41989
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationSet.java
@@ -0,0 +1,69 @@
+/**
+ * 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.curator.x.async.modeled.migrations;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.curator.x.async.modeled.ZPath;
+import java.util.List;
+import java.util.Objects;
+
+public interface MigrationSet
+{
+    String id();
+
+    ZPath path();
+
+    ZPath metaDataPath();
+
+    List<Migration> migrations();
+
+    static MigrationSet build(String id, ZPath path, ZPath metaDataPath, List<Migration> migrations)
+    {
+        Objects.requireNonNull(id, "id cannot be null");
+        Objects.requireNonNull(path, "path cannot be null");
+        Objects.requireNonNull(metaDataPath, "metaDataPath cannot be null");
+        final List<Migration> migrationsCopy = ImmutableList.copyOf(migrations);
+        return new MigrationSet()
+        {
+            @Override
+            public String id()
+            {
+                return id;
+            }
+
+            @Override
+            public ZPath path()
+            {
+                return path;
+            }
+
+            @Override
+            public ZPath metaDataPath()
+            {
+                return metaDataPath;
+            }
+
+            @Override
+            public List<Migration> migrations()
+            {
+                return migrationsCopy;
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/Result.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/Result.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/Result.java
new file mode 100644
index 0000000..4fcfeac
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/Result.java
@@ -0,0 +1,23 @@
+/**
+ * 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.curator.x.async.modeled.migrations;
+
+public interface Result
+{
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/test/java/org/apache/curator/x/async/CompletableBaseClassForTests.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/test/java/org/apache/curator/x/async/CompletableBaseClassForTests.java b/curator-x-async/src/test/java/org/apache/curator/x/async/CompletableBaseClassForTests.java
index 232d301..4a964b1 100644
--- a/curator-x-async/src/test/java/org/apache/curator/x/async/CompletableBaseClassForTests.java
+++ b/curator-x-async/src/test/java/org/apache/curator/x/async/CompletableBaseClassForTests.java
@@ -18,6 +18,7 @@
  */
 package org.apache.curator.x.async;
 
+import com.google.common.base.Throwables;
 import org.apache.curator.test.BaseClassForTests;
 import org.apache.curator.test.Timing;
 import org.testng.Assert;
@@ -33,7 +34,12 @@ public abstract class CompletableBaseClassForTests extends BaseClassForTests
 
     protected <T, U> void complete(CompletionStage<T> stage)
     {
-        complete(stage, (v, e) -> {});
+        complete(stage, (v, e) -> {
+            if ( e != null )
+            {
+                Throwables.propagate(e);
+            }
+        });
     }
 
     protected <T, U> void complete(CompletionStage<T> stage, BiConsumer<? super T, Throwable> handler)

http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/TestMigrationManager.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/TestMigrationManager.java b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/TestMigrationManager.java
new file mode 100644
index 0000000..d709abe
--- /dev/null
+++ b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/TestMigrationManager.java
@@ -0,0 +1,129 @@
+/**
+ * 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.curator.x.async.modeled.migrations;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.framework.CuratorFrameworkFactory;
+import org.apache.curator.retry.RetryOneTime;
+import org.apache.curator.utils.CloseableUtils;
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.CompletableBaseClassForTests;
+import org.apache.curator.x.async.modeled.JacksonModelSerializer;
+import org.apache.curator.x.async.modeled.ModelSpec;
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.ZPath;
+import org.apache.curator.x.async.modeled.migrations.models.ModelV1;
+import org.apache.curator.x.async.modeled.migrations.models.ModelV2;
+import org.apache.curator.x.async.modeled.migrations.models.ModelV3;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.function.UnaryOperator;
+
+public class TestMigrationManager extends CompletableBaseClassForTests
+{
+    private AsyncCuratorFramework client;
+    private MigrationSet migrationSet;
+    private ModelSpec<ModelV1> v1Spec;
+    private ModelSpec<ModelV2> v2Spec;
+    private ModelSpec<ModelV3> v3Spec;
+
+    @BeforeMethod
+    @Override
+    public void setup() throws Exception
+    {
+        super.setup();
+
+        CuratorFramework client = CuratorFrameworkFactory.newClient(server.getConnectString(), timing.session(), timing.connection(), new RetryOneTime(100));
+        client.start();
+
+        this.client = AsyncCuratorFramework.wrap(client);
+
+        ObjectMapper mapper = new ObjectMapper();
+        UnaryOperator<byte[]> from1to2 = bytes -> {
+            try
+            {
+                ModelV1 v1 = mapper.readerFor(ModelV1.class).readValue(bytes);
+                ModelV2 v2 = new ModelV2(v1.getName(), 64);
+                return mapper.writeValueAsBytes(v2);
+            }
+            catch ( IOException e )
+            {
+                throw new RuntimeException(e);
+            }
+        };
+
+        UnaryOperator<byte[]> from2to3 = bytes -> {
+            try
+            {
+                ModelV2 v2 = mapper.readerFor(ModelV2.class).readValue(bytes);
+                String[] nameParts = v2.getName().split("\\s");
+                ModelV3 v3 = new ModelV3(nameParts[0], nameParts[1], v2.getAge());
+                return mapper.writeValueAsBytes(v3);
+            }
+            catch ( IOException e )
+            {
+                throw new RuntimeException(e);
+            }
+        };
+
+        ZPath modelPath = ZPath.parse("/test/it");
+
+        Migration m1 = Migration.build("1",1, from1to2);
+        Migration m2 = Migration.build("2",1, from2to3);
+        migrationSet = MigrationSet.build("1", modelPath, ZPath.parse("/metadata"), Arrays.asList(m1, m2));
+
+        v1Spec = ModelSpec.builder(modelPath, JacksonModelSerializer.build(ModelV1.class)).build();
+        v2Spec = ModelSpec.builder(modelPath, JacksonModelSerializer.build(ModelV2.class)).build();
+        v3Spec = ModelSpec.builder(modelPath, JacksonModelSerializer.build(ModelV3.class)).build();
+    }
+
+    @AfterMethod
+    @Override
+    public void teardown() throws Exception
+    {
+        CloseableUtils.closeQuietly(client.unwrap());
+        super.teardown();
+    }
+
+    @Test
+    public void testBasic() throws Exception
+    {
+        ModeledFramework<ModelV1> v1Client = ModeledFramework.wrap(client, v1Spec);
+        ModelV1 v1 = new ModelV1("John Galt");
+        complete(v1Client.child("1").set(v1));
+
+        MigrationManager manager = MigrationManager.builder(this.client, ZPath.parse("/locks"), JacksonModelSerializer.build(MetaData.class))
+            .adding(migrationSet)
+            .build();
+
+        complete(manager.run());
+
+        ModeledFramework<ModelV3> v3Client = ModeledFramework.wrap(client, v3Spec);
+        complete(v3Client.child("1").read(), (m, e) -> {
+            Assert.assertEquals(m.getAge(), 64);
+            Assert.assertEquals(m.getFirstName(), "John");
+            Assert.assertEquals(m.getLastName(), "Galt");
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV1.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV1.java b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV1.java
new file mode 100644
index 0000000..02b13b7
--- /dev/null
+++ b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV1.java
@@ -0,0 +1,39 @@
+/**
+ * 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.curator.x.async.modeled.migrations.models;
+
+public class ModelV1
+{
+    private final String name;
+
+    public ModelV1()
+    {
+        this("");
+    }
+
+    public ModelV1(String name)
+    {
+        this.name = name;
+    }
+
+    public String getName()
+    {
+        return name;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV2.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV2.java b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV2.java
new file mode 100644
index 0000000..bd77a2e
--- /dev/null
+++ b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV2.java
@@ -0,0 +1,46 @@
+/**
+ * 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.curator.x.async.modeled.migrations.models;
+
+public class ModelV2
+{
+    private final String name;
+    private final int age;
+
+    public ModelV2()
+    {
+        this("", 0);
+    }
+
+    public ModelV2(String name, int age)
+    {
+        this.name = name;
+        this.age = age;
+    }
+
+    public String getName()
+    {
+        return name;
+    }
+
+    public int getAge()
+    {
+        return age;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV3.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV3.java b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV3.java
new file mode 100644
index 0000000..d4713b8
--- /dev/null
+++ b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV3.java
@@ -0,0 +1,53 @@
+/**
+ * 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.curator.x.async.modeled.migrations.models;
+
+public class ModelV3
+{
+    private final String firstName;
+    private final String lastName;
+    private final int age;
+
+    public ModelV3()
+    {
+        this("", "", 0);
+    }
+
+    public ModelV3(String firstName, String lastName, int age)
+    {
+        this.firstName = firstName;
+        this.lastName = lastName;
+        this.age = age;
+    }
+
+    public String getFirstName()
+    {
+        return firstName;
+    }
+
+    public String getLastName()
+    {
+        return lastName;
+    }
+
+    public int getAge()
+    {
+        return age;
+    }
+}


[2/2] curator git commit: Merge branch 'CURATOR-423' into CURATOR-421

Posted by ra...@apache.org.
Merge branch 'CURATOR-423' into CURATOR-421


Project: http://git-wip-us.apache.org/repos/asf/curator/repo
Commit: http://git-wip-us.apache.org/repos/asf/curator/commit/1784a7c6
Tree: http://git-wip-us.apache.org/repos/asf/curator/tree/1784a7c6
Diff: http://git-wip-us.apache.org/repos/asf/curator/diff/1784a7c6

Branch: refs/heads/CURATOR-421
Commit: 1784a7c68982bae8bed471d92464168f87b0fceb
Parents: 4cd731c 4a0e022
Author: randgalt <ra...@apache.org>
Authored: Thu Jul 13 23:52:58 2017 -0500
Committer: randgalt <ra...@apache.org>
Committed: Thu Jul 13 23:52:58 2017 -0500

----------------------------------------------------------------------
 .../x/async/details/AsyncTransactionOpImpl.java |  4 ++--
 .../curator/x/async/TestBasicOperations.java    | 22 ++++++++++++++------
 2 files changed, 18 insertions(+), 8 deletions(-)
----------------------------------------------------------------------