package net.tomp2p.p2p;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.NavigableSet;
import net.tomp2p.connection.ChannelCreator;
import net.tomp2p.futures.FutureChannelCreator;
import net.tomp2p.futures.FutureRouting;
import net.tomp2p.message.Message;
import net.tomp2p.p2p.builder.RoutingBuilder;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.peers.PeerMap;
import net.tomp2p.peers.PeerMapConfiguration;
import net.tomp2p.peers.PeerStatistic;
import net.tomp2p.peers.RTT;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
public class TestRTTRoutingComparator {
@Rule
public TestRule watcher = new TestWatcher() {
protected void starting(Description description) {
System.out.println("Starting test: " + description.getMethodName());
}
};
/**
* Two PeerStatistic with the same PeerIDs should compare to 0 (equality)
*/
@Test
public void testEquality() {
PeerAddress peer1 = PeerAddress.create(new Number160("0xa3fb3982c38193f12c40a3fb3982c38193f12c60"));
PeerStatistic peer1Statistic = new PeerStatistic(peer1).addRTT(new RTT(13, false).setEstimated());
PeerAddress peer2 = PeerAddress.create(new Number160("0xa3fb3982c38193f12c40a3fb3982c38193f12c60"));
PeerStatistic peer2Statistic = new PeerStatistic(peer2).addRTT(new RTT(44, true));
Comparator<PeerStatistic> comp = new RTTPeerStatisticComparator().getComparator(new Number160("0xfff"));
int compare = comp.compare(peer1Statistic,peer2Statistic);
Assert.assertEquals(compare, 0);
}
/**
* Two PeerStatistic with the same metrics but different PeerIDs should not compare to 0.
*/
@Test
public void testUnequality() {
PeerAddress peer1 = PeerAddress.create(new Number160("0xa3fb3982c38193f12c40a3fb3982c38193f12c61"));
PeerStatistic peer1Statistic = new PeerStatistic(peer1).addRTT(new RTT(44, true));
PeerAddress peer2 = PeerAddress.create(new Number160("0xa3fb3982c38193f12c40a3fb3982c38193f12c62"));
PeerStatistic peer2Statistic = new PeerStatistic(peer2).addRTT(new RTT(44, true));
Comparator<PeerStatistic> comp = new RTTPeerStatisticComparator().getComparator(new Number160("0xfff"));
int compare = comp.compare(peer1Statistic,peer2Statistic);
Assert.assertNotEquals(compare, 0);
}
/**
* A queue with the comparator should result in the correct order when polling
*/
@Test
public void testQueueToAskOrder() {
// Target location
Number160 location = new Number160("0xa3fb3982c38193f12c40a3fb3982c38193f12c40");
// 4 Peers with different (bucket)distances to location
PeerAddress peer1 = PeerAddress.create(new Number160("0xa3fb3982c38193f12c40a3fb3982c38193f12c60"));
PeerAddress peer2 = PeerAddress.create(new Number160("0xa3fb3982c38193f12c40a3fb3982000000000000"));
PeerAddress peer3 = PeerAddress.create(new Number160("0xa3fb3982c38193f12c40a3fb3982700000000000"));
PeerAddress peer4 = PeerAddress.create(new Number160("0xa3fb3982c38193f12c40a3fb3982c38193f12c70"));
// Test all bucket distances to location (this will be the first sorting criteria)
Assert.assertEquals(6, PeerMap.classMember(peer1.peerId(), location) + 1);
Assert.assertEquals(48, PeerMap.classMember(peer2.peerId(), location) + 1);
Assert.assertEquals(48, PeerMap.classMember(peer3.peerId(), location) + 1);
Assert.assertEquals(6, PeerMap.classMember(peer4.peerId(), location) + 1);
// Define Peer Statistics with some RTTs
PeerStatistic stat1 = new PeerStatistic(peer1).addRTT(new RTT(80, true));
PeerStatistic stat2 = new PeerStatistic(peer2).addRTT(new RTT(30, true));
PeerStatistic stat3 = new PeerStatistic(peer3).addRTT(new RTT(5, true)) .addRTT(new RTT(45, true));
PeerStatistic stat4 = new PeerStatistic(peer4); // no rtt available
// Create routing queue with comparator and add all statistics
UpdatableTreeSet<PeerStatistic> queueToAsk = new UpdatableTreeSet<PeerStatistic>(new RTTPeerStatisticComparator().getComparator(location));
queueToAsk.addAll(Arrays.asList(stat1,stat2,stat3,stat4));
// Sorting should be in the following priority order
// 1. KAD bucket distance (how many high-order bits are equal to destination)
// (Note that this is different from the complete XOR distance)
// 2. RTT information available: Those with RTT come before those without RTT
// 3. RTT Faster peers (low RTT) before slow peers (high RTT)
// 4. Complete XOR Distance
Iterator<PeerStatistic> it = queueToAsk.iterator();
Assert.assertEquals(queueToAsk.pollFirst().peerAddress(), peer1); // Dist 6, 80 RTT
Assert.assertEquals(queueToAsk.pollFirst().peerAddress(), peer4); // Dist 6, no RTT info
Assert.assertEquals(queueToAsk.pollFirst().peerAddress(), peer3); // Dist 48 25 RTT
Assert.assertEquals(queueToAsk.pollFirst().peerAddress(), peer2); // Dist 48 30ms RTT
}
@Test
public void testRouting() throws IOException, InterruptedException {
Peer startPeer = null;
ChannelCreator cc = null;
try {
// Create Peer from which we start a routing request with the RTT comparator
Number160 peerID = new Number160("0x1");
PeerMapConfiguration peerMapConfiguration = new PeerMapConfiguration(peerID);
peerMapConfiguration.setPeerStatisticComparator(new RTTPeerStatisticComparator());
PeerMap peerMap = new PeerMap(peerMapConfiguration);
startPeer = new PeerBuilder(peerID).peerMap(peerMap).ports(4001).start();
// Create routing location and two peers
Number160 location = new Number160("0xff0000");
Peer peer1 = new PeerBuilder(new Number160("0xffffff")).masterPeer(startPeer).start();
Peer peer2 = new PeerBuilder(new Number160("0xfff000")).masterPeer(startPeer).start();
// Make sure that both peers have the same bucket distance
Assert.assertEquals(0, PeerMap.classCloser(location, peer2.peerAddress(), peer1.peerAddress()));
// ... but peer2 is closer in the full XOR distance
Assert.assertEquals(-1, PeerMap.isKadCloser(location, peer2.peerAddress(), peer1.peerAddress()));
// Add the two peers with a slow and fast response time to the peer's PeerMap
startPeer.peerBean().peerMap().peerFound(peer2.peerAddress(), null, null, new RTT(100, true));
startPeer.peerBean().peerMap().peerFound(peer1.peerAddress(), null, null, new RTT(70, true));
// The peer1 has better RTT and should be the first in the queue of close peers to the location
NavigableSet<PeerStatistic> closePeers = startPeer.peerBean().peerMap().closePeers(location, 2);
Assert.assertEquals(closePeers.pollFirst().peerAddress(), peer1.peerAddress());
Assert.assertEquals(closePeers.pollFirst().peerAddress(), peer2.peerAddress());
// The peer2 gets an updated RTT (new average: 60ms)
startPeer.peerBean().peerMap().peerFound(peer2.peerAddress(), null, null, new RTT(10, true));
// ... and should now be the first in the queue since 60 ms < 70 ms
closePeers = startPeer.peerBean().peerMap().closePeers(location, 2);
Assert.assertEquals(closePeers.pollFirst().peerAddress(), peer2.peerAddress());
Assert.assertEquals(closePeers.pollFirst().peerAddress(), peer1.peerAddress());
// Start an actual routing
FutureChannelCreator fcc = startPeer.connectionBean().reservation().create(1, 0);
fcc.awaitUninterruptibly();
cc = fcc.channelCreator();
RoutingBuilder routingBuilder = new RoutingBuilder();
routingBuilder.locationKey(location);
routingBuilder.maxDirectHits(1);
routingBuilder.setMaxNoNewInfo(0);
routingBuilder.maxFailures(0);
routingBuilder.maxSuccess(1); // Only allow 1 success
routingBuilder.parallel(1); // ... and 1 request at a time
FutureRouting fr = startPeer.distributedRouting().route(routingBuilder, Message.Type.REQUEST_1, cc);
fr.awaitUninterruptibly();
// Best hit is the faster peer (peer2)
Assert.assertEquals(peer2.peerAddress(), fr.potentialHits().pollFirst() );
// The startPeer should only have contacted peer2 and not peer1 (because of maxSuccess=1, parallel=1)
Assert.assertTrue(peer2.peerBean().peerMap().contains(startPeer.peerAddress()));
Assert.assertFalse(peer1.peerBean().peerMap().contains(startPeer.peerAddress()));
} finally {
cc.shutdown().await();
startPeer.shutdown().awaitUninterruptibly();
}
}
}