summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarek Gradzki <mgradzki@cisco.com>2016-03-04 12:32:10 +0100
committerMarek Gradzki <mgradzki@cisco.com>2016-03-21 09:05:49 +0000
commit87a35a44acf380c29e9b0a0d12e25819505efca3 (patch)
tree63fefbe463fdb2a9672403c133e2a7359bfbb70d
parent08c7806335dfa24246210329630efafea3c151ea (diff)
Dedicated data-tree for Honeycomb agent.
Initial API Implementation. Change-Id: I96c682e2d0d544a4f937bc992a7d0919cb358fac Signed-off-by: Marek Gradzki <mgradzki@cisco.com>
-rw-r--r--v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/DataTreeUtils.java76
-rw-r--r--v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppConfigDataTree.java206
-rw-r--r--v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppDataTree.java5
-rw-r--r--v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTree.java80
-rw-r--r--v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/DataTreeUtilsTest.java72
-rw-r--r--v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VPPConfigDataTreeTest.java309
-rw-r--r--v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTreeTest.java83
7 files changed, 830 insertions, 1 deletions
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
index 000000000..bb9da5695
--- /dev/null
+++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/DataTreeUtils.java
@@ -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
index 000000000..e1f356592
--- /dev/null
+++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppConfigDataTree.java
@@ -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);
+ }
+ }
+ }
+}
+
+
+
diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppDataTree.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppDataTree.java
index 9f64c3966..a3a4fae65 100644
--- a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppDataTree.java
+++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppDataTree.java
@@ -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
index 000000000..0e6bc7d34
--- /dev/null
+++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTree.java
@@ -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
index 000000000..7f1db5118
--- /dev/null
+++ b/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/DataTreeUtilsTest.java
@@ -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
index 000000000..3f78150f9
--- /dev/null
+++ b/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VPPConfigDataTreeTest.java
@@ -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
index 000000000..32b218233
--- /dev/null
+++ b/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTreeTest.java
@@ -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