Bug 4454: Write to datastore: elements count error 99/28599/7
authorFilip Gregor <fgregor@cisco.com>
Tue, 10 Nov 2015 13:03:32 +0000 (14:03 +0100)
committerGerrit Code Review <gerrit@opendaylight.org>
Tue, 10 Nov 2015 17:04:52 +0000 (17:04 +0000)
added check for write
added aditional tests
added childrenBefore counter check

Change-Id: I970272520a2c97210cf10a7ff4aa92adc8dc6b27
Signed-off-by: Filip.Gregor <fgregor@cisco.com>
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/MinMaxElementsValidation.java
yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/Bug4454Test.java [new file with mode: 0644]
yang/yang-data-impl/src/test/resources/bug-4454-test.yang [new file with mode: 0644]

index 730f671485ccfd0427ff74d0bfd41bacc8a045f7..ab0afcf9f4d1a929e7890945ffae5af6fce27f6c 100644 (file)
@@ -71,7 +71,8 @@ final class MinMaxElementsValidation extends SchemaAwareApplyOperation {
         }
         final ModifiedNode modification = (ModifiedNode) nodeMod;
 
-        final int childrenBefore = findChildrenBefore(current);
+        final int childrenBefore = (modification.getOperation() == LogicalOperation.WRITE) ? 0 : findChildrenBefore
+                (current);
 
         final int childrenAfter = findChildrenAfter(modification);
 
