Fix mandatory enforcer failure on augmented nodes 96/96596/12
authorTibor Král <tibor.kral@pantheon.tech>
Tue, 22 Jun 2021 14:28:27 +0000 (08:28 -0600)
committerRobert Varga <robert.varga@pantheon.tech>
Tue, 2 Nov 2021 07:52:10 +0000 (08:52 +0100)
Changes the way mandatory nodes are extracted from the schema.
Previously all mandatory nodes were approached as direct children of
the parent node. Even if they came from an augmentation which caused
"missing mandatory descendant" exceptions. This patch preserves both
options of building data nodes containing augmented mandatory nodes:
- setting the mandatory node as a direct child of the parent node
- setting the mandatory node as an AugmentationNode to the parent node

However the former option seems to be incorrect and might be removed
in the future major release.

This patch also covers the case where the mandatory leaf is deeper in
the augmented subtree.

JIRA: YANGTOOLS-1276
Change-Id: Ic5cd1950b935720bf56f52cfbc24c0f9e776a474
Signed-off-by: Tibor Král <tibor.kral@pantheon.tech>
data/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MandatoryDescendant.java
data/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MandatoryLeafEnforcer.java
data/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/YT1276Test.java
data/yang-data-impl/src/test/resources/yt1276.yang

index 8a45cec996e89ec620dd728a1d0265e451f4e677..98208b232741d13a2d42a27790a40c6168cfb00c 100644 (file)
@@ -10,11 +10,13 @@ package org.opendaylight.yangtools.yang.data.impl.schema.tree;
 import static com.google.common.base.Verify.verify;
 import static java.util.Objects.requireNonNull;
 
+import java.util.List;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.yangtools.concepts.Immutable;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
@@ -44,23 +46,22 @@ final class MandatoryDescendant implements Immutable {
     }
 
     static @NonNull MandatoryDescendant create(final YangInstanceIdentifier parentId,
-            final DataNodeContainer parentSchema, final DataSchemaNode childSchema) {
+            final DataNodeContainer parentSchema, final DataSchemaNode childSchema, final boolean inAugmentedSubtree) {
         final NodeIdentifier childId = NodeIdentifier.create(childSchema.getQName());
 
         if (childSchema.isAugmenting()) {
-            verify(parentSchema instanceof AugmentationTarget, "Unexpected augmenting child %s in non-target %s at %s",
-                childSchema, parentSchema, parentId);
-
-            final AugmentationSchemaNode aug = ((AugmentationTarget) parentSchema).getAvailableAugmentations().stream()
-                .filter(augment -> augment.findDataChildByName(childSchema.getQName()).isPresent())
-                .findFirst()
-                .orElseThrow(() -> new IllegalArgumentException(String.format(
-                    "Node %s is marked as augmenting but is not present in the schema of %s", childSchema.getQName(),
-                    parentSchema)));
+            if (!inAugmentedSubtree) {
+                final AugmentationSchemaNode aug = getAugIdentifierOfChild(parentSchema, childSchema);
+                return new MandatoryDescendant(
+                    parentId.node(DataSchemaContextNode.augmentationIdentifierFrom(aug)).node(childId).toOptimized(),
+                    parentId.node(childId).toOptimized());
+            }
 
-            return new MandatoryDescendant(
-                parentId.node(DataSchemaContextNode.augmentationIdentifierFrom(aug)).node(childId).toOptimized(),
-                parentId.node(childId).toOptimized());
+            final List<PathArgument> augSubtreePath = parentId.getPathArguments();
+            // in case of augmented choice-case the pathArguments might be empty
+            final YangInstanceIdentifier legacyPath = !augSubtreePath.isEmpty()
+                ? YangInstanceIdentifier.create(augSubtreePath.subList(1, augSubtreePath.size())) : null;
+            return new MandatoryDescendant(parentId.node(childId).toOptimized(), legacyPath);
         }
 
         return new MandatoryDescendant(parentId.node(childId).toOptimized(), null);
@@ -85,6 +86,16 @@ final class MandatoryDescendant implements Immutable {
             data.getIdentifier(), path));
     }
 
