Bug 5109: Handle stand alone leaf nodes in CDS streaming 28/33228/2
authorTom Pantelis <tpanteli@brocade.com>
Thu, 21 Jan 2016 15:59:50 +0000 (10:59 -0500)
committerTony Tkacik <ttkacik@cisco.com>
Wed, 27 Jan 2016 12:18:33 +0000 (12:18 +0000)
Modified AbstractNormalizedNodeDataOutput to output the leaf set QName
that is now passed to leafSetEntryNode if no parent LeafSetNode QName is
present. Modified NormalizedNodeInputStreamReader accordingly.

I also found that OrderedLeafSetNode was not handled correctly.
AbstractNormalizedNodeDataOutput#startOrderedLeafSet needs to set
lastLeafSetQName.

The NormalizedNodePruner assumed a leaf set entry node must have a
parent and threw an exception if not, similarly with leaf node and anyXML
node. But all 3 can be standalone so I modified NormalizedNodePruner to
handle it.

Change-Id: I02a71d9280dac0eb466ff401699a40d3d8826220
Signed-off-by: Tom Pantelis <tpanteli@brocade.com>
opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/AbstractNormalizedNodeDataOutput.java
opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeInputStreamReader.java
opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/transformer/NormalizedNodePruner.java
opendaylight/md-sal/sal-clustering-commons/src/test/java/org/opendaylight/controller/cluster/datastore/node/utils/transformer/NormalizedNodePrunerTest.java
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataTreeCandidatePayloadTest.java

index 4a76119..abe1f8c 100644 (file)
@@ -30,6 +30,7 @@ abstract class AbstractNormalizedNodeDataOutput implements NormalizedNodeDataOut
 
     private NormalizedNodeWriter normalizedNodeWriter;
     private boolean headerWritten;
+    private QName lastLeafSetQName;
 
     AbstractNormalizedNodeDataOutput(final DataOutput output) {
         this.output = Preconditions.checkNotNull(output);
@@ -159,6 +160,7 @@ abstract class AbstractNormalizedNodeDataOutput implements NormalizedNodeDataOut
         Preconditions.checkNotNull(name, "Node identifier should not be null");
         LOG.debug("Starting a new leaf set");
 
+        lastLeafSetQName = name.getNodeType();
         startNode(name.getNodeType(), NodeTypes.LEAF_SET);
     }
 
@@ -167,14 +169,22 @@ abstract class AbstractNormalizedNodeDataOutput implements NormalizedNodeDataOut
         Preconditions.checkNotNull(name, "Node identifier should not be null");
         LOG.debug("Starting a new ordered leaf set");
 
+        lastLeafSetQName = name.getNodeType();
         startNode(name.getNodeType(), NodeTypes.ORDERED_LEAF_SET);
     }
 
     @Override
-    public void leafSetEntryNode(final Object value) throws IOException, IllegalArgumentException {
+    public void leafSetEntryNode(final QName name, final Object value) throws IOException, IllegalArgumentException {
         LOG.debug("Writing a new leaf set entry node");
 
         output.writeByte(NodeTypes.LEAF_SET_ENTRY_NODE);
+
+        // lastLeafSetQName is set if the parent LeafSetNode was previously written. Otherwise this is a
+        // stand alone LeafSetEntryNode so write out it's name here.
+        if(lastLeafSetQName == null) {
+            writeQName(name);
+        }
+
         writeObject(value);
     }
 
index 7753242..7ed50ff 100644 (file)
@@ -134,8 +134,13 @@ public class NormalizedNodeInputStreamReader implements NormalizedNodeDataInput,
                         withNodeIdentifier(augIdentifier)).build();
 
             case NodeTypes.LEAF_SET_ENTRY_NODE :
+                QName name = lastLeafSetQName;
+                if(name == null) {
+                    name = readQName();
+                }
+
                 Object value = readObject();
