HONEYCOMB-302: add support for nested augmentations 68/4768/2
authorMarek Gradzki <mgradzki@cisco.com>
Tue, 17 Jan 2017 12:30:50 +0000 (13:30 +0100)
committerMarek Gradzki <mgradzki@cisco.com>
Mon, 23 Jan 2017 08:44:59 +0000 (08:44 +0000)
Change-Id: I60f1b3f79ddb578d6fca157fe5736de40b30623e
Signed-off-by: Marek Gradzki <mgradzki@cisco.com>
(cherry picked from commit 78886acd688284585c2e219e18d7289f49cc8a45)

infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModificationDiff.java
infra/it/it-test/src/test/java/io/fd/honeycomb/data/impl/AbstractInfraTest.java
infra/it/it-test/src/test/java/io/fd/honeycomb/data/impl/NestedAugmentationWriteTest.java [new file with mode: 0644]
infra/it/test-model/src/main/yang/hc-aug-test.yang [new file with mode: 0644]

index 86666ba..9903d99 100644 (file)
@@ -24,6 +24,7 @@ import static org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationT
 import static org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType.WRITE;
 
 import com.google.common.collect.ImmutableMap;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashMap;
@@ -296,19 +297,23 @@ final class ModificationDiff {
         private final DataTreeCandidateNode dataCandidate;
         // Using Object as type for schema node since it's the only type that's a parent to all schema node types from
         // yangtools. The hierarchy does not use e.g. SchemaNode class for all types
+        private final Object parentNode;
         private final Object schemaNode;
 
         Modification(final YangInstanceIdentifier id,
                      final DataTreeCandidateNode dataCandidate,
+                     final Object parentNode,
                      final Object schemaNode) {
             this.id = id;
             this.dataCandidate = dataCandidate;
+            this.parentNode = parentNode;
             this.schemaNode = schemaNode;
         }
 
-        Stream<Modification> streamChildren() {
-            return dataCandidate.getChildNodes().stream()
-                    .map(child -> new Modification(id.node(child.getIdentifier()), child, schemaChild(schemaNode, child.getIdentifier())));
+        Modification(final YangInstanceIdentifier id,
+                     final DataTreeCandidateNode dataCandidate,
+                     final Object schemaNode) {
+            this(id, dataCandidate, schemaNode, schemaNode);
         }
 
         List<Modification> getChildNodes() {
@@ -355,35 +360,98 @@ final class ModificationDiff {
             return dataCandidate.getDataAfter().isPresent();
         }
 
+        private AugmentationSchema findAugmentation(Object currentNode,
+                                                    final YangInstanceIdentifier.AugmentationIdentifier identifier) {
+            if (currentNode != null) {
+                // check if identifier points to some augmentation of currentNode
+                if (currentNode instanceof AugmentationTarget) {
+                    Optional<AugmentationSchema> augmentationSchema =
+                        ((AugmentationTarget) currentNode).getAvailableAugmentations().stream()
+                            .filter(aug -> identifier.equals(new YangInstanceIdentifier.AugmentationIdentifier(
+                                aug.getChildNodes().stream()
+                                    .map(SchemaNode::getQName)
+                                    .collect(Collectors.toSet()))))
+                            .findFirst();
+                    if (augmentationSchema.isPresent()) {
+                        return augmentationSchema.get();
+                    }
+                }
+
+                // continue search:
+                Collection<DataSchemaNode> childNodes = Collections.emptyList();
+                if (currentNode instanceof DataNodeContainer) {
+                    childNodes = ((DataNodeContainer) currentNode).getChildNodes();
+                } else if (currentNode instanceof ChoiceSchemaNode) {
+                    childNodes = ((ChoiceSchemaNode) currentNode).getCases().stream()
+                        .flatMap(cas -> cas.getChildNodes().stream()).collect(Collectors.toList());
+                }
+                return childNodes.stream().map(n -> findAugmentation(n, identifier)).filter(n -> n != null).findFirst()
+                    .orElse(null);
+            } else {
+                return null;
+            }
+        }
+
+        Stream<Modification> streamChildren() {
+            return dataCandidate.getChildNodes().stream()
+                .map(child -> {
+                    final YangInstanceIdentifier childId = id.node(child.getIdentifier());
+                    final Object schemaChild = schemaChild(schemaNode, child.getIdentifier());
+                    // An augment cannot change other augment, so we do not update parent node if we are streaming
+                    // children of AugmentationSchema (otherwise we would fail to find schema for nested augmentations):
+                    final Object newParent = (schemaNode instanceof AugmentationSchema)
+                        ? parentNode
+                        : schemaNode;
+                    return new Modification(childId, child, newParent, schemaChild);
+                });
+        }
+
         /**
          * Find next schema node in hierarchy.
          */
-        private Object schemaChild(final Object schema, final YangInstanceIdentifier.PathArgument identifier) {
+        private Object schemaChild(final Object schemaNode, final YangInstanceIdentifier.PathArgument identifier) {
             Object found = null;
 
             if (identifier instanceof YangInstanceIdentifier.AugmentationIdentifier) {
-                if (schema instanceof AugmentationTarget) {
+                if (schemaNode instanceof AugmentationTarget) {
                     // Find matching augmentation
-                    found = ((AugmentationTarget) schema).getAvailableAugmentations().stream()
-                            .filter(aug -> identifier.equals(new YangInstanceIdentifier.AugmentationIdentifier(
-                                    aug.getChildNodes().stream()
-                                            .map(SchemaNode::getQName)
-                                            .collect(Collectors.toSet()))))
-                            .findFirst()
-                            .orElse(null);
+                    found = ((AugmentationTarget) schemaNode).getAvailableAugmentations().stream()
+                        .filter(aug -> identifier.equals(new YangInstanceIdentifier.AugmentationIdentifier(
+                            aug.getChildNodes().stream()
+                                .map(SchemaNode::getQName)
+                                .collect(Collectors.toSet()))))
+                        .findFirst()
+                        .orElse(null);
+
+                    if (found == null) {
+                        // An augment cannot change other augment, but all augments only change their targets (data nodes).
+                        //
+                        // As a consequence, if nested augmentations are present,
+                        // AugmentationSchema might reference child schema node instances that do not include changes
+                        // from nested augments.
+                        //
+                        // But schemaNode, as mentioned earlier, contains all the changes introduced by augments.
+                        //
+                        // On the other hand, in case of augments which introduce leaves,
+                        // we need to address AugmentationSchema node directly so we can't simply do
+                        // found = schemaNode;
+                        //
+                        found =
+                            findAugmentation(parentNode, (YangInstanceIdentifier.AugmentationIdentifier) identifier);
+                    }
                 }
-            } else if (schema instanceof DataNodeContainer) {
+            } else if (schemaNode instanceof DataNodeContainer) {
                 // Special handling for list aggregator nodes. If we are at list aggregator node e.g. MapNode and
                 // we are searching for schema for a list entry e.g. MapEntryNode just return the same schema
-                if (schema instanceof ListSchemaNode &&
-                        ((SchemaNode) schema).getQName().equals(identifier.getNodeType())) {
-                    found = schema;
+                if (schemaNode instanceof ListSchemaNode &&
+                    ((SchemaNode) schemaNode).getQName().equals(identifier.getNodeType())) {
+                    found = schemaNode;
                 } else {
-                    found = ((DataNodeContainer) schema).getDataChildByName(identifier.getNodeType());
+                    found = ((DataNodeContainer) schemaNode).getDataChildByName(identifier.getNodeType());
                 }
-            } else if (schema instanceof ChoiceSchemaNode) {
+            } else if (schemaNode instanceof ChoiceSchemaNode) {
                 // For choices, iterate through all the cases
-                final Optional<DataSchemaNode> maybeChild = ((ChoiceSchemaNode) schema).getCases().stream()
+                final Optional<DataSchemaNode> maybeChild = ((ChoiceSchemaNode) schemaNode).getCases().stream()
                         .flatMap(cas -> cas.getChildNodes().stream())
                         .filter(child -> child.getQName().equals(identifier.getNodeType()))
                         .findFirst();
@@ -391,12 +459,12 @@ final class ModificationDiff {
                     found = maybeChild.get();
                 }
                 // Special handling for leaf-list nodes. Basically the same as is for list mixin nodes
-            } else if (schema instanceof LeafListSchemaNode &&
-                    ((SchemaNode) schema).getQName().equals(identifier.getNodeType())) {
-                found = schema;
+            } else if (schemaNode instanceof LeafListSchemaNode &&
+                ((SchemaNode) schemaNode).getQName().equals(identifier.getNodeType())) {
+                found = schemaNode;
             }
 
-            return checkNotNull(found, "Unable to find child node in: %s identifiable by: %s", schema, identifier);
+            return checkNotNull(found, "Unable to find child node in: %s identifiable by: %s", schemaNode, identifier);
         }
 
         @Override
index 3c996f6..e2a74ab 100644 (file)
@@ -21,14 +21,13 @@ import static org.mockito.Mockito.when;
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.Multimap;
 import com.google.common.util.concurrent.Futures;
-import java.util.Collections;
+import java.util.Arrays;
 import java.util.Map;
 import javassist.ClassPool;
 import org.junit.Before;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.opendaylight.controller.md.sal.binding.impl.BindingToNormalizedNodeCodec;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.test.rev150105.$YangModuleInfoImpl;
 import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeSerializer;
 import org.opendaylight.yangtools.binding.data.codec.gen.impl.DataObjectSerializerGenerator;
 import org.opendaylight.yangtools.binding.data.codec.gen.impl.StreamWriterGenerator;
@@ -70,7 +69,11 @@ abstract class AbstractInfraTest {
 
     static ModuleInfoBackedContext getSchemaContext() {
         final ModuleInfoBackedContext moduleInfoBackedContext = ModuleInfoBackedContext.create();
-        moduleInfoBackedContext.addModuleInfos(Collections.singleton($YangModuleInfoImpl.getInstance()));
+        moduleInfoBackedContext.addModuleInfos(Arrays.asList(
+            org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.test.rev150105.$YangModuleInfoImpl.getInstance(),
+            org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.$YangModuleInfoImpl.getInstance()
+
+        ));
         return moduleInfoBackedContext;
     }
 
diff --git a/infra/it/it-test/src/test/java/io/fd/honeycomb/data/impl/NestedAugmentationWriteTest.java b/infra/it/it-test/src/test/java/io/fd/honeycomb/data/impl/NestedAugmentationWriteTest.java
new file mode 100644 (file)
index 0000000..ef7430d
--- /dev/null
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import io.fd.honeycomb.data.DataModification;
+import io.fd.honeycomb.translate.impl.write.registry.FlatWriterRegistryBuilder;
+import io.fd.honeycomb.translate.write.WriteContext;
+import io.fd.honeycomb.translate.write.Writer;
+import io.fd.honeycomb.translate.write.registry.WriterRegistry;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.AugTarget;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.AugTargetBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.FromAugment2Augment;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.FromAugment2AugmentBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.FromAugmentAugment;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.FromAugmentAugmentBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.FromAugmentListAugment;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.FromAugmentListAugmentBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.SimpleAugment;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.SimpleAugmentBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.SimpleNestedAugment;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.SimpleNestedAugmentBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.aug.target.FromAugment;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.aug.target.FromAugmentBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.aug.target.from.augment.FromAugment2;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.aug.target.from.augment.FromAugment2Builder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.aug.target.from.augment.FromAugmentEntry;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.aug.target.from.augment.FromAugmentEntryBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.aug.target.from.augment.FromAugmentEntryKey;
+import org.opendaylight.yangtools.yang.binding.Augmentation;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TipProducingDataTree;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
+import org.opendaylight.yangtools.yang.data.impl.schema.tree.InMemoryDataTreeFactory;
+
+/**
+ * Testing write for model with nested augmentation.
+ * See <a href="https://jira.fd.io/browse/HONEYCOMB-302">HONEYCOMB-302</a>} for more details.
+ */
+public class NestedAugmentationWriteTest extends AbstractInfraTest {
+
+    private static final InstanceIdentifier<AugTarget> AUG_TARGET_ID = InstanceIdentifier.create(AugTarget.class);
+    private static final InstanceIdentifier<FromAugmentAugment> FROM_AUGMENT_AUGMENT_ID =
+        AUG_TARGET_ID.augmentation(FromAugmentAugment.class);
+    private static final InstanceIdentifier<FromAugment> FROM_AUGMENT_ID =
+        FROM_AUGMENT_AUGMENT_ID.child(FromAugment.class);
+    private static final InstanceIdentifier<SimpleAugment> SIMPLE_AUGMENT_ID =
+        AUG_TARGET_ID.augmentation(SimpleAugment.class);
+    private static final InstanceIdentifier<FromAugment2Augment> FROM_AUGMENT2_AUGMENT_ID =
+        FROM_AUGMENT_ID.augmentation(FromAugment2Augment.class);
+    private static final InstanceIdentifier<FromAugment2> FROM_AUGMENT2_ID =
+        FROM_AUGMENT2_AUGMENT_ID.child(FromAugment2.class);
+    private static final InstanceIdentifier<SimpleNestedAugment> SIMPLE_NESTED_AUGMENT_ID =
+        FROM_AUGMENT_ID.augmentation(SimpleNestedAugment.class);
+    private static final InstanceIdentifier<FromAugmentListAugment> FROM_AUGMENT_LIST_AUGMENT_ID =
+        FROM_AUGMENT_ID.augmentation(FromAugmentListAugment.class);
+    private static final InstanceIdentifier<FromAugmentEntry> FROM_AUGMENT_ENTRY_ID =
+        FROM_AUGMENT_LIST_AUGMENT_ID.child(FromAugmentEntry.class);
+
+    private TipProducingDataTree dataTree;
+    private WriterRegistry writerRegistry;
+
+    private final Writer<AugTarget> augTargetWriter = mockWriter(AUG_TARGET_ID);
+    private final Writer<FromAugment> fromAugmentWriter = mockWriter(FROM_AUGMENT_ID);
+    private final Writer<FromAugment2> fromAugment2Writer = mockWriter(FROM_AUGMENT2_ID);
+    private final Writer<FromAugmentEntry> fromAugmentListWriter = mockWriter(FROM_AUGMENT_ENTRY_ID);
+
+    private final Writer<SimpleAugment> simpleAugmentWriter = mockWriter(SIMPLE_AUGMENT_ID);
+    private final Writer<SimpleNestedAugment> simpleNestedAugmentWriter = mockWriter(SIMPLE_NESTED_AUGMENT_ID);
+
+    private static <D extends DataObject> Writer<D> mockWriter(final InstanceIdentifier<D> id) {
+        final Writer<D> mock = (Writer<D>) mock(Writer.class);
+        when(mock.getManagedDataObjectType()).thenReturn(id);
+        return mock;
+    }
+
+    @Override
+    void postSetup() {
+        initDataTree();
+        initWriterRegistry();
+    }
+
+    private void initDataTree() {
+        dataTree = InMemoryDataTreeFactory.getInstance().create(TreeType.CONFIGURATION);
+        dataTree.setSchemaContext(schemaContext);
+    }
+
+    private void initWriterRegistry() {
+        writerRegistry = new FlatWriterRegistryBuilder()
+            .add(augTargetWriter)
+            .add(fromAugmentWriter)
+            .add(simpleAugmentWriter)
+            .add(fromAugment2Writer)
+            .add(simpleNestedAugmentWriter)
+            .add(fromAugmentListWriter)
+            .build();
+    }
+
+    @Test
+    public void testSimpleAugmentationWrite() throws Exception {
+        final ModifiableDataTreeDelegator modifiableDataTreeDelegator =
+            new ModifiableDataTreeDelegator(serializer, dataTree, schemaContext, writerRegistry, contextBroker);
+
+        final DataModification dataModification = modifiableDataTreeDelegator.newModification();
+        final AugTarget data = new AugTargetBuilder()
+            .setSomeLeaf("aug-target-leaf-val")
+            .addAugmentation(SimpleAugment.class, simpleAugment())
+            .build();
+
+        final Map.Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> normalizedNode =
+            serializer.toNormalizedNode(AUG_TARGET_ID, data);
+        dataModification.write(normalizedNode.getKey(), normalizedNode.getValue());
+
+        dataModification.commit();
+
+        verify(simpleAugmentWriter).update(eq(SIMPLE_AUGMENT_ID), eq(null), eq(simpleAugment()), any(WriteContext.class));
+    }
+
+    @Test
+    public void testSimpleNestedAugmentationWrite() throws Exception {
+        final ModifiableDataTreeDelegator modifiableDataTreeDelegator =
+            new ModifiableDataTreeDelegator(serializer, dataTree, schemaContext, writerRegistry, contextBroker);
+
+        final DataModification dataModification = modifiableDataTreeDelegator.newModification();
+
+        final SimpleNestedAugment augData = simpleNestedAugment();
+        final AugTarget data = augTarget(fromAugmentSimple(augData));
+
+        final Map.Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> normalizedNode =
+            serializer.toNormalizedNode(AUG_TARGET_ID, data);
+        dataModification.write(normalizedNode.getKey(), normalizedNode.getValue());
+
+        dataModification.commit();
+
+        verify(augTargetWriter).update(eq(AUG_TARGET_ID), eq(null), eq(data), any(WriteContext.class));
+        verify(fromAugmentWriter).update(eq(FROM_AUGMENT_ID), eq(null), eq(fromAugmentSimple(augData)), any(WriteContext.class));
+        verify(simpleNestedAugmentWriter).update(eq(SIMPLE_NESTED_AUGMENT_ID), eq(null), eq(augData), any(WriteContext.class));
+    }
+
+    private SimpleAugment simpleAugment() {
+        return new SimpleAugmentBuilder().setSimpleAugmentLeaf("val").build();
+    }
+
+    @Test
+    public void testNestedAugmentationWrite() throws Exception {
+        final ModifiableDataTreeDelegator modifiableDataTreeDelegator =
+            new ModifiableDataTreeDelegator(serializer, dataTree, schemaContext, writerRegistry, contextBroker);
+
+        final DataModification dataModification = modifiableDataTreeDelegator.newModification();
+        final AugTarget data = augTarget(fromAugment(FromAugment2Augment.class, fromAugment2Augment(fromAugment2())));
+
+        final Map.Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> normalizedNode =
+            serializer.toNormalizedNode(AUG_TARGET_ID, data);
+        dataModification.write(normalizedNode.getKey(), normalizedNode.getValue());
+
+        dataModification.commit();
+
+        verify(augTargetWriter).update(eq(AUG_TARGET_ID), eq(null), eq(data), any(WriteContext.class));
+        verify(fromAugmentWriter).update(eq(FROM_AUGMENT_ID), eq(null), eq(fromAugment()), any(WriteContext.class));
+        verify(fromAugment2Writer).update(eq(FROM_AUGMENT2_ID), eq(null), eq(fromAugment2()), any(WriteContext.class));
+    }
+
+    @Test
+    public void testNestedAugmentationListWrite() throws Exception {
+        final ModifiableDataTreeDelegator modifiableDataTreeDelegator =
+            new ModifiableDataTreeDelegator(serializer, dataTree, schemaContext, writerRegistry, contextBroker);
+
+        final DataModification dataModification = modifiableDataTreeDelegator.newModification();
+        final List<FromAugmentEntry> entries = Arrays.asList(
+            new FromAugmentEntryBuilder().setSomeLeaf("1").build(),
+            new FromAugmentEntryBuilder().setSomeLeaf("2").build()
+        );
+        final FromAugment fromAugment = fromAugment(FromAugmentListAugment.class, fromAugmentList(entries));
+        final AugTarget data = augTarget(fromAugment);
+
+        final Map.Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> normalizedNode =
+            serializer.toNormalizedNode(AUG_TARGET_ID, data);
+        dataModification.write(normalizedNode.getKey(), normalizedNode.getValue());
+        dataModification.commit();
+
+        final ArgumentCaptor<DataObject> doCaptor = ArgumentCaptor.forClass(DataObject.class);
+        verify(augTargetWriter).update(eq(AUG_TARGET_ID), eq(null), doCaptor.capture(), any(WriteContext.class));
+        assertEquals(data.getSomeLeaf(), ((AugTarget)doCaptor.getValue()).getSomeLeaf());
+
+        verify(fromAugmentWriter).update(eq(FROM_AUGMENT_ID), eq(null), doCaptor.capture(), any(WriteContext.class));
+        assertEquals(fromAugment.getSomeLeaf(), ((FromAugment)doCaptor.getValue()).getSomeLeaf());
+
+
+        final KeyedInstanceIdentifier<FromAugmentEntry, FromAugmentEntryKey> keyedNestedList1 =
+            FROM_AUGMENT_LIST_AUGMENT_ID.child(FromAugmentEntry.class, new FromAugmentEntryKey("1"));
+        final KeyedInstanceIdentifier<FromAugmentEntry, FromAugmentEntryKey> keyedNestedList2 =
+            FROM_AUGMENT_LIST_AUGMENT_ID.child(FromAugmentEntry.class, new FromAugmentEntryKey("2"));
+
+        verify(fromAugmentListWriter)
+            .update(eq(keyedNestedList1), eq(null), eq(entries.get(0)), any(WriteContext.class));
+        verify(fromAugmentListWriter)
+            .update(eq(keyedNestedList2), eq(null), eq(entries.get(1)), any(WriteContext.class));
+    }
+
+    private AugTarget augTarget(FromAugment fromAugment) {
+        return new AugTargetBuilder()
+            .setSomeLeaf("aug-target-leaf-val")
+            .addAugmentation(FromAugmentAugment.class,
+                new FromAugmentAugmentBuilder().setFromAugment(fromAugment).build())
+            .build();
+    }
+
+    private FromAugment fromAugment() {
+        return new FromAugmentBuilder()
+            .setSomeLeaf("from-augment-leaf-val")
+            .addAugmentation(FromAugment2Augment.class, new FromAugment2AugmentBuilder()
+                .setFromAugment2(fromAugment2()).build())
+            .build();
+    }
+
+    private FromAugment fromAugmentSimple(SimpleNestedAugment simpleNestedAugment) {
+        return new FromAugmentBuilder()
+            .setSomeLeaf("from-augment-leaf-val")
+            .addAugmentation(SimpleNestedAugment.class, simpleNestedAugment)
+            .build();
+    }
+
+    private SimpleNestedAugment simpleNestedAugment() {
+        return new SimpleNestedAugmentBuilder()
+            .setSimpleNestedAugmentLeaf("simple-nested-augment-leaf-val").build();
+    }
+
+    private FromAugment fromAugment(final Class<? extends Augmentation<FromAugment>> augmentationClass,
+                                    final Augmentation<FromAugment> augmentation) {
+        return new FromAugmentBuilder()
+            .setSomeLeaf("from-augment-leaf-val")
+            .addAugmentation(augmentationClass, augmentation)
+            .build();
+    }
+
+    private FromAugment2Augment fromAugment2Augment(FromAugment2 fromAugment2) {
+        return new FromAugment2AugmentBuilder().setFromAugment2(fromAugment2).build();
+    }
+
+    private FromAugment2 fromAugment2() {
+        return new FromAugment2Builder()
+            .setNewLeaf("new-leaf-val")
+            .build();
+    }
+
+    private FromAugmentListAugment fromAugmentList(final List<FromAugmentEntry> entries) {
+        return new FromAugmentListAugmentBuilder()
+            .setFromAugmentEntry(entries)
+            .build();
+    }
+}
diff --git a/infra/it/test-model/src/main/yang/hc-aug-test.yang b/infra/it/test-model/src/main/yang/hc-aug-test.yang
new file mode 100644 (file)
index 0000000..8a83ab5
--- /dev/null
@@ -0,0 +1,63 @@
+module hc-aug-test {
+  yang-version 1;
+  namespace "urn:opendaylight:params:xml:ns:yang:hc:aug:test";
+  prefix "hcat";
+
+  revision "2017-01--9" {
+    description "Test model for aumentations of augmentations (https://jira.fd.io/browse/HONEYCOMB-302)";
+  }
+
+  import yang-ext {
+    prefix "ext";
+  }
+
+  container aug-target {
+    leaf some-leaf {
+      type string;
+    }
+  }
+
+  augment "/aug-target" {
+    ext:augment-identifier "from-augment-augment";
+    container from-augment {
+      leaf some-leaf {
+        type string;
+      }
+    }
+  }
+
+  augment "/aug-target" {
+    ext:augment-identifier "simple-augment";
+    leaf simple-augment-leaf {
+        type string;
+    }
+  }
+
+  augment "/aug-target/from-augment" {
+    ext:augment-identifier "simple-nested-augment";
+    leaf simple-nested-augment-leaf {
+        type string;
+    }
+  }
+
+  augment "/aug-target/from-augment" {
+    ext:augment-identifier "from-augment2-augment";
+    container from-augment-2 {
+      leaf new-leaf {
+        type string;
+      }
+    }
+  }
+
+  augment "/aug-target/from-augment" {
+    ext:augment-identifier "from-augment-list-augment";
+    list from-augment-entry {
+      key some-leaf;
+
+      leaf some-leaf {
+        type string;
+      }
+    }
+  }
+
+}