Teach mdsal-binding-dom-codec about cases 56/72456/16
authorRobert Varga <robert.varga@pantheon.tech>
Tue, 29 May 2018 23:45:08 +0000 (01:45 +0200)
committerTom Pantelis <tompantelis@gmail.com>
Thu, 21 Jun 2018 22:34:14 +0000 (22:34 +0000)
With InstanceIdentifier.Item updates we can now carry unambiguous
identifier of grouping-held classes, which needs to be handled
at codec level: case-bearing PathArguments need to be recognized
and produced to ensure grouping items are correctly addressed.

JIRA: MDSAL-45
Change-Id: If6fa332db2f3c9d033ba2a1fb2c9678dc1e94276
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CaseNodeCodecContext.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ChoiceNodeCodecContext.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataContainerCodecPrototype.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataObjectCodecContext.java
binding/mdsal-binding-dom-codec/src/test/java/org/opendaylight/mdsal/binding/dom/codec/test/InstanceIdentifierSerializeDeserializeTest.java
binding/mdsal-binding-test-model/src/main/yang/opendaylight-mdsal45-aug.yang [new file with mode: 0644]
binding/mdsal-binding-test-model/src/main/yang/opendaylight-mdsal45-base.yang [new file with mode: 0644]

index 3f36628a1a454e6a5c0862292aba14a115accc12..1041319e434a06ff412b838037d08dbe90d4defa 100644 (file)
@@ -10,17 +10,26 @@ package org.opendaylight.mdsal.binding.dom.codec.impl;
 import com.google.common.base.Preconditions;
 import java.util.List;
 import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 
 final class CaseNodeCodecContext<D extends DataObject> extends DataObjectCodecContext<D, CaseSchemaNode> {
     CaseNodeCodecContext(final DataContainerCodecPrototype<CaseSchemaNode> prototype) {
         super(prototype);
     }
 
+    @Override
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    Item<?> createBindingArg(final Class<?> childClass, final DataSchemaNode childSchema) {
+        return childSchema.isAddedByUses() ? Item.of((Class)getBindingClass(), (Class)childClass)
+                : Item.of((Class<? extends DataObject>) childClass);
+    }
+
     @Override
     protected void addYangPathArgument(final PathArgument arg,
             final List<YangInstanceIdentifier.PathArgument> builder) {
index 7c372eb86f061f41c656bc9c0630666e9b1bf4a5..5f1be30abf0df93073bef738a44ff6b694111414 100644 (file)
@@ -9,13 +9,24 @@ package org.opendaylight.mdsal.binding.dom.codec.impl;
 
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.MultimapBuilder.SetMultimapBuilder;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import org.opendaylight.yangtools.yang.binding.DataObject;
@@ -37,15 +48,64 @@ import org.slf4j.LoggerFactory;
 final class ChoiceNodeCodecContext<D extends DataObject> extends DataContainerCodecContext<D, ChoiceSchemaNode> {
     private static final Logger LOG = LoggerFactory.getLogger(ChoiceNodeCodecContext.class);
     private final ImmutableMap<YangInstanceIdentifier.PathArgument, DataContainerCodecPrototype<?>> byYangCaseChild;
+
+    /*
+     * This is a bit tricky. DataObject addressing does not take into account choice/case statements, and hence
+     * given:
+     *
+     * container foo {
+     *     choice bar {
+     *         leaf baz;
+     *     }
+     * }
+     *
+     * we will see {@code Baz extends ChildOf<Foo>}, which is how the users would address it in InstanceIdentifier
+     * terms. The implicit assumption being made is that {@code Baz} identifies a particular instantiation and hence
+     * provides unambiguous reference to an effective schema statement.
+     *
+     * <p>
+     * Unfortunately this does not quite work with groupings, as their generation has changed: we do not have
+     * interfaces that would capture grouping instantiations, hence we do not have a proper addressing point and
+     * users need to specify the interfaces generated in the grouping's definition. These can be very much
+     * ambiguous, as a {@code grouping} can be used in multiple modules independently within an {@code augment}
+     * targeting {@code choice}, as each instantiation is guaranteed to have a unique namespace -- but we do not
+     * have the appropriate instantiations of those nodes.
+     *
+     * <p>
+     * To address this issue we have a two-class lookup mechanism, which relies on the interface generated for
+     * the {@code case} statement to act as the namespace anchor bridging the nodes inside the grouping to the
+     * namespace in which they are instantiated.
+     *
+     * <p>
+     * Furthermore downstream code relies on historical mechanics, which would guess what the instantiation is,
+     * silently assuming the ambiguity is theoretical and does not occur in practice.
+     *
+     * <p>
+     * This leads to three classes of addressing, in order descending performance requirements.
+     * <ul>
+     *   <li>Direct DataObject, where we name an exact child</li>
+     *   <li>Case DataObject + Grouping DataObject</li>
+     *   <li>Grouping DataObject, which is ambiguous</li>
+     * </ul>
+     *
+     * {@code byCaseChildClass} supports direct DataObject mapping and contains only unambiguous children, while
+     * {@code byClass} supports indirect mapping and contains {@code case} sub-statements.
+     *
+     * ambiguousByCaseChildClass contains ambiguous mappings, for which we end up issuing warnings. We track each
+     * ambiguous reference and issue warnings when they are encountered.
+     */
     private final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byClass;
     private final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byCaseChildClass;
+    private final ImmutableListMultimap<Class<?>, DataContainerCodecPrototype<?>> ambiguousByCaseChildClass;
+    private final Set<Class<?>> ambiguousByCaseChildWarnings;
 
     ChoiceNodeCodecContext(final DataContainerCodecPrototype<ChoiceSchemaNode> prototype) {
         super(prototype);
         final Map<YangInstanceIdentifier.PathArgument, DataContainerCodecPrototype<?>> byYangCaseChildBuilder =
                 new HashMap<>();
         final Map<Class<?>, DataContainerCodecPrototype<?>> byClassBuilder = new HashMap<>();
-        final Map<Class<?>, DataContainerCodecPrototype<?>> byCaseChildClassBuilder = new HashMap<>();
+        final SetMultimap<Class<?>, DataContainerCodecPrototype<?>> childToCase = SetMultimapBuilder.hashKeys()
+                .hashSetValues().build();
         final Set<Class<?>> potentialSubstitutions = new HashSet<>();
         // Walks all cases for supplied choice in current runtime context
         for (final Class<?> caze : factory().getRuntimeContext().getCases(getBindingClass())) {
@@ -60,7 +120,7 @@ final class ChoiceNodeCodecContext<D extends DataObject> extends DataContainerCo
                 @SuppressWarnings("unchecked")
                 final Class<? extends DataObject> cazeCls = (Class<? extends DataObject>) caze;
                 for (final Class<? extends DataObject> cazeChild : BindingReflections.getChildrenClasses(cazeCls)) {
-                    byCaseChildClassBuilder.put(cazeChild, cazeDef);
+                    childToCase.put(cazeChild, cazeDef);
                 }
                 // Updates collection of YANG instance identifier to case
                 for (final DataSchemaNode cazeChild : cazeDef.getSchema().getChildNodes()) {
@@ -82,6 +142,29 @@ final class ChoiceNodeCodecContext<D extends DataObject> extends DataContainerCo
                 potentialSubstitutions.add(caze);
             }
         }
+        byYangCaseChild = ImmutableMap.copyOf(byYangCaseChildBuilder);
+
+        // Move unambiguous child->case mappings to byCaseChildClass, removing them from childToCase
+        final ImmutableListMultimap.Builder<Class<?>, DataContainerCodecPrototype<?>> ambiguousByCaseBuilder =
+                ImmutableListMultimap.builder();
+        final Builder<Class<?>, DataContainerCodecPrototype<?>> unambiguousByCaseBuilder = ImmutableMap.builder();
+        for (Entry<Class<?>, Set<DataContainerCodecPrototype<?>>> e : Multimaps.asMap(childToCase).entrySet()) {
+            final Set<DataContainerCodecPrototype<?>> cases = e.getValue();
+            if (cases.size() != 1) {
+                // Sort all possibilities by their FQCN to retain semi-predictable results
+                final List<DataContainerCodecPrototype<?>> list = new ArrayList<>(e.getValue());
+                list.sort(Comparator.comparing(proto -> proto.getBindingClass().getCanonicalName()));
+                ambiguousByCaseBuilder.putAll(e.getKey(), list);
+            } else {
+                unambiguousByCaseBuilder.put(e.getKey(), cases.iterator().next());
+            }
+        }
+        byCaseChildClass = unambiguousByCaseBuilder.build();
+
+        // Setup ambiguous tracking, if needed
+        ambiguousByCaseChildClass = ambiguousByCaseBuilder.build();
+        ambiguousByCaseChildWarnings = ambiguousByCaseChildClass.isEmpty() ? ImmutableSet.of()
+                : ConcurrentHashMap.newKeySet();
 
         final Map<Class<?>, DataContainerCodecPrototype<?>> bySubstitutionBuilder = new HashMap<>();
         /*
@@ -100,17 +183,15 @@ final class ChoiceNodeCodecContext<D extends DataObject> extends DataContainerCo
             }
         }
         byClassBuilder.putAll(bySubstitutionBuilder);
-        byYangCaseChild = ImmutableMap.copyOf(byYangCaseChildBuilder);
         byClass = ImmutableMap.copyOf(byClassBuilder);
-        byCaseChildClass = ImmutableMap.copyOf(byCaseChildClassBuilder);
     }
 
     @SuppressWarnings("unchecked")
     @Override
     public <C extends DataObject> DataContainerCodecContext<C, ?> streamChild(final Class<C> childClass) {
         final DataContainerCodecPrototype<?> child = byClass.get(childClass);
-        return (DataContainerCodecContext<C, ?>) childNonNull(child, childClass, "Supplied class %s is not valid case",
-            childClass).get();
+        return (DataContainerCodecContext<C, ?>) childNonNull(child, childClass,
+            "Supplied class %s is not valid case in %s", childClass, bindingArg()).get();
     }
 
     @SuppressWarnings("unchecked")
@@ -125,7 +206,7 @@ final class ChoiceNodeCodecContext<D extends DataObject> extends DataContainerCo
     }
 
     Iterable<Class<?>> getCaseChildrenClasses() {
-        return byCaseChildClass.keySet();
+        return Iterables.concat(byCaseChildClass.keySet(), ambiguousByCaseChildClass.keySet());
     }
 
     protected DataContainerCodecPrototype<CaseSchemaNode> loadCase(final Class<?> childClass) {
@@ -168,13 +249,6 @@ final class ChoiceNodeCodecContext<D extends DataObject> extends DataContainerCo
         return (D) caze.get().deserialize(data);
     }
 
-    DataContainerCodecContext<?, ?> getCazeByChildClass(final @Nonnull Class<? extends DataObject> type) {
-        final DataContainerCodecPrototype<?> protoCtx =
-                childNonNull(byCaseChildClass.get(type), type, "Class %s is not child of any cases for %s", type,
-                        bindingArg());
-        return protoCtx.get();
-    }
-
     @Override
     protected Object deserializeObject(final NormalizedNode<?, ?> normalizedNode) {
         return deserialize(normalizedNode);
@@ -191,4 +265,25 @@ final class ChoiceNodeCodecContext<D extends DataObject> extends DataContainerCo
         // FIXME: check for null, since binding container is null.
         return getDomPathArgument();
     }
+
+    DataContainerCodecContext<?, ?> getCaseByChildClass(final @Nonnull Class<? extends DataObject> type) {
+        DataContainerCodecPrototype<?> result = byCaseChildClass.get(type);
+        if (result == null) {
+            // We have not found an unambiguous result, try ambiguous ones
+            final List<DataContainerCodecPrototype<?>> inexact = ambiguousByCaseChildClass.get(type);
+            if (!inexact.isEmpty()) {
+                result = inexact.get(0);
+                // Issue a warning, but only once so as not to flood the logs
+                if (ambiguousByCaseChildWarnings.add(type)) {
+                    LOG.warn("Ambiguous reference {} to child of {} resolved to {}, the first case in {} This mapping "
+                            + "is not guaranteed to be stable and is subject to variations based on runtime "
+                            + "circumstances. Please see the stack trace for hints about the source of ambiguity.",
+                            type, bindingArg(), result.getBindingClass(),
+                            Lists.transform(inexact, DataContainerCodecPrototype::getBindingClass), new Throwable());
+                }
+            }
+        }
+
+        return childNonNull(result, type, "Class %s is not child of any cases for %s", type, bindingArg()).get();
+    }
 }
index 9fe7bed7be8c9dcb7ad8ee9ed12d09b5536a3875..9fa864d825305eb60793ff2c5d7832f38ac44a58 100644 (file)
@@ -41,7 +41,6 @@ final class DataContainerCodecPrototype<T extends WithStatus> implements NodeCon
     private final T schema;
     private final QNameModule namespace;
     private final CodecContextFactory factory;
-    private final Class<?> bindingClass;
     private final Item<?> bindingArg;
     private final PathArgument yangArg;
     private final ChildAddressabilitySummary childAddressabilitySummary;
@@ -51,11 +50,15 @@ final class DataContainerCodecPrototype<T extends WithStatus> implements NodeCon
     @SuppressWarnings("unchecked")
     private DataContainerCodecPrototype(final Class<?> cls, final PathArgument arg, final T nodeSchema,
             final CodecContextFactory factory) {
-        this.bindingClass = cls;
+        this(Item.of((Class<? extends DataObject>) cls), arg, nodeSchema, factory);
+    }
+
+    private DataContainerCodecPrototype(final Item<?> bindingArg, final PathArgument arg, final T nodeSchema,
+            final CodecContextFactory factory) {
+        this.bindingArg = bindingArg;
         this.yangArg = arg;
         this.schema = nodeSchema;
         this.factory = factory;
-        this.bindingArg = Item.of((Class<? extends DataObject>) bindingClass);
 
         if (arg instanceof AugmentationIdentifier) {
             this.namespace = Iterables.getFirst(((AugmentationIdentifier) arg).getPossibleChildNames(), null)
@@ -152,6 +155,12 @@ final class DataContainerCodecPrototype<T extends WithStatus> implements NodeCon
         return new DataContainerCodecPrototype(cls, NodeIdentifier.create(schema.getQName()), schema, factory);
     }
 
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    static <T extends DataSchemaNode> DataContainerCodecPrototype<T> from(final Item<?> bindingArg, final T schema,
+            final CodecContextFactory factory) {
+        return new DataContainerCodecPrototype(bindingArg, NodeIdentifier.create(schema.getQName()), schema, factory);
+    }
+
     @SuppressWarnings({ "rawtypes", "unchecked" })
     static DataContainerCodecPrototype<?> from(final Class<?> augClass, final AugmentationIdentifier arg,
             final AugmentationSchemaNode schema, final CodecContextFactory factory) {
@@ -181,7 +190,7 @@ final class DataContainerCodecPrototype<T extends WithStatus> implements NodeCon
     }
 
     protected Class<?> getBindingClass() {
-        return bindingClass;
+        return bindingArg.getType();
     }
 
     protected Item<?> getBindingArg() {
@@ -224,7 +233,7 @@ final class DataContainerCodecPrototype<T extends WithStatus> implements NodeCon
         } else if (schema instanceof CaseSchemaNode) {
             return new CaseNodeCodecContext(this);
         }
-        throw new IllegalArgumentException("Unsupported type " + bindingClass + " " + schema);
+        throw new IllegalArgumentException("Unsupported type " + getBindingClass() + " " + schema);
     }
 
     boolean isChoice() {
index 5ef88440759a682b18799f533eedcca125e258d0..dd4958aa3c83d2a8dca6976595821eda681c8dc9 100644 (file)
@@ -37,6 +37,7 @@ import org.opendaylight.yangtools.yang.binding.Augmentation;
 import org.opendaylight.yangtools.yang.binding.AugmentationHolder;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
 import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
@@ -181,8 +182,18 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
                 childNonNull(ctxProto, argType, "Class %s is not valid child of %s", argType, getBindingClass()).get();
         if (context instanceof ChoiceNodeCodecContext) {
             final ChoiceNodeCodecContext<?> choice = (ChoiceNodeCodecContext<?>) context;
-            final DataContainerCodecContext<?, ?> caze = choice.getCazeByChildClass(arg.getType());
             choice.addYangPathArgument(arg, builder);
+
+            final java.util.Optional<? extends Class<? extends DataObject>> caseType = arg.getCaseType();
+            final Class<? extends DataObject> type = arg.getType();
+            final DataContainerCodecContext<?, ?> caze;
+            if (caseType.isPresent()) {
+                // Non-ambiguous addressing this should not pose any problems
+                caze = choice.streamChild(caseType.get());
+            } else {
+                caze = choice.getCaseByChildClass(type);
+            }
+
             caze.addYangPathArgument(arg, builder);
             return caze.bindingPathArgumentChild(arg, builder);
         }
@@ -248,7 +259,12 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
         }
         final DataSchemaNode nonNullChild =
                 childNonNull(childSchema, childClass, "Node %s does not have child named %s", getSchema(), childClass);
-        return DataContainerCodecPrototype.from(childClass, nonNullChild, factory());
+        return DataContainerCodecPrototype.from(createBindingArg(childClass, nonNullChild), nonNullChild, factory());
+    }
+
+    @SuppressWarnings("unchecked")
+    Item<?> createBindingArg(final Class<?> childClass, final DataSchemaNode childSchema) {
+        return Item.of((Class<? extends DataObject>) childClass);
     }
 
     private DataContainerCodecPrototype<?> yangAugmentationChild(final AugmentationIdentifier arg) {
index 7106d73e51a3108644091ffa1bfb342ff77b85bf..f233a971e9c7d15ac2779b57ade7906b0d67a6d0 100644 (file)
@@ -24,6 +24,11 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.te
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.Top;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.two.level.list.TopLevelList;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.two.level.list.TopLevelListKey;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal45.aug.norev.cont.cont.choice.ContAug;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal45.base.norev.Cont;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal45.base.norev.cont.ContChoice;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal45.base.norev.cont.cont.choice.ContBase;
+import org.opendaylight.yang.gen.v1.urn.test.opendaylight.mdsal45.base.norev.grp.GrpCont;
 import org.opendaylight.yangtools.yang.binding.Identifier;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -131,4 +136,44 @@ public class InstanceIdentifierSerializeDeserializeTest extends AbstractBindingR
         assertTrue(leafOnlyLastArg instanceof AugmentationIdentifier);
         assertTrue(((AugmentationIdentifier) leafOnlyLastArg).getPossibleChildNames().contains(SIMPLE_VALUE_QNAME));
     }
+
+    @Test
+    public void testChoiceCaseGroupingFromBinding() {
+        final YangInstanceIdentifier contBase = registry.toYangInstanceIdentifier(
+            InstanceIdentifier.builder(Cont.class).child(ContBase.class, GrpCont.class).build());
+        assertEquals(YangInstanceIdentifier.create(NodeIdentifier.create(Cont.QNAME),
+            NodeIdentifier.create(ContChoice.QNAME), NodeIdentifier.create(GrpCont.QNAME)), contBase);
+
+        final YangInstanceIdentifier contAug = registry.toYangInstanceIdentifier(
+            InstanceIdentifier.builder(Cont.class).child(ContAug.class, GrpCont.class).build());
+        assertEquals(YangInstanceIdentifier.create(NodeIdentifier.create(Cont.QNAME),
+            NodeIdentifier.create(ContChoice.QNAME),
+            NodeIdentifier.create(GrpCont.QNAME.withModule(ContAug.QNAME.getModule()))), contAug);
+
+        // Legacy: downcast the child to Class, losing type safety but still working. Faced with ambiguity, it will
+        //         select the lexically-lower class
+        assertEquals(1, ContBase.class.getCanonicalName().compareTo(ContAug.class.getCanonicalName()));
+        final YangInstanceIdentifier contAugLegacy = registry.toYangInstanceIdentifier(
+            InstanceIdentifier.builder(Cont.class).child((Class) GrpCont.class).build());
+        assertEquals(YangInstanceIdentifier.create(NodeIdentifier.create(Cont.QNAME),
+            NodeIdentifier.create(ContChoice.QNAME),
+            NodeIdentifier.create(GrpCont.QNAME.withModule(ContAug.QNAME.getModule()))), contAugLegacy);
+
+        // FIXME: root choice handling is busted
+        //      final YangInstanceIdentifier rootAugLegacy = registry.toYangInstanceIdentifier(
+        //          InstanceIdentifier.create((Class) GrpCont.class));
+    }
+
+    @Test
+    public void testChoiceCaseGroupingToBinding() {
+        final InstanceIdentifier<?> contBase = registry.fromYangInstanceIdentifier(
+            YangInstanceIdentifier.create(NodeIdentifier.create(Cont.QNAME),
+            NodeIdentifier.create(ContChoice.QNAME), NodeIdentifier.create(GrpCont.QNAME)));
+        assertEquals(InstanceIdentifier.builder(Cont.class).child(ContBase.class, GrpCont.class).build(), contBase);
+
+        final InstanceIdentifier<?> contAug = registry.fromYangInstanceIdentifier(
+            YangInstanceIdentifier.create(NodeIdentifier.create(Cont.QNAME), NodeIdentifier.create(ContChoice.QNAME),
+                NodeIdentifier.create(GrpCont.QNAME.withModule(ContAug.QNAME.getModule()))));
+        assertEquals(InstanceIdentifier.builder(Cont.class).child(ContAug.class, GrpCont.class).build(), contAug);
+    }
 }
diff --git a/binding/mdsal-binding-test-model/src/main/yang/opendaylight-mdsal45-aug.yang b/binding/mdsal-binding-test-model/src/main/yang/opendaylight-mdsal45-aug.yang
new file mode 100644 (file)
index 0000000..87a26b7
--- /dev/null
@@ -0,0 +1,19 @@
+module opendaylight-mdsal45-aug {
+    namespace "urn:test:opendaylight-mdsal45-aug";
+    prefix aug;
+
+    import opendaylight-mdsal45-base { prefix base; }
+
+    augment /base:cont/base:cont-choice {
+        case cont-aug {
+            uses base:grp;
+        }
+    }
+
+    augment /base:root {
+        case root-aug {
+            uses base:grp;
+        }
+    }
+}
+
diff --git a/binding/mdsal-binding-test-model/src/main/yang/opendaylight-mdsal45-base.yang b/binding/mdsal-binding-test-model/src/main/yang/opendaylight-mdsal45-base.yang
new file mode 100644 (file)
index 0000000..5da8eeb
--- /dev/null
@@ -0,0 +1,27 @@
+module opendaylight-mdsal45-base {
+    namespace "urn:test:opendaylight-mdsal45-base";
+    prefix base;
+
+    grouping grp {
+        container grp-cont {
+            leaf grp-leaf {
+                type string;
+            }
+        }
+    }
+
+    container cont {
+        choice cont-choice {
+            case cont-base {
+                uses grp;
+            }
+        }
+    }
+
+    choice root {
+        case root-base {
+            uses grp;
+        }
+    }
+}
+