-                NodeWithValue leafIdentifier = new NodeWithValue(lastLeafSetQName, value);
+                NodeWithValue<?> leafIdentifier = new NodeWithValue<>(name, value);
 
                 LOG.debug("Reading leaf set entry node {}, value {}", leafIdentifier, value);
 
@@ -222,7 +227,7 @@ public class NormalizedNodeInputStreamReader implements NormalizedNodeDataInput,
                         Builders.leafSetBuilder().withNodeIdentifier(identifier)).build();
 
             case NodeTypes.ORDERED_LEAF_SET:
-                LOG.debug("Read leaf set node");
+                LOG.debug("Read ordered leaf set node {}", identifier);
                 ListNodeBuilder<Object, LeafSetEntryNode<Object>> orderedLeafSetBuilder =
                         Builders.orderedLeafSetBuilder().withNodeIdentifier(identifier);
                 orderedLeafSetBuilder = addLeafSetChildren(identifier.getNodeType(), orderedLeafSetBuilder);
index 88c4763..27504f3 100644 (file)
@@ -10,18 +10,17 @@ package org.opendaylight.controller.cluster.datastore.node.utils.transformer;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
-
 import java.io.IOException;
 import java.net.URI;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
-
 import javax.xml.transform.dom.DOMSource;
-
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
@@ -49,6 +48,7 @@ public class NormalizedNodePruner implements NormalizedNodeStreamWriter {
         this.validNamespaces = validNamespaces;
     }
 
