Dedicated data-tree for Honeycomb agent.
authorMarek Gradzki <[email protected]>
Fri, 4 Mar 2016 11:32:10 +0000 (12:32 +0100)
committerMarek Gradzki <[email protected]>
Mon, 21 Mar 2016 09:05:49 +0000 (09:05 +0000)
Initial API Implementation.

Change-Id: I96c682e2d0d544a4f937bc992a7d0919cb358fac
Signed-off-by: Marek Gradzki <[email protected]>
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/DataTreeUtils.java [new file with mode: 0644]
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppConfigDataTree.java [new file with mode: 0644]
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppDataTree.java
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTree.java [new file with mode: 0644]
v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/DataTreeUtilsTest.java [new file with mode: 0644]
v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VPPConfigDataTreeTest.java [new file with mode: 0644]
v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTreeTest.java [new file with mode: 0644]

diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/DataTreeUtils.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/DataTreeUtils.java
new file mode 100644 (file)
index 0000000..bb9da56
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * 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.data;
+
+import com.google.common.base.Preconditions;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility class for various operations on DataTree.
+ */
+final class DataTreeUtils {
+    private static final Logger LOG = LoggerFactory.getLogger(DataTreeUtils.class);
+
+    private DataTreeUtils() {
+        throw new UnsupportedOperationException("Can't instantiate util class");
+    }
+
+    /**
+     * Translates children of supplied YANG ContainerNode into Binding data.
+     *
+     * @param parent     ContainerNode representing data
+     * @param serializer service for serialization between Java Binding Data representation and NormalizedNode
+     *                   representation.
+     * @return NormalizedNode representation of parent's node children
+     */
+    static Map<InstanceIdentifier<?>, DataObject> childrenFromNormalized(@Nonnull final DataContainerNode parent,
+                                                                         @Nonnull final BindingNormalizedNodeSerializer serializer) {
+
+        Preconditions.checkNotNull(parent, "parent node should not be null");
+        Preconditions.checkNotNull(serializer, "serializer should not be null");
+
+        final Map<InstanceIdentifier<?>, DataObject> map = new HashMap<>();
+
+        final Collection<DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> children =
+                parent.getValue();
+
+        for (final DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> child : children) {
+            final YangInstanceIdentifier.PathArgument pathArgument = child.getIdentifier();
+            final YangInstanceIdentifier identifier = YangInstanceIdentifier.create(pathArgument);
+            LOG.debug("VppConfigDataProxy.extractDataObject() child={}, pathArgument={}, identifier={}", child,
+                    pathArgument, identifier);
+
+            final Map.Entry<InstanceIdentifier<?>, DataObject> entry = serializer.fromNormalizedNode(identifier, child);
+            if (entry != null) {
+                map.put(entry.getKey(), entry.getValue());
+            }
+        }
+
+        return map;
+    }
+}
diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppConfigDataTree.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppConfigDataTree.java
new file mode 100644 (file)
index 0000000..e1f3565
--- /dev/null
@@ -0,0 +1,206 @@
+/*
+ * 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.data;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
+import io.fd.honeycomb.v3po.impl.trans.VppApiInvocationException;
+import io.fd.honeycomb.v3po.impl.trans.VppWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Nonnull;
+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;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
+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;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * VppDataTree implementation for configuration data.
+ */
+public final class VppConfigDataTree implements VppDataTree {
+
+    private static final Logger LOG = LoggerFactory.getLogger(VppConfigDataTree.class);
+
+    private final BindingNormalizedNodeSerializer serializer;
+    private final DataTree dataTree;
+    private final VppWriter writer;
+
+    /**
+     * Creates configuration data tree instance.
+     *
+     * @param serializer service for serialization between Java Binding Data representation and NormalizedNode
+     *                   representation.
+     * @param dataTree   data tree for configuration data representation
+     * @param vppWriter  service for translation between Java Binding Data and Vpp.
+     */
+    public VppConfigDataTree(@Nonnull final BindingNormalizedNodeSerializer serializer,
+                             @Nonnull final DataTree dataTree, @Nonnull final VppWriter vppWriter) {
+        this.serializer = Preconditions.checkNotNull(serializer, "serializer should not be null");
+        this.dataTree = Preconditions.checkNotNull(dataTree, "dataTree should not be null");
+        this.writer = Preconditions.checkNotNull(vppWriter, "vppWriter should not be null");
+    }
+
+    @Override
+    public VppDataTreeSnapshot takeSnapshot() {
+        return new ConfigSnapshot(dataTree.takeSnapshot());
+    }
+
+    @Override
+    public void commit(final DataTreeModification modification) throws DataValidationFailedException, VppApiInvocationException {
+        dataTree.validate(modification);
+
+        final DataTreeCandidate candidate = dataTree.prepare(modification);
+
+        final DataTreeCandidateNode rootNode = candidate.getRootNode();
+        final YangInstanceIdentifier rootPath = candidate.getRootPath();
+        final Optional<NormalizedNode<?, ?>> normalizedDataBefore = rootNode.getDataBefore();
+        final Optional<NormalizedNode<?, ?>> normalizedDataAfter = rootNode.getDataAfter();
+        LOG.debug("VppConfigDataProxy.commit() rootPath={}, rootNode={}, dataBefore={}, dataAfter={}",
+                rootPath, rootNode, normalizedDataBefore, normalizedDataAfter);
+
+        final Map<InstanceIdentifier<?>, DataObject> nodesBefore = extractNetconfData(normalizedDataBefore);
+        LOG.debug("VppConfigDataProxy.commit() extracted nodesBefore={}", nodesBefore.keySet());
+
+        final Map<InstanceIdentifier<?>, DataObject> nodesAfter = extractNetconfData(normalizedDataAfter);
+        LOG.debug("VppConfigDataProxy.commit() extracted nodesAfter={}", nodesAfter.keySet());
+
+        final ChangesProcessor processor = new ChangesProcessor(writer, nodesBefore, nodesAfter);
+        try {
+            processor.applyChanges();
+        } catch (VppApiInvocationException e) {
+            LOG.warn("Failed to apply changes", e);
+            LOG.info("Trying to revert successful changes for current transaction");
+
+            try {
+                processor.revertChanges();
+                LOG.info("Changes successfully reverted");
+            } catch (VppApiInvocationException e2) {
+                LOG.error("Failed to revert successful changes", e2);
+            }
+
+            // rethrow as we can't do anything more about it
+            throw e;
+        }
+
+
+        dataTree.commit(candidate);
+    }
+
+    private Map<InstanceIdentifier<?>, DataObject> extractNetconfData(final Optional<NormalizedNode<?, ?>> parentOptional) {
+        if (parentOptional.isPresent()) {
+            final DataContainerNode parent = (DataContainerNode)parentOptional.get();
+            return DataTreeUtils.childrenFromNormalized(parent, serializer);
+        }
+        return Collections.emptyMap();
+    }
+
+    private final static class ConfigSnapshot implements VppDataTreeSnapshot {
+        private final DataTreeSnapshot snapshot;
+
+        ConfigSnapshot(@Nonnull final DataTreeSnapshot snapshot) {
+            this.snapshot = snapshot;
+        }
+
+        @Override
+        public CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read(
+                final YangInstanceIdentifier path) {
+            return Futures.immediateCheckedFuture(snapshot.readNode(path));
+        }
+
+        @Override
+        public DataTreeModification newModification() {
+            return snapshot.newModification();
+        }
+    }
+
+    private static final class ChangesProcessor {
+        private final VppWriter writer;
+        private final List<InstanceIdentifier<?>> processedNodes;
+        private final Map<InstanceIdentifier<?>, DataObject> nodesBefore;
+        private final Map<InstanceIdentifier<?>, DataObject> nodesAfter;
+
+        ChangesProcessor(@Nonnull final VppWriter writer,
+                         final Map<InstanceIdentifier<?>, DataObject> nodesBefore,
+                         final Map<InstanceIdentifier<?>, DataObject> nodesAfter) {
+            this.writer = Preconditions.checkNotNull(writer, "VppWriter is null!");
+            this.nodesBefore = Preconditions.checkNotNull(nodesBefore, "nodesBefore is null!");
+            this.nodesAfter = Preconditions.checkNotNull(nodesAfter, "nodesAfter is null!");
+            processedNodes = new ArrayList<>();
+        }
+
+        void applyChanges() throws VppApiInvocationException {
+            // TODO we should care about the order of modified subtrees
+            final Set<InstanceIdentifier<?>> allNodes = new HashSet<>();
+            allNodes.addAll(nodesBefore.keySet());
+            allNodes.addAll(nodesAfter.keySet());
+            LOG.debug("VppConfigDataProxy.applyChanges() all extracted nodes: {}", allNodes);
+
+            for (InstanceIdentifier<?> node : allNodes) {
+                LOG.debug("VppConfigDataProxy.applyChanges() processing node={}", node);
+                final DataObject dataBefore = nodesBefore.get(node);
+                final DataObject dataAfter = nodesAfter.get(node);
+
+                try {
+                    writer.process(dataBefore, dataAfter);
+                    processedNodes.add(node);
+                } catch (VppApiInvocationException e) {
+                    LOG.error("Error while processing data change (before={}, after={})", dataBefore, dataAfter, e);
+                    throw e;
+                }
+            }
+        }
+
+        void revertChanges() throws VppApiInvocationException {
+            Preconditions.checkNotNull(writer, "VppWriter is nuserializerll!");
+
+            // revert changes in reverse order they were applied
+            final ListIterator<InstanceIdentifier<?>> iterator = processedNodes.listIterator(processedNodes.size());
+
+            while (iterator.hasPrevious()) {
+                final InstanceIdentifier<?> node = iterator.previous();
+                LOG.debug("VppConfigDataProxy.revertChanges() processing node={}", node);
+
+                final DataObject dataBefore = nodesBefore.get(node);
+                final DataObject dataAfter = nodesAfter.get(node);
+
+                // revert a change by invoking writer with reordered arguments
+                writer.process(dataAfter, dataBefore);
+            }
+        }
+    }
+}
+
+
+
index 9f64c39..a3a4fae 100644 (file)
@@ -17,6 +17,7 @@
 package io.fd.honeycomb.v3po.impl.data;
 
 import com.google.common.annotations.Beta;
