/*
* Copyright 2016 higherfrequencytrading.com
*
* 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
*
* http://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 net.openhft.chronicle.engine.map;
import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.core.OS;
import net.openhft.chronicle.core.onoes.ExceptionKey;
import net.openhft.chronicle.core.pool.ClassAliasPool;
import net.openhft.chronicle.core.threads.ThreadDump;
import net.openhft.chronicle.engine.ChronicleMapKeyValueStoreTest;
import net.openhft.chronicle.engine.api.EngineReplication;
import net.openhft.chronicle.engine.api.map.KeyValueStore;
import net.openhft.chronicle.engine.api.map.MapView;
import net.openhft.chronicle.engine.api.tree.AssetTree;
import net.openhft.chronicle.engine.fs.ChronicleMapGroupFS;
import net.openhft.chronicle.engine.fs.Clusters;
import net.openhft.chronicle.engine.fs.EngineHostDetails;
import net.openhft.chronicle.engine.fs.FilePerKeyGroupFS;
import net.openhft.chronicle.engine.server.ServerEndpoint;
import net.openhft.chronicle.engine.tree.VanillaAssetTree;
import net.openhft.chronicle.network.TCPRegistry;
import net.openhft.chronicle.network.VanillaSessionDetails;
import net.openhft.chronicle.network.api.session.SessionDetails;
import net.openhft.chronicle.network.connection.TcpChannelHub;
import net.openhft.chronicle.wire.WireType;
import net.openhft.chronicle.wire.YamlLogging;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentMap;
import static org.junit.Assert.assertNotNull;
/**
* Created by Rob Austin
*/
@Ignore("todo fix - ROB")
public class ReplicationTestBootstrappingAfterLostConnection {
public static final WireType WIRE_TYPE = WireType.TEXT;
public static final String NAME = "/ChMaps/test";
public static ServerEndpoint serverEndpoint1;
public static ServerEndpoint serverEndpoint2;
private static AssetTree tree3;
private static AssetTree tree1;
private static AssetTree tree2;
private static ThreadDump threadDump;
private static Map<ExceptionKey, Integer> exceptions;
@BeforeClass
public static void before() throws IOException {
exceptions = Jvm.recordExceptions();
YamlLogging.setAll(false);
ClassAliasPool.CLASS_ALIASES.addAlias(ChronicleMapGroupFS.class);
ClassAliasPool.CLASS_ALIASES.addAlias(FilePerKeyGroupFS.class);
//Delete any files from the last run
Files.deleteIfExists(Paths.get(OS.TARGET, NAME));
TCPRegistry.createServerSocketChannelFor("host.port1", "host.port2", "host.port3");
@NotNull WireType writeType = WireType.TEXT;
tree1 = create(1, writeType, "clusterTwo");
serverEndpoint1 = new ServerEndpoint("host.port1", tree1);
tree2 = create(2, writeType, "clusterTwo");
serverEndpoint2 = new ServerEndpoint("host.port2", tree2);
}
@AfterClass
public static void after() {
if (serverEndpoint1 != null)
serverEndpoint1.close();
if (serverEndpoint2 != null)
serverEndpoint2.close();
if (tree1 != null)
tree1.close();
if (tree2 != null)
tree2.close();
TcpChannelHub.closeAllHubs();
TCPRegistry.reset();
threadDump.assertNoNewThreads();
}
@NotNull
private static AssetTree create(final int hostId, WireType writeType, final String
clusterName) {
@NotNull AssetTree tree = new VanillaAssetTree((byte) hostId)
.forTesting()
.withConfig(resourcesDir() + "/cmkvst", OS.TARGET + "/" + hostId);
tree.root().addWrappingRule(MapView.class, "map directly to KeyValueStore",
VanillaMapView::new,
KeyValueStore.class);
tree.root().addLeafRule(EngineReplication.class, "Engine replication holder",
CMap2EngineReplicator::new);
tree.root().addLeafRule(KeyValueStore.class, "KVS is Chronicle Map", (context, asset) ->
new ChronicleMapKeyValueStore(context.wireType(writeType).cluster(clusterName),
asset));
// VanillaAssetTreeEgMain.registerTextViewofTree("host " + hostId, tree);
return tree;
}
@NotNull
public static String resourcesDir() {
String path = ChronicleMapKeyValueStoreTest.class.getProtectionDomain().getCodeSource().getLocation().getPath();
if (path == null)
return ".";
return new File(path).getParentFile().getParentFile() + "/src/test/resources";
}
@BeforeClass
public void threadDump() {
threadDump = new ThreadDump();
}
@Test
public void testCreateAndThenFindView() {
@NotNull final String userId = "myUser";
@NotNull final String securityToken = "myToken";
@NotNull final String domain = "myDomain";
tree1.root().addView(SessionDetails.class, VanillaSessionDetails.of(userId,
securityToken, domain));
@Nullable final SessionDetails view = tree1.root().findView(SessionDetails.class);
Assert.assertNotNull(view);
Assert.assertEquals(userId, view.userId());
Assert.assertEquals(securityToken, view.securityToken());
Assert.assertEquals(domain, view.domain());
}
@After
public void afterMethod() {
}
@Test
public void testBootstrapWhenTheClientConnectionIsKilled() throws InterruptedException {
@NotNull final ConcurrentMap<String, String> map1 = tree2.acquireMap(NAME, String.class, String
.class);
assertNotNull(map1);
map1.put("hello1", "world1");
@NotNull final ConcurrentMap<String, String> map2 = tree1.acquireMap(NAME, String.class, String
.class);
assertNotNull(map2);
map2.put("hello2", "world2");
checkEqual(map1, map2, 2);
simulateSomeonePullingOutTheNetworkCableAndPluginItBackIn();
map2.put("hello5", "world5");
map1.put("hello4", "world4");
map2.put("hello3", "world3");
map1.put("hello6", "world6");
checkEqual(map1, map2, 6);
}
private void simulateSomeonePullingOutTheNetworkCableAndPluginItBackIn() {
@NotNull final Collection<EngineHostDetails> cluster = cluster();
@NotNull final Iterator<EngineHostDetails> iterator = cluster.iterator();
iterator.next();
final TcpChannelHub tcpChannelHub = iterator.next().tcpChannelHub();
tcpChannelHub.forceDisconnect();
}
private Collection<EngineHostDetails> cluster() {
@Nullable final Clusters clusters = tree1.root().getView(Clusters.class);
return clusters.get("clusterTwo").hostDetails();
}
private void checkEqual(@NotNull ConcurrentMap<String, String> map1, @NotNull ConcurrentMap<String, String> map2, final int expectedSize) {
for (int j = 0; j < 5; j++) {
for (int i = 1; i <= 50; i++) {
if (map1.size() == expectedSize && map2.size() == expectedSize)
break;
Jvm.pause(10);
}
}
// we wrap the maps in a tree-map to ensure that thier order is the same as
// this make it easier to compare when looking at them
Assert.assertEquals(new TreeMap<>(map1), new TreeMap<String, String>(map2));
}
@Test
public void testBootstrapWhenTheServerIsKilled() throws InterruptedException, IOException {
@NotNull ConcurrentMap<String, String> map1 = tree1.acquireMap(NAME
, String.class,
String
.class);
assertNotNull(map1);
map1.put("hello1", "world1");
@NotNull final ConcurrentMap<String, String> map2 = tree2.acquireMap(NAME, String.class, String
.class);
assertNotNull(map2);
map2.put("hello2", "world2");
checkEqual(map1, map2, 2);
serverEndpoint1.close();
if (tree1 != null)
tree1.close();
map2.put("hello3", "world3");
tree1 = create(1, WireType.TEXT, "clusterTwo");
serverEndpoint1 = new ServerEndpoint("host.port1", tree1);
map1 = tree1.acquireMap(NAME
, String.class, String.class);
// given that the old map1 has been shut down this will cause and exception to be thrown
// and map2 will attempt a reconnect to map1
map2.put("hello4", "world4");
map1.put("hello5", "world5");
checkEqual(map1, map2, 6);
}
@Test
public void testBootstrapWhenTheClientIsKilled() throws InterruptedException, IOException {
@NotNull ConcurrentMap<String, String> map1 = tree1.acquireMap(NAME
, String.class,
String
.class);
assertNotNull(map1);
map1.put("hello1", "world1");
@NotNull ConcurrentMap<String, String> map2 = tree2.acquireMap(NAME, String.class, String
.class);
assertNotNull(map2);
map2.put("hello2", "world2");
checkEqual(map1, map2, 2);
serverEndpoint2.close();
if (tree2 != null)
tree2.close();
map1.put("hello3", "world3");
tree2 = create(2, WireType.TEXT, "clusterTwo");
serverEndpoint2 = new ServerEndpoint("host.port2", tree2);
map2 = tree2.acquireMap(NAME, String.class, String.class);
// given that the old map1 has been shut down this will cause and exception to be thrown
// and map2 will attempt a reconnect to map1
map2.put("hello4", "world4");
map1.put("hello5", "world5");
checkEqual(map1, map2, 5);
}
@Test
public void testCheckDataIsLoadedFromPersistedFile() throws InterruptedException,
IOException {
final Path basePath = Files.createTempDirectory("temp");
//create the map with a single entry
@NotNull ConcurrentMap<String, String> map1 = tree1.acquireMap(NAME + "unique" + "?basePath=" + basePath
, String.class,
String
.class);
assertNotNull(map1);
map1.put("hello1", "world1");
Jvm.pause(100);
//close the map
serverEndpoint1.close();
tree1.close();
//recreate the map and load off the persisted file
tree1 = create(1, WireType.TEXT, "clusterTwo");
serverEndpoint1 = new ServerEndpoint("host.port3", tree1);
@NotNull ConcurrentMap<String, String> map1a = tree1.acquireMap(NAME + "unique" + "?basePath=" +
basePath
, String.class, String.class);
assertNotNull(map1a);
Jvm.pause(100);
Assert.assertEquals(1, map1a.size());
}
@Test
public void testBootstrapWhenTheServerIsKilledUsingPersistedFile() throws InterruptedException,
IOException {
final Path basePath = Files.createTempDirectory("");
@NotNull ConcurrentMap<String, String> map1 = tree1.acquireMap(NAME + "?basePath=" + basePath
, String.class, String.class);
assertNotNull(map1);
map1.put("hello1", "world1");
@NotNull final ConcurrentMap<String, String> map2 = tree2.acquireMap(NAME, String.class, String
.class);
assertNotNull(map2);
map2.put("hello2", "world2");
checkEqual(map1, map2, 2);
serverEndpoint1.close();
if (tree1 != null)
tree1.close();
map2.put("hello3", "world3");
tree1 = create(1, WireType.TEXT, "clusterTwo");
serverEndpoint1 = new ServerEndpoint("host.port1", tree1);
map1 = tree1.acquireMap(NAME + "?basePath=" + basePath
, String.class, String.class);
// given that the old map1 has been shut down this will cause and exception to be thrown
// and map2 will attempt a reconnect to map1
map2.put("hello4", "world4");
map1.put("hello5", "world5");
checkEqual(map1, map2, 5);
}
}