X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=blobdiff_plain;f=opendaylight%2Fmd-sal%2Fsal-distributed-datastore%2Fsrc%2Ftest%2Fjava%2Forg%2Fopendaylight%2Fcontroller%2Fcluster%2Fdatastore%2FShardTest.java;h=deb71c2df4aa9cc522904e4014ed66d536aa2fa4;hp=2568b0f555b650f16d8d0c4018461613934f9463;hb=70c27e8bf6d323376a78aa5468faf4f27d081638;hpb=1e59825dbec7b354d76bd7efa6a61e4ad802c802 diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTest.java index 2568b0f555..deb71c2df4 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTest.java @@ -1,47 +1,102 @@ package org.opendaylight.controller.cluster.datastore; import akka.actor.ActorRef; +import akka.actor.ActorSystem; import akka.actor.Props; +import akka.event.Logging; import akka.testkit.JavaTestKit; +import akka.testkit.TestActorRef; +import com.google.common.base.Optional; +import com.google.common.util.concurrent.CheckedFuture; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import org.junit.Assert; import org.junit.Test; +import org.opendaylight.controller.cluster.datastore.identifiers.ShardIdentifier; import org.opendaylight.controller.cluster.datastore.messages.CreateTransaction; -import org.opendaylight.controller.cluster.datastore.messages.CreateTransactionChain; -import org.opendaylight.controller.cluster.datastore.messages.CreateTransactionChainReply; -import org.opendaylight.controller.md.cluster.datastore.model.SchemaContextHelper; -import org.opendaylight.controller.protobuff.messages.transaction.ShardTransactionMessages.CreateTransactionReply; +import org.opendaylight.controller.cluster.datastore.messages.EnableNotification; +import org.opendaylight.controller.cluster.datastore.messages.PeerAddressResolved; import org.opendaylight.controller.cluster.datastore.messages.RegisterChangeListener; import org.opendaylight.controller.cluster.datastore.messages.RegisterChangeListenerReply; import org.opendaylight.controller.cluster.datastore.messages.UpdateSchemaContext; +import org.opendaylight.controller.cluster.datastore.node.NormalizedNodeToNodeCodec; +import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshot; +import org.opendaylight.controller.md.cluster.datastore.model.SchemaContextHelper; import org.opendaylight.controller.md.cluster.datastore.model.TestModel; import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker; import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent; import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener; -import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; +import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; +import org.opendaylight.controller.md.sal.dom.store.impl.InMemoryDOMDataStore; +import org.opendaylight.controller.protobuff.messages.common.NormalizedNodeMessages; +import org.opendaylight.controller.protobuff.messages.transaction.ShardTransactionMessages.CreateTransactionReply; +import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadTransaction; +import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort; +import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class ShardTest extends AbstractActorTest { + + private static final DatastoreContext DATA_STORE_CONTEXT = new DatastoreContext(); + @Test - public void testOnReceiveCreateTransactionChain() throws Exception { + public void testOnReceiveRegisterListener() throws Exception { new JavaTestKit(getSystem()) {{ - final Props props = Shard.props("config"); + final ShardIdentifier identifier = + ShardIdentifier.builder().memberName("member-1") + .shardName("inventory").type("config").build(); + + final Props props = Shard.props(identifier, Collections.EMPTY_MAP, DATA_STORE_CONTEXT, TestModel.createTestContext()); final ActorRef subject = - getSystem().actorOf(props, "testCreateTransactionChain"); + getSystem().actorOf(props, "testRegisterChangeListener"); - new Within(duration("1 seconds")) { + new Within(duration("3 seconds")) { + @Override protected void run() { - subject.tell(new CreateTransactionChain(), getRef()); + subject.tell( + new UpdateSchemaContext(SchemaContextHelper.full()), + getRef()); + + subject.tell(new RegisterChangeListener(TestModel.TEST_PATH, + getRef().path(), AsyncDataBroker.DataChangeScope.BASE), + getRef()); + + final Boolean notificationEnabled = new ExpectMsg( + duration("3 seconds"), "enable notification") { + // do not put code outside this method, will run afterwards + @Override + protected Boolean match(Object in) { + if(in instanceof EnableNotification){ + return ((EnableNotification) in).isEnabled(); + } else { + throw noMatch(); + } + } + }.get(); // this extracts the received message + + assertFalse(notificationEnabled); - final String out = new ExpectMsg("match hint") { + final String out = new ExpectMsg(duration("3 seconds"), "match hint") { // do not put code outside this method, will run afterwards + @Override protected String match(Object in) { - if (in instanceof CreateTransactionChainReply) { - CreateTransactionChainReply reply = - (CreateTransactionChainReply) in; - return reply.getTransactionChainPath() + if (in.getClass().equals(RegisterChangeListenerReply.class)) { + RegisterChangeListenerReply reply = + (RegisterChangeListenerReply) in; + return reply.getListenerRegistrationPath() .toString(); } else { throw noMatch(); @@ -49,11 +104,8 @@ public class ShardTest extends AbstractActorTest { } }.get(); // this extracts the received message - assertEquals("Unexpected transaction path " + out, - "akka://test/user/testCreateTransactionChain/$a", - out); - - expectNoMsg(); + assertTrue(out.matches( + "akka:\\/\\/test\\/user\\/testRegisterChangeListener\\/\\$.*")); } @@ -62,30 +114,49 @@ public class ShardTest extends AbstractActorTest { } @Test - public void testOnReceiveRegisterListener() throws Exception { + public void testCreateTransaction(){ new JavaTestKit(getSystem()) {{ - final Props props = Shard.props("config"); + final ShardIdentifier identifier = + ShardIdentifier.builder().memberName("member-1") + .shardName("inventory").type("config").build(); + + final Props props = Shard.props(identifier, Collections.EMPTY_MAP, DATA_STORE_CONTEXT, TestModel.createTestContext()); final ActorRef subject = - getSystem().actorOf(props, "testRegisterChangeListener"); + getSystem().actorOf(props, "testCreateTransaction"); + + // Wait for a specific log message to show up + final boolean result = + new JavaTestKit.EventFilter(Logging.Info.class + ) { + @Override + protected Boolean run() { + return true; + } + }.from(subject.path().toString()) + .message("Switching from state Candidate to Leader") + .occurrences(1).exec(); + + Assert.assertEquals(true, result); - new Within(duration("1 seconds")) { + new Within(duration("3 seconds")) { + @Override protected void run() { subject.tell( - new UpdateSchemaContext(SchemaContextHelper.full()), + new UpdateSchemaContext(TestModel.createTestContext()), getRef()); - subject.tell(new RegisterChangeListener(TestModel.TEST_PATH, - getRef().path(), AsyncDataBroker.DataChangeScope.BASE).toSerializable(), + subject.tell(new CreateTransaction("txn-1", TransactionProxy.TransactionType.READ_ONLY.ordinal() ).toSerializable(), getRef()); - final String out = new ExpectMsg("match hint") { + final String out = new ExpectMsg(duration("3 seconds"), "match hint") { // do not put code outside this method, will run afterwards + @Override protected String match(Object in) { - if (in.getClass().equals(RegisterChangeListenerReply.SERIALIZABLE_CLASS)) { - RegisterChangeListenerReply reply = - RegisterChangeListenerReply.fromSerializable(getSystem(),in); - return reply.getListenerRegistrationPath() + if (in instanceof CreateTransactionReply) { + CreateTransactionReply reply = + (CreateTransactionReply) in; + return reply.getTransactionActorPath() .toString(); } else { throw noMatch(); @@ -93,36 +164,53 @@ public class ShardTest extends AbstractActorTest { } }.get(); // this extracts the received message - assertTrue(out.matches( - "akka:\\/\\/test\\/user\\/testRegisterChangeListener\\/\\$.*")); - // Will wait for the rest of the 3 seconds + assertTrue("Unexpected transaction path " + out, + out.contains("akka://test/user/testCreateTransaction/shard-txn-1")); expectNoMsg(); } - - }; }}; } @Test - public void testCreateTransaction(){ + public void testCreateTransactionOnChain(){ new JavaTestKit(getSystem()) {{ - final Props props = Shard.props("config"); + final ShardIdentifier identifier = + ShardIdentifier.builder().memberName("member-1") + .shardName("inventory").type("config").build(); + + final Props props = Shard.props(identifier, Collections.EMPTY_MAP, DATA_STORE_CONTEXT, TestModel.createTestContext()); final ActorRef subject = - getSystem().actorOf(props, "testCreateTransaction"); + getSystem().actorOf(props, "testCreateTransactionOnChain"); + + // Wait for a specific log message to show up + final boolean result = + new JavaTestKit.EventFilter(Logging.Info.class + ) { + @Override + protected Boolean run() { + return true; + } + }.from(subject.path().toString()) + .message("Switching from state Candidate to Leader") + .occurrences(1).exec(); - new Within(duration("1 seconds")) { + Assert.assertEquals(true, result); + + new Within(duration("3 seconds")) { + @Override protected void run() { subject.tell( new UpdateSchemaContext(TestModel.createTestContext()), getRef()); - subject.tell(new CreateTransaction("txn-1"), + subject.tell(new CreateTransaction("txn-1", TransactionProxy.TransactionType.READ_ONLY.ordinal() , "foobar").toSerializable(), getRef()); - final String out = new ExpectMsg("match hint") { + final String out = new ExpectMsg(duration("3 seconds"), "match hint") { // do not put code outside this method, will run afterwards + @Override protected String match(Object in) { if (in instanceof CreateTransactionReply) { CreateTransactionReply reply = @@ -135,24 +223,203 @@ public class ShardTest extends AbstractActorTest { } }.get(); // this extracts the received message - assertEquals("Unexpected transaction path " + out, - "akka://test/user/testCreateTransaction/shard-txn-1", - out); + assertTrue("Unexpected transaction path " + out, + out.contains("akka://test/user/testCreateTransactionOnChain/shard-txn-1")); expectNoMsg(); } + }; + }}; + } + @Test + public void testPeerAddressResolved(){ + new JavaTestKit(getSystem()) {{ + Map peerAddresses = new HashMap<>(); + + final ShardIdentifier identifier = + ShardIdentifier.builder().memberName("member-1") + .shardName("inventory").type("config").build(); + + peerAddresses.put(identifier, null); + final Props props = Shard.props(identifier, peerAddresses, DATA_STORE_CONTEXT, TestModel.createTestContext()); + final ActorRef subject = + getSystem().actorOf(props, "testPeerAddressResolved"); + + new Within(duration("3 seconds")) { + @Override + protected void run() { + + subject.tell( + new PeerAddressResolved(identifier, "akka://foobar"), + getRef()); + expectNoMsg(); + } }; }}; } + @Test + public void testApplySnapshot() throws ExecutionException, InterruptedException { + Map peerAddresses = new HashMap<>(); + + final ShardIdentifier identifier = + ShardIdentifier.builder().memberName("member-1") + .shardName("inventory").type("config").build(); + + peerAddresses.put(identifier, null); + final Props props = Shard.props(identifier, peerAddresses, DATA_STORE_CONTEXT, TestModel.createTestContext()); + + TestActorRef ref = TestActorRef.create(getSystem(), props); + + ref.underlyingActor().updateSchemaContext(TestModel.createTestContext()); + + NormalizedNodeToNodeCodec codec = + new NormalizedNodeToNodeCodec(TestModel.createTestContext()); + + ref.underlyingActor().writeToStore(TestModel.TEST_PATH, ImmutableNodes.containerNode(TestModel.TEST_QNAME)); + + NormalizedNode expected = ref.underlyingActor().readStore(); + + NormalizedNodeMessages.Container encode = codec + .encode(YangInstanceIdentifier.builder().build(), expected); + + + ref.underlyingActor().applySnapshot(encode.getNormalizedNode().toByteString()); + + NormalizedNode actual = ref.underlyingActor().readStore(); + + assertEquals(expected, actual); + } + + private static class ShardTestKit extends JavaTestKit { + private ShardTestKit(ActorSystem actorSystem) { + super(actorSystem); + } + + protected void waitForLogMessage(final Class logLevel, ActorRef subject, String logMessage){ + // Wait for a specific log message to show up + final boolean result = + new JavaTestKit.EventFilter(logLevel + ) { + @Override + protected Boolean run() { + return true; + } + }.from(subject.path().toString()) + .message(logMessage) + .occurrences(1).exec(); + + Assert.assertEquals(true, result); + + } + + } + + @Test + public void testCreateSnapshot() throws IOException, InterruptedException { + new ShardTestKit(getSystem()) {{ + final ShardIdentifier identifier = + ShardIdentifier.builder().memberName("member-1") + .shardName("inventory").type("config").build(); + + final Props props = Shard.props(identifier, Collections.EMPTY_MAP, DATA_STORE_CONTEXT, TestModel.createTestContext()); + final ActorRef subject = + getSystem().actorOf(props, "testCreateSnapshot"); + + // Wait for a specific log message to show up + this.waitForLogMessage(Logging.Info.class, subject, "Switching from state Candidate to Leader"); + + + new Within(duration("3 seconds")) { + @Override + protected void run() { + + subject.tell( + new UpdateSchemaContext(TestModel.createTestContext()), + getRef()); + + subject.tell(new CaptureSnapshot(-1,-1,-1,-1), + getRef()); + + waitForLogMessage(Logging.Info.class, subject, "CaptureSnapshotReply received by actor"); + + subject.tell(new CaptureSnapshot(-1,-1,-1,-1), + getRef()); + + waitForLogMessage(Logging.Info.class, subject, "CaptureSnapshotReply received by actor"); + + } + }; + + deletePersistenceFiles(); + }}; + } + + /** + * This test simply verifies that the applySnapShot logic will work + * @throws ReadFailedException + */ + @Test + public void testInMemoryDataStoreRestore() throws ReadFailedException { + InMemoryDOMDataStore store = new InMemoryDOMDataStore("test", MoreExecutors.listeningDecorator( + MoreExecutors.sameThreadExecutor()), MoreExecutors.sameThreadExecutor()); + + store.onGlobalContextUpdated(TestModel.createTestContext()); + + DOMStoreWriteTransaction putTransaction = store.newWriteOnlyTransaction(); + putTransaction.write(TestModel.TEST_PATH, + ImmutableNodes.containerNode(TestModel.TEST_QNAME)); + commitTransaction(putTransaction); + + + NormalizedNode expected = readStore(store); + + DOMStoreWriteTransaction writeTransaction = store.newWriteOnlyTransaction(); + + writeTransaction.delete(YangInstanceIdentifier.builder().build()); + writeTransaction.write(YangInstanceIdentifier.builder().build(), expected); + + commitTransaction(writeTransaction); + + NormalizedNode actual = readStore(store); + + assertEquals(expected, actual); + + } + + private NormalizedNode readStore(InMemoryDOMDataStore store) throws ReadFailedException { + DOMStoreReadTransaction transaction = store.newReadOnlyTransaction(); + CheckedFuture>, ReadFailedException> read = + transaction.read(YangInstanceIdentifier.builder().build()); + + Optional> optional = read.checkedGet(); + + NormalizedNode normalizedNode = optional.get(); + + transaction.close(); + + return normalizedNode; + } + + private void commitTransaction(DOMStoreWriteTransaction transaction) { + DOMStoreThreePhaseCommitCohort commitCohort = transaction.ready(); + ListenableFuture future = + commitCohort.preCommit(); + try { + future.get(); + future = commitCohort.commit(); + future.get(); + } catch (InterruptedException | ExecutionException e) { + } + } - private AsyncDataChangeListener> noOpDataChangeListener() { - return new AsyncDataChangeListener>() { + private AsyncDataChangeListener> noOpDataChangeListener() { + return new AsyncDataChangeListener>() { @Override public void onDataChanged( - AsyncDataChangeEvent> change) { + AsyncDataChangeEvent> change) { } };