/* * A CCNx library test. * * Copyright (C) 2011 Palo Alto Research Center, Inc. * * This work is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License version 2 as published by the * Free Software Foundation. * This work 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 General Public License * for more details. You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ package org.ccnx.ccn.test.io.content; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.Random; import java.util.TreeSet; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import junit.framework.Assert; import org.ccnx.ccn.CCNInterestHandler; import org.ccnx.ccn.KeyManager; import org.ccnx.ccn.impl.CCNNetworkManager; import org.ccnx.ccn.impl.CCNFlowControl.SaveType; import org.ccnx.ccn.impl.security.keys.BasicKeyManager; import org.ccnx.ccn.impl.support.Log; import org.ccnx.ccn.io.content.CCNStringObject; import org.ccnx.ccn.io.content.LocalCopyListener; import org.ccnx.ccn.io.content.LocalCopyWrapper; import org.ccnx.ccn.profiles.VersioningProfile; import org.ccnx.ccn.profiles.ccnd.CCNDaemonException; import org.ccnx.ccn.profiles.ccnd.PrefixRegistrationManager; import org.ccnx.ccn.profiles.ccnd.PrefixRegistrationManager.ForwardingEntry; import org.ccnx.ccn.profiles.repo.RepositoryControl; import org.ccnx.ccn.profiles.repo.RepositoryOperations; import org.ccnx.ccn.protocol.ContentName; import org.ccnx.ccn.protocol.Interest; import org.ccnx.ccn.protocol.MalformedContentNameStringException; import org.ccnx.ccn.test.AssertionCCNHandle; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.w3c.dom.DOMException; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /** * Test RepositoryControl, LocalCopyListener, and LocalCopyWrapper to make sure * there are no dangling faces in ccnd when they stop. * * In general, the tests go like this: * - Create an Interest listener for our object * - Ask the repo to sync it * - Create the object and save it in the Interest handler. * - At each step, use the ccnd xml export to verify that the proper number of * prefixes are registered. */ public class LocalCopyTestRepo { AssertionCCNHandle readhandle; AssertionCCNHandle listenerhandle; int readFaceId; int listenerFaceId; BasicKeyManager km; final static Random _rnd = new Random(); final static String _prefix = String.format("/test_%016X", _rnd.nextLong()); final static int LONG_TIMEOUT = 1000; final static int SHORT_TIMEOUT = 500; final static int CHECK_TIMEOUT = 200; static int _port = CCNNetworkManager.DEFAULT_AGENT_PORT; static String _host = CCNNetworkManager.DEFAULT_AGENT_HOST; static String _ccndurl; // by faceid final HashMap<Integer,TreeSet<ContentName>> fentries = new HashMap<Integer, TreeSet<ContentName>>(); @BeforeClass public static void setUpBeforeClass() throws Exception { // Determine port at which to contact agent String portval = System.getProperty(CCNNetworkManager.PROP_AGENT_PORT); if (null != portval) { try { _port = new Integer(portval); } catch (Exception ex) { throw new IOException("Invalid port '" + portval + "' specified in " + CCNNetworkManager.PROP_AGENT_PORT); } Log.warning(Log.FAC_NETMANAGER, "Non-standard CCN agent port " + _port + " per property " + CCNNetworkManager.PROP_AGENT_PORT); } String hostval = System.getProperty(CCNNetworkManager.PROP_AGENT_HOST); if (null != hostval && hostval.length() > 0) { _host = hostval; Log.warning(Log.FAC_NETMANAGER, "Non-standard CCN agent host " + _host + " per property " + CCNNetworkManager.PROP_AGENT_HOST); } _ccndurl = String.format("http://%s:%d/?f=xml", _host, _port); Log.info("Using ccnd url: " + _ccndurl); } @Before public void setUp() throws Exception { Log.info("setUp"); km = new BasicKeyManager(); km.initialize(); KeyManager.setDefaultKeyManager(km); readhandle = AssertionCCNHandle.open(km); listenerhandle = AssertionCCNHandle.open(km); // Setup a prefix to get my face readFaceId = getFaceId(readhandle, "read"); listenerFaceId = getFaceId(listenerhandle, "listener"); Log.info(String.format("Face IDs: read %d, listen %d", readFaceId, listenerFaceId)); } @After public void tearDown() throws Exception { listenerhandle.close(); readhandle.close(); KeyManager.closeDefaultKeyManager(); } // @Test // public void testDumpFaces() throws Exception { // getfaces(); // } @Test public void testRepositoryControlObject() throws Exception { Log.info(Log.FAC_TEST, "Starting testRepositoryControlObject"); MyHandler listener = new MyHandler(); try { listener.open(); String namestring = String.format("%s/obj_%016X", _prefix, _rnd.nextLong()); ContentName name = ContentName.fromNative(namestring); CCNStringObject so_in = new CCNStringObject(name, readhandle); readhandle.checkError(LONG_TIMEOUT); Log.info("======= After reading string object"); getfaces(); Assert.assertEquals(1, dumpreg(readFaceId)); Assert.assertEquals(2, dumpreg(listenerFaceId)); RepositoryControl.localRepoSync(readhandle, so_in); readhandle.checkError(LONG_TIMEOUT); Log.info("======= After localRepoSync on string object"); getfaces(); Assert.assertEquals(1, dumpreg(readFaceId)); Assert.assertEquals(2, dumpreg(listenerFaceId)); so_in.close(); } finally { listener.close(); } Log.info(Log.FAC_TEST, "Completed testRepositoryControlObject"); } @Test public void testLocalCopyWrapper() throws Exception { Log.info(Log.FAC_TEST, "Starting testLocalCopyWrapper"); MyHandler listener = new MyHandler(); try { listener.open(); String namestring = String.format("%s/obj_%016X", _prefix, _rnd.nextLong()); ContentName name = ContentName.fromNative(namestring); CCNStringObject so_in = new CCNStringObject(name, readhandle); Thread.sleep(LONG_TIMEOUT); Log.info("======= After reading string object"); getfaces(); Assert.assertEquals(1, dumpreg(readFaceId)); Assert.assertEquals(2, dumpreg(listenerFaceId)); LocalCopyWrapper lcw = new LocalCopyWrapper(so_in); Thread.sleep(SHORT_TIMEOUT); Log.info("======= After LocalCopyWrapper on string object"); getfaces(); Assert.assertEquals(1, dumpreg(readFaceId)); Assert.assertEquals(2, dumpreg(listenerFaceId)); lcw.close(); Thread.sleep(SHORT_TIMEOUT); Log.info("======= After LocalCopyWrapper close"); getfaces(); Assert.assertEquals(1, dumpreg(readFaceId)); Assert.assertEquals(2, dumpreg(listenerFaceId)); } finally { listener.close(); } Log.info(Log.FAC_TEST, "Completed testLocalCopyWrapper"); } @Test public void testLocalCopyListener() throws Exception { Log.info(Log.FAC_TEST, "Starting testLocalCopyListener"); MyHandler listener = new MyHandler(); try { listener.open(); String namestring = String.format("%s/obj_%016X", _prefix, _rnd.nextLong()); ContentName name = ContentName.fromNative(namestring); CCNStringObject so_in = new CCNStringObject(name, readhandle); readhandle.checkError(CHECK_TIMEOUT); Thread.sleep(LONG_TIMEOUT); Log.info("======= After reading string object"); getfaces(); Assert.assertEquals(1, dumpreg(readFaceId)); Assert.assertEquals(2, dumpreg(listenerFaceId)); LocalCopyListener.startBackup(so_in); readhandle.checkError(CHECK_TIMEOUT); Thread.sleep(LONG_TIMEOUT); Log.info("======= After LocalCopyListener.startBackup"); getfaces(); Assert.assertEquals(1, dumpreg(readFaceId)); Assert.assertEquals(2, dumpreg(listenerFaceId)); } finally { listener.close(); } Log.info(Log.FAC_TEST, "Completed testLocalCopyListener"); } @Test public void testLocalCopyWrapperWithSaveAndLcwClose() throws Exception { Log.info(Log.FAC_TEST, "Starting testLocalCopyWrapperWithSaveAndLcwClose"); MyHandler listener = new MyHandler(); try { listener.open(); String namestring = String.format("%s/obj_%016X", _prefix, _rnd.nextLong()); ContentName name = ContentName.fromNative(namestring); CCNStringObject so_in = new CCNStringObject(name, readhandle); so_in.setupSave(SaveType.LOCALREPOSITORY); readhandle.checkError(LONG_TIMEOUT); Log.info("======= After reading string object"); getfaces(); Assert.assertEquals(1, dumpreg(readFaceId)); Assert.assertEquals(2, dumpreg(listenerFaceId)); LocalCopyWrapper lcw = new LocalCopyWrapper(so_in); readhandle.checkError(SHORT_TIMEOUT); Log.info("======= After LocalCopyWrapper on string object"); getfaces(); Assert.assertEquals(1, dumpreg(readFaceId)); Assert.assertEquals(2, dumpreg(listenerFaceId)); // Now modify the string object and save again. so_in.setData(String.format("%016X", _rnd.nextLong())); lcw.save(); so_in.update(); readhandle.checkError(LONG_TIMEOUT); Log.info("======= After LocalCopyWrapper save"); getfaces(); Assert.assertEquals(1, dumpreg(readFaceId)); Assert.assertEquals(2, dumpreg(listenerFaceId)); lcw.close(); readhandle.checkError(SHORT_TIMEOUT); Log.info("======= After LocalCopyWrapper close"); getfaces(); Assert.assertEquals(1, dumpreg(readFaceId)); Assert.assertEquals(2, dumpreg(listenerFaceId)); } finally { listener.close(); } Log.info(Log.FAC_TEST, "Completed testLocalCopyWrapperWithSaveAndLcwClose"); } @Test public void testLocalCopyWrapperWithSaveAndObjectClose() throws Exception { Log.info(Log.FAC_TEST, "Starting testLocalCopyWrapperWithSaveAndObjectClose"); MyHandler listener = new MyHandler(); try { listener.open(); String namestring = String.format("%s/obj_%016X", _prefix, _rnd.nextLong()); ContentName name = ContentName.fromNative(namestring); CCNStringObject so_in = new CCNStringObject(name, readhandle); so_in.setupSave(SaveType.LOCALREPOSITORY); readhandle.checkError(LONG_TIMEOUT); Log.info("======= After reading string object"); getfaces(); Assert.assertEquals(1, dumpreg(readFaceId)); Assert.assertEquals(2, dumpreg(listenerFaceId)); LocalCopyWrapper lcw = new LocalCopyWrapper(so_in); readhandle.checkError(SHORT_TIMEOUT); Log.info("======= After LocalCopyWrapper on string object"); getfaces(); Assert.assertEquals(1, dumpreg(readFaceId)); Assert.assertEquals(2, dumpreg(listenerFaceId)); // Now modify the string object and save again. so_in.setData(String.format("%016X", _rnd.nextLong())); lcw.save(); so_in.update(); readhandle.checkError(LONG_TIMEOUT); Log.info("======= After LocalCopyWrapper save"); getfaces(); Assert.assertEquals(1, dumpreg(readFaceId)); Assert.assertEquals(2, dumpreg(listenerFaceId)); // IMPORTANT: This in an incorrect usage, as we're closing the underlying // object, not the localcopywrapper. so_in.close(); readhandle.checkError(SHORT_TIMEOUT); Log.info("======= After LocalCopyWrapper close"); getfaces(); // IMPORTANT: Notice that the readFaceId still has 2 registrations. Assert.assertEquals(1, dumpreg(readFaceId)); Assert.assertEquals(2, dumpreg(listenerFaceId)); } finally { listener.close(); } Log.info(Log.FAC_TEST, "Completed testLocalCopyWrapperWithSaveAndObjectClose"); } @Test public void testLocalCopyListnerWithSaveAndObjectClose() throws Exception { Log.info(Log.FAC_TEST, "Starting testLocalCopyListnerWithSaveAndObjectClose"); MyHandler listener = new MyHandler(); try { listener.open(); String namestring = String.format("%s/obj_%016X", _prefix, _rnd.nextLong()); ContentName name = ContentName.fromNative(namestring); CCNStringObject so_in = new CCNStringObject(name, readhandle); so_in.setupSave(SaveType.LOCALREPOSITORY); readhandle.checkError(LONG_TIMEOUT); Log.info("======= After reading string object"); getfaces(); Assert.assertEquals(1, dumpreg(readFaceId)); Assert.assertEquals(2, dumpreg(listenerFaceId)); LocalCopyListener.startBackup(so_in); readhandle.checkError(LONG_TIMEOUT); Log.info("======= After LocalCopyWrapper on string object"); getfaces(); Assert.assertEquals(1, dumpreg(readFaceId)); Assert.assertEquals(2, dumpreg(listenerFaceId)); // Now modify the string object and save again. so_in.setData(String.format("%016X", _rnd.nextLong())); so_in.save(); so_in.update(); readhandle.checkError(LONG_TIMEOUT); Log.info("======= After LocalCopyWrapper save"); getfaces(); Assert.assertEquals(1, dumpreg(readFaceId)); Assert.assertEquals(2, dumpreg(listenerFaceId)); so_in.close(); readhandle.checkError(SHORT_TIMEOUT); Log.info("======= After LocalCopyWrapper close"); getfaces(); Assert.assertEquals(1, dumpreg(readFaceId)); Assert.assertEquals(2, dumpreg(listenerFaceId)); } finally { listener.close(); } Log.info(Log.FAC_TEST, "Completed testLocalCopyListnerWithSaveAndObjectClose"); } // ============================================================================================== /** * Interest listener to create random objects for the repo to sync * @author mmosko * */ private class MyHandler implements CCNInterestHandler { // These are the replies we have sent // public ConcurrentHashMap<ContentName, String> replies = new ConcurrentHashMap<ContentName, String>(); public HashSet<ContentName> replies = new HashSet<ContentName>(); boolean inListener = false; public void open() throws MalformedContentNameStringException, IOException, InterruptedException { listenerhandle.registerFilter(ContentName.fromNative(_prefix), this); listenerhandle.checkError(CHECK_TIMEOUT); } public void close() throws MalformedContentNameStringException, InterruptedException { listenerhandle.unregisterFilter(ContentName.fromNative(_prefix), this); synchronized (this) { while (inListener) { wait(); } } listenerhandle.checkError(CHECK_TIMEOUT); } public boolean handleInterest(Interest interest) { boolean ret = false; // Ignore start write requests if( RepositoryOperations.isStartWriteOperation(interest) ) return ret; if( RepositoryOperations.isCheckedWriteOperation(interest) ) return ret; synchronized(replies) { if( replies.contains(interest.name())) return ret; Log.info("handleInterest: " + interest.toString()); ContentName name = VersioningProfile.cutLastVersion(interest.name()); synchronized (this) { inListener = true; } try { String s = interest.name().toString(); CCNStringObject so = new CCNStringObject(name, s, SaveType.RAW, listenerhandle); so.saveLaterWithClose(); replies.add(interest.name()); ret = true; } catch (IOException e) { synchronized (this) { inListener = false; notifyAll(); } Assert.fail(e.getMessage()); } synchronized (this) { inListener = false; notifyAll(); } Log.info("handleInterest done: " + interest.toString()); return ret; } } } /* <ccnd> <identity><ccndid>0E0BBF5633A562DDD2D354150FBB6D0055042A389F10700D5C010DA621B6586E</ccndid> <apiversion>3002</apiversion> <starttime>1303848970.212333</starttime> <now>1303852704.538848</now> </identity> <cobs> <accessioned>302</accessioned> <stored>302</stored> <stale>47</stale> <sparse>0</sparse> <duplicate>0</duplicate> <sent>352</sent> </cobs> <interests> <names>15</names> <pending>0</pending> <propagating>0</propagating> <noted>0</noted> <accepted>376</accepted> <dropped>0</dropped> <sent>600</sent> <stuffed>0</stuffed> </interests> <faces> <face> <faceid>0</faceid> <faceflags>000c</faceflags> <pending>0</pending> <recvcount>0</recvcount> <meters> <bytein><total>45140</total><persec>0</persec></bytein><byteout><total>26665</total><persec>0</persec></byteout><datain><total>46</total><persec>0</persec></datain><introut><total>51</total><persec>0</persec></introut><dataout><total>0</total><persec>0</persec></dataout><intrin><total>0</total><persec>0</persec></intrin> </meters> </face> <face><faceid>1</faceid><faceflags>400c</faceflags><pending>0</pending><recvcount>0</recvcount></face> <face><faceid>2</faceid><faceflags>5012</faceflags><pending>0</pending><recvcount>0</recvcount><ip>0.0.0.0:9695</ip></face> <face><faceid>3</faceid><faceflags>5010</faceflags><pending>0</pending><recvcount>0</recvcount><ip>0.0.0.0:9695</ip></face> <face><faceid>4</faceid><faceflags>4042</faceflags><pending>0</pending><recvcount>0</recvcount><ip>[::]:9695</ip></face> <face><faceid>5</faceid><faceflags>4040</faceflags><pending>0</pending><recvcount>0</recvcount><ip>[::]:9695</ip></face> <face> <faceid>6</faceid> <faceflags>1014</faceflags> <pending>0</pending> <recvcount>6</recvcount> <ip>127.0.0.1:57037</ip> <meters><bytein><total>1949</total><persec>0</persec></bytein><byteout><total>4006</total><persec>0</persec></byteout><datain><total>1</total><persec>0</persec></datain><introut><total>1</total><persec>0</persec></introut><dataout><total>5</total><persec>0</persec></dataout><intrin><total>5</total><persec>0</persec></intrin> </meters> </face> <face><faceid>7</faceid><faceflags>1014</faceflags><pending>0</pending><recvcount>14</recvcount><ip>127.0.0.1:57040</ip><meters><bytein><total>4121</total><persec>0</persec></bytein><byteout><total>36582</total><persec>0</persec></byteout><datain><total>5</total><persec>0</persec></datain><introut><total>273</total><persec>0</persec></introut><dataout><total>7</total><persec>0</persec></dataout><intrin><total>9</total><persec>0</persec></intrin> </meters></face> </faces> <forwarding> <fentry> <prefix>ccnx:/%C1.M.S.localhost/%C1.M.SRV/ccnd</prefix> <dest><faceid>0</faceid> <flags>3</flags> <expires>2147479917</expires> </dest> </fentry> <fentry><prefix>ccnx:/ccnx/ping</prefix><dest><faceid>0</faceid><flags>3</flags><expires>2147479917</expires></dest></fentry><fentry><prefix>ccnx:/ccnx/%0E%0B%BFV3%A5b%DD%D2%D3T%15%0F%BBm%00U%04%2A8%9F%10p%0D%5C%01%0D%A6%21%B6Xn</prefix><dest><faceid>0</faceid><flags>17</flags><expires>2147479917</expires></dest></fentry><fentry><prefix>ccnx:/ccnx.org/Users/Repository/Keys/%C1.M.K%00%00%96%96c1%3D%A5%D5%E4%C8%29B%08%B1t%80D%1D%2A%BC3%BA%9A6%90N%09%8D%A2t%27%24</prefix><dest><faceid>6</faceid><flags>3</flags><expires>2147479922</expires></dest></fentry><fentry><prefix>ccnx:/</prefix><dest><faceid>7</faceid><flags>43</flags><expires>2147479927</expires></dest></fentry><fentry><prefix>ccnx:/%C1.M.S.neighborhood</prefix><dest><faceid>0</faceid><flags>3</flags><expires>2147479917</expires></dest></fentry><fentry><prefix>ccnx:/%C1.M.S.localhost</prefix><dest><faceid>0</faceid><flags>23</flags><expires>2147479917</expires></dest></fentry><fentry><prefix>ccnx:/%C1.M.S.localhost/%C1.M.SRV/repository/KEY</prefix><dest><faceid>6</faceid><flags>3</flags><expires>2147479922</expires></dest></fentry> </forwarding> </ccnd> */ private void getfaces() throws ParserConfigurationException, SAXException, IOException, MalformedContentNameStringException, DOMException { javax.xml.parsers.DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setValidating(false); javax.xml.parsers.DocumentBuilder parser = dbf.newDocumentBuilder(); org.w3c.dom.Document document; synchronized(fentries) { fentries.clear(); document = parser.parse(_ccndurl); NodeList nodes = document.getElementsByTagName("fentry"); Log.info("fentry count: " + nodes.getLength()); for(int i = 0; i< nodes.getLength(); i++) { // System.out.println("Parsing entry " + i); Node node = nodes.item(i); FEntry fentry = new FEntry(node); for(Dest dest : fentry.dests) { TreeSet<ContentName> regs = fentries.get(dest.faceid); if( null == regs ) { regs = new TreeSet<ContentName>(); fentries.put(dest.faceid, regs); } regs.add(fentry.entryprefix); } } // for(Integer faceid : fentries.keySet() ) { // dumpreg(faceid); // } } } private static class Dest { int faceid = -1; int flags = 0; long expires = -1; public Dest(Node node) { if( node.getNodeName() != "dest" ) throw new ClassCastException("node is not of type 'dest': " + node.getNodeName()); NodeList children = node.getChildNodes(); for(int i = 0; i < children.getLength(); i++) { Node child = children.item(i); if( child.getNodeName() == "faceid" ) faceid = Integer.parseInt(child.getFirstChild().getNodeValue()); if( child.getNodeName() == "flags" ) flags = Integer.parseInt(child.getFirstChild().getNodeValue()); if( child.getNodeName() == "expires" ) expires = Long.parseLong(child.getFirstChild().getNodeValue()); } } public String toString() { return String.format("faceid %d flags %08X expires %d", faceid, flags, expires); } } private static class FEntry { ContentName entryprefix = null; LinkedList<Dest> dests = new LinkedList<Dest>(); public FEntry(final Node node) throws MalformedContentNameStringException, DOMException { if( node.getNodeName() != "fentry" ) throw new ClassCastException("node is not of type 'fentry'"); NodeList children = node.getChildNodes(); for(int i = 0; i < children.getLength(); i++) { Node child = children.item(i); if( child.getNodeName() == "dest" ) { Dest dest = new Dest(child); // System.out.println("adding: " + dest.toString()); dests.add(dest); } if( child.getNodeName() == "prefix" ) { entryprefix = ContentName.fromURI(child.getFirstChild().getNodeValue()); // System.out.println("prefix " + entryprefix.toURIString()); } } } public String toString() { StringBuilder sb = new StringBuilder(); sb.append("prefix: "); sb.append(entryprefix.toURIString()); sb.append('\n'); for(Dest dest : dests) { sb.append(" faceid: "); sb.append(dest.faceid); sb.append('\n'); } return sb.toString(); } } private int getFaceId(AssertionCCNHandle handle, String type) throws CCNDaemonException, InterruptedException, Error { PrefixRegistrationManager prm = new PrefixRegistrationManager(handle); String reg = String.format("ccnx:%s/%s_%016X", _prefix, type, _rnd.nextLong()); ForwardingEntry entry = prm.selfRegisterPrefix(reg); handle.checkError(CHECK_TIMEOUT); return entry.getFaceID(); } /** * * @param faceid * @return the # of prefixes registered on the face */ private int dumpreg(int faceid) { int count = 0; synchronized(fentries) { TreeSet<ContentName> regs = fentries.get(faceid); Log.info("Registrations for faceid " + faceid); for(ContentName name : regs) { Log.info(" " + name.toURIString()); count++; } } return count; } }