HONEYCOMB-9: Exception handling for VPP APIs
authorMaros Marsalek <[email protected]>
Tue, 12 Apr 2016 08:13:02 +0000 (10:13 +0200)
committerMaros Marsalek <[email protected]>
Tue, 12 Apr 2016 08:13:02 +0000 (10:13 +0200)
Change-Id: Ic71a2ac3d01e88cb38596a24a12a7bf8ebf54da5
Signed-off-by: Marek Gradzki <[email protected]>
Signed-off-by: Maros Marsalek <[email protected]>
24 files changed:
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppConfigDataTree.java
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTree.java
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppReaderRegistry.java
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppWriterRegistry.java
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/ReadFailedException.java [new file with mode: 0644]
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/VppApiInvocationException.java
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/VppException.java
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/ChildVppReader.java
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/ListVppReader.java
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/ReaderRegistry.java
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/VppReader.java
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/AbstractCompositeVppReader.java
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/CompositeChildVppReader.java
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/CompositeListVppReader.java
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/CompositeRootVppReader.java
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/spi/RootVppReaderCustomizer.java
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/util/DelegatingReaderRegistry.java
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/w/VppWriter.java
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/w/WriterRegistry.java
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/w/util/DelegatingWriterRegistry.java
v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VPPConfigDataTreeTest.java
v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTreeTest.java
v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/trans/ReadFailedExceptionTest.java [new file with mode: 0644]
v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/trans/w/util/DelegatingWriterRegistryTest.java [new file with mode: 0644]

