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=2051c9debe88c69c071663df511fbff130f325ed;hp=06bcac8d786b943a0bf12087c31c0ba659c1f017;hb=5adfd98dac66cab656090d1b51324b4d09573bd9;hpb=dd281c0e33267296ad3babbffd03e1122cdb127e 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 06bcac8d78..2051c9debe 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 @@ -4,23 +4,43 @@ import akka.actor.ActorRef; import akka.actor.ActorSystem; import akka.actor.Props; import akka.event.Logging; +import akka.japi.Creator; 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.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; +import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.opendaylight.controller.cluster.datastore.identifiers.ShardIdentifier; +import org.opendaylight.controller.cluster.datastore.messages.CommitTransactionReply; import org.opendaylight.controller.cluster.datastore.messages.CreateTransaction; import org.opendaylight.controller.cluster.datastore.messages.EnableNotification; +import org.opendaylight.controller.cluster.datastore.messages.ForwardedCommitTransaction; 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.modification.MergeModification; +import org.opendaylight.controller.cluster.datastore.modification.Modification; +import org.opendaylight.controller.cluster.datastore.modification.MutableCompositeModification; +import org.opendaylight.controller.cluster.datastore.modification.WriteModification; import org.opendaylight.controller.cluster.datastore.node.NormalizedNodeToNodeCodec; +import org.opendaylight.controller.cluster.datastore.utils.InMemoryJournal; +import org.opendaylight.controller.cluster.datastore.utils.InMemorySnapshotStore; +import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry; +import org.opendaylight.controller.cluster.raft.ReplicatedLogImplEntry; +import org.opendaylight.controller.cluster.raft.Snapshot; +import org.opendaylight.controller.cluster.raft.base.messages.ApplyLogEntries; +import org.opendaylight.controller.cluster.raft.base.messages.ApplySnapshot; +import org.opendaylight.controller.cluster.raft.base.messages.ApplyState; import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshot; +import org.opendaylight.controller.cluster.raft.protobuff.client.messages.CompositeModificationPayload; +import org.opendaylight.controller.cluster.raft.protobuff.client.messages.Payload; 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; @@ -28,222 +48,138 @@ 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.common.api.data.ReadFailedException; import org.opendaylight.controller.md.sal.dom.store.impl.InMemoryDOMDataStore; +import org.opendaylight.controller.md.sal.dom.store.impl.InMemoryDOMDataStoreFactory; 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.YangInstanceIdentifier.PathArgument; +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.NormalizedNode; import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; - +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import scala.concurrent.duration.Duration; import java.io.IOException; import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; - +import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; public class ShardTest extends AbstractActorTest { - private static final DatastoreContext DATA_STORE_CONTEXT = new DatastoreContext(); + private static final DatastoreContext DATA_STORE_CONTEXT = + new DatastoreContext("", null, Duration.create(10, TimeUnit.MINUTES), 5, 3, 5000, 500); - @Test - public void testOnReceiveRegisterListener() throws Exception { - new JavaTestKit(getSystem()) {{ - final ShardIdentifier identifier = - ShardIdentifier.builder().memberName("member-1") - .shardName("inventory").type("config").build(); + private static final SchemaContext SCHEMA_CONTEXT = TestModel.createTestContext(); - final Props props = Shard.props(identifier, Collections.EMPTY_MAP, DATA_STORE_CONTEXT, TestModel.createTestContext()); - final ActorRef subject = - getSystem().actorOf(props, "testRegisterChangeListener"); + private static final ShardIdentifier IDENTIFIER = ShardIdentifier.builder().memberName("member-1") + .shardName("inventory").type("config").build(); - new Within(duration("3 seconds")) { - @Override - protected void run() { + @Before + public void setUp() { + System.setProperty("shard.persistent", "false"); - subject.tell( - new UpdateSchemaContext(SchemaContextHelper.full()), - getRef()); + InMemorySnapshotStore.clear(); + InMemoryJournal.clear(); + } - subject.tell(new RegisterChangeListener(TestModel.TEST_PATH, - getRef().path(), AsyncDataBroker.DataChangeScope.BASE), - getRef()); + @After + public void tearDown() { + InMemorySnapshotStore.clear(); + InMemoryJournal.clear(); + } - 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(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.class)) { - RegisterChangeListenerReply reply = - (RegisterChangeListenerReply) in; - return reply.getListenerRegistrationPath() - .toString(); - } else { - throw noMatch(); - } - } - }.get(); // this extracts the received message + private Props newShardProps() { + return Shard.props(IDENTIFIER, Collections.emptyMap(), + DATA_STORE_CONTEXT, SCHEMA_CONTEXT); + } - assertTrue(out.matches( - "akka:\\/\\/test\\/user\\/testRegisterChangeListener\\/\\$.*")); - } + @Test + public void testOnReceiveRegisterListener() throws Exception { + new JavaTestKit(getSystem()) {{ + ActorRef subject = getSystem().actorOf(newShardProps(), "testRegisterChangeListener"); + subject.tell(new UpdateSchemaContext(SchemaContextHelper.full()), getRef()); - }; + subject.tell(new RegisterChangeListener(TestModel.TEST_PATH, + getRef().path(), AsyncDataBroker.DataChangeScope.BASE), getRef()); + + EnableNotification enable = expectMsgClass(duration("3 seconds"), EnableNotification.class); + assertEquals("isEnabled", false, enable.isEnabled()); + + RegisterChangeListenerReply reply = expectMsgClass(duration("3 seconds"), + RegisterChangeListenerReply.class); + assertTrue(reply.getListenerRegistrationPath().toString().matches( + "akka:\\/\\/test\\/user\\/testRegisterChangeListener\\/\\$.*")); }}; } @Test public void testCreateTransaction(){ - new JavaTestKit(getSystem()) {{ - final ShardIdentifier identifier = - ShardIdentifier.builder().memberName("member-1") - .shardName("inventory").type("config").build(); + new ShardTestKit(getSystem()) {{ + ActorRef subject = getSystem().actorOf(newShardProps(), "testCreateTransaction"); - final Props props = Shard.props(identifier, Collections.EMPTY_MAP, DATA_STORE_CONTEXT, TestModel.createTestContext()); - final ActorRef subject = - getSystem().actorOf(props, "testCreateTransaction"); + waitUntilLeader(subject); - // 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(); + subject.tell(new UpdateSchemaContext(TestModel.createTestContext()), getRef()); - Assert.assertEquals(true, result); + subject.tell(new CreateTransaction("txn-1", + TransactionProxy.TransactionType.READ_ONLY.ordinal() ).toSerializable(), getRef()); - new Within(duration("3 seconds")) { - @Override - protected void run() { + CreateTransactionReply reply = expectMsgClass(duration("3 seconds"), + CreateTransactionReply.class); - subject.tell( - new UpdateSchemaContext(TestModel.createTestContext()), - getRef()); - - subject.tell(new CreateTransaction("txn-1", TransactionProxy.TransactionType.READ_ONLY.ordinal() ).toSerializable(), - getRef()); - - 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 = - (CreateTransactionReply) in; - return reply.getTransactionActorPath() - .toString(); - } else { - throw noMatch(); - } - } - }.get(); // this extracts the received message - - assertTrue("Unexpected transaction path " + out, - out.contains("akka://test/user/testCreateTransaction/shard-txn-1")); - expectNoMsg(); - } - }; + String path = reply.getTransactionActorPath().toString(); + assertTrue("Unexpected transaction path " + path, + path.contains("akka://test/user/testCreateTransaction/shard-txn-1")); + expectNoMsg(); }}; } @Test public void testCreateTransactionOnChain(){ - new JavaTestKit(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, "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(); - - Assert.assertEquals(true, result); - - new Within(duration("3 seconds")) { - @Override - protected void run() { + new ShardTestKit(getSystem()) {{ + final ActorRef subject = getSystem().actorOf(newShardProps(), "testCreateTransactionOnChain"); - subject.tell( - new UpdateSchemaContext(TestModel.createTestContext()), - getRef()); + waitUntilLeader(subject); - subject.tell(new CreateTransaction("txn-1", TransactionProxy.TransactionType.READ_ONLY.ordinal() , "foobar").toSerializable(), - getRef()); + subject.tell(new CreateTransaction("txn-1", + TransactionProxy.TransactionType.READ_ONLY.ordinal() , "foobar").toSerializable(), + getRef()); - 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 = - (CreateTransactionReply) in; - return reply.getTransactionActorPath() - .toString(); - } else { - throw noMatch(); - } - } - }.get(); // this extracts the received message + CreateTransactionReply reply = expectMsgClass(duration("3 seconds"), + CreateTransactionReply.class); - assertTrue("Unexpected transaction path " + out, - out.contains("akka://test/user/testCreateTransactionOnChain/shard-txn-1")); - expectNoMsg(); - } - }; + String path = reply.getTransactionActorPath().toString(); + assertTrue("Unexpected transaction path " + path, + path.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"); + Props props = Shard.props(identifier, + Collections.singletonMap(identifier, null), + DATA_STORE_CONTEXT, SCHEMA_CONTEXT); + final ActorRef subject = getSystem().actorOf(props, "testPeerAddressResolved"); new Within(duration("3 seconds")) { @Override @@ -261,94 +197,205 @@ public class ShardTest extends AbstractActorTest { @Test public void testApplySnapshot() throws ExecutionException, InterruptedException { - Map peerAddresses = new HashMap<>(); + TestActorRef ref = TestActorRef.create(getSystem(), newShardProps()); - final ShardIdentifier identifier = - ShardIdentifier.builder().memberName("member-1") - .shardName("inventory").type("config").build(); + NormalizedNodeToNodeCodec codec = + new NormalizedNodeToNodeCodec(SCHEMA_CONTEXT); - peerAddresses.put(identifier, null); - final Props props = Shard.props(identifier, peerAddresses, DATA_STORE_CONTEXT, TestModel.createTestContext()); + ref.underlyingActor().writeToStore(TestModel.TEST_PATH, ImmutableNodes.containerNode( + TestModel.TEST_QNAME)); - TestActorRef ref = TestActorRef.create(getSystem(), props); + YangInstanceIdentifier root = YangInstanceIdentifier.builder().build(); + NormalizedNode expected = ref.underlyingActor().readStore(root); - ref.underlyingActor().updateSchemaContext(TestModel.createTestContext()); + NormalizedNodeMessages.Container encode = codec.encode(expected); - NormalizedNodeToNodeCodec codec = - new NormalizedNodeToNodeCodec(TestModel.createTestContext()); + ApplySnapshot applySnapshot = new ApplySnapshot(Snapshot.create( + encode.getNormalizedNode().toByteString().toByteArray(), + Collections.emptyList(), 1, 2, 3, 4)); - ref.underlyingActor().writeToStore(TestModel.TEST_PATH, ImmutableNodes.containerNode(TestModel.TEST_QNAME)); + ref.underlyingActor().onReceiveCommand(applySnapshot); - NormalizedNode expected = ref.underlyingActor().readStore(); + NormalizedNode actual = ref.underlyingActor().readStore(root); - NormalizedNodeMessages.Container encode = codec - .encode(YangInstanceIdentifier.builder().build(), expected); + assertEquals(expected, actual); + } + @Test + public void testApplyState() throws Exception { - ref.underlyingActor().applySnapshot(encode.getNormalizedNode().toByteString()); + TestActorRef shard = TestActorRef.create(getSystem(), newShardProps()); - NormalizedNode actual = ref.underlyingActor().readStore(); + NormalizedNode node = ImmutableNodes.containerNode(TestModel.TEST_QNAME); - assertEquals(expected, actual); - } + MutableCompositeModification compMod = new MutableCompositeModification(); + compMod.addModification(new WriteModification(TestModel.TEST_PATH, node, SCHEMA_CONTEXT)); + Payload payload = new CompositeModificationPayload(compMod.toSerializable()); + ApplyState applyState = new ApplyState(null, "test", + new ReplicatedLogImplEntry(1, 2, payload)); - private static class ShardTestKit extends JavaTestKit { + shard.underlyingActor().onReceiveCommand(applyState); - private ShardTestKit(ActorSystem actorSystem) { - super(actorSystem); + NormalizedNode actual = shard.underlyingActor().readStore(TestModel.TEST_PATH); + assertEquals("Applied state", node, actual); + } + + @SuppressWarnings("serial") + @Test + public void testRecovery() throws Exception { + + // Set up the InMemorySnapshotStore. + + InMemoryDOMDataStore testStore = InMemoryDOMDataStoreFactory.create("Test", null, null); + testStore.onGlobalContextUpdated(SCHEMA_CONTEXT); + + DOMStoreWriteTransaction writeTx = testStore.newWriteOnlyTransaction(); + writeTx.write(TestModel.TEST_PATH, ImmutableNodes.containerNode(TestModel.TEST_QNAME)); + DOMStoreThreePhaseCommitCohort commitCohort = writeTx.ready(); + commitCohort.preCommit().get(); + commitCohort.commit().get(); + + DOMStoreReadTransaction readTx = testStore.newReadOnlyTransaction(); + NormalizedNode root = readTx.read(YangInstanceIdentifier.builder().build()).get().get(); + + InMemorySnapshotStore.addSnapshot(IDENTIFIER.toString(), Snapshot.create( + new NormalizedNodeToNodeCodec(SCHEMA_CONTEXT).encode( + root). + getNormalizedNode().toByteString().toByteArray(), + Collections.emptyList(), 0, 1, -1, -1)); + + // Set up the InMemoryJournal. + + InMemoryJournal.addEntry(IDENTIFIER.toString(), 0, new ReplicatedLogImplEntry(0, 1, newPayload( + new WriteModification(TestModel.OUTER_LIST_PATH, + ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME).build(), + SCHEMA_CONTEXT)))); + + int nListEntries = 11; + Set listEntryKeys = new HashSet<>(); + for(int i = 1; i <= nListEntries; i++) { + listEntryKeys.add(Integer.valueOf(i)); + YangInstanceIdentifier path = YangInstanceIdentifier.builder(TestModel.OUTER_LIST_PATH) + .nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, i).build(); + Modification mod = new MergeModification(path, + ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, i), + SCHEMA_CONTEXT); + InMemoryJournal.addEntry(IDENTIFIER.toString(), i, new ReplicatedLogImplEntry(i, 1, + newPayload(mod))); } - 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 - ) { + InMemoryJournal.addEntry(IDENTIFIER.toString(), nListEntries + 1, + new ApplyLogEntries(nListEntries)); + + // Create the actor and wait for recovery complete. + + final CountDownLatch recoveryComplete = new CountDownLatch(1); + + Creator creator = new Creator() { + @Override + public Shard create() throws Exception { + return new Shard(IDENTIFIER, Collections.emptyMap(), + DATA_STORE_CONTEXT, SCHEMA_CONTEXT) { @Override - protected Boolean run() { - return true; + protected void onRecoveryComplete() { + try { + super.onRecoveryComplete(); + } finally { + recoveryComplete.countDown(); + } } - }.from(subject.path().toString()) - .message(logMessage) - .occurrences(1).exec(); + }; + } + }; - Assert.assertEquals(true, result); + TestActorRef shard = TestActorRef.create(getSystem(), + Props.create(new DelegatingShardCreator(creator)), "testRecovery"); + + assertEquals("Recovery complete", true, recoveryComplete.await(5, TimeUnit.SECONDS)); + + // Verify data in the data store. + + NormalizedNode outerList = shard.underlyingActor().readStore(TestModel.OUTER_LIST_PATH); + assertNotNull(TestModel.OUTER_LIST_QNAME.getLocalName() + " not found", outerList); + assertTrue(TestModel.OUTER_LIST_QNAME.getLocalName() + " value is not Iterable", + outerList.getValue() instanceof Iterable); + for(Object entry: (Iterable) outerList.getValue()) { + assertTrue(TestModel.OUTER_LIST_QNAME.getLocalName() + " entry is not MapEntryNode", + entry instanceof MapEntryNode); + MapEntryNode mapEntry = (MapEntryNode)entry; + Optional> idLeaf = + mapEntry.getChild(new YangInstanceIdentifier.NodeIdentifier(TestModel.ID_QNAME)); + assertTrue("Missing leaf " + TestModel.ID_QNAME.getLocalName(), idLeaf.isPresent()); + Object value = idLeaf.get().getValue(); + assertTrue("Unexpected value for leaf "+ TestModel.ID_QNAME.getLocalName() + ": " + value, + listEntryKeys.remove(value)); + } + if(!listEntryKeys.isEmpty()) { + fail("Missing " + TestModel.OUTER_LIST_QNAME.getLocalName() + " entries with keys: " + + listEntryKeys); } + assertEquals("Last log index", nListEntries, + shard.underlyingActor().getShardMBean().getLastLogIndex()); + assertEquals("Commit index", nListEntries, + shard.underlyingActor().getShardMBean().getCommitIndex()); + assertEquals("Last applied", nListEntries, + shard.underlyingActor().getShardMBean().getLastApplied()); } + private CompositeModificationPayload newPayload(Modification... mods) { + MutableCompositeModification compMod = new MutableCompositeModification(); + for(Modification mod: mods) { + compMod.addModification(mod); + } + + return new CompositeModificationPayload(compMod.toSerializable()); + } + + @SuppressWarnings("unchecked") @Test - public void testCreateSnapshot() throws IOException, InterruptedException { + public void testForwardedCommitTransactionWithPersistence() throws IOException { + System.setProperty("shard.persistent", "true"); + new ShardTestKit(getSystem()) {{ - final ShardIdentifier identifier = - ShardIdentifier.builder().memberName("member-1") - .shardName("inventory").type("config").build(); + TestActorRef shard = TestActorRef.create(getSystem(), newShardProps()); - final Props props = Shard.props(identifier, Collections.EMPTY_MAP, DATA_STORE_CONTEXT, TestModel.createTestContext()); - final ActorRef subject = - getSystem().actorOf(props, "testCreateSnapshot"); + waitUntilLeader(shard); - // Wait for a specific log message to show up - this.waitForLogMessage(Logging.Info.class, subject, "Switching from state Candidate to Leader"); + NormalizedNode node = ImmutableNodes.containerNode(TestModel.TEST_QNAME); + DOMStoreThreePhaseCommitCohort cohort = mock(DOMStoreThreePhaseCommitCohort.class); + doReturn(Futures.immediateFuture(null)).when(cohort).commit(); - new Within(duration("3 seconds")) { - @Override - protected void run() { + MutableCompositeModification modification = new MutableCompositeModification(); + modification.addModification(new WriteModification(TestModel.TEST_PATH, node, + SCHEMA_CONTEXT)); - subject.tell( - new UpdateSchemaContext(TestModel.createTestContext()), - getRef()); + shard.tell(new ForwardedCommitTransaction(cohort, modification), getRef()); - subject.tell(new CaptureSnapshot(-1,-1,-1,-1), - getRef()); + expectMsgClass(duration("5 seconds"), CommitTransactionReply.SERIALIZABLE_CLASS); - waitForLogMessage(Logging.Debug.class, subject, "CaptureSnapshotReply received by actor"); - } - }; + verify(cohort).commit(); + + assertEquals("Last log index", 0, shard.underlyingActor().getShardMBean().getLastLogIndex()); + }}; + } + + @Test + public void testCreateSnapshot() throws IOException, InterruptedException { + new ShardTestKit(getSystem()) {{ + final ActorRef subject = getSystem().actorOf(newShardProps(), "testCreateSnapshot"); + + waitUntilLeader(subject); + + subject.tell(new CaptureSnapshot(-1,-1,-1,-1), getRef()); + + waitForLogMessage(Logging.Info.class, subject, "CaptureSnapshotReply received by actor"); - Thread.sleep(2000); - deletePersistenceFiles(); + subject.tell(new CaptureSnapshot(-1,-1,-1,-1), getRef()); + + waitForLogMessage(Logging.Info.class, subject, "CaptureSnapshotReply received by actor"); }}; } @@ -361,7 +408,7 @@ public class ShardTest extends AbstractActorTest { InMemoryDOMDataStore store = new InMemoryDOMDataStore("test", MoreExecutors.listeningDecorator( MoreExecutors.sameThreadExecutor()), MoreExecutors.sameThreadExecutor()); - store.onGlobalContextUpdated(TestModel.createTestContext()); + store.onGlobalContextUpdated(SCHEMA_CONTEXT); DOMStoreWriteTransaction putTransaction = store.newWriteOnlyTransaction(); putTransaction.write(TestModel.TEST_PATH, @@ -419,4 +466,46 @@ public class ShardTest extends AbstractActorTest { } }; } + + private static final class DelegatingShardCreator implements Creator { + private final Creator delegate; + + DelegatingShardCreator(Creator delegate) { + this.delegate = delegate; + } + + @Override + public Shard create() throws Exception { + return delegate.create(); + } + } + + 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); + + } + + protected void waitUntilLeader(ActorRef subject) { + waitForLogMessage(Logging.Info.class, subject, + "Switching from state Candidate to Leader"); + } + } }