@@ -105,7 +106,9 @@ final class MinMaxElementsValidation extends SchemaAwareApplyOperation {
         for (final ModifiedNode modChild : modification.getChildren()) {
             switch (modChild.getOperation()) {
                 case WRITE:
-                    result++;
+                    if (!modChild.getOriginal().isPresent()) {
+                        result++;
+                    }
                     break;
                 case MERGE:
                     if (!current.isPresent()) {
@@ -113,7 +116,9 @@ final class MinMaxElementsValidation extends SchemaAwareApplyOperation {
                     }
                     break;
                 case DELETE:
-                    result--;
+                    if (modChild.getOriginal().isPresent()) {
+                        result--;
+                    }
                     break;
                 case NONE:
                 case TOUCH:
diff --git a/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/Bug4454Test.java b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/Bug4454Test.java
new file mode 100644 (file)
index 0000000..ad0846c
--- /dev/null
@@ -0,0 +1,411 @@
+/*
+ * Copyright (c) 2015 Pantheon Technologies s.r.o. 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 junit.framework.TestCase.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.base.Optional;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.yangtools.util.UnmodifiableCollection;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+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.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.ImmutableMapEntryNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
+import org.opendaylight.yangtools.yang.parser.stmt.reactor.CrossSourceStatementReactor;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangInferencePipeline;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangStatementSourceImpl;
+
+public class Bug4454Test {
+    private static final YangStatementSourceImpl source = new YangStatementSourceImpl("/bug-4454-test.yang", false);
+
+    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_LEAF_LIST_QNAME = QName.create(MASTER_CONTAINER_QNAME, "min-max-leaf-list");
+    private static final QName MIN_MAX_LIST_QNAME_NO_MINMAX = QName
+            .create(MASTER_CONTAINER_QNAME, "min-max-list-no-minmax");
+    private static final QName MIN_MAX_KEY_LEAF_QNAME = QName.create(MASTER_CONTAINER_QNAME, "min-max-key-leaf");
+    private static final QName MIN_MAX_VALUE_LEAF_QNAME = QName.create(MASTER_CONTAINER_QNAME, "min-max-value-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 MIN_MAX_LIST_NO_MINMAX_PATH = YangInstanceIdentifier
+            .builder(MASTER_CONTAINER_PATH)
+            .node(MIN_MAX_LIST_QNAME_NO_MINMAX).build();
+    private static final YangInstanceIdentifier MIN_MAX_LEAF_LIST_PATH = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH)
+            .node(MIN_MAX_LEAF_LIST_QNAME).build();
+
+    private static Map<QName,Object> fooPredicates = new HashMap<>();
+    static {
+        fooPredicates.put(MIN_MAX_KEY_LEAF_QNAME,"foo");
+    }
+
+    private static Map<QName,Object> bazPredicates = new HashMap<>();
+    static {
+        bazPredicates.put(MIN_MAX_KEY_LEAF_QNAME,"baz");
+    }
+
+    private final MapEntryNode fooEntryNodeWithValue = ImmutableMapEntryNodeBuilder.create().withNodeIdentifier(new
+            YangInstanceIdentifier.NodeIdentifierWithPredicates(MIN_MAX_LIST_QNAME, fooPredicates)).
+            withChild(ImmutableNodes.leafNode(MIN_MAX_VALUE_LEAF_QNAME, "footest")).build();
+    private final MapEntryNode BazEntryNodeWithValue = ImmutableMapEntryNodeBuilder.create().withNodeIdentifier(new
+            YangInstanceIdentifier.NodeIdentifierWithPredicates(MIN_MAX_LIST_QNAME, bazPredicates)).
+            withChild(ImmutableNodes.leafNode(MIN_MAX_VALUE_LEAF_QNAME, "baztest")).build();
+    private final MapEntryNode fooEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME,
+            "foo");
+    private final MapEntryNode barEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME,
+            "bar");
+    private final MapEntryNode bazEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME, MIN_MAX_KEY_LEAF_QNAME,
+            "baz");
+    private final MapNode mapNodeBazFuzWithNodes = ImmutableNodes.mapNodeBuilder()
+            .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(MIN_MAX_LIST_QNAME))
+            .withChild(bazEntryNode).withChild(BazEntryNodeWithValue).withChild(fooEntryNode)
+            .build();
+    private final MapNode mapNodeFooWithNodes = ImmutableNodes.mapNodeBuilder()
+            .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(MIN_MAX_LIST_QNAME))
+            .withChild(fooEntryNode).withChild(fooEntryNodeWithValue).withChild(barEntryNode).withChild(bazEntryNode)
+            .build();
+    private final MapNode mapNodeBar = ImmutableNodes.mapNodeBuilder()
+            .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(MIN_MAX_LIST_QNAME))
+            .withChild(barEntryNode).build();
+    private final MapNode mapNodeBaz = ImmutableNodes.mapNodeBuilder()
+            .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(MIN_MAX_LIST_QNAME))
+            .withChild(bazEntryNode).build();
+
+    private InMemoryDataTree inMemoryDataTree;
+
+    @Before
+    public void prepare() throws IOException, YangSyntaxErrorException, ReactorException {
+        SchemaContext schemaContext = createTestContext();
+        assertNotNull("Schema context must not be null.", schemaContext);
+        inMemoryDataTree =  (InMemoryDataTree) InMemoryDataTreeFactory.getInstance().create();
+        inMemoryDataTree.setSchemaContext(schemaContext);
+        final InMemoryDataTreeSnapshot initialDataTreeSnapshot = inMemoryDataTree.takeSnapshot();
+        final DataTreeModification modificationTree = initialDataTreeSnapshot.newModification();
+
+        modificationTree.write(MASTER_CONTAINER_PATH, ImmutableNodes.containerNode(MASTER_CONTAINER_QNAME));
+        modificationTree.ready();
+        inMemoryDataTree.commit(inMemoryDataTree.prepare(modificationTree));
+    }
+
+    public static SchemaContext createTestContext() throws IOException, YangSyntaxErrorException, ReactorException {
+        CrossSourceStatementReactor.BuildAction reactor = YangInferencePipeline.RFC6020_REACTOR.newBuild();
+        reactor.addSources(source);
+
+        return reactor.buildEffective();
+    }
+
+    @Test
+    public void minMaxListDeleteWriteTest() throws DataValidationFailedException {
+        final InMemoryDataTreeModification modificationTree1 = inMemoryDataTree.takeSnapshot().newModification();
+
+        Map<QName, Object> key = new HashMap<>();
+        key.put(MIN_MAX_KEY_LEAF_QNAME, "foo");
+
+        YangInstanceIdentifier.NodeIdentifierWithPredicates mapEntryPath2 = new YangInstanceIdentifier
+                .NodeIdentifierWithPredicates(MIN_MAX_LIST_QNAME , key);
+
+        YangInstanceIdentifier MIN_MAX_LEAF_FOO = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH)
+                .node(MIN_MAX_LIST_QNAME).node(mapEntryPath2).build();
+
+        key.clear();
+        key.put(MIN_MAX_KEY_LEAF_QNAME, "NON-EXISTING-LEAF");
+
+        mapEntryPath2 = new YangInstanceIdentifier.NodeIdentifierWithPredicates(MIN_MAX_LIST_QNAME, key);
+
+        YangInstanceIdentifier MIN_MAX_LEAF_NEL = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH).node
+                (MIN_MAX_LIST_QNAME).node(mapEntryPath2).build();
+
+        final Map<QName, Object> keyTemp = new HashMap<>();
+        keyTemp.put(MIN_MAX_KEY_LEAF_QNAME, "baz");
+
+        YangInstanceIdentifier.NodeIdentifierWithPredicates mapEntryPathTest = new YangInstanceIdentifier
+                .NodeIdentifierWithPredicates(MIN_MAX_LIST_QNAME , keyTemp);
+
+        final YangInstanceIdentifier pathToBaz = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH)
+                .node(MIN_MAX_LIST_QNAME).node(mapEntryPathTest).node(MIN_MAX_VALUE_LEAF_QNAME).build();
+
+        keyTemp.clear();
+        keyTemp.put(MIN_MAX_KEY_LEAF_QNAME, "bar");
+
+        mapEntryPathTest = new YangInstanceIdentifier.NodeIdentifierWithPredicates(MIN_MAX_LIST_QNAME , keyTemp);
+
+        final YangInstanceIdentifier pathToBar = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH)
+                .node(MIN_MAX_LIST_QNAME).node(mapEntryPathTest).node(MIN_MAX_VALUE_LEAF_QNAME).build();
+
+        keyTemp.clear();
+        keyTemp.put(MIN_MAX_KEY_LEAF_QNAME, "foo");
+
+        final YangInstanceIdentifier.NodeIdentifierWithPredicates mapEntryPathTestKey = new YangInstanceIdentifier
+                .NodeIdentifierWithPredicates(MIN_MAX_LIST_QNAME , keyTemp);
+
+        final YangInstanceIdentifier pathToKeyFoo = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH)
+                .node(MIN_MAX_LIST_QNAME).node(mapEntryPathTestKey).node(MIN_MAX_KEY_LEAF_QNAME).build();
+
+        final LeafNode<String> newNode = ImmutableNodes.leafNode(MIN_MAX_VALUE_LEAF_QNAME, "test");
+        final LeafNode<String> newNode1 = ImmutableNodes.leafNode(MIN_MAX_VALUE_LEAF_QNAME, "test1");
+        final LeafNode<String> newNode2 = ImmutableNodes.leafNode(MIN_MAX_VALUE_LEAF_QNAME, "test2");
+        final LeafNode<String> newNodekey = ImmutableNodes.leafNode(MIN_MAX_KEY_LEAF_QNAME, "foo");
+
+        assertFalse(inMemoryDataTree.toString().contains("list"));
+
+        InMemoryDataTreeSnapshot snapshotAfterCommit = inMemoryDataTree.takeSnapshot();
+        Optional<NormalizedNode<?, ?>> minMaxListRead = snapshotAfterCommit.readNode(MIN_MAX_LIST_PATH);
+        assertTrue(!minMaxListRead.isPresent());
+
+        modificationTree1.write(MIN_MAX_LIST_PATH, mapNodeFooWithNodes);
+        modificationTree1.write(MIN_MAX_LIST_PATH, mapNodeFooWithNodes);
+        modificationTree1.write(MIN_MAX_LIST_PATH, mapNodeFooWithNodes);
+        modificationTree1.merge(MIN_MAX_LIST_PATH, mapNodeBar);
+        modificationTree1.merge(MIN_MAX_LIST_PATH, mapNodeBaz);
+        modificationTree1.write(pathToKeyFoo, newNodekey);
+        modificationTree1.write(pathToBaz, newNode2);
+        modificationTree1.write(pathToBaz, newNode1);
+        modificationTree1.write(pathToBaz, newNode);
+        modificationTree1.delete(MIN_MAX_LEAF_FOO);
+        modificationTree1.delete(MIN_MAX_LEAF_NEL);
+
+        modificationTree1.ready();
+        inMemoryDataTree.validate(modificationTree1);
+        final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree1);
+        inMemoryDataTree.commit(prepare);
+
+        InMemoryDataTreeSnapshot test = inMemoryDataTree.takeSnapshot();
+        testLoop(test, "bar", "test");
+
+        InMemoryDataTreeModification tempMod = test.newModification();
+        tempMod.write(pathToBaz, newNode2);
+        tempMod.write(pathToBaz, newNode1);
+        tempMod.merge(pathToBaz, newNode2);
+        tempMod.write(pathToBaz, newNode1);
+
+        tempMod.ready();
+        inMemoryDataTree.validate(tempMod);
+        final DataTreeCandidate prepare1 = inMemoryDataTree.prepare(tempMod);
+        inMemoryDataTree.commit(prepare1);
+
+        InMemoryDataTreeSnapshot test1 = inMemoryDataTree.takeSnapshot();
+        testLoop(test1, "bar", "test1");
+
+        InMemoryDataTreeModification tempMod1 = test1.newModification();
+        tempMod1.write(MIN_MAX_LIST_PATH, mapNodeFooWithNodes);
+
+        tempMod1.ready();
+        inMemoryDataTree.validate(tempMod1);
+        final DataTreeCandidate prepare2 = inMemoryDataTree.prepare(tempMod1);
+        inMemoryDataTree.commit(prepare2);
+
+        InMemoryDataTreeSnapshot test2 = inMemoryDataTree.takeSnapshot();
+        minMaxListRead = test2.readNode(MIN_MAX_LIST_PATH);
+        assertTrue(minMaxListRead.isPresent());
+        assertTrue(((NormalizedNodeContainer<?, ?, ?>) minMaxListRead.get()).getValue().size() == 3);
+
+        InMemoryDataTreeModification tempMod2 = test2.newModification();
+        tempMod2.write(MIN_MAX_LIST_PATH, mapNodeBaz);
+        tempMod2.write(pathToBaz, newNode2);
+
+        tempMod2.ready();
+        inMemoryDataTree.validate(tempMod2);
+        final DataTreeCandidate prepare3 = inMemoryDataTree.prepare(tempMod2);
+        inMemoryDataTree.commit(prepare3);
+
+        InMemoryDataTreeSnapshot test3 = inMemoryDataTree.takeSnapshot();
+        minMaxListRead = test3.readNode(MIN_MAX_LIST_PATH);
+        assertTrue(minMaxListRead.isPresent());
+        assertTrue(((NormalizedNodeContainer<?, ?, ?>) minMaxListRead.get()).getValue().size() == 1);
+        assertTrue(minMaxListRead.get().getValue().toString().contains("test2"));
+
+        InMemoryDataTreeModification tempMod3 = test3.newModification();
+        tempMod3.merge(MIN_MAX_LIST_PATH, mapNodeBar);
+        tempMod3.merge(pathToBar, newNode1);
+
+        tempMod3.ready();
+        inMemoryDataTree.validate(tempMod3);
+        final DataTreeCandidate prepare4 = inMemoryDataTree.prepare(tempMod3);
+        inMemoryDataTree.commit(prepare4);
+
+        InMemoryDataTreeSnapshot test4 = inMemoryDataTree.takeSnapshot();
+        testLoop(test4, "test1", "test2");
+    }
+
+    @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<Object> barLeafSetEntry = ImmutableLeafSetEntryNodeBuilder.create()
+                .withNodeIdentifier(barPath)
+                .withValue("bar").build();
+        final LeafSetEntryNode<Object> gooLeafSetEntry = ImmutableLeafSetEntryNodeBuilder.create()
+                .withNodeIdentifier(gooPath)
+                .withValue("goo").build();
+
+        final LeafSetNode<Object> fooLeafSetNode = ImmutableLeafSetNodeBuilder.create()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(MIN_MAX_LEAF_LIST_QNAME))
+                .withChildValue("foo").build();
+
+        modificationTree.write(MIN_MAX_LEAF_LIST_PATH, fooLeafSetNode);
+        modificationTree.write(MIN_MAX_LEAF_LIST_PATH.node(barPath), barLeafSetEntry);
+        modificationTree.ready();
+
+        inMemoryDataTree.validate(modificationTree);
+        final DataTreeCandidate prepare1 = inMemoryDataTree.prepare(modificationTree);
+        inMemoryDataTree.commit(prepare1);
+
+        InMemoryDataTreeSnapshot test1 = inMemoryDataTree.takeSnapshot();
+
+        InMemoryDataTreeModification tempMod1 = test1.newModification();
+        tempMod1.write(MIN_MAX_LEAF_LIST_PATH.node(gooPath), gooLeafSetEntry);
+        tempMod1.write(MIN_MAX_LEAF_LIST_PATH.node(barPath), barLeafSetEntry);
+        tempMod1.ready();
+
+        inMemoryDataTree.validate(tempMod1);
+        final DataTreeCandidate prepare2 = inMemoryDataTree.prepare(tempMod1);
+        inMemoryDataTree.commit(prepare2);
+
+        final InMemoryDataTreeSnapshot snapshotAfterCommit = inMemoryDataTree.takeSnapshot();
+        final Optional<NormalizedNode<?, ?>> masterContainer = snapshotAfterCommit.readNode(MASTER_CONTAINER_PATH);
+        assertTrue(masterContainer.isPresent());
+        final Optional<NormalizedNodeContainer<?, ?, ?>> leafList = ((NormalizedNodeContainer) masterContainer.get()).getChild(
+                new YangInstanceIdentifier.NodeIdentifier(MIN_MAX_LEAF_LIST_QNAME));
+        assertTrue(leafList.isPresent());
+        assertTrue(leafList.get().getValue().size() == 3);
+    }
+
+
+    @Test(expected = DataValidationFailedException.class)
+    public void minMaxListDeleteExceptionTest() throws DataValidationFailedException {
+        final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification();
+
+        Map<QName, Object> key = new HashMap<>();
+        key.put(MIN_MAX_KEY_LEAF_QNAME, "foo");
+
+        YangInstanceIdentifier.NodeIdentifierWithPredicates mapEntryPath2 = new YangInstanceIdentifier
+                .NodeIdentifierWithPredicates(MIN_MAX_LIST_QNAME , key);
+
+        YangInstanceIdentifier MIN_MAX_LEAF_FOO = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH)
+                .node(MIN_MAX_LIST_QNAME).node(mapEntryPath2).build();
+
+        key.clear();
+        key.put(MIN_MAX_KEY_LEAF_QNAME, "bar");
+
+        mapEntryPath2 = new YangInstanceIdentifier.NodeIdentifierWithPredicates(MIN_MAX_LIST_QNAME, key);
+
+        YangInstanceIdentifier MIN_MAX_LEAF_BAR = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH).node
+                (MIN_MAX_LIST_QNAME)
+                .node(mapEntryPath2).build();
+
+        key.clear();
+        key.put(MIN_MAX_KEY_LEAF_QNAME, "bar");
+
+        mapEntryPath2 = new YangInstanceIdentifier.NodeIdentifierWithPredicates(MIN_MAX_LIST_QNAME, key);
+
+        YangInstanceIdentifier MIN_MAX_LEAF_BAZ = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH).node
+                (MIN_MAX_LIST_QNAME)
+                .node(mapEntryPath2).build();
+
+        modificationTree.write(MIN_MAX_LIST_PATH, mapNodeFooWithNodes);
+        modificationTree.merge(MIN_MAX_LIST_PATH, mapNodeBar);
+        modificationTree.merge(MIN_MAX_LIST_PATH, mapNodeBaz);
+        modificationTree.delete(MIN_MAX_LEAF_FOO);
+        modificationTree.delete(MIN_MAX_LEAF_BAR);
+        modificationTree.delete(MIN_MAX_LEAF_BAZ);
+
+        modificationTree.ready();
+
+        inMemoryDataTree.validate(modificationTree);
+        final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree);
+        inMemoryDataTree.commit(prepare);
+    }
+
+    @Test
+    public void minMaxListNoMinMaxDeleteTest() throws DataValidationFailedException {
+        final MapEntryNode fooEntryNode = ImmutableNodes.mapEntry(MIN_MAX_LIST_QNAME_NO_MINMAX, MIN_MAX_KEY_LEAF_QNAME
+                , "foo");
+        final MapNode mapNode1 = ImmutableNodes.mapNodeBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(MIN_MAX_LIST_QNAME_NO_MINMAX))
+                .withChild(fooEntryNode).build();
+
+        final InMemoryDataTreeModification modificationTree = inMemoryDataTree.takeSnapshot().newModification();
+
+        Map<QName, Object> key = new HashMap<>();
+        key.put(MIN_MAX_KEY_LEAF_QNAME, "foo");
+
+        YangInstanceIdentifier.NodeIdentifierWithPredicates mapEntryPath2 = new YangInstanceIdentifier
+                .NodeIdentifierWithPredicates(MIN_MAX_LIST_QNAME_NO_MINMAX , key);
+
+        YangInstanceIdentifier MIN_MAX_LEAF_FOO = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH).node
+                (MIN_MAX_LIST_QNAME_NO_MINMAX).node(mapEntryPath2).build();
+
+        key.clear();
+        key.put(MIN_MAX_KEY_LEAF_QNAME, "non-existing-leaf");
+
+        mapEntryPath2 = new YangInstanceIdentifier.NodeIdentifierWithPredicates(MIN_MAX_LIST_QNAME_NO_MINMAX, key);
+
+        YangInstanceIdentifier MIN_MAX_LEAF_NEL = YangInstanceIdentifier.builder(MASTER_CONTAINER_PATH).node
+                (MIN_MAX_LIST_QNAME_NO_MINMAX)
+                .node(mapEntryPath2).build();
+
+        modificationTree.write(MIN_MAX_LIST_NO_MINMAX_PATH, mapNode1);
+        modificationTree.delete(MIN_MAX_LEAF_FOO);
+        modificationTree.delete(MIN_MAX_LEAF_NEL);
+
+        modificationTree.ready();
+
+        inMemoryDataTree.validate(modificationTree);
+        final DataTreeCandidate prepare = inMemoryDataTree.prepare(modificationTree);
+        inMemoryDataTree.commit(prepare);
+
+        final InMemoryDataTreeSnapshot snapshotAfterCommit = inMemoryDataTree.takeSnapshot();
+        final Optional<NormalizedNode<?, ?>> minMaxListRead = snapshotAfterCommit.readNode(MIN_MAX_LIST_NO_MINMAX_PATH);
+        assertTrue(minMaxListRead.isPresent());
+        assertTrue(((NormalizedNodeContainer<?, ?, ?>) minMaxListRead.get()).getValue().size() == 0);
+    }
+
+    private void testLoop(InMemoryDataTreeSnapshot snapshot, String first, String second) {
+        Optional<NormalizedNode<?, ?>> minMaxListRead = snapshot.readNode(MIN_MAX_LIST_PATH);
+        assertTrue(minMaxListRead.isPresent());
+        assertTrue(((NormalizedNodeContainer<?, ?, ?>) minMaxListRead.get()).getValue().size() == 2);
+        UnmodifiableCollection collectionChildren = (UnmodifiableCollection) minMaxListRead.get().getValue();
+
+        for (Object collectionChild : collectionChildren) {
+            if(collectionChild.toString().contains(first)){
+                assertTrue(collectionChild.toString().contains(first));
+            } else {
+                assertTrue(collectionChild.toString().contains(second));
+            }
+        }
+    }
+}
diff --git a/yang/yang-data-impl/src/test/resources/bug-4454-test.yang b/yang/yang-data-impl/src/test/resources/bug-4454-test.yang
new file mode 100644 (file)
index 0000000..04edcf7
--- /dev/null
@@ -0,0 +1,39 @@
+module Bug4454Test {
+    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 1;
+            max-elements 3;
+
+            key "min-max-key-leaf";
+
+            leaf min-max-key-leaf {
+                type string;
+            }
+            leaf min-max-value-leaf {
+                type string;
+            }
+        }
+
+        list min-max-list-no-minmax {
+            key "min-max-key-leaf";
+
+            leaf min-max-key-leaf {
+                type string;
+            }
+        }
+
+        leaf-list min-max-leaf-list {
+            min-elements 0;
+            max-elements 10;
+            type string;
+        }
+    }
+}