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 +++++++++++++++++++-- 2 files changed, 185 insertions(+), 92 deletions(-) delete mode 100644 v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/DataTreeUtils.java (limited to 'v3po/data-impl/src/main/java/io/fd') 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 + + '}'; } } } -- cgit 1.2.3-korg