summaryrefslogtreecommitdiffstats
path: root/v3po
diff options
context:
space:
mode:
authorMaros Marsalek <mmarsale@cisco.com>2016-06-14 10:48:26 +0200
committerMaros Marsalek <mmarsale@cisco.com>2016-06-15 08:59:04 +0200
commitd23f4be2c62a8fbab984fc7dea8ec2e317b8a662 (patch)
tree1c974f2ac8bd68a098ff9407e454c97d16b83475 /v3po
parent9ad6dfd73089e26bb74cea9ab775cb9030dacb46 (diff)
HONEYCOMB-92: Process modifications recursively
+ Fix update subtree, child writer lookup + Change initializers operation to merge Change-Id: I6ece7eb3d17d5a0b4a413189ddd383567d7e2270 Signed-off-by: Maros Marsalek <mmarsale@cisco.com>
Diffstat (limited to 'v3po')
-rw-r--r--v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/DataTreeUtils.java77
-rw-r--r--v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegator.java200
-rw-r--r--v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/DataTreeUtilsTest.java68
-rw-r--r--v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegatorTest.java100
-rw-r--r--v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModificationDiffTest.java376
-rw-r--r--v3po/data-impl/src/test/resources/test-diff.yang37
-rw-r--r--v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/AbstractCompositeWriter.java29
-rw-r--r--v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/DelegatingWriterRegistry.java72
-rw-r--r--v3po/vpp-cfg-init/src/main/java/io/fd/honeycomb/v3po/vpp/data/init/AbstractDataTreeConverter.java5
9 files changed, 737 insertions, 227 deletions
diff --git a/v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/DataTreeUtils.java b/v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/DataTreeUtils.java
deleted file mode 100644
index 5833459..0000000
--- a/v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/DataTreeUtils.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.data.impl;
-
-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.yangtools.binding.data.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("DataTreeUtils.childrenFromNormalized() 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/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegator.java b/v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegator.java
index 75ffb70..c8d258b 100644
--- a/v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegator.java
+++ b/v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegator.java
@@ -16,11 +16,13 @@
package io.fd.honeycomb.v3po.data.impl;
+import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.util.concurrent.Futures.immediateCheckedFuture;
-import static io.fd.honeycomb.v3po.data.impl.DataTreeUtils.childrenFromNormalized;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
+import com.google.common.collect.Sets;
import com.google.common.util.concurrent.CheckedFuture;
import io.fd.honeycomb.v3po.data.DataModification;
import io.fd.honeycomb.v3po.data.ReadableDataManager;
@@ -30,6 +32,8 @@ import io.fd.honeycomb.v3po.translate.util.write.TransactionWriteContext;
import io.fd.honeycomb.v3po.translate.write.WriteContext;
import io.fd.honeycomb.v3po.translate.write.WriterRegistry;
import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nonnull;
import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
@@ -38,11 +42,12 @@ import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeSe
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.LeafNode;
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.ModificationType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -55,10 +60,10 @@ public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager
private static final Logger LOG = LoggerFactory.getLogger(ModifiableDataTreeDelegator.class);
private static final ReadableDataManager EMPTY_OPERATIONAL = p -> immediateCheckedFuture(Optional.absent());
- // TODO what to use instead of deprecated BindingNormalizedNodeSerializer ?
- private final BindingNormalizedNodeSerializer serializer;
private final WriterRegistry writerRegistry;
private final org.opendaylight.controller.md.sal.binding.api.DataBroker contextBroker;
+ // TODO what to use instead of deprecated BindingNormalizedNodeSerializer ?
+ private final BindingNormalizedNodeSerializer serializer;
/**
* Creates configuration data tree instance.
@@ -107,14 +112,16 @@ public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager
throws TranslationException {
final DataTreeCandidateNode rootNode = candidate.getRootNode();
final YangInstanceIdentifier rootPath = candidate.getRootPath();
- final Optional<NormalizedNode<?, ?>> normalizedDataBefore = rootNode.getDataBefore();
- final Optional<NormalizedNode<?, ?>> normalizedDataAfter = rootNode.getDataAfter();
- LOG.debug("ConfigDataTree.modify() rootPath={}, rootNode={}, dataBefore={}, dataAfter={}",
- rootPath, rootNode, normalizedDataBefore, normalizedDataAfter);
+ LOG.trace("ConfigDataTree.modify() rootPath={}, rootNode={}, dataBefore={}, dataAfter={}",
+ rootPath, rootNode, rootNode.getDataBefore(), rootNode.getDataAfter());
- final Map<InstanceIdentifier<?>, DataObject> nodesBefore = toBindingAware(normalizedDataBefore);
+ final ModificationDiff modificationDiff =
+ ModificationDiff.recursivelyFromCandidate(YangInstanceIdentifier.EMPTY, rootNode);
+ LOG.debug("ConfigDataTree.modify() diff: {}", modificationDiff);
+
+ final Map<InstanceIdentifier<?>, DataObject> nodesBefore = toBindingAware(modificationDiff.getModificationsBefore());
LOG.debug("ConfigDataTree.modify() extracted nodesBefore={}", nodesBefore.keySet());
- final Map<InstanceIdentifier<?>, DataObject> nodesAfter = toBindingAware(normalizedDataAfter);
+ final Map<InstanceIdentifier<?>, DataObject> nodesAfter = toBindingAware(modificationDiff.getModificationsAfter());
LOG.debug("ConfigDataTree.modify() extracted nodesAfter={}", nodesAfter.keySet());
try (final WriteContext ctx = getTransactionWriteContext()) {
@@ -160,12 +167,175 @@ public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager
return new TransactionWriteContext(serializer, beforeTx, afterTx, mappingContext);
}
- private Map<InstanceIdentifier<?>, DataObject> toBindingAware(final Optional<NormalizedNode<?, ?>> parentOptional) {
- if (parentOptional.isPresent()) {
- final DataContainerNode parent = (DataContainerNode) parentOptional.get();
- return childrenFromNormalized(parent, serializer);
+ private Map<InstanceIdentifier<?>, DataObject> toBindingAware(final Map<YangInstanceIdentifier, NormalizedNode<?, ?>> biNodes) {
+ return ModifiableDataTreeDelegator.toBindingAware(biNodes, serializer);
+ }
+ }
+
+ @VisibleForTesting
+ static Map<InstanceIdentifier<?>, DataObject> toBindingAware(final Map<YangInstanceIdentifier, NormalizedNode<?, ?>> biNodes,
+ final BindingNormalizedNodeSerializer serializer) {
+ final HashMap<InstanceIdentifier<?>, DataObject> transformed = new HashMap<>(biNodes.size());
+ for (Map.Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> biEntry : biNodes.entrySet()) {
+ final Map.Entry<InstanceIdentifier<?>, DataObject> baEntry = serializer.fromNormalizedNode(biEntry.getKey(), biEntry.getValue());
+ if(baEntry != null) {
+ transformed.put(baEntry.getKey(), baEntry.getValue());
+ }
+ }
+ return transformed;
+ }
+
+ @VisibleForTesting
+ static final class ModificationDiff {
+
+ private static final ModificationDiff EMPTY_DIFF = new ModificationDiff(Collections.emptyMap(), Collections.emptyMap());
+ private static final EnumSet LEAF_MODIFICATIONS = EnumSet.of(ModificationType.WRITE, ModificationType.DELETE);
+
+ private final Map<YangInstanceIdentifier, NormalizedNode<?, ?>> modificationsBefore;
+ private final Map<YangInstanceIdentifier, NormalizedNode<?, ?>> modificationsAfter;
+
+ private ModificationDiff(@Nonnull final Map<YangInstanceIdentifier, NormalizedNode<?, ?>> modificationsBefore,
+ @Nonnull final Map<YangInstanceIdentifier, NormalizedNode<?, ?>> modificationsAfter) {
+ this.modificationsBefore = modificationsBefore;
+ this.modificationsAfter = modificationsAfter;
+ }
+
+ Map<YangInstanceIdentifier, NormalizedNode<?, ?>> getModificationsBefore() {
+ return modificationsBefore;
+ }
+
+ Map<YangInstanceIdentifier, NormalizedNode<?, ?>> getModificationsAfter() {
+ return modificationsAfter;
+ }
+
+ private ModificationDiff merge(final ModificationDiff other) {
+ if (this == EMPTY_DIFF) {
+ return other;
}
- return Collections.emptyMap();
+
+ if (other == EMPTY_DIFF) {
+ return this;
+ }
+
+ return new ModificationDiff(join(modificationsBefore, other.modificationsBefore),
+ join(modificationsAfter, other.modificationsAfter));
+ }
+
+ private static Map<YangInstanceIdentifier, NormalizedNode<?, ?>> join(
+ final Map<YangInstanceIdentifier, NormalizedNode<?, ?>> mapOne,
+ final Map<YangInstanceIdentifier, NormalizedNode<?, ?>> mapTwo) {
+ // Check unique modifications
+ // TODO Probably not necessary to check
+ final Sets.SetView<YangInstanceIdentifier> duplicates = Sets.intersection(mapOne.keySet(), mapTwo.keySet());
+ checkArgument(duplicates.size() == 0, "Duplicates detected: %s. In maps: %s and %s", duplicates, mapOne, mapTwo);
+ final HashMap<YangInstanceIdentifier, NormalizedNode<?, ?>> joined = new HashMap<>();
+ joined.putAll(mapOne);
+ joined.putAll(mapTwo);
+ return joined;
+ }
+
+ private static ModificationDiff createFromBefore(YangInstanceIdentifier idBefore, DataTreeCandidateNode candidate) {
+ return new ModificationDiff(
+ Collections.singletonMap(idBefore, candidate.getDataBefore().get()),
+ Collections.emptyMap());
+ }
+
+ private static ModificationDiff create(YangInstanceIdentifier id, DataTreeCandidateNode candidate) {
+ return new ModificationDiff(
+ Collections.singletonMap(id, candidate.getDataBefore().get()),
+ Collections.singletonMap(id, candidate.getDataAfter().get()));
+ }
+
+ private static ModificationDiff createFromAfter(YangInstanceIdentifier idAfter, DataTreeCandidateNode candidate) {
+ return new ModificationDiff(
+ Collections.emptyMap(),
+ Collections.singletonMap(idAfter, candidate.getDataAfter().get()));
+ }
+
+ /**
+ * Produce a diff from a candidate node recursively
+ */
+ @Nonnull
+ static ModificationDiff recursivelyFromCandidate(@Nonnull final YangInstanceIdentifier yangIid,
+ @Nonnull final DataTreeCandidateNode currentCandidate) {
+ switch (currentCandidate.getModificationType()) {
+ case APPEARED:
+ case DISAPPEARED:
+ case UNMODIFIED: {
+ // (dis)appeared nodes are not important, no real data to process
+ return ModificationDiff.EMPTY_DIFF;
+ }
+ case WRITE: {
+ return currentCandidate.getDataBefore().isPresent() ?
+ ModificationDiff.create(yangIid, currentCandidate) :
+ ModificationDiff.createFromAfter(yangIid, currentCandidate);
+ // TODO HONEYCOMB-94 process children recursively to get modifications for child nodes
+ }
+ case DELETE:
+ return ModificationDiff.createFromBefore(yangIid, currentCandidate);
+ case SUBTREE_MODIFIED: {
+ // Modifications here are presented also for leaves. However that kind of granularity is not required
+ // So if there's a modified leaf, mark current complex node also as modification
+ java.util.Optional<Boolean> leavesModified = currentCandidate.getChildNodes().stream()
+ .filter(ModificationDiff::isLeaf)
+ // For some reason, we get modifications on unmodified list keys TODO debug and report ODL bug
+ // and that messes up our modifications collection here, so we need to skip
+ .filter(ModificationDiff::isModification)
+ .map(child -> LEAF_MODIFICATIONS.contains(child.getModificationType()))
+ .reduce((aBoolean, aBoolean2) -> aBoolean || aBoolean2);
+
+ //
+ if(leavesModified.isPresent() && leavesModified.get()) {
+ return ModificationDiff.create(yangIid, currentCandidate);
+ // TODO HONEYCOMB-94 process children recursively to get modifications for child nodes even if current
+ // was modified
+ } else {
+ // SUBTREE MODIFIED, no modification on current, but process children recursively
+ return currentCandidate.getChildNodes().stream()
+ // not interested in modifications to leaves
+ .filter(child -> !isLeaf(child))
+ .map(candidate -> recursivelyFromCandidate(yangIid.node(candidate.getIdentifier()), candidate))
+ .reduce(ModificationDiff::merge)
+ .orElse(EMPTY_DIFF);
+ }
+ }
+ default:
+ throw new IllegalStateException("Unknown modification type: "
+ + currentCandidate.getModificationType() + ". Unsupported");
+ }
+ }
+
+ /**
+ * Check whether candidate.before and candidate.after is different. If not
+ * return false.
+ */
+ private static boolean isModification(final DataTreeCandidateNode a) {
+ if(a.getDataBefore().isPresent()) {
+ if(a.getDataAfter().isPresent()) {
+ return !a.getDataAfter().get().equals(a.getDataBefore().get());
+ } else {
+ return true;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Check whether candidate node is for a leaf type node
+ */
+ private static boolean isLeaf(final DataTreeCandidateNode a) {
+ return a.getDataAfter().isPresent()
+ ? (a.getDataAfter().get() instanceof LeafNode<?>)
+ : (a.getDataBefore().get() instanceof LeafNode<?>);
+ }
+
+ @Override
+ public String toString() {
+ return "ModificationDiff{" +
+ "modificationsBefore=" + modificationsBefore +
+ ", modificationsAfter=" + modificationsAfter +
+ '}';
}
}
}
diff --git a/v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/DataTreeUtilsTest.java b/v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/DataTreeUtilsTest.java
deleted file mode 100644
index 40792a1..0000000
--- a/v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/DataTreeUtilsTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.data.impl;
-
-import static org.junit.Assert.assertEquals;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Map;
-import org.junit.Test;
-import org.mockito.Matchers;
-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 = Mockito.mock(ContainerNode.class);
- final BindingNormalizedNodeSerializer serializer = Mockito.mock(BindingNormalizedNodeSerializer.class);
-
- final Collection<DataContainerChild> list = new ArrayList<>();
- Mockito.doReturn(list).when(parent).getValue();
-
- // init child1 (will not be serialized)
- final DataContainerChild child1 = Mockito.mock(DataContainerChild.class);
- Mockito.when(child1.getIdentifier()).thenReturn(Mockito.mock(YangInstanceIdentifier.PathArgument.class));
- Mockito.when(serializer.fromNormalizedNode(Matchers.any(YangInstanceIdentifier.class), Matchers.eq(child1))).thenReturn(null);
- list.add(child1);
-
- // init child 2 (will be serialized)
- final DataContainerChild child2 = Mockito.mock(DataContainerChild.class);
- Mockito.when(child2.getIdentifier()).thenReturn(Mockito.mock(YangInstanceIdentifier.PathArgument.class));
-
- final Map.Entry entry = Mockito.mock(Map.Entry.class);
- final InstanceIdentifier<?> id = Mockito.mock(InstanceIdentifier.class);
- Mockito.doReturn(id).when(entry).getKey();
- final DataObject data = Mockito.mock(DataObject.class);
- Mockito.doReturn(data).when(entry).getValue();
- Mockito.when(serializer.fromNormalizedNode(Matchers.any(YangInstanceIdentifier.class), Matchers.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/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegatorTest.java b/v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegatorTest.java
index fed32da..086636d 100644
--- a/v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegatorTest.java
+++ b/v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegatorTest.java
@@ -37,9 +37,8 @@ import io.fd.honeycomb.v3po.data.DataModification;
import io.fd.honeycomb.v3po.translate.TranslationException;
import io.fd.honeycomb.v3po.translate.write.WriteContext;
import io.fd.honeycomb.v3po.translate.write.WriterRegistry;
-import java.util.ArrayList;
import java.util.Collections;
-import java.util.List;
+import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
@@ -58,6 +57,7 @@ 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.ModificationType;
public class ModifiableDataTreeDelegatorTest {
@@ -109,7 +109,7 @@ public class ModifiableDataTreeDelegatorTest {
when(dataTree.takeSnapshot()).thenReturn(snapshot);
when(snapshot.newModification()).thenReturn(modification);
- final DataModification dataTreeSnapshot = configDataTree.newModification();
+ configDataTree.newModification();
// Snapshot captured twice, so that original data could be provided to translation layer without any possible
// modification
verify(dataTree, times(2)).takeSnapshot();
@@ -135,12 +135,19 @@ public class ModifiableDataTreeDelegatorTest {
// Prepare modification:
final DataTreeCandidateNode rootNode = mockRootNode();
+ doReturn(ModificationType.SUBTREE_MODIFIED).when(rootNode).getModificationType();
+ doReturn(mock(YangInstanceIdentifier.PathArgument.class)).when(rootNode).getIdentifier();
+ final DataTreeCandidateNode childNode = mock(DataTreeCandidateNode.class);
+ doReturn(mock(YangInstanceIdentifier.PathArgument.class)).when(childNode).getIdentifier();
+ doReturn(Collections.singleton(childNode)).when(rootNode).getChildNodes();
+ doReturn(ModificationType.WRITE).when(childNode).getModificationType();
+
// data before:
final ContainerNode nodeBefore = mockContainerNode(dataBefore);
- when(rootNode.getDataBefore()).thenReturn(Optional.<NormalizedNode<?, ?>>fromNullable(nodeBefore));
+ when(childNode.getDataBefore()).thenReturn(Optional.fromNullable(nodeBefore));
// data after:
final ContainerNode nodeAfter = mockContainerNode(dataAfter);
- when(rootNode.getDataAfter()).thenReturn(Optional.<NormalizedNode<?, ?>>fromNullable(nodeAfter));
+ when(childNode.getDataAfter()).thenReturn(Optional.fromNullable(nodeAfter));
// Run the test
doReturn(rootNode).when(prepare).getRootNode();
@@ -160,9 +167,7 @@ public class ModifiableDataTreeDelegatorTest {
}
private Map<InstanceIdentifier<?>, DataObject> mapOf(final DataObject dataBefore, final Class<Ethernet> type) {
- return eq(
- Collections.<InstanceIdentifier<?>, DataObject>singletonMap(InstanceIdentifier.create(type),
- dataBefore));
+ return eq(Collections.singletonMap(InstanceIdentifier.create(type),dataBefore));
}
private DataObject mockDataObject(final String name, final Class<? extends DataObject> classToMock) {
@@ -194,12 +199,19 @@ public class ModifiableDataTreeDelegatorTest {
// Prepare modification:
final DataTreeCandidateNode rootNode = mockRootNode();
+ doReturn(ModificationType.SUBTREE_MODIFIED).when(rootNode).getModificationType();
+ doReturn(mock(YangInstanceIdentifier.PathArgument.class)).when(rootNode).getIdentifier();
+ final DataTreeCandidateNode childNode = mock(DataTreeCandidateNode.class);
+ doReturn(mock(YangInstanceIdentifier.PathArgument.class)).when(childNode).getIdentifier();
+ doReturn(Collections.singleton(childNode)).when(rootNode).getChildNodes();
+ doReturn(ModificationType.WRITE).when(childNode).getModificationType();
+
// data before:
final ContainerNode nodeBefore = mockContainerNode(dataBefore);
- when(rootNode.getDataBefore()).thenReturn(Optional.<NormalizedNode<?, ?>>fromNullable(nodeBefore));
+ when(childNode.getDataBefore()).thenReturn(Optional.fromNullable(nodeBefore));
// data after:
final ContainerNode nodeAfter = mockContainerNode(dataAfter);
- when(rootNode.getDataAfter()).thenReturn(Optional.<NormalizedNode<?, ?>>fromNullable(nodeAfter));
+ when(childNode.getDataAfter()).thenReturn(Optional.fromNullable(nodeAfter));
// Run the test
try {
@@ -245,12 +257,19 @@ public class ModifiableDataTreeDelegatorTest {
// Prepare modification:
final DataTreeCandidateNode rootNode = mockRootNode();
+ doReturn(ModificationType.SUBTREE_MODIFIED).when(rootNode).getModificationType();
+ doReturn(mock(YangInstanceIdentifier.PathArgument.class)).when(rootNode).getIdentifier();
+ final DataTreeCandidateNode childNode = mock(DataTreeCandidateNode.class);
+ doReturn(mock(YangInstanceIdentifier.PathArgument.class)).when(childNode).getIdentifier();
+ doReturn(Collections.singleton(childNode)).when(rootNode).getChildNodes();
+ doReturn(ModificationType.WRITE).when(childNode).getModificationType();
+
// data before:
final ContainerNode nodeBefore = mockContainerNode(dataBefore);
- when(rootNode.getDataBefore()).thenReturn(Optional.<NormalizedNode<?, ?>>fromNullable(nodeBefore));
+ when(childNode.getDataBefore()).thenReturn(Optional.fromNullable(nodeBefore));
// data after:
final ContainerNode nodeAfter = mockContainerNode(dataAfter);
- when(rootNode.getDataAfter()).thenReturn(Optional.<NormalizedNode<?, ?>>fromNullable(nodeAfter));
+ when(childNode.getDataAfter()).thenReturn(Optional.fromNullable(nodeAfter));
// Run the test
try {
@@ -267,6 +286,37 @@ public class ModifiableDataTreeDelegatorTest {
fail("RevertFailedException was expected");
}
+ @Test
+ public void testChildrenFromNormalized() throws Exception {
+ final BindingNormalizedNodeSerializer serializer = mock(BindingNormalizedNodeSerializer.class);
+
+ final Map<YangInstanceIdentifier, NormalizedNode<?, ?>> map = new HashMap<>();
+
+ // init child1 (will not be serialized)
+ final DataContainerChild child1 = mock(DataContainerChild.class);
+ when(child1.getIdentifier()).thenReturn(mock(YangInstanceIdentifier.PathArgument.class));
+ when(serializer.fromNormalizedNode(any(YangInstanceIdentifier.class), eq(child1))).thenReturn(null);
+ map.put(mock(YangInstanceIdentifier.class), 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);
+ map.put(mock(YangInstanceIdentifier.class), child2);
+
+ // run tested method
+ final Map<InstanceIdentifier<?>, DataObject> baMap =
+ ModifiableDataTreeDelegator.toBindingAware(map, serializer);
+ assertEquals(1, baMap.size());
+ assertEquals(data, baMap.get(id));
+ }
+
private DataTreeCandidateNode mockRootNode() {
final DataTreeCandidate candidate = mock(DataTreeCandidate.class);
when(dataTree.prepare(modification)).thenReturn(candidate);
@@ -277,32 +327,22 @@ public class ModifiableDataTreeDelegatorTest {
return rootNode;
}
- private ContainerNode mockContainerNode(DataObject... modifications) {
- final int numberOfChildren = modifications.length;
-
+ private ContainerNode mockContainerNode(DataObject modification) {
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();
+ final Map.Entry entry = mock(Map.Entry.class);
+ final Class<? extends DataObject> implementedInterface =
+ (Class<? extends DataObject>) modification.getImplementedInterface();
+ final InstanceIdentifier<?> id = InstanceIdentifier.create(implementedInterface);
- for (DataObject modification : modifications) {
- final DataContainerChild child = mock(DataContainerChild.class);
- when(child.getIdentifier()).thenReturn(mock(YangInstanceIdentifier.PathArgument.class));
- list.add(child);
+ doReturn(id).when(entry).getKey();
+ doReturn(modification).when(entry).getValue();
+ doReturn(entry).when(serializer).fromNormalizedNode(any(YangInstanceIdentifier.class), eq(node));
- final Map.Entry entry = mock(Map.Entry.class);
- final Class<? extends DataObject> implementedInterface =
- (Class<? extends DataObject>) modification.getImplementedInterface();
- final InstanceIdentifier<?> id = InstanceIdentifier.create(implementedInterface);
-
- doReturn(id).when(entry).getKey();
- doReturn(modification).when(entry).getValue();
- doReturn(entry).when(serializer).fromNormalizedNode(any(YangInstanceIdentifier.class), eq(child));
- }
return node;
}
}
diff --git a/v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModificationDiffTest.java b/v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModificationDiffTest.java
new file mode 100644
index 0000000..f5c2706
--- /dev/null
+++ b/v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModificationDiffTest.java
@@ -0,0 +1,376 @@
+package io.fd.honeycomb.v3po.data.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Map;
+import org.junit.Test;
+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.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateTip;
+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.opendaylight.yangtools.yang.data.api.schema.tree.TipProducingDataTree;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.tree.InMemoryDataTreeFactory;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
+import org.opendaylight.yangtools.yang.parser.stmt.reactor.CrossSourceStatementReactor;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangInferencePipeline;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangStatementSourceImpl;
+
+public class ModificationDiffTest {
+
+ private static final QName TOP_CONTAINER_QNAME = QName.create("urn:opendaylight:params:xml:ns:yang:test:diff", "2015-01-05", "top-container");
+ private static final QName STRING_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "string");
+ private static final QName NAME_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "name");
+ private static final QName TEXT_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "text");
+ private static final QName NESTED_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "nested-list");
+ private static final QName DEEP_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "deep-list");
+
+ private YangInstanceIdentifier topContainerId = YangInstanceIdentifier.of(TOP_CONTAINER_QNAME);;
+
+ @Test
+ public void testInitialWrite() throws Exception {
+ final TipProducingDataTree dataTree = getDataTree();
+ final DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
+ final DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
+ final NormalizedNode<?, ?> topContainer = getTopContainer("string1");
+ final YangInstanceIdentifier topContainerId = YangInstanceIdentifier.of(TOP_CONTAINER_QNAME);
+ dataTreeModification.write(topContainerId, topContainer);
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ final DataTreeCandidateTip prepare = dataTree.prepare(dataTreeModification);
+
+ final ModifiableDataTreeDelegator.ModificationDiff modificationDiff =
+ ModifiableDataTreeDelegator.ModificationDiff
+ .recursivelyFromCandidate(YangInstanceIdentifier.EMPTY, prepare.getRootNode());
+
+ assertTrue(modificationDiff.getModificationsBefore().isEmpty());
+ assertAfter(topContainer, topContainerId, modificationDiff);
+ }
+
+ @Test
+ public void testUpdateWrite() throws Exception {
+ final TipProducingDataTree dataTree = getDataTree();
+ final NormalizedNode<?, ?> topContainerBefore = addTopContainer(dataTree);
+
+ final DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
+ final DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
+ final NormalizedNode<?, ?> topContainerAfter = getTopContainer("string2");
+ dataTreeModification.write(topContainerId, topContainerAfter);
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ final DataTreeCandidateTip prepare = dataTree.prepare(dataTreeModification);
+
+ final ModifiableDataTreeDelegator.ModificationDiff modificationDiff =
+ ModifiableDataTreeDelegator.ModificationDiff
+ .recursivelyFromCandidate(YangInstanceIdentifier.EMPTY, prepare.getRootNode());
+
+ assertBefore(topContainerBefore, topContainerId, modificationDiff);
+ assertAfter(topContainerAfter, topContainerId, modificationDiff);
+ }
+
+ @Test
+ public void testUpdateMerge() throws Exception {
+ final TipProducingDataTree dataTree = getDataTree();
+ final NormalizedNode<?, ?> topContainerBefore = addTopContainer(dataTree);
+
+ final DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
+ final DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
+ final NormalizedNode<?, ?> topContainerAfter = getTopContainer("string2");
+ dataTreeModification.merge(topContainerId, topContainerAfter);
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ final DataTreeCandidateTip prepare = dataTree.prepare(dataTreeModification);
+
+ final ModifiableDataTreeDelegator.ModificationDiff modificationDiff =
+ ModifiableDataTreeDelegator.ModificationDiff
+ .recursivelyFromCandidate(YangInstanceIdentifier.EMPTY, prepare.getRootNode());
+
+ assertBefore(topContainerBefore, topContainerId, modificationDiff);
+ assertAfter(topContainerAfter, topContainerId, modificationDiff);
+ }
+
+ @Test
+ public void testUpdateDelete() throws Exception {
+ final TipProducingDataTree dataTree = getDataTree();
+ final NormalizedNode<?, ?> topContainerBefore = addTopContainer(dataTree);
+
+ final DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
+ final DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
+ dataTreeModification.delete(topContainerId);
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ final DataTreeCandidateTip prepare = dataTree.prepare(dataTreeModification);
+
+ final ModifiableDataTreeDelegator.ModificationDiff modificationDiff =
+ ModifiableDataTreeDelegator.ModificationDiff
+ .recursivelyFromCandidate(YangInstanceIdentifier.EMPTY, prepare.getRootNode());
+
+ assertBefore(topContainerBefore, topContainerId, modificationDiff);
+ assertTrue(modificationDiff.getModificationsAfter().isEmpty());
+ }
+
+ @Test
+ public void testWriteAndUpdateInnerList() throws Exception {
+ final TipProducingDataTree dataTree = getDataTree();
+ addTopContainer(dataTree);
+
+ DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
+ DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
+ final YangInstanceIdentifier listId =
+ YangInstanceIdentifier.create(
+ new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME),
+ new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME));
+
+ final MapNode mapNode = getNestedList("name1", "text");
+ dataTreeModification.write(listId, mapNode);
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ DataTreeCandidateTip prepare = dataTree.prepare(dataTreeModification);
+
+ ModifiableDataTreeDelegator.ModificationDiff modificationDiff =
+ ModifiableDataTreeDelegator.ModificationDiff
+ .recursivelyFromCandidate(YangInstanceIdentifier.EMPTY, prepare.getRootNode());
+
+ assertTrue(modificationDiff.getModificationsBefore().isEmpty());
+ assertAfter(mapNode, listId, modificationDiff);
+
+ // Commit so that update can be tested next
+ dataTree.commit(prepare);
+
+ YangInstanceIdentifier listItemId = listId.node(
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(NESTED_LIST_QNAME, NAME_LEAF_QNAME, "name1"));
+ MapEntryNode mapEntryNode =
+ getNestedList("name1", "text-update").getValue().iterator().next();
+
+ dataTreeSnapshot = dataTree.takeSnapshot();
+ dataTreeModification = dataTreeSnapshot.newModification();
+ dataTreeModification.write(listItemId, mapEntryNode);
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ prepare = dataTree.prepare(dataTreeModification);
+
+ modificationDiff =
+ ModifiableDataTreeDelegator.ModificationDiff
+ .recursivelyFromCandidate(YangInstanceIdentifier.EMPTY, prepare.getRootNode());
+
+ assertBefore(mapNode.getValue().iterator().next(), listItemId, modificationDiff);
+ assertAfter(mapEntryNode, listItemId, modificationDiff);
+ }
+
+ @Test
+ public void testWriteTopContainerAndInnerList() throws Exception {
+ final TipProducingDataTree dataTree = getDataTree();
+
+ DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
+ DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
+
+ final ContainerNode topContainer = getTopContainer("string1");
+ dataTreeModification.write(topContainerId, topContainer);
+
+ final YangInstanceIdentifier listId =
+ YangInstanceIdentifier.create(
+ new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME),
+ new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME));
+
+ final MapNode mapNode = getNestedList("name1", "text");
+ dataTreeModification.write(listId, mapNode);
+
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ final DataTreeCandidateTip prepare = dataTree.prepare(dataTreeModification);
+
+ final ModifiableDataTreeDelegator.ModificationDiff modificationDiff =
+ ModifiableDataTreeDelegator.ModificationDiff
+ .recursivelyFromCandidate(YangInstanceIdentifier.EMPTY, prepare.getRootNode());
+
+ assertTrue(modificationDiff.getModificationsBefore().isEmpty());
+
+ // TODO HONEYCOMB-94 2 after modifications should appear, for top-container and nested-list entry
+ assertAfter(Builders.containerBuilder(topContainer)
+ .withChild(mapNode)
+ .build(),
+ topContainerId, modificationDiff);
+ }
+
+ @Test
+ public void testWriteDeepList() throws Exception {
+ final TipProducingDataTree dataTree = getDataTree();
+ addTopContainer(dataTree);
+
+ DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
+ DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
+
+ YangInstanceIdentifier listId =
+ YangInstanceIdentifier.create(
+ new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME),
+ new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME));
+
+ MapNode mapNode = getNestedList("name1", "text");
+ dataTreeModification.write(listId, mapNode);
+
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ DataTreeCandidateTip prepare = dataTree.prepare(dataTreeModification);
+ dataTree.commit(prepare);
+
+ dataTreeSnapshot = dataTree.takeSnapshot();
+ dataTreeModification = dataTreeSnapshot.newModification();
+
+ final YangInstanceIdentifier.NodeIdentifierWithPredicates nestedListNodeId =
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(NESTED_LIST_QNAME, NAME_LEAF_QNAME, "name1");
+ listId = YangInstanceIdentifier.create(
+ new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME),
+ new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME),
+ nestedListNodeId);
+ final YangInstanceIdentifier deepListId = listId.node(new YangInstanceIdentifier.NodeIdentifier(DEEP_LIST_QNAME));
+ final YangInstanceIdentifier deepListEntryId = deepListId.node(
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(DEEP_LIST_QNAME, NAME_LEAF_QNAME,"name1"));
+
+ final MapEntryNode deepListEntry = getDeepList("name1").getValue().iterator().next();
+ // Merge parent list, just to see no modifications on it
+ dataTreeModification.merge(
+ listId,
+ Builders.mapEntryBuilder().withNodeIdentifier(nestedListNodeId).withChild(ImmutableNodes.leafNode(NAME_LEAF_QNAME, "name1")).build());
+ dataTreeModification.merge(
+ deepListId,
+ Builders.mapBuilder().withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(DEEP_LIST_QNAME)).build());
+ dataTreeModification.merge(
+ deepListEntryId,
+ deepListEntry);
+
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ prepare = dataTree.prepare(dataTreeModification);
+ dataTree.commit(prepare);
+
+ final ModifiableDataTreeDelegator.ModificationDiff modificationDiff = ModifiableDataTreeDelegator.ModificationDiff
+ .recursivelyFromCandidate(YangInstanceIdentifier.EMPTY, prepare.getRootNode());
+
+ assertTrue(modificationDiff.getModificationsBefore().isEmpty());
+ assertAfter(getDeepList("name1"), deepListId, modificationDiff);
+ }
+
+ @Test
+ public void testDeleteInnerListItem() throws Exception {
+ final TipProducingDataTree dataTree = getDataTree();
+ addTopContainer(dataTree);
+
+ DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
+ DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
+ final YangInstanceIdentifier listId =
+ YangInstanceIdentifier.create(
+ new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME),
+ new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME));
+
+ final MapNode mapNode = getNestedList("name1", "text");
+ dataTreeModification.write(listId, mapNode);
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ DataTreeCandidateTip prepare = dataTree.prepare(dataTreeModification);
+
+ // Commit so that update can be tested next
+ dataTree.commit(prepare);
+
+ YangInstanceIdentifier listItemId = listId.node(
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(NESTED_LIST_QNAME, NAME_LEAF_QNAME, "name1"));
+
+ dataTreeSnapshot = dataTree.takeSnapshot();
+ dataTreeModification = dataTreeSnapshot.newModification();
+ dataTreeModification.delete(listItemId);
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ prepare = dataTree.prepare(dataTreeModification);
+
+ final ModifiableDataTreeDelegator.ModificationDiff modificationDiff =
+ ModifiableDataTreeDelegator.ModificationDiff
+ .recursivelyFromCandidate(YangInstanceIdentifier.EMPTY, prepare.getRootNode());
+
+ assertBefore(mapNode.getValue().iterator().next(), listItemId, modificationDiff);
+ assertTrue(modificationDiff.getModificationsAfter().isEmpty());
+ }
+
+ private NormalizedNode<?, ?> addTopContainer(final TipProducingDataTree dataTree) throws DataValidationFailedException {
+ DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
+ DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
+ final NormalizedNode<?, ?> topContainerBefore = getTopContainer("string1");
+ dataTreeModification.write(topContainerId, topContainerBefore);
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ DataTreeCandidateTip prepare = dataTree.prepare(dataTreeModification);
+ dataTree.commit(prepare);
+ return topContainerBefore;
+ }
+
+ private void assertAfter(final NormalizedNode<?, ?> topContainer, final YangInstanceIdentifier topContainerId,
+ final ModifiableDataTreeDelegator.ModificationDiff modificationDiff) {
+ assertModification(topContainer, topContainerId, modificationDiff.getModificationsAfter());
+ }
+
+ private void assertModification(final NormalizedNode<?, ?> topContainer,
+ final YangInstanceIdentifier topContainerId,
+ final Map<YangInstanceIdentifier, NormalizedNode<?, ?>> modificationMap) {
+ assertEquals(1, modificationMap.keySet().size());
+ assertEquals(topContainerId, modificationMap.keySet().iterator().next());
+ assertEquals(topContainer, modificationMap.values().iterator().next());
+ }
+
+ private void assertBefore(final NormalizedNode<?, ?> topContainerBefore,
+ final YangInstanceIdentifier topContainerId,
+ final ModifiableDataTreeDelegator.ModificationDiff modificationDiff) {
+ assertModification(topContainerBefore, topContainerId, modificationDiff.getModificationsBefore());
+ }
+
+ private TipProducingDataTree getDataTree() throws ReactorException {
+ final TipProducingDataTree dataTree = InMemoryDataTreeFactory.getInstance().create(TreeType.CONFIGURATION);
+ dataTree.setSchemaContext(getSchemaCtx());
+ return dataTree;
+ }
+
+ private ContainerNode getTopContainer(final String stringValue) {
+ return Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+ .withChild(ImmutableNodes.leafNode(STRING_LEAF_QNAME, stringValue))
+ .build();
+ }
+
+ private MapNode getNestedList(final String listItemName, final String text) {
+ return Builders.mapBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME))
+ .withChild(
+ Builders.mapEntryBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifierWithPredicates(NESTED_LIST_QNAME, NAME_LEAF_QNAME, listItemName))
+ .withChild(ImmutableNodes.leafNode(NAME_LEAF_QNAME, listItemName))
+ .withChild(ImmutableNodes.leafNode(TEXT_LEAF_QNAME, text))
+ .build()
+ )
+ .build();
+ }
+
+ private MapNode getDeepList(final String listItemName) {
+ return Builders.mapBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(DEEP_LIST_QNAME))
+ .withChild(
+ Builders.mapEntryBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifierWithPredicates(DEEP_LIST_QNAME, NAME_LEAF_QNAME, listItemName))
+ .withChild(ImmutableNodes.leafNode(NAME_LEAF_QNAME, listItemName))
+ .build()
+ )
+ .build();
+ }
+
+ private SchemaContext getSchemaCtx() throws ReactorException {
+ final CrossSourceStatementReactor.BuildAction buildAction = YangInferencePipeline.RFC6020_REACTOR.newBuild();
+ buildAction.addSource(new YangStatementSourceImpl(getClass().getResourceAsStream("/test-diff.yang")));
+ return buildAction.buildEffective();
+ }
+} \ No newline at end of file
diff --git a/v3po/data-impl/src/test/resources/test-diff.yang b/v3po/data-impl/src/test/resources/test-diff.yang
new file mode 100644
index 0000000..7e8721f
--- /dev/null
+++ b/v3po/data-impl/src/test/resources/test-diff.yang
@@ -0,0 +1,37 @@
+module test-diff {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:test:diff";
+ prefix "td";
+
+ revision "2015-01-05" {
+ description "Initial revision";
+ }
+
+ container top-container {
+ leaf string {
+ type string;
+ }
+
+ list nested-list {
+ key "name";
+
+ leaf name {
+ type string;
+ }
+
+ leaf text {
+ type string;
+ }
+
+ list deep-list {
+ key "name";
+
+ leaf name {
+ type string;
+ }
+
+ }
+ }
+ }
+
+}
diff --git a/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/AbstractCompositeWriter.java b/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/AbstractCompositeWriter.java
index 2a08da1..0212c08 100644
--- a/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/AbstractCompositeWriter.java
+++ b/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/AbstractCompositeWriter.java
@@ -220,11 +220,16 @@ public abstract class AbstractCompositeWriter<D extends DataObject> implements W
private void writeSubtree(final InstanceIdentifier<? extends DataObject> id,
final DataObject dataAfter, final WriteContext ctx) throws WriteFailedException {
LOG.debug("{}: Writing subtree: {}", this, id);
- final Writer<? extends ChildOf<D>> writer = getNextWriter(id);
+
+ final Writer<? extends ChildOf<D>> writer = getNextChildWriter(id);
+ final Writer<? extends Augmentation<D>> augWriter = getNextAgumentationWriter(id);
if (writer != null) {
LOG.debug("{}: Writing subtree: {} in: {}", this, id, writer);
writer.update(id, null, dataAfter, ctx);
+ } else if (augWriter != null) {
+ LOG.debug("{}: Updating augmented subtree: {} in: {}", this, id, augWriter);
+ augWriter.update(id, null, dataAfter, ctx);
} else {
// If there's no dedicated writer, use write current
// But we need current data after to do so
@@ -243,12 +248,17 @@ public abstract class AbstractCompositeWriter<D extends DataObject> implements W
private void deleteSubtree(final InstanceIdentifier<? extends DataObject> id,
final DataObject dataBefore, final WriteContext ctx) throws WriteFailedException {
LOG.debug("{}: Deleting subtree: {}", this, id);
- final Writer<? extends ChildOf<D>> writer = getNextWriter(id);
+
+ final Writer<? extends ChildOf<D>> writer = getNextChildWriter(id);
+ final Writer<? extends Augmentation<D>> augWriter = getNextAgumentationWriter(id);
if (writer != null) {
LOG.debug("{}: Deleting subtree: {} in: {}", this, id, writer);
writer.update(id, dataBefore, null, ctx);
- } else {
+ } else if (augWriter != null) {
+ LOG.debug("{}: Updating augmented subtree: {} in: {}", this, id, augWriter);
+ augWriter.update(id, dataBefore, null, ctx);
+ } else {
updateSubtreeFromCurrent(id, ctx);
}
}
@@ -270,21 +280,30 @@ public abstract class AbstractCompositeWriter<D extends DataObject> implements W
final DataObject dataAfter,
final WriteContext ctx) throws WriteFailedException {
LOG.debug("{}: Updating subtree: {}", this, id);
- final Writer<? extends ChildOf<D>> writer = getNextWriter(id);
+ final Writer<? extends ChildOf<D>> writer = getNextChildWriter(id);
+ final Writer<? extends Augmentation<D>> augWriter = getNextAgumentationWriter(id);
if (writer != null) {
LOG.debug("{}: Updating subtree: {} in: {}", this, id, writer);
writer.update(id, dataBefore, dataAfter, ctx);
+ } else if (augWriter != null) {
+ LOG.debug("{}: Updating augmented subtree: {} in: {}", this, id, augWriter);
+ augWriter.update(id, dataBefore, dataAfter, ctx);
} else {
updateSubtreeFromCurrent(id, ctx);
}
}
- private Writer<? extends ChildOf<D>> getNextWriter(final InstanceIdentifier<? extends DataObject> id) {
+ private Writer<? extends ChildOf<D>> getNextChildWriter(final InstanceIdentifier<? extends DataObject> id) {
final Class<? extends DataObject> next = RWUtils.getNextId(id, getManagedDataObjectType()).getType();
return childWriters.get(next);
}
+ private Writer<? extends Augmentation<D>> getNextAgumentationWriter(final InstanceIdentifier<? extends DataObject> id) {
+ final Class<? extends DataObject> next = RWUtils.getNextId(id, getManagedDataObjectType()).getType();
+ return augWriters.get(next);
+ }
+
private static <T> List<T> reverseCollection(final Collection<T> original) {
// TODO find a better reverse mechanism (probably a different collection for child writers is necessary)
final ArrayList<T> list = Lists.newArrayList(original);
diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/DelegatingWriterRegistry.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/DelegatingWriterRegistry.java
index 8b981af..061d3fa 100644
--- a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/DelegatingWriterRegistry.java
+++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/DelegatingWriterRegistry.java
@@ -19,11 +19,10 @@ package io.fd.honeycomb.v3po.translate.util.write;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
-import com.google.common.base.Function;
-import com.google.common.collect.Collections2;
+import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
+import com.google.common.collect.Multimap;
import io.fd.honeycomb.v3po.translate.util.RWUtils;
import io.fd.honeycomb.v3po.translate.write.WriteContext;
import io.fd.honeycomb.v3po.translate.write.WriteFailedException;
@@ -32,7 +31,6 @@ import io.fd.honeycomb.v3po.translate.write.WriterRegistry;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.opendaylight.yangtools.yang.binding.DataObject;
@@ -88,8 +86,15 @@ public final class DelegatingWriterRegistry implements WriterRegistry {
public void update(@Nonnull final Map<InstanceIdentifier<?>, DataObject> nodesBefore,
@Nonnull final Map<InstanceIdentifier<?>, DataObject> nodesAfter,
@Nonnull final WriteContext ctx) throws WriteFailedException {
- checkAllWritersPresent(nodesBefore);
- checkAllWritersPresent(nodesAfter);
+
+ Multimap<InstanceIdentifier<?>, InstanceIdentifier<?>> rootIdToNestedIds = HashMultimap.create();
+ try {
+ checkAllWritersPresent(nodesBefore, rootIdToNestedIds);
+ checkAllWritersPresent(nodesAfter, rootIdToNestedIds);
+ } catch (IllegalArgumentException e) {
+ LOG.warn("Unable to process update", e);
+ throw e;
+ }
final List<InstanceIdentifier<?>> processedNodes = Lists.newArrayList();
@@ -97,36 +102,41 @@ public final class DelegatingWriterRegistry implements WriterRegistry {
.entrySet()) {
final InstanceIdentifier<? extends DataObject> id = rootWriterEntry.getValue().getManagedDataObjectType();
+ // FIXME !! this is not ideal, we are not handling nested updates in expected order
+ // Root writers are invoked in order they were registered, but nested updates are not, since they are
+ // iterated here.
+ //
+ for (InstanceIdentifier<?> specificInstanceIdentifier : rootIdToNestedIds.get(id)) {
+ final DataObject dataBefore = nodesBefore.get(specificInstanceIdentifier);
+ final DataObject dataAfter = nodesAfter.get(specificInstanceIdentifier);
+
+ // No change to current writer
+ if (dataBefore == null && dataAfter == null) {
+ continue;
+ }
- final DataObject dataBefore = nodesBefore.get(id);
- final DataObject dataAfter = nodesAfter.get(id);
-
- // No change to current writer
- if (dataBefore == null && dataAfter == null) {
- continue;
- }
-
- LOG.debug("ChangesProcessor.applyChanges() processing dataBefore={}, dataAfter={}", dataBefore, dataAfter);
-
- try {
- update(id, dataBefore, dataAfter, ctx);
- processedNodes.add(id);
- } 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);
+ try {
+ LOG.debug("ChangesProcessor.applyChanges() processing dataBefore={}, dataAfter={}", dataBefore, dataAfter);
+ update(specificInstanceIdentifier, dataBefore, dataAfter, ctx);
+ processedNodes.add(id);
+ } 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) {
- final Set<Class<? extends DataObject>> nodesBeforeClasses =
- Sets.newHashSet(Collections2.transform(nodesBefore.keySet(),
- (Function<InstanceIdentifier<?>, Class<? extends DataObject>>) InstanceIdentifier::getTargetType));
- checkArgument(rootWriters.keySet().containsAll(nodesBeforeClasses),
- "Unable to handle all changes. Missing dedicated writers for: %s",
- Sets.difference(nodesBeforeClasses, rootWriters.keySet()));
+ private void checkAllWritersPresent(final @Nonnull Map<InstanceIdentifier<?>, DataObject> nodesBefore,
+ final @Nonnull Multimap<InstanceIdentifier<?>, InstanceIdentifier<?>> rootIdToNestedIds) {
+ for (final InstanceIdentifier<?> changeId : nodesBefore.keySet()) {
+ final InstanceIdentifier.PathArgument first = Iterables.getFirst(changeId.getPathArguments(), null);
+ checkNotNull(first, "Empty identifier detected");
+ final InstanceIdentifier<? extends DataObject> rootId = InstanceIdentifier.create(first.getType());
+ checkArgument(rootWriters.keySet().contains(first.getType()),
+ "Unable to handle change. Missing dedicated writer for: %s", first.getType());
+ rootIdToNestedIds.put(rootId, changeId);
+ }
}
private static final class ReverterImpl implements Reverter {
diff --git a/v3po/vpp-cfg-init/src/main/java/io/fd/honeycomb/v3po/vpp/data/init/AbstractDataTreeConverter.java b/v3po/vpp-cfg-init/src/main/java/io/fd/honeycomb/v3po/vpp/data/init/AbstractDataTreeConverter.java
index 575f1d8..f005826 100644
--- a/v3po/vpp-cfg-init/src/main/java/io/fd/honeycomb/v3po/vpp/data/init/AbstractDataTreeConverter.java
+++ b/v3po/vpp-cfg-init/src/main/java/io/fd/honeycomb/v3po/vpp/data/init/AbstractDataTreeConverter.java
@@ -97,7 +97,10 @@ public abstract class AbstractDataTreeConverter<O extends DataObject, C extends
private void writeData(final C configData) throws TransactionCommitFailedException {
final WriteTransaction writeTx = bindingDataBroker.newWriteOnlyTransaction();
- writeTx.put(LogicalDatastoreType.CONFIGURATION, idConfig, configData);
+ // Merge(instead of put) has to be used due to dynamic start, this might be executed multiple times
+ // and might overwrite config restored from persisted file with the same incomplete config.
+ // Making the entire configuration trigger VPP twice (on second persis ... and VPP does not like that
+ writeTx.merge(LogicalDatastoreType.CONFIGURATION, idConfig, configData);
writeTx.submit().checkedGet();
}