From d23f4be2c62a8fbab984fc7dea8ec2e317b8a662 Mon Sep 17 00:00:00 2001 From: Maros Marsalek Date: Tue, 14 Jun 2016 10:48:26 +0200 Subject: HONEYCOMB-92: Process modifications recursively + Fix update subtree, child writer lookup + Change initializers operation to merge Change-Id: I6ece7eb3d17d5a0b4a413189ddd383567d7e2270 Signed-off-by: Maros Marsalek --- .../fd/honeycomb/v3po/data/impl/DataTreeUtils.java | 77 ----- .../data/impl/ModifiableDataTreeDelegator.java | 200 ++++++++++- .../v3po/data/impl/DataTreeUtilsTest.java | 68 ---- .../data/impl/ModifiableDataTreeDelegatorTest.java | 100 ++++-- .../v3po/data/impl/ModificationDiffTest.java | 376 +++++++++++++++++++++ v3po/data-impl/src/test/resources/test-diff.yang | 37 ++ .../impl/write/AbstractCompositeWriter.java | 29 +- .../util/write/DelegatingWriterRegistry.java | 72 ++-- .../vpp/data/init/AbstractDataTreeConverter.java | 5 +- 9 files changed, 737 insertions(+), 227 deletions(-) delete mode 100644 v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/DataTreeUtils.java delete mode 100644 v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/DataTreeUtilsTest.java create mode 100644 v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModificationDiffTest.java create mode 100644 v3po/data-impl/src/test/resources/test-diff.yang (limited to 'v3po') 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 5833459ea..000000000 --- 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, 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, DataObject> map = new HashMap<>(); - - final Collection> children = - parent.getValue(); - - for (final DataContainerChild 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, 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 75ffb702a..c8d258b43 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> normalizedDataBefore = rootNode.getDataBefore(); - final Optional> 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, DataObject> nodesBefore = toBindingAware(normalizedDataBefore); + final ModificationDiff modificationDiff = + ModificationDiff.recursivelyFromCandidate(YangInstanceIdentifier.EMPTY, rootNode); + LOG.debug("ConfigDataTree.modify() diff: {}", modificationDiff); + + final Map, DataObject> nodesBefore = toBindingAware(modificationDiff.getModificationsBefore()); LOG.debug("ConfigDataTree.modify() extracted nodesBefore={}", nodesBefore.keySet()); - final Map, DataObject> nodesAfter = toBindingAware(normalizedDataAfter); + final Map, 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, DataObject> toBindingAware(final Optional> parentOptional) { - if (parentOptional.isPresent()) { - final DataContainerNode parent = (DataContainerNode) parentOptional.get(); - return childrenFromNormalized(parent, serializer); + private Map, DataObject> toBindingAware(final Map> biNodes) { + return ModifiableDataTreeDelegator.toBindingAware(biNodes, serializer); + } + } + + @VisibleForTesting + static Map, DataObject> toBindingAware(final Map> biNodes, + final BindingNormalizedNodeSerializer serializer) { + final HashMap, DataObject> transformed = new HashMap<>(biNodes.size()); + for (Map.Entry> biEntry : biNodes.entrySet()) { + final Map.Entry, 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> modificationsBefore; + private final Map> modificationsAfter; + + private ModificationDiff(@Nonnull final Map> modificationsBefore, + @Nonnull final Map> modificationsAfter) { + this.modificationsBefore = modificationsBefore; + this.modificationsAfter = modificationsAfter; + } + + Map> getModificationsBefore() { + return modificationsBefore; + } + + Map> 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> join( + final Map> mapOne, + final Map> mapTwo) { + // Check unique modifications + // TODO Probably not necessary to check + final Sets.SetView duplicates = Sets.intersection(mapOne.keySet(), mapTwo.keySet()); + checkArgument(duplicates.size() == 0, "Duplicates detected: %s. In maps: %s and %s", duplicates, mapOne, mapTwo); + final HashMap> 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 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 40792a135..000000000 --- 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 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, 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 fed32da8a..086636de6 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.>fromNullable(nodeBefore)); + when(childNode.getDataBefore()).thenReturn(Optional.fromNullable(nodeBefore)); // data after: final ContainerNode nodeAfter = mockContainerNode(dataAfter); - when(rootNode.getDataAfter()).thenReturn(Optional.>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, DataObject> mapOf(final DataObject dataBefore, final Class type) { - return eq( - Collections., DataObject>singletonMap(InstanceIdentifier.create(type), - dataBefore)); + return eq(Collections.singletonMap(InstanceIdentifier.create(type),dataBefore)); } private DataObject mockDataObject(final String name, final Class 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.>fromNullable(nodeBefore)); + when(childNode.getDataBefore()).thenReturn(Optional.fromNullable(nodeBefore)); // data after: final ContainerNode nodeAfter = mockContainerNode(dataAfter); - when(rootNode.getDataAfter()).thenReturn(Optional.>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.>fromNullable(nodeBefore)); + when(childNode.getDataBefore()).thenReturn(Optional.fromNullable(nodeBefore)); // data after: final ContainerNode nodeAfter = mockContainerNode(dataAfter); - when(rootNode.getDataAfter()).thenReturn(Optional.>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> 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, 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 list = new ArrayList<>(numberOfChildren); - doReturn(list).when(node).getValue(); + final Map.Entry entry = mock(Map.Entry.class); + final Class implementedInterface = + (Class) 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 implementedInterface = - (Class) 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 000000000..f5c270618 --- /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> 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 000000000..7e8721f00 --- /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 2a08da12c..0212c086b 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 implements W private void writeSubtree(final InstanceIdentifier id, final DataObject dataAfter, final WriteContext ctx) throws WriteFailedException { LOG.debug("{}: Writing subtree: {}", this, id); - final Writer> writer = getNextWriter(id); + + final Writer> writer = getNextChildWriter(id); + final Writer> 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 implements W private void deleteSubtree(final InstanceIdentifier id, final DataObject dataBefore, final WriteContext ctx) throws WriteFailedException { LOG.debug("{}: Deleting subtree: {}", this, id); - final Writer> writer = getNextWriter(id); + + final Writer> writer = getNextChildWriter(id); + final Writer> 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 implements W final DataObject dataAfter, final WriteContext ctx) throws WriteFailedException { LOG.debug("{}: Updating subtree: {}", this, id); - final Writer> writer = getNextWriter(id); + final Writer> writer = getNextChildWriter(id); + final Writer> 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> getNextWriter(final InstanceIdentifier id) { + private Writer> getNextChildWriter(final InstanceIdentifier id) { final Class next = RWUtils.getNextId(id, getManagedDataObjectType()).getType(); return childWriters.get(next); } + private Writer> getNextAgumentationWriter(final InstanceIdentifier id) { + final Class next = RWUtils.getNextId(id, getManagedDataObjectType()).getType(); + return augWriters.get(next); + } + private static List reverseCollection(final Collection original) { // TODO find a better reverse mechanism (probably a different collection for child writers is necessary) final ArrayList 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 8b981af0d..061d3fa4a 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, DataObject> nodesBefore, @Nonnull final Map, DataObject> nodesAfter, @Nonnull final WriteContext ctx) throws WriteFailedException { - checkAllWritersPresent(nodesBefore); - checkAllWritersPresent(nodesAfter); + + Multimap, 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> processedNodes = Lists.newArrayList(); @@ -97,36 +102,41 @@ public final class DelegatingWriterRegistry implements WriterRegistry { .entrySet()) { final InstanceIdentifier 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, DataObject> nodesBefore) { - final Set> nodesBeforeClasses = - Sets.newHashSet(Collections2.transform(nodesBefore.keySet(), - (Function, Class>) 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, DataObject> nodesBefore, + final @Nonnull Multimap, InstanceIdentifier> rootIdToNestedIds) { + for (final InstanceIdentifier changeId : nodesBefore.keySet()) { + final InstanceIdentifier.PathArgument first = Iterables.getFirst(changeId.getPathArguments(), null); + checkNotNull(first, "Empty identifier detected"); + final InstanceIdentifier 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 575f1d860..f0058264e 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