+import io.fd.honeycomb.v3po.impl.trans.VppApiInvocationException;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
 
@@ -30,8 +31,10 @@ public interface VppDataTree {
      *
      * @param modification VPP data tree modification
      * @throws DataValidationFailedException if modification data is not valid
+     * @throws VppApiInvocationException if commit failed while updating VPP state
      */
-    void commit(final DataTreeModification modification) throws DataValidationFailedException;
+    void commit(final DataTreeModification modification) throws DataValidationFailedException,
+            VppApiInvocationException;
 
     /**
      * Creates read-only snapshot of a VppDataTree.
diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTree.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTree.java
new file mode 100644 (file)
index 0000000..0e6bc7d
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * 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.data;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
+import io.fd.honeycomb.v3po.impl.trans.VppReader;
+import java.util.Map;
+import javax.annotation.Nonnull;
+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;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * ReadableVppDataTree implementation for operational data.
+ */
+public final class VppOperationalDataTree implements ReadableVppDataTree {
+    private static final Logger LOG = LoggerFactory.getLogger(VppOperationalDataTree.class);
+    private final BindingNormalizedNodeSerializer serializer;
+    private final VppReader reader;
+
+    /**
+     * Creates operational data tree instance.
+     *
+     * @param serializer service for serialization between Java Binding Data representation and NormalizedNode
+     *                   representation.
+     * @param reader     service for translation between Vpp and Java Binding Data.
+     */
+    public VppOperationalDataTree(@Nonnull BindingNormalizedNodeSerializer serializer,
+                                  @Nonnull VppReader reader) {
+        this.serializer = Preconditions.checkNotNull(serializer, "serializer should not be null");
+        this.reader = Preconditions.checkNotNull(reader, "reader should not be null");
+    }
+
+    @Override
+    public CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read(
+            final YangInstanceIdentifier yangInstanceIdentifier) {
+        // TODO What if the path is ROOT/empty?
+        final InstanceIdentifier<?> path = serializer.fromYangInstanceIdentifier(yangInstanceIdentifier);
+        LOG.debug("VppOperationalDataProxy.read(), path={}", path);
+
+        final DataObject dataObject = reader.read(path); // FIXME we need to expect a list of dataObjects here
+        return Futures.immediateCheckedFuture(toNormalizedNode(path, dataObject));
+    }
+
+    private Optional<NormalizedNode<?, ?>> toNormalizedNode(final InstanceIdentifier path,
+                                                            final DataObject dataObject) {
+        LOG.trace("VppOperationalDataProxy.toNormalizedNode(), path={}, path={}", path, dataObject);
+        final Map.Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> entry =
+                serializer.toNormalizedNode(path, dataObject);
+
+        final NormalizedNode<?, ?> value = entry.getValue();
+        LOG.trace("VppOperationalDataProxy.toNormalizedNode(), value={}", value);
+
+        final Optional<NormalizedNode<?, ?>> optional = Optional.<NormalizedNode<?, ?>>fromNullable(value);
+        LOG.trace("VppOperationalDataProxy.toNormalizedNode(), optional={}", optional);
+        return optional;
+    }
+}
diff --git a/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/DataTreeUtilsTest.java b/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/DataTreeUtilsTest.java
new file mode 100644 (file)
index 0000000..7f1db51
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * 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.data;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+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;
+
+public class DataTreeUtilsTest {
+
+    @Test
+    public void testChildrenFromNormalized() throws Exception {
+        final ContainerNode parent = mock(ContainerNode.class);
+        final BindingNormalizedNodeSerializer serializer = mock(BindingNormalizedNodeSerializer.class);
+
+        final Collection<DataContainerChild> list = new ArrayList<>();
+        doReturn(list).when(parent).getValue();
+
+        // init child1 (will not be serialized)
+        final DataContainerChild child1 = Mockito.mock(DataContainerChild.class);
+        when(child1.getIdentifier()).thenReturn(mock(YangInstanceIdentifier.PathArgument.class));
+        when(serializer.fromNormalizedNode(any(YangInstanceIdentifier.class), eq(child1))).thenReturn(null);
+        list.add(child1);
+
+        // init child 2 (will be serialized)
+        final DataContainerChild child2 = mock(DataContainerChild.class);
+        when(child2.getIdentifier()).thenReturn(mock(YangInstanceIdentifier.PathArgument.class));
+
+        final Map.Entry entry = mock(Map.Entry.class);
+        final InstanceIdentifier<?> id = mock(InstanceIdentifier.class);
+        doReturn(id).when(entry).getKey();
+        final DataObject data = mock(DataObject.class);
+        doReturn(data).when(entry).getValue();
+        when(serializer.fromNormalizedNode(any(YangInstanceIdentifier.class), eq(child2))).thenReturn(entry);
+
+        list.add(child2);
+
+        // run tested method
+        final Map<InstanceIdentifier<?>, DataObject> map = DataTreeUtils.childrenFromNormalized(parent, serializer);
+        assertEquals(1, map.size());
+        assertEquals(data, map.get(id));
+    }
+}
\ No newline at end of file
diff --git a/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VPPConfigDataTreeTest.java b/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VPPConfigDataTreeTest.java
new file mode 100644 (file)
index 0000000..3f78150
--- /dev/null
@@ -0,0 +1,309 @@
+/*
+ * 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.data;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+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.util.concurrent.CheckedFuture;
+import io.fd.honeycomb.v3po.impl.trans.VppApiInvocationException;
+import io.fd.honeycomb.v3po.impl.trans.VppWriter;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+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;
+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.data.api.schema.tree.DataTree;
+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 {
+
+    @Mock
+    private VppWriter<DataObject> vppWriter;
+    @Mock
+    private BindingNormalizedNodeSerializer serializer;
+    @Mock
+    private DataTree dataTree;
+    @Mock
+    private DataTreeModification modification;
+
+    private VppConfigDataTree proxy;
+
+    @Before
+    public void setUp() {
+        initMocks(this);
+        proxy = new VppConfigDataTree(serializer, dataTree, vppWriter);
+    }
+
+    @Test
+    public void testRead() throws Exception {
+        final DataTreeSnapshot snapshot = mock(DataTreeSnapshot.class);
+        when(dataTree.takeSnapshot()).thenReturn(snapshot);
+
+        final YangInstanceIdentifier path = mock(YangInstanceIdentifier.class);
+        final Optional node = mock(Optional.class);
+        doReturn(node).when(snapshot).readNode(path);
+
+        final VppDataTreeSnapshot vppDataTreeSnapshot = proxy.takeSnapshot();
+        final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> future = vppDataTreeSnapshot.read(path);
+
+        verify(dataTree).takeSnapshot();
+        verify(snapshot).readNode(path);
+
+        assertTrue(future.isDone());
+        assertEquals(node, future.get());
+    }
+
+    @Test
+    public void testNewModification() throws Exception {
+        final DataTreeSnapshot snapshot = mock(DataTreeSnapshot.class);
+        when(dataTree.takeSnapshot()).thenReturn(snapshot);
+
+        when(snapshot.newModification()).thenReturn(modification);
+
+        final VppDataTreeSnapshot vppDataTreeSnapshot = proxy.takeSnapshot();
+        final DataTreeModification newModification = vppDataTreeSnapshot.newModification();
+        verify(dataTree).takeSnapshot();
+        verify(snapshot).newModification();
+
+        assertEquals(modification, newModification);
+    }
+
+    @Test
+    public void testCommitSuccessful() throws Exception {
+        final DataObject dataBefore = mock(Ethernet.class);
+        final DataObject dataAfter = mock(Ethernet.class);
+
+        // Prepare modification:
+        final DataTreeCandidateNode rootNode = mockRootNode();
+        // data before:
+        final ContainerNode nodeBefore = mockContainerNode(dataBefore);
+        when(rootNode.getDataBefore()).thenReturn(Optional.<NormalizedNode<?, ?>>fromNullable(nodeBefore));
+        // data after:
+        final ContainerNode nodeAfter = mockContainerNode(dataAfter);
+        when(rootNode.getDataAfter()).thenReturn(Optional.<NormalizedNode<?, ?>>fromNullable(nodeAfter));
+
+        // Run the test
+        proxy.commit(modification);
+
+        // Verify all changes were processed:
+        verify(vppWriter).process(dataBefore, dataAfter);
+
+        // Verify modification was validated
+        verify(dataTree).validate(modification);
+    }
+
+    @Test
+    public void testCommitUndoSuccessful() throws Exception {
+        // Prepare data changes:
+        final DataObject dataBefore1 = mock(Ethernet.class);
+        final DataObject dataAfter1 = mock(Ethernet.class);
+
+        final DataObject dataBefore2 = mock(Vxlan.class);
+        final DataObject dataAfter2 = mock(Vxlan.class);
+
+        final DataObject dataBefore3 = mock(L2.class);
+        final DataObject dataAfter3 = mock(L2.class);
+
+        final List<Map.Entry<DataObject, DataObject>> processedChanges = new ArrayList<>();
+        // reject third applied change
+        final Answer answer = new VppWriterAnswer(processedChanges, Arrays.asList(1,2), Collections.singletonList(3));
+        doAnswer(answer).when(vppWriter).process(any(DataObject.class), any(DataObject.class));
+
+        // Prepare modification:
+        final DataTreeCandidateNode rootNode = mockRootNode();
+        // data before:
+        final ContainerNode nodeBefore = mockContainerNode(dataBefore1, dataBefore2, dataBefore3);
+        when(rootNode.getDataBefore()).thenReturn(Optional.<NormalizedNode<?, ?>>fromNullable(nodeBefore));
+        // data after:
+        final ContainerNode nodeAfter = mockContainerNode(dataAfter1, dataAfter2, dataAfter3);
+        when(rootNode.getDataAfter()).thenReturn(Optional.<NormalizedNode<?, ?>>fromNullable(nodeAfter));
+
+        // Run the test
+        try {
+            proxy.commit(modification);
+        } catch (DataValidationFailedException | VppApiInvocationException e) {
+            // verify that all changes were processed:
+            verify(vppWriter).process(dataBefore1, dataAfter1);
+            verify(vppWriter).process(dataBefore2, dataAfter2);
+            verify(vppWriter).process(dataBefore3, dataAfter3);
+
+            // verify that only two changes were processed successfully:
+            assertEquals(2, processedChanges.size());
+
+            // verify that successful changes were undone
+            for (final Map.Entry<DataObject, DataObject> change : processedChanges) {
+                verify(vppWriter).process(change.getValue(), change.getKey());
+            }
+            return;
+        }
+
+        fail("DataValidationFailedException was expected");
+    }
+
+    @Test
+    public void testCommitUndoFailed() throws Exception {
+        // Prepare data changes:
+        final DataObject dataBefore1 = mock(Ethernet.class);
+        final DataObject dataAfter1 = mock(Ethernet.class);
+
+        final DataObject dataBefore2 = mock(Vxlan.class);
+        final DataObject dataAfter2 = mock(Vxlan.class);
+
+        final DataObject dataBefore3 = mock(L2.class);
+        final DataObject dataAfter3 = mock(L2.class);
+
+        // reject third applied change and fourth (first undo):
+        final List<Map.Entry<DataObject, DataObject>> processedChanges = new ArrayList<>();
+        final Answer answer = new VppWriterAnswer(processedChanges, Arrays.asList(1,2), Arrays.asList(3,4));
+        doAnswer(answer).when(vppWriter).process(any(DataObject.class), any(DataObject.class));
+
+        // Prepare modification:
+        final DataTreeCandidateNode rootNode = mockRootNode();
+        // data before:
+        final ContainerNode nodeBefore = mockContainerNode(dataBefore1, dataBefore2, dataBefore3);
+        when(rootNode.getDataBefore()).thenReturn(Optional.<NormalizedNode<?, ?>>fromNullable(nodeBefore));
+        // data after:
+        final ContainerNode nodeAfter = mockContainerNode(dataAfter1, dataAfter2, dataAfter3);
+        when(rootNode.getDataAfter()).thenReturn(Optional.<NormalizedNode<?, ?>>fromNullable(nodeAfter));
+
+        // Run the test
+        try {
+            proxy.commit(modification);
+        } catch (DataValidationFailedException | VppApiInvocationException e) {
+            // verify that all changes were processed:
+            verify(vppWriter).process(dataBefore1, dataAfter1);
+            verify(vppWriter).process(dataBefore2, dataAfter2);
+            verify(vppWriter).process(dataBefore3, dataAfter3);
+
+            // verify that only two changes were processed successfully:
+            assertEquals(2, processedChanges.size());
+
+            // verify we tried to undo the last successful change:
+            Map.Entry<DataObject, DataObject> change = processedChanges.get(1);
+            verify(vppWriter).process(change.getValue(), change.getKey());
+
+            // but failed, and did not try to undo the first:
+            change = processedChanges.get(0);
+            verify(vppWriter, never()).process(change.getValue(), change.getKey());
+            return;
+        }
+
+        fail("DataValidationFailedException was expected");
+    }
+
+    private DataTreeCandidateNode mockRootNode() {
+        final DataTreeCandidate candidate = mock(DataTreeCandidate.class);
+        when(dataTree.prepare(modification)).thenReturn(candidate);
+
+        final DataTreeCandidateNode rootNode = mock(DataTreeCandidateNode.class);
+        when(candidate.getRootNode()).thenReturn(rootNode);
+
+        return rootNode;
+    }
+
+    private ContainerNode mockContainerNode(DataObject... modifications) {
+        final int numberOfChildren = modifications.length;
+
+        final YangInstanceIdentifier.NodeIdentifier identifier =
+                YangInstanceIdentifier.NodeIdentifier.create(QName.create("/"));
+
+        final ContainerNode node = mock(ContainerNode.class);
+        when(node.getIdentifier()).thenReturn(identifier);
+
+        final List<DataContainerChild> list = new ArrayList<>(numberOfChildren);
+        doReturn(list).when(node).getValue();
+
+        for (DataObject modification : modifications) {
+            final DataContainerChild child = mock(DataContainerChild.class);
+            when(child.getIdentifier()).thenReturn(mock(YangInstanceIdentifier.PathArgument.class));
+            list.add(child);
+
+            final Map.Entry entry  = mock(Map.Entry.class);
+            final InstanceIdentifier<?> id = InstanceIdentifier.create(modification.getClass());
+
+            doReturn(id).when(entry).getKey();
+            doReturn(modification).when(entry).getValue();
+            doReturn(entry).when(serializer).fromNormalizedNode(any(YangInstanceIdentifier.class), eq(child));
+        }
+        return node;
+    }
+
+    private static final class VppWriterAnswer implements Answer {
+        private final List<Map.Entry<DataObject, DataObject>> capturedChanges;
+        private final Collection<Integer> toCapture;
+        private final Collection<Integer> toReject;
+        private int count = 0;
+
+        private VppWriterAnswer(final List<Map.Entry<DataObject, DataObject>> capturedChanges,
+                                final Collection<Integer> toCapture,
+                                final Collection<Integer> toReject) {
+            this.capturedChanges = capturedChanges;
+            this.toCapture = toCapture;
+            this.toReject = toReject;
+        }
+
+        @Override
+        public Object answer(final InvocationOnMock invocation) throws Throwable {
+            ++count;
+            if (toCapture.contains(count)) {
+                final DataObject dataBefore = (DataObject)invocation.getArguments()[0];
+                final DataObject dataAfter = (DataObject)invocation.getArguments()[1];
+                capturedChanges.add(new AbstractMap.SimpleImmutableEntry<>(dataBefore, dataAfter));
+            }
+            if (toReject.contains(count)) {
+                throw mock(VppApiInvocationException.class);
+            }
+            return null;
+        }
+    }
+
+}
diff --git a/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTreeTest.java b/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTreeTest.java
new file mode 100644 (file)
index 0000000..32b2182
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * 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.data;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.TestCase.assertTrue;
+import static org.mockito.Mockito.doReturn;
+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.util.concurrent.CheckedFuture;
+import io.fd.honeycomb.v3po.impl.trans.VppReader;
+import java.util.Map;
+import org.junit.Before;
+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.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public class VppOperationalDataTreeTest {
+
+    @Mock
+    private BindingNormalizedNodeSerializer serializer;
+    @Mock
+    private VppReader reader;
+
+    private VppOperationalDataTree operationalData;
+
+    @Mock
+    private InstanceIdentifier<DataObject> id;
+    @Mock
+    private Map.Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> entry;
+
+    @Before
+    public void setUp() {
+        initMocks(this);
+        operationalData = new VppOperationalDataTree(serializer, reader);
+    }
+
+    @Test
+    public void testRead() throws Exception {
+        final YangInstanceIdentifier yangId = mock(YangInstanceIdentifier.class);
+
+        doReturn(id).when(serializer).fromYangInstanceIdentifier(yangId);
+
+        final DataObject dataObject = mock(DataObject.class);
+        when(reader.read(id)).thenReturn(dataObject);
+
+        when(serializer.toNormalizedNode(id, dataObject)).thenReturn(entry);
+        final NormalizedNode<?, ?> expectedValue = mock(NormalizedNode.class);
+        doReturn(expectedValue).when(entry).getValue();
+
+        final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> future = operationalData.read(yangId);
+
+        verify(serializer).fromYangInstanceIdentifier(yangId);
+        verify(reader).read(id);
+        final Optional<NormalizedNode<?, ?>> result = future.get();
+        assertTrue(result.isPresent());
+        assertEquals(expectedValue, result.get());
+
+    }
+}
\ No newline at end of file