From 5ec45595d85f7c42310707ea058dac8a6be62acb Mon Sep 17 00:00:00 2001 From: Peter Kajsa Date: Thu, 28 Jul 2016 15:05:16 +0200 Subject: [PATCH] Bug 5968: Mandatory leaf enforcement does not work in some cases When MapEntry nodes are directly written into datatree, mandatory leaf enforcement works correctly. However, when a List, Container or any other parent node containing MapEntry nodes as its children is written into datatree, datatree does not check presence of mandatory nodes of in this way written map entry nodes. This patch provides fix which ensures mandatory node validation in all cases above. In addition, full validation on unsealed modification was performed after each MERGE operation performed on this modification, which may lead to undesirable results, because unsealed modification does not have to contain all elements, which can be added during next operations on this modification. This patch fixes this behavior of MERGE operation and performs full validation during seal() method. Change-Id: If653eeda5378cf1ed1418123eb33713ee80976d9 Signed-off-by: Peter Kajsa Signed-off-by: Robert Varga Signed-off-by: Peter Kajsa --- ...ractNodeContainerModificationStrategy.java | 16 +- .../tree/ListEntryModificationStrategy.java | 8 + .../data/impl/schema/tree/ModifiedNode.java | 13 + .../tree/OperationWithModification.java | 4 +- .../impl/schema/tree/Bug5968MergeTest.java | 476 ++++++++++++++++++ .../data/impl/schema/tree/Bug5968Test.java | 227 +++++++++ .../src/test/resources/bug5968/foo.yang | 25 + 7 files changed, 760 insertions(+), 9 deletions(-) create mode 100644 yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/Bug5968MergeTest.java create mode 100644 yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/Bug5968Test.java create mode 100644 yang/yang-data-impl/src/test/resources/bug5968/foo.yang diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractNodeContainerModificationStrategy.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractNodeContainerModificationStrategy.java index f908483b40..cf1b9edeaa 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractNodeContainerModificationStrategy.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractNodeContainerModificationStrategy.java @@ -18,10 +18,10 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgum 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.ConflictingModificationAppliedException; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeConfiguration; 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.ModifiedNodeDoesNotExistException; -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.api.schema.tree.spi.MutableTreeNode; import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode; @@ -34,18 +34,22 @@ abstract class AbstractNodeContainerModificationStrategy extends SchemaAwareAppl private final Class> nodeClass; private final boolean verifyChildrenStructure; - protected AbstractNodeContainerModificationStrategy(final Class> nodeClass, + AbstractNodeContainerModificationStrategy(final Class> nodeClass, final DataTreeConfiguration treeConfig) { this.nodeClass = Preconditions.checkNotNull(nodeClass , "nodeClass"); this.verifyChildrenStructure = (treeConfig.getTreeType() == TreeType.CONFIGURATION); } + final boolean verifyChildrenStructure() { + return verifyChildrenStructure; + } + @SuppressWarnings("rawtypes") @Override void verifyStructure(final NormalizedNode writtenValue, final boolean verifyChildren) { checkArgument(nodeClass.isInstance(writtenValue), "Node %s is not of type %s", writtenValue, nodeClass); checkArgument(writtenValue instanceof NormalizedNodeContainer); - if (verifyChildrenStructure && verifyChildren) { + if (verifyChildren && verifyChildrenStructure) { final NormalizedNodeContainer container = (NormalizedNodeContainer) writtenValue; for (final Object child : container.getValue()) { checkArgument(child instanceof NormalizedNode); @@ -64,7 +68,7 @@ abstract class AbstractNodeContainerModificationStrategy extends SchemaAwareAppl @Override protected void recursivelyVerifyStructure(final NormalizedNode value) { - final NormalizedNodeContainer container = (NormalizedNodeContainer) value; + final NormalizedNodeContainer container = (NormalizedNodeContainer) value; for (final Object child : container.getValue()) { checkArgument(child instanceof NormalizedNode); final NormalizedNode castedChild = (NormalizedNode) child; @@ -314,10 +318,6 @@ abstract class AbstractNodeContainerModificationStrategy extends SchemaAwareAppl } } - protected boolean verifyChildrenStructure() { - return verifyChildrenStructure; - } - @SuppressWarnings("rawtypes") protected abstract NormalizedNodeContainerBuilder createBuilder(NormalizedNode original); 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 599ef01ae6..d77f639167 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 @@ -27,6 +27,14 @@ final class ListEntryModificationStrategy extends AbstractDataNodeContainerModif enforcer = MandatoryLeafEnforcer.forContainer(schema, treeConfig); } + @Override + void verifyStructure(final NormalizedNode writtenValue, final boolean verifyChildren) { + if (verifyChildren && verifyChildrenStructure()) { + enforcer.enforceOnTreeNode(writtenValue); + } + super.verifyStructure(writtenValue, verifyChildren); + } + @Override protected TreeNode applyMerge(final ModifiedNode modification, final TreeNode currentMeta, final Version version) { final TreeNode ret = super.applyMerge(modification, currentMeta, version); diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ModifiedNode.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ModifiedNode.java index 35da5d3650..e1757a0a74 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ModifiedNode.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ModifiedNode.java @@ -275,6 +275,7 @@ final class ModifiedNode extends NodeModification implements StoreTreeNode root = Builders.containerBuilder() + .withNodeIdentifier(new NodeIdentifier(ROOT)); + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + modificationTree.merge( + YangInstanceIdentifier.of(ROOT), + withMapNode ? root.withChild( + Builders.mapBuilder().withNodeIdentifier(new NodeIdentifier(MY_LIST)).build()).build() : root + .build()); + modificationTree.ready(); + + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + + return inMemoryDataTree; + } + + private static InMemoryDataTree emptyDataTree(final SchemaContext schemaContext) + throws DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = (InMemoryDataTree) InMemoryDataTreeFactory.getInstance().create( + DataTreeConfiguration.DEFAULT_CONFIGURATION); + inMemoryDataTree.setSchemaContext(schemaContext); + + return inMemoryDataTree; + } + + @Test + public void mergeInvalidContainerTest() throws ReactorException, DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = emptyDataTree(schemaContext); + + final MapNode myList = createMap(true); + final DataContainerNodeAttrBuilder root = Builders.containerBuilder() + .withNodeIdentifier(new NodeIdentifier(ROOT)).withChild(myList); + + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + modificationTree.merge(YangInstanceIdentifier.of(ROOT), root.build()); + + try { + modificationTree.ready(); + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + fail("Should fail due to missing mandatory leaf."); + } catch (final IllegalArgumentException e) { + assertEquals( + "Node (foo?revision=2016-07-28)my-list[{(foo?revision=2016-07-28)list-id=1}] is missing mandatory " + + "descendant /(foo?revision=2016-07-28)mandatory-leaf", e.getMessage()); + } + } + + @Test + public void mergeInvalidMapTest() throws ReactorException, DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = emptyDataTree(schemaContext); + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + mergeMap(modificationTree, true); + + try { + modificationTree.ready(); + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + fail("Should fail due to missing mandatory leaf."); + } catch (final IllegalArgumentException e) { + assertEquals( + "Node (foo?revision=2016-07-28)my-list[{(foo?revision=2016-07-28)list-id=1}] is missing mandatory " + + "descendant /(foo?revision=2016-07-28)mandatory-leaf", e.getMessage()); + } + } + + @Test + public void mergeInvalidMapEntryTest() throws ReactorException, DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = initDataTree(schemaContext, true); + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + + mergeMapEntry(modificationTree, "1", null, "common-value"); + + try { + modificationTree.ready(); + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + fail("Should fail due to missing mandatory leaf."); + } catch (final IllegalArgumentException e) { + assertEquals( + "Node (foo?revision=2016-07-28)my-list[{(foo?revision=2016-07-28)list-id=1}] is missing mandatory " + + "descendant /(foo?revision=2016-07-28)mandatory-leaf", e.getMessage()); + } + } + + private static void mergeMap(final InMemoryDataTreeModification modificationTree, final boolean mandatoryDataMissing) + throws DataValidationFailedException { + final MapNode myList = createMap(mandatoryDataMissing); + modificationTree.merge(YangInstanceIdentifier.of(ROOT).node(MY_LIST), myList); + } + + private static MapNode createMap(final boolean mandatoryDataMissing) throws DataValidationFailedException { + return Builders + .mapBuilder() + .withNodeIdentifier(new NodeIdentifier(MY_LIST)) + .withChild( + mandatoryDataMissing ? createMapEntry("1", "common-value") : createMapEntry("1", + "mandatory-value", "common-value")).build(); + } + + private static void mergeMapEntry(final InMemoryDataTreeModification modificationTree, final Object listIdValue, + final Object mandatoryLeafValue, final Object commonLeafValue) throws DataValidationFailedException { + final MapEntryNode taskEntryNode = mandatoryLeafValue == null ? createMapEntry(listIdValue, commonLeafValue) + : createMapEntry(listIdValue, mandatoryLeafValue, commonLeafValue); + + modificationTree.merge( + YangInstanceIdentifier.of(ROOT).node(MY_LIST) + .node(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, listIdValue))), + taskEntryNode); + } + + private static MapEntryNode createMapEntry(final Object listIdValue, final Object mandatoryLeafValue, + final Object commonLeafValue) throws DataValidationFailedException { + return Builders.mapEntryBuilder() + .withNodeIdentifier(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, listIdValue))) + .withChild(ImmutableNodes.leafNode(LIST_ID, listIdValue)) + .withChild(ImmutableNodes.leafNode(MANDATORY_LEAF, mandatoryLeafValue)) + .withChild(ImmutableNodes.leafNode(COMMON_LEAF, commonLeafValue)).build(); + } + + private static MapEntryNode createMapEntry(final Object listIdValue, final Object commonLeafValue) + throws DataValidationFailedException { + return Builders.mapEntryBuilder() + .withNodeIdentifier(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, listIdValue))) + .withChild(ImmutableNodes.leafNode(LIST_ID, listIdValue)) + .withChild(ImmutableNodes.leafNode(COMMON_LEAF, commonLeafValue)).build(); + } + + private static MapEntryNode createMapEntryM(final Object listIdValue, final Object mandatoryLeafValue) + throws DataValidationFailedException { + return Builders.mapEntryBuilder() + .withNodeIdentifier(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, listIdValue))) + .withChild(ImmutableNodes.leafNode(LIST_ID, listIdValue)) + .withChild(ImmutableNodes.leafNode(MANDATORY_LEAF, mandatoryLeafValue)).build(); + } + + @Test + public void mergeValidContainerTest() throws ReactorException, DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = emptyDataTree(schemaContext); + + final MapNode myList = createMap(false); + final DataContainerNodeAttrBuilder root = Builders.containerBuilder() + .withNodeIdentifier(new NodeIdentifier(ROOT)).withChild(myList); + + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + modificationTree.merge(YangInstanceIdentifier.of(ROOT), root.build()); + modificationTree.ready(); + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + } + + @Test + public void mergeValidMapTest() throws ReactorException, DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = emptyDataTree(schemaContext); + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + mergeMap(modificationTree, false); + + modificationTree.ready(); + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + } + + @Test + public void mergeValidMapEntryTest() throws ReactorException, DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = initDataTree(schemaContext, true); + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + + mergeMapEntry(modificationTree, "1", "mandatory-value", "common-value"); + + modificationTree.ready(); + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + } + + @Test + public void validMultiStepsMergeTest() throws ReactorException, DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = emptyDataTree(schemaContext); + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + + modificationTree.merge(YangInstanceIdentifier.of(ROOT), createContainerBuilder().build()); + modificationTree.merge(YangInstanceIdentifier.of(ROOT).node(MY_LIST), createMapBuilder().build()); + modificationTree.merge( + YangInstanceIdentifier.of(ROOT).node(MY_LIST) + .node(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, "1"))), + createEmptyMapEntryBuilder("1").build()); + modificationTree.merge( + YangInstanceIdentifier.of(ROOT).node(MY_LIST) + .node(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, "1"))), + createMapEntry("1", "mandatory-value", "common-value")); + + modificationTree.ready(); + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + } + + @Test + public void invalidMultiStepsMergeTest() throws ReactorException, DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = emptyDataTree(schemaContext); + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + + modificationTree.merge(YangInstanceIdentifier.of(ROOT), createContainerBuilder().build()); + modificationTree.merge(YangInstanceIdentifier.of(ROOT).node(MY_LIST), createMapBuilder().build()); + modificationTree.merge( + YangInstanceIdentifier.of(ROOT).node(MY_LIST) + .node(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, "1"))), + createEmptyMapEntryBuilder("1").build()); + modificationTree.merge( + YangInstanceIdentifier.of(ROOT).node(MY_LIST) + .node(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, "1"))), + createMapEntry("1", "common-value")); + + try { + modificationTree.ready(); + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + fail("Should fail due to missing mandatory leaf."); + } catch (final IllegalArgumentException e) { + assertEquals( + "Node (foo?revision=2016-07-28)my-list[{(foo?revision=2016-07-28)list-id=1}] is missing mandatory " + + "descendant /(foo?revision=2016-07-28)mandatory-leaf", e.getMessage()); + } + } + + private static DataContainerNodeAttrBuilder createEmptyMapEntryBuilder( + final Object listIdValue) throws DataValidationFailedException { + return Builders.mapEntryBuilder() + .withNodeIdentifier(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, listIdValue))) + .withChild(ImmutableNodes.leafNode(LIST_ID, listIdValue)); + } + + private static CollectionNodeBuilder createMapBuilder() throws DataValidationFailedException { + return Builders.mapBuilder().withNodeIdentifier(new NodeIdentifier(MY_LIST)); + } + + private static DataContainerNodeAttrBuilder createContainerBuilder() + throws DataValidationFailedException { + return Builders.containerBuilder().withNodeIdentifier(new NodeIdentifier(ROOT)); + } + + @Test + public void validMultiStepsWriteAndMergeTest() throws ReactorException, DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = emptyDataTree(schemaContext); + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + + modificationTree.write(YangInstanceIdentifier.of(ROOT), createContainerBuilder().build()); + modificationTree.merge(YangInstanceIdentifier.of(ROOT).node(MY_LIST), createMapBuilder().build()); + modificationTree.merge( + YangInstanceIdentifier.of(ROOT).node(MY_LIST) + .node(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, "1"))), + createEmptyMapEntryBuilder("1").build()); + modificationTree.merge( + YangInstanceIdentifier.of(ROOT).node(MY_LIST) + .node(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, "1"))), + createMapEntry("1", "mandatory-value", "common-value")); + + modificationTree.ready(); + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + } + + @Test + public void invalidMultiStepsWriteAndMergeTest() throws ReactorException, DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = emptyDataTree(schemaContext); + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + + modificationTree.write(YangInstanceIdentifier.of(ROOT), createContainerBuilder().build()); + modificationTree.merge(YangInstanceIdentifier.of(ROOT).node(MY_LIST), createMapBuilder().build()); + modificationTree.merge( + YangInstanceIdentifier.of(ROOT).node(MY_LIST) + .node(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, "1"))), + createEmptyMapEntryBuilder("1").build()); + modificationTree.merge( + YangInstanceIdentifier.of(ROOT).node(MY_LIST) + .node(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, "1"))), + createMapEntry("1", "common-value")); + + try { + modificationTree.ready(); + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + fail("Should fail due to missing mandatory leaf."); + } catch (final IllegalArgumentException e) { + assertEquals( + "Node (foo?revision=2016-07-28)my-list[{(foo?revision=2016-07-28)list-id=1}] is missing mandatory " + + "descendant /(foo?revision=2016-07-28)mandatory-leaf", e.getMessage()); + } + } + + @Test + public void validMapEntryMultiCommitMergeTest() throws ReactorException, DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = emptyDataTree(schemaContext); + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + + modificationTree.write(YangInstanceIdentifier.of(ROOT), createContainerBuilder().build()); + modificationTree.merge(YangInstanceIdentifier.of(ROOT).node(MY_LIST), createMapBuilder().build()); + modificationTree.merge( + YangInstanceIdentifier.of(ROOT).node(MY_LIST) + .node(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, "1"))), + createEmptyMapEntryBuilder("1").build()); + modificationTree.merge( + YangInstanceIdentifier.of(ROOT).node(MY_LIST) + .node(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, "1"))), + createMapEntryM("1", "mandatory-value")); + + modificationTree.ready(); + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + + final InMemoryDataTreeModification modificationTree2 = inMemoryDataTree.takeSnapshot().newModification(); + modificationTree2.merge( + YangInstanceIdentifier.of(ROOT).node(MY_LIST) + .node(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, "1"))), + createMapEntry("1", "common-value")); + modificationTree2.ready(); + inMemoryDataTree.validate(modificationTree2); + final DataTreeCandidate prepare2 = inMemoryDataTree.prepare(modificationTree2); + inMemoryDataTree.commit(prepare2); + } + + @Test + public void invalidMapEntryMultiCommitMergeTest() throws ReactorException, DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = emptyDataTree(schemaContext); + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + + modificationTree.write(YangInstanceIdentifier.of(ROOT), createContainerBuilder().build()); + modificationTree.merge(YangInstanceIdentifier.of(ROOT).node(MY_LIST), createMapBuilder().build()); + modificationTree.merge( + YangInstanceIdentifier.of(ROOT).node(MY_LIST) + .node(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, "1"))), + createEmptyMapEntryBuilder("1").build()); + modificationTree.merge( + YangInstanceIdentifier.of(ROOT).node(MY_LIST) + .node(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, "1"))), + createMapEntryM("1", "mandatory-value")); + + modificationTree.ready(); + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + + final InMemoryDataTreeModification modificationTree2 = inMemoryDataTree.takeSnapshot().newModification(); + modificationTree2.write( + YangInstanceIdentifier.of(ROOT).node(MY_LIST) + .node(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, "1"))), + createMapEntry("1", "common-value")); + modificationTree2.merge( + YangInstanceIdentifier.of(ROOT).node(MY_LIST) + .node(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, "1"))), + createMapEntryM("1", "mandatory-value")); + modificationTree2.merge( + YangInstanceIdentifier.of(ROOT).node(MY_LIST) + .node(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, "2"))), + createMapEntry("2", "common-value")); + try { + modificationTree2.ready(); + inMemoryDataTree.validate(modificationTree2); + final DataTreeCandidate prepare2 = inMemoryDataTree.prepare(modificationTree2); + inMemoryDataTree.commit(prepare2); + fail("Should fail due to missing mandatory leaf."); + } catch (final IllegalArgumentException e) { + assertEquals( + "Node (foo?revision=2016-07-28)my-list[{(foo?revision=2016-07-28)list-id=2}] is missing mandatory " + + "descendant /(foo?revision=2016-07-28)mandatory-leaf", e.getMessage()); + } + } + + @Ignore + @Test + /* + * FIXME: This test consists of two transactions (i.e. data tree modifications) on empty data tree. + * The first one writes mandatory data and second one writes common data without any mandatory data. + * Is it correct to rely on other transactions that they fill mandatory data before our transaction is + * commited ? + */ + public void validMapEntryMultiCommitMergeTest2() throws ReactorException, DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = emptyDataTree(schemaContext); + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + final InMemoryDataTreeModification modificationTree2 = inMemoryDataTree.takeSnapshot().newModification(); + + modificationTree.write(YangInstanceIdentifier.of(ROOT), createContainerBuilder().build()); + modificationTree.merge(YangInstanceIdentifier.of(ROOT).node(MY_LIST), createMapBuilder().build()); + modificationTree.merge( + YangInstanceIdentifier.of(ROOT).node(MY_LIST) + .node(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, "1"))), + createEmptyMapEntryBuilder("1").build()); + modificationTree.merge( + YangInstanceIdentifier.of(ROOT).node(MY_LIST) + .node(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, "1"))), + createMapEntryM("1", "mandatory-value")); + + modificationTree.ready(); + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + System.out.println(inMemoryDataTree); + + modificationTree2.merge( + YangInstanceIdentifier.of(ROOT).node(MY_LIST) + .node(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, "1"))), + createMapEntry("1", "common-value")); + modificationTree2.ready(); + inMemoryDataTree.validate(modificationTree2); + final DataTreeCandidate prepare2 = inMemoryDataTree.prepare(modificationTree2); + inMemoryDataTree.commit(prepare2); + + System.out.println(inMemoryDataTree); + } +} diff --git a/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/Bug5968Test.java b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/Bug5968Test.java new file mode 100644 index 0000000000..523d7d2473 --- /dev/null +++ b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/Bug5968Test.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2016 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.junit.Assert.fail; + +import com.google.common.collect.ImmutableMap; +import org.junit.Before; +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.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +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.tree.DataTreeCandidate; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeConfiguration; +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.api.DataContainerNodeAttrBuilder; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; + +public class Bug5968Test { + private static final String NS = "foo"; + private static final String REV = "2016-07-28"; + private static final QName ROOT = QName.create(NS, REV, "root"); + private static final QName MY_LIST = QName.create(NS, REV, "my-list"); + private static final QName LIST_ID = QName.create(NS, REV, "list-id"); + private static final QName MANDATORY_LEAF = QName.create(NS, REV, "mandatory-leaf"); + private static final QName COMMON_LEAF = QName.create(NS, REV, "common-leaf"); + private SchemaContext schemaContext; + + @Before + public void init() throws ReactorException { + this.schemaContext = TestModel.createTestContext("/bug5968/foo.yang"); + assertNotNull("Schema context must not be null.", this.schemaContext); + } + + private static InMemoryDataTree initDataTree(final SchemaContext schemaContext, final boolean withMapNode) + throws DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = (InMemoryDataTree) InMemoryDataTreeFactory.getInstance().create( + DataTreeConfiguration.DEFAULT_CONFIGURATION); + inMemoryDataTree.setSchemaContext(schemaContext); + + final DataContainerNodeAttrBuilder root = Builders.containerBuilder() + .withNodeIdentifier(new NodeIdentifier(ROOT)); + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + modificationTree.write( + YangInstanceIdentifier.of(ROOT), + withMapNode ? root.withChild( + Builders.mapBuilder().withNodeIdentifier(new NodeIdentifier(MY_LIST)).build()).build() : root + .build()); + modificationTree.ready(); + + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + + return inMemoryDataTree; + } + + private static InMemoryDataTree emptyDataTree(final SchemaContext schemaContext) + throws DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = (InMemoryDataTree) InMemoryDataTreeFactory.getInstance().create( + DataTreeConfiguration.DEFAULT_CONFIGURATION); + inMemoryDataTree.setSchemaContext(schemaContext); + + return inMemoryDataTree; + } + + @Test + public void writeInvalidContainerTest() throws ReactorException, DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = emptyDataTree(schemaContext); + + final MapNode myList = createMap(true); + final DataContainerNodeAttrBuilder root = Builders.containerBuilder() + .withNodeIdentifier(new NodeIdentifier(ROOT)).withChild(myList); + + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + modificationTree.write(YangInstanceIdentifier.of(ROOT), root.build()); + + try { + modificationTree.ready(); + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + fail("Should fail due to missing mandatory leaf."); + } catch (final IllegalArgumentException e) { + assertEquals( + "Node (foo?revision=2016-07-28)my-list[{(foo?revision=2016-07-28)list-id=1}] is missing mandatory " + + "descendant /(foo?revision=2016-07-28)mandatory-leaf", e.getMessage()); + } + } + + @Test + public void writeInvalidMapTest() throws ReactorException, DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = emptyDataTree(schemaContext); + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + writeMap(modificationTree, true); + + try { + modificationTree.ready(); + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + fail("Should fail due to missing mandatory leaf."); + } catch (final IllegalArgumentException e) { + assertEquals( + "Node (foo?revision=2016-07-28)my-list[{(foo?revision=2016-07-28)list-id=1}] is missing mandatory " + + "descendant /(foo?revision=2016-07-28)mandatory-leaf", e.getMessage()); + } + } + + @Test + public void writeInvalidMapEntryTest() throws ReactorException, DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = initDataTree(schemaContext, true); + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + + writeMapEntry(modificationTree, "1", null, "common-value"); + + try { + modificationTree.ready(); + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + fail("Should fail due to missing mandatory leaf."); + } catch (final IllegalArgumentException e) { + assertEquals( + "Node (foo?revision=2016-07-28)my-list[{(foo?revision=2016-07-28)list-id=1}] is missing mandatory " + + "descendant /(foo?revision=2016-07-28)mandatory-leaf", e.getMessage()); + } + } + + private static void writeMap(final InMemoryDataTreeModification modificationTree, final boolean mandatoryDataMissing) + throws DataValidationFailedException { + final MapNode myList = createMap(mandatoryDataMissing); + modificationTree.write(YangInstanceIdentifier.of(ROOT).node(MY_LIST), myList); + } + + private static MapNode createMap(final boolean mandatoryDataMissing) throws DataValidationFailedException { + return Builders + .mapBuilder() + .withNodeIdentifier(new NodeIdentifier(MY_LIST)) + .withChild( + mandatoryDataMissing ? createMapEntry("1", "common-value") : createMapEntry("1", + "mandatory-value", "common-value")).build(); + } + + private static void writeMapEntry(final InMemoryDataTreeModification modificationTree, final Object listIdValue, + final Object mandatoryLeafValue, final Object commonLeafValue) throws DataValidationFailedException { + final MapEntryNode taskEntryNode = mandatoryLeafValue == null ? createMapEntry(listIdValue, commonLeafValue) + : createMapEntry(listIdValue, mandatoryLeafValue, commonLeafValue); + + modificationTree.write( + YangInstanceIdentifier.of(ROOT).node(MY_LIST) + .node(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, listIdValue))), + taskEntryNode); + } + + private static MapEntryNode createMapEntry(final Object listIdValue, final Object mandatoryLeafValue, + final Object commonLeafValue) throws DataValidationFailedException { + return Builders.mapEntryBuilder() + .withNodeIdentifier(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, listIdValue))) + .withChild(ImmutableNodes.leafNode(LIST_ID, listIdValue)) + .withChild(ImmutableNodes.leafNode(MANDATORY_LEAF, mandatoryLeafValue)) + .withChild(ImmutableNodes.leafNode(COMMON_LEAF, commonLeafValue)).build(); + } + + private static MapEntryNode createMapEntry(final Object listIdValue, final Object commonLeafValue) + throws DataValidationFailedException { + return Builders.mapEntryBuilder() + .withNodeIdentifier(new NodeIdentifierWithPredicates(MY_LIST, ImmutableMap.of(LIST_ID, listIdValue))) + .withChild(ImmutableNodes.leafNode(LIST_ID, listIdValue)) + .withChild(ImmutableNodes.leafNode(COMMON_LEAF, commonLeafValue)).build(); + } + + @Test + public void writeValidContainerTest() throws ReactorException, DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = emptyDataTree(schemaContext); + + final MapNode myList = createMap(false); + final DataContainerNodeAttrBuilder root = Builders.containerBuilder() + .withNodeIdentifier(new NodeIdentifier(ROOT)).withChild(myList); + + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + modificationTree.write(YangInstanceIdentifier.of(ROOT), root.build()); + modificationTree.ready(); + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + } + + @Test + public void writeValidMapTest() throws ReactorException, DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = emptyDataTree(schemaContext); + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + writeMap(modificationTree, false); + + modificationTree.ready(); + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + } + + @Test + public void writeValidMapEntryTest() throws ReactorException, DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = initDataTree(schemaContext, true); + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + + writeMapEntry(modificationTree, "1", "mandatory-value", "common-value"); + + modificationTree.ready(); + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + } +} diff --git a/yang/yang-data-impl/src/test/resources/bug5968/foo.yang b/yang/yang-data-impl/src/test/resources/bug5968/foo.yang new file mode 100644 index 0000000000..a3bdec4cb6 --- /dev/null +++ b/yang/yang-data-impl/src/test/resources/bug5968/foo.yang @@ -0,0 +1,25 @@ +module foo { + yang-version 1; + namespace "foo"; + prefix foo; + + revision 2016-07-28 { + description "test"; + } + + container root { + list my-list { + key "list-id"; + leaf list-id { + type string; + } + leaf mandatory-leaf { + type string; + mandatory true; + } + leaf common-leaf { + type string; + } + } + } +} -- 2.36.6