BUG-2399: Implement automatic container removal 30/17030/26
authorJan Hajnar <jhajnar@cisco.com>
Wed, 20 May 2015 09:47:30 +0000 (11:47 +0200)
committerGerrit Code Review <gerrit@opendaylight.org>
Wed, 28 Oct 2015 15:04:20 +0000 (15:04 +0000)
Structural containers all all container nodes which do not contain a
presence statement. RFC6020 specifies that structural containers have no
semantic meaning aside from being containment nodes for their children
and therefore can be created when a child would appear and should
disappear when they lose their last child.

Containers which should not be subject to this lifecycle need to be
marked with a 'presence' statement, which attaches semantic meaning
to their presence.

Change-Id: Icb69c959a27726b549f563b84b52727592b67cb1
Signed-off-by: Robert Varga <rovarga@cisco.com>
Signed-off-by: Jan Hajnar <jhajnar@cisco.com>
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractModifiedNodeBasedCandidateNode.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ChoiceModificationStrategy.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/DataTreeState.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTree.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/PresenceContainerModificationStrategy.java [moved from yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ContainerModificationStrategy.java with 83% similarity]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/SchemaAwareApplyOperation.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/StructuralContainerModificationStrategy.java [new file with mode: 0644]
yang/yang-data-impl/src/test/resources/list-constraints-validation-test-model.yang
yang/yang-data-impl/src/test/resources/odl-datastore-test.yang
yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/DataSchemaContextTree.java

