/* * Copyright 2014-2020 Real Logic Limited. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.aeron.cluster; import io.aeron.CommonContext; import io.aeron.archive.*; import io.aeron.cluster.client.AeronCluster; import io.aeron.cluster.client.EgressListener; import io.aeron.cluster.service.*; import io.aeron.driver.MediaDriver; import io.aeron.driver.ThreadingMode; import io.aeron.logbuffer.Header; import io.aeron.test.Tests; import org.agrona.*; import org.agrona.collections.MutableReference; import org.junit.jupiter.api.*; import java.io.File; import static org.junit.jupiter.api.Assertions.assertEquals; public class MultiModuleSharedDriverTest { @Test @Timeout(20) public void shouldSupportTwoSingleNodeClusters() { final MediaDriver.Context driverCtx = new MediaDriver.Context() .threadingMode(ThreadingMode.SHARED) .errorHandler(Tests::onError) .dirDeleteOnStart(true); final Archive.Context archiveCtx = new Archive.Context() .threadingMode(ArchiveThreadingMode.SHARED) .archiveDir(new File(SystemUtil.tmpDirName(), "archive")) .errorHandler(Tests::onError) .recordingEventsEnabled(false) .deleteArchiveOnStart(true); try (ArchivingMediaDriver ignore = ArchivingMediaDriver.launch(driverCtx, archiveCtx)) { final ConsensusModule.Context moduleCtx0 = new ConsensusModule.Context() .clusterId(0) .errorHandler(Tests::onError) .deleteDirOnStart(true) .clusterDir(new File(SystemUtil.tmpDirName(), "cluster-0-0")) .logChannel("aeron:ipc?term-length=64k") .logStreamId(100) .serviceStreamId(104) .consensusModuleStreamId(105) .ingressChannel("aeron:udp?endpoint=localhost:9010"); final ClusteredServiceContainer.Context containerCtx0 = new ClusteredServiceContainer.Context() .clusterId(moduleCtx0.clusterId()) .errorHandler(Tests::onError) .clusteredService(new EchoService()) .clusterDir(moduleCtx0.clusterDir()) .serviceStreamId(moduleCtx0.serviceStreamId()) .consensusModuleStreamId(moduleCtx0.consensusModuleStreamId()); final ConsensusModule.Context moduleCtx1 = new ConsensusModule.Context() .clusterId(1) .errorHandler(Tests::onError) .deleteDirOnStart(true) .clusterDir(new File(SystemUtil.tmpDirName(), "cluster-0-1")) .logChannel("aeron:ipc?term-length=64k") .logStreamId(200) .serviceStreamId(204) .consensusModuleStreamId(205) .ingressChannel("aeron:udp?endpoint=localhost:9011"); final ClusteredServiceContainer.Context containerCtx1 = new ClusteredServiceContainer.Context() .errorHandler(Tests::onError) .clusteredService(new EchoService()) .clusterDir(moduleCtx1.clusterDir()) .serviceStreamId(moduleCtx1.serviceStreamId()) .consensusModuleStreamId(moduleCtx1.consensusModuleStreamId()) .clusterId(moduleCtx1.clusterId()); ConsensusModule consensusModule0 = null; ClusteredServiceContainer container0 = null; ConsensusModule consensusModule1 = null; ClusteredServiceContainer container1 = null; AeronCluster client0 = null; AeronCluster client1 = null; try { consensusModule0 = ConsensusModule.launch(moduleCtx0); consensusModule1 = ConsensusModule.launch(moduleCtx1); container0 = ClusteredServiceContainer.launch(containerCtx0); container1 = ClusteredServiceContainer.launch(containerCtx1); final MutableReference<String> egress = new MutableReference<>(); final EgressListener egressListener = (clusterSessionId, timestamp, buffer, offset, length, header) -> egress.set(buffer.getStringWithoutLengthAscii(offset, length)); client0 = AeronCluster.connect(new AeronCluster.Context() .egressListener(egressListener) .ingressChannel(moduleCtx0.ingressChannel()) .egressChannel("aeron:udp?endpoint=localhost:9020")); client1 = AeronCluster.connect(new AeronCluster.Context() .egressListener(egressListener) .ingressChannel(moduleCtx1.ingressChannel()) .egressChannel("aeron:udp?endpoint=localhost:9021")); echoMessage(client0, "Message 0", egress); echoMessage(client1, "Message 1", egress); } finally { CloseHelper.closeAll(client0, client1, consensusModule0, consensusModule1, container0, container1); moduleCtx0.deleteDirectory(); moduleCtx1.deleteDirectory(); } } finally { archiveCtx.deleteDirectory(); driverCtx.deleteDirectory(); } } @Test @Timeout(30) public void shouldSupportTwoMultiNodeClusters() { try (MultiClusterNode node0 = new MultiClusterNode(0); MultiClusterNode node1 = new MultiClusterNode(1)) { final MutableReference<String> egress = new MutableReference<>(); final EgressListener egressListener = (clusterSessionId, timestamp, buffer, offset, length, header) -> egress.set(buffer.getStringWithoutLengthAscii(offset, length)); try ( AeronCluster client0 = AeronCluster.connect(new AeronCluster.Context() .aeronDirectoryName(node0.archivingMediaDriver.mediaDriver().aeronDirectoryName()) .egressListener(egressListener) .ingressChannel("aeron:udp?term-length=64k") .ingressEndpoints(TestCluster.ingressEndpoints(0, 2)) .egressChannel("aeron:udp?endpoint=localhost:9020")); AeronCluster client1 = AeronCluster.connect(new AeronCluster.Context() .aeronDirectoryName(node1.archivingMediaDriver.mediaDriver().aeronDirectoryName()) .egressListener(egressListener) .ingressChannel("aeron:udp?term-length=64k") .ingressEndpoints(TestCluster.ingressEndpoints(1, 2)) .egressChannel("aeron:udp?endpoint=localhost:9120"))) { echoMessage(client0, "Message 0", egress); echoMessage(client1, "Message 1", egress); } } } private void echoMessage(final AeronCluster client, final String message, final MutableReference<String> egress) { final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer(); final int length = buffer.putStringWithoutLengthAscii(0, message); while (client.offer(buffer, 0, length) < 0) { Tests.yield(); } egress.set(null); while (null == egress.get()) { Tests.yield(); client.pollEgress(); } assertEquals(message, egress.get()); } static class EchoService extends StubClusteredService { public void onSessionMessage( final ClientSession session, final long timestamp, final DirectBuffer buffer, final int offset, final int length, final Header header) { idleStrategy.reset(); while (session.offer(buffer, offset, length) < 0) { idleStrategy.idle(); } } } static class MultiClusterNode implements AutoCloseable { final int nodeId; final ArchivingMediaDriver archivingMediaDriver; final ConsensusModule consensusModule0; final ClusteredServiceContainer container0; AeronCluster client0; final ConsensusModule consensusModule1; final ClusteredServiceContainer container1; AeronCluster client1; MultiClusterNode(final int nodeId) { this.nodeId = nodeId; final MediaDriver.Context driverCtx = new MediaDriver.Context() .aeronDirectoryName(CommonContext.getAeronDirectoryName() + "-" + nodeId) .threadingMode(ThreadingMode.SHARED) .errorHandler(Tests::onError) .dirDeleteOnStart(true); final Archive.Context archiveCtx = new Archive.Context() .threadingMode(ArchiveThreadingMode.SHARED) .archiveDir(new File(SystemUtil.tmpDirName(), "archive-" + nodeId)) .controlChannel("aeron:udp?endpoint=localhost:801" + nodeId) .errorHandler(Tests::onError) .recordingEventsEnabled(false) .deleteArchiveOnStart(true); archivingMediaDriver = ArchivingMediaDriver.launch(driverCtx, archiveCtx); consensusModule0 = consensusModule(0, driverCtx.aeronDirectoryName()); container0 = container(consensusModule0.context()); consensusModule1 = consensusModule(1, driverCtx.aeronDirectoryName()); container1 = container(consensusModule1.context()); } public void close() { CloseHelper.closeAll( client0, consensusModule0, container0, client1, consensusModule1, container1, archivingMediaDriver); consensusModule0.context().deleteDirectory(); consensusModule1.context().deleteDirectory(); archivingMediaDriver.archive().context().deleteDirectory(); archivingMediaDriver.mediaDriver().context().deleteDirectory(); } ConsensusModule consensusModule(final int clusterId, final String aeronDirectoryName) { final int nodeOffset = (clusterId * 100) + (nodeId * 10); final ConsensusModule.Context ctx = new ConsensusModule.Context() .clusterMemberId(nodeId) .clusterId(clusterId) .errorHandler(Tests::onError) .deleteDirOnStart(true) .aeronDirectoryName(aeronDirectoryName) .clusterDir(new File(SystemUtil.tmpDirName(), "cluster-" + nodeId + "-" + clusterId)) .clusterMembers(TestCluster.clusterMembers(clusterId, 2)) .logChannel("aeron:udp?term-length=64k") .serviceStreamId(104 + nodeOffset) .consensusModuleStreamId(105 + nodeOffset) .ingressChannel("aeron:udp?term-length=64k"); return ConsensusModule.launch(ctx); } ClusteredServiceContainer container(final ConsensusModule.Context moduleCtx) { final ClusteredServiceContainer.Context ctx = new ClusteredServiceContainer.Context() .clusterId(moduleCtx.clusterId()) .errorHandler(Tests::onError) .clusteredService(new EchoService()) .aeronDirectoryName(moduleCtx.aeronDirectoryName()) .clusterDir(moduleCtx.clusterDir()) .serviceStreamId(moduleCtx.serviceStreamId()) .consensusModuleStreamId(moduleCtx.consensusModuleStreamId()); return ClusteredServiceContainer.launch(ctx); } } }