+    static AugmentationSchemaNode getAugIdentifierOfChild(final DataNodeContainer parent, final DataSchemaNode child) {
+        verify(parent instanceof AugmentationTarget,
+            "Unexpected augmenting child %s in non-target parent %s", child, parent);
+        return ((AugmentationTarget) parent).getAvailableAugmentations().stream()
+            .filter(augment -> augment.findDataChildByName(child.getQName()).isPresent())
+            .findFirst()
+            .orElseThrow(() -> new IllegalArgumentException(String.format(
+                "Node %s is marked as augmenting but is not present in the schema of %s", child.getQName(), parent)));
+    }
+
     @Override
     public String toString() {
         return legacyPath == null ? path.toString() : "(" + path + " || " + legacyPath + ")";
index 0100796a4f8387e7cc64ac181b79dec7a0658d46..e1100f4e5915c25bba68aee9d4b34dfc1f533c2c 100644 (file)
@@ -8,6 +8,7 @@
 package org.opendaylight.yangtools.yang.data.impl.schema.tree;
 
 import static java.util.Objects.requireNonNull;
+import static org.opendaylight.yangtools.yang.data.impl.schema.tree.MandatoryDescendant.getAugIdentifierOfChild;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableList.Builder;
@@ -19,7 +20,10 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeConfiguration;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
 import org.opendaylight.yangtools.yang.data.spi.tree.TreeNode;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.CopyableNode;
 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ElementCountConstraintAware;
@@ -44,7 +48,8 @@ final class MandatoryLeafEnforcer implements Immutable {
         }
 
         final Builder<MandatoryDescendant> builder = ImmutableList.builder();
-        findMandatoryNodes(builder, YangInstanceIdentifier.empty(), schema, treeConfig.getTreeType());
+        final boolean isAugmentingNode = schema instanceof CopyableNode && ((CopyableNode) schema).isAugmenting();
+        findMandatoryNodes(builder, YangInstanceIdentifier.empty(), schema, treeConfig.getTreeType(), isAugmentingNode);
         final ImmutableList<MandatoryDescendant> mandatoryNodes = builder.build();
         return mandatoryNodes.isEmpty() ? Optional.empty() : Optional.of(new MandatoryLeafEnforcer(mandatoryNodes));
     }
