Bug 4094: Fix DCNs on initial registration 49/26949/2
authorTom Pantelis <tpanteli@brocade.com>
Wed, 19 Aug 2015 02:07:52 +0000 (22:07 -0400)
committerGerrit Code Review <gerrit@opendaylight.org>
Tue, 15 Sep 2015 14:53:54 +0000 (14:53 +0000)
For DataChangeListener, I modified the code to use a
ResolveDataChangeEventsTask to resolve the initial changed event. It
was noted in Bug 4094 to read the registration path up to the first
wildcard. However this did not work. ResolveDataChangeEventsTask
expects the candidate root path and "after" data to be the tree root
to match the structure of the ListenerTree. When a transaction is
committed, the resulting DataTreeCandidate always points to the root.
So I had to read the root path in ShardDataTree. I could've optimized
for non-wildcarded path registrations but I think wildcarded path
registrations will be the norm anyway.

I added a new method, notifyOfInitialData, in ShardDataTree. Because I
had to create a new ListenerTree with the single registration, I
needed to know the path and scope of the original registration so I
changed several method signatures from the general ListenerRegistration
to the specific DataChangeListenerRegistration which provides access to
the path and scope. However we also have an actor class by that name so
to avoid confusion I renamed the actor class to
DataChangeListenerRegistrationActor.

DataTreeChangeListener was implemented similarly.

Change-Id: I0ab88d0991761c058b6af81d6d26402ff370b78e
Signed-off-by: Tom Pantelis <tpanteli@brocade.com>
(cherry picked from commit 0a7e13a8c7fed697800c792698cfef32b2ef0d11)

13 files changed:
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistrationActor.java [moved from opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistration.java with 89% similarity]
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerSupport.java
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataTreeChangeListenerSupport.java
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DelayedDataTreeListenerRegistration.java
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTree.java
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/AbstractShardTest.java
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistrationTest.java
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerSupportTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataTreeChangeListenerSupportTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/MockDataChangeListener.java
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/MockDataTreeChangeListener.java
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/TestModel.java
opendaylight/md-sal/sal-distributed-datastore/src/test/resources/odl-datastore-test.yang

