/*
* Part of the CCNx Java Library.
*
* Copyright (C) 2011-2012 Palo Alto Research Center, Inc.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details. You should have received
* a copy of the GNU Lesser General Public License along with this library;
* if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
* Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.ccnx.ccn.test.profiles.versioning;
import java.io.IOException;
import java.util.Random;
import java.util.TreeSet;
import java.util.logging.Level;
import junit.framework.Assert;
import org.ccnx.ccn.CCNHandle;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.profiles.versioning.InterestData;
import org.ccnx.ccn.profiles.versioning.VersionNumber;
import org.ccnx.ccn.profiles.versioning.VersioningInterestManager;
import org.ccnx.ccn.protocol.CCNTime;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.ContentObject;
import org.ccnx.ccn.protocol.Interest;
import org.ccnx.ccn.protocol.MalformedContentNameStringException;
import org.ccnx.ccn.test.profiles.versioning.VersioningHelper.SinkHandle;
import org.ccnx.ccn.test.profiles.versioning.VersioningHelper.TestListener;
import org.ccnx.ccn.test.profiles.versioning.VersioningHelper.TestVIM;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* Test the receive method, and check how interests are rebuilt
*/
public class VersioningInterestManagerTestRepo {
protected final Random _rnd = new Random();
protected final static long TIMEOUT=30000;
protected final static long SEND_PAUSE = 100;
protected final static int LONG_SEND_MULTIPLE = 30;
protected final ContentName prefix;
protected CCNHandle realhandle = null;
protected SinkHandle sinkhandle = null;
public VersioningInterestManagerTestRepo() throws MalformedContentNameStringException {
prefix = ContentName.fromNative(String.format("/repotest/test_%016X", _rnd.nextLong()));
}
@BeforeClass
public static void setUpBeforeClass() throws Exception {
Log.setLevel(Log.FAC_ALL, Level.WARNING);
Log.setLevel(Log.FAC_ENCODING, Level.WARNING);
}
@Before
public void setUp() throws Exception {
realhandle = CCNHandle.open();
sinkhandle = SinkHandle.open(realhandle);
}
@After
public void tearDown() throws Exception {
realhandle.close();
sinkhandle.close();
}
/**
* Send a stream of versions from the right to the left in order. Sends
* just enough to fill to FIL_MAX and verify we have exactly 1 interest.
* Then sends 1 more exclusion and verifies that the interest was
* split the right way.
* @throws Exception
*/
@Test
public void testStreamFromRight() throws Exception {
System.out.println("********** testStreamFromRight");
ContentName basename = new ContentName(prefix, String.format("content_%016X", _rnd.nextLong()));
TestListener listener = new TestListener();
TestVIM vim = new TestVIM(sinkhandle, basename, null, VersionNumber.getMinimumVersion(), listener);
vim.setSendInterest(true);
vim.start();
// Verify that we have 1 interest pending
Assert.assertTrue( sinkhandle.count.waitForValue(1, TIMEOUT) );
// send MAX_FILL items, should only be one interest
CCNTime now = CCNTime.now();
long t = now.getTime();
int tosend = VersioningInterestManager.MAX_FILL;
System.out.println("***** Sending stream 1 *****");
TreeSet<CCNTime> sent1 = sendStreamRight(sinkhandle, vim, basename, t, tosend);
// wait for them to be received
Assert.assertTrue( sinkhandle.total_count.waitForValue(tosend + 1, TIMEOUT));
// we should see only the desired number of interests
Assert.assertEquals(1, vim.getInterestDataTree().size());
Assert.assertEquals( sent1.size(), vim.getExclusions().size());
// System.out.println(String.format("data (%d): %s", vim.getInterestDataTree().first().size(), vim.getInterestDataTree().first().dumpContents()));
System.out.println("***** Sending stream 2 *****");
// now send one more and we should see the right sort of split
TreeSet<CCNTime> sent2 = sendStreamRight(sinkhandle, vim, basename, t, 1);
sent1.addAll(sent2);
// wait for them to be received
// it's 23 because: 20 to start with + 1 extra, then that one is replaced by 2 because
// the new interest is split.
boolean b = sinkhandle.total_count.waitForValue(tosend + 3, TIMEOUT);
Assert.assertTrue("sinkhandle incorrect count: " + sinkhandle.total_count.getValue(), b);
// make sure we have all the right versions
System.out.println("total sent : " + sent1.size());
System.out.println("exclusion size: " + vim.getExclusions().size());
// the new left one should have MIN_FILL elements and the old (right) one should have the rest
InterestData left = vim.getInterestDataTree().first();
InterestData right = vim.getInterestDataTree().last();
int left_size = VersioningInterestManager.MIN_FILL;
int right_size = sent1.size() - left_size;
if( left.size() != left_size || right.size() != right_size ) {
System.out.println(String.format("truth (%d): %s", vim.getExclusions().size(), vim.dumpExcluded()));
System.out.println(String.format("left (%d): %s", left.size(), left.dumpContents()));
System.out.println(String.format("right (%d): %s", right.size(), right.dumpContents()));
}
vim.stop();
Assert.assertEquals(sent1.size(), left_size + right_size);
// there should now be 2 extrea re-expressions because of extra interest
Assert.assertEquals(2, vim.getInterestDataTree().size());
Assert.assertEquals( sent1.size(), vim.getExclusions().size());
Assert.assertEquals(left_size, left.size());
Assert.assertEquals(right_size, right.size());
}
/**
* Send a very long stream from the right
* @throws Exception
*/
@Test
public void testLongStreamFromRight() throws Exception {
System.out.println("********** testLongStreamFromRight");
ContentName basename = new ContentName(prefix, String.format("content_%016X", _rnd.nextLong()));
TestListener listener = new TestListener();
TestVIM vim = new TestVIM(sinkhandle, basename, null, VersionNumber.getMinimumVersion(), listener);
vim.setSendInterest(true);
vim.start();
// Verify that we have 1 interest pending
Assert.assertTrue( sinkhandle.count.waitForValue(1, TIMEOUT) );
// send MAX_FILL items, should only be one interest
CCNTime now = CCNTime.now();
long t = now.getTime();
int tosend = VersioningInterestManager.MAX_FILL * LONG_SEND_MULTIPLE;
// How many interets will this be? Every time it fills an interest, it will
// leave MAX_FILL - MIN_FILL in that interest, then shift MIN_FILL to the left.
int packets = 1;
int occupancy = 0;
for(int i = 0; i < tosend; i++) {
if( occupancy >= VersioningInterestManager.MAX_FILL ) {
packets++;
occupancy = VersioningInterestManager.MIN_FILL;
}
occupancy++;
}
System.out.println(String.format("Sending %d exclusions should result in %d interest packets", tosend, packets));
System.out.println("***** Sending stream 1 *****");
TreeSet<CCNTime> sent1 = sendStreamRight(sinkhandle, vim, basename, t, tosend);
// There will be 1 interest per exclusion plus the number of outstanding packets
boolean b = sinkhandle.total_count.waitForValue(tosend + packets, TIMEOUT);
Assert.assertTrue("sinkhandle incorrect count: " + sinkhandle.total_count.getValue(), b);
// we should see only the desired number of interests
Assert.assertEquals(packets, vim.getInterestDataTree().size());
Assert.assertEquals(sent1.size(), vim.getExclusions().size());
vim.stop();
}
/**
* Send a very long stream from the right
* @throws Exception
*/
@Test
public void testLongStreamFromLeft() throws Exception {
System.out.println("********** testLongStreamFromLeft");
ContentName basename = new ContentName(prefix, String.format("content_%016X", _rnd.nextLong()));
TestListener listener = new TestListener();
TestVIM vim = new TestVIM(sinkhandle, basename, null, VersionNumber.getMinimumVersion(), listener);
vim.setSendInterest(true);
vim.start();
// Verify that we have 1 interest pending
Assert.assertTrue( sinkhandle.count.waitForValue(1, TIMEOUT) );
// send MAX_FILL items, should only be one interest
CCNTime now = CCNTime.now();
long t = now.getTime();
int tosend = VersioningInterestManager.MAX_FILL * LONG_SEND_MULTIPLE;
// How many interets will this be? Every time it fills an interest, it will
// leave MAX_FILL - MIN_FILL in that interest, then shift MIN_FILL to the left.
int packets = 1;
int occupancy = 0;
for(int i = 0; i < tosend; i++) {
if( occupancy >= VersioningInterestManager.MAX_FILL ) {
packets++;
occupancy = VersioningInterestManager.MIN_FILL;
}
occupancy++;
}
System.out.println(String.format("Sending %d exclusions should result in %d interest packets", tosend, packets));
System.out.println("***** Sending stream 1 *****");
TreeSet<CCNTime> sent1 = sendStreamLeft(sinkhandle, vim, basename, t, tosend);
int expected = tosend + packets + 1;
boolean b = sinkhandle.total_count.waitForValue(expected, TIMEOUT);
// we should see only the desired number of interests
Assert.assertEquals(packets, vim.getInterestDataTree().size());
Assert.assertEquals(sent1.size(), vim.getExclusions().size());
Assert.assertTrue(
String.format("sinkhandle incorrect count %d expected %d",
sinkhandle.total_count.getValue(),
expected), b);
vim.stop();
}
/**
* Send a very long stream with arrivals uniformly over some interval
* @throws Exception
*/
@Test
public void testLongStreamUniform() throws Exception {
System.out.println("********** testLongStreamUniform");
ContentName basename = new ContentName(prefix, String.format("content_%016X", _rnd.nextLong()));
TestListener listener = new TestListener();
TestVIM vim = new TestVIM(sinkhandle, basename, null, VersionNumber.getMinimumVersion(), listener);
vim.setSendInterest(true);
vim.start();
int tosend = VersioningInterestManager.MAX_FILL * LONG_SEND_MULTIPLE;
// send MAX_FILL items, should only be one interest
CCNTime now = CCNTime.now();
long max_spacing = 20000;
long start_time = now.getTime();
long stop_time = start_time + tosend * max_spacing;
System.out.println("***** Sending stream 1 *****");
sendStreamUniform(sinkhandle, vim, basename, start_time, stop_time, tosend);
// wait a while
Thread.sleep(10000);
// we dont know how many interets this will take, but we can bound it
int min_interests = (int) Math.ceil((double) tosend / VersioningInterestManager.MAX_FILL);
int max_interests = (int) Math.floor((double) tosend / VersioningInterestManager.MIN_FILL);
System.out.println("handle interests: " + sinkhandle.total_count.getValue());
System.out.println("exclusions : " + vim.getExclusions().size());
System.out.println("vim interests : " + vim.getInterestDataTree().size() + " (should be close to min interests)");
System.out.println("min interests : " + min_interests);
System.out.println("max interests : " + max_interests);
Assert.assertTrue( min_interests <= vim.getInterestDataTree().size() );
Assert.assertTrue( vim.getInterestDataTree().size() <= max_interests );
Assert.assertTrue( min_interests + tosend <= sinkhandle.total_count.getValue() );
Assert.assertTrue( sinkhandle.total_count.getValue() <= max_interests + tosend );
vim.stop();
}
/**
* Send a very long stream with arrivals normally distributed around a spot
* @throws Exception
*/
@Test
public void testLongStreamGaussian() throws Exception {
System.out.println("********** testLongStreamGaussian");
ContentName basename = new ContentName(prefix, String.format("content_%016X", _rnd.nextLong()));
TestListener listener = new TestListener();
TestVIM vim = new TestVIM(sinkhandle, basename, null, VersionNumber.getMinimumVersion(), listener);
vim.setSendInterest(true);
vim.start();
int tosend = VersioningInterestManager.MAX_FILL * LONG_SEND_MULTIPLE;
// send MAX_FILL items, should only be one interest
CCNTime now = CCNTime.now();
int max_spacing = 20000;
double mean_time = now.getTime();
double std_time = tosend * max_spacing;
System.out.println("***** Sending stream 1 *****");
sendStreamGaussian(sinkhandle, vim, basename, mean_time, std_time, tosend);
// wait a while
Thread.sleep(10000);
// we dont know how many interets this will take, but we can bound it
int min_interests = (int) Math.ceil((double) tosend / VersioningInterestManager.MAX_FILL);
int max_interests = (int) Math.floor((double) tosend / VersioningInterestManager.MIN_FILL);
System.out.println("handle interests: " + sinkhandle.total_count.getValue());
System.out.println("exclusions : " + vim.getExclusions().size());
System.out.println("vim interests : " + vim.getInterestDataTree().size() + " (should be close to min interests)");
System.out.println("min interests : " + min_interests);
System.out.println("max interests : " + max_interests);
Assert.assertTrue( min_interests <= vim.getInterestDataTree().size() );
Assert.assertTrue( vim.getInterestDataTree().size() <= max_interests );
Assert.assertTrue( min_interests + tosend <= sinkhandle.total_count.getValue() );
Assert.assertTrue( sinkhandle.total_count.getValue() <= max_interests + tosend );
vim.stop();
}
// ==============================
private TreeSet<CCNTime> sendStreamGaussian(CCNHandle handle, TestVIM vim, ContentName name, double mean_time, double std_time, int count) throws Exception {
TreeSet<CCNTime> sent = new TreeSet<CCNTime>();
for(int i = 0; i < count; i++) {
CCNTime version;
// avoid sending duplicate version numbers
do {
double d = std_time * _rnd.nextGaussian() + mean_time;
version = new CCNTime(Math.round(d));
} while( !sent.add(version));
send(handle, vim, name, version);
}
return sent;
}
private TreeSet<CCNTime> sendStreamUniform(CCNHandle handle, TestVIM vim, ContentName name, long start_time, long stop_time, int count) throws Exception {
TreeSet<CCNTime> sent = new TreeSet<CCNTime>();
int width = (int) (stop_time - start_time + 1);
for(int i = 0; i < count; i++) {
CCNTime version;
// avoid sending duplicate version numbers
do {
int delta = _rnd.nextInt(width);
version = new CCNTime(start_time + delta);
} while( !sent.add(version));
send(handle, vim, name, version);
}
return sent;
}
private TreeSet<CCNTime> sendStreamRight(CCNHandle handle, TestVIM vim, ContentName name, long t, int count) throws Exception {
TreeSet<CCNTime> sent = new TreeSet<CCNTime>();
for(int i = 0; i < count; i++) {
// walk backwares from 10 msec to 100 sec
t -= _rnd.nextInt(100000) + 10;
CCNTime version = new CCNTime(t);
sent.add(version);
send(handle, vim, name, version);
}
return sent;
}
private TreeSet<CCNTime> sendStreamLeft(CCNHandle handle, TestVIM vim, ContentName name, long t, int count) throws Exception {
TreeSet<CCNTime> sent = new TreeSet<CCNTime>();
for(int i = 0; i < count; i++) {
// walk backwares from 10 msec to 100 sec
t += _rnd.nextInt(100000) + 10;
CCNTime version = new CCNTime(t);
sent.add(version);
send(handle, vim, name, version);
}
return sent;
}
private void send(CCNHandle handle, TestVIM vim, ContentName name, CCNTime version) throws IOException, InterruptedException {
String mystring = "Hello, World!";
ContentName versionedNamed = new ContentName(name, version);
ContentObject data = ContentObject.buildContentObject(versionedNamed, mystring.getBytes());
// We are satisfying the interest, so it is no longer pending
Interest interest = sinkhandle.interests.get(0);
sinkhandle.cancelInterest(interest, vim);
Assert.assertNotNull(interest);
Interest newInterest = vim.exposeReceive(data, interest);
// this is normally done by handleContent
if( newInterest != null )
sinkhandle.expressInterest(newInterest, vim);
}
@SuppressWarnings("unused")
private void dumpstate(TestVIM vim) {
System.out.println("=========================================");
System.out.println("Sinkhandle state");
for(Interest interest : sinkhandle.interests )
System.out.println(interest.toString());
System.out.println("=========================================");
System.out.println("TestVM InterestData");
for(InterestData data : vim.getInterestDataTree() )
System.out.println(data.toString());
// System.out.println("=========================================");
// System.out.println("TestVM pending interests");
// for(InterestData data : vim.getInterestDataTree() )
// System.out.println(data.toString());
}
}