index f14bec3..9f34fcb 100644 (file)
@@ -118,15 +118,13 @@ public final class VppConfigDataTree implements VppDataTree {
             try {
                 e.revertChanges();
                 LOG.info("Changes successfully reverted");
-            } catch (VppException | RuntimeException e2) {
-                LOG.error("Failed to revert successful changes", e2);
+            } catch (WriterRegistry.Reverter.RevertFailedException revertFailedException) {
+                // fail with failed revert
+                LOG.error("Failed to revert successful changes", revertFailedException);
+                throw revertFailedException;
             }
 
-            // rethrow as we can't do anything more about it
-            // FIXME we need to throw a different kind of exception here to differentiate between:
-            // fail with success revert
-            // fail with failed revert (this one needs to contain IDs of changes that were not reverted)
-            throw e;
+            throw e; // fail with success revert
         } catch (VppException e) {
             LOG.error("Error while processing data change (before={}, after={})", nodesBefore, nodesAfter, e);
             throw e;
index 3fddd10..d0acd05 100644 (file)
@@ -26,12 +26,12 @@ import com.google.common.collect.Collections2;
 import com.google.common.collect.Multimap;
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.common.util.concurrent.Futures;
+import io.fd.honeycomb.v3po.impl.trans.ReadFailedException;
 import io.fd.honeycomb.v3po.impl.trans.r.ReaderRegistry;
 import java.util.Collection;
 import java.util.Map;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
-import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
 import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
@@ -52,7 +52,6 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-
 /**
  * ReadableVppDataTree implementation for operational data.
  */
@@ -79,34 +78,44 @@ public final class VppOperationalDataTree implements ReadableVppDataTree {
     }
 
     @Override
-    public CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read(
+    public CheckedFuture<Optional<NormalizedNode<?, ?>>,
+            org.opendaylight.controller.md.sal.common.api.data.ReadFailedException> read(
             @Nonnull final YangInstanceIdentifier yangInstanceIdentifier) {
 
-        if (checkNotNull(yangInstanceIdentifier).equals(YangInstanceIdentifier.EMPTY)) {
-            LOG.debug("VppOperationalDataProxy.read(), yangInstanceIdentifier=ROOT");
-            return Futures.immediateCheckedFuture(Optional.<NormalizedNode<?, ?>>of(readRoot()));
+        try {
+            if (checkNotNull(yangInstanceIdentifier).equals(YangInstanceIdentifier.EMPTY)) {
+                return Futures.immediateCheckedFuture(readRoot());
+            } else {
+                return Futures.immediateCheckedFuture(readNode(yangInstanceIdentifier));
+            }
+        } catch (ReadFailedException e) {
+            return Futures.immediateFailedCheckedFuture(
+                    new org.opendaylight.controller.md.sal.common.api.data.ReadFailedException(
+                            "Failed to read VPP data", e));
         }
+    }
 
-        LOG.debug("VppOperationalDataProxy.read(), yangInstanceIdentifier={}", yangInstanceIdentifier);
+    private Optional<NormalizedNode<?, ?>> readNode(final YangInstanceIdentifier yangInstanceIdentifier)
+            throws ReadFailedException {
+        LOG.debug("VppOperationalDataTree.readNode(), yangInstanceIdentifier={}", yangInstanceIdentifier);
         final InstanceIdentifier<?> path = serializer.fromYangInstanceIdentifier(yangInstanceIdentifier);
         checkNotNull(path, "Invalid instance identifier %s. Cannot create BA equivalent.", yangInstanceIdentifier);
-        LOG.debug("VppOperationalDataProxy.read(), path={}", path);
+        LOG.debug("VppOperationalDataTree.readNode(), path={}", path);
 
-        final Optional<? extends DataObject> dataObject = readerRegistry.read(path);
+        final Optional<? extends DataObject> dataObject;
 
+        dataObject = readerRegistry.read(path);
         if (dataObject.isPresent()) {
             final NormalizedNode<?, ?> value = toNormalizedNodeFunction(path).apply(dataObject.get());
-            return Futures.immediateCheckedFuture(Optional.<NormalizedNode<?, ?>>fromNullable(value));
+            return Optional.<NormalizedNode<?, ?>>fromNullable(value);
+        } else {
+            return Optional.absent();
         }
-
-        return Futures.immediateCheckedFuture(Optional.<NormalizedNode<?, ?>>absent());
     }
 
-    private DataSchemaNode getSchemaNode(final @Nonnull YangInstanceIdentifier yangInstanceIdentifier) {
-        return globalContext.getDataChildByName(yangInstanceIdentifier.getLastPathArgument().getNodeType());
-    }
+    private Optional<NormalizedNode<?, ?>> readRoot() throws ReadFailedException {
+        LOG.debug("VppOperationalDataTree.readRoot()");
 
-    private NormalizedNode<?, ?> readRoot() {
         final DataContainerNodeAttrBuilder<YangInstanceIdentifier.NodeIdentifier, ContainerNode> dataNodeBuilder =
                 Builders.containerBuilder()
                         .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(SchemaContext.NAME));
@@ -121,16 +130,17 @@ public final class VppOperationalDataTree implements ReadableVppDataTree {
             dataNodeBuilder.withChild((DataContainerChild<?, ?>) node);
         }
 
-        return dataNodeBuilder.build();
+        return Optional.<NormalizedNode<?, ?>>of(dataNodeBuilder.build());
     }
 
     private NormalizedNode<?, ?> wrapDataObjects(final YangInstanceIdentifier yangInstanceIdentifier,
-                                              final InstanceIdentifier<? extends DataObject> instanceIdentifier,
-                                              final Collection<? extends DataObject> dataObjects) {
+                                                 final InstanceIdentifier<? extends DataObject> instanceIdentifier,
+                                                 final Collection<? extends DataObject> dataObjects) {
         final Collection<NormalizedNode<?, ?>> normalizedRootElements = Collections2
                 .transform(dataObjects, toNormalizedNodeFunction(instanceIdentifier));
 
-        final DataSchemaNode schemaNode = getSchemaNode(yangInstanceIdentifier);
+        final DataSchemaNode schemaNode =
+                globalContext.getDataChildByName(yangInstanceIdentifier.getLastPathArgument().getNodeType());
         if (schemaNode instanceof ListSchemaNode) {
             // In case of a list, wrap all the values in a Mixin parent node
             final ListSchemaNode listSchema = (ListSchemaNode) schemaNode;
@@ -142,19 +152,19 @@ public final class VppOperationalDataTree implements ReadableVppDataTree {
     }
 
     private static DataContainerChild<?, ?> wrapListIntoMixinNode(
-        final Collection<NormalizedNode<?, ?>> normalizedRootElements, final ListSchemaNode listSchema) {
+            final Collection<NormalizedNode<?, ?>> normalizedRootElements, final ListSchemaNode listSchema) {
         if (listSchema.getKeyDefinition().isEmpty()) {
             final CollectionNodeBuilder<UnkeyedListEntryNode, UnkeyedListNode> listBuilder =
-                Builders.unkeyedListBuilder();
+                    Builders.unkeyedListBuilder();
             for (NormalizedNode<?, ?> normalizedRootElement : normalizedRootElements) {
                 listBuilder.withChild((UnkeyedListEntryNode) normalizedRootElement);
             }
             return listBuilder.build();
         } else {
             final CollectionNodeBuilder<MapEntryNode, ? extends MapNode> listBuilder =
-                listSchema.isUserOrdered()
-                    ? Builders.orderedMapBuilder()
-                    : Builders.mapBuilder();
+                    listSchema.isUserOrdered()
+                            ? Builders.orderedMapBuilder()
+                            : Builders.mapBuilder();
 
             for (NormalizedNode<?, ?> normalizedRootElement : normalizedRootElements) {
                 listBuilder.withChild((MapEntryNode) normalizedRootElement);
@@ -168,11 +178,11 @@ public final class VppOperationalDataTree implements ReadableVppDataTree {
         return new Function<DataObject, NormalizedNode<?, ?>>() {
             @Override
             public NormalizedNode<?, ?> apply(@Nullable final DataObject dataObject) {
-                LOG.trace("VppOperationalDataProxy.toNormalizedNode(), path={}, dataObject={}", path, dataObject);
+                LOG.trace("VppOperationalDataTree.toNormalizedNode(), path={}, dataObject={}", path, dataObject);
                 final Map.Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> entry =
-                    serializer.toNormalizedNode(path, dataObject);
+                        serializer.toNormalizedNode(path, dataObject);
 
-                LOG.trace("VppOperationalDataProxy.toNormalizedNode(), normalizedNodeEntry={}", entry);
+                LOG.trace("VppOperationalDataTree.toNormalizedNode(), normalizedNodeEntry={}", entry);
                 return entry.getValue();
             }
         };
index df7b656..72d17b7 100644 (file)
@@ -18,6 +18,7 @@ package io.fd.honeycomb.v3po.impl.data;
 
 import com.google.common.base.Optional;
 import com.google.common.collect.Multimap;
+import io.fd.honeycomb.v3po.impl.trans.ReadFailedException;
 import io.fd.honeycomb.v3po.impl.trans.r.ChildVppReader;
 import io.fd.honeycomb.v3po.impl.trans.r.ReaderRegistry;
 import io.fd.honeycomb.v3po.impl.trans.r.VppReader;
@@ -95,13 +96,15 @@ public class VppReaderRegistry implements ReaderRegistry {
 
     @Nonnull
     @Override
-    public Multimap<InstanceIdentifier<? extends DataObject>, ? extends DataObject> readAll() {
+    public Multimap<InstanceIdentifier<? extends DataObject>, ? extends DataObject> readAll()
+            throws io.fd.honeycomb.v3po.impl.trans.ReadFailedException {
         return reader.readAll();
     }
 
     @Nonnull
     @Override
-    public Optional<? extends DataObject> read(@Nonnull final InstanceIdentifier<? extends DataObject> id) {
+    public Optional<? extends DataObject> read(@Nonnull final InstanceIdentifier<? extends DataObject> id)
+            throws ReadFailedException {
         return reader.read(id);
     }
 
index 83186c2..defcca4 100644 (file)
@@ -98,7 +98,7 @@ public class VppWriterRegistry implements WriterRegistry {
     public void update(@Nonnull final Map<InstanceIdentifier<?>, DataObject> dataBefore,
                        @Nonnull final Map<InstanceIdentifier<?>, DataObject> dataAfter,
                        @Nonnull final WriteContext ctx)
-        throws VppException, BulkUpdateException {
+        throws VppException {
         writer.update(dataBefore, dataAfter, ctx);
     }
 }
diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/ReadFailedException.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/ReadFailedException.java
new file mode 100644 (file)
index 0000000..4da8b0e
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed 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 io.fd.honeycomb.v3po.impl.trans;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Thrown when Vpp reader or customizer is not able to read data for the given id.
+ */
+public class ReadFailedException extends VppException {
+
+    private final InstanceIdentifier<?> failedId;
+
+    /**
+     * Constructs an ReadFailedException given data id and exception cause.
+     *
+     * @param failedId instance identifier of the data object that could not be read
+     * @param cause              the cause of read failure
+     */
+    public ReadFailedException(@Nonnull final InstanceIdentifier<?> failedId, final Throwable cause) {
+        super("Failed to read " + failedId, cause);
+        this.failedId = checkNotNull(failedId, "failedId should not be null");
+    }
+
+    /**
+     * Constructs an ReadFailedException given data id.
+     *
+     * @param failedId instance identifier of the data object that could not be read
+     */
+    public ReadFailedException(@Nonnull final InstanceIdentifier<?> failedId) {
+        this(failedId, null);
+    }
+
+    /**
+     * Returns id of the data object that could not be read.
+     *
+     * @return data object instance identifier
+     */
+    @Nonnull
+    public InstanceIdentifier<?> getFailedId() {
+        return failedId;
+    }
+}
index 0bb7c2b..042c627 100644 (file)
@@ -21,7 +21,7 @@ import com.google.common.base.Preconditions;
 import javax.annotation.Nonnull;
 
 /**
- * Throws when Vpp jAPI method invocation failed.
+ * Thrown when Vpp jAPI method invocation failed.
  */
 @Beta
 public class VppApiInvocationException extends VppException {
index aa18ae7..aadeaa9 100644 (file)
@@ -19,7 +19,7 @@ package io.fd.honeycomb.v3po.impl.trans;
 import com.google.common.annotations.Beta;
 
 /**
- * Base exception for Vpp writers
+ * Base exception for Vpp translation layer
  */
 @Beta
 public class VppException extends Exception {
@@ -28,11 +28,11 @@ public class VppException extends Exception {
         super(s);
     }
 
-    public VppException(final String s, final Throwable throwable) {
-        super(s, throwable);
+    public VppException(final String s, final Throwable cause) {
+        super(s, cause);
     }
 
-    public VppException(final Throwable throwable) {
-        super(throwable);
+    public VppException(final Throwable cause) {
+        super(cause);
     }
 }
index 7f4d6fc..aad3080 100644 (file)
@@ -17,6 +17,7 @@
 package io.fd.honeycomb.v3po.impl.trans.r;
 
 import com.google.common.annotations.Beta;
+import io.fd.honeycomb.v3po.impl.trans.ReadFailedException;
 import javax.annotation.Nonnull;
 import org.opendaylight.yangtools.concepts.Builder;
 import org.opendaylight.yangtools.yang.binding.DataObject;
@@ -38,9 +39,10 @@ public interface ChildVppReader<C extends DataObject> extends VppReader<C> {
      *                      determine the exact position within more complex subtrees.
      * @param parentBuilder Builder of parent DataObject. Objects read on this level (if any) must be placed into the
      *                      parent builder.
+     * @throws ReadFailedException if read was unsuccessful
      */
     void read(@Nonnull final InstanceIdentifier<? extends DataObject> id,
-              @Nonnull final Builder<? extends DataObject> parentBuilder);
+              @Nonnull final Builder<? extends DataObject> parentBuilder) throws ReadFailedException;
 
 }
 
index 8a7f29c..ce392c6 100644 (file)
@@ -17,6 +17,7 @@
 package io.fd.honeycomb.v3po.impl.trans.r;
 
 import com.google.common.annotations.Beta;
+import io.fd.honeycomb.v3po.impl.trans.ReadFailedException;
 import java.util.List;
 import javax.annotation.Nonnull;
 import org.opendaylight.yangtools.yang.binding.DataObject;
@@ -38,7 +39,8 @@ public interface ListVppReader<D extends DataObject & Identifiable<K>, K extends
      * @param id Wildcarded identifier of list managed by this reader
      *
      * @return List of all entries in this list
+     * @throws ReadFailedException if read was unsuccessful
      */
     @Nonnull
-    List<D> readList(@Nonnull final InstanceIdentifier<D> id);
+    List<D> readList(@Nonnull final InstanceIdentifier<D> id) throws ReadFailedException;
 }
index 8c592a6..6a99376 100644 (file)
@@ -18,6 +18,7 @@ package io.fd.honeycomb.v3po.impl.trans.r;
 
 import com.google.common.annotations.Beta;
 import com.google.common.collect.Multimap;
+import io.fd.honeycomb.v3po.impl.trans.ReadFailedException;
 import javax.annotation.Nonnull;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
@@ -29,11 +30,13 @@ import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 public interface ReaderRegistry extends VppReader<DataObject> {
 
     /**
-     * Performs read on all registered root readers and merges the results into a Multimap.
-     * Keys represent identifiers for root DataObjects from the data tree modeled by YANG.
+     * Performs read on all registered root readers and merges the results into a Multimap. Keys represent identifiers
+     * for root DataObjects from the data tree modeled by YANG.
      *
      * @return multimap that preserves deterministic iteration order across non-distinct key values
+     * @throws ReadFailedException if read was unsuccessful
      */
     @Nonnull
-    Multimap<InstanceIdentifier<? extends DataObject>, ? extends DataObject> readAll();
+    Multimap<InstanceIdentifier<? extends DataObject>, ? extends DataObject> readAll()
+            throws ReadFailedException;
 }
index 1cc76a3..02189e4 100644 (file)
@@ -18,6 +18,7 @@ package io.fd.honeycomb.v3po.impl.trans.r;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.Optional;
+import io.fd.honeycomb.v3po.impl.trans.ReadFailedException;
 import io.fd.honeycomb.v3po.impl.trans.SubtreeManager;
 import javax.annotation.Nonnull;
 import org.opendaylight.yangtools.yang.binding.DataObject;
@@ -42,8 +43,10 @@ public interface VppReader<D extends DataObject> extends SubtreeManager<D> {
      *           identifiers pointing below node managed by this reader, it's reader's responsibility to filter out the
      *           right node or to delegate the read to a child reader.
      * @return List of DataObjects identified by id. If the ID points to a single node, it will be wrapped in a list
+     * @throws ReadFailedException if read was unsuccessful
      */
     @Nonnull
-    Optional<? extends DataObject> read(@Nonnull final InstanceIdentifier<? extends DataObject> id);
+    Optional<? extends DataObject> read(@Nonnull final InstanceIdentifier<? extends DataObject> id) throws
+            ReadFailedException;
 
 }
index 61f318b..061cfc9 100644 (file)
@@ -22,6 +22,7 @@ import com.google.common.annotations.Beta;
 import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
+import io.fd.honeycomb.v3po.impl.trans.ReadFailedException;
 import io.fd.honeycomb.v3po.impl.trans.r.ChildVppReader;
 import io.fd.honeycomb.v3po.impl.trans.r.VppReader;
 import io.fd.honeycomb.v3po.impl.trans.util.ReflectionUtils;
@@ -68,7 +69,8 @@ abstract class AbstractCompositeVppReader<D extends DataObject, B extends Builde
     /**
      * @param id {@link InstanceIdentifier} pointing to current node. In case of keyed list, key must be present.
      */
-    protected Optional<D> readCurrent(final InstanceIdentifier<D> id) {
+    protected Optional<D> readCurrent(final InstanceIdentifier<D> id) throws
+            ReadFailedException {
         LOG.debug("{}: Reading current: {}", this, id);
         final B builder = getBuilder(id);
         // Cache empty value to determine if anything has changed later TODO cache in a field
@@ -101,7 +103,8 @@ abstract class AbstractCompositeVppReader<D extends DataObject, B extends Builde
     @Nonnull
     @Override
     @SuppressWarnings("unchecked")
-    public Optional<? extends DataObject> read(@Nonnull final InstanceIdentifier<? extends DataObject> id) {
+    public Optional<? extends DataObject> read(@Nonnull final InstanceIdentifier<? extends DataObject> id)
+            throws ReadFailedException {
         LOG.trace("{}: Reading : {}", this, id);
         if (id.getTargetType().equals(getManagedDataObjectType().getTargetType())) {
             return readCurrent((InstanceIdentifier<D>) id);
@@ -110,7 +113,8 @@ abstract class AbstractCompositeVppReader<D extends DataObject, B extends Builde
         }
     }
 
-    private Optional<? extends DataObject> readSubtree(final InstanceIdentifier<? extends DataObject> id) {
+    private Optional<? extends DataObject> readSubtree(final InstanceIdentifier<? extends DataObject> id)
+            throws ReadFailedException {
         LOG.debug("{}: Reading subtree: {}", this, id);
         final Class<? extends DataObject> next = VppRWUtils.getNextId(id, getManagedDataObjectType()).getType();
         final ChildVppReader<? extends ChildOf<D>> vppReader = childReaders.get(next);
@@ -139,7 +143,8 @@ abstract class AbstractCompositeVppReader<D extends DataObject, B extends Builde
      * @param id {@link InstanceIdentifier} pointing to current node. In case of keyed list, key must be present.
      * @param builder Builder object for current node where the read attributes must be placed
      */
-    protected abstract void readCurrentAttributes(final InstanceIdentifier<D> id, B builder);
+    protected abstract void readCurrentAttributes(final InstanceIdentifier<D> id, B builder) throws
+            ReadFailedException;
 
     /**
      * Return new instance of a builder object for current node
index a64a72b..f18a5b3 100644 (file)
@@ -18,6 +18,7 @@ package io.fd.honeycomb.v3po.impl.trans.r.impl;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.Optional;
+import io.fd.honeycomb.v3po.impl.trans.ReadFailedException;
 import io.fd.honeycomb.v3po.impl.trans.r.ChildVppReader;
 import io.fd.honeycomb.v3po.impl.trans.r.impl.spi.ChildVppReaderCustomizer;
 import io.fd.honeycomb.v3po.impl.trans.util.VppRWUtils;
@@ -78,7 +79,7 @@ public final class CompositeChildVppReader<C extends DataObject, B extends Build
 
     @Override
     public final void read(@Nonnull final InstanceIdentifier<? extends DataObject> parentId,
-                           @Nonnull final Builder<? extends DataObject> parentBuilder) {
+                           @Nonnull final Builder<? extends DataObject> parentBuilder) throws ReadFailedException {
         final Optional<C> read = readCurrent(VppRWUtils.appendTypeToId(parentId, getManagedDataObjectType()));
 
         if(read.isPresent()) {
@@ -87,7 +88,8 @@ public final class CompositeChildVppReader<C extends DataObject, B extends Build
     }
 
     @Override
-    protected void readCurrentAttributes(@Nonnull final InstanceIdentifier<C> id, @Nonnull final B builder) {
+    protected void readCurrentAttributes(@Nonnull final InstanceIdentifier<C> id, @Nonnull final B builder)
+            throws ReadFailedException {
         customizer.readCurrentAttributes(id, builder);
     }
 
index 7e3d845..a9ca3e7 100644 (file)
@@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkArgument;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.Optional;
+import io.fd.honeycomb.v3po.impl.trans.ReadFailedException;
 import io.fd.honeycomb.v3po.impl.trans.r.ChildVppReader;
 import io.fd.honeycomb.v3po.impl.trans.r.ListVppReader;
 import io.fd.honeycomb.v3po.impl.trans.r.impl.spi.ListVppReaderCustomizer;
@@ -39,17 +40,16 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * Composite implementation of {@link ChildVppReader} able to place the read result into
- * parent builder object intended for list node type.
+ * Composite implementation of {@link ChildVppReader} able to place the read result into parent builder object intended
+ * for list node type.
  *
- * This reader checks if the IDs are wildcarded in which case it performs read of all
- * list entries. In case the ID has a key, it reads only the specified value.
+ * This reader checks if the IDs are wildcarded in which case it performs read of all list entries. In case the ID has a
+ * key, it reads only the specified value.
  */
 @Beta
 @ThreadSafe
 public final class CompositeListVppReader<C extends DataObject & Identifiable<K>, K extends Identifier<C>, B extends Builder<C>>
-    extends AbstractCompositeVppReader<C, B> implements ChildVppReader<C>, ListVppReader<C, K>
-{
+        extends AbstractCompositeVppReader<C, B> implements ChildVppReader<C>, ListVppReader<C, K> {
 
     private static final Logger LOG = LoggerFactory.getLogger(CompositeListVppReader.class);
 
@@ -59,10 +59,9 @@ public final class CompositeListVppReader<C extends DataObject & Identifiable<K>
      * Create new {@link CompositeListVppReader}
      *
      * @param managedDataObjectType Class object for managed data type. Must come from a list node type.
-     * @param childReaders Child nodes(container, list) readers
-     * @param augReaders Child augmentations readers
-     * @param customizer Customizer instance to customize this generic reader
-     *
+     * @param childReaders          Child nodes(container, list) readers
+     * @param augReaders            Child augmentations readers
+     * @param customizer            Customizer instance to customize this generic reader
      */
     public CompositeListVppReader(@Nonnull final Class<C> managedDataObjectType,
                                   @Nonnull final List<ChildVppReader<? extends ChildOf<C>>> childReaders,
@@ -87,12 +86,12 @@ public final class CompositeListVppReader<C extends DataObject & Identifiable<K>
     public CompositeListVppReader(@Nonnull final Class<C> managedDataObjectType,
                                   @Nonnull final ListVppReaderCustomizer<C, K, B> customizer) {
         this(managedDataObjectType, VppRWUtils.<C>emptyChildReaderList(), VppRWUtils.<C>emptyAugReaderList(),
-            customizer);
+                customizer);
     }
 
     @Override
     public void read(@Nonnull final InstanceIdentifier<? extends DataObject> id,
-                     @Nonnull final Builder<? extends DataObject> parentBuilder) {
+                     @Nonnull final Builder<? extends DataObject> parentBuilder) throws ReadFailedException {
         // Create ID pointing to current node
         final InstanceIdentifier<C> currentId = VppRWUtils.appendTypeToId(id, getManagedDataObjectType());
         // Read all, since current ID is definitely wildcarded
@@ -102,7 +101,7 @@ public final class CompositeListVppReader<C extends DataObject & Identifiable<K>
 
     @Override
     @Nonnull
-    public List<C> readList(@Nonnull final InstanceIdentifier<C> id) {
+    public List<C> readList(@Nonnull final InstanceIdentifier<C> id) throws ReadFailedException {
         LOG.trace("{}: Reading all list entries", this);
         final List<K> allIds = customizer.getAllIds(id);
         LOG.debug("{}: Reading list entries for: {}", this, allIds);
@@ -110,7 +109,7 @@ public final class CompositeListVppReader<C extends DataObject & Identifiable<K>
         final ArrayList<C> allEntries = new ArrayList<>(allIds.size());
         for (K key : allIds) {
             final InstanceIdentifier.IdentifiableItem<C, K> currentBdItem =
-                VppRWUtils.getCurrentIdItem(id, key);
+                    VppRWUtils.getCurrentIdItem(id, key);
             final InstanceIdentifier<C> keyedId = VppRWUtils.replaceLastInId(id, currentBdItem);
             final Optional<C> read = readCurrent(keyedId);
             final DataObject singleItem = read.get();
@@ -121,7 +120,8 @@ public final class CompositeListVppReader<C extends DataObject & Identifiable<K>
     }
 
     @Override
-    protected void readCurrentAttributes(@Nonnull final InstanceIdentifier<C> id, @Nonnull final B builder) {
+    protected void readCurrentAttributes(@Nonnull final InstanceIdentifier<C> id, @Nonnull final B builder)
+            throws ReadFailedException {
         customizer.readCurrentAttributes(id, builder);
     }
 
index c87600b..d5d82e7 100644 (file)
@@ -17,6 +17,7 @@
 package io.fd.honeycomb.v3po.impl.trans.r.impl;
 
 import com.google.common.annotations.Beta;
+import io.fd.honeycomb.v3po.impl.trans.ReadFailedException;
 import io.fd.honeycomb.v3po.impl.trans.r.ChildVppReader;
 import io.fd.honeycomb.v3po.impl.trans.r.VppReader;
 import io.fd.honeycomb.v3po.impl.trans.r.impl.spi.RootVppReaderCustomizer;
@@ -76,7 +77,8 @@ public final class CompositeRootVppReader<C extends DataObject, B extends Builde
     }
 
     @Override
-    protected void readCurrentAttributes(@Nonnull final InstanceIdentifier<C> id, @Nonnull final B builder) {
+    protected void readCurrentAttributes(@Nonnull final InstanceIdentifier<C> id, @Nonnull final B builder)
+            throws ReadFailedException {
         customizer.readCurrentAttributes(id, builder);
     }
 
index a09ed04..299e943 100644 (file)
@@ -17,6 +17,7 @@
 package io.fd.honeycomb.v3po.impl.trans.r.impl.spi;
 
 import com.google.common.annotations.Beta;
+import io.fd.honeycomb.v3po.impl.trans.ReadFailedException;
 import javax.annotation.Nonnull;
 import org.opendaylight.yangtools.concepts.Builder;
 import org.opendaylight.yangtools.yang.binding.DataObject;
@@ -31,16 +32,20 @@ import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 @Beta
 public interface RootVppReaderCustomizer<C extends DataObject, B extends Builder<C>> {
 
-    // TODO add (un)checked, well defined exception here to indicate issues in the customizer
-
     /**
-     * Create new builder that will be used to build read value
+     * Creates new builder that will be used to build read value.
      */
     @Nonnull
     B getBuilder(@Nonnull final InstanceIdentifier<C> id);
 
+
     /**
-     * Add current data (identified by id) to the provided builder
+     * Adds current data (identified by id) to the provided builder.
+     *
+     * @param id      id of current data object
+     * @param builder builder for creating read value
+     * @throws ReadFailedException if read was unsuccessful
      */
-    void readCurrentAttributes(@Nonnull final InstanceIdentifier<C> id, @Nonnull final B builder);
+    void readCurrentAttributes(@Nonnull final InstanceIdentifier<C> id, @Nonnull final B builder) throws
+            ReadFailedException;
 }
index 0777425..2052407 100644 (file)
@@ -22,6 +22,7 @@ import com.google.common.base.Optional;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.LinkedListMultimap;
 import com.google.common.collect.Multimap;
+import io.fd.honeycomb.v3po.impl.trans.ReadFailedException;
 import io.fd.honeycomb.v3po.impl.trans.r.ListVppReader;
 import io.fd.honeycomb.v3po.impl.trans.r.ReaderRegistry;
 import io.fd.honeycomb.v3po.impl.trans.r.VppReader;
@@ -36,8 +37,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * Simple reader registry able to perform and aggregated read (ROOT read) on top of all
- * provided readers. Also able to delegate a specific read to one of the delegate readers.
+ * Simple reader registry able to perform and aggregated read (ROOT read) on top of all provided readers. Also able to
+ * delegate a specific read to one of the delegate readers.
  *
  * This could serve as a utility to hold & hide all available readers in upper layers.
  */
@@ -58,7 +59,8 @@ public final class DelegatingReaderRegistry implements ReaderRegistry {
 
     @Override
     @Nonnull
-    public Multimap<InstanceIdentifier<? extends DataObject>, ? extends DataObject> readAll() {
+    public Multimap<InstanceIdentifier<? extends DataObject>, ? extends DataObject> readAll()
+            throws ReadFailedException {
         LOG.debug("Reading from all delegates: {}", this);
         LOG.trace("Reading from all delegates: {}", rootReaders.values());
 
@@ -66,15 +68,15 @@ public final class DelegatingReaderRegistry implements ReaderRegistry {
         for (VppReader<? extends DataObject> rootReader : rootReaders.values()) {
             LOG.debug("Reading from delegate: {}", rootReader);
 
-            if(rootReader instanceof ListVppReader) {
+            if (rootReader instanceof ListVppReader) {
                 final List<? extends DataObject> listEntries =
-                    ((ListVppReader) rootReader).readList(rootReader.getManagedDataObjectType());
-                if(!listEntries.isEmpty()) {
+                        ((ListVppReader) rootReader).readList(rootReader.getManagedDataObjectType());
+                if (!listEntries.isEmpty()) {
                     objects.putAll(rootReader.getManagedDataObjectType(), listEntries);
                 }
             } else {
                 final Optional<? extends DataObject> read = rootReader.read(rootReader.getManagedDataObjectType());
-                if(read.isPresent()) {
+                if (read.isPresent()) {
                     objects.putAll(rootReader.getManagedDataObjectType(), Collections.singletonList(read.get()));
                 }
             }
@@ -85,12 +87,13 @@ public final class DelegatingReaderRegistry implements ReaderRegistry {
 
     @Nonnull
     @Override
-    public Optional<? extends DataObject> read(@Nonnull final InstanceIdentifier<? extends DataObject> id) {
+    public Optional<? extends DataObject> read(@Nonnull final InstanceIdentifier<? extends DataObject> id)
+            throws ReadFailedException {
         final InstanceIdentifier.PathArgument first = checkNotNull(
-            Iterables.getFirst(id.getPathArguments(), null), "Empty id");
+                Iterables.getFirst(id.getPathArguments(), null), "Empty id");
         final VppReader<? extends DataObject> vppReader = rootReaders.get(first.getType());
         checkNotNull(vppReader,
-            "Unable to read %s. Missing reader. Current readers for: %s", id, rootReaders.keySet());
+                "Unable to read %s. Missing reader. Current readers for: %s", id, rootReaders.keySet());
         LOG.debug("Reading from delegate: {}", vppReader);
         return vppReader.read(id);
     }
index f8a49a2..d338faf 100644 (file)
@@ -25,7 +25,8 @@ import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 
 /**
- * Base VPP writer, responsible for translation between DataObjects and VPP APIs. Handling all update operations(create, update, delete)
+ * Base VPP writer, responsible for translation between DataObjects and VPP APIs. Handling all update operations(create,
+ * update, delete)
  *
  * @param <D> Specific DataObject derived type, that is handled by this writer
  */
@@ -35,10 +36,11 @@ public interface VppWriter<D extends DataObject> extends SubtreeManager<D> {
     /**
      * Handle update operation. U from CRUD.
      *
-     * @param id Identifier(from root) of data being written
+     * @param id         Identifier(from root) of data being written
      * @param dataBefore Old data
-     * @param dataAfter New, updated data
-     * @param ctx Write context enabling writer to get information about candidate data as well as current data
+     * @param dataAfter  New, updated data
+     * @param ctx        Write context enabling writer to get information about candidate data as well as current data
+     * @throws VppException if update failed
      */
     void update(@Nonnull final InstanceIdentifier<? extends DataObject> id,
                 @Nullable final DataObject dataBefore,
index 4b09ff2..26c294b 100644 (file)
 
 package io.fd.honeycomb.v3po.impl.trans.w;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableList;
 import io.fd.honeycomb.v3po.impl.trans.VppException;
+import java.util.List;
 import java.util.Map;
 import javax.annotation.Nonnull;
 import org.opendaylight.yangtools.yang.binding.DataObject;
@@ -33,23 +37,51 @@ public interface WriterRegistry extends VppWriter<DataObject> {
      * Performs bulk update
      *
      * @throws BulkUpdateException in case bulk update fails
+     * @throws VppException        in case some other error occurs while processing update request
      */
     void update(@Nonnull final Map<InstanceIdentifier<?>, DataObject> dataBefore,
                 @Nonnull final Map<InstanceIdentifier<?>, DataObject> dataAfter,
-                @Nonnull final WriteContext ctx) throws VppException, BulkUpdateException;
+                @Nonnull final WriteContext ctx) throws VppException;
 
+    /**
+     * Thrown when bulk update failed.
+     */
     @Beta
-    public class BulkUpdateException extends VppException {
+    class BulkUpdateException extends VppException {
 
-        private final Revert runnable;
+        private final Reverter reverter;
+        private final InstanceIdentifier<?> failedId; // TODO change to VppDataModification
+
+        /**
+         * Constructs an BulkUpdateException.
+         *
+         * @param failedId instance identifier of the data object that caused bulk update to fail.
+         * @param cause    the cause of bulk update failure
+         */
+        public BulkUpdateException(@Nonnull final InstanceIdentifier<?> failedId, @Nonnull final Reverter reverter,
+                                   final Throwable cause) {
+            super("Bulk update failed at " + failedId, cause);
+            this.failedId = checkNotNull(failedId, "failedId should not be null");
+            this.reverter = checkNotNull(reverter, "reverter should not be null");
+        }
 
-        public BulkUpdateException(final InstanceIdentifier<?> id, final RuntimeException e, final Revert runnable) {
-            super("Bulk edit failed at " + id, e);
-            this.runnable = runnable;
+        /**
+         * Reverts changes that were successfully applied during bulk update before failure occurred.
+         *
+         * @throws Reverter.RevertFailedException if revert fails
+         */
+        public void revertChanges() throws Reverter.RevertFailedException {
+            reverter.revert();
         }
 
-        public void revertChanges() throws VppException {
-            runnable.revert();
+        /**
+         * Returns instance identifier of the data object that caused bulk update to fail.
+         *
+         * @return data object's instance identifier
+         */
+        @Nonnull
+        public InstanceIdentifier<?> getFailedId() {
+            return failedId;
         }
     }
 
@@ -57,8 +89,47 @@ public interface WriterRegistry extends VppWriter<DataObject> {
      * Abstraction over revert mechanism in cast of a bulk update failure
      */
     @Beta
-    public interface Revert {
+    interface Reverter {
 
-        public void revert() throws VppException;
+        /**
+         * Reverts changes that were successfully applied during bulk update before failure occurred. Changes are
+         * reverted in reverse order they were applied.
+         *
+         * @throws RevertFailedException if not all of applied changes were successfully reverted
+         */
+        void revert() throws RevertFailedException;
+
+        /**
+         * Thrown when some of the changes applied during bulk update were not reverted.
+         */
+        @Beta
+        class RevertFailedException extends VppException {
+
+            // TODO change to list of VppDataModifications to make debugging easier
+            private final List<InstanceIdentifier<?>> notRevertedChanges;
+
+            /**
+             * Constructs an RevertFailedException with the list of changes that were not reverted.
+             *
+             * @param notRevertedChanges list of changes that were not reverted
+             * @param cause              the cause of revert failure
+             */
+            public RevertFailedException(@Nonnull final List<InstanceIdentifier<?>> notRevertedChanges,
+                                         final Throwable cause) {
+                super(cause);
+                checkNotNull(notRevertedChanges, "notRevertedChanges should not be null");
+                this.notRevertedChanges = ImmutableList.copyOf(notRevertedChanges);
+            }
+
+            /**
+             * Returns the list of changes that were not reverted.
+             *
+             * @return list of changes that were not reverted
+             */
+            @Nonnull
+            public List<InstanceIdentifier<?>> getNotRevertedChanges() {
+                return notRevertedChanges;
+            }
+        }
     }
-}
+}
\ No newline at end of file
index d20e69a..cc1188e 100644 (file)
@@ -29,8 +29,8 @@ import io.fd.honeycomb.v3po.impl.trans.util.VppRWUtils;
 import io.fd.honeycomb.v3po.impl.trans.w.VppWriter;
 import io.fd.honeycomb.v3po.impl.trans.w.WriteContext;
 import io.fd.honeycomb.v3po.impl.trans.w.WriterRegistry;
+import java.util.LinkedList;
 import java.util.List;
-import java.util.ListIterator;
 import java.util.Map;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -40,8 +40,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * Simple writer registry able to perform and aggregated read (ROOT write) on top of all
- * provided writers. Also able to delegate a specific read to one of the delegate writers.
+ * Simple writer registry able to perform and aggregated read (ROOT write) on top of all provided writers. Also able to
+ * delegate a specific read to one of the delegate writers.
  *
  * This could serve as a utility to hold & hide all available writers in upper layers.
  */
@@ -50,12 +50,12 @@ public final class DelegatingWriterRegistry implements WriterRegistry {
     private static final Logger LOG = LoggerFactory.getLogger(DelegatingWriterRegistry.class);
 
     private static final Function<InstanceIdentifier<?>, Class<? extends DataObject>> ID_TO_CLASS =
-        new Function<InstanceIdentifier<?>, Class<? extends DataObject>>() {
-        @Override
-        public Class<? extends DataObject> apply(final InstanceIdentifier<?> input) {
-            return input.getTargetType();
-        }
-    };
+            new Function<InstanceIdentifier<?>, Class<? extends DataObject>>() {
+                @Override
+                public Class<? extends DataObject> apply(final InstanceIdentifier<?> input) {
+                    return input.getTargetType();
+                }
+            };
 
     private final Map<Class<? extends DataObject>, VppWriter<? extends DataObject>> rootWriters;
 
@@ -84,10 +84,10 @@ public final class DelegatingWriterRegistry implements WriterRegistry {
                        @Nullable final DataObject dataAfter,
                        @Nonnull final WriteContext ctx) throws VppException {
         final InstanceIdentifier.PathArgument first = checkNotNull(
-            Iterables.getFirst(id.getPathArguments(), null), "Empty id");
+                Iterables.getFirst(id.getPathArguments(), null), "Empty id");
         final VppWriter<? extends DataObject> vppWriter = rootWriters.get(first.getType());
         checkNotNull(vppWriter,
-            "Unable to write %s. Missing writer. Current writers for: %s", id, rootWriters.keySet());
+                "Unable to write %s. Missing writer. Current writers for: %s", id, rootWriters.keySet());
         vppWriter.update(id, dataBefore, dataAfter, ctx);
     }
 
@@ -101,7 +101,7 @@ public final class DelegatingWriterRegistry implements WriterRegistry {
         final List<InstanceIdentifier<?>> processedNodes = Lists.newArrayList();
 
         for (Map.Entry<Class<? extends DataObject>, VppWriter<? extends DataObject>> rootWriterEntry : rootWriters
-            .entrySet()) {
+                .entrySet()) {
 
             final InstanceIdentifier<? extends DataObject> id = rootWriterEntry.getValue().getManagedDataObjectType();
 
@@ -109,7 +109,7 @@ public final class DelegatingWriterRegistry implements WriterRegistry {
             final DataObject dataAfter = nodesAfter.get(id);
 
             // No change to current writer
-            if(dataBefore == null && dataAfter == null) {
+            if (dataBefore == null && dataAfter == null) {
                 continue;
             }
 
@@ -118,31 +118,32 @@ public final class DelegatingWriterRegistry implements WriterRegistry {
             try {
                 update(id, dataBefore, dataAfter, ctx);
                 processedNodes.add(id);
-            } catch (RuntimeException e) {
-                LOG.error("Error while processing data change of: {} (before={}, after={})", id, dataBefore, dataAfter, e);
-                throw new BulkUpdateException(id, e, new RevertImpl(this, processedNodes, nodesBefore, nodesAfter, ctx));
+            } catch (Exception e) {
+                LOG.error("Error while processing data change of: {} (before={}, after={})",
+                        id, dataBefore, dataAfter, e);
+                throw new BulkUpdateException(
+                        id, new ReverterImpl(this, processedNodes, nodesBefore, nodesAfter, ctx), e);
             }
         }
-
     }
 
     private void checkAllWritersPresent(final @Nonnull Map<InstanceIdentifier<?>, DataObject> nodesBefore) {
         checkArgument(rootWriters.keySet().containsAll(Collections2.transform(nodesBefore.keySet(), ID_TO_CLASS)),
-            "Unable to handle all changes. Missing dedicated writers for: %s",
-            Sets.difference(nodesBefore.keySet(), rootWriters.keySet()));
+                "Unable to handle all changes. Missing dedicated writers for: %s",
+                Sets.difference(nodesBefore.keySet(), rootWriters.keySet()));
     }
 
-    private static final class RevertImpl implements Revert {
+    private static final class ReverterImpl implements Reverter {
         private final WriterRegistry delegatingWriterRegistry;
         private final List<InstanceIdentifier<?>> processedNodes;
         private final Map<InstanceIdentifier<?>, DataObject> nodesBefore;
         private final Map<InstanceIdentifier<?>, DataObject> nodesAfter;
         private final WriteContext ctx;
 
-        public RevertImpl(final WriterRegistry delegatingWriterRegistry,
-                          final List<InstanceIdentifier<?>> processedNodes,
-                          final Map<InstanceIdentifier<?>, DataObject> nodesBefore,
-                          final Map<InstanceIdentifier<?>, DataObject> nodesAfter, final WriteContext ctx) {
+        ReverterImpl(final WriterRegistry delegatingWriterRegistry,
+                            final List<InstanceIdentifier<?>> processedNodes,
+                            final Map<InstanceIdentifier<?>, DataObject> nodesBefore,
+                            final Map<InstanceIdentifier<?>, DataObject> nodesAfter, final WriteContext ctx) {
             this.delegatingWriterRegistry = delegatingWriterRegistry;
             this.processedNodes = processedNodes;
             this.nodesBefore = nodesBefore;
@@ -151,12 +152,11 @@ public final class DelegatingWriterRegistry implements WriterRegistry {
         }
 
         @Override
-        public void revert() throws VppException {
-
-            final ListIterator<InstanceIdentifier<?>> iterator = processedNodes.listIterator(processedNodes.size());
+        public void revert() throws RevertFailedException {
+            final LinkedList<InstanceIdentifier<?>> notReverted = new LinkedList<>(processedNodes);
 
-            while (iterator.hasPrevious()) {
-                final InstanceIdentifier<?> node = iterator.previous();
+            while (notReverted.size() > 0) {
+                final InstanceIdentifier<?> node = notReverted.peekLast();
                 LOG.debug("ChangesProcessor.revertChanges() processing node={}", node);
 
                 final DataObject dataBefore = nodesBefore.get(node);
@@ -165,9 +165,11 @@ public final class DelegatingWriterRegistry implements WriterRegistry {
                 // revert a change by invoking writer with reordered arguments
                 try {
                     delegatingWriterRegistry.update(node, dataAfter, dataBefore, ctx);
-                } catch (RuntimeException e) {
-                    throw new RuntimeException();
+                    notReverted.removeLast(); // change was successfully reverted
+                } catch (Exception e) {
+                    throw new RevertFailedException(notReverted, e);
                 }
+
             }
         }
     }
index 963df73..8719a53 100644 (file)
@@ -44,8 +44,6 @@ import org.mockito.Mock;
 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
 import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.Ethernet;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.L2;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.Vxlan;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -58,7 +56,6 @@ import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
 
 public class VPPConfigDataTreeTest {
 
@@ -89,7 +86,8 @@ public class VPPConfigDataTreeTest {
         doReturn(node).when(snapshot).readNode(path);
 
         final VppDataTreeSnapshot vppDataTreeSnapshot = proxy.takeSnapshot();
-        final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> future = vppDataTreeSnapshot.read(path);
+        final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> future =
+                vppDataTreeSnapshot.read(path);
 
         verify(dataTree).takeSnapshot();
         verify(snapshot).readNode(path);
@@ -132,9 +130,9 @@ public class VPPConfigDataTreeTest {
 
         // Verify all changes were processed:
         verify(vppWriter).update(
-            mapOf(dataBefore, Ethernet.class),
-            mapOf(dataAfter, Ethernet.class),
-            any(WriteContext.class));
+                mapOf(dataBefore, Ethernet.class),
+                mapOf(dataAfter, Ethernet.class),
+                any(WriteContext.class));
 
         // Verify modification was validated
         verify(dataTree).validate(modification);
@@ -142,7 +140,8 @@ public class VPPConfigDataTreeTest {
 
     private Map<InstanceIdentifier<?>, DataObject> mapOf(final DataObject dataBefore, final Class<Ethernet> type) {
         return eq(
-            Collections.<InstanceIdentifier<?>, DataObject>singletonMap(InstanceIdentifier.create(type), dataBefore));
+                Collections.<InstanceIdentifier<?>, DataObject>singletonMap(InstanceIdentifier.create(type),
+                        dataBefore));
     }
 
     private DataObject mockDataObject(final String name, final Class<? extends DataObject> classToMock) {
@@ -154,79 +153,77 @@ public class VPPConfigDataTreeTest {
     @Test
     public void testCommitUndoSuccessful() throws Exception {
         // Prepare data changes:
-        final DataObject dataBefore1 = mockDataObject("before", Ethernet.class);
-        final DataObject dataAfter1 = mockDataObject("after", Ethernet.class);
-
-        final DataObject dataBefore2 = mockDataObject("before", Vxlan.class);
-        final DataObject dataAfter2 = mockDataObject("after", Vxlan.class);
+        final DataObject dataBefore = mockDataObject("before", Ethernet.class);
+        final DataObject dataAfter = mockDataObject("after", Ethernet.class);
 
-        final DataObject dataBefore3 = mockDataObject("before", L2.class);
-        final DataObject dataAfter3 = mockDataObject("after", L2.class);
+        final WriterRegistry.Reverter reverter = mock(WriterRegistry.Reverter.class);
 
-        // reject third applied change
-        final WriterRegistry.Revert revert = mock(WriterRegistry.Revert.class);
-        doThrow(new WriterRegistry.BulkUpdateException(InstanceIdentifier.create(L2.class), new RuntimeException(),
-            revert)).when(vppWriter).update(anyMap(), anyMap(), any(WriteContext.class));
+        // Fail on update:
+        final VppException failedOnUpdateException = new VppException("update failed");
+        doThrow(new WriterRegistry.BulkUpdateException(InstanceIdentifier.create(Ethernet.class), reverter,
+                failedOnUpdateException)).when(vppWriter).update(anyMap(), anyMap(), any(WriteContext.class));
 
         // Prepare modification:
         final DataTreeCandidateNode rootNode = mockRootNode();
         // data before:
-        final ContainerNode nodeBefore = mockContainerNode(dataBefore1, dataBefore2, dataBefore3);
+        final ContainerNode nodeBefore = mockContainerNode(dataBefore);
         when(rootNode.getDataBefore()).thenReturn(Optional.<NormalizedNode<?, ?>>fromNullable(nodeBefore));
         // data after:
-        final ContainerNode nodeAfter = mockContainerNode(dataAfter1, dataAfter2, dataAfter3);
+        final ContainerNode nodeAfter = mockContainerNode(dataAfter);
         when(rootNode.getDataAfter()).thenReturn(Optional.<NormalizedNode<?, ?>>fromNullable(nodeAfter));
 
         // Run the test
         try {
             proxy.commit(modification);
-        } catch (DataValidationFailedException | VppException e) {
+        } catch (WriterRegistry.BulkUpdateException e) {
             verify(vppWriter).update(anyMap(), anyMap(), any(WriteContext.class));
-            verify(revert).revert();
+            verify(reverter).revert();
+            assertEquals(failedOnUpdateException, e.getCause());
             return;
         }
 
-        fail("DataValidationFailedException was expected");
+        fail("WriterRegistry.BulkUpdateException was expected");
     }
 
     @Test
     public void testCommitUndoFailed() throws Exception {
         // Prepare data changes:
-        final DataObject dataBefore1 = mockDataObject("before", Ethernet.class);
-        final DataObject dataAfter1 = mockDataObject("after", Ethernet.class);
+        final DataObject dataBefore = mockDataObject("before", Ethernet.class);
+        final DataObject dataAfter = mockDataObject("after", Ethernet.class);
 
-        final DataObject dataBefore2 = mockDataObject("before", Vxlan.class);
-        final DataObject dataAfter2 = mockDataObject("after", Vxlan.class);
+        final WriterRegistry.Reverter reverter = mock(WriterRegistry.Reverter.class);
 
-        final DataObject dataBefore3 = mockDataObject("before", L2.class);
-        final DataObject dataAfter3 = mockDataObject("after", L2.class);
+        // Fail on update:
+        doThrow(new WriterRegistry.BulkUpdateException(InstanceIdentifier.create(Ethernet.class), reverter,
+                new VppException("update failed"))).when(vppWriter).update(anyMap(), anyMap(), any(WriteContext.class));
 
-        // reject third applied change
-        final WriterRegistry.Revert revert = mock(WriterRegistry.Revert.class);
-        doThrow(new RuntimeException("revert failed")).when(revert).revert();
-        doThrow(new WriterRegistry.BulkUpdateException(InstanceIdentifier.create(L2.class), new RuntimeException(),
-            revert)).when(vppWriter).update(anyMap(), anyMap(), any(WriteContext.class));
+        // Fail on revert:
+        final VppException failedOnRevertException = new VppException("update failed");
+        final WriterRegistry.Reverter.RevertFailedException revertFailedException =
+                new WriterRegistry.Reverter.RevertFailedException(Collections.<InstanceIdentifier<?>>emptyList(),
+                        failedOnRevertException);
+        doThrow(revertFailedException).when(reverter).revert();
 
         // Prepare modification:
         final DataTreeCandidateNode rootNode = mockRootNode();
         // data before:
-        final ContainerNode nodeBefore = mockContainerNode(dataBefore1, dataBefore2, dataBefore3);
+        final ContainerNode nodeBefore = mockContainerNode(dataBefore);
         when(rootNode.getDataBefore()).thenReturn(Optional.<NormalizedNode<?, ?>>fromNullable(nodeBefore));
         // data after:
-        final ContainerNode nodeAfter = mockContainerNode(dataAfter1, dataAfter2, dataAfter3);
+        final ContainerNode nodeAfter = mockContainerNode(dataAfter);
         when(rootNode.getDataAfter()).thenReturn(Optional.<NormalizedNode<?, ?>>fromNullable(nodeAfter));
 
         // Run the test
         try {
             proxy.commit(modification);
-        } catch (DataValidationFailedException | VppException e) {
-            // FIXME the behavior with successful and failed revert is the same from outside world
+        } catch (WriterRegistry.Reverter.RevertFailedException e) {
             verify(vppWriter).update(anyMap(), anyMap(), any(WriteContext.class));
-            verify(revert).revert();
+            verify(reverter).revert();
+            assertEquals(failedOnRevertException, e.getCause());
             return;
         }
 
-        fail("DataValidationFailedException was expected");
+        fail("RevertFailedException was expected");
     }
 
     private DataTreeCandidateNode mockRootNode() {
@@ -256,9 +253,9 @@ public class VPPConfigDataTreeTest {
             when(child.getIdentifier()).thenReturn(mock(YangInstanceIdentifier.PathArgument.class));
             list.add(child);
 
-            final Map.Entry entry  = mock(Map.Entry.class);
+            final Map.Entry entry = mock(Map.Entry.class);
             final Class<? extends DataObject> implementedInterface =
-                (Class<? extends DataObject>) modification.getImplementedInterface();
+                    (Class<? extends DataObject>) modification.getImplementedInterface();
             final InstanceIdentifier<?> id = InstanceIdentifier.create(implementedInterface);
 
             doReturn(id).when(entry).getKey();
@@ -267,5 +264,4 @@ public class VPPConfigDataTreeTest {
         }
         return node;
     }
-
 }
index f4b2faa..8939f8f 100644 (file)
 
 package io.fd.honeycomb.v3po.impl.data;
 
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.mockito.MockitoAnnotations.initMocks;
 
 import com.google.common.base.Optional;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Multimap;
 import com.google.common.util.concurrent.CheckedFuture;
 import io.fd.honeycomb.v3po.impl.trans.r.ReaderRegistry;
 import java.util.Map;
@@ -34,10 +40,12 @@ import org.junit.Test;
 import org.mockito.Mock;
 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
 import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppState;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
@@ -65,16 +73,16 @@ public class VppOperationalDataTreeTest {
     public void setUp() {
         initMocks(this);
         operationalData = new VppOperationalDataTree(serializer, globalContext, reader);
+        doReturn(schemaNode).when(globalContext).getDataChildByName(any(QName.class));
     }
 
     @Test
-    public void testRead() throws Exception {
+    public void testReadNode() throws Exception {
         final YangInstanceIdentifier yangId = mock(YangInstanceIdentifier.class);
         final YangInstanceIdentifier.PathArgument pArg = mock(YangInstanceIdentifier.PathArgument.class);
         doReturn(pArg).when(yangId).getLastPathArgument();
 
         doReturn(QName.create("namespace", "2012-12-12", "local")).when(pArg).getNodeType();
-        doReturn(schemaNode).when(globalContext).getDataChildByName(any(QName.class));
         doReturn(id).when(serializer).fromYangInstanceIdentifier(yangId);
 
         final DataObject dataObject = mock(DataObject.class);
@@ -91,6 +99,69 @@ public class VppOperationalDataTreeTest {
         final Optional<NormalizedNode<?, ?>> result = future.get();
         assertTrue(result.isPresent());
         assertEquals(expectedValue, result.get());
+    }
+
+    @Test
+    public void testReadNonExistingNode() throws Exception {
+        final YangInstanceIdentifier yangId = mock(YangInstanceIdentifier.class);
+        doReturn(id).when(serializer).fromYangInstanceIdentifier(yangId);
+        doReturn(Optional.absent()).when(reader).read(id);
+
+        final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> future = operationalData.read(yangId);
+
+        verify(serializer).fromYangInstanceIdentifier(yangId);
+        verify(reader).read(id);
+        final Optional<NormalizedNode<?, ?>> result = future.get();
+        assertFalse(result.isPresent());
+    }
+
+    @Test
+    public void testReadFailed() throws Exception{
+        doThrow(io.fd.honeycomb.v3po.impl.trans.ReadFailedException.class).when(reader).readAll();
+
+        final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> future =
+                operationalData.read( YangInstanceIdentifier.EMPTY);
+
+        try {
+            future.checkedGet();
+        } catch (ReadFailedException e) {
+            assertTrue(e.getCause() instanceof io.fd.honeycomb.v3po.impl.trans.ReadFailedException);
+            return;
+        }
+        fail("ReadFailedException was expected");
+    }
+
+    @Test
+    public void testReadRootWithOneNonListElement() throws Exception {
+        // Prepare data
+        final InstanceIdentifier<VppState> vppStateII = InstanceIdentifier.create(VppState.class);
+        final VppState vppState = mock(VppState.class);
+        Multimap<InstanceIdentifier<?>, DataObject> dataObjects = LinkedListMultimap.create();
+        dataObjects.put(vppStateII, vppState);
+        doReturn(dataObjects).when(reader).readAll();
+
+        // Init serializer
+        final YangInstanceIdentifier vppYangId = YangInstanceIdentifier.builder().node(VppState.QNAME).build();
+        when(serializer.toYangInstanceIdentifier(vppStateII)).thenReturn(vppYangId);
+        when(serializer.toNormalizedNode(vppStateII, vppState)).thenReturn(entry);
+        final DataContainerChild<?, ?> vppStateContainer = mock(DataContainerChild.class);
+        doReturn(vppStateContainer).when(entry).getValue();
+        doReturn(vppYangId.getLastPathArgument()).when(vppStateContainer).getIdentifier();
+
+        // Read root
+        final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> future =
+                operationalData.read(YangInstanceIdentifier.EMPTY);
+
+        verify(reader).readAll();
+        verify(serializer).toYangInstanceIdentifier(vppStateII);
+        verify(serializer).toNormalizedNode(vppStateII, vppState);
+
+        // Check the result is an ContainerNode with only one child
+        final Optional<NormalizedNode<?, ?>> result = future.get();
+        assertTrue(result.isPresent());
 
+        final ContainerNode rootNode = (ContainerNode) result.get();
+        assertEquals(SchemaContext.NAME, rootNode.getIdentifier().getNodeType());
+        assertEquals(vppStateContainer, Iterables.getOnlyElement(rootNode.getValue()));
     }
 }
\ No newline at end of file
diff --git a/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/trans/ReadFailedExceptionTest.java b/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/trans/ReadFailedExceptionTest.java
new file mode 100644 (file)
index 0000000..b815434
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed 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 io.fd.honeycomb.v3po.impl.trans;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.state.bridge.domains.BridgeDomain;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.state.bridge.domains.bridge.domain.Interface;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+public class ReadFailedExceptionTest {
+
+    @Test
+    public void testInstantiation() {
+        final InstanceIdentifier<BridgeDomain> id = InstanceIdentifier.create(BridgeDomain.class);
+        ReadFailedException e = new ReadFailedException(id);
+        assertEquals(id, e.getFailedId());
+        assertNull(e.getCause());
+        assertTrue(e.getMessage().contains(id.toString()));
+    }
+
+    @Test
+    public void testInstantiationWithCause() {
+        final InstanceIdentifier<Interface> id = InstanceIdentifier.create(Interface.class);
+        final RuntimeException cause = new RuntimeException();
+        ReadFailedException e = new ReadFailedException(id, cause);
+        assertEquals(id, e.getFailedId());
+        assertEquals(cause, e.getCause());
+        assertTrue(e.getMessage().contains(id.toString()));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testInstantiationFailed() {
+        new ReadFailedException(null);
+    }
+}
\ No newline at end of file
diff --git a/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/trans/w/util/DelegatingWriterRegistryTest.java b/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/trans/w/util/DelegatingWriterRegistryTest.java
new file mode 100644 (file)
index 0000000..26f63f4
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed 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 io.fd.honeycomb.v3po.impl.trans.w.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import io.fd.honeycomb.v3po.impl.trans.VppException;
+import io.fd.honeycomb.v3po.impl.trans.w.VppWriter;
+import io.fd.honeycomb.v3po.impl.trans.w.WriteContext;
+import io.fd.honeycomb.v3po.impl.trans.w.WriterRegistry;
+import io.fd.honeycomb.v3po.impl.trans.w.impl.CompositeRootVppWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.Interfaces;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.Vpp;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppState;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+public class DelegatingWriterRegistryTest {
+
+    private final InstanceIdentifier<Vpp> vppId;
+    private final InstanceIdentifier<VppState> vppStateId;
+    private final InstanceIdentifier<Interfaces> interfaceId;
+
+    private WriteContext ctx;
+    private CompositeRootVppWriter<Vpp> vppWriter;
+    private CompositeRootVppWriter<VppState> vppStateWriter;
+    private CompositeRootVppWriter<Interfaces> interfacesWriter;
+
+    private DelegatingWriterRegistry registry;
+
+    public DelegatingWriterRegistryTest() {
+        vppId = InstanceIdentifier.create(Vpp.class);
+        vppStateId = InstanceIdentifier.create(VppState.class);
+        interfaceId = InstanceIdentifier.create(Interfaces.class);
+    }
+
+    @SuppressWarnings("unchecked")
+    private <D extends DataObject> CompositeRootVppWriter<D> mockWriter(Class<D> clazz) {
+        final CompositeRootVppWriter<D> mock = (CompositeRootVppWriter<D>) Mockito.mock(CompositeRootVppWriter.class);
+        doReturn(InstanceIdentifier.create(clazz)).when(mock).getManagedDataObjectType();
+        return mock;
+    }
+
+    private DataObject mockDataObject(final String name, final Class<? extends DataObject> classToMock) {
+        final DataObject dataBefore = mock(classToMock, name);
+        doReturn(classToMock).when(dataBefore).getImplementedInterface();
+        return dataBefore;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Map<InstanceIdentifier<?>, DataObject> asMap(DataObject... objects) {
+        final Map<InstanceIdentifier<?>, DataObject> map = new HashMap<>();
+        for (DataObject object : objects) {
+            final Class<? extends DataObject> implementedInterface =
+                    (Class<? extends DataObject>) object.getImplementedInterface();
+            final InstanceIdentifier<?> id = InstanceIdentifier.create(implementedInterface);
+            map.put(id, object);
+        }
+        return map;
+    }
+
+    @Before
+    public void setUp() {
+        ctx = mock(WriteContext.class);
+        vppWriter = mockWriter(Vpp.class);
+        vppStateWriter = mockWriter(VppState.class);
+        interfacesWriter = mockWriter(Interfaces.class);
+
+        final List<VppWriter<? extends DataObject>> writers = new ArrayList<>();
+        writers.add(vppWriter);
+        writers.add(vppStateWriter);
+        writers.add(interfacesWriter);
+
+        registry = new DelegatingWriterRegistry(writers);
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testGetManagedDataObjectType() {
+        registry.getManagedDataObjectType();
+    }
+
+    @Test
+    public void testBulkUpdateRevert() throws Exception {
+        // Prepare data changes:
+        final DataObject dataBefore1 = mockDataObject("Vpp before", Vpp.class);
+        final DataObject dataAfter1 = mockDataObject("Vpp after", Vpp.class);
+
+        final DataObject dataBefore2 = mockDataObject("VppState before", VppState.class);
+        final DataObject dataAfter2 = mockDataObject("VppState after", VppState.class);
+
+        // Fail on update
+        doThrow(new VppException("vpp failed")).when(vppStateWriter)
+                .update(vppStateId, dataBefore2, dataAfter2, ctx);
+
+        // Run the test
+        try {
+            registry.update(asMap(dataBefore1, dataBefore2), asMap(dataAfter1, dataAfter2), ctx);
+        } catch (WriterRegistry.BulkUpdateException e) {
+            // Check second update failed
+            assertEquals(vppStateId, e.getFailedId());
+            verify(vppWriter).update(vppId, dataBefore1, dataAfter1, ctx);
+            verify(vppStateWriter).update(vppStateId, dataBefore2, dataAfter2, ctx);
+
+            // Try to revert changes
+            e.revertChanges();
+
+            // Check revert was successful
+            verify(vppWriter).update(vppId, dataAfter1, dataBefore1, ctx);
+            verify(vppStateWriter, never()).update(vppStateId, dataAfter2, dataBefore2, ctx);
+
+            return;
+        }
+        fail("BulkUpdateException expected");
+    }
+
+    @Test
+    public void testBulkUpdateRevertFail() throws Exception {
+        // Prepare data changes:
+        final DataObject dataBefore1 = mockDataObject("Vpp before", Vpp.class);
+        final DataObject dataAfter1 = mockDataObject("Vpp after", Vpp.class);
+
+        final DataObject dataBefore2 = mockDataObject("VppState before", VppState.class);
+        final DataObject dataAfter2 = mockDataObject("VppState after", VppState.class);
+
+        final DataObject dataBefore3 = mockDataObject("Interfaces before", Interfaces.class);
+        final DataObject dataAfter3 = mockDataObject("Interfaces after", Interfaces.class);
+
+        // Fail on the third update
+        doThrow(new VppException("vpp failed")).when(interfacesWriter)
+                .update(interfaceId, dataBefore3, dataAfter3, ctx);
+
+        // Fail on the second revert
+        doThrow(new VppException("vpp failed again")).when(vppWriter)
+                .update(vppId, dataAfter1, dataBefore1, ctx);
+
+        // Run the test
+        try {
+            registry.update(asMap(dataBefore1, dataBefore2, dataBefore3), asMap(dataAfter1, dataAfter2, dataAfter3), ctx);
+        } catch (WriterRegistry.BulkUpdateException e) {
+            // Check third update failed
+            assertEquals(interfaceId, e.getFailedId());
+            verify(vppWriter).update(vppId, dataBefore1, dataAfter1, ctx);
+            verify(vppStateWriter).update(vppStateId, dataBefore2, dataAfter2, ctx);
+            verify(interfacesWriter).update(interfaceId, dataBefore3, dataAfter3, ctx);
+
+            // Try to revert changes
+            try {
+                e.revertChanges();
+            } catch (WriterRegistry.Reverter.RevertFailedException e2) {
+                // Check second revert failed
+                assertEquals(Collections.singletonList(vppId), e2.getNotRevertedChanges());
+                verify(vppWriter).update(vppId, dataAfter1, dataBefore1, ctx);
+                verify(vppStateWriter).update(vppStateId, dataAfter2, dataBefore2, ctx);
+                verify(interfacesWriter, never()).update(interfaceId, dataAfter3, dataBefore3, ctx);
+                return;
+            }
+            fail("WriterRegistry.Revert.RevertFailedException expected");
+        }
+        fail("BulkUpdateException expected");
+    }
+}
\ No newline at end of file