@@ -20,12 +20,12 @@ import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 
-public class DataChangeListenerRegistration extends AbstractUntypedActor {
+public class DataChangeListenerRegistrationActor extends AbstractUntypedActor {
 
     private final ListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>>
         registration;
 
-    public DataChangeListenerRegistration(
+    public DataChangeListenerRegistrationActor(
         ListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>> registration) {
         this.registration = registration;
     }
@@ -52,7 +52,7 @@ public class DataChangeListenerRegistration extends AbstractUntypedActor {
     }
 
     private static class DataChangeListenerRegistrationCreator
-                                            implements Creator<DataChangeListenerRegistration> {
+                                            implements Creator<DataChangeListenerRegistrationActor> {
         private static final long serialVersionUID = 1L;
         final ListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier,
                                                            NormalizedNode<?, ?>>> registration;
@@ -64,8 +64,8 @@ public class DataChangeListenerRegistration extends AbstractUntypedActor {
         }
 
         @Override
-        public DataChangeListenerRegistration create() throws Exception {
-            return new DataChangeListenerRegistration(registration);
+        public DataChangeListenerRegistrationActor create() throws Exception {
+            return new DataChangeListenerRegistrationActor(registration);
         }
     }
 }
index c7b55414e6d8b8146cf8f00812860716baebf158..05accddb7885625a89d93d4bc8936bf763c267b1 100644 (file)
@@ -9,22 +9,25 @@ package org.opendaylight.controller.cluster.datastore;
 
 import akka.actor.ActorRef;
 import akka.actor.ActorSelection;
+import com.google.common.base.Optional;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map.Entry;
 import org.opendaylight.controller.cluster.datastore.messages.EnableNotification;
 import org.opendaylight.controller.cluster.datastore.messages.RegisterChangeListener;
 import org.opendaylight.controller.cluster.datastore.messages.RegisterChangeListenerReply;
-import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
-import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent;
+import org.opendaylight.controller.md.sal.dom.store.impl.DataChangeListenerRegistration;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
 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.DataTreeCandidate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-final class DataChangeListenerSupport extends LeaderLocalDelegateFactory<RegisterChangeListener, ListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>>, DOMImmutableDataChangeEvent> {
+final class DataChangeListenerSupport extends LeaderLocalDelegateFactory<RegisterChangeListener,
+        DataChangeListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>>,
+        Optional<DataTreeCandidate>> {
     private static final Logger LOG = LoggerFactory.getLogger(DataChangeListenerSupport.class);
     private final List<DelayedListenerRegistration> delayedListenerRegistrations = new ArrayList<>();
     private final List<ActorSelection> dataChangeListeners =  new ArrayList<>();
@@ -60,12 +63,10 @@ final class DataChangeListenerSupport extends LeaderLocalDelegateFactory<Registe
 
     private void registerDelayedListeners(DelayedListenerRegistration reg) {
         if(!reg.isClosed()) {
-            final Entry<ListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>>, DOMImmutableDataChangeEvent> res =
-                createDelegate(reg.getRegisterChangeListener());
+            final Entry<DataChangeListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>>,
+                            Optional<DataTreeCandidate>> res = createDelegate(reg.getRegisterChangeListener());
             reg.setDelegate(res.getKey());
-            if (res.getValue() != null) {
-                reg.getInstance().onDataChanged(res.getValue());
-            }
+            getShard().getDataStore().notifyOfInitialData(res.getKey(), res.getValue());
         }
     }
 
@@ -77,12 +78,12 @@ final class DataChangeListenerSupport extends LeaderLocalDelegateFactory<Registe
 
         final ListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier,
                                                      NormalizedNode<?, ?>>> registration;
-        final AsyncDataChangeEvent<YangInstanceIdentifier, NormalizedNode<?, ?>> event;
         if ((hasLeader && message.isRegisterOnAllInstances()) || isLeader) {
-            final Entry<ListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>>, DOMImmutableDataChangeEvent> res =
-                    createDelegate(message);
+            final Entry<DataChangeListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>>,
+                    Optional<DataTreeCandidate>> res = createDelegate(message);
             registration = res.getKey();
-            event = res.getValue();
+
+            getShard().getDataStore().notifyOfInitialData(res.getKey(), res.getValue());
         } else {
             LOG.debug("{}: Shard is not the leader - delaying registration", persistenceId());
 
@@ -93,23 +94,19 @@ final class DataChangeListenerSupport extends LeaderLocalDelegateFactory<Registe
                 delayedListenerRegistrations.add(delayedReg);
             }
             registration = delayedReg;
-            event = null;
         }
 
-        ActorRef listenerRegistration = createActor(DataChangeListenerRegistration.props(registration));
+        ActorRef listenerRegistration = createActor(DataChangeListenerRegistrationActor.props(registration));
 
         LOG.debug("{}: registerDataChangeListener sending reply, listenerRegistrationPath = {} ",
                 persistenceId(), listenerRegistration.path());
 
         tellSender(new RegisterChangeListenerReply(listenerRegistration));
-        if (event != null) {
-            registration.getInstance().onDataChanged(event);
-        }
     }
 
     @Override
-    Entry<ListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>>, DOMImmutableDataChangeEvent> createDelegate(
-            final RegisterChangeListener message) {
+    Entry<DataChangeListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>>,
+            Optional<DataTreeCandidate>> createDelegate(final RegisterChangeListener message) {
         ActorSelection dataChangeListenerPath = selectActor(message.getDataChangeListenerPath());
 
         // Notify the listener if notifications should be enabled or not
index 4281bfe796afd1e1844286b4ae854aa57de8dab8..76458fd8edd1471a87cf278d66f4e9d000c26d61 100644 (file)
@@ -9,9 +9,9 @@ package org.opendaylight.controller.cluster.datastore;
 
 import akka.actor.ActorRef;
 import akka.actor.ActorSelection;
+import com.google.common.base.Optional;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Map.Entry;
 import org.opendaylight.controller.cluster.datastore.messages.EnableNotification;
 import org.opendaylight.controller.cluster.datastore.messages.RegisterDataTreeChangeListener;
@@ -22,7 +22,8 @@ import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-final class DataTreeChangeListenerSupport extends LeaderLocalDelegateFactory<RegisterDataTreeChangeListener, ListenerRegistration<DOMDataTreeChangeListener>, DataTreeCandidate> {
+final class DataTreeChangeListenerSupport extends LeaderLocalDelegateFactory<RegisterDataTreeChangeListener,
+        ListenerRegistration<DOMDataTreeChangeListener>, Optional<DataTreeCandidate>> {
     private static final Logger LOG = LoggerFactory.getLogger(DataTreeChangeListenerSupport.class);
     private final ArrayList<DelayedDataTreeListenerRegistration> delayedRegistrations = new ArrayList<>();
     private final Collection<ActorSelection> actors = new ArrayList<>();
@@ -33,6 +34,11 @@ final class DataTreeChangeListenerSupport extends LeaderLocalDelegateFactory<Reg
 
     @Override
     void onLeadershipChange(final boolean isLeader, boolean hasLeader) {
+        final EnableNotification msg = new EnableNotification(isLeader);
+        for (ActorSelection dataChangeListener : actors) {
+            dataChangeListener.tell(msg, getSelf());
+        }
+
         if (isLeader) {
             for (DelayedDataTreeListenerRegistration reg : delayedRegistrations) {
                 reg.createDelegate(this);
@@ -40,11 +46,6 @@ final class DataTreeChangeListenerSupport extends LeaderLocalDelegateFactory<Reg
             delayedRegistrations.clear();
             delayedRegistrations.trimToSize();
         }
-
-        final EnableNotification msg = new EnableNotification(isLeader);
-        for (ActorSelection dataChangeListener : actors) {
-            dataChangeListener.tell(msg, getSelf());
-        }
     }
 
     @Override
@@ -52,7 +53,6 @@ final class DataTreeChangeListenerSupport extends LeaderLocalDelegateFactory<Reg
         LOG.debug("{}: registerTreeChangeListener for {}, leader: {}", persistenceId(), registerTreeChangeListener.getPath(), isLeader);
 
         final ListenerRegistration<DOMDataTreeChangeListener> registration;
-        final DataTreeCandidate event;
         if (!isLeader) {
             LOG.debug("{}: Shard is not the leader - delaying registration", persistenceId());
 
@@ -60,11 +60,12 @@ final class DataTreeChangeListenerSupport extends LeaderLocalDelegateFactory<Reg
                     new DelayedDataTreeListenerRegistration(registerTreeChangeListener);
             delayedRegistrations.add(delayedReg);
             registration = delayedReg;
-            event = null;
         } else {
-            final Entry<ListenerRegistration<DOMDataTreeChangeListener>, DataTreeCandidate> res = createDelegate(registerTreeChangeListener);
+            final Entry<ListenerRegistration<DOMDataTreeChangeListener>, Optional<DataTreeCandidate>> res =
+                    createDelegate(registerTreeChangeListener);
             registration = res.getKey();
-            event = res.getValue();
+            getShard().getDataStore().notifyOfInitialData(registerTreeChangeListener.getPath(),
+                    registration.getInstance(), res.getValue());
         }
 
         ActorRef listenerRegistration = createActor(DataTreeChangeListenerRegistrationActor.props(registration));
@@ -73,13 +74,10 @@ final class DataTreeChangeListenerSupport extends LeaderLocalDelegateFactory<Reg
             persistenceId(), listenerRegistration.path());
 
         tellSender(new RegisterDataTreeChangeListenerReply(listenerRegistration));
-        if (event != null) {
-            registration.getInstance().onDataTreeChanged(Collections.singletonList(event));
-        }
     }
 
     @Override
-    Entry<ListenerRegistration<DOMDataTreeChangeListener>, DataTreeCandidate> createDelegate(final RegisterDataTreeChangeListener message) {
+    Entry<ListenerRegistration<DOMDataTreeChangeListener>, Optional<DataTreeCandidate>> createDelegate(final RegisterDataTreeChangeListener message) {
         ActorSelection dataChangeListenerPath = selectActor(message.getDataTreeChangeListenerPath());
 
         // Notify the listener if notifications should be enabled or not
index e8cd31097b872d222d6f886c173b362de26fb804..958ccc4438be135a8355bcd65f8841c2159510a9 100644 (file)
@@ -7,8 +7,8 @@
  */
 package org.opendaylight.controller.cluster.datastore;
 
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
-import java.util.Collections;
 import java.util.Map.Entry;
 import javax.annotation.concurrent.GuardedBy;
 import org.opendaylight.controller.cluster.datastore.messages.RegisterDataTreeChangeListener;
@@ -31,13 +31,13 @@ final class DelayedDataTreeListenerRegistration implements ListenerRegistration<
         this.registerTreeChangeListener = Preconditions.checkNotNull(registerTreeChangeListener);
     }
 
-    synchronized void createDelegate(final DelegateFactory<RegisterDataTreeChangeListener, ListenerRegistration<DOMDataTreeChangeListener>, DataTreeCandidate> factory) {
+    synchronized void createDelegate(final LeaderLocalDelegateFactory<RegisterDataTreeChangeListener, ListenerRegistration<DOMDataTreeChangeListener>, Optional<DataTreeCandidate>> factory) {
         if (!closed) {
-            final Entry<ListenerRegistration<DOMDataTreeChangeListener>, DataTreeCandidate> res = factory.createDelegate(registerTreeChangeListener);
+            final Entry<ListenerRegistration<DOMDataTreeChangeListener>, Optional<DataTreeCandidate>> res =
+                    factory.createDelegate(registerTreeChangeListener);
             this.delegate = res.getKey();
-            if (res.getValue() != null) {
-                delegate.getInstance().onDataTreeChanged(Collections.singletonList(res.getValue()));
-            }
+            factory.getShard().getDataStore().notifyOfInitialData(registerTreeChangeListener.getPath(),
+                    this.delegate.getInstance(), res.getValue());
         }
     }
 
index c167ec630637fc3ac6e98401edbd397ed9702e4d..18f0cddf179b2ed010123c6eaf9f65f320314049 100644 (file)
@@ -18,7 +18,7 @@ import javax.annotation.concurrent.NotThreadSafe;
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener;
-import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent;
+import org.opendaylight.controller.md.sal.dom.store.impl.DataChangeListenerRegistration;
 import org.opendaylight.controller.md.sal.dom.store.impl.ResolveDataChangeEventsTask;
 import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
@@ -44,6 +44,7 @@ import org.slf4j.LoggerFactory;
 @NotThreadSafe
 public class ShardDataTree extends ShardDataTreeTransactionParent {
     private static final Logger LOG = LoggerFactory.getLogger(ShardDataTree.class);
+    private static final YangInstanceIdentifier ROOT_PATH = YangInstanceIdentifier.builder().build();
     private static final ShardDataTreeNotificationManager MANAGER = new ShardDataTreeNotificationManager();
     private final Map<String, ShardDataTreeTransactionChain> transactionChains = new HashMap<>();
     private final ShardDataTreeChangePublisher treeChangePublisher = new ShardDataTreeChangePublisher();
@@ -108,6 +109,27 @@ public class ShardDataTree extends ShardDataTreeTransactionParent {
         ResolveDataChangeEventsTask.create(candidate, listenerTree).resolve(MANAGER);
     }
 
+    void notifyOfInitialData(DataChangeListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier,
+            NormalizedNode<?, ?>>> listenerReg, Optional<DataTreeCandidate> currentState) {
+
+        if(currentState.isPresent()) {
+            ListenerTree localListenerTree = ListenerTree.create();
+            localListenerTree.registerDataChangeListener(listenerReg.getPath(), listenerReg.getInstance(),
+                    listenerReg.getScope());
+
+            ResolveDataChangeEventsTask.create(currentState.get(), localListenerTree).resolve(MANAGER);
+        }
+    }
+
+    void notifyOfInitialData(final YangInstanceIdentifier path, final DOMDataTreeChangeListener listener,
+            final Optional<DataTreeCandidate> currentState) {
+        if(currentState.isPresent()) {
+            ShardDataTreeChangePublisher localTreeChangePublisher = new ShardDataTreeChangePublisher();
+            localTreeChangePublisher.registerTreeChangeListener(path, listener);
+            localTreeChangePublisher.publishChanges(currentState.get());
+        }
+    }
+
     void closeAllTransactionChains() {
         for (ShardDataTreeTransactionChain chain : transactionChains.values()) {
             chain.close();
@@ -125,36 +147,28 @@ public class ShardDataTree extends ShardDataTreeTransactionParent {
         }
     }
 
-    Entry<ListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>>, DOMImmutableDataChangeEvent> registerChangeListener(
-            final YangInstanceIdentifier path,
-            final AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>> listener, final DataChangeScope scope) {
-        final ListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>> reg =
+    Entry<DataChangeListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>>,
+            Optional<DataTreeCandidate>> registerChangeListener(final YangInstanceIdentifier path,
+                    final AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>> listener,
+                    final DataChangeScope scope) {
+        final DataChangeListenerRegistration<AsyncDataChangeListener<YangInstanceIdentifier, NormalizedNode<?, ?>>> reg =
                 listenerTree.registerDataChangeListener(path, listener, scope);
 
-        final Optional<NormalizedNode<?, ?>> currentState = dataTree.takeSnapshot().readNode(path);
-        final DOMImmutableDataChangeEvent event;
-        if (currentState.isPresent()) {
-            final NormalizedNode<?, ?> data = currentState.get();
-            event = DOMImmutableDataChangeEvent.builder(DataChangeScope.BASE).setAfter(data).addCreated(path, data).build();
-        } else {
-            event = null;
-        }
+        return new SimpleEntry<>(reg, readCurrentData());
+    }
 
-        return new SimpleEntry<>(reg, event);
+    private Optional<DataTreeCandidate> readCurrentData() {
+        final Optional<NormalizedNode<?, ?>> currentState = dataTree.takeSnapshot().readNode(ROOT_PATH);
+        return currentState.isPresent() ? Optional.of(DataTreeCandidates.fromNormalizedNode(
+                ROOT_PATH, currentState.get())) : Optional.<DataTreeCandidate>absent();
     }
 
-    public Entry<ListenerRegistration<DOMDataTreeChangeListener>, DataTreeCandidate> registerTreeChangeListener(final YangInstanceIdentifier path,
-            final DOMDataTreeChangeListener listener) {
-        final ListenerRegistration<DOMDataTreeChangeListener> reg = treeChangePublisher.registerTreeChangeListener(path, listener);
+    public Entry<ListenerRegistration<DOMDataTreeChangeListener>, Optional<DataTreeCandidate>> registerTreeChangeListener(
+            final YangInstanceIdentifier path, final DOMDataTreeChangeListener listener) {
+        final ListenerRegistration<DOMDataTreeChangeListener> reg = treeChangePublisher.registerTreeChangeListener(
+                path, listener);
 
-        final Optional<NormalizedNode<?, ?>> currentState = dataTree.takeSnapshot().readNode(path);
-        final DataTreeCandidate event;
-        if (currentState.isPresent()) {
-            event = DataTreeCandidates.fromNormalizedNode(path, currentState.get());
-        } else {
-            event = null;
-        }
-        return new SimpleEntry<>(reg, event);
+        return new SimpleEntry<>(reg, readCurrentData());
     }
 
     void applyForeignCandidate(final String identifier, final DataTreeCandidate foreign) throws DataValidationFailedException {
index 97c9e923a9b656eb067242f1c27bb8a121dd13e8..3f0a1bfc861ab34b9499bc47a2da7af345f4f136 100644 (file)
@@ -271,6 +271,17 @@ public abstract class AbstractShardTest extends AbstractActorTest{
         cohort.commit();
     }
 
+    public static void mergeToStore(final ShardDataTree store, final YangInstanceIdentifier id,
+            final NormalizedNode<?,?> node) throws InterruptedException, ExecutionException {
+        final ReadWriteShardDataTreeTransaction transaction = store.newReadWriteTransaction("writeToStore", null);
+
+        transaction.getSnapshot().merge(id, node);
+        final ShardDataTreeCohort cohort = transaction.ready();
+        cohort.canCommit().get();
+        cohort.preCommit().get();
+        cohort.commit();
+    }
+
     public static void writeToStore(final DataTree store, final YangInstanceIdentifier id,
             final NormalizedNode<?,?> node) throws DataValidationFailedException {
         final DataTreeModification transaction = store.takeSnapshot().newModification();
index 19b051e4b6119c9bef1d3ffa327934cdcf5caf1d..ff801364690e09032873ee30e13c6856c71a1e10 100644 (file)
@@ -35,7 +35,7 @@ public class DataChangeListenerRegistrationTest extends AbstractActorTest {
   @Test
   public void testOnReceiveCloseListenerRegistration() throws Exception {
     new JavaTestKit(getSystem()) {{
-      final Props props = DataChangeListenerRegistration.props(store
+      final Props props = DataChangeListenerRegistrationActor.props(store
           .registerChangeListener(TestModel.TEST_PATH, noOpDataChangeListener(),
               AsyncDataBroker.DataChangeScope.BASE));
       final ActorRef subject = getSystem().actorOf(props, "testCloseListenerRegistration");
diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerSupportTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerSupportTest.java
new file mode 100644 (file)
index 0000000..ac6f801
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.cluster.datastore;
+
+import static org.junit.Assert.assertEquals;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.INNER_LIST_QNAME;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.OUTER_CONTAINER_PATH;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.OUTER_CONTAINER_QNAME;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.OUTER_LIST_PATH;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.OUTER_LIST_QNAME;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.TEST_PATH;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.TEST_QNAME;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.innerEntryPath;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.innerNode;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.outerEntryKey;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.outerEntryPath;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.outerNode;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.outerNodeEntry;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.testNodeWithOuter;
+import akka.actor.ActorRef;
+import akka.testkit.TestActorRef;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.cluster.datastore.messages.RegisterChangeListener;
+import org.opendaylight.controller.cluster.datastore.utils.MockDataChangeListener;
+import org.opendaylight.controller.cluster.raft.TestActorFactory;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+
+/**
+ * Unit tests for DataChangeListenerSupport.
+ *
+ * @author Thomas Pantelis
+ */
+public class DataChangeListenerSupportTest extends AbstractShardTest {
+    private final TestActorFactory actorFactory = new TestActorFactory(getSystem());
+
+    private Shard shard;
+    private DataChangeListenerSupport support;
+
+    @Before
+    public void setup() {
+        shard = createShard();
+        support = new DataChangeListenerSupport(shard);
+    }
+
+    @Override
+    @After
+    public void tearDown() {
+        super.tearDown();
+        actorFactory.close();
+    }
+
+    @Test
+    public void testChangeListenerWithNoInitialData() throws Exception {
+        MockDataChangeListener listener = registerChangeListener(TEST_PATH, DataChangeScope.ONE, 0, true);
+
+        listener.expectNoMoreChanges("Unexpected initial change event");
+    }
+
+    @Test
+    public void testInitialChangeListenerEventWithContainerPath() throws Exception {
+        writeToStore(shard.getDataStore(), TEST_PATH, ImmutableNodes.containerNode(TEST_QNAME));
+
+        MockDataChangeListener listener = registerChangeListener(TEST_PATH, DataChangeScope.ONE, 1, true);
+
+        listener.waitForChangeEvents(TEST_PATH);
+    }
+
+    @Test
+    public void testInitialChangeListenerEventWithListPath() throws Exception {
+        mergeToStore(shard.getDataStore(), TEST_PATH, testNodeWithOuter(1, 2));
+
+        MockDataChangeListener listener = registerChangeListener(OUTER_LIST_PATH, DataChangeScope.ONE, 1, true);
+
+        listener.waitForChangeEvents();
+        assertEquals("Outer entry 1 present", true, NormalizedNodes.findNode(
+                listener.getCreatedData(0, OUTER_LIST_PATH), outerEntryKey(1)).isPresent());
+        assertEquals("Outer entry 2 present", true, NormalizedNodes.findNode(
+                listener.getCreatedData(0, OUTER_LIST_PATH), outerEntryKey(2)).isPresent());
+    }
+
+    @Test
+    public void testInitialChangeListenerEventWithWildcardedListPath() throws Exception {
+        mergeToStore(shard.getDataStore(), TEST_PATH, testNodeWithOuter(1, 2));
+        writeToStore(shard.getDataStore(), OUTER_CONTAINER_PATH, ImmutableNodes.containerNode(OUTER_CONTAINER_QNAME));
+
+        MockDataChangeListener listener = registerChangeListener(OUTER_LIST_PATH.node(OUTER_LIST_QNAME),
+                DataChangeScope.ONE, 1, true);
+
+        listener.waitForChangeEvents();
+        listener.verifyCreatedData(0, outerEntryPath(1));
+        listener.verifyCreatedData(0, outerEntryPath(2));
+        listener.verifyNoCreatedData(0, OUTER_CONTAINER_PATH);
+    }
+
+    @Test
+    public void testInitialChangeListenerEventWithNestedWildcardedListsPath() throws Exception {
+        mergeToStore(shard.getDataStore(), TEST_PATH, testNodeWithOuter(outerNode(
+                outerNodeEntry(1, innerNode("one", "two")), outerNodeEntry(2, innerNode("three", "four")))));
+
+        MockDataChangeListener listener = registerChangeListener(
+                OUTER_LIST_PATH.node(OUTER_LIST_QNAME).node(INNER_LIST_QNAME).node(INNER_LIST_QNAME),
+                DataChangeScope.ONE, 1, true);
+
+        listener.waitForChangeEvents();
+        listener.verifyCreatedData(0, innerEntryPath(1, "one"));
+        listener.verifyCreatedData(0, innerEntryPath(1, "two"));
+        listener.verifyCreatedData(0, innerEntryPath(2, "three"));
+        listener.verifyCreatedData(0, innerEntryPath(2, "four"));
+
+        // Register for a specific outer list entry
+
+        MockDataChangeListener listener2 = registerChangeListener(
+                OUTER_LIST_PATH.node(outerEntryKey(1)).node(INNER_LIST_QNAME).node(INNER_LIST_QNAME),
+                DataChangeScope.ONE, 1, true);
+
+        listener2.waitForChangeEvents();
+        listener2.verifyCreatedData(0, innerEntryPath(1, "one"));
+        listener2.verifyCreatedData(0, innerEntryPath(1, "two"));
+        listener2.verifyNoCreatedData(0, innerEntryPath(2, "three"));
+        listener2.verifyNoCreatedData(0, innerEntryPath(2, "four"));
+    }
+
+    @Test
+    public void testInitialChangeListenerEventWhenNotInitiallyLeader() throws Exception {
+        mergeToStore(shard.getDataStore(), TEST_PATH, testNodeWithOuter(outerNode(
+                outerNodeEntry(1, innerNode("one", "two")), outerNodeEntry(2, innerNode("three", "four")))));
+
+        MockDataChangeListener listener = registerChangeListener(
+                OUTER_LIST_PATH.node(OUTER_LIST_QNAME).node(INNER_LIST_QNAME).node(INNER_LIST_QNAME),
+                DataChangeScope.ONE, 0, false);
+
+        listener.expectNoMoreChanges("Unexpected initial change event");
+        listener.reset(1);
+
+        support.onLeadershipChange(true, true);
+
+        listener.waitForChangeEvents();
+        listener.verifyCreatedData(0, innerEntryPath(1, "one"));
+        listener.verifyCreatedData(0, innerEntryPath(1, "two"));
+        listener.verifyCreatedData(0, innerEntryPath(2, "three"));
+        listener.verifyCreatedData(0, innerEntryPath(2, "four"));
+    }
+
+    private MockDataChangeListener registerChangeListener(YangInstanceIdentifier path, DataChangeScope scope,
+            int expectedEvents, boolean isLeader) {
+        MockDataChangeListener listener = new MockDataChangeListener(expectedEvents);
+        ActorRef dclActor = actorFactory.createActor(DataChangeListener.props(listener));
+
+        support.onMessage(new RegisterChangeListener(path, dclActor, scope, false), isLeader, true);
+        return listener;
+    }
+
+    private Shard createShard() {
+        TestActorRef<Shard> actor = actorFactory.createTestActor(newShardProps());
+        return actor.underlyingActor();
+    }
+}
diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataTreeChangeListenerSupportTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataTreeChangeListenerSupportTest.java
new file mode 100644 (file)
index 0000000..c19f609
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.cluster.datastore;
+
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.INNER_LIST_QNAME;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.OUTER_LIST_PATH;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.OUTER_LIST_QNAME;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.TEST_PATH;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.TEST_QNAME;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.innerEntryPath;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.innerNode;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.outerEntryKey;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.outerEntryPath;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.outerNode;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.outerNodeEntry;
+import static org.opendaylight.controller.md.cluster.datastore.model.TestModel.testNodeWithOuter;
+import akka.actor.ActorRef;
+import akka.testkit.TestActorRef;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.cluster.datastore.messages.RegisterDataTreeChangeListener;
+import org.opendaylight.controller.cluster.datastore.utils.MockDataTreeChangeListener;
+import org.opendaylight.controller.cluster.raft.TestActorFactory;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+
+/**
+ * Unit tests for DataTreeChangeListenerSupport.
+ *
+ * @author Thomas Pantelis
+ */
+public class DataTreeChangeListenerSupportTest extends AbstractShardTest {
+    private final TestActorFactory actorFactory = new TestActorFactory(getSystem());
+
+    private Shard shard;
+    private DataTreeChangeListenerSupport support;
+
+    @Before
+    public void setup() {
+        shard = createShard();
+        support = new DataTreeChangeListenerSupport(shard);
+    }
+
+    @Override
+    @After
+    public void tearDown() {
+        super.tearDown();
+        actorFactory.close();
+    }
+
+    @Test
+    public void testChangeListenerWithNoInitialData() throws Exception {
+        MockDataTreeChangeListener listener = registerChangeListener(TEST_PATH, 0, true);
+
+        listener.expectNoMoreChanges("Unexpected initial change event");
+    }
+
+    @Test
+    public void testInitialChangeListenerEventWithContainerPath() throws Exception {
+        writeToStore(shard.getDataStore(), TEST_PATH, ImmutableNodes.containerNode(TEST_QNAME));
+
+        MockDataTreeChangeListener listener = registerChangeListener(TEST_PATH, 1, true);
+
+        listener.waitForChangeEvents();
+        listener.verifyNotifiedData(TEST_PATH);
+    }
+
+    @Test
+    public void testInitialChangeListenerEventWithListPath() throws Exception {
+        mergeToStore(shard.getDataStore(), TEST_PATH, testNodeWithOuter(1, 2));
+
+        MockDataTreeChangeListener listener = registerChangeListener(OUTER_LIST_PATH, 1, true);
+
+        listener.waitForChangeEvents();
+        listener.verifyNotifiedData(OUTER_LIST_PATH);
+    }
+
+    @Test
+    public void testInitialChangeListenerEventWithWildcardedListPath() throws Exception {
+        mergeToStore(shard.getDataStore(), TEST_PATH, testNodeWithOuter(1, 2));
+
+        MockDataTreeChangeListener listener = registerChangeListener(OUTER_LIST_PATH.node(OUTER_LIST_QNAME), 2, true);
+
+        listener.waitForChangeEvents();
+        listener.verifyNotifiedData(outerEntryPath(1), outerEntryPath(2));
+    }
+
+    @Test
+    public void testInitialChangeListenerEventWithNestedWildcardedListsPath() throws Exception {
+        mergeToStore(shard.getDataStore(), TEST_PATH, testNodeWithOuter(outerNode(
+                outerNodeEntry(1, innerNode("one", "two")), outerNodeEntry(2, innerNode("three", "four")))));
+
+        MockDataTreeChangeListener listener = registerChangeListener(
+                OUTER_LIST_PATH.node(OUTER_LIST_QNAME).node(INNER_LIST_QNAME).node(INNER_LIST_QNAME), 4, true);
+
+        listener.waitForChangeEvents();
+        listener.verifyNotifiedData(innerEntryPath(1, "one"), innerEntryPath(1, "two"), innerEntryPath(2, "three"),
+                innerEntryPath(2, "four"));
+
+        // Register for a specific outer list entry
+
+        MockDataTreeChangeListener listener2 = registerChangeListener(
+                OUTER_LIST_PATH.node(outerEntryKey(1)).node(INNER_LIST_QNAME).node(INNER_LIST_QNAME), 2, true);
+
+        listener2.waitForChangeEvents();
+        listener2.verifyNotifiedData(innerEntryPath(1, "one"), innerEntryPath(1, "two"));
+        listener2.verifyNoNotifiedData(innerEntryPath(2, "three"), innerEntryPath(2, "four"));
+    }
+
+    private MockDataTreeChangeListener registerChangeListener(YangInstanceIdentifier path,
+            int expectedEvents, boolean isLeader) {
+        MockDataTreeChangeListener listener = new MockDataTreeChangeListener(expectedEvents);
+        ActorRef dclActor = actorFactory.createActor(DataTreeChangeListenerActor.props(listener));
+        support.onMessage(new RegisterDataTreeChangeListener(path, dclActor), isLeader, true);
+        return listener;
+    }
+
+    private Shard createShard() {
+        TestActorRef<Shard> actor = actorFactory.createTestActor(newShardProps());
+        return actor.underlyingActor();
+    }
+}
index efd58620a23501b114e80a15084406a004ed4179..24664be0449114d579c943d7ace289c733160dc4 100644 (file)
@@ -14,6 +14,7 @@ import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.Uninterruptibles;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
@@ -60,13 +61,28 @@ public class MockDataChangeListener implements
         }
 
         for(int i = 0; i < expPaths.length; i++) {
-            assertTrue(String.format("Change %d does not contain %s", (i+1), expPaths[i]),
-                    changeList.get(i).getCreatedData().containsKey(expPaths[i]));
+            Map<YangInstanceIdentifier, NormalizedNode<?, ?>> createdData = changeList.get(i).getCreatedData();
+            assertTrue(String.format("Change %d does not contain %s. Actual: %s", (i+1), expPaths[i], createdData),
+                    createdData.containsKey(expPaths[i]));
         }
     }
 
+    public NormalizedNode<?, ?> getCreatedData(int i, YangInstanceIdentifier path) {
+        return changeList.get(i).getCreatedData().get(path);
+    }
+
+    public void verifyCreatedData(int i, YangInstanceIdentifier path) {
+        Map<YangInstanceIdentifier, NormalizedNode<?, ?>> createdData = changeList.get(i).getCreatedData();
+        assertTrue(path + " not present in " + createdData.keySet(), createdData.get(path) != null);
+    }
+
     public void expectNoMoreChanges(String assertMsg) {
-        Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
+        Uninterruptibles.sleepUninterruptibly(500, TimeUnit.MILLISECONDS);
         assertEquals(assertMsg, expChangeEventCount, changeList.size());
     }
+
+    public void verifyNoCreatedData(int i, YangInstanceIdentifier path) {
+        Map<YangInstanceIdentifier, NormalizedNode<?, ?>> createdData = changeList.get(i).getCreatedData();
+        assertTrue("Unexpected " + path + " present in createdData", createdData.get(path) == null);
+    }
 }
index d06fc435720fca6d40f0b7cb54e33f64b8307c8a..cf188b339e2d5b3c420634919b6e46b8b2ba2d05 100644 (file)
@@ -7,20 +7,23 @@
  */
 package org.opendaylight.controller.cluster.datastore.utils;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
 import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.Uninterruptibles;
-import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
-
-import javax.annotation.Nonnull;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
+import javax.annotation.Nonnull;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
 
 public class MockDataTreeChangeListener implements DOMDataTreeChangeListener {
 
@@ -54,8 +57,31 @@ public class MockDataTreeChangeListener implements DOMDataTreeChangeListener {
         }
     }
 
+    public void verifyNotifiedData(YangInstanceIdentifier... paths) {
+        Set<YangInstanceIdentifier> pathSet = new HashSet<>(Arrays.asList(paths));
+        for(Collection<DataTreeCandidate> list: changeList) {
+            for(DataTreeCandidate c: list) {
+                pathSet.remove(c.getRootPath());
+            }
+        }
+
+        if(!pathSet.isEmpty()) {
+            fail(pathSet + " not present in " + changeList);
+        }
+    }
+
     public void expectNoMoreChanges(String assertMsg) {
-        Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
+        Uninterruptibles.sleepUninterruptibly(500, TimeUnit.MILLISECONDS);
         assertEquals(assertMsg, expChangeEventCount, changeList.size());
     }
+
+    public void verifyNoNotifiedData(YangInstanceIdentifier... paths) {
+        Set<YangInstanceIdentifier> pathSet = new HashSet<>(Arrays.asList(paths));
+        for(Collection<DataTreeCandidate> list: changeList) {
+            for(DataTreeCandidate c: list) {
+                assertFalse("Unexpected " + c.getRootPath() + " present in DataTreeCandidate",
+                        pathSet.contains(c.getRootPath()));
+            }
+        }
+    }
 }
index c4fb9a11dd0a9b4bdbd9129da1ba0cd4cf759ae4..86c0f110bcf7861802c39fc268ed76f56d20f0d9 100644 (file)
@@ -13,6 +13,14 @@ import java.io.InputStream;
 import java.util.Collections;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+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.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException;
 import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl;
@@ -30,6 +38,7 @@ public class TestModel {
 
 
     public static final QName OUTER_LIST_QNAME = QName.create(TEST_QNAME, "outer-list");
+    public static final QName OUTER_CONTAINER_QNAME = QName.create(TEST_QNAME, "outer-container");
     public static final QName INNER_LIST_QNAME = QName.create(TEST_QNAME, "inner-list");
     public static final QName OUTER_CHOICE_QNAME = QName.create(TEST_QNAME, "outer-choice");
     public static final QName ID_QNAME = QName.create(TEST_QNAME, "id");
@@ -44,6 +53,7 @@ public class TestModel {
             node(OUTER_LIST_QNAME).build();
     public static final YangInstanceIdentifier INNER_LIST_PATH = YangInstanceIdentifier.builder(TEST_PATH).
             node(OUTER_LIST_QNAME).node(INNER_LIST_QNAME).build();
+    public static final YangInstanceIdentifier OUTER_CONTAINER_PATH = TEST_PATH.node(OUTER_CONTAINER_QNAME);
     public static final QName TWO_QNAME = QName.create(TEST_QNAME,"two");
     public static final QName THREE_QNAME = QName.create(TEST_QNAME,"three");
 
@@ -60,4 +70,60 @@ public class TestModel {
             throw new ExceptionInInitializerError(e);
         }
     }
+
+    public static DataContainerChild<?, ?> outerNode(int... ids) {
+        CollectionNodeBuilder<MapEntryNode, MapNode> outer = ImmutableNodes.mapNodeBuilder(OUTER_LIST_QNAME);
+        for(int id: ids) {
+            outer.addChild(ImmutableNodes.mapEntry(OUTER_LIST_QNAME, ID_QNAME, id));
+        }
+
+        return outer.build();
+    }
+
+    public static DataContainerChild<?, ?> outerNode(MapEntryNode... entries) {
+        CollectionNodeBuilder<MapEntryNode, MapNode> outer = ImmutableNodes.mapNodeBuilder(OUTER_LIST_QNAME);
+        for(MapEntryNode e: entries) {
+            outer.addChild(e);
+        }
+
+        return outer.build();
+    }
+
+    public static DataContainerChild<?, ?> innerNode(String... names) {
+        CollectionNodeBuilder<MapEntryNode, MapNode> outer = ImmutableNodes.mapNodeBuilder(INNER_LIST_QNAME);
+        for(String name: names) {
+            outer.addChild(ImmutableNodes.mapEntry(INNER_LIST_QNAME, NAME_QNAME, name));
+        }
+
+        return outer.build();
+    }
+
+    public static MapEntryNode outerNodeEntry(int id, DataContainerChild<?, ?> inner) {
+        return ImmutableNodes.mapEntryBuilder(OUTER_LIST_QNAME, ID_QNAME, id).addChild(inner).build();
+    }
+
+    public static NormalizedNode<?, ?> testNodeWithOuter(int... ids) {
+        return testNodeWithOuter(outerNode(ids));
+    }
+
+    public static NormalizedNode<?, ?> testNodeWithOuter(DataContainerChild<?, ?> outer) {
+        return ImmutableContainerNodeBuilder.create().withNodeIdentifier(
+                new YangInstanceIdentifier.NodeIdentifier(TEST_QNAME)).withChild(outer).build();
+    }
+
+    public static NodeIdentifierWithPredicates outerEntryKey(int id) {
+        return new NodeIdentifierWithPredicates(OUTER_LIST_QNAME, ID_QNAME, id);
+    }
+
+    public static YangInstanceIdentifier outerEntryPath(int id) {
+        return OUTER_LIST_PATH.node(outerEntryKey(id));
+    }
+
+    public static NodeIdentifierWithPredicates innerEntryKey(String name) {
+        return new NodeIdentifierWithPredicates(INNER_LIST_QNAME, NAME_QNAME, name);
+    }
+
+    public static YangInstanceIdentifier innerEntryPath(int id, String name) {
+        return OUTER_LIST_PATH.node(outerEntryKey(id)).node(INNER_LIST_QNAME).node(innerEntryKey(name));
+    }
 }
index 98c7bb453d94b6279c4cdc302c668fafddcadb18..0834c73f64bf963fa5d6b42549ff386f521c022d 100644 (file)
@@ -46,6 +46,9 @@ module odl-datastore-test {
                 }
             }
         }
+        
+        container outer-container {
+        }
     }
     
     container test2 {