index 25fb2df24078880b78bb1ad3f2ec3600899fad72..876b2855a334456065121cda9d3fcab55363aa98 100644 (file)
@@ -84,6 +84,8 @@ abstract class AbstractModifiedNodeBasedCandidateNode implements DataTreeCandida
     @Override
     public Collection<DataTreeCandidateNode> getChildNodes() {
         switch (mod.getModificationType()) {
+        case APPEARED:
+        case DISAPPEARED:
         case SUBTREE_MODIFIED:
             return Collections2.transform(mod.getChildren(), new Function<ModifiedNode, DataTreeCandidateNode>() {
                 @Override
@@ -139,6 +141,8 @@ abstract class AbstractModifiedNodeBasedCandidateNode implements DataTreeCandida
     @Override
     public final DataTreeCandidateNode getModifiedChild(final PathArgument identifier) {
         switch (mod.getModificationType()) {
+        case APPEARED:
+        case DISAPPEARED:
         case SUBTREE_MODIFIED:
             final Optional<ModifiedNode> childMod = mod.getChild(identifier);
             if (childMod.isPresent()) {
@@ -178,4 +182,4 @@ abstract class AbstractModifiedNodeBasedCandidateNode implements DataTreeCandida
             return getMod().getIdentifier();
         }
     }
-}
\ No newline at end of file
+}
index c418c16c006731cdf0eee02591d7fdae2b6ce31e..47f1e66b4e33527fbf556c7d388b7538c6753e43 100644 (file)
@@ -30,10 +30,10 @@ final class ChoiceModificationStrategy extends AbstractNodeContainerModification
         final ImmutableMap.Builder<YangInstanceIdentifier.PathArgument, ModificationApplyOperation> child = ImmutableMap.builder();
 
         for (final ChoiceCaseNode caze : schemaNode.getCases()) {
-            if(SchemaAwareApplyOperation.belongsToTree(treeType,caze)) {
+            if (SchemaAwareApplyOperation.belongsToTree(treeType,caze)) {
                 for (final DataSchemaNode cazeChild : caze.getChildNodes()) {
-                    if(SchemaAwareApplyOperation.belongsToTree(treeType,cazeChild)) {
-                        final SchemaAwareApplyOperation childNode = SchemaAwareApplyOperation.from(cazeChild,treeType);
+                    if (SchemaAwareApplyOperation.belongsToTree(treeType,cazeChild)) {
+                        final ModificationApplyOperation childNode = SchemaAwareApplyOperation.from(cazeChild, treeType);
                         child.put(NodeIdentifier.create(cazeChild.getQName()), childNode);
                     }
                 }
@@ -53,4 +53,4 @@ final class ChoiceModificationStrategy extends AbstractNodeContainerModification
         checkArgument(original instanceof ChoiceNode);
         return ImmutableChoiceNodeBuilder.create((ChoiceNode) original);
     }
-}
\ No newline at end of file
+}
index 12dfc5f557b751a8e224befb40cad775808f6b0d..49e29ae2a08f61beb5cee8027a8d16973b2812d3 100644 (file)
@@ -48,7 +48,7 @@ final class DataTreeState {
         return new InMemoryDataTreeSnapshot(schemaContext, root, holder.newSnapshot());
     }
 
-    DataTreeState withSchemaContext(final SchemaContext newSchemaContext, final SchemaAwareApplyOperation operation) {
+    DataTreeState withSchemaContext(final SchemaContext newSchemaContext, final ModificationApplyOperation operation) {
         holder.setCurrent(operation);
         return new DataTreeState(root, holder, newSchemaContext);
     }
index 21f4b98226a22075e17c7b89faeb5b276729a231..08125b90971e5b2bccfdf3b2ef8f19afaba079fb 100644 (file)
@@ -8,16 +8,19 @@
 package org.opendaylight.yangtools.yang.data.impl.schema.tree;
 
 import com.google.common.base.MoreObjects;
-import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNodes;
 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.api.schema.tree.spi.TreeNode;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -57,23 +60,30 @@ final class InMemoryDataTree extends AbstractDataTreeTip implements TipProducing
 
         LOG.debug("Following schema contexts will be attempted {}", newSchemaContext);
 
-        final ModificationApplyOperation op = SchemaAwareApplyOperation.from(newSchemaContext, treeType);
-        final Optional<ModificationApplyOperation> maybeRootNode = StoreTreeNodes.findNode(op, rootPath);
-        if (!maybeRootNode.isPresent()) {
+        final DataSchemaContextTree contextTree = DataSchemaContextTree.from(newSchemaContext);
+        final DataSchemaContextNode<?> rootContextNode = contextTree.getChild(rootPath);
+        if (rootContextNode == null) {
             LOG.debug("Could not find root {} in new schema context, not upgrading", rootPath);
             return;
         }
 
-        final ModificationApplyOperation rootNode = maybeRootNode.get();
-        if (!(rootNode instanceof AbstractNodeContainerModificationStrategy)) {
-            LOG.warn("Root {} resolves to non-container type {}, not upgrading", rootPath, rootNode);
+        final DataSchemaNode rootSchemaNode = rootContextNode.getDataSchemaNode();
+        if (!(rootSchemaNode instanceof DataNodeContainer)) {
+            LOG.warn("Root {} resolves to non-container type {}, not upgrading", rootPath, rootSchemaNode);
             return;
         }
 
+        final ModificationApplyOperation rootNode;
+        if (rootSchemaNode instanceof ContainerSchemaNode) {
+            rootNode = new PresenceContainerModificationStrategy((ContainerSchemaNode) rootSchemaNode, treeType);
+        } else {
+            rootNode = SchemaAwareApplyOperation.from(rootSchemaNode, treeType);
+        }
+
         DataTreeState currentState, newState;
         do {
             currentState = state;
-            newState = currentState.withSchemaContext(newSchemaContext, (SchemaAwareApplyOperation) rootNode);
+            newState = currentState.withSchemaContext(newSchemaContext, rootNode);
         } while (!STATE_UPDATER.compareAndSet(this, currentState, newState));
     }
 
@@ -17,8 +17,9 @@ import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContaine
 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
 
-final class ContainerModificationStrategy extends AbstractDataNodeContainerModificationStrategy<ContainerSchemaNode> {
-    ContainerModificationStrategy(final ContainerSchemaNode schemaNode, final TreeType treeType) {
+final class PresenceContainerModificationStrategy extends
+        AbstractDataNodeContainerModificationStrategy<ContainerSchemaNode> {
+    PresenceContainerModificationStrategy(final ContainerSchemaNode schemaNode, final TreeType treeType) {
         super(schemaNode, ContainerNode.class, treeType);
     }
 
@@ -28,4 +29,4 @@ final class ContainerModificationStrategy extends AbstractDataNodeContainerModif
         checkArgument(original instanceof ContainerNode);
         return ImmutableContainerNodeBuilder.create((ContainerNode) original);
     }
-}
+}
\ No newline at end of file
index 1f33b7c7a7e2d4a528421a7343bc40d7c1ad7ff6..b119a64664a130f17bfe660687b183894146780b 100644 (file)
@@ -36,16 +36,17 @@ import org.slf4j.LoggerFactory;
 abstract class SchemaAwareApplyOperation extends ModificationApplyOperation {
     private static final Logger LOG = LoggerFactory.getLogger(SchemaAwareApplyOperation.class);
 
-    static SchemaAwareApplyOperation from(final ContainerSchemaNode schemaNode, final TreeType treeType) {
-        return new ContainerModificationStrategy(schemaNode, treeType);
-    }
-
-    public static SchemaAwareApplyOperation from(final DataSchemaNode schemaNode, final TreeType treeType) {
-        if(treeType == TreeType.CONFIGURATION) {
+    public static ModificationApplyOperation from(final DataSchemaNode schemaNode, final TreeType treeType) {
+        if (treeType == TreeType.CONFIGURATION) {
             Preconditions.checkArgument(schemaNode.isConfiguration(), "Supplied %s does not belongs to configuration tree.", schemaNode.getPath());
         }
         if (schemaNode instanceof ContainerSchemaNode) {
-            return new ContainerModificationStrategy((ContainerSchemaNode) schemaNode, treeType);
+            final ContainerSchemaNode containerSchema = (ContainerSchemaNode) schemaNode;
+            if (containerSchema.isPresenceContainer()) {
+                return new PresenceContainerModificationStrategy(containerSchema, treeType);
+            } else {
+                return new StructuralContainerModificationStrategy(containerSchema, treeType);
+            }
         } else if (schemaNode instanceof ListSchemaNode) {
             return fromListSchemaNode((ListSchemaNode) schemaNode, treeType);
         } else if (schemaNode instanceof ChoiceSchemaNode) {
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/StructuralContainerModificationStrategy.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/StructuralContainerModificationStrategy.java
new file mode 100644 (file)
index 0000000..3cc6ae7
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.impl.schema.tree;
+
+import com.google.common.base.Optional;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNodeFactory;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+
+/**
+ * Structural containers are special in that they appear when implied by child
+ * nodes and disappear whenever they are empty. We could implement this as a
+ * subclass of {@link SchemaAwareApplyOperation}, but the automatic semantic
+ * is quite different from all the other strategies. We create a
+ * {@link PresenceContainerModificationStrategy} to tap into that logic, but
+ * wrap it so we only call out into it
+ */
+final class StructuralContainerModificationStrategy extends ModificationApplyOperation {
+    /**
+     * Fake TreeNode version used in {@link #checkApplicable(YangInstanceIdentifier, NodeModification, Optional)}.
+     * It is okay to use a global constant, as the delegate will ignore it anyway. For
+     * {@link #apply(ModifiedNode, Optional, Version)} we will use the appropriate version as provided to us.
+     */
+    private static final Version FAKE_VERSION = Version.initial();
+    private final PresenceContainerModificationStrategy delegate;
+
+    StructuralContainerModificationStrategy(final ContainerSchemaNode schemaNode, final TreeType treeType) {
+        this.delegate = new PresenceContainerModificationStrategy(schemaNode, treeType);
+    }
+
+    private Optional<TreeNode> fakeMeta(final Version version) {
+        final ContainerNode container = ImmutableNodes.containerNode(delegate.getSchema().getQName());
+        return Optional.of(TreeNodeFactory.createTreeNode(container, version));
+    }
+
+    @Override
+    Optional<TreeNode> apply(final ModifiedNode modification, final Optional<TreeNode> storeMeta, final Version version) {
+        final Optional<TreeNode> ret;
+        if (modification.getOperation() == LogicalOperation.TOUCH && !storeMeta.isPresent()) {
+            // Container is not present, let's take care of the 'magically appear' part of our job
+            ret = delegate.apply(modification, fakeMeta(version), version);
+
+            // Fake container got removed: that is a no-op
+            if (!ret.isPresent()) {
+                modification.resolveModificationType(ModificationType.UNMODIFIED);
+                return ret;
+            }
+
+            // If the delegate indicated SUBTREE_MODIFIED, account for the fake and report APPEARED
+            if (modification.getModificationType() == ModificationType.SUBTREE_MODIFIED) {
+                modification.resolveModificationType(ModificationType.APPEARED);
+            }
+        } else {
+            // Container is present, run normal apply operation
+            ret = delegate.apply(modification, storeMeta, version);
+
+            // Container was explicitly deleted, no magic required
+            if (!ret.isPresent()) {
+                return ret;
+            }
+        }
+
+        /*
+         * At this point ret is guaranteed to be present. We need to take care of the 'magically disappear' part of
+         * our job. Check if there are any child nodes left. If there are none, remove this container and turn the
+         * modification into a DISAPPEARED.
+         */
+        if (((NormalizedNodeContainer<?, ?, ?>) ret.get().getData()).getValue().isEmpty()) {
+            modification.resolveModificationType(ModificationType.DISAPPEARED);
+            return Optional.absent();
+        }
+
+        return ret;
+    }
+
+    @Override
+    void checkApplicable(final YangInstanceIdentifier path, final NodeModification modification, final Optional<TreeNode> current) throws DataValidationFailedException {
+        if (modification.getOperation() == LogicalOperation.TOUCH && !current.isPresent()) {
+            // Structural containers are created as needed, so we pretend this container is here
+            delegate.checkApplicable(path, modification, fakeMeta(FAKE_VERSION));
+        } else {
+            delegate.checkApplicable(path, modification, current);
+        }
+    }
+
+    @Override
+    void verifyStructure(final NormalizedNode<?, ?> modification, final boolean verifyChildren) throws IllegalArgumentException {
+        delegate.verifyStructure(modification, verifyChildren);
+    }
+
+    @Override
+    ChildTrackingPolicy getChildPolicy() {
+        return delegate.getChildPolicy();
+    }
+
+    @Override
+    public Optional<ModificationApplyOperation> getChild(final PathArgument child) {
+        return delegate.getChild(child);
+    }
+}
index f7b2a40a70c569e8300e274c0234ff965431e0c4..c5d4fde1e07ca9a303d5cd8cf64d501bcce4dc8c 100644 (file)
@@ -8,6 +8,7 @@ module list-constraints-validation-test-model  {
     }
 
     container master-container {
+        presence true;
         list min-max-list {
             min-elements 2;
             max-elements 3;
index 94b70f4d155325aa835b1973765333c821da0944..c9df1e0b3b18a29dd469d53956f100794d5568e7 100644 (file)
@@ -8,6 +8,7 @@ module odl-datastore-test {
     }
 
     container test {
+        presence true;
         choice choice1 {
             case case1 {
                 leaf case1-leaf1 {
@@ -47,7 +48,7 @@ module odl-datastore-test {
                }
            }
            list inner-list {
-               config false;
+                config false;
                 key name;
                 leaf name {
                     type string;
index d11efb4f84ce708081b67b05585d537e6e1152e0..e30b5244883d877e26cdec7052e15f1421e55282 100644 (file)
@@ -11,18 +11,18 @@ import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import java.util.Iterator;
+import javax.annotation.Nonnull;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
 public final class DataSchemaContextTree {
-
     private static final LoadingCache<SchemaContext, DataSchemaContextTree> TREES = CacheBuilder.newBuilder()
             .weakKeys()
             .build(new CacheLoader<SchemaContext, DataSchemaContextTree>() {
 
                 @Override
-                public DataSchemaContextTree load(SchemaContext key) throws Exception {
+                public DataSchemaContextTree load(final SchemaContext key) throws Exception {
                     return new DataSchemaContextTree(key);
                 }
 
@@ -34,8 +34,7 @@ public final class DataSchemaContextTree {
         root = DataSchemaContextNode.from(ctx);
     }
 
-
-    public static DataSchemaContextTree from(SchemaContext ctx) {
+    @Nonnull public static DataSchemaContextTree from(@Nonnull final SchemaContext ctx) {
         return TREES.getUnchecked(ctx);
     }
 
@@ -51,5 +50,4 @@ public final class DataSchemaContextTree {
     public DataSchemaContextNode<?> getRoot() {
         return root;
     }
-
 }