From 5500c3ce51088ed6de1adedd35f52662a8304b1e Mon Sep 17 00:00:00 2001 From: Peter Kajsa Date: Tue, 1 Dec 2015 11:25:17 +0100 Subject: [PATCH] BUG-4355: mandatory node presence enforcement Introduce mandatory leaf validation as specified in RFC6020. Mandatory node presence is enforced whenever a presence container, a list entry, and a case node. The presence of mandatory leaves which do not have any ancestors other than structural containers are not enforced as mandatory. The reason for this is that the first transaction on such a DataTree would have to introduce all mandatory leaves, as their non-presence would make other transactions fail consistency checks. Change-Id: I19df441026854955fd37f6e1ff7b4ea3c48c60b8 Signed-off-by: Robert Varga Signed-off-by: Peter Kajsa --- .../data/impl/schema/tree/CaseEnforcer.java | 22 +- .../tree/ChoiceModificationStrategy.java | 3 + .../tree/ContainerModificationStrategy.java | 33 +++ .../impl/schema/tree/InMemoryDataTree.java | 3 +- .../tree/ListEntryModificationStrategy.java | 28 +++ .../schema/tree/MandatoryLeafEnforcer.java | 106 ++++++++++ ...PresenceContainerModificationStrategy.java | 46 +++-- ...ructuralContainerModificationStrategy.java | 16 +- .../impl/schema/tree/MandatoryLeafTest.java | 189 ++++++++++++++++++ ...ist-constraints-validation-test-model.yang | 1 - .../test/resources/mandatory-leaf-test.yang | 74 +++++++ 11 files changed, 492 insertions(+), 29 deletions(-) create mode 100644 yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ContainerModificationStrategy.java create mode 100644 yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MandatoryLeafEnforcer.java create mode 100644 yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MandatoryLeafTest.java create mode 100644 yang/yang-data-impl/src/test/resources/mandatory-leaf-test.yang diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/CaseEnforcer.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/CaseEnforcer.java index 62a5f837dd..87a7f786f2 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/CaseEnforcer.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/CaseEnforcer.java @@ -15,29 +15,33 @@ import java.util.Map.Entry; import java.util.Set; import org.opendaylight.yangtools.concepts.Immutable; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; 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.model.api.ChoiceCaseNode; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; final class CaseEnforcer implements Immutable { private final Map children; + private final MandatoryLeafEnforcer enforcer; - private CaseEnforcer(final Map children) { + private CaseEnforcer(final Map children, final MandatoryLeafEnforcer enforcer) { this.children = Preconditions.checkNotNull(children); + this.enforcer = Preconditions.checkNotNull(enforcer); } - static CaseEnforcer forTree(final ChoiceCaseNode schema, final TreeType treeType) { + static CaseEnforcer forTree(final ChoiceCaseNode schema, final TreeType type) { final Builder builder = ImmutableMap.builder(); - if (SchemaAwareApplyOperation.belongsToTree(treeType, schema)) { + if (SchemaAwareApplyOperation.belongsToTree(type, schema)) { for (DataSchemaNode child : schema.getChildNodes()) { - if (SchemaAwareApplyOperation.belongsToTree(treeType, child)) { + if (SchemaAwareApplyOperation.belongsToTree(type, child)) { builder.put(NodeIdentifier.create(child.getQName()), child); } } } final Map children = builder.build(); - return children.isEmpty() ? null : new CaseEnforcer(children); + return children.isEmpty() ? null : new CaseEnforcer(children, MandatoryLeafEnforcer.forContainer(schema, type)); } Set> getChildEntries() { @@ -47,4 +51,12 @@ final class CaseEnforcer implements Immutable { Set getChildIdentifiers() { return children.keySet(); } + + void enforceOnTreeNode(final TreeNode tree) { + enforcer.enforceOnTreeNode(tree); + } + + void enforceOnTreeNode(final NormalizedNode normalizedNode) { + enforcer.enforceOnTreeNode(normalizedNode); + } } diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ChoiceModificationStrategy.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ChoiceModificationStrategy.java index 56e2bac02f..38edd7bb2f 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ChoiceModificationStrategy.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ChoiceModificationStrategy.java @@ -108,6 +108,9 @@ final class ChoiceModificationStrategy extends AbstractNodeContainerModification firstChild.getIdentifier(), enforcer, id, other, maybeChild.orNull()); } } + + // Make sure all mandatory children are present + enforcer.enforceOnTreeNode(normalizedNode); } } diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ContainerModificationStrategy.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ContainerModificationStrategy.java new file mode 100644 index 0000000000..58671b8c3e --- /dev/null +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ContainerModificationStrategy.java @@ -0,0 +1,33 @@ +/* + * 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 static com.google.common.base.Preconditions.checkArgument; +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.tree.TreeType; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; + +/** + * General container modification strategy. Used by {@link PresenceContainerModificationStrategy} via subclassing + * and by {@link StructuralContainerModificationStrategy} as a delegate. + */ +class ContainerModificationStrategy extends AbstractDataNodeContainerModificationStrategy { + ContainerModificationStrategy(final ContainerSchemaNode schemaNode, final TreeType treeType) { + super(schemaNode, ContainerNode.class, treeType); + } + + @Override + @SuppressWarnings("rawtypes") + protected final DataContainerNodeBuilder createBuilder(final NormalizedNode original) { + checkArgument(original instanceof ContainerNode); + return ImmutableContainerNodeBuilder.create((ContainerNode) original); + } +} diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTree.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTree.java index 08125b9097..13070428ec 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTree.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTree.java @@ -75,7 +75,8 @@ final class InMemoryDataTree extends AbstractDataTreeTip implements TipProducing final ModificationApplyOperation rootNode; if (rootSchemaNode instanceof ContainerSchemaNode) { - rootNode = new PresenceContainerModificationStrategy((ContainerSchemaNode) rootSchemaNode, treeType); + // FIXME: real root needs to enfore presence, but that require pre-population + rootNode = new ContainerModificationStrategy((ContainerSchemaNode) rootSchemaNode, treeType); } else { rootNode = SchemaAwareApplyOperation.from(rootSchemaNode, treeType); } diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ListEntryModificationStrategy.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ListEntryModificationStrategy.java index 71f23f0715..30b17b1c0e 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ListEntryModificationStrategy.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ListEntryModificationStrategy.java @@ -8,16 +8,44 @@ package org.opendaylight.yangtools.yang.data.impl.schema.tree; import static com.google.common.base.Preconditions.checkArgument; +import com.google.common.base.Optional; import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; 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.Version; import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder; import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapEntryNodeBuilder; import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; final class ListEntryModificationStrategy extends AbstractDataNodeContainerModificationStrategy { + private final MandatoryLeafEnforcer enforcer; + ListEntryModificationStrategy(final ListSchemaNode schema, final TreeType treeType) { super(schema, MapEntryNode.class, treeType); + enforcer = MandatoryLeafEnforcer.forContainer(schema, treeType); + } + + @Override + protected TreeNode applyMerge(final ModifiedNode modification, final TreeNode currentMeta, final Version version) { + final TreeNode ret = super.applyMerge(modification, currentMeta, version); + enforcer.enforceOnTreeNode(ret); + return ret; + } + + @Override + protected TreeNode applyWrite(final ModifiedNode modification, final Optional currentMeta, + final Version version) { + final TreeNode ret = super.applyWrite(modification, currentMeta, version); + enforcer.enforceOnTreeNode(ret); + return ret; + } + + @Override + protected TreeNode applyTouch(final ModifiedNode modification, final TreeNode currentMeta, final Version version) { + final TreeNode ret = super.applyTouch(modification, currentMeta, version); + enforcer.enforceOnTreeNode(ret); + return ret; } @Override diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MandatoryLeafEnforcer.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MandatoryLeafEnforcer.java new file mode 100644 index 0000000000..94af9b3507 --- /dev/null +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MandatoryLeafEnforcer.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2014 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 com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableCollection.Builder; +import com.google.common.collect.ImmutableList; +import java.util.Collection; +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.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes; +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.model.api.ConstraintDefinition; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// TODO: would making this Serializable be useful (for Functions and similar?) +abstract class MandatoryLeafEnforcer implements Immutable { + private static final class NoOp extends MandatoryLeafEnforcer { + @Override + protected void enforceOnTreeNode(final TreeNode tree) { + // Intentional no-op + } + + @Override + protected void enforceOnTreeNode(final NormalizedNode normalizedNode) { + // Intentional no-op + } + }; + private static final class Strict extends MandatoryLeafEnforcer { + private final Collection mandatoryNodes; + + private Strict(final Collection mandatoryNodes) { + this.mandatoryNodes = Preconditions.checkNotNull(mandatoryNodes); + } + + @Override + protected void enforceOnTreeNode(final TreeNode tree) { + enforceOnTreeNode(tree.getData()); + } + + @Override + protected void enforceOnTreeNode(final NormalizedNode data) { + for (YangInstanceIdentifier id : mandatoryNodes) { + final Optional> descandant = NormalizedNodes.findNode(data, id); + Preconditions.checkArgument(descandant.isPresent(), "Node %s is missing mandatory descendant %s", + data.getIdentifier(), id); + } + } + }; + + private static final Logger LOG = LoggerFactory.getLogger(MandatoryLeafEnforcer.class); + private static final MandatoryLeafEnforcer NOOP_ENFORCER = new NoOp(); + + protected abstract void enforceOnTreeNode(final TreeNode tree); + + protected abstract void enforceOnTreeNode(final NormalizedNode normalizedNode); + + private static void findMandatoryNodes(final Builder builder, + final YangInstanceIdentifier id, final DataNodeContainer schema, final TreeType type) { + for (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(child.getQName())), container, type); + } + } else { + final ConstraintDefinition constraints = child.getConstraints(); + final Integer minElements = constraints.getMinElements(); + if (constraints.isMandatory() || (minElements != null && minElements > 0)) { + final YangInstanceIdentifier childId = id.node(NodeIdentifier.create(child.getQName())); + LOG.debug("Adding mandatory child {}", childId); + builder.add(childId.toOptimized()); + } + } + } + } + } + + static MandatoryLeafEnforcer forContainer(final DataNodeContainer schema, final TreeType type) { + switch (type) { + case CONFIGURATION: + final Builder builder = ImmutableList.builder(); + findMandatoryNodes(builder, YangInstanceIdentifier.EMPTY, schema, type); + final Collection mandatoryNodes = builder.build(); + return mandatoryNodes.isEmpty() ? NOOP_ENFORCER : new Strict(mandatoryNodes); + case OPERATIONAL: + return NOOP_ENFORCER; + default: + throw new UnsupportedOperationException(String.format("Not supported tree type %s", type)); + } + } +} diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/PresenceContainerModificationStrategy.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/PresenceContainerModificationStrategy.java index 54bdb21bb7..4becd4f709 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/PresenceContainerModificationStrategy.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/PresenceContainerModificationStrategy.java @@ -8,25 +8,43 @@ package org.opendaylight.yangtools.yang.data.impl.schema.tree; -import static com.google.common.base.Preconditions.checkArgument; - -import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import com.google.common.base.Optional; import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType; -import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder; -import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; -final class PresenceContainerModificationStrategy extends - AbstractDataNodeContainerModificationStrategy { +/** + * Presence container modification strategy. In addition to {@link ContainerModificationStrategy} it also enforces + * presence of mandatory leaves. + */ +final class PresenceContainerModificationStrategy extends ContainerModificationStrategy { + private final MandatoryLeafEnforcer enforcer; + PresenceContainerModificationStrategy(final ContainerSchemaNode schemaNode, final TreeType treeType) { - super(schemaNode, ContainerNode.class, treeType); + super(schemaNode, treeType); + enforcer = MandatoryLeafEnforcer.forContainer(schemaNode, treeType); + } + + @Override + protected TreeNode applyMerge(final ModifiedNode modification, final TreeNode currentMeta, final Version version) { + final TreeNode ret = super.applyMerge(modification, currentMeta, version); + enforcer.enforceOnTreeNode(ret); + return ret; + } + + @Override + protected TreeNode applyWrite(final ModifiedNode modification, final Optional currentMeta, + final Version version) { + final TreeNode ret = super.applyWrite(modification, currentMeta, version); + enforcer.enforceOnTreeNode(ret); + return ret; } @Override - @SuppressWarnings("rawtypes") - protected DataContainerNodeBuilder createBuilder(final NormalizedNode original) { - checkArgument(original instanceof ContainerNode); - return ImmutableContainerNodeBuilder.create((ContainerNode) original); + protected TreeNode applyTouch(final ModifiedNode modification, final TreeNode currentMeta, final Version version) { + final TreeNode ret = super.applyTouch(modification, currentMeta, version); + enforcer.enforceOnTreeNode(ret); + return ret; } -} \ No newline at end of file +} 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 index 3cc6ae7d64..f2eefd293c 100644 --- 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 @@ -23,12 +23,12 @@ 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 + * 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 ContainerModificationStrategy} to tap into that + * logic, but wrap it so we only call out into it. We do not use {@link PresenceContainerModificationStrategy} because + * it enforces presence of mandatory leaves, which is not something we want here, as structural containers are not + * root anchors for that validation. */ final class StructuralContainerModificationStrategy extends ModificationApplyOperation { /** @@ -37,10 +37,10 @@ final class StructuralContainerModificationStrategy extends ModificationApplyOpe * {@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; + private final ContainerModificationStrategy delegate; StructuralContainerModificationStrategy(final ContainerSchemaNode schemaNode, final TreeType treeType) { - this.delegate = new PresenceContainerModificationStrategy(schemaNode, treeType); + this.delegate = new ContainerModificationStrategy(schemaNode, treeType); } private Optional fakeMeta(final Version version) { diff --git a/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MandatoryLeafTest.java b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MandatoryLeafTest.java new file mode 100644 index 0000000000..98cd9aef4a --- /dev/null +++ b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MandatoryLeafTest.java @@ -0,0 +1,189 @@ +/* + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.leafNode; + +import org.junit.Before; +import org.junit.Test; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException; +import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType; +import org.opendaylight.yangtools.yang.data.impl.schema.Builders; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; + +public class MandatoryLeafTest { + + private SchemaContext schemaContext; + + @Before + public void prepare() throws ReactorException { + schemaContext = RetestModel.createTestContext("/mandatory-leaf-test.yang"); + assertNotNull("Schema context must not be null.", schemaContext); + } + + private InMemoryDataTree initDataTree() { + InMemoryDataTree inMemoryDataTree = (InMemoryDataTree) InMemoryDataTreeFactory.getInstance().create( + TreeType.CONFIGURATION); + inMemoryDataTree.setSchemaContext(schemaContext); + return inMemoryDataTree; + } + + @Test + public void testCorrectMandatoryLeafWrite() throws DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = initDataTree(); + final NodeIdentifier choice1Id = new NodeIdentifier(QName.create(TestModel.TEST_QNAME, "choice1")); + + final ContainerNode container = Builders + .containerBuilder() + .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME)) + .withChild( + Builders.choiceBuilder() + .withNodeIdentifier(choice1Id) + .withChild( + Builders.containerBuilder() + .withNodeIdentifier( + new NodeIdentifier(QName.create(TestModel.TEST_QNAME, + "case2-cont"))) + .withChild( + leafNode(QName.create(TestModel.TEST_QNAME, "case2-leaf1"), + "leaf-value")) + .withChild( + leafNode(QName.create(TestModel.TEST_QNAME, "case2-leaf2"), + "leaf-value2")).build()).build()).build(); + + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + modificationTree.write(TestModel.TEST_PATH, container); + modificationTree.ready(); + + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + } + + @Test + public void testCorrectMandatoryLeafChoiceWrite() throws DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = initDataTree(); + // Container write + final ContainerNode container = Builders.containerBuilder() + .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME)).build(); + + final InMemoryDataTreeModification modificationTree1 = inMemoryDataTree.takeSnapshot().newModification(); + modificationTree1.write(TestModel.TEST_PATH, container); + modificationTree1.ready(); + + inMemoryDataTree.validate(modificationTree1); + final DataTreeCandidate prepare1 = inMemoryDataTree.prepare(modificationTree1); + inMemoryDataTree.commit(prepare1); + + // Choice write + final NodeIdentifier choice1Id = new NodeIdentifier(QName.create(TestModel.TEST_QNAME, "choice1")); + final ChoiceNode choice = Builders + .choiceBuilder() + .withNodeIdentifier(choice1Id) + .withChild( + Builders.containerBuilder() + .withNodeIdentifier( + new NodeIdentifier(QName.create(TestModel.TEST_QNAME, "case2-cont"))) + .withChild(leafNode(QName.create(TestModel.TEST_QNAME, "case2-leaf1"), "leaf-value")) + .withChild(leafNode(QName.create(TestModel.TEST_QNAME, "case2-leaf2"), "leaf-value2")) + .build()).build(); + + final InMemoryDataTreeModification modificationTree2 = inMemoryDataTree.takeSnapshot().newModification(); + modificationTree2.write(TestModel.TEST_PATH.node(choice1Id), choice); + modificationTree2.ready(); + + inMemoryDataTree.validate(modificationTree2); + final DataTreeCandidate prepare2 = inMemoryDataTree.prepare(modificationTree2); + inMemoryDataTree.commit(prepare2); + } + + @Test(expected = IllegalArgumentException.class) + public void testMandatoryLeafViolation() throws DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = initDataTree(); + final NodeIdentifier choice1Id = new NodeIdentifier(QName.create(TestModel.TEST_QNAME, "choice1")); + + final ContainerNode container = Builders + .containerBuilder() + .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME)) + .withChild( + Builders.choiceBuilder() + .withNodeIdentifier(choice1Id) + .withChild( + Builders.containerBuilder() + .withNodeIdentifier( + new NodeIdentifier(QName.create(TestModel.TEST_QNAME, + "case2-cont"))) + .withChild( + leafNode(QName.create(TestModel.TEST_QNAME, "case2-leaf2"), + "leaf-value2")).build()).build()).build(); + try { + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + modificationTree.write(TestModel.TEST_PATH, container); + modificationTree.ready(); + + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + } catch (IllegalArgumentException e) { + assertEquals( + "Node (urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:test?revision=2014-03-13)choice1 is missing mandatory descendant /(urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:test?revision=2014-03-13)case2-cont/case2-leaf1", + e.getMessage()); + throw e; + } + } + + @Test(expected = IllegalArgumentException.class) + public void testMandatoryLeafViolationChoiceWrite() throws DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = initDataTree(); + // Container write + final ContainerNode container = Builders.containerBuilder() + .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME)).build(); + + final InMemoryDataTreeModification modificationTree1 = inMemoryDataTree.takeSnapshot().newModification(); + modificationTree1.write(TestModel.TEST_PATH, container); + modificationTree1.ready(); + + inMemoryDataTree.validate(modificationTree1); + final DataTreeCandidate prepare1 = inMemoryDataTree.prepare(modificationTree1); + inMemoryDataTree.commit(prepare1); + + // Choice write + final NodeIdentifier choice1Id = new NodeIdentifier(QName.create(TestModel.TEST_QNAME, "choice1")); + final ChoiceNode choice = Builders + .choiceBuilder() + .withNodeIdentifier(choice1Id) + .withChild( + Builders.containerBuilder() + .withNodeIdentifier( + new NodeIdentifier(QName.create(TestModel.TEST_QNAME, "case2-cont"))) + .withChild(leafNode(QName.create(TestModel.TEST_QNAME, "case2-leaf2"), "leaf-value2")) + .build()).build(); + + try { + final InMemoryDataTreeModification modificationTree2 = inMemoryDataTree.takeSnapshot().newModification(); + modificationTree2.write(TestModel.TEST_PATH.node(choice1Id), choice); + modificationTree2.ready(); + inMemoryDataTree.validate(modificationTree2); + final DataTreeCandidate prepare2 = inMemoryDataTree.prepare(modificationTree2); + inMemoryDataTree.commit(prepare2); + } catch (IllegalArgumentException e) { + assertEquals( + "Node (urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:test?revision=2014-03-13)choice1 is missing mandatory descendant /(urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:test?revision=2014-03-13)case2-cont/case2-leaf1", + e.getMessage()); + throw e; + } + } +} diff --git a/yang/yang-data-impl/src/test/resources/list-constraints-validation-test-model.yang b/yang/yang-data-impl/src/test/resources/list-constraints-validation-test-model.yang index c5d4fde1e0..f7b2a40a70 100644 --- a/yang/yang-data-impl/src/test/resources/list-constraints-validation-test-model.yang +++ b/yang/yang-data-impl/src/test/resources/list-constraints-validation-test-model.yang @@ -8,7 +8,6 @@ module list-constraints-validation-test-model { } container master-container { - presence true; list min-max-list { min-elements 2; max-elements 3; diff --git a/yang/yang-data-impl/src/test/resources/mandatory-leaf-test.yang b/yang/yang-data-impl/src/test/resources/mandatory-leaf-test.yang new file mode 100644 index 0000000000..32e92e4a05 --- /dev/null +++ b/yang/yang-data-impl/src/test/resources/mandatory-leaf-test.yang @@ -0,0 +1,74 @@ +module mandatory-leaf-test { + yang-version 1; + namespace "urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:test"; + prefix "store-test"; + + revision "2014-03-13" { + description "Initial revision."; + } + + container test { + presence true; + config true; + choice choice1 { + case case1 { + leaf case1-leaf1 { + type string; + } + } + case case2 { + container case2-cont { + leaf case2-leaf1 { + mandatory true; + type string; + } + leaf case2-leaf2 { + type string; + } + } + } + } + + list outer-list { + + key id; + leaf id { + type uint16; + } + choice outer-choice { + case one { + leaf one { + type string; + } + } + case two-three { + leaf two { + type string; + } + leaf three { + type string; + } + } + } + list inner-list { + key name; + leaf name { + type string; + } + leaf value { + type string; + } + } + + list inner-list2 { + key name; + leaf name { + type string; + } + leaf value { + type string; + } + } + } + } +} -- 2.36.6