@@ -58,14 +63,34 @@ final class MandatoryLeafEnforcer implements Immutable {
     }
 
     private static void findMandatoryNodes(final Builder<MandatoryDescendant> builder,
-        final YangInstanceIdentifier id, final DataNodeContainer schema, final TreeType type) {
+        final YangInstanceIdentifier id, final DataNodeContainer schema, final TreeType type,
+        final boolean augmentedSubtree) {
         for (final DataSchemaNode child : schema.getChildNodes()) {
             if (SchemaAwareApplyOperation.belongsToTree(type, child)) {
                 if (child instanceof ContainerSchemaNode) {
                     final ContainerSchemaNode container = (ContainerSchemaNode) child;
                     if (!container.isPresenceContainer()) {
-                        findMandatoryNodes(builder,
-                            id.node(NodeIdentifier.create(container.getQName())), container, type);
+                        if (!augmentedSubtree) {
+                            // this container is not part of augmented subtree.
+                            final boolean parentSchemaAugmenting = schema instanceof CopyableNode
+                                && ((CopyableNode)schema).isAugmenting();
+                            if (container.isAugmenting() && !parentSchemaAugmenting) {
+                                // the container is augmenting, but the parent schema is not. Meaning this is the root
+                                // of the augmentation (the augmented subtree starts here). The container has to be
+                                // represented by AugmentationID and the whole subtree needs to be based on it.
+                                final AugmentationSchemaNode aug = getAugIdentifierOfChild(schema, child);
+                                findMandatoryNodes(builder, id.node(DataSchemaContextNode
+                                    .augmentationIdentifierFrom(aug)).node(NodeIdentifier.create(container.getQName())),
+                                    container, type, true);
+                                continue;
+                            }
+                        }
+                        // the container is either:
+                        //    - not in an augmented subtree and not augmenting
+                        //    - in an augmented subtree
+                        // in both cases just append the NodeID to the ongoing ID and continue the search.
+                        findMandatoryNodes(builder, id.node(NodeIdentifier.create(container.getQName())),
+                            container, type, augmentedSubtree);
                     }
                 } else {
                     boolean needEnforce = child instanceof MandatoryAware && ((MandatoryAware) child).isMandatory();
@@ -78,7 +103,8 @@ final class MandatoryLeafEnforcer implements Immutable {
                             .orElse(Boolean.FALSE);
                     }
                     if (needEnforce) {
-                        final MandatoryDescendant desc = MandatoryDescendant.create(id, schema, child);
+                        final MandatoryDescendant desc = MandatoryDescendant.create(id, schema, child,
+                            augmentedSubtree);
                         LOG.debug("Adding mandatory child {}", desc);
                         builder.add(desc);
                     }
index 7e138cc92914b22e60f121d8a37f07b05d832b3d..55223f854e5b0e299269919af672268e6187c115 100644 (file)
@@ -24,6 +24,7 @@ import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
 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.builder.impl.ImmutableContainerNodeBuilder;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
 
@@ -33,6 +34,14 @@ public class YT1276Test {
     private static final QName BAZ = QName.create(FOO, "baz");
     private static final QName XYZZY_LEAF = QName.create(FOO, "xyzzy-leaf");
     private static final QName XYZZY_AUGMENT = QName.create(FOO, "xyzzy-augment");
+    private static final QName XYZZY_AUGMENT_CONT = QName.create(FOO, "xyzzy-augment-container");
+    private static final QName XYZZY_AUGMENT_CONT_INNER = QName.create(FOO, "xyzzy-augment-container-inner");
+    private static final QName XYZZY_AUGMENT_CONT_LEAF = QName.create(FOO, "xyzzy-augment-container-leaf");
+    private static final QName BAZ_AUG_CASE_MANDAT_LEAF = QName.create(FOO, "augmented-case-mandatory");
+    private static final QName BAZ_AUG_CASE_NON_MANDAT_LEAF = QName.create(FOO, "augmented-case-non-mandatory");
+    private static final QName NESTED_BAZ_CHOICE = QName.create(FOO, "nested-baz");
+    private static final QName NESTED_BAZ_XYZ_CASE_MANDATORY = QName.create(FOO, "nested-xyz-mandatory");
+    private static final QName NESTED_BAZ_XYZ_CASE_NON_MANDATORY = QName.create(FOO, "nested-xyz-non-mandatory");
 
     private static EffectiveModelContext MODEL;
 
@@ -81,7 +90,7 @@ public class YT1276Test {
     }
 
     @Test
-    public void testBarWithXyzzy() throws DataValidationFailedException {
+    public void testBarWithXyzzyWithSubtree() throws DataValidationFailedException {
         applyOperation(mod -> {
             mod.write(YangInstanceIdentifier.of(BAR), Builders.containerBuilder()
                 .withNodeIdentifier(new NodeIdentifier(BAR))
@@ -89,8 +98,75 @@ public class YT1276Test {
                     .withNodeIdentifier(new NodeIdentifier(BAZ))
                     .withChild(ImmutableNodes.leafNode(XYZZY_LEAF, "xyzzy"))
                     .withChild(Builders.augmentationBuilder()
-                        .withNodeIdentifier(new AugmentationIdentifier(Set.of(XYZZY_AUGMENT)))
+                        .withNodeIdentifier(new AugmentationIdentifier(Set.of(XYZZY_AUGMENT, XYZZY_AUGMENT_CONT)))
                         .withChild(ImmutableNodes.leafNode(XYZZY_AUGMENT, "xyzzy"))
+                        .withChild(ImmutableContainerNodeBuilder.create()
+                            .withNodeIdentifier(new NodeIdentifier(XYZZY_AUGMENT_CONT))
+                            .withChild(ImmutableContainerNodeBuilder.create()
+                                .withNodeIdentifier(new NodeIdentifier(XYZZY_AUGMENT_CONT_INNER))
+                                .withChild(ImmutableNodes.leafNode(XYZZY_AUGMENT_CONT_LEAF, "aug-cont-leaf"))
+                                .build())
+                            .build())
+                        .build())
+                    .build())
+                .build());
+        });
+    }
+
+    @Test
+    public void testBazWithAugmentedCaseWithMandatoryLeaf() throws DataValidationFailedException {
+        applyOperation(mod -> {
+            mod.write(YangInstanceIdentifier.of(BAR), Builders.containerBuilder()
+                .withNodeIdentifier(new NodeIdentifier(BAR))
+                .withChild(Builders.choiceBuilder()
+                    .withNodeIdentifier(new NodeIdentifier(BAZ))
+                    .withChild(ImmutableNodes.leafNode(BAZ_AUG_CASE_MANDAT_LEAF, "augmentedCaseMandatory"))
+                    .withChild(ImmutableNodes.leafNode(BAZ_AUG_CASE_NON_MANDAT_LEAF, "augmentedCaseNonMandatory"))
+                    .build())
+                .build());
+        });
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBazWithAugmentedCaseWithoutMandatoryLeaf() throws DataValidationFailedException {
+        applyOperation(mod -> {
+            mod.write(YangInstanceIdentifier.of(BAR), Builders.containerBuilder()
+                .withNodeIdentifier(new NodeIdentifier(BAR))
+                .withChild(Builders.choiceBuilder()
+                    .withNodeIdentifier(new NodeIdentifier(BAZ))
+                    .withChild(ImmutableNodes.leafNode(BAZ_AUG_CASE_NON_MANDAT_LEAF, "augmentedCaseNonMandatory"))
+                    .build())
+                .build());
+        });
+    }
+
+    @Test
+    public void testWithAugmentedNestedBazWithMandatoryLeaf() throws DataValidationFailedException {
+        applyOperation(mod -> {
+            mod.write(YangInstanceIdentifier.of(BAR), Builders.containerBuilder()
+                .withNodeIdentifier(new NodeIdentifier(BAR))
+                .withChild(Builders.choiceBuilder()
+                    .withNodeIdentifier(new NodeIdentifier(BAZ))
+                    .withChild(Builders.choiceBuilder()
+                        .withNodeIdentifier(new NodeIdentifier(NESTED_BAZ_CHOICE))
+                        .withChild(ImmutableNodes.leafNode(NESTED_BAZ_XYZ_CASE_MANDATORY, "nestedMandatory"))
+                        .withChild(ImmutableNodes.leafNode(NESTED_BAZ_XYZ_CASE_NON_MANDATORY, "nestedNonMandatory"))
+                    .build())
+                    .build())
+                .build());
+        });
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testWithAugmentedNestedBazWithhoutMandatoryLeaf() throws DataValidationFailedException {
+        applyOperation(mod -> {
+            mod.write(YangInstanceIdentifier.of(BAR), Builders.containerBuilder()
+                .withNodeIdentifier(new NodeIdentifier(BAR))
+                .withChild(Builders.choiceBuilder()
+                    .withNodeIdentifier(new NodeIdentifier(BAZ))
+                    .withChild(Builders.choiceBuilder()
+                        .withNodeIdentifier(new NodeIdentifier(NESTED_BAZ_CHOICE))
+                        .withChild(ImmutableNodes.leafNode(NESTED_BAZ_XYZ_CASE_NON_MANDATORY, "nestedNonMandatory"))
                         .build())
                     .build())
                 .build());
@@ -107,6 +183,13 @@ public class YT1276Test {
                     .withNodeIdentifier(new NodeIdentifier(BAZ))
                     .withChild(ImmutableNodes.leafNode(XYZZY_LEAF, "xyzzy"))
                     .withChild(ImmutableNodes.leafNode(XYZZY_AUGMENT, "xyzzy"))
+                    .withChild(ImmutableContainerNodeBuilder.create()
+                        .withNodeIdentifier(NodeIdentifier.create(XYZZY_AUGMENT_CONT))
+                        .withChild(ImmutableContainerNodeBuilder.create()
+                            .withNodeIdentifier(NodeIdentifier.create(XYZZY_AUGMENT_CONT_INNER))
+                            .withChild(ImmutableNodes.leafNode(XYZZY_AUGMENT_CONT_LEAF, "aug-cont-leaf"))
+                            .build())
+                        .build())
                     .build())
                 .build());
         });
@@ -120,8 +203,15 @@ public class YT1276Test {
                 .withChild(Builders.choiceBuilder()
                     .withNodeIdentifier(new NodeIdentifier(BAZ))
                     .withChild(Builders.augmentationBuilder()
-                        .withNodeIdentifier(new AugmentationIdentifier(Set.of(XYZZY_AUGMENT)))
+                        .withNodeIdentifier(new AugmentationIdentifier(Set.of(XYZZY_AUGMENT, XYZZY_AUGMENT_CONT)))
                         .withChild(ImmutableNodes.leafNode(XYZZY_AUGMENT, "xyzzy"))
+                        .withChild(ImmutableContainerNodeBuilder.create()
+                            .withNodeIdentifier(NodeIdentifier.create(XYZZY_AUGMENT_CONT))
+                            .withChild(ImmutableContainerNodeBuilder.create()
+                                .withNodeIdentifier(NodeIdentifier.create(XYZZY_AUGMENT_CONT_INNER))
+                                .withChild(ImmutableNodes.leafNode(XYZZY_AUGMENT_CONT_LEAF, "aug-cont-leaf"))
+                                .build())
+                            .build())
                         .build())
                     .build())
                 .build());
@@ -142,9 +232,8 @@ public class YT1276Test {
                     .build())
                 .build());
         });
-        assertEquals("Node (foo)baz is missing mandatory descendant "
-            + "/AugmentationIdentifier{childNames=[(foo)xyzzy-augment]}/(foo)xyzzy-augment",
-            ex.getMessage());
+        assertEquals("Node (foo)baz is missing mandatory descendant /AugmentationIdentifier{childNames="
+                + "[(foo)xyzzy-augment, (foo)xyzzy-augment-container]}/(foo)xyzzy-augment", ex.getMessage());
 
     }
 
index 33c2979b1c95ec1230c8a4eae3414d128cb157bc..4f4b1462b21720c30c9b1a46909eaaffe8f2e0cd 100644 (file)
@@ -31,6 +31,45 @@ module foo {
       type string;
       mandatory true;
     }
+
+    container xyzzy-augment-container {
+      container xyzzy-augment-container-inner {
+        leaf xyzzy-augment-container-leaf {
+          type string;
+          mandatory true;
+        }
+      }
+    }
+  }
+
+  augment /bar/baz {
+    case augmented-case {
+      leaf augmented-case-mandatory {
+        type string;
+        mandatory true;
+      }
+
+      leaf augmented-case-non-mandatory {
+        type string;
+      }
+    }
+  }
+
+  augment /bar/baz {
+    case case-nested-choice {
+      choice nested-baz {
+        case nested-xyz-case {
+          leaf nested-xyz-mandatory {
+            type string;
+            mandatory true;
+          }
+
+          leaf nested-xyz-non-mandatory {
+            type string;
+          }
+        }
+      }
+    }
   }
 }