From d9ea4400df226eb65c964ab0cb2aa81ee495ba15 Mon Sep 17 00:00:00 2001 From: Tom Pantelis Date: Tue, 18 Aug 2015 22:07:52 -0400 Subject: [PATCH] Bug 4094: Fix DCNs on initial registration 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 (cherry picked from commit 0a7e13a8c7fed697800c792698cfef32b2ef0d11) --- ... DataChangeListenerRegistrationActor.java} | 10 +- .../datastore/DataChangeListenerSupport.java | 35 ++-- .../DataTreeChangeListenerSupport.java | 28 ++- .../DelayedDataTreeListenerRegistration.java | 12 +- .../cluster/datastore/ShardDataTree.java | 64 ++++--- .../cluster/datastore/AbstractShardTest.java | 11 ++ .../DataChangeListenerRegistrationTest.java | 2 +- .../DataChangeListenerSupportTest.java | 167 ++++++++++++++++++ .../DataTreeChangeListenerSupportTest.java | 128 ++++++++++++++ .../utils/MockDataChangeListener.java | 22 ++- .../utils/MockDataTreeChangeListener.java | 42 ++++- .../md/cluster/datastore/model/TestModel.java | 66 +++++++ .../test/resources/odl-datastore-test.yang | 3 + 13 files changed, 508 insertions(+), 82 deletions(-) rename opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/{DataChangeListenerRegistration.java => DataChangeListenerRegistrationActor.java} (89%) create mode 100644 opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerSupportTest.java create mode 100644 opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataTreeChangeListenerSupportTest.java diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistration.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistrationActor.java similarity index 89% rename from opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistration.java rename to opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistrationActor.java index a8d7d22105..bbeb13f936 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistration.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistrationActor.java @@ -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>> registration; - public DataChangeListenerRegistration( + public DataChangeListenerRegistrationActor( ListenerRegistration>> registration) { this.registration = registration; } @@ -52,7 +52,7 @@ public class DataChangeListenerRegistration extends AbstractUntypedActor { } private static class DataChangeListenerRegistrationCreator - implements Creator { + implements Creator { private static final long serialVersionUID = 1L; final ListenerRegistration>> 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); } } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerSupport.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerSupport.java index c7b55414e6..05accddb78 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerSupport.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerSupport.java @@ -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>>, DOMImmutableDataChangeEvent> { +final class DataChangeListenerSupport extends LeaderLocalDelegateFactory>>, + Optional> { private static final Logger LOG = LoggerFactory.getLogger(DataChangeListenerSupport.class); private final List delayedListenerRegistrations = new ArrayList<>(); private final List dataChangeListeners = new ArrayList<>(); @@ -60,12 +63,10 @@ final class DataChangeListenerSupport extends LeaderLocalDelegateFactory>>, DOMImmutableDataChangeEvent> res = - createDelegate(reg.getRegisterChangeListener()); + final Entry>>, + Optional> 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>> registration; - final AsyncDataChangeEvent> event; if ((hasLeader && message.isRegisterOnAllInstances()) || isLeader) { - final Entry>>, DOMImmutableDataChangeEvent> res = - createDelegate(message); + final Entry>>, + Optional> 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>>, DOMImmutableDataChangeEvent> createDelegate( - final RegisterChangeListener message) { + Entry>>, + Optional> createDelegate(final RegisterChangeListener message) { ActorSelection dataChangeListenerPath = selectActor(message.getDataChangeListenerPath()); // Notify the listener if notifications should be enabled or not diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataTreeChangeListenerSupport.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataTreeChangeListenerSupport.java index 4281bfe796..76458fd8ed 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataTreeChangeListenerSupport.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DataTreeChangeListenerSupport.java @@ -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, DataTreeCandidate> { +final class DataTreeChangeListenerSupport extends LeaderLocalDelegateFactory, Optional> { private static final Logger LOG = LoggerFactory.getLogger(DataTreeChangeListenerSupport.class); private final ArrayList delayedRegistrations = new ArrayList<>(); private final Collection actors = new ArrayList<>(); @@ -33,6 +34,11 @@ final class DataTreeChangeListenerSupport extends LeaderLocalDelegateFactory 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, DataTreeCandidate> res = createDelegate(registerTreeChangeListener); + final Entry, Optional> 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, DataTreeCandidate> createDelegate(final RegisterDataTreeChangeListener message) { + Entry, Optional> createDelegate(final RegisterDataTreeChangeListener message) { ActorSelection dataChangeListenerPath = selectActor(message.getDataTreeChangeListenerPath()); // Notify the listener if notifications should be enabled or not diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DelayedDataTreeListenerRegistration.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DelayedDataTreeListenerRegistration.java index e8cd31097b..958ccc4438 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DelayedDataTreeListenerRegistration.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DelayedDataTreeListenerRegistration.java @@ -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, DataTreeCandidate> factory) { + synchronized void createDelegate(final LeaderLocalDelegateFactory, Optional> factory) { if (!closed) { - final Entry, DataTreeCandidate> res = factory.createDelegate(registerTreeChangeListener); + final Entry, Optional> 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()); } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTree.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTree.java index c167ec6306..18f0cddf17 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTree.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardDataTree.java @@ -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 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>> listenerReg, Optional 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 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>>, DOMImmutableDataChangeEvent> registerChangeListener( - final YangInstanceIdentifier path, - final AsyncDataChangeListener> listener, final DataChangeScope scope) { - final ListenerRegistration>> reg = + Entry>>, + Optional> registerChangeListener(final YangInstanceIdentifier path, + final AsyncDataChangeListener> listener, + final DataChangeScope scope) { + final DataChangeListenerRegistration>> reg = listenerTree.registerDataChangeListener(path, listener, scope); - final Optional> 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 readCurrentData() { + final Optional> currentState = dataTree.takeSnapshot().readNode(ROOT_PATH); + return currentState.isPresent() ? Optional.of(DataTreeCandidates.fromNormalizedNode( + ROOT_PATH, currentState.get())) : Optional.absent(); } - public Entry, DataTreeCandidate> registerTreeChangeListener(final YangInstanceIdentifier path, - final DOMDataTreeChangeListener listener) { - final ListenerRegistration reg = treeChangePublisher.registerTreeChangeListener(path, listener); + public Entry, Optional> registerTreeChangeListener( + final YangInstanceIdentifier path, final DOMDataTreeChangeListener listener) { + final ListenerRegistration reg = treeChangePublisher.registerTreeChangeListener( + path, listener); - final Optional> 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 { diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/AbstractShardTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/AbstractShardTest.java index 97c9e923a9..3f0a1bfc86 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/AbstractShardTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/AbstractShardTest.java @@ -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(); diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistrationTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistrationTest.java index 19b051e4b6..ff80136469 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistrationTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistrationTest.java @@ -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 index 0000000000..ac6f8017b8 --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerSupportTest.java @@ -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 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 index 0000000000..c19f60968c --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataTreeChangeListenerSupportTest.java @@ -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 actor = actorFactory.createTestActor(newShardProps()); + return actor.underlyingActor(); + } +} diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/MockDataChangeListener.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/MockDataChangeListener.java index efd58620a2..24664be044 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/MockDataChangeListener.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/MockDataChangeListener.java @@ -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> 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> 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> createdData = changeList.get(i).getCreatedData(); + assertTrue("Unexpected " + path + " present in createdData", createdData.get(path) == null); + } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/MockDataTreeChangeListener.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/MockDataTreeChangeListener.java index d06fc43572..cf188b339e 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/MockDataTreeChangeListener.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/MockDataTreeChangeListener.java @@ -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 pathSet = new HashSet<>(Arrays.asList(paths)); + for(Collection 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 pathSet = new HashSet<>(Arrays.asList(paths)); + for(Collection list: changeList) { + for(DataTreeCandidate c: list) { + assertFalse("Unexpected " + c.getRootPath() + " present in DataTreeCandidate", + pathSet.contains(c.getRootPath())); + } + } + } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/TestModel.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/TestModel.java index c4fb9a11dd..86c0f110bc 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/TestModel.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/TestModel.java @@ -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 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 outer = ImmutableNodes.mapNodeBuilder(OUTER_LIST_QNAME); + for(MapEntryNode e: entries) { + outer.addChild(e); + } + + return outer.build(); + } + + public static DataContainerChild innerNode(String... names) { + CollectionNodeBuilder 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)); + } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/resources/odl-datastore-test.yang b/opendaylight/md-sal/sal-distributed-datastore/src/test/resources/odl-datastore-test.yang index 98c7bb453d..0834c73f64 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/resources/odl-datastore-test.yang +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/resources/odl-datastore-test.yang @@ -46,6 +46,9 @@ module odl-datastore-test { } } } + + container outer-container { + } } container test2 { -- 2.36.6