From 11d15938d7d4a0a6558a17fac0dea962ee709d68 Mon Sep 17 00:00:00 2001 From: Jan Hajnar Date: Fri, 6 Feb 2015 15:26:34 +0100 Subject: [PATCH] Bug 2362 - Milestone: Basic constraints validation * added validator class for min and max elements constraint * added validation method calls from NormalizedNodeContainerModificationStrategy and UnkeyedListModificationStrategy * added tests Change-Id: I384837f1a2a6dc730b824d509f7080df7408fd3f Signed-off-by: Jan Hajnar Signed-off-by: Robert Varga --- .../schema/tree/MinMaxElementsValidation.java | 178 +++++++++++ .../tree/SchemaAwareApplyOperation.java | 33 +- .../tree/ListConstraintsValidationTest.java | 298 ++++++++++++++++++ ...ist-constraints-validation-test-model.yang | 47 +++ 4 files changed, 541 insertions(+), 15 deletions(-) create mode 100644 yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MinMaxElementsValidation.java create mode 100644 yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ListConstraintsValidationTest.java create mode 100644 yang/yang-data-impl/src/test/resources/list-constraints-validation-test-model.yang diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MinMaxElementsValidation.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MinMaxElementsValidation.java new file mode 100644 index 0000000000..306d9c8581 --- /dev/null +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MinMaxElementsValidation.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the terms of the Eclipse + * Public License v1.0 which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.data.impl.schema.tree; + + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer; +import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException; +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.ConstraintDefinition; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class MinMaxElementsValidation extends SchemaAwareApplyOperation { + + private static final Logger LOG = LoggerFactory.getLogger(MinMaxElementsValidation.class); + private final SchemaAwareApplyOperation delegate; + private final Integer minElements; + private final Integer maxElements; + + private MinMaxElementsValidation(final SchemaAwareApplyOperation delegate, final Integer minElements, + final Integer maxElements) { + this.delegate = Preconditions.checkNotNull(delegate); + this.minElements = minElements; + this.maxElements = maxElements; + } + + static final SchemaAwareApplyOperation from(final SchemaAwareApplyOperation delegate, final DataSchemaNode schema) { + final ConstraintDefinition constraints = schema.getConstraints(); + if (constraints == null || (constraints.getMinElements() == null && constraints.getMaxElements() == null)) { + return delegate; + } + return new MinMaxElementsValidation(delegate, constraints.getMinElements(), constraints.getMaxElements()); + + } + + private void checkMinMaxElements(final YangInstanceIdentifier path, final NodeModification nodeMod, + final Optional current) throws DataValidationFailedException { + if (!(nodeMod instanceof ModifiedNode)) { + LOG.debug("Could not validate {}, does not implement expected class {}", nodeMod, ModifiedNode.class); + return; + } + final ModifiedNode modification = (ModifiedNode) nodeMod; + final int childrenBefore; + if (current.isPresent()) { + childrenBefore = numOfChildrenFromValue(current.get().getData()); + } else { + childrenBefore = 0; + } + + final int childrenAfter; + if (modification.getWrittenValue() != null) { + childrenAfter = numOfChildrenFromValue(modification.getWrittenValue()); + } else { + childrenAfter = 0; + } + + final int childrenTotal = childrenBefore + childrenAfter + numOfChildrenFromChildMods(modification, current); + if (minElements != null && minElements > childrenTotal) { + throw new DataValidationFailedException(path, String.format( + "%s does not have enough elements (%s), needs at least %s", modification.getIdentifier(), + childrenTotal, minElements)); + } + if (maxElements != null && maxElements < childrenTotal) { + throw new DataValidationFailedException(path, String.format( + "%s has too many elements (%s), can have at most %s", modification.getIdentifier(), childrenTotal, + maxElements)); + } + } + + private static int numOfChildrenFromValue(final NormalizedNode value) { + if (value instanceof NormalizedNodeContainer) { + return ((NormalizedNodeContainer) value).getValue().size(); + } else if (value instanceof UnkeyedListNode) { + return ((UnkeyedListNode) value).getSize(); + } + + throw new IllegalArgumentException(String.format( + "Unexpected type '%s', expected types are NormalizedNodeContainer and UnkeyedListNode", + value.getClass())); + } + + private static int numOfChildrenFromChildMods(final ModifiedNode modification, final Optional current) { + int result = 0; + for (final ModifiedNode modChild : modification.getChildren()) { + switch (modChild.getOperation()) { + case WRITE: + result++; + break; + case MERGE: + if (!current.isPresent()) { + result++; + } + break; + case DELETE: + result--; + break; + case NONE: + case TOUCH: + // NOOP + break; + default: + throw new IllegalArgumentException("Unsupported operation type: " + modChild.getOperation()); + } + } + return result; + } + + @Override + protected void checkTouchApplicable(final YangInstanceIdentifier path, final NodeModification modification, + final Optional current) throws DataValidationFailedException { + delegate.checkTouchApplicable(path, modification, current); + checkMinMaxElements(path, modification, current); + } + + @Override + protected void checkMergeApplicable(final YangInstanceIdentifier path, final NodeModification modification, + final Optional current) throws DataValidationFailedException { + delegate.checkMergeApplicable(path, modification, current); + checkMinMaxElements(path, modification, current); + } + + @Override + protected void checkWriteApplicable(final YangInstanceIdentifier path, final NodeModification modification, + final Optional current) throws DataValidationFailedException { + delegate.checkWriteApplicable(path, modification, current); + checkMinMaxElements(path, modification, current); + } + + + @Override + public Optional getChild(final PathArgument child) { + return delegate.getChild(child); + } + + @Override + void verifyStructure(final ModifiedNode modification) throws IllegalArgumentException { + delegate.verifyStructure(modification); + } + + @Override + protected TreeNode applyMerge(final ModifiedNode modification, final TreeNode currentMeta, final Version version) { + return delegate.applyMerge(modification, currentMeta, version); + } + + @Override + protected TreeNode applyTouch(final ModifiedNode modification, final TreeNode currentMeta, final Version version) { + return delegate.applyTouch(modification, currentMeta, version); + } + + @Override + protected TreeNode applyWrite(final ModifiedNode modification, final Optional currentMeta, + final Version version) { + return delegate.applyWrite(modification, currentMeta, version); + } + + @Override + protected void verifyWrittenStructure(final NormalizedNode writtenValue) { + delegate.verifyWrittenStructure(writtenValue); + } + + @Override + protected ChildTrackingPolicy getChildPolicy() { + return delegate.getChildPolicy(); + } +} diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/SchemaAwareApplyOperation.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/SchemaAwareApplyOperation.java index 80ef74cf9a..ad2e60b379 100644 --- a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/SchemaAwareApplyOperation.java +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/SchemaAwareApplyOperation.java @@ -53,8 +53,8 @@ abstract class SchemaAwareApplyOperation extends ModificationApplyOperation { public static SchemaAwareApplyOperation from(final DataNodeContainer resolvedTree, final AugmentationTarget augSchemas, final AugmentationIdentifier identifier) { - for (AugmentationSchema potential : augSchemas.getAvailableAugmentations()) { - for (DataSchemaNode child : potential.getChildNodes()) { + for (final AugmentationSchema potential : augSchemas.getAvailableAugmentations()) { + for (final DataSchemaNode child : potential.getChildNodes()) { if (identifier.getPossibleChildNames().contains(child.getQName())) { return new AugmentationModificationStrategy(potential, resolvedTree); } @@ -72,26 +72,29 @@ abstract class SchemaAwareApplyOperation extends ModificationApplyOperation { } private static SchemaAwareApplyOperation fromListSchemaNode(final ListSchemaNode schemaNode) { - List keyDefinition = schemaNode.getKeyDefinition(); + final List keyDefinition = schemaNode.getKeyDefinition(); + final SchemaAwareApplyOperation op; if (keyDefinition == null || keyDefinition.isEmpty()) { - return new UnkeyedListModificationStrategy(schemaNode); - } - if (schemaNode.isUserOrdered()) { - return new OrderedMapModificationStrategy(schemaNode); + op = new UnkeyedListModificationStrategy(schemaNode); + } else if (schemaNode.isUserOrdered()) { + op = new OrderedMapModificationStrategy(schemaNode); + } else { + op = new UnorderedMapModificationStrategy(schemaNode); } - - return new UnorderedMapModificationStrategy(schemaNode); + return MinMaxElementsValidation.from(op, schemaNode); } private static SchemaAwareApplyOperation fromLeafListSchemaNode(final LeafListSchemaNode schemaNode) { + final SchemaAwareApplyOperation op; if(schemaNode.isUserOrdered()) { - return new OrderedLeafSetModificationStrategy(schemaNode); + op = new OrderedLeafSetModificationStrategy(schemaNode); } else { - return new UnorderedLeafSetModificationStrategy(schemaNode); + op = new UnorderedLeafSetModificationStrategy(schemaNode); } + return MinMaxElementsValidation.from(op, schemaNode); } - private static final void checkNotConflicting(final YangInstanceIdentifier path, final TreeNode original, final TreeNode current) throws ConflictingModificationAppliedException { + protected static final void checkNotConflicting(final YangInstanceIdentifier path, final TreeNode original, final TreeNode current) throws ConflictingModificationAppliedException { checkConflicting(path, original.getVersion().equals(current.getVersion()), "Node was replaced by other transaction."); checkConflicting(path, original.getSubtreeVersion().equals(current.getSubtreeVersion()), @@ -99,7 +102,7 @@ abstract class SchemaAwareApplyOperation extends ModificationApplyOperation { } protected final ModificationApplyOperation resolveChildOperation(final PathArgument child) { - Optional potential = getChild(child); + final Optional potential = getChild(child); Preconditions.checkArgument(potential.isPresent(), "Operation for child %s is not defined.", child); return potential.get(); } @@ -134,7 +137,7 @@ abstract class SchemaAwareApplyOperation extends ModificationApplyOperation { } protected void checkMergeApplicable(final YangInstanceIdentifier path, final NodeModification modification, final Optional current) throws DataValidationFailedException { - Optional original = modification.getOriginal(); + final Optional original = modification.getOriginal(); if (original.isPresent() && current.isPresent()) { /* * We need to do conflict detection only and only if the value of leaf changed @@ -160,7 +163,7 @@ abstract class SchemaAwareApplyOperation extends ModificationApplyOperation { */ protected void checkWriteApplicable(final YangInstanceIdentifier path, final NodeModification modification, final Optional current) throws DataValidationFailedException { - Optional original = modification.getOriginal(); + final Optional original = modification.getOriginal(); if (original.isPresent() && current.isPresent()) { checkNotConflicting(path, original.get(), current.get()); } else if(original.isPresent()) { diff --git a/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ListConstraintsValidationTest.java b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ListConstraintsValidationTest.java new file mode 100644 index 0000000000..7f4e20f8a6 --- /dev/null +++ b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/ListConstraintsValidationTest.java @@ -0,0 +1,298 @@ +/* + * 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 static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import com.google.common.base.Optional; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +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.schema.LeafSetEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode; +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.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer; +import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate; +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.ImmutableNodes; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafSetEntryNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafSetNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableUnkeyedListEntryNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableUnkeyedListNodeBuilder; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ListConstraintsValidationTest { + private static final Logger LOG = LoggerFactory.getLogger(ListConstraintsValidationTest.class); + + private static final String CONSTRAINTS_VALIDATION_TEST_YANG = "/list-constraints-validation-test-model.yang"; + private SchemaContext schemaContext; + private RootModificationApplyOperation rootOper; + + private InMemoryDataTree inMemoryDataTree; + + private static final QName MASTER_CONTAINER_QNAME = QName.create("urn:opendaylight:params:xml:ns:yang:list-constraints-validation-test-model", "2015-02-02", + "master-container"); + private static final QName MIN_MAX_LIST_QNAME = QName.create(MASTER_CONTAINER_QNAME, "min-max-list"); + private static final QName MIN_MAX_KEY_LEAF_QNAME = QName.create(MASTER_CONTAINER_QNAME, "min-max-key-leaf"); + private static final QName UNBOUNDED_LIST_QNAME = QName.create(MASTER_CONTAINER_QNAME, "unbounded-list"); + private static final QName UNBOUNDED_KEY_LEAF_QNAME = QName.create(MASTER_CONTAINER_QNAME, "unbounded-key-leaf"); + private static final QName MIN_MAX_LEAF_LIST_QNAME = QName.create(MASTER_CONTAINER_QNAME, "min-max-leaf-list"); + private static final QName UNBOUNDED_LEAF_LIST_QNAME = QName.create(MASTER_CONTAINER_QNAME, "unbounded-leaf-list"); + private static final QName UNKEYED_LIST_QNAME = QName.create(MASTER_CONTAINER_QNAME, "unkeyed-list"); + private static final QName UNKEYED_LEAF_QNAME = QName.create(MASTER_CONTAINER_QNAME, "unkeyed-leaf"); + + private static final YangInstanceIdentifier MASTER_CONTAINER_PATH = YangInstanceIdentifier.of(MASTER_CONTAINER_QNAME); + private static final YangInstanceIdentifier MIN_MAX_LIST_PATH = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH) + .node(MIN_MAX_LIST_QNAME).build(); + private static final YangInstanceIdentifier UNBOUNDED_LIST_PATH = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH) + .node(UNBOUNDED_LIST_QNAME).build(); + private static final YangInstanceIdentifier MIN_MAX_LEAF_LIST_PATH = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH) + .node(MIN_MAX_LEAF_LIST_QNAME).build(); + private static final YangInstanceIdentifier UNBOUNDED_LEAF_LIST_PATH = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH) + .node(UNBOUNDED_LEAF_LIST_QNAME).build(); + private static final YangInstanceIdentifier UNKEYED_LIST_PATH = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH) + .node(UNKEYED_LIST_QNAME).build(); + + @Before + public void prepare() { + schemaContext = createTestContext(); + assertNotNull("Schema context must not be null.", schemaContext); + rootOper = RootModificationApplyOperation.from(SchemaAwareApplyOperation.from(schemaContext)); + inMemoryDataTree = (InMemoryDataTree) InMemoryDataTreeFactory.getInstance().create(); + inMemoryDataTree.setSchemaContext(schemaContext); + final InMemoryDataTreeSnapshot initialDataTreeSnapshot = inMemoryDataTree.takeSnapshot(); + final DataTreeModification modificationTree = new InMemoryDataTreeModification(initialDataTreeSnapshot, + rootOper); + + modificationTree.write(MASTER_CONTAINER_PATH, ImmutableNodes.containerNode(MASTER_CONTAINER_QNAME)); + inMemoryDataTree.commit(inMemoryDataTree.prepare(modificationTree)); + + } + + public static final InputStream getDatastoreTestInputStream() { + return getInputStream(CONSTRAINTS_VALIDATION_TEST_YANG); + } + + private static InputStream getInputStream(final String resourceName) { + return TestModel.class.getResourceAsStream(CONSTRAINTS_VALIDATION_TEST_YANG); + } + + public static SchemaContext createTestContext() { + final YangParserImpl parser = new YangParserImpl(); + final Set modules = parser.parseYangModelsFromStreams(Collections.singletonList(getDatastoreTestInputStream())); + return parser.resolveSchemaContext(modules); + } + + @Test + public void minMaxListTestPass() throws DataValidationFailedException { + + final MapEntryNode fooEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME, "foo"); + final MapEntryNode barEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME, "bar"); + final MapNode mapNode1 = ImmutableNodes.mapNodeBuilder() + .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(MIN_MAX_LIST_QNAME)) + .withChild(fooEntryNode).build(); + final MapNode mapNode2 = ImmutableNodes.mapNodeBuilder() + .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(MIN_MAX_LIST_QNAME)) + .withChild(barEntryNode).build(); + + final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + modificationTree.merge(MIN_MAX_LIST_PATH, mapNode1); + modificationTree.merge(MIN_MAX_LIST_PATH, mapNode2); + // TODO: check why write and then merge on list commits only "bar" child + + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare); + + final InMemoryDataTreeSnapshot snapshotAfterCommit = inMemoryDataTree.takeSnapshot(); + final Optional> minMaxListRead = snapshotAfterCommit.readNode(MIN_MAX_LIST_PATH); + assertTrue(minMaxListRead.isPresent()); + assertTrue(((NormalizedNodeContainer) minMaxListRead.get()).getValue().size() == 2); + } + + @Test(expected=DataValidationFailedException.class) + public void minMaxListFail() throws DataValidationFailedException { + InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + + final MapEntryNode fooEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME, "foo"); + final MapEntryNode barEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME, "bar"); + final MapEntryNode gooEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME, "goo"); + final MapNode mapNode = ImmutableNodes.mapNodeBuilder() + .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(MIN_MAX_LIST_QNAME)) + .withChild(fooEntryNode).build(); + + final YangInstanceIdentifier fooPath = MIN_MAX_LIST_PATH.node(fooEntryNode.getIdentifier()); + final YangInstanceIdentifier barPath = MIN_MAX_LIST_PATH.node(barEntryNode.getIdentifier()); + final YangInstanceIdentifier gooPath = MIN_MAX_LIST_PATH.node(gooEntryNode.getIdentifier()); + + modificationTree.write(MIN_MAX_LIST_PATH, mapNode); + modificationTree.merge(barPath, barEntryNode); + modificationTree.write(gooPath, gooEntryNode); + modificationTree.delete(gooPath); + + inMemoryDataTree.validate(modificationTree); + DataTreeCandidate prepare1 = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare1); + + InMemoryDataTreeSnapshot snapshotAfterCommit = inMemoryDataTree.takeSnapshot(); + Optional> minMaxListRead = snapshotAfterCommit.readNode(MIN_MAX_LIST_PATH); + assertTrue(minMaxListRead.isPresent()); + assertTrue(((NormalizedNodeContainer) minMaxListRead.get()).getValue().size() == 2); + + modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + modificationTree.write(gooPath, gooEntryNode); + + inMemoryDataTree.validate(modificationTree); + prepare1 = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare1); + + snapshotAfterCommit = inMemoryDataTree.takeSnapshot(); + minMaxListRead = snapshotAfterCommit.readNode(MIN_MAX_LIST_PATH); + assertTrue(minMaxListRead.isPresent()); + assertTrue(((NormalizedNodeContainer) minMaxListRead.get()).getValue().size() == 3); + + modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + + modificationTree.delete(gooPath); + modificationTree.delete(fooPath); + + inMemoryDataTree.validate(modificationTree); + } + + @Test + public void minMaxLeafListPass() throws DataValidationFailedException { + final DataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + + final YangInstanceIdentifier.NodeWithValue barPath = new YangInstanceIdentifier.NodeWithValue(MIN_MAX_LIST_QNAME, "bar"); + final YangInstanceIdentifier.NodeWithValue gooPath = new YangInstanceIdentifier.NodeWithValue(MIN_MAX_LIST_QNAME, "goo"); + + final LeafSetEntryNode barLeafSetEntry = ImmutableLeafSetEntryNodeBuilder.create() + .withNodeIdentifier(barPath) + .withValue("bar").build(); + final LeafSetEntryNode gooLeafSetEntry = ImmutableLeafSetEntryNodeBuilder.create() + .withNodeIdentifier(gooPath) + .withValue("goo").build(); + + final LeafSetNode fooLeafSetNode = ImmutableLeafSetNodeBuilder.create() + .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(MIN_MAX_LIST_QNAME)) + .withChildValue("foo").build(); + + modificationTree.write(MIN_MAX_LEAF_LIST_PATH, fooLeafSetNode); + modificationTree.write(MIN_MAX_LEAF_LIST_PATH.node(barPath), barLeafSetEntry); + modificationTree.merge(MIN_MAX_LEAF_LIST_PATH.node(gooPath), gooLeafSetEntry); + modificationTree.delete(MIN_MAX_LEAF_LIST_PATH.node(gooPath)); + + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare1 = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare1); + + final InMemoryDataTreeSnapshot snapshotAfterCommit = inMemoryDataTree.takeSnapshot(); + final Optional> masterContainer = snapshotAfterCommit.readNode(MASTER_CONTAINER_PATH); + assertTrue(masterContainer.isPresent()); + final Optional leafList = ((NormalizedNodeContainer) masterContainer.get()).getChild( + new YangInstanceIdentifier.NodeIdentifier(MIN_MAX_LIST_QNAME)); + assertTrue(leafList.isPresent()); + assertTrue(leafList.get().getValue().size() == 2); + } + + @Test(expected=DataValidationFailedException.class) + public void minMaxLeafListFail() throws DataValidationFailedException { + final DataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + + + final YangInstanceIdentifier.NodeWithValue fooPath = new YangInstanceIdentifier.NodeWithValue(MIN_MAX_LIST_QNAME, "foo"); + final YangInstanceIdentifier.NodeWithValue barPath = new YangInstanceIdentifier.NodeWithValue(MIN_MAX_LIST_QNAME, "bar"); + final YangInstanceIdentifier.NodeWithValue gooPath = new YangInstanceIdentifier.NodeWithValue(MIN_MAX_LIST_QNAME, "goo"); + final YangInstanceIdentifier.NodeWithValue fuuPath = new YangInstanceIdentifier.NodeWithValue(MIN_MAX_LIST_QNAME, "fuu"); + + final LeafSetEntryNode barLeafSetEntry = ImmutableLeafSetEntryNodeBuilder.create() + .withNodeIdentifier(barPath) + .withValue("bar").build(); + final LeafSetEntryNode gooLeafSetEntry = ImmutableLeafSetEntryNodeBuilder.create() + .withNodeIdentifier(gooPath) + .withValue("goo").build(); + final LeafSetEntryNode fuuLeafSetEntry = ImmutableLeafSetEntryNodeBuilder.create() + .withNodeIdentifier(fuuPath) + .withValue("fuu").build(); + + final LeafSetNode fooLeafSetNode = ImmutableLeafSetNodeBuilder.create() + .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(MIN_MAX_LIST_QNAME)) + .withChildValue("foo").build(); + + modificationTree.write(MIN_MAX_LEAF_LIST_PATH, fooLeafSetNode); + modificationTree.write(MIN_MAX_LEAF_LIST_PATH.node(barPath), barLeafSetEntry); + modificationTree.merge(MIN_MAX_LEAF_LIST_PATH.node(gooPath), gooLeafSetEntry); + modificationTree.write(MIN_MAX_LEAF_LIST_PATH.node(fuuPath), fuuLeafSetEntry); + + inMemoryDataTree.validate(modificationTree); + } + + @Test + public void unkeyedListTestPass() throws DataValidationFailedException { + final DataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + + final UnkeyedListEntryNode foo = ImmutableUnkeyedListEntryNodeBuilder.create() + .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(UNKEYED_LEAF_QNAME)) + .withChild(ImmutableNodes.leafNode(UNKEYED_LEAF_QNAME, "foo")).build(); + final List unkeyedEntries = new ArrayList<>(); + unkeyedEntries.add(foo); + final UnkeyedListNode unkeyedListNode = ImmutableUnkeyedListNodeBuilder.create() + .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(UNKEYED_LIST_QNAME)) + .withValue(unkeyedEntries).build(); + + modificationTree.write(MASTER_CONTAINER_PATH, ImmutableNodes.containerNode(MASTER_CONTAINER_QNAME)); + modificationTree.merge(UNKEYED_LIST_PATH, unkeyedListNode); + + inMemoryDataTree.validate(modificationTree); + final DataTreeCandidate prepare1 = inMemoryDataTree.prepare(modificationTree); + inMemoryDataTree.commit(prepare1); + + final InMemoryDataTreeSnapshot snapshotAfterCommit = inMemoryDataTree.takeSnapshot(); + final Optional> unkeyedListRead = snapshotAfterCommit.readNode(UNKEYED_LIST_PATH); + assertTrue(unkeyedListRead.isPresent()); + assertTrue(((UnkeyedListNode) unkeyedListRead.get()).getSize() == 1); + } + + @Test(expected=DataValidationFailedException.class) + public void unkeyedListTestFail() throws DataValidationFailedException { + final DataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification(); + + final UnkeyedListEntryNode foo = ImmutableUnkeyedListEntryNodeBuilder.create() + .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(UNKEYED_LEAF_QNAME)) + .withChild(ImmutableNodes.leafNode(UNKEYED_LEAF_QNAME, "foo")).build(); + final UnkeyedListEntryNode bar = ImmutableUnkeyedListEntryNodeBuilder.create() + .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(UNKEYED_LEAF_QNAME)) + .withChild(ImmutableNodes.leafNode(UNKEYED_LEAF_QNAME, "bar")).build(); + final List unkeyedEntries = new ArrayList<>(); + unkeyedEntries.add(foo); + unkeyedEntries.add(bar); + final UnkeyedListNode unkeyedListNode = ImmutableUnkeyedListNodeBuilder.create() + .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(UNKEYED_LIST_QNAME)) + .withValue(unkeyedEntries).build(); + + modificationTree.write(UNKEYED_LIST_PATH, unkeyedListNode); + + inMemoryDataTree.validate(modificationTree); + } +} 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 new file mode 100644 index 0000000000..f7b2a40a70 --- /dev/null +++ b/yang/yang-data-impl/src/test/resources/list-constraints-validation-test-model.yang @@ -0,0 +1,47 @@ +module list-constraints-validation-test-model { + yang-version 1; + namespace "urn:opendaylight:params:xml:ns:yang:list-constraints-validation-test-model"; + prefix "list-constraints-validation"; + + revision "2015-02-02" { + description "Initial revision."; + } + + container master-container { + list min-max-list { + min-elements 2; + max-elements 3; + + key "min-max-key-leaf"; + + leaf min-max-key-leaf { + type string; + } + } + + list unbounded-list { + key "unbounded-key-leaf"; + + leaf unbounded-key-leaf { + type int8; + } + } + + leaf-list min-max-leaf-list { + min-elements 1; + max-elements 3; + type string; + } + + leaf-list unbounded-leaf-list { + type string; + } + + list unkeyed-list { + max-elements 1; + leaf unkeyed-leaf { + type string; + } + } + } +} -- 2.36.6