+    @SuppressWarnings("unchecked")
     @Override
     public void leafNode(YangInstanceIdentifier.NodeIdentifier nodeIdentifier, Object o) throws IOException, IllegalArgumentException {
 
@@ -57,13 +57,16 @@ public class NormalizedNodePruner implements NormalizedNodeStreamWriter {
         if(!isValidNamespace(nodeIdentifier)){
             return;
         }
+
         NormalizedNodeBuilderWrapper parent = stack.peek();
-        Preconditions.checkState(parent != null, "leafNode has no parent");
-        parent.builder()
-                .addChild(Builders.leafBuilder()
-                        .withNodeIdentifier(nodeIdentifier)
-                        .withValue(o)
-                        .build());
+        LeafNode<Object> leafNode = Builders.leafBuilder().withNodeIdentifier(nodeIdentifier).withValue(o).build();
+        if(parent != null) {
+            parent.builder().addChild(leafNode);
+        } else {
+            // If there's no parent node then this is a stand alone LeafNode.
+            this.normalizedNode = leafNode;
+            sealed = true;
+        }
     }
 
     @Override
@@ -82,22 +85,25 @@ public class NormalizedNodePruner implements NormalizedNodeStreamWriter {
         addBuilder(Builders.orderedLeafSetBuilder().withNodeIdentifier(nodeIdentifier), nodeIdentifier);
     }
 
+    @SuppressWarnings({ "unchecked" })
     @Override
-    public void leafSetEntryNode(Object o) throws IOException, IllegalArgumentException {
-
+    public void leafSetEntryNode(QName name, Object o) throws IOException, IllegalArgumentException {
         checkNotSealed();
 
-        NormalizedNodeBuilderWrapper parent = stack.peek();
-        Preconditions.checkState(parent != null, "leafSetEntryNode has no parent");
-        if(!isValidNamespace(parent.identifier())){
+        if(!isValidNamespace(name)){
             return;
         }
 
-        parent.builder()
-                .addChild(Builders.leafSetEntryBuilder()
-                        .withValue(o)
-                        .withNodeIdentifier(new YangInstanceIdentifier.NodeWithValue(parent.nodeType(), o))
-                        .build());
+        NormalizedNodeBuilderWrapper parent = stack.peek();
+        if(parent != null) {
+            parent.builder().addChild(Builders.leafSetEntryBuilder().withValue(o).withNodeIdentifier(
+                    new YangInstanceIdentifier.NodeWithValue<>(parent.nodeType(), o)).build());
+        } else {
+            // If there's no parent LeafSetNode then this is a stand alone LeafSetEntryNode.
+            this.normalizedNode = Builders.leafSetEntryBuilder().withValue(o).withNodeIdentifier(
+                    new YangInstanceIdentifier.NodeWithValue<>(name, o)).build();
+            sealed = true;
+        }
     }
 
     @Override
@@ -169,17 +175,25 @@ public class NormalizedNodePruner implements NormalizedNodeStreamWriter {
         addBuilder(Builders.augmentationBuilder().withNodeIdentifier(augmentationIdentifier), augmentationIdentifier);
     }
 
+    @SuppressWarnings("unchecked")
     @Override
     public void anyxmlNode(YangInstanceIdentifier.NodeIdentifier nodeIdentifier, Object o) throws IOException, IllegalArgumentException {
-
         checkNotSealed();
 
         if(!isValidNamespace(nodeIdentifier)){
             return;
         }
+
         NormalizedNodeBuilderWrapper parent = stack.peek();
-        Preconditions.checkState(parent != null, "anyxmlNode has no parent");
-        parent.builder().addChild(Builders.anyXmlBuilder().withNodeIdentifier(nodeIdentifier).withValue((DOMSource) o).build());
+        AnyXmlNode anyXmlNode = Builders.anyXmlBuilder().withNodeIdentifier(nodeIdentifier).
+                withValue((DOMSource) o).build();
+        if(parent != null) {
+            parent.builder().addChild(anyXmlNode);
+        } else {
+            // If there's no parent node then this is a stand alone AnyXmlNode.
+            this.normalizedNode = anyXmlNode;
+            sealed = true;
+        }
     }
 
     @Override
index 596540d..2ee8594 100644 (file)
@@ -25,7 +25,6 @@ import javax.xml.transform.dom.DOMSource;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.opendaylight.controller.cluster.datastore.node.utils.NormalizedNodeNavigator;
@@ -33,6 +32,8 @@ import org.opendaylight.controller.cluster.datastore.node.utils.NormalizedNodeVi
 import org.opendaylight.controller.cluster.datastore.util.TestModel;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
+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.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
@@ -169,26 +170,26 @@ public class NormalizedNodePrunerTest {
         return count.get();
     }
 
-    @Test(expected = IllegalStateException.class)
+    @Test
     public void testLeafNodeHasNoParent() throws IOException {
-        prunerFullSchema.leafNode(new YangInstanceIdentifier.NodeIdentifier(TestModel.BOOLEAN_LEAF_QNAME), mock(Object.class));
+        NormalizedNode<?, ?> input = Builders.leafBuilder().withNodeIdentifier(
+                new YangInstanceIdentifier.NodeIdentifier(TestModel.DESC_QNAME)).withValue("test").build();
+        NormalizedNodeWriter.forStreamWriter(prunerFullSchema).write(input);
+
+        NormalizedNode<?, ?> actual = prunerFullSchema.normalizedNode();
+        assertEquals("normalizedNode", input, actual);
     }
 
     @Test
     public void testLeafNodeHasParent() throws IOException {
-        prunerFullSchema.stack().push(normalizedNodeBuilderWrapper);
-        Object o = mock(Object.class);
-        prunerFullSchema.leafNode(new YangInstanceIdentifier.NodeIdentifier(TestModel.BOOLEAN_LEAF_QNAME), o);
-
-        ArgumentCaptor<NormalizedNode> captor = ArgumentCaptor.forClass(NormalizedNode.class);
-
-        verify(normalizedNodeContainerBuilder).addChild(captor.capture());
-
-        NormalizedNode<?, ?> value = captor.getValue();
-        assertEquals(normalizedNodeBuilderWrapper.identifier().getNodeType(), value.getNodeType());
-        assertEquals(normalizedNodeBuilderWrapper.identifier(), value.getIdentifier());
-        assertEquals(o, value.getValue());
-
+        LeafNode<Object> child = Builders.leafBuilder().withNodeIdentifier(
+                new YangInstanceIdentifier.NodeIdentifier(TestModel.DESC_QNAME)).withValue("test").build();
+        NormalizedNode<?, ?> input = Builders.containerBuilder().withNodeIdentifier(
+                new YangInstanceIdentifier.NodeIdentifier(TestModel.AUG_CONT_QNAME)).withChild(child).build();
+        NormalizedNodeWriter.forStreamWriter(prunerFullSchema).write(input);
+
+        NormalizedNode<?, ?> actual = prunerFullSchema.normalizedNode();
+        assertEquals("normalizedNode", input, actual);
     }
 
     @Test
@@ -198,28 +199,26 @@ public class NormalizedNodePrunerTest {
         verify(normalizedNodeContainerBuilder, never()).addChild(any(NormalizedNode.class));
     }
 
-    @Test(expected = IllegalStateException.class)
+    @Test
     public void testLeafSetEntryNodeHasNoParent() throws IOException {
-        prunerFullSchema.leafSetEntryNode(mock(Object.class));
+        NormalizedNode<?, ?> input = Builders.leafSetEntryBuilder().withValue("test").withNodeIdentifier(
+                new YangInstanceIdentifier.NodeWithValue<>(TestModel.FAMILY_QNAME, "test")).build();
+        NormalizedNodeWriter.forStreamWriter(prunerFullSchema).write(input);
+
+        NormalizedNode<?, ?> actual = prunerFullSchema.normalizedNode();
+        assertEquals("normalizedNode", input, actual);
     }
 
     @Test
     public void testLeafSetEntryNodeHasParent() throws IOException {
-        prunerFullSchema.stack().push(normalizedNodeBuilderWrapper);
-        Object o = mock(Object.class);
-        YangInstanceIdentifier.PathArgument nodeIdentifier
-                = new YangInstanceIdentifier.NodeWithValue(normalizedNodeBuilderWrapper.identifier().getNodeType(), o);
-        prunerFullSchema.leafSetEntryNode(o);
-
-        ArgumentCaptor<NormalizedNode> captor = ArgumentCaptor.forClass(NormalizedNode.class);
-
-        verify(normalizedNodeContainerBuilder).addChild(captor.capture());
-
-        NormalizedNode<?, ?> value = captor.getValue();
-        assertEquals(nodeIdentifier.getNodeType(), value.getNodeType());
-        assertEquals(nodeIdentifier, value.getIdentifier());
-        assertEquals(o, value.getValue());
-
+        LeafSetEntryNode<Object> child = Builders.leafSetEntryBuilder().withValue("test").withNodeIdentifier(
+                new YangInstanceIdentifier.NodeWithValue<>(TestModel.FAMILY_QNAME, "test")).build();
+        NormalizedNode<?, ?> input = Builders.leafSetBuilder().withNodeIdentifier(
+                new YangInstanceIdentifier.NodeIdentifier(TestModel.FAMILY_QNAME)).withChild(child).build();
+        NormalizedNodeWriter.forStreamWriter(prunerFullSchema).write(input);
+
+        NormalizedNode<?, ?> actual = prunerFullSchema.normalizedNode();
+        assertEquals("normalizedNode", input, actual);
     }
 
     @Test
@@ -227,31 +226,33 @@ public class NormalizedNodePrunerTest {
         doReturn(new YangInstanceIdentifier.NodeIdentifier(TestModel.AUG_CONT_QNAME)).when(normalizedNodeBuilderWrapper).identifier();
 
         prunerNoAugSchema.stack().push(normalizedNodeBuilderWrapper);
-        prunerNoAugSchema.leafSetEntryNode(new YangInstanceIdentifier.NodeIdentifier(TestModel.AUG_CONT_QNAME));
+        prunerNoAugSchema.leafSetEntryNode(TestModel.AUG_CONT_QNAME, "");
 
         verify(normalizedNodeContainerBuilder, never()).addChild(any(NormalizedNode.class));
     }
 
-    @Test(expected = IllegalStateException.class)
+    @Test
     public void testAnyXMLNodeHasNoParent() throws IOException {
-        prunerFullSchema.anyxmlNode(new YangInstanceIdentifier.NodeIdentifier(TestModel.BOOLEAN_LEAF_QNAME), mock(Object.class));
+        NormalizedNode<?, ?> input = Builders.anyXmlBuilder().withNodeIdentifier(
+                new YangInstanceIdentifier.NodeIdentifier(TestModel.CHILD_NAME_QNAME)).
+                    withValue(mock(DOMSource.class)).build();
+        NormalizedNodeWriter.forStreamWriter(prunerFullSchema).write(input);
+
+        NormalizedNode<?, ?> actual = prunerFullSchema.normalizedNode();
+        assertEquals("normalizedNode", input, actual);
     }
 
     @Test
     public void testAnyXMLNodeHasParent() throws IOException {
-        prunerFullSchema.stack().push(normalizedNodeBuilderWrapper);
-        YangInstanceIdentifier.NodeIdentifier nodeIdentifier = new YangInstanceIdentifier.NodeIdentifier(TestModel.BOOLEAN_LEAF_QNAME);
-        DOMSource o = mock(DOMSource.class);
-        prunerFullSchema.anyxmlNode(nodeIdentifier, o);
-
-        ArgumentCaptor<NormalizedNode> captor = ArgumentCaptor.forClass(NormalizedNode.class);
-
-        verify(normalizedNodeContainerBuilder).addChild(captor.capture());
-
-        NormalizedNode<?, ?> value = captor.getValue();
-        assertEquals(nodeIdentifier.getNodeType(), value.getNodeType());
-        assertEquals(nodeIdentifier, value.getIdentifier());
-        assertEquals(o, value.getValue());
+        AnyXmlNode child = Builders.anyXmlBuilder().withNodeIdentifier(
+                new YangInstanceIdentifier.NodeIdentifier(TestModel.CHILD_NAME_QNAME)).
+                    withValue(mock(DOMSource.class)).build();
+        NormalizedNode<?, ?> input = Builders.containerBuilder().withNodeIdentifier(
+                new YangInstanceIdentifier.NodeIdentifier(TestModel.AUG_CONT_QNAME)).withChild(child).build();
+        NormalizedNodeWriter.forStreamWriter(prunerFullSchema).write(input);
+
+        NormalizedNode<?, ?> actual = prunerFullSchema.normalizedNode();
+        assertEquals("normalizedNode", input, actual);
     }
 
     @Test
index 781c3db..07ccce2 100644 (file)
@@ -16,17 +16,23 @@ import java.util.Collection;
 import org.apache.commons.lang3.SerializationUtils;
 import org.junit.Before;
 import org.junit.Test;
+import org.opendaylight.controller.md.cluster.datastore.model.TestModel;
+import org.opendaylight.yangtools.yang.common.QName;
 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.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidates;
+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.impl.ImmutableContainerNodeBuilder;
-import org.opendaylight.controller.md.cluster.datastore.model.TestModel;
 
 public class DataTreeCandidatePayloadTest {
+    static final QName LEAF_SET = QName.create(TestModel.TEST_QNAME, "leaf-set");
+
     private DataTreeCandidate candidate;
 
     private static DataTreeCandidateNode findNode(final Collection<DataTreeCandidateNode> nodes, final PathArgument arg) {
@@ -120,4 +126,62 @@ public class DataTreeCandidatePayloadTest {
         final DataTreeCandidatePayload payload = DataTreeCandidatePayload.create(candidate);
         assertCandidateEquals(candidate, SerializationUtils.clone(payload).getCandidate());
     }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void testLeafSetEntryNodeCandidate() throws Exception {
+        YangInstanceIdentifier.NodeWithValue entryPathArg = new YangInstanceIdentifier.NodeWithValue(LEAF_SET, "one");
+        YangInstanceIdentifier leafSetEntryPath = YangInstanceIdentifier.builder(TestModel.TEST_PATH).node(LEAF_SET)
+                .node(entryPathArg).build();
+
+        NormalizedNode<?, ?> leafSetEntryNode = Builders.leafSetEntryBuilder().
+                withNodeIdentifier(entryPathArg).withValue("one").build();
+
+        DataTreeCandidate candidate = DataTreeCandidates.fromNormalizedNode(leafSetEntryPath, leafSetEntryNode);
+        DataTreeCandidatePayload payload = DataTreeCandidatePayload.create(candidate);
+        assertCandidateEquals(candidate, payload.getCandidate());
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void testLeafSetNodeCandidate() throws Exception {
+        YangInstanceIdentifier.NodeWithValue entryPathArg = new YangInstanceIdentifier.NodeWithValue(LEAF_SET, "one");
+        YangInstanceIdentifier leafSetPath = YangInstanceIdentifier.builder(TestModel.TEST_PATH).node(LEAF_SET).build();
+
+        LeafSetEntryNode leafSetEntryNode = Builders.leafSetEntryBuilder().
+                withNodeIdentifier(entryPathArg).withValue("one").build();
+        NormalizedNode<?, ?> leafSetNode = Builders.leafSetBuilder().withNodeIdentifier(
+                new YangInstanceIdentifier.NodeIdentifier(LEAF_SET)).withChild(leafSetEntryNode).build();
+
+        DataTreeCandidate candidate = DataTreeCandidates.fromNormalizedNode(leafSetPath, leafSetNode);
+        DataTreeCandidatePayload payload = DataTreeCandidatePayload.create(candidate);
+        assertCandidateEquals(candidate, payload.getCandidate());
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void testOrderedLeafSetNodeCandidate() throws Exception {
+        YangInstanceIdentifier.NodeWithValue entryPathArg = new YangInstanceIdentifier.NodeWithValue(LEAF_SET, "one");
+        YangInstanceIdentifier leafSetPath = YangInstanceIdentifier.builder(TestModel.TEST_PATH).node(LEAF_SET).build();
+
+        LeafSetEntryNode leafSetEntryNode = Builders.leafSetEntryBuilder().
+                withNodeIdentifier(entryPathArg).withValue("one").build();
+        NormalizedNode<?, ?> leafSetNode = Builders.orderedLeafSetBuilder().withNodeIdentifier(
+                new YangInstanceIdentifier.NodeIdentifier(LEAF_SET)).withChild(leafSetEntryNode).build();
+
+        DataTreeCandidate candidate = DataTreeCandidates.fromNormalizedNode(leafSetPath, leafSetNode);
+        DataTreeCandidatePayload payload = DataTreeCandidatePayload.create(candidate);
+        assertCandidateEquals(candidate, payload.getCandidate());
+    }
+
+    @Test
+    public void testLeafNodeCandidate() throws Exception {
+        YangInstanceIdentifier leafPath = YangInstanceIdentifier.builder(TestModel.TEST_PATH).node(TestModel.DESC_QNAME).build();
+        LeafNode<Object> leafNode = Builders.leafBuilder().withNodeIdentifier(
+                new YangInstanceIdentifier.NodeIdentifier(TestModel.DESC_QNAME)).withValue("test").build();
+
+        DataTreeCandidate candidate = DataTreeCandidates.fromNormalizedNode(leafPath, leafNode);
+        DataTreeCandidatePayload payload = DataTreeCandidatePayload.create(candidate);
+        assertCandidateEquals(candidate, payload.getCandidate());
+    }
 }