From 36db1c816bd8454d4539e895ae02bbbcfba2b740 Mon Sep 17 00:00:00 2001 From: Xiao Liang Date: Sat, 18 Feb 2017 07:51:58 +0800 Subject: [PATCH] Bug 7945: Fix schema validation for augmentation of case Add modification strategy of case augmentation as child of choice. Change-Id: Ie7b03f2d06ab279f0dd9c85edf9a370b0f63abac Signed-off-by: Xiao Liang --- .../data/impl/schema/tree/CaseEnforcer.java | 43 +++++- .../tree/ChoiceModificationStrategy.java | 9 +- .../impl/schema/tree/CaseAugmentTest.java | 137 ++++++++++++++++++ .../src/test/resources/case-augment-test.yang | 33 +++++ 4 files changed, 215 insertions(+), 7 deletions(-) create mode 100644 yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/CaseAugmentTest.java create mode 100644 yang/yang-data-impl/src/test/resources/case-augment-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 09d061197d..548bfb5f9d 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 @@ -10,40 +10,59 @@ package org.opendaylight.yangtools.yang.data.impl.schema.tree; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; +import com.google.common.collect.Sets; + import java.util.Map; import java.util.Map.Entry; import java.util.Set; + import org.opendaylight.yangtools.concepts.Immutable; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; 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.tree.DataTreeConfiguration; import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType; +import org.opendaylight.yangtools.yang.data.impl.schema.SchemaUtils; +import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; 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 Map augmentations; private final MandatoryLeafEnforcer enforcer; - private CaseEnforcer(final Map children, final MandatoryLeafEnforcer enforcer) { + private CaseEnforcer(final Map children, + final Map augmentations, + final MandatoryLeafEnforcer enforcer) { this.children = Preconditions.checkNotNull(children); + this.augmentations = Preconditions.checkNotNull(augmentations); this.enforcer = Preconditions.checkNotNull(enforcer); } static CaseEnforcer forTree(final ChoiceCaseNode schema, final DataTreeConfiguration treeConfig) { final TreeType type = treeConfig.getTreeType(); - final Builder builder = ImmutableMap.builder(); + final Builder childrenBuilder = ImmutableMap.builder(); + final Builder augmentationsBuilder = ImmutableMap.builder(); if (SchemaAwareApplyOperation.belongsToTree(type, schema)) { for (final DataSchemaNode child : schema.getChildNodes()) { if (SchemaAwareApplyOperation.belongsToTree(type, child)) { - builder.put(NodeIdentifier.create(child.getQName()), child); + childrenBuilder.put(NodeIdentifier.create(child.getQName()), child); + } + } + for (final AugmentationSchema augment : schema.getAvailableAugmentations()) { + if (augment.getChildNodes().stream() + .anyMatch(child -> SchemaAwareApplyOperation.belongsToTree(type, child))) { + augmentationsBuilder.put(SchemaUtils.getNodeIdentifierForAugmentation(augment), augment); } } } - final Map children = builder.build(); - return children.isEmpty() ? null : new CaseEnforcer(children, MandatoryLeafEnforcer.forContainer(schema, - treeConfig)); + final Map children = childrenBuilder.build(); + final Map augmentations = augmentationsBuilder.build(); + return children.isEmpty() ? null + : new CaseEnforcer(children, augmentations, MandatoryLeafEnforcer.forContainer(schema, treeConfig)); } Set> getChildEntries() { @@ -54,6 +73,18 @@ final class CaseEnforcer implements Immutable { return children.keySet(); } + Set> getAugmentationEntries() { + return augmentations.entrySet(); + } + + Set getAugmentationIdentifiers() { + return augmentations.keySet(); + } + + Set getAllChildIdentifiers() { + return Sets.union(children.keySet(), augmentations.keySet()); + } + void enforceOnTreeNode(final NormalizedNode normalizedNode) { enforcer.enforceOnData(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 c991f71a76..6dab318451 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 @@ -21,6 +21,8 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; + +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; 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.ChoiceNode; @@ -32,6 +34,7 @@ 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.ImmutableChoiceNodeBuilder; +import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; @@ -54,6 +57,10 @@ final class ChoiceModificationStrategy extends AbstractNodeContainerModification childBuilder.put(e.getKey(), SchemaAwareApplyOperation.from(e.getValue(), treeConfig)); enforcerBuilder.put(e.getKey(), enforcer); } + for (final Entry e : enforcer.getAugmentationEntries()) { + childBuilder.put(e.getKey(), new AugmentationModificationStrategy(e.getValue(), caze, treeConfig)); + enforcerBuilder.put(e.getKey(), enforcer); + } } } childNodes = childBuilder.build(); @@ -101,7 +108,7 @@ final class ChoiceModificationStrategy extends AbstractNodeContainerModification // Make sure no leaves from other cases are present for (final CaseEnforcer other : exclusions.get(enforcer)) { - for (final NodeIdentifier id : other.getChildIdentifiers()) { + for (final PathArgument id : other.getAllChildIdentifiers()) { final Optional> maybeChild = NormalizedNodes.getDirectChild(normalizedNode, id); Preconditions.checkArgument(!maybeChild.isPresent(), "Child %s (from case %s) implies non-presence of child %s (from case %s), which is %s", diff --git a/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/CaseAugmentTest.java b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/CaseAugmentTest.java new file mode 100644 index 0000000000..282aa3caab --- /dev/null +++ b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/CaseAugmentTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2017 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.collect.ImmutableSet; +import org.junit.Before; +import org.junit.Test; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; +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; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes.leafNode; + +public class CaseAugmentTest { + + private SchemaContext schemaContext; + private final QName CHOICE1_QNAME = QName.create(TestModel.TEST_QNAME, "choice1"); + private final QName C1L1_QNAME = QName.create(TestModel.TEST_QNAME, "case1-leaf1"); + private final QName C1L2_QNAME = QName.create(TestModel.TEST_QNAME, "case1-leaf2"); + private final QName C1L3_QNAME = QName.create(TestModel.TEST_QNAME, "case1-leaf3"); + private final QName C2L1_QNAME = QName.create(TestModel.TEST_QNAME, "case2-leaf1"); + private final NodeIdentifier CHOICE_ID = new NodeIdentifier(CHOICE1_QNAME); + private final AugmentationIdentifier AUGMENT_ID = + new AugmentationIdentifier(ImmutableSet.builder().add(C1L2_QNAME).add(C1L3_QNAME).build()); + + + @Before + public void prepare() throws ReactorException { + schemaContext = TestModel.createTestContext("/case-augment-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 testWriteAugment() throws DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = initDataTree(); + + AugmentationNode augmentationNode = Builders.augmentationBuilder() + .withNodeIdentifier(AUGMENT_ID) + .withChild(leafNode(C1L2_QNAME, "leaf-value")) + .build(); + + final ContainerNode container = Builders.containerBuilder() + .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME)) + .withChild( + Builders.choiceBuilder().withNodeIdentifier(CHOICE_ID) + .withChild(augmentationNode) + .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 testWriteCase1All() throws DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = initDataTree(); + + AugmentationNode augmentationNode = Builders.augmentationBuilder() + .withNodeIdentifier(AUGMENT_ID) + .withChild(leafNode(C1L2_QNAME, "leaf-value")) + .withChild(leafNode(C1L3_QNAME, "leaf-value")) + .build(); + + final ContainerNode container = Builders + .containerBuilder() + .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME)) + .withChild( + Builders.choiceBuilder().withNodeIdentifier(CHOICE_ID) + .withChild(leafNode(QName.create(TestModel.TEST_QNAME, "case1-leaf1"), "leaf-value")) + .withChild(augmentationNode) + .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(expected = IllegalArgumentException.class) + public void testWriteConflict() throws DataValidationFailedException { + final InMemoryDataTree inMemoryDataTree = initDataTree(); + + AugmentationNode augmentationNode = Builders.augmentationBuilder() + .withNodeIdentifier(AUGMENT_ID) + .withChild(leafNode(C1L2_QNAME, "leaf-value")) + .build(); + + final ContainerNode container = Builders.containerBuilder() + .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME)) + .withChild( + Builders.choiceBuilder().withNodeIdentifier(CHOICE_ID) + .withChild(augmentationNode) + .withChild(leafNode(QName.create(TestModel.TEST_QNAME, "case2-leaf1"), "leaf-value")) + .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) { + assertTrue(e.getMessage().contains("implies non-presence of child")); + throw e; + } + } + +} diff --git a/yang/yang-data-impl/src/test/resources/case-augment-test.yang b/yang/yang-data-impl/src/test/resources/case-augment-test.yang new file mode 100644 index 0000000000..4ee9820a6e --- /dev/null +++ b/yang/yang-data-impl/src/test/resources/case-augment-test.yang @@ -0,0 +1,33 @@ +module case-augment-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 { + choice choice1 { + case case1 { + leaf case1-leaf1 { + type string; + } + } + case case2 { + leaf case2-leaf1 { + type string; + } + } + } + } + + augment "/test/choice1/case1" { + leaf case1-leaf2 { + type string; + } + leaf case1-leaf3 { + type string; + } + } +} -- 2.36.6