Merge pull request #7 from OPENNETWORKINGLAB/master

Merge with Master changes
diff --git a/README.md b/README.md
index 08878d4..b71b9aa 100644
--- a/README.md
+++ b/README.md
@@ -41,8 +41,9 @@
     Edit file (ONOS-INSTALL-DIR)/start-cassandra.sh and set variable
     "CASSANDRA_DIR" to point to the Cassandra directory.
 
-Running ONOS
-------------
+Running ONOS with Cassandra as a separate process
+-------------------------------------------------
+[See below for information how to run ONOS with Embedded Cassandra]
 
 1. Start Zookeeper
 
diff --git a/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRoute.java b/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRoute.java
index c36d4a5..2124f78 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRoute.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRoute.java
@@ -25,7 +25,6 @@
 import net.floodlightcontroller.core.module.IFloodlightModule;
 import net.floodlightcontroller.core.module.IFloodlightService;
 import net.floodlightcontroller.core.util.SingletonTask;
-import net.floodlightcontroller.devicemanager.IDeviceService;
 import net.floodlightcontroller.packet.Ethernet;
 import net.floodlightcontroller.packet.IPv4;
 import net.floodlightcontroller.restserver.IRestApiService;
@@ -71,24 +70,24 @@
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Multimaps;
 import com.google.common.collect.SetMultimap;
+import com.google.common.net.InetAddresses;
 import com.google.common.util.concurrent.ThreadFactoryBuilder;
 
 public class BgpRoute implements IFloodlightModule, IBgpRouteService, 
-									ITopologyListener, IOFSwitchListener,
-									IArpRequester {
+									ITopologyListener, IArpRequester,
+									IOFSwitchListener {
 	
 	protected static Logger log = LoggerFactory.getLogger(BgpRoute.class);
 
 	protected IFloodlightProviderService floodlightProvider;
 	protected ITopologyService topology;
 	protected ITopoRouteService topoRouteService;
-	protected IDeviceService devices;
 	protected IRestApiService restApi;
 	
 	protected ProxyArpManager proxyArp;
 	
-	//protected static Ptree ptree;
-	protected IPatriciaTrie ptree;
+	protected IPatriciaTrie<RibEntry> ptree;
+	protected IPatriciaTrie<Interface> interfacePtrie;
 	protected BlockingQueue<RibUpdate> ribUpdates;
 	
 	protected String bgpdRestIp;
@@ -107,6 +106,7 @@
 	//Forwarding uses priority 0, and the mac rewrite entries in ingress switches
 	//need to be higher priority than this otherwise the rewrite may not get done
 	protected final short SDNIP_PRIORITY = 10;
+	protected final short ARP_PRIORITY = 20;
 	
 	protected final short BGP_PORT = 179;
 	
@@ -117,6 +117,7 @@
 	protected Map<String, Interface> interfaces;
 	protected Map<InetAddress, BgpPeer> bgpPeers;
 	protected SwitchPort bgpdAttachmentPoint;
+	protected MACAddress bgpdMacAddress;
 	
 	//True when all switches have connected
 	protected volatile boolean switchesConnected = false;
@@ -127,30 +128,17 @@
 	protected SingletonTask topologyChangeDetectorTask;
 	
 	protected SetMultimap<InetAddress, RibUpdate> prefixesWaitingOnArp;
-	protected SetMultimap<InetAddress, PathUpdate> pathsWaitingOnArp;
+	
+	protected Map<InetAddress, Path> pathsWaitingOnArp;
 	
 	protected ExecutorService bgpUpdatesExecutor;
 	
+	protected Map<InetAddress, Path> pushedPaths;
+	protected Map<Prefix, Path> prefixToPath;
 	protected Multimap<Prefix, PushedFlowMod> pushedFlows;
 	
-	private class PushedFlowMod {
-		private long dpid;
-		private OFFlowMod flowMod;
+	protected volatile Map<Long, ?> topoRouteTopology = null;
 		
-		public PushedFlowMod(long dpid, OFFlowMod flowMod) {
-			this.dpid = dpid;
-			this.flowMod = flowMod;
-		}
-		
-		public long getDpid() {
-			return dpid;
-		}
-		
-		public OFFlowMod getFlowMod() {
-			return flowMod;
-		}
-	}
-	
 	protected class TopologyChangeDetector implements Runnable {
 		@Override
 		public void run() {
@@ -174,16 +162,18 @@
 				}
 			}
 			
-			if (linkUpdates.isEmpty()){
-				//All updates have been seen in network map.
-				//We can check if topology is ready
-				log.debug("No known changes outstanding. Checking topology now");
-				checkStatus();
-			}
-			else {
-				//We know of some link updates that haven't propagated to the database yet
-				log.debug("Some changes not found in network map - {} links missing", linkUpdates.size());
-				topologyChangeDetectorTask.reschedule(TOPO_DETECTION_WAIT, TimeUnit.SECONDS);
+			if (!topologyReady) {
+				if (linkUpdates.isEmpty()){
+					//All updates have been seen in network map.
+					//We can check if topology is ready
+					log.debug("No known changes outstanding. Checking topology now");
+					checkStatus();
+				}
+				else {
+					//We know of some link updates that haven't propagated to the database yet
+					log.debug("Some changes not found in network map - {} links missing", linkUpdates.size());
+					topologyChangeDetectorTask.reschedule(TOPO_DETECTION_WAIT, TimeUnit.SECONDS);
+				}
 			}
 		}
 	}
@@ -209,6 +199,7 @@
 					new Dpid(config.getBgpdAttachmentDpid()),
 					new Port(config.getBgpdAttachmentPort()));
 			
+			bgpdMacAddress = config.getBgpdMacAddress();
 		} catch (JsonParseException e) {
 			log.error("Error in JSON file", e);
 			System.exit(1);
@@ -219,6 +210,12 @@
 			log.error("Error reading JSON file", e);
 			System.exit(1);
 		}
+		
+		//Populate the interface Patricia Trie
+		for (Interface intf : interfaces.values()) {
+			Prefix prefix = new Prefix(intf.getIpAddress().getAddress(), intf.getPrefixLength());
+			interfacePtrie.put(prefix, intf);
+		}
 	}
 	
 	@Override
@@ -243,7 +240,6 @@
 			= new ArrayList<Class<? extends IFloodlightService>>();
 		l.add(IFloodlightProviderService.class);
 		l.add(ITopologyService.class);
-		l.add(IDeviceService.class);
 		l.add(IRestApiService.class);
 		return l;
 	}
@@ -252,20 +248,19 @@
 	public void init(FloodlightModuleContext context)
 			throws FloodlightModuleException {
 	    
-	    //ptree = new Ptree(32);
-		ptree = new PatriciaTrie(32);
+		ptree = new PatriciaTrie<RibEntry>(32);
+		interfacePtrie = new PatriciaTrie<Interface>(32);
 	    
 	    ribUpdates = new LinkedBlockingQueue<RibUpdate>();
 	    	
 		// Register floodlight provider and REST handler.
 		floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
 		topology = context.getServiceImpl(ITopologyService.class);
-		devices = context.getServiceImpl(IDeviceService.class);
 		restApi = context.getServiceImpl(IRestApiService.class);
 		
 		//TODO We'll initialise this here for now, but it should really be done as
 		//part of the controller core
-		proxyArp = new ProxyArpManager(floodlightProvider, topology, devices);
+		proxyArp = new ProxyArpManager(floodlightProvider, topology);
 		
 		linkUpdates = new ArrayList<LDUpdate>();
 		ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
@@ -273,11 +268,12 @@
 
 		topoRouteService = new TopoRouteService("");
 		
-		pathsWaitingOnArp = Multimaps.synchronizedSetMultimap(
-				HashMultimap.<InetAddress, PathUpdate>create());
+		pathsWaitingOnArp = new HashMap<InetAddress, Path>();
 		prefixesWaitingOnArp = Multimaps.synchronizedSetMultimap(
 				HashMultimap.<InetAddress, RibUpdate>create());
 		
+		pushedPaths = new HashMap<InetAddress, Path>();
+		prefixToPath = new HashMap<Prefix, Path>();
 		pushedFlows = HashMultimap.<Prefix, PushedFlowMod>create();
 		
 		bgpUpdatesExecutor = Executors.newSingleThreadExecutor(
@@ -309,19 +305,30 @@
 		log.debug("Config file set to {}", configFilename);
 		
 		readGatewaysConfiguration(configFilename);
-		// Test.
-		//test();
+		
+		proxyArp.setL3Mode(interfacePtrie, interfaces.values(), bgpdMacAddress);
+	}
+	
+	@Override
+	public void startUp(FloodlightModuleContext context) {
+		restApi.addRestletRoutable(new BgpRouteWebRoutable());
+		topology.addListener(this);
+		floodlightProvider.addOFSwitchListener(this);
+		
+		proxyArp.startUp();
+		
+		floodlightProvider.addOFMessageListener(OFType.PACKET_IN, proxyArp);
+		
+		//Retrieve the RIB from BGPd during startup
+		retrieveRib();
 	}
 
-	//public Ptree getPtree() {
-	public IPatriciaTrie getPtree() {
+	public IPatriciaTrie<RibEntry> getPtree() {
 		return ptree;
 	}
 	
 	public void clearPtree() {
-		//ptree = null;
-		//ptree = new Ptree(32);
-		ptree = new PatriciaTrie(32);
+		ptree = new PatriciaTrie<RibEntry>(32);
 	}
 	
 	public String getBGPdRestIp() {
@@ -332,105 +339,6 @@
 		return routerId;
 	}
 	
-	// Return nexthop address as byte array.
-	/*
-	public RibEntry lookupRib(byte[] dest) {
-		if (ptree == null) {
-		    log.debug("lookupRib: ptree null");
-		    return null;
-		}
-		
-		PtreeNode node = ptree.match(dest, 32);
-		if (node == null) {
-            log.debug("lookupRib: ptree node null");
-			return null;
-		}
-		
-		if (node.rib == null) {
-            log.debug("lookupRib: ptree rib null");
-			return null;
-		}
-		
-		ptree.delReference(node);
-		
-		return node.rib;
-	}
-	*/
-	
-	/*
-	//TODO looks like this should be a unit test
-	@SuppressWarnings("unused")
-    private void test() throws UnknownHostException {
-		System.out.println("Here it is");
-		Prefix p = new Prefix("128.0.0.0", 8);
-		Prefix q = new Prefix("8.0.0.0", 8);
-		Prefix r = new Prefix("10.0.0.0", 24);
-		Prefix a = new Prefix("10.0.0.1", 32);
-	
-		ptree.acquire(p.getAddress(), p.getPrefixLength());
-		ptree.acquire(q.getAddress(), q.getPrefixLength());
-		ptree.acquire(r.getAddress(), r.getPrefixLength());
-	
-		System.out.println("Traverse start");
-		for (PtreeNode node = ptree.begin(); node != null; node = ptree.next(node)) {
-			Prefix p_result = new Prefix(node.key, node.keyBits);
-		}
-	
-		PtreeNode n = ptree.match(a.getAddress(), a.getPrefixLength());
-		if (n != null) {
-			System.out.println("Matched prefix for 10.0.0.1:");
-			Prefix x = new Prefix(n.key, n.keyBits);
-			ptree.delReference(n);
-		}
-		
-		n = ptree.lookup(p.getAddress(), p.getPrefixLength());
-		if (n != null) {
-			ptree.delReference(n);
-			ptree.delReference(n);
-		}
-		System.out.println("Traverse start");
-		for (PtreeNode node = ptree.begin(); node != null; node = ptree.next(node)) {
-			Prefix p_result = new Prefix(node.key, node.keyBits);
-		}
-		
-		n = ptree.lookup(q.getAddress(), q.getPrefixLength());
-		if (n != null) {
-			ptree.delReference(n);
-			ptree.delReference(n);
-		}
-		System.out.println("Traverse start");
-		for (PtreeNode node = ptree.begin(); node != null; node = ptree.next(node)) {
-			Prefix p_result = new Prefix(node.key, node.keyBits);
-		}
-		
-		n = ptree.lookup(r.getAddress(), r.getPrefixLength());
-		if (n != null) {
-			ptree.delReference(n);
-			ptree.delReference(n);
-		}
-		System.out.println("Traverse start");
-		for (PtreeNode node = ptree.begin(); node != null; node = ptree.next(node)) {
-			Prefix p_result = new Prefix(node.key, node.keyBits);
-		}
-
-	}
-	*/
-	
-	//TODO once the Ptree is object oriented this can go
-	/*
-	private String getPrefixFromPtree(PtreeNode node){
-        InetAddress address = null;
-        try {
-			address = InetAddress.getByAddress(node.key);
-		} catch (UnknownHostException e1) {
-			//Should never happen is the reverse conversion has already been done
-			log.error("Malformed IP address");
-			return "";
-		}
-        return address.toString() + "/" + node.rib.masklen;
-	}
-	*/
-	
 	private void retrieveRib(){
 		String url = "http://" + bgpdRestIp + "/wm/bgp/" + routerId;
 		String response = RestClient.get(url);
@@ -469,21 +377,8 @@
 				continue;
 			}
 			
-			//PtreeNode node = ptree.acquire(p.getAddress(), p.getPrefixLength());
 			RibEntry rib = new RibEntry(router_id, nexthop);
-			
-			/*
-			if (node.rib != null) {
-				node.rib = null;
-				ptree.delReference(node);
-			}
-			
-			node.rib = rib;
-			*/
-			
-			//ptree.put(p, rib);
-			
-			//addPrefixFlows(p, rib);
+
 			try {
 				ribUpdates.put(new RibUpdate(Operation.UPDATE, p, rib));
 			} catch (InterruptedException e) {
@@ -497,8 +392,8 @@
 		try {
 			ribUpdates.put(update);
 		} catch (InterruptedException e) {
-			// TODO Auto-generated catch block
-			log.debug(" ", e);
+			log.debug("Interrupted while putting on ribUpdates queue", e);
+			Thread.currentThread().interrupt();
 		}
 	}
 	
@@ -507,140 +402,114 @@
 		
 		log.debug("Processing prefix add {}", prefix);
 		
-		//PtreeNode node = ptree.acquire(prefix.getAddress(), prefix.getPrefixLength());
 		RibEntry rib = ptree.put(prefix, update.getRibEntry());
 		
-		//if (node.rib != null) {
 		if (rib != null && !rib.equals(update.getRibEntry())) {
 			//There was an existing nexthop for this prefix. This update supersedes that,
 			//so we need to remove the old flows for this prefix from the switches
-			deletePrefixFlows(prefix);
-			
-			//Then remove the old nexthop from the Ptree
-			//node.rib = null;
-			//ptree.delReference(node);
+			_processDeletePrefix(prefix, rib);
 		}
 		
-		//Put the new nexthop in the Ptree
-		//node.rib = update.getRibEntry();
-
-		//Push flows for the new <prefix, nexthop>
-		addPrefixFlows(prefix, update.getRibEntry());
+		if (update.getRibEntry().getNextHop().equals(
+				InetAddresses.forString("0.0.0.0"))) {
+			//Route originated by SDN domain
+			//We don't handle these at the moment
+			log.debug("Own route {} to {}", prefix, 
+					update.getRibEntry().getNextHop().getHostAddress());
+			return;
+		}
+		
+		_processRibAdd(update);
 	}
 	
-	public synchronized void processRibDelete(RibUpdate update) {
+	private void _processRibAdd(RibUpdate update) {
 		Prefix prefix = update.getPrefix();
+		RibEntry rib = update.getRibEntry();
 		
-		//PtreeNode node = ptree.lookup(prefix.getAddress(), prefix.getPrefixLength());
+		InetAddress dstIpAddress = rib.getNextHop();
 		
-		/* 
-		 * Remove the flows from the switches before the rib is lost
-		 * Theory: we could get a delete for a prefix not in the Ptree.
-		 * This would result in a null node being returned. We could get a delete for
-		 * a node that's not actually there, but is a aggregate node. This would result
-		 * in a non-null node with a null rib. Only a non-null node with a non-null
-		 * rib is an actual prefix in the Ptree.
-		 */
-
-		/*
-		if (node != null && node.rib != null) {
-			if (update.getRibEntry().equals(node.rib)) {
-				node.rib = null;
-				ptree.delReference(node);
-				
-				deletePrefixFlows(update.getPrefix());
+		//See if we know the MAC address of the next hop
+		byte[] nextHopMacAddress = proxyArp.getMacAddress(rib.getNextHop());
+		
+		//Find the attachment point (egress interface) of the next hop
+		Interface egressInterface = null;
+		if (bgpPeers.containsKey(dstIpAddress)) {
+			//Route to a peer
+			log.debug("Route to peer {}", dstIpAddress);
+			BgpPeer peer = bgpPeers.get(dstIpAddress);
+			egressInterface = interfaces.get(peer.getInterfaceName());
+		}
+		else {
+			//Route to non-peer
+			log.debug("Route to non-peer {}", dstIpAddress);
+			egressInterface = interfacePtrie.match(
+					new Prefix(dstIpAddress.getAddress(), 32));
+			if (egressInterface == null) {
+				log.warn("No outgoing interface found for {}", dstIpAddress.getHostAddress());
+				return;
 			}
 		}
-		*/
 		
-		if (ptree.remove(prefix, update.getRibEntry())) {
-			/*
-			 * Only delete flows if an entry was actually removed from the trie.
-			 * If no entry was removed, the <prefix, nexthop> wasn't there so
-			 * it's probably already been removed and we don't need to do anything
-			 */
-			deletePrefixFlows(prefix);
+		if (nextHopMacAddress == null) {
+			prefixesWaitingOnArp.put(dstIpAddress, 
+					new RibUpdate(Operation.UPDATE, prefix, rib));
+			proxyArp.sendArpRequest(dstIpAddress, this, true);
+			return;
+		}
+		else {
+			if (!bgpPeers.containsKey(dstIpAddress)) {
+				//If the prefix is for a non-peer we need to ensure there's a path,
+				//and push one if there isn't.
+				Path path = pushedPaths.get(dstIpAddress);
+				if (path == null) {
+					path = new Path(egressInterface, dstIpAddress);
+					setUpDataPath(path, MACAddress.valueOf(nextHopMacAddress));
+					pushedPaths.put(dstIpAddress, path);
+				}
+				
+				path.incrementUsers();
+				prefixToPath.put(prefix, path);
+			}
+			
+			//For all prefixes we need to add the first-hop mac-rewriting flows
+			addPrefixFlows(prefix, egressInterface, nextHopMacAddress);
 		}
 	}
 	
-	//TODO compatibility layer, used by beginRouting()
-	/*public void prefixAdded(PtreeNode node) {
-		Prefix prefix = null;
-		try {
-			prefix = new Prefix(node.key, node.rib.masklen);
-		} catch (IllegalArgumentException e) {
-			log.error(" ", e);
-		}
+	private void addPrefixFlows(Prefix prefix, Interface egressInterface, byte[] nextHopMacAddress) {		
+		log.debug("Adding flows for prefix {} added, next hop mac {}",
+				prefix, HexString.toHexString(nextHopMacAddress));
 
-		addPrefixFlows(prefix, node.rib);
-	}*/
-
-	private void addPrefixFlows(Prefix prefix, RibEntry rib) {
-		if (!topologyReady){
-			return;
-		}
-		
-		//TODO before we do anything, we have to check that the RIB entry is still in the
-		//Ptree because it could have been removed while we were waiting for ARP.
-		//I think we'll have to make prefixAdded and prefixDelete atomic as well
-		//to protect against the prefix getting deleted while where trying to add it
-
-		log.debug("New prefix {} added, next hop {}", 
-				prefix, rib.getNextHop().getHostAddress());
-		
-		//TODO this is wrong, we shouldn't be dealing with BGP peers here.
-		//We need to figure out where the device is attached and what its
-		//mac address is by learning. 
-		//The next hop is not necessarily the peer, and the peer's attachment
-		//point is not necessarily the next hop's attachment point.
-		BgpPeer peer = bgpPeers.get(rib.getNextHop());
-		
-		if (peer == null){
-			//TODO local router isn't in peers list so this will get thrown
-			//Need to work out what to do about local prefixes with next hop 0.0.0.0.
-			
-			//The other scenario is this is a route server route. In that
-			//case the next hop is not in our configuration
-			log.error("Couldn't find next hop router in router {} in config",
-					rib.getNextHop().getHostAddress());
-			return; //just quit out here? This is probably a configuration error
-		}
-		
-		//Get MAC address for peer from the ARP module
-		//TODO separate out the 'ask for MAC' bit to another method
-		byte[] peerMacAddress = proxyArp.getMacAddress(peer.getIpAddress());
-		if (peerMacAddress == null) {
-			//A RibUpdate is still a nice way to package them up
-			prefixesWaitingOnArp.put(peer.getIpAddress(), 
-					new RibUpdate(Operation.UPDATE, prefix, rib));
-			proxyArp.sendArpRequest(peer.getIpAddress(), this, true);
-			return;
-		}
-		
-		Interface peerInterface = interfaces.get(peer.getInterfaceName());
-
-		//Add a flow to rewrite mac for this prefix to all border switches
+		//Add a flow to rewrite mac for this prefix to all other border switches
 		for (Interface srcInterface : interfaces.values()) {
-			if (srcInterface == peerInterface) {
+			if (srcInterface == egressInterface) {
 				//Don't push a flow for the switch where this peer is attached
 				continue;
 			}
-						
-			DataPath shortestPath = topoRouteService.getShortestPath(
-					srcInterface.getSwitchPort(),
-					peerInterface.getSwitchPort());
+			
+			
+			DataPath shortestPath; 
+			if (topoRouteTopology == null) {
+				shortestPath = topoRouteService.getShortestPath(
+						srcInterface.getSwitchPort(),
+						egressInterface.getSwitchPort());
+			}
+			else {
+				shortestPath = topoRouteService.getTopoShortestPath(
+						topoRouteTopology, srcInterface.getSwitchPort(),
+						egressInterface.getSwitchPort());
+			}
 			
 			if (shortestPath == null){
 				log.debug("Shortest path between {} and {} not found",
 						srcInterface.getSwitchPort(),
-						peerInterface.getSwitchPort());
+						egressInterface.getSwitchPort());
 				return; // just quit here?
 			}
 			
 			//Set up the flow mod
-			OFFlowMod fm =
-	                (OFFlowMod) floodlightProvider.getOFMessageFactory()
-	                                              .getMessage(OFType.FLOW_MOD);
+			OFFlowMod fm = (OFFlowMod) floodlightProvider.getOFMessageFactory()
+					.getMessage(OFType.FLOW_MOD);
 			
 	        fm.setIdleTimeout((short)0)
 	        .setHardTimeout((short)0)
@@ -656,30 +525,16 @@
 	        match.setDataLayerType(Ethernet.TYPE_IPv4);
 	        match.setWildcards(match.getWildcards() & ~OFMatch.OFPFW_DL_TYPE);
 	        
-	        /*
-	        InetAddress address = null;
-	        try {
-	        	address = InetAddress.getByAddress(prefix.getAddress());
-			} catch (UnknownHostException e1) {
-				//Should never happen is the reverse conversion has already been done
-				log.error("Malformed IP address");
-				return;
-			}*/
-	        
-	        //match.setFromCIDR(address.getHostAddress() + "/" + 
-	        //		prefix.getPrefixLength(), OFMatch.STR_NW_DST);
 	        match.setFromCIDR(prefix.toString(), OFMatch.STR_NW_DST);
 	        fm.setMatch(match);
 	        
 	        //Set up MAC rewrite action
 	        OFActionDataLayerDestination macRewriteAction = new OFActionDataLayerDestination();
-	        //TODO the peer's mac address is not necessarily the next hop's...
-	        macRewriteAction.setDataLayerAddress(peerMacAddress);
+	        macRewriteAction.setDataLayerAddress(nextHopMacAddress);
 	        
 	        //Set up output action
 	        OFActionOutput outputAction = new OFActionOutput();
 	        outputAction.setMaxLength((short)0xffff);
-	        
 	        Port outputPort = shortestPath.flowEntries().get(0).outPort();
 	        outputAction.setPort(outputPort.value());
 	        
@@ -697,7 +552,6 @@
             	continue;
             }
             
-            //TODO if prefix Added/Deleted are synchronized this shouldn't have to be
             pushedFlows.put(prefix, new PushedFlowMod(sw.getId(), fm));
             
             List<OFMessage> msglist = new ArrayList<OFMessage>();
@@ -711,20 +565,43 @@
 		}
 	}
 	
-	//TODO test next-hop changes
-	//TODO check delete/add synchronization
+	public synchronized void processRibDelete(RibUpdate update) {
+		Prefix prefix = update.getPrefix();
 		
-	private void deletePrefixFlows(Prefix prefix) {
-		if (!topologyReady) {
-			return;
+		if (ptree.remove(prefix, update.getRibEntry())) {
+			/*
+			 * Only delete flows if an entry was actually removed from the trie.
+			 * If no entry was removed, the <prefix, nexthop> wasn't there so
+			 * it's probably already been removed and we don't need to do anything
+			 */
+			_processDeletePrefix(prefix, update.getRibEntry());
 		}
+	}
+	
+	private void _processDeletePrefix(Prefix prefix, RibEntry ribEntry) {
+		deletePrefixFlows(prefix);
 		
-		log.debug("In deletePrefixFlows for {}", prefix);
-		
-		/*for (Map.Entry<Prefix, PushedFlowMod> entry : pushedFlows.entries()) {
-			log.debug("Pushed flow: {} => {}", entry.getKey(), entry.getValue());
-		}*/
-		
+		log.debug("Deleting {} to {}", prefix, ribEntry.getNextHop());
+		log.debug("is peer {}", bgpPeers.containsKey(ribEntry.getNextHop()));
+		if (!bgpPeers.containsKey(ribEntry.getNextHop())) {
+			log.debug("Getting path for route with non-peer nexthop");
+			//Path path = prefixToPath.get(prefix);
+			Path path = prefixToPath.remove(prefix);
+			
+			if (path == null) {
+				log.error("No path found for non-peer path");
+			}
+			
+			path.decrementUsers();
+			log.debug("users {}, permanent {}", path.getUsers(), path.isPermanent());
+			if (path.getUsers() <= 0 && !path.isPermanent()) {
+				deletePath(path);
+				pushedPaths.remove(path.getDstIpAddress());
+			}
+		}
+	}
+	
+	private void deletePrefixFlows(Prefix prefix) {	
 		Collection<PushedFlowMod> pushedFlowMods 
 				= pushedFlows.removeAll(prefix);
 		
@@ -736,29 +613,45 @@
 					HexString.toHexString(((OFActionDataLayerDestination)pfm.getFlowMod().getActions().get(0))
 							.getDataLayerAddress())});
 			
-			OFFlowMod fm = pfm.getFlowMod();
-			
-			fm.setCommand(OFFlowMod.OFPFC_DELETE)
-			.setOutPort(OFPort.OFPP_NONE)
-			.setLengthU(OFFlowMod.MINIMUM_LENGTH);
-			
-			fm.getActions().clear();
-			
-			IOFSwitch sw = floodlightProvider.getSwitches().get(pfm.getDpid());
-			if (sw == null) {
-            	log.warn("Switch not found when pushing delete flow mod");
-            	continue;
-			}
-			
-			try {
-				sw.write(fm, null);
-				sw.flush();
-			} catch (IOException e) {
-				log.error("Failure writing flow mod", e);
-			}
+			sendDeleteFlowMod(pfm.getFlowMod(), pfm.getDpid());
 		}
 	}
 	
+	private void deletePath(Path path) {
+		for (PushedFlowMod pfm : path.getFlowMods()) {
+			log.debug("Pushing a DELETE flow mod to {}, dst MAC {}",
+					new Object[] {HexString.toHexString(pfm.getDpid()),
+					HexString.toHexString(pfm.getFlowMod().getMatch().getDataLayerDestination())
+			});
+			
+			sendDeleteFlowMod(pfm.getFlowMod(), pfm.getDpid());
+		}
+	}
+	
+	private void sendDeleteFlowMod(OFFlowMod addFlowMod, long dpid) {
+		addFlowMod.setCommand(OFFlowMod.OFPFC_DELETE_STRICT)
+		.setOutPort(OFPort.OFPP_NONE)
+		.setLengthU(OFFlowMod.MINIMUM_LENGTH);
+		
+		addFlowMod.getActions().clear();
+		
+		IOFSwitch sw = floodlightProvider.getSwitches().get(dpid);
+		if (sw == null) {
+        	log.warn("Switch not found when pushing delete flow mod");
+        	return;
+		}
+		
+		try {
+			sw.write(addFlowMod, null);
+			sw.flush();
+		} catch (IOException e) {
+			log.error("Failure writing flow mod", e);
+		}
+	}
+	
+	//TODO test next-hop changes
+	//TODO check delete/add synchronization
+	
 	/*
 	 * On startup we need to calculate a full mesh of paths between all gateway
 	 * switches
@@ -773,32 +666,56 @@
 		for (BgpPeer peer : bgpPeers.values()) {
 			Interface peerInterface = interfaces.get(peer.getInterfaceName());
 			
+			//We know there's not already a Path here pushed, because this is
+			//called before all other routing
+			Path path = new Path(peerInterface, peer.getIpAddress());
+			path.setPermanent();
+			
 			//See if we know the MAC address of the peer. If not we can't
 			//do anything until we learn it
 			byte[] mac = proxyArp.getMacAddress(peer.getIpAddress());
 			if (mac == null) {
 				log.debug("Don't know MAC for {}", peer.getIpAddress().getHostAddress());
 				//Put in the pending paths list first
-				pathsWaitingOnArp.put(peer.getIpAddress(),
-						new PathUpdate(peerInterface, peer.getIpAddress()));
+				pathsWaitingOnArp.put(peer.getIpAddress(), path);
 				
 				proxyArp.sendArpRequest(peer.getIpAddress(), this, true);
 				continue;
 			}
 			
 			//If we know the MAC, lets go ahead and push the paths to this peer
-			calculateAndPushPath(peerInterface, MACAddress.valueOf(mac));
+			setUpDataPath(path, MACAddress.valueOf(mac));
 		}
 	}
 	
-	private void calculateAndPushPath(Interface dstInterface, MACAddress dstMacAddress) {
+	private void setUpDataPath(Path path, MACAddress dstMacAddress) {
+		calculateAndPushPath(path, dstMacAddress);
+	}
+	
+	private void calculateAndPushPath(Path path, MACAddress dstMacAddress) {
+		Interface dstInterface = path.getDstInterface();
+		
+		log.debug("Setting up path to {}, {}", path.getDstIpAddress().getHostAddress(),
+				dstMacAddress);
+		
+		List<PushedFlowMod> pushedFlows = new ArrayList<PushedFlowMod>();
+		
 		for (Interface srcInterface : interfaces.values()) {
 			if (dstInterface.equals(srcInterface.getName())){
 				continue;
 			}
 			
-			DataPath shortestPath = topoRouteService.getShortestPath(
-						srcInterface.getSwitchPort(), dstInterface.getSwitchPort()); 
+			DataPath shortestPath;
+			if (topoRouteTopology == null) {
+				log.debug("Using database topo");
+				shortestPath = topoRouteService.getShortestPath(
+						srcInterface.getSwitchPort(), dstInterface.getSwitchPort());
+			}
+			else {
+				log.debug("Using prepared topo");
+				shortestPath = topoRouteService.getTopoShortestPath(topoRouteTopology, 
+						srcInterface.getSwitchPort(), dstInterface.getSwitchPort());
+			}
 			
 			if (shortestPath == null){
 				log.debug("Shortest path between {} and {} not found",
@@ -806,11 +723,15 @@
 				return; // just quit here?
 			}
 			
-			installPath(shortestPath.flowEntries(), dstMacAddress);
+			pushedFlows.addAll(installPath(shortestPath.flowEntries(), dstMacAddress));
 		}
+		
+		path.setFlowMods(pushedFlows);
 	}
 	
-	private void installPath(List<FlowEntry> flowEntries, MACAddress dstMacAddress){
+	private List<PushedFlowMod> installPath(List<FlowEntry> flowEntries, MACAddress dstMacAddress){
+		List<PushedFlowMod> flowMods = new ArrayList<PushedFlowMod>();
+		
 		//Set up the flow mod
 		OFFlowMod fm =
                 (OFFlowMod) floodlightProvider.getOFMessageFactory()
@@ -835,7 +756,6 @@
         	FlowEntry flowEntry = flowEntries.get(i);
            
             OFMatch match = new OFMatch();
-            //TODO Again using MAC address from configuration
             match.setDataLayerDestination(dstMacAddress.toBytes());
             match.setWildcards(match.getWildcards() & ~OFMatch.OFPFW_DL_DST);
             ((OFActionOutput) fm.getActions().get(0)).setPort(flowEntry.outPort().value());
@@ -849,6 +769,8 @@
             	continue;
             }
             
+            flowMods.add(new PushedFlowMod(sw.getId(), fm));
+            
             List<OFMessage> msglist = new ArrayList<OFMessage>();
             msglist.add(fm);
             try {
@@ -864,6 +786,8 @@
                 log.error("Failure cloning flow mod", e1);
             }
 		}
+        
+        return flowMods;
 	}
 	
 	private void setupBgpPaths(){
@@ -908,7 +832,6 @@
 	        
 	        //Common match fields
 	        forwardMatchSrc.setDataLayerType(Ethernet.TYPE_IPv4);
-	        //forwardMatch.setWildcards(forwardMatch.getWildcards() & ~OFMatch.OFPFW_DL_TYPE);
 	        forwardMatchSrc.setNetworkProtocol(IPv4.PROTOCOL_TCP);
 	        forwardMatchSrc.setTransportDestination(BGP_PORT);
 	        forwardMatchSrc.setWildcards(forwardMatchSrc.getWildcards() & ~OFMatch.OFPFW_IN_PORT
@@ -1024,30 +947,39 @@
 		log.debug("Received ARP response: {} => {}", ipAddress.getHostAddress(), 
 				MACAddress.valueOf(macAddress).toString());
 		
-		Set<PathUpdate> pathsToPush = pathsWaitingOnArp.removeAll(ipAddress);
-		
-		for (PathUpdate update : pathsToPush) {
-			log.debug("Pushing path to {} at {} on {}", new Object[] {
-					update.getDstIpAddress().getHostAddress(), 
-					MACAddress.valueOf(macAddress),
-					update.getDstInterface().getSwitchPort()});
-			calculateAndPushPath(update.getDstInterface(), 
-					MACAddress.valueOf(macAddress));
-		}
-		
-		Set<RibUpdate> prefixesToPush = prefixesWaitingOnArp.removeAll(ipAddress);
-		
 		/*
 		 * We synchronize on this to prevent changes to the ptree while we're pushing
 		 * flows to the switches. If the ptree changes, the ptree and switches
 		 * could get out of sync. 
 		 */
 		synchronized (this) {
+			Path path = pathsWaitingOnArp.remove(ipAddress);
+			
+			if (path != null) {
+				log.debug("Pushing path to {} at {} on {}", new Object[] {
+						path.getDstIpAddress().getHostAddress(), 
+						MACAddress.valueOf(macAddress),
+						path.getDstInterface().getSwitchPort()});
+				//These paths should always be to BGP peers. Paths to non-peers are
+				//handled once the first prefix is ready to push
+				if (pushedPaths.containsKey(path.getDstInterface())) {
+					//A path already got pushed to this endpoint while we were waiting
+					//for ARP. We'll copy over the permanent attribute if it is set on this path.
+					if (path.isPermanent()) {
+						pushedPaths.get(path.getDstInterface()).setPermanent();
+					}
+				}
+				else {
+					setUpDataPath(path, MACAddress.valueOf(macAddress));
+					pushedPaths.put(path.getDstIpAddress(), path);
+				}
+			}
+			
+			Set<RibUpdate> prefixesToPush = prefixesWaitingOnArp.removeAll(ipAddress);
+			
 			for (RibUpdate update : prefixesToPush) {
 				//These will always be adds
 				
-				//addPrefixFlows(update.getPrefix(), update.getRibEntry());
-				//processRibAdd(update);
 				RibEntry rib = ptree.lookup(update.getPrefix()); 
 				if (rib != null && rib.equals(update.getRibEntry())) {
 					log.debug("Pushing prefix {} next hop {}", update.getPrefix(), 
@@ -1056,7 +988,7 @@
 					//and the next hop is the same as our update. The prefix could 
 					//have been removed while we were waiting for the ARP, or the 
 					//next hop could have changed.
-					addPrefixFlows(update.getPrefix(), rib);
+					_processRibAdd(update);
 				} else {
 					log.debug("Received ARP response, but {},{} is no longer in ptree", 
 							update.getPrefix(), update.getRibEntry());
@@ -1065,37 +997,59 @@
 		}
 	}
 	
+	private void setupArpFlows() {
+		OFMatch match = new OFMatch();
+		match.setDataLayerType(Ethernet.TYPE_ARP);
+		match.setWildcards(match.getWildcards() & ~OFMatch.OFPFW_DL_TYPE);
+		
+		OFFlowMod fm = new OFFlowMod();
+		fm.setMatch(match);
+		
+		OFActionOutput action = new OFActionOutput();
+		action.setPort(OFPort.OFPP_CONTROLLER.getValue());
+		action.setMaxLength((short)0xffff);
+		List<OFAction> actions = new ArrayList<OFAction>(1);
+		actions.add(action);
+		fm.setActions(actions);
+		
+		fm.setIdleTimeout((short)0)
+        .setHardTimeout((short)0)
+        .setBufferId(OFPacketOut.BUFFER_ID_NONE)
+        .setCookie(0)
+        .setCommand(OFFlowMod.OFPFC_ADD)
+        .setPriority(ARP_PRIORITY)
+		.setLengthU(OFFlowMod.MINIMUM_LENGTH + OFActionOutput.MINIMUM_LENGTH);
+		
+		for (String strdpid : switches){
+			IOFSwitch sw = floodlightProvider.getSwitches().get(HexString.toLong(strdpid));
+			if (sw == null) {
+				log.debug("Couldn't find switch to push ARP flow");
+			}
+			else {
+				try {
+					sw.write(fm, null);
+				} catch (IOException e) {
+					log.warn("Failure writing ARP flow to switch", e);
+				}
+			}
+		}
+	}
+	
 	private void beginRouting(){
 		log.debug("Topology is now ready, beginning routing function");
+		topoRouteTopology = topoRouteService.prepareShortestPathTopo();
+		
+		setupArpFlows();
+		
 		setupBgpPaths();
 		setupFullMesh();
-		
-		//Traverse ptree and create flows for all routes
-		/*
-		for (PtreeNode node = ptree.begin(); node != null; node = ptree.next(node)){
-			if (node.rib != null){
-				prefixAdded(node);
-			}
-		}
-		*/
-		
-		/*
-		synchronized (ptree) {
-			Iterator<IPatriciaTrie.Entry> it = ptree.iterator();
-			while (it.hasNext()) {
-				IPatriciaTrie.Entry entry = it.next();
-				addPrefixFlows(entry.getPrefix(), entry.getRib());
-			}
-		}
-		*/
-		
+
 		bgpUpdatesExecutor.execute(new Runnable() {
 			@Override
 			public void run() {
 				doUpdatesThread();
 			}
 		});
-		
 	}
 	
 	private void checkSwitchesConnected(){
@@ -1131,8 +1085,6 @@
 	}
 	
 	private void checkStatus(){
-		log.debug("In checkStatus, swC {}, toRe {}", switchesConnected, topologyReady);
-		
 		if (!switchesConnected){
 			checkSwitchesConnected();
 		}
@@ -1144,19 +1096,7 @@
 			beginRouting();
 		}
 	}
-	
-	@Override
-	public void startUp(FloodlightModuleContext context) {
-		restApi.addRestletRoutable(new BgpRouteWebRoutable());
-		floodlightProvider.addOFSwitchListener(this);
-		topology.addListener(this);
-		
-		floodlightProvider.addOFMessageListener(OFType.PACKET_IN, proxyArp);
-		
-		//Retrieve the RIB from BGPd during startup
-		retrieveRib();
-	}
-	
+
 	private void doUpdatesThread() {
 		boolean interrupted = false;
 		try {
@@ -1172,8 +1112,10 @@
 						break;
 					}
 				} catch (InterruptedException e) {
-					log.debug("interrupted", e);
+					log.debug("Interrupted while taking from updates queue", e);
 					interrupted = true;
+				} catch (Exception e) {
+					log.debug("exception", e);
 				}
 			}
 		} finally {
@@ -1184,7 +1126,11 @@
 	}
 
 	@Override
-	public void topologyChanged() {		
+	public void topologyChanged() {
+		if (topologyReady) {
+			return;
+		}
+		
 		boolean refreshNeeded = false;
 		for (LDUpdate ldu : topology.getLastLinkUpdates()){
 			if (!ldu.getOperation().equals(ILinkDiscovery.UpdateOperation.LINK_UPDATED)){
@@ -1202,27 +1148,33 @@
 			}
 		}
 		
-		if (refreshNeeded){
+		if (refreshNeeded && !topologyReady){
 			topologyChangeDetectorTask.reschedule(TOPO_DETECTION_WAIT, TimeUnit.SECONDS);
 		}
 	}
 
-	//TODO determine whether we need to listen for switch joins
 	@Override
 	public void addedSwitch(IOFSwitch sw) {
-		//checkStatus();
+		if (!topologyReady) {
+			sw.clearAllFlowMods();
+		}
 	}
 
 	@Override
 	public void removedSwitch(IOFSwitch sw) {
-		// TODO Auto-generated method stub	
+		// TODO Auto-generated method stub
+		
 	}
 
 	@Override
-	public void switchPortChanged(Long switchId) {}
+	public void switchPortChanged(Long switchId) {
+		// TODO Auto-generated method stub
+		
+	}
 
 	@Override
 	public String getName() {
-		return "BgpRoute";
+		// TODO Auto-generated method stub
+		return null;
 	}
 }
diff --git a/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRouteResource.java b/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRouteResource.java
index c9b3265..f058843 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRouteResource.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRouteResource.java
@@ -60,7 +60,7 @@
 		} 
 		else {
 			//Ptree ptree = bgpRoute.getPtree();
-			IPatriciaTrie ptree = bgpRoute.getPtree();
+			IPatriciaTrie<RibEntry> ptree = bgpRoute.getPtree();
 			output += "{\n  \"rib\": [\n";
 			boolean printed = false;
 			
@@ -78,16 +78,16 @@
 			}*/
 			
 			synchronized(ptree) {
-				Iterator<IPatriciaTrie.Entry> it = ptree.iterator();
+				Iterator<IPatriciaTrie.Entry<RibEntry>> it = ptree.iterator();
 				while (it.hasNext()) {
-					IPatriciaTrie.Entry entry = it.next();
+					IPatriciaTrie.Entry<RibEntry> entry = it.next();
 					
 					if (printed == true) {
 						output += ",\n";
 					}
 					
 					output += "    {\"prefix\": \"" + entry.getPrefix() +"\", ";
-					output += "\"nexthop\": \"" + entry.getRib().getNextHop().getHostAddress() +"\"}";
+					output += "\"nexthop\": \"" + entry.getValue().getNextHop().getHostAddress() +"\"}";
 					//output += ",\n";
 					
 					printed = true;
diff --git a/src/main/java/net/onrc/onos/ofcontroller/bgproute/Configuration.java b/src/main/java/net/onrc/onos/ofcontroller/bgproute/Configuration.java
index 4b623e4..1d90edc 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/bgproute/Configuration.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/Configuration.java
@@ -3,12 +3,15 @@
 import java.util.Collections;
 import java.util.List;
 
+import net.floodlightcontroller.util.MACAddress;
+
 import org.codehaus.jackson.annotate.JsonProperty;
 import org.openflow.util.HexString;
 
 public class Configuration {
 	private long bgpdAttachmentDpid;
 	private short bgpdAttachmentPort;
+	private MACAddress bgpdMacAddress;
 	private List<String> switches;
 	private List<Interface> interfaces;
 	private List<BgpPeer> peers;
@@ -34,6 +37,15 @@
 	public void setBgpdAttachmentPort(short bgpdAttachmentPort) {
 		this.bgpdAttachmentPort = bgpdAttachmentPort;
 	}
+	
+	public MACAddress getBgpdMacAddress() {
+		return bgpdMacAddress;
+	}
+
+	@JsonProperty("bgpdMacAddress")
+	public void setBgpdMacAddress(String strMacAddress) {
+		this.bgpdMacAddress = MACAddress.valueOf(strMacAddress);
+	}
 
 	public List<String> getSwitches() {
 		return Collections.unmodifiableList(switches);
diff --git a/src/main/java/net/onrc/onos/ofcontroller/bgproute/IBgpRouteService.java b/src/main/java/net/onrc/onos/ofcontroller/bgproute/IBgpRouteService.java
index ba912ce..954976c 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/bgproute/IBgpRouteService.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/IBgpRouteService.java
@@ -7,7 +7,7 @@
 	//public RibEntry lookupRib(byte[] dest);
 
 	//public Ptree getPtree();
-	public IPatriciaTrie getPtree();
+	public IPatriciaTrie<RibEntry> getPtree();
 
 	public String getBGPdRestIp();
 
diff --git a/src/main/java/net/onrc/onos/ofcontroller/bgproute/IPatriciaTrie.java b/src/main/java/net/onrc/onos/ofcontroller/bgproute/IPatriciaTrie.java
index 7fd7382..1fb0716 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/bgproute/IPatriciaTrie.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/IPatriciaTrie.java
@@ -2,19 +2,19 @@
 
 import java.util.Iterator;
 
-public interface IPatriciaTrie {
-	public RibEntry put(Prefix p, RibEntry r);
+public interface IPatriciaTrie<V> {
+	public V put(Prefix prefix, V value);
 	
-	public RibEntry lookup(Prefix p);
+	public V lookup(Prefix prefix);
 	
-	public RibEntry match(Prefix p);
+	public V match(Prefix prefix);
 	
-	public boolean remove(Prefix p, RibEntry r);
+	public boolean remove(Prefix prefix, V value);
 	
-	public Iterator<Entry> iterator();
+	public Iterator<Entry<V>> iterator();
 	
-	interface Entry {
+	interface Entry<V> {
 		public Prefix getPrefix();
-		public RibEntry getRib();
+		public V getValue();
 	}
 }
diff --git a/src/main/java/net/onrc/onos/ofcontroller/bgproute/Path.java b/src/main/java/net/onrc/onos/ofcontroller/bgproute/Path.java
new file mode 100644
index 0000000..5cf4b09
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/Path.java
@@ -0,0 +1,61 @@
+package net.onrc.onos.ofcontroller.bgproute;
+
+import java.net.InetAddress;
+import java.util.Collections;
+import java.util.List;
+
+/*
+ * A path is always assumed to be from all other interfaces (external-facing
+ * switchports) to the destination interface.
+ */
+
+public class Path {
+
+	private Interface dstInterface;
+	private InetAddress dstIpAddress;
+	private int numUsers = 0;
+	
+	private List<PushedFlowMod> flowMods = null;
+	private boolean permanent = false;
+	
+	public Path(Interface dstInterface, InetAddress dstIpAddress) {
+		this.dstInterface = dstInterface;
+		this.dstIpAddress = dstIpAddress;
+	}
+
+	public Interface getDstInterface() {
+		return dstInterface;
+	}
+
+	public InetAddress getDstIpAddress() {
+		return dstIpAddress;
+	}
+	
+	public void incrementUsers() {
+		numUsers++;
+	}
+	
+	public void decrementUsers() {
+		numUsers--;
+	}
+	
+	public int getUsers() {
+		return numUsers;
+	}
+	
+	public List<PushedFlowMod> getFlowMods() {
+		return Collections.unmodifiableList(flowMods);
+	}
+	
+	public void setFlowMods(List<PushedFlowMod> flowMods) {
+		this.flowMods = flowMods;
+	}
+	
+	public boolean isPermanent() {
+		return permanent;
+	}
+	
+	public void setPermanent() {
+		permanent = true;
+	}
+}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/bgproute/PathUpdate.java b/src/main/java/net/onrc/onos/ofcontroller/bgproute/PathUpdate.java
deleted file mode 100644
index 1d2a47b..0000000
--- a/src/main/java/net/onrc/onos/ofcontroller/bgproute/PathUpdate.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package net.onrc.onos.ofcontroller.bgproute;
-
-import java.net.InetAddress;
-
-/*
- * A path is always assumed to be from all other interfaces (external-facing
- * switchports) to the destination interface.
- */
-
-public class PathUpdate {
-
-	private Interface dstInterface;
-	private InetAddress dstIpAddress;
-	
-	public PathUpdate(Interface dstInterface, InetAddress dstIpAddress) {
-		this.dstInterface = dstInterface;
-		this.dstIpAddress = dstIpAddress;
-	}
-
-	public Interface getDstInterface() {
-		return dstInterface;
-	}
-
-	public InetAddress getDstIpAddress() {
-		return dstIpAddress;
-	}
-}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/bgproute/PatriciaTrie.java b/src/main/java/net/onrc/onos/ofcontroller/bgproute/PatriciaTrie.java
index 86bc8cf..89dfb30 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/bgproute/PatriciaTrie.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/PatriciaTrie.java
@@ -3,7 +3,7 @@
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 
-public class PatriciaTrie implements IPatriciaTrie{
+public class PatriciaTrie<V> implements IPatriciaTrie<V> {
 	private final byte maskBits[] = {(byte)0x00, (byte)0x80, (byte)0xc0, (byte)0xe0, (byte)0xf0, 
 												 (byte)0xf8, (byte)0xfc, (byte)0xfe, (byte)0xff};
 	
@@ -15,14 +15,15 @@
 		this.maxPrefixLength = maxPrefixLength;
 	}
 
-	public synchronized RibEntry put(Prefix p, RibEntry r) {
-		if (p.getPrefixLength() > maxPrefixLength) {
+	@Override
+	public synchronized V put(Prefix prefix, V value) {
+		if (prefix.getPrefixLength() > maxPrefixLength) {
 			throw new IllegalArgumentException(String.format(
 					"Prefix length %d is greater than max prefix length %d", 
-					p.getPrefixLength(), maxPrefixLength));
+					prefix.getPrefixLength(), maxPrefixLength));
 		}
 		
-		if (p == null || r == null) {
+		if (prefix == null || value == null) {
 			throw new NullPointerException();
 		}
 		
@@ -30,23 +31,23 @@
 		Node match = null;
 		
 		while (node != null
-				&& node.prefix.getPrefixLength() <= p.getPrefixLength()
-				&& key_match(node.prefix.getAddress(), node.prefix.getPrefixLength(), p.getAddress(), p.getPrefixLength()) == true) {
-		    if (node.prefix.getPrefixLength() == p.getPrefixLength()) {
+				&& node.prefix.getPrefixLength() <= prefix.getPrefixLength()
+				&& key_match(node.prefix.getAddress(), node.prefix.getPrefixLength(), prefix.getAddress(), prefix.getPrefixLength()) == true) {
+		    if (node.prefix.getPrefixLength() == prefix.getPrefixLength()) {
 		    	/*
 		    	 * Prefix is already in tree. This may be an aggregate node, in which case
 		    	 * we are inserting a new prefix, or it could be an actual node, in which 
 		    	 * case we are inserting a new nexthop for the prefix and should return
 		    	 * the old nexthop.
 		    	 */
-		    	RibEntry oldRib = node.rib;
-		    	node.rib = r;
-		    	return oldRib;
+		    	V oldValue = node.value;
+		    	node.value = value;
+		    	return oldValue;
 			}
 
 			match = node;
 			
-			if (bit_check(p.getAddress(), node.prefix.getPrefixLength()) == true) {
+			if (bit_check(prefix.getAddress(), node.prefix.getPrefixLength()) == true) {
 				node = node.right;
 			} else {
 				node = node.left;
@@ -56,7 +57,9 @@
 		Node add = null;
 		
 		if (node == null) {
-			add = new Node(p, r);
+			//add = new Node(p, r);
+			add = new Node(prefix);
+			add.value = value;
 			
 			if (match != null) {
 				node_link(match, add);
@@ -64,7 +67,7 @@
 				top = add;
 			}
 		} else {
-			add = node_common(node, p.getAddress(), p.getPrefixLength());
+			add = node_common(node, prefix.getAddress(), prefix.getPrefixLength());
 			if (add == null) {
 				//I think this is -ENOMEM?
 				//return null;
@@ -77,14 +80,16 @@
 			}
 			node_link(add, node);
 			
-			if (add.prefix.getPrefixLength() != p.getPrefixLength()) {
+			if (add.prefix.getPrefixLength() != prefix.getPrefixLength()) {
 				match = add;
 				
-				add = new Node(p, r);
+				//add = new Node(p, r);
+				add = new Node(prefix);
+				add.value = value;
 				node_link(match, add);
 			}
 			else {
-				add.rib = r;
+				add.value = value;
 			}
 		}
 		
@@ -94,10 +99,9 @@
 	}
 	
 	/*exact match*/
-	public synchronized RibEntry lookup(Prefix p) {
-		//TODO
-		
-		if (p.getPrefixLength() > maxPrefixLength) {
+	@Override
+	public synchronized V lookup(Prefix prefix) {
+		if (prefix.getPrefixLength() > maxPrefixLength) {
 			return null;
 		}
 		
@@ -120,28 +124,36 @@
 		}
 		*/
 		
-		Node node = findNode(p);
+		Node node = findNode(prefix);
 		
-		return node == null ? null : node.rib;
+		return node == null ? null : node.value;
 	}
 	
 	/*closest containing prefix*/
-	public synchronized RibEntry match(Prefix p) {
+	@Override
+	public synchronized V match(Prefix prefix) {
 		//TODO
-		return null;
+		if (prefix.getPrefixLength() > maxPrefixLength) {
+			return null;
+		}
+		
+		Node closestNode = findClosestNode(prefix);
+		
+		return closestNode == null ? null : closestNode.value;
 	}
 	
-	public synchronized boolean remove(Prefix p, RibEntry r) {
+	@Override
+	public synchronized boolean remove(Prefix prefix, V value) {
 		Node child;
 		Node parent;
 		
-		if (p == null || r == null) {
+		if (prefix == null || value == null) {
 			return false;
 		}
 		
-		Node node = findNode(p);
+		Node node = findNode(prefix);
 		
-		if (node == null || node.rib == null || !node.rib.equals(r)) {
+		if (node == null || node.isAggregate() || !node.value.equals(value)) {
 			//Given <prefix, nexthop> mapping is not in the tree
 			return false;
 		}
@@ -151,7 +163,7 @@
 			//In the future, maybe we should re-evaluate what the aggregate prefix should be?
 			//It shouldn't necessarily stay the same.
 			//More complicated if the above prefix is also aggregate.
-			node.rib = null;
+			node.value = null;
 			return true;
 		}
 		
@@ -192,22 +204,23 @@
 		return true;
 	}
 	
-	public Iterator<Entry> iterator() {
+	@Override
+	public Iterator<Entry<V>> iterator() {
 		return new PatriciaTrieIterator(top);
 	}
 	
-	private Node findNode(Prefix p) {
+	private Node findNode(Prefix prefix) {
 		Node node = top;
 		
 		while (node != null
-				&& node.prefix.getPrefixLength() <= p.getPrefixLength()
-				&& key_match(node.prefix.getAddress(), node.prefix.getPrefixLength(), p.getAddress(), p.getPrefixLength()) == true) {
-			if (node.prefix.getPrefixLength() == p.getPrefixLength()) {
+				&& node.prefix.getPrefixLength() <= prefix.getPrefixLength()
+				&& key_match(node.prefix.getAddress(), node.prefix.getPrefixLength(), prefix.getAddress(), prefix.getPrefixLength()) == true) {
+			if (node.prefix.getPrefixLength() == prefix.getPrefixLength()) {
 				//return addReference(node);
 				return node;
 			}
 			
-			if (bit_check(p.getAddress(), node.prefix.getPrefixLength()) == true) {
+			if (bit_check(prefix.getAddress(), node.prefix.getPrefixLength()) == true) {
 				node = node.right;
 			} else {
 				node = node.left;
@@ -217,6 +230,27 @@
 		return null;
 	}
 	
+	private Node findClosestNode(Prefix prefix) {
+		Node node = top;
+		Node match = null;
+		
+		while (node != null
+				&& node.prefix.getPrefixLength() <= prefix.getPrefixLength()
+				&& key_match(node.prefix.getAddress(), node.prefix.getPrefixLength(), prefix.getAddress(), prefix.getPrefixLength()) == true) {
+			if (!node.isAggregate()) {
+				match = node;
+			}
+			
+			if (bit_check(prefix.getAddress(), node.prefix.getPrefixLength()) == true) {
+				node = node.right;
+			} else {
+				node = node.left;
+			}
+		}
+		
+		return match;
+	}
+	
 	/*
 	 * Receives a 1-based bit index
 	 * Returns a 1-based byte index
@@ -325,7 +359,8 @@
 		if (boundary != 0)
 			newPrefix[j] = (byte)(node.prefix.getAddress()[j] & maskBits[common_len % 8]);
 		
-		return new Node(new Prefix(newPrefix, common_len), null);
+		//return new Node(new Prefix(newPrefix, common_len), null);
+		return new Node(new Prefix(newPrefix, common_len));
 		//return add;
 	}
 	
@@ -334,26 +369,33 @@
 		public Node left = null;
 		public Node right = null;
 		
-		public Prefix prefix;
-		public RibEntry rib;
+		public final Prefix prefix;
+		public V value;
 		
-		public Node(Prefix p, RibEntry r) {
+		//public Node(Prefix p, RibEntry r) {
+		//	this.prefix = p;
+		//	this.rib = r;
+		//}
+		public Node(Prefix p) {
 			this.prefix = p;
-			this.rib = r;
 		}
 		
-		public Entry getEntry() {
-			return new PatriciaTrieEntry(prefix, rib);
+		public boolean isAggregate() {
+			return value == null;
+		}
+				
+		public Entry<V> getEntry() {
+			return new PatriciaTrieEntry(prefix, value);
 		}
 	}
 	
-	private class PatriciaTrieEntry implements Entry {
+	private class PatriciaTrieEntry implements Entry<V> {
 		private Prefix prefix;
-		private RibEntry rib;
+		private V value;
 		
-		public PatriciaTrieEntry(Prefix prefix, RibEntry rib) {
+		public PatriciaTrieEntry(Prefix prefix, V value) {
 			this.prefix = prefix;
-			this.rib = rib;
+			this.value = value;
 		}
 		
 		@Override
@@ -362,12 +404,12 @@
 		}
 		
 		@Override
-		public RibEntry getRib() {
-			return rib;
+		public V getValue() {
+			return value;
 		}
 	}
 	
-	private class PatriciaTrieIterator implements Iterator<Entry> {
+	private class PatriciaTrieIterator implements Iterator<Entry<V>> {
 		
 		private Node current;
 		private boolean started = false;
@@ -376,7 +418,7 @@
 			current = start;
 			
 			//If the start is an aggregate node fast forward to find the next valid node
-			if (current != null && current.rib == null) {
+			if (current != null && current.isAggregate()) {
 				current = findNext(current);
 			}
 		}
@@ -395,7 +437,7 @@
 		}
 
 		@Override
-		public Entry next() {
+		public Entry<V> next() {
 			if (current == null) {
 				throw new NoSuchElementException();
 			}
@@ -452,9 +494,9 @@
 				return null;
 			}
 			
-			//If the node doesn't have a rib, it's not an actual node, it's an artifically
+			//If the node doesn't have a value, it's not an actual node, it's an artifically
 			//inserted aggregate node. We don't want to return these to the user.
-			if (next.rib == null) {
+			if (next.isAggregate()) {
 				return findNext(next);
 			}
 			
diff --git a/src/main/java/net/onrc/onos/ofcontroller/bgproute/Prefix.java b/src/main/java/net/onrc/onos/ofcontroller/bgproute/Prefix.java
index 21dd45c..05ce0a4 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/bgproute/Prefix.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/Prefix.java
@@ -4,6 +4,8 @@
 import java.net.UnknownHostException;
 import java.util.Arrays;
 
+import com.google.common.net.InetAddresses;
+
 public class Prefix {
 	private final int MAX_BYTES = 4;
 	
@@ -31,11 +33,7 @@
 
 	public Prefix(String strAddress, int prefixLength) {
 		byte[] addr = null;
-		try {
-			addr = InetAddress.getByName(strAddress).getAddress();
-		} catch (UnknownHostException e) {
-			throw new IllegalArgumentException("Invalid IP inetAddress argument");
-		}
+		addr = InetAddresses.forString(strAddress).getAddress();
 				
 		if (addr == null || addr.length != MAX_BYTES || 
 				prefixLength < 0 || prefixLength > MAX_BYTES * Byte.SIZE) {
diff --git a/src/main/java/net/onrc/onos/ofcontroller/bgproute/PushedFlowMod.java b/src/main/java/net/onrc/onos/ofcontroller/bgproute/PushedFlowMod.java
new file mode 100644
index 0000000..56f4c04
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/PushedFlowMod.java
@@ -0,0 +1,25 @@
+package net.onrc.onos.ofcontroller.bgproute;
+
+import org.openflow.protocol.OFFlowMod;
+
+public class PushedFlowMod {
+	private long dpid;
+	private OFFlowMod flowMod;
+	
+	public PushedFlowMod(long dpid, OFFlowMod flowMod) {
+		this.dpid = dpid;
+		try {
+			this.flowMod = flowMod.clone();
+		} catch (CloneNotSupportedException e) {
+			this.flowMod = flowMod;
+		}
+	}
+	
+	public long getDpid() {
+		return dpid;
+	}
+	
+	public OFFlowMod getFlowMod() {
+		return flowMod;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/net/onrc/onos/ofcontroller/bgproute/RibEntry.java b/src/main/java/net/onrc/onos/ofcontroller/bgproute/RibEntry.java
index c27f962..8087471 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/bgproute/RibEntry.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/RibEntry.java
@@ -1,7 +1,8 @@
 package net.onrc.onos.ofcontroller.bgproute;
 
 import java.net.InetAddress;
-import java.net.UnknownHostException;
+
+import com.google.common.net.InetAddresses;
 
 public class RibEntry {
 	private InetAddress routerId;
@@ -13,12 +14,8 @@
 	}
 	
 	public RibEntry(String routerId, String nextHop) {
-		try {
-			this.routerId = InetAddress.getByName(routerId);
-			this.nextHop = InetAddress.getByName(nextHop);
-		} catch (UnknownHostException e) {
-			throw new IllegalArgumentException("Invalid address format");
-		}
+		this.routerId = InetAddresses.forString(routerId);
+		this.nextHop = InetAddresses.forString(nextHop);
 	}
 	
 	public InetAddress getNextHop() {
diff --git a/src/main/java/net/onrc/onos/ofcontroller/flowmanager/FlowManager.java b/src/main/java/net/onrc/onos/ofcontroller/flowmanager/FlowManager.java
index a072882..bca9ef7 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/flowmanager/FlowManager.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/flowmanager/FlowManager.java
@@ -97,11 +97,6 @@
     private static String measurementFlowIdStr = "0x186a0";	// 100000
     private long modifiedMeasurementFlowTime = 0;
     //
-    private LinkedList<FlowPath> measurementStoredPaths = new LinkedList<FlowPath>();
-    private long measurementStartTimeProcessingPaths = 0;
-    private long measurementEndTimeProcessingPaths = 0;
-    Map<Long, ?> measurementShortestPathTopo = null;
-    private String measurementPerFlowStr = new String();
 
     /** The logger. */
     private static Logger log = LoggerFactory.getLogger(FlowManager.class);
@@ -2305,207 +2300,4 @@
 	//
 	return (installRemoteFlowEntry(flowPath, flowEntry));
     }
-
-    /**
-     * Store a path flow for measurement purpose.
-     *
-     * NOTE: The Flow Path argument does NOT contain flow entries.
-     * The Shortest Path is computed, and the corresponding Flow Entries
-     * are stored in the Flow Path.
-     *
-     * @param flowPath the Flow Path with the endpoints and the match
-     * conditions to store.
-     * @return the stored shortest-path flow on success, otherwise null.
-     */
-    @Override
-    public synchronized FlowPath measurementStorePathFlow(FlowPath flowPath) {
-	//
-	// Prepare the Shortest Path computation if the first Flow Path
-	//
-	if (measurementStoredPaths.isEmpty())
-	    measurementShortestPathTopo = topoRouteService.prepareShortestPathTopo();
-
-	//
-	// Compute the Shortest Path
-	//
-	DataPath dataPath =
-	    topoRouteService.getTopoShortestPath(measurementShortestPathTopo,
-						 flowPath.dataPath().srcPort(),
-						 flowPath.dataPath().dstPort());
-	if (dataPath == null) {
-	    // We need the DataPath to populate the Network MAP
-	    dataPath = new DataPath();
-	    dataPath.setSrcPort(flowPath.dataPath().srcPort());
-	    dataPath.setDstPort(flowPath.dataPath().dstPort());
-	}
-	dataPath.applyFlowPathFlags(flowPath.flowPathFlags());
-
-	//
-	// Set the incoming port matching and the outgoing port output
-	// actions for each flow entry.
-	//
-	for (FlowEntry flowEntry : dataPath.flowEntries()) {
-	    // Set the incoming port matching
-	    FlowEntryMatch flowEntryMatch = new FlowEntryMatch();
-	    flowEntry.setFlowEntryMatch(flowEntryMatch);
-	    flowEntryMatch.enableInPort(flowEntry.inPort());
-
-	    // Set the outgoing port output action
-	    FlowEntryActions flowEntryActions = flowEntry.flowEntryActions();
-	    FlowEntryAction flowEntryAction = new FlowEntryAction();
-	    flowEntryAction.setActionOutput(flowEntry.outPort());
-	    flowEntryActions.addAction(flowEntryAction);
-	}
-
-	//
-	// Prepare the computed Flow Path
-	//
-	FlowPath computedFlowPath = new FlowPath();
-	computedFlowPath.setFlowId(new FlowId(flowPath.flowId().value()));
-	computedFlowPath.setInstallerId(new CallerId(flowPath.installerId().value()));
-	computedFlowPath.setFlowPathFlags(new FlowPathFlags(flowPath.flowPathFlags().flags()));
-	computedFlowPath.setDataPath(dataPath);
-	computedFlowPath.setFlowEntryMatch(new FlowEntryMatch(flowPath.flowEntryMatch()));
-
-	//
-	// Add the computed Flow Path to the internal storage
-	//
-	measurementStoredPaths.add(computedFlowPath);
-
-	log.debug("Measurement storing path {}",
-		  computedFlowPath.flowId().toString());
-
-	return (computedFlowPath);
-    }
-
-    /**
-     * Install path flows for measurement purpose.
-     *
-     * @param numThreads the number of threads to use to install the path
-     * flows.
-     * @return true on success, otherwise false.
-     */
-    @Override
-    public boolean measurementInstallPaths(Integer numThreads) {
-	// Create a copy of the Flow Paths to install
-	final ConcurrentLinkedQueue<FlowPath> measurementProcessingPaths =
-	    new ConcurrentLinkedQueue<FlowPath>(measurementStoredPaths);
-
-	/**
-	 * A Thread-wrapper class for executing the threads and collecting
-	 * the measurement data.
-	 */
-	class MyThread extends Thread {
-	    public long[] execTime = new long[2000];
-	    public int samples = 0;
-	    public int threadId = -1;
-	    @Override
-	    public void run() {
-		while (true) {
-		    FlowPath flowPath = measurementProcessingPaths.poll();
-		    if (flowPath == null)
-			return;
-		    // Install the Flow Path
-		    FlowId flowId = new FlowId();
-		    String dataPathSummaryStr =
-			flowPath.dataPath().dataPathSummary();
-		    long startTime = System.nanoTime();
-		    addFlow(flowPath, flowId, dataPathSummaryStr);
-		    long endTime = System.nanoTime();
-		    execTime[samples] = endTime - startTime;
-		    samples++;
-		}
-	    }
-	};
-
-	List<MyThread> threads = new LinkedList<MyThread>();
-
-	log.debug("Measurement Installing {} flows",
-		  measurementProcessingPaths.size());
-
-	//
-	// Create the threads to install the Flow Paths
-	//
-	for (int i = 0; i < numThreads; i++) {
-	    MyThread thread = new MyThread();
-	    thread.threadId = i;
-	    threads.add(thread);
-	}
-
-	//
-	// Start processing
-	//
-	measurementEndTimeProcessingPaths = 0;
-	measurementStartTimeProcessingPaths = System.nanoTime();
-	for (Thread thread : threads) {
-	    thread.start();
-	}
-
-	// Wait for all threads to complete
-	for (Thread thread : threads) {
-	    try {
-		thread.join();
-	    } catch (InterruptedException e) {
-		log.debug("Exception waiting for a thread to install a Flow Path: ", e);
-	    }
-	}
-
-	// Record the end of processing
-	measurementEndTimeProcessingPaths = System.nanoTime();
-
-	//
-	// Prepare the string with measurement data per each Flow Path
-	// installation.
-	// The string is multiple lines: one line per Flow Path installation:
-	//    ThreadAndTimePerFlow <ThreadId> <TotalThreads> <Time(ns)>
-	//
-	measurementPerFlowStr = new String();
-	String eol = System.getProperty("line.separator");
-	for (MyThread thread : threads) {
-	    for (int i = 0; i < thread.samples; i++) {
-		measurementPerFlowStr += "ThreadAndTimePerFlow " + thread.threadId + " " + numThreads + " " + thread.execTime[i] + eol;
-	    }
-	}
-
-	return true;
-    }
-
-    /**
-     * Get the measurement time that took to install the path flows.
-     *
-     * @return the measurement time (in nanoseconds) it took to install
-     * the path flows.
-     */
-    @Override
-    public Long measurementGetInstallPathsTimeNsec() {
-	return new Long(measurementEndTimeProcessingPaths -
-			measurementStartTimeProcessingPaths);
-    }
-
-    /**
-     * Get the measurement install time per Flow.
-     *
-     * @return a multi-line string with the following format per line:
-     * ThreadAndTimePerFlow <ThreadId> <TotalThreads> <Time(ns)>
-     */
-    @Override
-    public String measurementGetPerFlowInstallTime() {
-	return new String(measurementPerFlowStr);
-    }
-
-    /**
-     * Clear the path flows stored for measurement purpose.
-     *
-     * @return true on success, otherwise false.
-     */
-    @Override
-    public boolean measurementClearAllPaths() {
-	measurementStoredPaths.clear();
-	topoRouteService.dropShortestPathTopo(measurementShortestPathTopo);
-	measurementStartTimeProcessingPaths = 0;
-	measurementEndTimeProcessingPaths = 0;
-	measurementPerFlowStr = new String();
-
-	return true;
-    }
 }
diff --git a/src/main/java/net/onrc/onos/ofcontroller/flowmanager/IFlowService.java b/src/main/java/net/onrc/onos/ofcontroller/flowmanager/IFlowService.java
index ba9cd1b..0fbb23c 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/flowmanager/IFlowService.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/flowmanager/IFlowService.java
@@ -115,47 +115,4 @@
      * @return the added shortest-path flow on success, otherwise null.
      */
     public FlowPath addAndMaintainShortestPathFlow(FlowPath flowPath);
-
-    /**
-     * Store a path flow for measurement purpose.
-     *
-     * NOTE: The Flow Path argument does NOT contain flow entries.
-     *
-     * @param flowPath the Flow Path with the endpoints and the match
-     * conditions to store.
-     * @return the stored shortest-path flow on success, otherwise null.
-     */
-    public FlowPath measurementStorePathFlow(FlowPath flowPath);
-
-    /**
-     * Install path flows for measurement purpose.
-     *
-     * @param numThreads the number of threads to use to install the path
-     * flows.
-     * @return true on success, otherwise false.
-     */
-    public boolean measurementInstallPaths(Integer numThreads);
-
-    /**
-     * Get the measurement time that took to install the path flows.
-     *
-     * @return the measurement time (in nanoseconds) it took to install
-     * the path flows.
-     */
-    public Long measurementGetInstallPathsTimeNsec();
-
-    /**
-     * Get the measurement install time per Flow.
-     *
-     * @return a multi-line string with the following format per line:
-     * ThreadAndTimePerFlow <ThreadId> <TotalThreads> <Time(ns)>
-     */
-    public String measurementGetPerFlowInstallTime();
-
-    /**
-     * Clear the path flows stored for measurement purpose.
-     *
-     * @return true on success, otherwise false.
-     */
-    public boolean measurementClearAllPaths();
 }
diff --git a/src/main/java/net/onrc/onos/ofcontroller/flowmanager/web/FlowWebRoutable.java b/src/main/java/net/onrc/onos/ofcontroller/flowmanager/web/FlowWebRoutable.java
index 954c84d..e1c6da9 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/flowmanager/web/FlowWebRoutable.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/flowmanager/web/FlowWebRoutable.java
@@ -22,11 +22,6 @@
         router.attach("/getall-by-endpoints/{src-dpid}/{src-port}/{dst-dpid}/{dst-port}/json", GetAllFlowsByEndpointsResource.class);
         router.attach("/getall/json", GetAllFlowsResource.class);
         router.attach("/getsummary/{flow-id}/{max-flows}/json", GetSummaryFlowsResource.class);
-        router.attach("/measurement-store-path/json", MeasurementStorePathFlowResource.class);
-        router.attach("/measurement-install-paths/{num-threads}/json", MeasurementInstallPathsFlowResource.class);
-        router.attach("/measurement-get-install-paths-time-nsec/json", MeasurementGetInstallPathsTimeNsecFlowResource.class);
-        router.attach("/measurement-get-per-flow-install-time/json", MeasurementGetPerFlowInstallTimeFlowResource.class);
-        router.attach("/measurement-clear-all-paths/json", MeasurementClearAllPathsFlowResource.class);
         return router;
     }
 
diff --git a/src/main/java/net/onrc/onos/ofcontroller/flowmanager/web/MeasurementClearAllPathsFlowResource.java b/src/main/java/net/onrc/onos/ofcontroller/flowmanager/web/MeasurementClearAllPathsFlowResource.java
deleted file mode 100644
index 07d9fb2..0000000
--- a/src/main/java/net/onrc/onos/ofcontroller/flowmanager/web/MeasurementClearAllPathsFlowResource.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package net.onrc.onos.ofcontroller.flowmanager.web;
-
-import net.onrc.onos.ofcontroller.flowmanager.IFlowService;
-
-import org.restlet.resource.Get;
-import org.restlet.resource.ServerResource;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class MeasurementClearAllPathsFlowResource extends ServerResource {
-    protected static Logger log = LoggerFactory.getLogger(MeasurementClearAllPathsFlowResource.class);
-
-    @Get("json")
-    public Boolean retrieve() {
-	Boolean result = false;
-
-        IFlowService flowService =
-                (IFlowService)getContext().getAttributes().
-                get(IFlowService.class.getCanonicalName());
-
-        if (flowService == null) {
-	    log.debug("ONOS Flow Service not found");
-            return result;
-	}
-
-	// Extract the arguments
-	log.debug("Measurement Clear All Paths");
-
-	// Process the request
-	result = flowService.measurementClearAllPaths();
-	return result;
-    }
-}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/flowmanager/web/MeasurementGetInstallPathsTimeNsecFlowResource.java b/src/main/java/net/onrc/onos/ofcontroller/flowmanager/web/MeasurementGetInstallPathsTimeNsecFlowResource.java
deleted file mode 100644
index 467afca..0000000
--- a/src/main/java/net/onrc/onos/ofcontroller/flowmanager/web/MeasurementGetInstallPathsTimeNsecFlowResource.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package net.onrc.onos.ofcontroller.flowmanager.web;
-
-import net.onrc.onos.ofcontroller.flowmanager.IFlowService;
-
-import org.restlet.resource.Get;
-import org.restlet.resource.ServerResource;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class MeasurementGetInstallPathsTimeNsecFlowResource extends ServerResource {
-    protected static Logger log = LoggerFactory.getLogger(MeasurementGetInstallPathsTimeNsecFlowResource.class);
-
-    @Get("json")
-    public Long retrieve() {
-	Long result = null;
-
-        IFlowService flowService =
-                (IFlowService)getContext().getAttributes().
-                get(IFlowService.class.getCanonicalName());
-
-        if (flowService == null) {
-	    log.debug("ONOS Flow Service not found");
-	    return result;
-	}
-
-	// Extract the arguments
-
-	// Process the request
-	result = flowService.measurementGetInstallPathsTimeNsec();
-
-	log.debug("Measurement Get Install Paths Time (nsec): " + result);
-
-	return result;
-    }
-}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/flowmanager/web/MeasurementGetPerFlowInstallTimeFlowResource.java b/src/main/java/net/onrc/onos/ofcontroller/flowmanager/web/MeasurementGetPerFlowInstallTimeFlowResource.java
deleted file mode 100644
index 92d84ab..0000000
--- a/src/main/java/net/onrc/onos/ofcontroller/flowmanager/web/MeasurementGetPerFlowInstallTimeFlowResource.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package net.onrc.onos.ofcontroller.flowmanager.web;
-
-import net.onrc.onos.ofcontroller.flowmanager.IFlowService;
-
-import org.restlet.resource.Get;
-import org.restlet.resource.ServerResource;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class MeasurementGetPerFlowInstallTimeFlowResource extends ServerResource {
-    protected static Logger log = LoggerFactory.getLogger(MeasurementGetPerFlowInstallTimeFlowResource.class);
-
-    @Get("json")
-    public String retrieve() {
-	String result = null;
-
-        IFlowService flowService =
-                (IFlowService)getContext().getAttributes().
-                get(IFlowService.class.getCanonicalName());
-
-        if (flowService == null) {
-	    log.debug("ONOS Flow Service not found");
-	    return result;
-	}
-
-	// Extract the arguments
-
-	// Process the request
-	result = flowService.measurementGetPerFlowInstallTime();
-
-	log.debug("Measurement Get Install Paths Time (nsec): " + result);
-
-	return result;
-    }
-}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/flowmanager/web/MeasurementInstallPathsFlowResource.java b/src/main/java/net/onrc/onos/ofcontroller/flowmanager/web/MeasurementInstallPathsFlowResource.java
deleted file mode 100644
index 074dfb4..0000000
--- a/src/main/java/net/onrc/onos/ofcontroller/flowmanager/web/MeasurementInstallPathsFlowResource.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package net.onrc.onos.ofcontroller.flowmanager.web;
-
-import net.onrc.onos.ofcontroller.flowmanager.IFlowService;
-
-import org.restlet.resource.Get;
-import org.restlet.resource.ServerResource;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class MeasurementInstallPathsFlowResource extends ServerResource {
-    protected static Logger log = LoggerFactory.getLogger(MeasurementInstallPathsFlowResource.class);
-
-    @Get("json")
-    public Boolean retrieve() {
-	Boolean result = false;
-
-        IFlowService flowService =
-                (IFlowService)getContext().getAttributes().
-                get(IFlowService.class.getCanonicalName());
-
-        if (flowService == null) {
-	    log.debug("ONOS Flow Service not found");
-	    return result;
-	}
-
-	// Extract the arguments
-	String numThreadsStr = (String) getRequestAttributes().get("num-threads");
-	Integer numThreads = new Integer(numThreadsStr);
-	log.debug("Measurement Install Paths Number of Threads " + numThreadsStr);
-
-	// Process the request
-	result = flowService.measurementInstallPaths(numThreads);
-	return result;
-    }
-}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/flowmanager/web/MeasurementStorePathFlowResource.java b/src/main/java/net/onrc/onos/ofcontroller/flowmanager/web/MeasurementStorePathFlowResource.java
deleted file mode 100644
index 0f23663..0000000
--- a/src/main/java/net/onrc/onos/ofcontroller/flowmanager/web/MeasurementStorePathFlowResource.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package net.onrc.onos.ofcontroller.flowmanager.web;
-
-import java.io.IOException;
-
-import net.onrc.onos.ofcontroller.flowmanager.IFlowService;
-import net.onrc.onos.ofcontroller.util.FlowId;
-import net.onrc.onos.ofcontroller.util.FlowPath;
-
-import org.codehaus.jackson.JsonGenerationException;
-import org.codehaus.jackson.map.JsonMappingException;
-import org.codehaus.jackson.map.ObjectMapper;
-import org.restlet.resource.Post;
-import org.restlet.resource.ServerResource;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class MeasurementStorePathFlowResource extends ServerResource {
-
-    protected static Logger log = LoggerFactory.getLogger(MeasurementStorePathFlowResource.class);
-
-    @Post("json")
-    public FlowId store(String flowJson) {
-	FlowId result = new FlowId();
-
-        IFlowService flowService =
-                (IFlowService)getContext().getAttributes().
-                get(IFlowService.class.getCanonicalName());
-
-        if (flowService == null) {
-	    log.debug("ONOS Flow Service not found");
-            return result;
-	}
-
-	//
-	// Extract the arguments
-	// NOTE: The "flow" is specified in JSON format.
-	//
-	ObjectMapper mapper = new ObjectMapper();
-	String flowPathStr = flowJson;
-	FlowPath flowPath = null;
-	log.debug("Measurement Store Flow Path: " + flowPathStr);
-	try {
-	    flowPath = mapper.readValue(flowPathStr, FlowPath.class);
-	} catch (JsonGenerationException e) {
-	    e.printStackTrace();
-	} catch (JsonMappingException e) {
-	    e.printStackTrace();
-	} catch (IOException e) {
-	    e.printStackTrace();
-	}
-
-	// Process the request
-	if (flowPath != null) {
-	    FlowPath addedFlowPath =
-		flowService.measurementStorePathFlow(flowPath);
-	    if (addedFlowPath == null)
-		result = new FlowId();		// Error: Return empty Flow Id
-	    else
-		result = addedFlowPath.flowId();
-	}
-
-        return result;
-    }
-}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ProxyArpManager.java b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ProxyArpManager.java
index 521ba0f..493d58e 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ProxyArpManager.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ProxyArpManager.java
@@ -17,11 +17,13 @@
 import net.floodlightcontroller.core.IFloodlightProviderService;
 import net.floodlightcontroller.core.IOFMessageListener;
 import net.floodlightcontroller.core.IOFSwitch;
-import net.floodlightcontroller.devicemanager.IDeviceService;
 import net.floodlightcontroller.packet.ARP;
 import net.floodlightcontroller.packet.Ethernet;
 import net.floodlightcontroller.topology.ITopologyService;
 import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.ofcontroller.bgproute.IPatriciaTrie;
+import net.onrc.onos.ofcontroller.bgproute.Interface;
+import net.onrc.onos.ofcontroller.bgproute.Prefix;
 
 import org.openflow.protocol.OFMessage;
 import org.openflow.protocol.OFPacketIn;
@@ -39,6 +41,7 @@
 import com.google.common.collect.SetMultimap;
 
 //TODO have L2 and also L3 mode, where it takes into account interface addresses
+//TODO REST API to inspect ARP table
 public class ProxyArpManager implements IProxyArpService, IOFMessageListener {
 	private static Logger log = LoggerFactory.getLogger(ProxyArpManager.class);
 	
@@ -48,12 +51,19 @@
 			
 	protected IFloodlightProviderService floodlightProvider;
 	protected ITopologyService topology;
-	protected IDeviceService devices;
 	
 	protected Map<InetAddress, ArpTableEntry> arpTable;
 
 	protected SetMultimap<InetAddress, ArpRequest> arpRequests;
 	
+	public enum Mode {L2_MODE, L3_MODE}
+	
+	private Mode mode;
+	private IPatriciaTrie<Interface> interfacePtrie = null;
+	private Collection<Interface> interfaces = null;
+	private MACAddress routerMacAddress = null;
+	//private SwitchPort bgpdAttachmentPoint = null;
+	
 	private class ArpRequest {
 		private IArpRequester requester;
 		private boolean retry;
@@ -88,16 +98,28 @@
 	}
 	
 	public ProxyArpManager(IFloodlightProviderService floodlightProvider,
-				ITopologyService topology, IDeviceService devices){
+				ITopologyService topology){
 		this.floodlightProvider = floodlightProvider;
 		this.topology = topology;
-		this.devices = devices;
 		
 		arpTable = new HashMap<InetAddress, ArpTableEntry>();
 
 		arpRequests = Multimaps.synchronizedSetMultimap(
 				HashMultimap.<InetAddress, ArpRequest>create());
 		
+		mode = Mode.L2_MODE;
+	}
+	
+	public void setL3Mode(IPatriciaTrie<Interface> interfacePtrie, 
+			Collection<Interface> interfaces, MACAddress routerMacAddress) {
+		this.interfacePtrie = interfacePtrie;
+		this.interfaces = interfaces;
+		this.routerMacAddress = routerMacAddress;
+
+		mode = Mode.L3_MODE;
+	}
+	
+	public void startUp() {
 		Timer arpTimer = new Timer();
 		arpTimer.scheduleAtFixedRate(new TimerTask() {
 			@Override
@@ -203,43 +225,77 @@
 	}
 	
 	protected void handleArpRequest(IOFSwitch sw, OFPacketIn pi, ARP arp) {
-		log.debug("ARP request received for {}", 
+		log.trace("ARP request received for {}", 
 				bytesToStringAddr(arp.getTargetProtocolAddress()));
+
+		InetAddress target;
+		InetAddress source;
+		try {
+			 target = InetAddress.getByAddress(arp.getTargetProtocolAddress());
+			 source = InetAddress.getByAddress(arp.getSenderProtocolAddress());
+		} catch (UnknownHostException e) {
+			log.debug("Invalid address in ARP request", e);
+			return;
+		}
+		
+		if (mode == Mode.L3_MODE) {
+			
+			//if (originatedOutsideNetwork(source)) {
+			if (originatedOutsideNetwork(sw.getId(), pi.getInPort())) {
+				//If the request came from outside our network, we only care if
+				//it was a request for one of our interfaces.
+				if (isInterfaceAddress(target)) {
+					log.trace("ARP request for our interface. Sending reply {} => {}",
+							target.getHostAddress(), routerMacAddress.toString());
+					sendArpReply(arp, sw.getId(), pi.getInPort(), routerMacAddress.toBytes());
+				}
+				return;
+			}
+			
+			/*
+			Interface intf = interfacePtrie.match(new Prefix(target.getAddress(), 32));
+			//if (intf != null && target.equals(intf.getIpAddress())) {
+			if (intf != null) {
+				if (target.equals(intf.getIpAddress())) {
+					//ARP request for one of our interfaces, we can reply straight away
+					sendArpReply(arp, sw.getId(), pi.getInPort(), routerMacAddress.toBytes());
+				}
+				// If we didn't enter the above if block, then we found a matching
+				// interface for the target IP but the request wasn't for us.
+				// This is someone else ARPing for a different host in the subnet.
+				// We shouldn't do anything in this case - if we let processing continue
+				// we'll end up erroneously re-broadcasting an ARP for someone else.
+				return;
+			}
+			*/
+		}
 		
 		byte[] mac = lookupArpTable(arp.getTargetProtocolAddress());
 		
 		if (mac == null){
 			//Mac address is not in our arp table.
 			
-			//TODO check what the DeviceManager thinks
-			
 			//Record where the request came from so we know where to send the reply
-			InetAddress target;
-			try {
-				 target = InetAddress.getByAddress(arp.getTargetProtocolAddress());
-			} catch (UnknownHostException e) {
-				log.debug("Invalid address in ARP request", e);
-				//return Command.CONTINUE; //Continue or stop?
-				return;
-			}
-			
-			//Should we just broadcast all received requests here? Or rate limit
-			//if we know we just sent an request?
 			arpRequests.put(target, new ArpRequest(
 					new HostArpRequester(this, arp, sw.getId(), pi.getInPort()), false));
 						
 			//Flood the request out edge ports
-			broadcastArpRequestOutEdge(pi.getPacketData(), sw.getId(), pi.getInPort());
+			//broadcastArpRequestOutEdge(pi.getPacketData(), sw.getId(), pi.getInPort());
+			sendArpRequestToSwitches(target, pi.getPacketData(), sw.getId(), pi.getInPort());
 		}
 		else {
 			//We know the address, so send a reply
-			log.debug("Sending reply of {}", MACAddress.valueOf(mac).toString());
+			log.trace("Sending reply: {} => {} to host at {}/{}", new Object [] {
+					bytesToStringAddr(arp.getTargetProtocolAddress()),
+					MACAddress.valueOf(mac).toString(),
+					HexString.toHexString(sw.getId()), pi.getInPort()});
+			
 			sendArpReply(arp, sw.getId(), pi.getInPort(), mac);
 		}
 	}
 	
 	protected void handleArpReply(IOFSwitch sw, OFPacketIn pi, ARP arp){
-		log.debug("ARP reply recieved for {}, is {}, on {}/{}", new Object[] { 
+		log.trace("ARP reply recieved: {} => {}, on {}/{}", new Object[] { 
 				bytesToStringAddr(arp.getSenderProtocolAddress()),
 				HexString.toHexString(arp.getSenderHardwareAddress()),
 				HexString.toHexString(sw.getId()), pi.getInPort()});
@@ -257,14 +313,21 @@
 		Set<ArpRequest> requests = arpRequests.get(addr);
 		
 		//Synchronize on the Multimap while using an iterator for one of the sets
+		List<ArpRequest> requestsToSend = new ArrayList<ArpRequest>(requests.size());
 		synchronized (arpRequests) {
 			Iterator<ArpRequest> it = requests.iterator();
 			while (it.hasNext()) {
 				ArpRequest request = it.next();
 				it.remove();
-				request.dispatchReply(addr, arp.getSenderHardwareAddress());
+				//request.dispatchReply(addr, arp.getSenderHardwareAddress());
+				requestsToSend.add(request);
 			}
 		}
+		
+		//Don't hold an ARP lock while dispatching requests
+		for (ArpRequest request : requestsToSend) {
+			request.dispatchReply(addr, arp.getSenderHardwareAddress());
+		}
 	}
 
 	private synchronized byte[] lookupArpTable(byte[] ipAddress){
@@ -279,7 +342,7 @@
 		ArpTableEntry arpEntry = arpTable.get(addr);
 		
 		if (arpEntry == null){
-			log.debug("MAC for {} unknown", bytesToStringAddr(ipAddress));
+			//log.debug("MAC for {} unknown", bytesToStringAddr(ipAddress));
 			return null;
 		}
 		
@@ -320,7 +383,7 @@
 		//TODO what should the sender IP address be? Probably not 0.0.0.0
 		byte[] zeroIpv4 = {0x0, 0x0, 0x0, 0x0};
 		byte[] zeroMac = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
-		byte[] bgpdMac = {0x0, 0x0, 0x0, 0x0, 0x0, 0x01};
+		//byte[] bgpdMac = {0x0, 0x0, 0x0, 0x0, 0x0, 0x01};
 		byte[] broadcastMac = {(byte)0xff, (byte)0xff, (byte)0xff, 
 				(byte)0xff, (byte)0xff, (byte)0xff};
 		
@@ -331,19 +394,54 @@
 			.setHardwareAddressLength((byte)Ethernet.DATALAYER_ADDRESS_LENGTH)
 			.setProtocolAddressLength((byte)4) //can't find the constant anywhere
 			.setOpCode(ARP.OP_REQUEST)
-			.setSenderHardwareAddress(bgpdMac)
-			.setSenderProtocolAddress(zeroIpv4)
+			//.setSenderHardwareAddress(bgpdMac)
+			.setSenderHardwareAddress(routerMacAddress.toBytes())
+			//.setSenderProtocolAddress(zeroIpv4)
 			.setTargetHardwareAddress(zeroMac)
 			.setTargetProtocolAddress(ipAddress.getAddress());
-	
+
+		byte[] senderIPAddress = zeroIpv4;
+		if (mode == Mode.L3_MODE) {
+			Interface intf = interfacePtrie.match(new Prefix(ipAddress.getAddress(), 32));
+			if (intf != null) {
+				senderIPAddress = intf.getIpAddress().getAddress();
+			}
+		}
+		
+		arpRequest.setSenderProtocolAddress(senderIPAddress);
+		
 		Ethernet eth = new Ethernet();
-		//eth.setDestinationMACAddress(arpRequest.getSenderHardwareAddress())
-		eth.setSourceMACAddress(bgpdMac)
+		eth.setSourceMACAddress(routerMacAddress.toBytes())
 			.setDestinationMACAddress(broadcastMac)
 			.setEtherType(Ethernet.TYPE_ARP)
 			.setPayload(arpRequest);
 		
-		broadcastArpRequestOutEdge(eth.serialize(), 0, OFPort.OFPP_NONE.getValue());
+		//broadcastArpRequestOutEdge(eth.serialize(), 0, OFPort.OFPP_NONE.getValue());
+		sendArpRequestToSwitches(ipAddress, eth.serialize());
+	}
+	
+	private void sendArpRequestToSwitches(InetAddress dstAddress, byte[] arpRequest) {
+		sendArpRequestToSwitches(dstAddress, arpRequest, 0, OFPort.OFPP_NONE.getValue());
+	}
+	private void sendArpRequestToSwitches(InetAddress dstAddress, byte[] arpRequest,
+			long inSwitch, short inPort) {
+		if (mode == Mode.L2_MODE) {
+			//log.debug("mode is l2");
+			broadcastArpRequestOutEdge(arpRequest, inSwitch, inPort);
+		}
+		else if (mode == Mode.L3_MODE) {
+			//log.debug("mode is l3");
+			//TODO the case where it should be broadcast out all non-interface
+			//edge ports
+			Interface intf = interfacePtrie.match(new Prefix(dstAddress.getAddress(), 32));
+			if (intf != null) {
+				sendArpRequestOutPort(arpRequest, intf.getDpid(), intf.getPort());
+			}
+			else {
+				log.debug("No interface found to send ARP request for {}", 
+						dstAddress.getHostAddress());
+			}
+		}
 	}
 	
 	private void broadcastArpRequestOutEdge(byte[] arpRequest, long inSwitch, short inPort) {
@@ -394,7 +492,43 @@
 		}
 	}
 	
+	private void sendArpRequestOutPort(byte[] arpRequest, long dpid, short port) {
+		log.debug("Sending ARP request out {}/{}", HexString.toHexString(dpid), port);
+		
+		OFPacketOut po = new OFPacketOut();
+		po.setInPort(OFPort.OFPP_NONE)
+			.setBufferId(-1)
+			.setPacketData(arpRequest);
+			
+		List<OFAction> actions = new ArrayList<OFAction>();
+		actions.add(new OFActionOutput(port));
+		po.setActions(actions);
+		short actionsLength = (short) (actions.size() * OFActionOutput.MINIMUM_LENGTH);
+		po.setActionsLength(actionsLength);
+		po.setLengthU(OFPacketOut.MINIMUM_LENGTH + actionsLength 
+				+ arpRequest.length);
+		
+		IOFSwitch sw = floodlightProvider.getSwitches().get(dpid);
+		
+		if (sw == null) {
+			log.debug("Switch not found when sending ARP request");
+			return;
+		}
+		
+		try {
+			sw.write(po, null);
+			sw.flush();
+		} catch (IOException e) {
+			log.error("Failure writing packet out to switch", e);
+		}
+	}
+	
 	public void sendArpReply(ARP arpRequest, long dpid, short port, byte[] targetMac) {
+		log.trace("Sending reply {} => {} to {}", new Object[] {
+				bytesToStringAddr(arpRequest.getTargetProtocolAddress()),
+				HexString.toHexString(targetMac),
+				bytesToStringAddr(arpRequest.getSenderProtocolAddress())});
+		
 		ARP arpReply = new ARP();
 		arpReply.setHardwareType(ARP.HW_TYPE_ETHERNET)
 			.setProtocolType(ARP.PROTO_TYPE_IP)
@@ -430,6 +564,8 @@
 		IOFSwitch sw = floodlightProvider.getSwitches().get(dpid);
 		
 		if (sw == null) {
+			log.error("Switch {} not found when sending ARP reply", 
+					HexString.toHexString(dpid));
 			return;
 		}
 		
@@ -467,6 +603,55 @@
 		arpRequests.put(ipAddress, new ArpRequest(requester, retry));
 		//storeRequester(ipAddress, requester, retry);
 		
-		sendArpRequestForAddress(ipAddress);
+		//Sanity check to make sure we don't send a request for our own address
+		if (!isInterfaceAddress(ipAddress)) {
+			sendArpRequestForAddress(ipAddress);
+		}
+	}
+	
+	/*
+	 * TODO These methods might be more suited to some kind of L3 information service
+	 * that ProxyArpManager could query, rather than having the information 
+	 * embedded in ProxyArpManager. There may be many modules that need L3 information.
+	 */
+	
+	private boolean originatedOutsideNetwork(InetAddress source) {
+		Interface intf = interfacePtrie.match(new Prefix(source.getAddress(), 32));
+		if (intf != null) {
+			if (intf.getIpAddress().equals(source)) {
+				// This request must have been originated by us (the controller)
+				return false;
+			}
+			else {
+				// Source was in one of our interface subnets, but wasn't us.
+				// It must be external.
+				return true;
+			}
+		}
+		else {
+			// Source is not in one of our interface subnets. It's probably a host
+			// in our network as we should only receive ARPs broadcast by external
+			// hosts if they're in the same subnet.
+			return false;
+		}
+	}
+	
+	private boolean originatedOutsideNetwork(long inDpid, short inPort) {
+		for (Interface intf : interfaces) {
+			if (intf.getDpid() == inDpid && intf.getPort() == inPort) {
+				return true;
+			}
+		}
+		return false;
+	}
+	
+	private boolean isInterfaceAddress(InetAddress address) {
+		Interface intf = interfacePtrie.match(new Prefix(address.getAddress(), 32));
+		return (intf != null && intf.getIpAddress().equals(address));
+	}
+	
+	private boolean inInterfaceSubnet(InetAddress address) {
+		Interface intf = interfacePtrie.match(new Prefix(address.getAddress(), 32));
+		return (intf != null && !intf.getIpAddress().equals(address));
 	}
 }
diff --git a/src/test/java/net/onrc/onos/ofcontroller/bgproute/PatriciaTrieTest.java b/src/test/java/net/onrc/onos/ofcontroller/bgproute/PatriciaTrieTest.java
index 1af0416..571d37d 100644
--- a/src/test/java/net/onrc/onos/ofcontroller/bgproute/PatriciaTrieTest.java
+++ b/src/test/java/net/onrc/onos/ofcontroller/bgproute/PatriciaTrieTest.java
@@ -4,7 +4,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import java.util.HashMap;
 import java.util.Iterator;
@@ -12,18 +11,17 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class PatriciaTrieTest {
 
-	IPatriciaTrie ptrie;
+	IPatriciaTrie<RibEntry> ptrie;
 	Prefix[] prefixes;
 	Map<Prefix, RibEntry> mappings;
 	
 	@Before
 	public void setUp() throws Exception {
-		ptrie = new PatriciaTrie(32);
+		ptrie = new PatriciaTrie<RibEntry>(32);
 		mappings = new HashMap<Prefix, RibEntry>();
 		
 		prefixes = new Prefix[] {
@@ -50,7 +48,7 @@
 
 	@Test
 	public void testPut() {
-		IPatriciaTrie ptrie = new PatriciaTrie(32);
+		IPatriciaTrie<RibEntry> ptrie = new PatriciaTrie<RibEntry>(32);
 		
 		Prefix p1 = new Prefix("192.168.240.0", 20);
 		RibEntry r1 = new RibEntry("192.168.10.101", "192.168.60.2");
@@ -107,10 +105,27 @@
 		assertTrue(retval.equals(r2));
 	}
 
-	@Ignore
+	//@Ignore
 	@Test
 	public void testMatch() {
-		fail("Not yet implemented");
+		Prefix p1 = new Prefix("192.168.10.30", 32);
+		Prefix p2 = new Prefix("192.168.10.30", 31);
+		Prefix p3 = new Prefix("192.168.8.241", 32);
+		Prefix p4 = new Prefix("1.0.0.0", 32);
+		Prefix p5 = new Prefix("192.168.8.0", 22);
+		Prefix p6 = new Prefix("192.168.8.0", 21);
+		
+		assertTrue(ptrie.match(p1).equals(mappings.get(prefixes[0])));
+		assertTrue(ptrie.match(p2).equals(mappings.get(prefixes[0])));
+		assertTrue(ptrie.match(p3).equals(mappings.get(prefixes[1])));
+		assertNull(ptrie.match(p4));
+		assertTrue(ptrie.match(p5).equals(mappings.get(prefixes[2])));
+		//System.out.println(ptrie.match(p6).getNextHop().getHostAddress());
+		assertTrue(ptrie.match(p6).equals(mappings.get(prefixes[8])));
+		
+		
+		//TODO more extensive tests
+		//fail("Not yet implemented");
 	}
 
 	@Test
@@ -169,19 +184,19 @@
 	public void testIterator() {		
 		int[] order = new int[] {7, 5, 3, 8, 2, 1, 0, 4, 6};
 		
-		Iterator<IPatriciaTrie.Entry> it = ptrie.iterator();
+		Iterator<IPatriciaTrie.Entry<RibEntry>> it = ptrie.iterator();
 		int i = 0;
 		assertTrue(it.hasNext());
 		while (it.hasNext()) {
-			IPatriciaTrie.Entry entry = it.next();
+			IPatriciaTrie.Entry<RibEntry> entry = it.next();
 			assertTrue(entry.getPrefix().equals(prefixes[order[i]]));
 			i++;
 		}
 		assertFalse(it.hasNext());
 		assertTrue(i == order.length);
 		
-		IPatriciaTrie pt = new PatriciaTrie(32);
-		Iterator<IPatriciaTrie.Entry> it2 = pt.iterator();
+		IPatriciaTrie<RibEntry> pt = new PatriciaTrie<RibEntry>(32);
+		Iterator<IPatriciaTrie.Entry<RibEntry>> it2 = pt.iterator();
 		assertFalse(it2.hasNext());
 		it.next(); //throws NoSuchElementException
 	}
diff --git a/src/test/java/net/onrc/onos/ofcontroller/bgproute/PtreeTest.java b/src/test/java/net/onrc/onos/ofcontroller/bgproute/PtreeTest.java
index 6af9d30..1135407 100644
--- a/src/test/java/net/onrc/onos/ofcontroller/bgproute/PtreeTest.java
+++ b/src/test/java/net/onrc/onos/ofcontroller/bgproute/PtreeTest.java
@@ -25,14 +25,14 @@
 	private Logger log = LoggerFactory.getLogger(PtreeTest.class);
 	
 	private Ptree ptree;
-	private PatriciaTrie ooptrie;
+	private PatriciaTrie<RibEntry> ooptrie;
 	
 	private Map<String, byte[]> byteAddresses;
 
 	@Before
 	public void setUp() throws Exception {
 		ptree = new Ptree(32);
-		ooptrie = new PatriciaTrie(32);
+		ooptrie = new PatriciaTrie<RibEntry>(32);
 			
 		String[] strPrefixes = {
 			"192.168.10.0/24",
@@ -176,6 +176,7 @@
 		fail("Not yet implemented");
 	}
 	
+	@Ignore
 	@Test
 	public void testMisc() {
 		int bitIndex = -1;
@@ -197,10 +198,10 @@
 	
 	@Test
 	public void testIteration() {
-		Iterator<IPatriciaTrie.Entry> it = ooptrie.iterator();
+		Iterator<IPatriciaTrie.Entry<RibEntry>> it = ooptrie.iterator();
 		
 		while (it.hasNext()) {
-			IPatriciaTrie.Entry entry = it.next();
+			IPatriciaTrie.Entry<RibEntry> entry = it.next();
 			log.debug("PatriciaTrie prefix {} \t {}", entry.getPrefix(), entry.getPrefix().printAsBits());
 		}
 		
diff --git a/src/test/java/net/onrc/onos/ofcontroller/flowmanager/FlowManagerTest.java b/src/test/java/net/onrc/onos/ofcontroller/flowmanager/FlowManagerTest.java
index 7bc0aac..83a5fab 100644
--- a/src/test/java/net/onrc/onos/ofcontroller/flowmanager/FlowManagerTest.java
+++ b/src/test/java/net/onrc/onos/ofcontroller/flowmanager/FlowManagerTest.java
@@ -574,234 +574,8 @@
 		assertEquals(paramFlow.flowEntryMatch().toString(), resultFlow.flowEntryMatch().toString());
 	}
 		
-	/**
-	 * Test method for {@link FlowManager#measurementStorePathFlow(FlowPath)}.
-	 * @throws Exception 
-	 */
-	@Test
-	public final void testMeasurementStorePathFlowSuccessNormally() throws Exception {
-		// instantiate required objects
-		FlowPath paramFlow = createTestFlowPath(100, "installer id", 0, 1, 3, 2, 4);
-		Map<Long, Object> shortestPathMap = new HashMap<Long, Object>();
-		FlowManager fm = new FlowManager();
-
-		// setup expectations
-		expectInitWithContext();
-		expect((Map<Long,Object>)topoRouteService.prepareShortestPathTopo()
-				).andReturn(shortestPathMap);
-		expect(topoRouteService.getTopoShortestPath(
-				shortestPathMap,
-				paramFlow.dataPath().srcPort(),
-				paramFlow.dataPath().dstPort())).andReturn(null);
-		
-		// start the test
-		replayAll();
-		
-		fm.init(context);
-		FlowPath resultFlowPath = fm.measurementStorePathFlow(paramFlow);
-		
-		// verify the test
-		verifyAll();
-		assertEquals(paramFlow.flowId().value(), resultFlowPath.flowId().value());
-		assertEquals(paramFlow.installerId().toString(), resultFlowPath.installerId().toString());
-		assertEquals(paramFlow.flowPathFlags().flags(), resultFlowPath.flowPathFlags().flags());
-		assertEquals(paramFlow.dataPath().toString(), resultFlowPath.dataPath().toString());
-		assertEquals(paramFlow.flowEntryMatch().toString(), resultFlowPath.flowEntryMatch().toString());
-	}
-	
-	/**
-	 * Test method for {@link FlowManager#measurementInstallPaths(Integer)}.
-	 * @throws Exception 
-	 */
-	@Test
-	public final void testMeasurementInstallPathsSuccessNormally() throws Exception {
-		final String addFlow = "addFlow";
-
-		// create mock objects
-		FlowManager fm = createPartialMockAndInvokeDefaultConstructor(FlowManager.class, addFlow);
-
-		// instantiate required objects
-		FlowPath flow1 = createTestFlowPath(1, "installer id", 0, 1, 2, 3, 4);
-		FlowPath flow2 = createTestFlowPath(2, "installer id", 0, 2, 3, 4, 5);
-		FlowPath flow3 = createTestFlowPath(3, "installer id", 0, 3, 4, 5, 6);
-		Map<Long, Object> shortestPathMap = new HashMap<Long, Object>();
-
-		// setup expectations
-		expectInitWithContext();
-		expect((Map<Long,Object>)topoRouteService.prepareShortestPathTopo()
-				).andReturn(shortestPathMap);
-
-		expect(topoRouteService.getTopoShortestPath(
-				shortestPathMap,
-				flow1.dataPath().srcPort(),
-				flow1.dataPath().dstPort())).andReturn(null);
-		
-		expect(topoRouteService.getTopoShortestPath(
-				shortestPathMap,
-				flow2.dataPath().srcPort(),
-				flow2.dataPath().dstPort())).andReturn(null);
-
-		expect(topoRouteService.getTopoShortestPath(
-				shortestPathMap,
-				flow3.dataPath().srcPort(),
-				flow3.dataPath().dstPort())).andReturn(null);
-
-		expectPrivate(fm, addFlow,
-				EasyMock.cmpEq(flow1),
-				EasyMock.anyObject(FlowId.class),
-				EasyMock.anyObject(String.class)).andReturn(true);
-
-		expectPrivate(fm, addFlow,
-				EasyMock.cmpEq(flow2),
-				EasyMock.anyObject(FlowId.class),
-				EasyMock.anyObject(String.class)).andReturn(true);
-
-		expectPrivate(fm, addFlow,
-				EasyMock.cmpEq(flow3),
-				EasyMock.anyObject(FlowId.class),
-				EasyMock.anyObject(String.class)).andReturn(true);
-
-		// start the test
-		replayAll();
-
-		fm.init(context);
-		fm.measurementStorePathFlow(flow1);
-		fm.measurementStorePathFlow(flow2);
-		fm.measurementStorePathFlow(flow3);
-		Boolean result = fm.measurementInstallPaths(3);
-		
-		// verify the test
-		verifyAll();
-		assertTrue(result);
-	}
-	
-	/**
-	 * Test method for {@link FlowManager#measurementGetInstallPathsTimeNsec()}.
-	 * @throws Exception 
-	 */
-	@Test
-	public final void testMeasurementGetInstallPathsTimeNsecSuccessNormally() throws Exception {
-		final String addFlow = "addFlow";
-
-		// create mock objects
-		FlowManager fm = createPartialMockAndInvokeDefaultConstructor(FlowManager.class, addFlow);
-		mockStaticPartial(System.class, "nanoTime");
-
-		// instantiate required objects
-		FlowPath flow1 = createTestFlowPath(1, "installer id", 0, 1, 2, 3, 4);
-		Map<Long, Object> shortestPathMap = new HashMap<Long, Object>();
-
-		// setup expectations
-		expectInitWithContext();
-		expect(System.nanoTime()).andReturn(new Long(100000));
-		expect(System.nanoTime()).andReturn(new Long(110000));
-		expect((Map<Long,Object>)topoRouteService.prepareShortestPathTopo()
-				).andReturn(shortestPathMap);
-		expect(topoRouteService.getTopoShortestPath(
-				shortestPathMap,
-				flow1.dataPath().srcPort(),
-				flow1.dataPath().dstPort())).andReturn(null);
-		expectPrivate(fm, addFlow,
-				EasyMock.cmpEq(flow1),
-				EasyMock.anyObject(FlowId.class),
-				EasyMock.anyObject(String.class)).andReturn(true);
-		
-		// start the test
-		replayAll();
-
-		fm.init(context);
-		fm.measurementStorePathFlow(flow1).toString();
-		fm.measurementInstallPaths(1);
-		Long result = fm.measurementGetInstallPathsTimeNsec();
-		
-		// verify the test
-		verifyAll();
-		assertEquals(new Long(10000), result);
-	}
-
-	/**
-	 * Test method for {@link FlowManager#measurementGetPerFlowInstallTime()}.
-	 * @throws Exception 
-	 */
-	@Test
-	public final void testMeasurementGetPerFlowInstallTimeSuccessNormally() throws Exception {
-		final String addFlow = "addFlow";
-
-		// create mock objects
-		FlowManager fm = createPartialMockAndInvokeDefaultConstructor(FlowManager.class, addFlow);
-		
-		// instantiate required objects
-		FlowPath flow1 = createTestFlowPath(1, "installer id", 0, 1, 2, 3, 4);
-		Map<Long, Object> shortestPathMap = new HashMap<Long, Object>();
-
-		// setup expectations
-		expectInitWithContext();
-		expect((Map<Long,Object>)topoRouteService.prepareShortestPathTopo()
-				).andReturn(shortestPathMap);
-
-		expect(topoRouteService.getTopoShortestPath(
-				shortestPathMap,
-				flow1.dataPath().srcPort(),
-				flow1.dataPath().dstPort())).andReturn(null);
-
-		expectPrivate(fm, addFlow,
-				EasyMock.cmpEq(flow1),
-				EasyMock.anyObject(FlowId.class),
-				EasyMock.anyObject(String.class)).andReturn(true);
-
-
-		// start the test
-		replayAll();
-
-		fm.init(context);
-		fm.measurementStorePathFlow(flow1);
-		fm.measurementInstallPaths(10);
-		String result = fm.measurementGetPerFlowInstallTime();
-				
-		// verify the test
-		verifyAll();
-		assertTrue(result.startsWith("ThreadAndTimePerFlow"));
-	}
-
-	/**
-	 * Test method for {@link FlowManager#measurementClearAllPaths()}.
-	 * @throws Exception 
-	 */
-	@Test
-	public final void testMeasurementClearAllPathsSuccessNormally() throws Exception {
-		// instantiate required objects
-		FlowPath paramFlow = createTestFlowPath(100, "installer id", 0, 1, 3, 2, 4);
-		Map<Long, Object> shortestPathMap = new HashMap<Long, Object>();
-		FlowManager fm = new FlowManager();
-
-		// setup expectations
-		expectInitWithContext();
-		expect((Map<Long,Object>)topoRouteService.prepareShortestPathTopo()
-				).andReturn(shortestPathMap);
-		expect(topoRouteService.getTopoShortestPath(
-				shortestPathMap,
-				paramFlow.dataPath().srcPort(),
-				paramFlow.dataPath().dstPort())).andReturn(null);
-		topoRouteService.dropShortestPathTopo(shortestPathMap);
-		
-		// start the test
-		replayAll();
-		
-		fm.init(context);
-		fm.measurementStorePathFlow(paramFlow);
-		Boolean result = fm.measurementClearAllPaths();
-				
-		// verify the test
-		verifyAll();
-		assertTrue(result);
-		assertEquals(new Long(0), fm.measurementGetInstallPathsTimeNsec());
-		assertEquals("", fm.measurementGetPerFlowInstallTime());
-	}
-	
-		
 	// INetMapStorage methods
 	
-	
 	/**
 	 * Test method for {@link FlowManager#init(String)}.
 	 * @throws Exception 
diff --git a/web/measurement_clear_all_paths.py b/web/measurement_clear_all_paths.py
deleted file mode 100755
index 5bb73c5..0000000
--- a/web/measurement_clear_all_paths.py
+++ /dev/null
@@ -1,61 +0,0 @@
-#! /usr/bin/env python
-# -*- Mode: python; py-indent-offset: 4; tab-width: 8; indent-tabs-mode: t; -*-
-
-import pprint
-import os
-import sys
-import subprocess
-import json
-import argparse
-import io
-import time
-
-from flask import Flask, json, Response, render_template, make_response, request
-
-#
-# TODO: remove this! We don't use JSON argument here!
-# curl http://127.0.0.1:8080/wm/flow/delete/{"value":"0xf"}/json'
-#
-
-## Global Var ##
-ControllerIP="127.0.0.1"
-ControllerPort=8080
-
-DEBUG=0
-pp = pprint.PrettyPrinter(indent=4)
-
-app = Flask(__name__)
-
-## Worker Functions ##
-def log_error(txt):
-  print '%s' % (txt)
-
-def debug(txt):
-  if DEBUG:
-    print '%s' % (txt)
-
-# @app.route("/wm/flow/measurement-clear-all-paths/json")
-def measurement_clear_all_paths():
-  command = "curl -s \"http://%s:%s/wm/flow/measurement-clear-all-paths/json\"" % (ControllerIP, ControllerPort)
-  debug("measurement_clear_all_paths %s" % command)
-  result = os.popen(command).read()
-  debug("result %s" % result)
-  # parsedResult = json.loads(result)
-  # debug("parsed %s" % parsedResult)
-
-if __name__ == "__main__":
-  usage_msg = "Clear the paths that have been stored for measurement purpose\n"
-  usage_msg = usage_msg + "Usage: %s\n" % (sys.argv[0])
-  usage_msg = usage_msg + "\n"
-
-  # app.debug = False;
-
-  # Usage info
-  if len(sys.argv) > 1 and (sys.argv[1] == "-h" or sys.argv[1] == "--help"):
-    print(usage_msg)
-    exit(0)
-
-  # Check arguments
-
-  # Do the work
-  measurement_clear_all_paths()
diff --git a/web/measurement_get_install_paths_time_nsec.py b/web/measurement_get_install_paths_time_nsec.py
deleted file mode 100755
index d64dc49..0000000
--- a/web/measurement_get_install_paths_time_nsec.py
+++ /dev/null
@@ -1,61 +0,0 @@
-#! /usr/bin/env python
-# -*- Mode: python; py-indent-offset: 4; tab-width: 8; indent-tabs-mode: t; -*-
-
-import pprint
-import os
-import sys
-import subprocess
-import json
-import argparse
-import io
-import time
-
-from flask import Flask, json, Response, render_template, make_response, request
-
-#
-# TODO: remove this! We don't use JSON argument here!
-# curl http://127.0.0.1:8080/wm/flow/delete/{"value":"0xf"}/json'
-#
-
-## Global Var ##
-ControllerIP="127.0.0.1"
-ControllerPort=8080
-
-DEBUG=0
-pp = pprint.PrettyPrinter(indent=4)
-
-app = Flask(__name__)
-
-## Worker Functions ##
-def log_error(txt):
-  print '%s' % (txt)
-
-def debug(txt):
-  if DEBUG:
-    print '%s' % (txt)
-
-# @app.route("/wm/flow/measurement-get-install-paths-time-nsec/json")
-def measurement_get_install_paths_time_nsec():
-  command = "curl -s \"http://%s:%s/wm/flow/measurement-get-install-paths-time-nsec/json\"" % (ControllerIP, ControllerPort)
-  debug("measurement_get_install_paths_time_nsec %s" % command)
-  result = os.popen(command).read()
-  print '%s nsec' % (result)
-  # parsedResult = json.loads(result)
-  # debug("parsed %s" % parsedResult)
-
-if __name__ == "__main__":
-  usage_msg = "Get the measured time to install the stored flow paths\n"
-  usage_msg = usage_msg + "Usage: %s\n" % (sys.argv[0])
-  usage_msg = usage_msg + "\n"
-
-  # app.debug = False;
-
-  # Usage info
-  if len(sys.argv) > 1 and (sys.argv[1] == "-h" or sys.argv[1] == "--help"):
-    print(usage_msg)
-    exit(0)
-
-  # Check arguments
-
-  # Do the work
-  measurement_get_install_paths_time_nsec()
diff --git a/web/measurement_get_per_flow_install_time.py b/web/measurement_get_per_flow_install_time.py
deleted file mode 100755
index bf2bcc7..0000000
--- a/web/measurement_get_per_flow_install_time.py
+++ /dev/null
@@ -1,61 +0,0 @@
-#! /usr/bin/env python
-# -*- Mode: python; py-indent-offset: 4; tab-width: 8; indent-tabs-mode: t; -*-
-
-import pprint
-import os
-import sys
-import subprocess
-import json
-import argparse
-import io
-import time
-
-from flask import Flask, json, Response, render_template, make_response, request
-
-#
-# TODO: remove this! We don't use JSON argument here!
-# curl http://127.0.0.1:8080/wm/flow/delete/{"value":"0xf"}/json'
-#
-
-## Global Var ##
-ControllerIP="127.0.0.1"
-ControllerPort=8080
-
-DEBUG=0
-pp = pprint.PrettyPrinter(indent=4)
-
-app = Flask(__name__)
-
-## Worker Functions ##
-def log_error(txt):
-  print '%s' % (txt)
-
-def debug(txt):
-  if DEBUG:
-    print '%s' % (txt)
-
-# @app.route("/wm/flow/measurement-get-per-flow-install-time/json")
-def measurement_get_per_flow_install_time():
-  command = "curl -s \"http://%s:%s/wm/flow/measurement-get-per-flow-install-time/json\"" % (ControllerIP, ControllerPort)
-  debug("measurement_get_per_flow_install_time %s" % command)
-  result = os.popen(command).read()
-  print '%s' % (result)
-  # parsedResult = json.loads(result)
-  # debug("parsed %s" % parsedResult)
-
-if __name__ == "__main__":
-  usage_msg = "Get the measured time per flow to install each stored flow path\n"
-  usage_msg = usage_msg + "Usage: %s\n" % (sys.argv[0])
-  usage_msg = usage_msg + "\n"
-
-  # app.debug = False;
-
-  # Usage info
-  if len(sys.argv) > 1 and (sys.argv[1] == "-h" or sys.argv[1] == "--help"):
-    print(usage_msg)
-    exit(0)
-
-  # Check arguments
-
-  # Do the work
-  measurement_get_per_flow_install_time()
diff --git a/web/measurement_install_paths.py b/web/measurement_install_paths.py
deleted file mode 100755
index d99070e..0000000
--- a/web/measurement_install_paths.py
+++ /dev/null
@@ -1,67 +0,0 @@
-#! /usr/bin/env python
-# -*- Mode: python; py-indent-offset: 4; tab-width: 8; indent-tabs-mode: t; -*-
-
-import pprint
-import os
-import sys
-import subprocess
-import json
-import argparse
-import io
-import time
-
-from flask import Flask, json, Response, render_template, make_response, request
-
-#
-# TODO: remove this! We don't use JSON argument here!
-# curl http://127.0.0.1:8080/wm/flow/delete/{"value":"0xf"}/json'
-#
-
-## Global Var ##
-ControllerIP="127.0.0.1"
-ControllerPort=8080
-
-DEBUG=0
-pp = pprint.PrettyPrinter(indent=4)
-
-app = Flask(__name__)
-
-## Worker Functions ##
-def log_error(txt):
-  print '%s' % (txt)
-
-def debug(txt):
-  if DEBUG:
-    print '%s' % (txt)
-
-# @app.route("/wm/flow/measurement-install-paths/<num-threads>/json")
-def measurement_install_paths(num_threads):
-  command = "curl -s \"http://%s:%s/wm/flow/measurement-install-paths/%s/json\"" % (ControllerIP, ControllerPort, num_threads)
-  debug("measurement_install_paths %s" % command)
-  result = os.popen(command).read()
-  debug("result %s" % result)
-  # parsedResult = json.loads(result)
-  # debug("parsed %s" % parsedResult)
-
-if __name__ == "__main__":
-  usage_msg = "Install flow paths and start measurements\n"
-  usage_msg = usage_msg + "Usage: %s <num-threads>\n" % (sys.argv[0])
-  usage_msg = usage_msg + "\n"
-  usage_msg = usage_msg + "    Arguments:\n"
-  usage_msg = usage_msg + "        <num-threads>      Number of threads to use to install the flows\n"
-
-  # app.debug = False;
-
-  # Usage info
-  if len(sys.argv) > 1 and (sys.argv[1] == "-h" or sys.argv[1] == "--help"):
-    print(usage_msg)
-    exit(0)
-
-  # Check arguments
-  if len(sys.argv) < 2:
-    log_error(usage_msg)
-    exit(1)
-  num_threads = int(sys.argv[1], 0)
-
-  # Do the work
-  measurement_install_paths(num_threads)
diff --git a/web/measurement_process.py b/web/measurement_process.py
deleted file mode 100755
index 3187299..0000000
--- a/web/measurement_process.py
+++ /dev/null
@@ -1,59 +0,0 @@
-#! /usr/bin/env python
-# -*- Mode: python; py-indent-offset: 4; tab-width: 8; indent-tabs-mode: t; -*-
-
-import functools
-import math
-import sys
-
-## {{{ http://code.activestate.com/recipes/511478/ (r1)
-
-def percentile(N, percent, key=lambda x:x):
-    """
-    Find the percentile of a list of values.
-
-    @parameter N - is a list of values. Note N MUST BE already sorted.
-    @parameter percent - a float value from 0.0 to 1.0.
-    @parameter key - optional key function to compute value from each element of N.
-
-    @return - the percentile of the values
-    """
-    if not N:
-        return None
-    k = (len(N)-1) * percent
-    f = math.floor(k)
-    c = math.ceil(k)
-    if f == c:
-        return key(N[int(k)])
-    d0 = key(N[int(f)]) * (c-k)
-    d1 = key(N[int(c)]) * (k-f)
-    return d0+d1
-
-# median is 50th percentile.
-# median = functools.partial(percentile, percent=0.5)
-## end of http://code.activestate.com/recipes/511478/ }}}
-
-if __name__ == "__main__":
-
-    dict = {}
-
-    #
-    # Read the data from the stdin, and store it in a dictionary.
-    # The dictionary uses lists as values.
-    #
-    data = sys.stdin.readlines()
-    for line in data:
-	words = line.split()
-	thread_n = int(words[0])
-	msec = float(words[1])
-	dict.setdefault(thread_n, []).append(msec)
-
-    #
-    # Compute and print the values: median (50-th), 10-th, and 90-th
-    # percentile:
-    # <key> <median> <10-percentile> <90-percentile>
-    #
-    for key, val_list in sorted(dict.items()):
-	val_10 = percentile(sorted(val_list), 0.1)
-	val_50 = percentile(sorted(val_list), 0.5)
-	val_90 = percentile(sorted(val_list), 0.9)
-	print "%s %s %s %s" % (str(key), str(val_50), str(val_10), str(val_90))
diff --git a/web/measurement_run.py b/web/measurement_run.py
deleted file mode 100755
index 80d0517..0000000
--- a/web/measurement_run.py
+++ /dev/null
@@ -1,104 +0,0 @@
-#! /usr/bin/env python
-# -*- Mode: python; py-indent-offset: 4; tab-width: 8; indent-tabs-mode: t; -*-
-
-import os
-import string
-import subprocess
-import time
-
-# flow_n = 252
-# threads_n = [1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 100]
-# iterations_n = 10
-
-flow_n = 1
-threads_n = [1]
-iterations_n = 10
-# iterations_n = 100
-
-# flow_n = 42
-# flow_n = 420
-# flow_n = 1008
-
-def run_command(cmd):
-    """
-    - Run an external command, and return a tuple: stdout as the
-    first argument, and stderr as the second argument.
-    - Returns None if error.
-    """
-    try:
-	pr = subprocess.Popen(cmd, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
-	ret_tuple = pr.communicate();
-	if pr.returncode:
-	    print "%s failed with error code: %s" % (cmd, str(pr.returncode))
-	return ret_tuple
-    except OSError:
-	print "OS Error running %s" % cmd
-
-def run_install_paths(flowdef_filename):
-    # Prepare the flows to measure
-    cmd = "web/measurement_store_flow.py -f " + flowdef_filename
-    os.system(cmd)
-
-def run_measurement(thread_n):
-    # Install the Flow Paths
-    cmd = ["web/measurement_install_paths.py", str(thread_n)]
-    run_command(cmd)
-
-    # Get the measurement data and print it
-    cmd = "web/measurement_get_install_paths_time_nsec.py"
-    r = run_command(cmd)		# Tuple: [<stdout>, <stderr>]
-    res = r[0].split()			# Tuple: [<num>, nsec]
-    nsec_str = res[0]
-    msec = float(nsec_str) / (1000 * 1000)
-
-    # Get the measurement data and print it
-    cmd = "web/measurement_get_per_flow_install_time.py"
-    r = run_command(cmd)		# Tuple: [<stdout>, <stderr>]
-    res = r[0]
-    print res
-
-    # Keep checking until all Flow Paths are installed
-    while True:
-	# time.sleep(3)
-	cmd = ["web/get_flow.py", "all"]
-	r = run_command(cmd)
-	if string.count(r[0], "FlowPath") != flow_n:
-	    continue
-	if string.find(r[0], "NOT") == -1:
-	    break
-
-    # Remove the installed Flow Paths
-    cmd = ["web/delete_flow.py", "all"]
-    run_command(cmd)
-
-    # Keep checking until all Flows are removed
-    while True:
-	# time.sleep(3)
-	cmd = ["web/get_flow.py", "all"]
-	r = run_command(cmd)
-	if r[0] == "":
-	    break
-
-    return msec
-
-
-if __name__ == "__main__":
-
-    # Initial cleanup
-    cmd = "web/measurement_clear_all_paths.py"
-    run_command(cmd)
-
-    # Install the Flow Paths to measure
-    flowdef_filename = "web/flowdef_8node_" + str(flow_n) + ".txt"
-    run_install_paths(flowdef_filename)
-
-    # Do the work
-    for thread_n in threads_n:
-	for n in range(iterations_n):
-	    msec = run_measurement(thread_n)
-	    # Format: <number of threads> <time in ms>
-	    print "%d %f" % (thread_n, msec / flow_n)
-
-    # Cleanup on exit
-    cmd = "web/measurement_clear_all_paths.py"
-    run_command(cmd)
diff --git a/web/measurement_store_flow.py b/web/measurement_store_flow.py
deleted file mode 100755
index 0e39465..0000000
--- a/web/measurement_store_flow.py
+++ /dev/null
@@ -1,471 +0,0 @@
-#! /usr/bin/env python
-# -*- Mode: python; py-indent-offset: 4; tab-width: 8; indent-tabs-mode: t; -*-
-
-import copy
-import pprint
-import os
-import sys
-import subprocess
-import json
-import argparse
-import io
-import time
-
-from flask import Flask, json, Response, render_template, make_response, request
-
-## Global Var ##
-ControllerIP = "127.0.0.1"
-ControllerPort = 8080
-ReadFromFile = ""
-
-DEBUG=0
-pp = pprint.PrettyPrinter(indent=4)
-
-app = Flask(__name__)
-
-## Worker Functions ##
-def log_error(txt):
-  print '%s' % (txt)
-
-def debug(txt):
-  if DEBUG:
-    print '%s' % (txt)
-
-def measurement_store_path_flow(flow_path):
-  flow_path_json = json.dumps(flow_path)
-
-  try:
-    command = "curl -s -H 'Content-Type: application/json' -d '%s' http://%s:%s/wm/flow/measurement-store-path/json" % (flow_path_json, ControllerIP, ControllerPort)
-    debug("measurement_store_path_flow %s" % command)
-    result = os.popen(command).read()
-    debug("result %s" % result)
-    # parsedResult = json.loads(result)
-    # debug("parsed %s" % parsedResult)
-  except:
-    log_error("Controller IF has issue")
-    exit(1)
-
-def extract_flow_args(my_args):
-  # Check the arguments
-  if len(my_args) < 6:
-    log_error(usage_msg)
-    exit(1)
-
-  # Extract the mandatory arguments
-  my_flow_id = my_args[0]
-  my_installer_id = my_args[1]
-  my_src_dpid = my_args[2]
-  my_src_port = my_args[3]
-  my_dst_dpid = my_args[4]
-  my_dst_port = my_args[5]
-
-  #
-  # Extract the "flowPathFlags", "match" and "action" arguments
-  #
-  flowPathFlags = 0L
-  match = {}
-  matchInPortEnabled = True		# NOTE: Enabled by default
-  actions = []
-  actionOutputEnabled = True		# NOTE: Enabled by default
-  idx = 6
-  while idx < len(my_args):
-    action = {}
-    arg1 = my_args[idx]
-    idx = idx + 1
-    # Extract the second argument
-    if idx >= len(my_args):
-      error_arg = "ERROR: Missing or invalid '" + arg1 + "' argument"
-      log_error(error_arg)
-      log_error(usage_msg)
-      exit(1)
-    arg2 = my_args[idx]
-    idx = idx + 1
-
-    if arg1 == "flowPathFlags":
-      if "DISCARD_FIRST_HOP_ENTRY" in arg2:
-	flowPathFlags = flowPathFlags + 0x1
-      if "KEEP_ONLY_FIRST_HOP_ENTRY" in arg2:
-	flowPathFlags = flowPathFlags + 0x2
-    elif arg1 == "matchInPort":
-      # Mark whether ACTION_OUTPUT action is enabled
-      matchInPortEnabled = arg2 in ['True', 'true']
-      # inPort = {}
-      # inPort['value'] = int(arg2, 0)
-      # match['inPort'] = inPort
-      ## match['matchInPort'] = True
-    elif arg1 == "matchSrcMac":
-      srcMac = {}
-      srcMac['value'] = arg2
-      match['srcMac'] = srcMac
-      # match['matchSrcMac'] = True
-    elif arg1 == "matchDstMac":
-      dstMac = {}
-      dstMac['value'] = arg2
-      match['dstMac'] = dstMac
-      # match['matchDstMac'] = True
-    elif arg1 == "matchEthernetFrameType":
-      match['ethernetFrameType'] = int(arg2, 0)
-      # match['matchEthernetFrameType'] = True
-    elif arg1 == "matchVlanId":
-      match['vlanId'] = int(arg2, 0)
-      # match['matchVlanId'] = True
-    elif arg1 == "matchVlanPriority":
-      match['vlanPriority'] = int(arg2, 0)
-      # match['matchVlanPriority'] = True
-    elif arg1 == "matchSrcIPv4Net":
-      srcIPv4Net = {}
-      srcIPv4Net['value'] = arg2
-      match['srcIPv4Net'] = srcIPv4Net
-      # match['matchSrcIPv4Net'] = True
-    elif arg1 == "matchDstIPv4Net":
-      dstIPv4Net = {}
-      dstIPv4Net['value'] = arg2
-      match['dstIPv4Net'] = dstIPv4Net
-      # match['matchDstIPv4Net'] = True
-    elif arg1 == "matchIpProto":
-      match['ipProto'] = int(arg2, 0)
-      # match['matchIpProto'] = True
-    elif arg1 == "matchIpToS":
-      match['ipToS'] = int(arg2, 0)
-      # match['matchIpToS'] = True
-    elif arg1 == "matchSrcTcpUdpPort":
-      match['srcTcpUdpPort'] = int(arg2, 0)
-      # match['matchSrcTcpUdpPort'] = True
-    elif arg1 == "matchDstTcpUdpPort":
-      match['dstTcpUdpPort'] = int(arg2, 0)
-      # match['matchDstTcpUdpPort'] = True
-    elif arg1 == "actionOutput":
-      # Mark whether ACTION_OUTPUT action is enabled
-      actionOutputEnabled = arg2 in ['True', 'true']
-      # If ACTION_OUTPUT is explicitly enabled, add an entry with a fake
-      # port number. We need this entry to preserve the action ordering.
-      if actionOutputEnabled == True:
-        actionOutput = {}
-        outPort = {}
-        outPort['value'] = 0xffff
-        actionOutput['port'] = outPort
-        actionOutput['maxLen'] = 0
-        action['actionOutput'] = actionOutput
-        # action['actionType'] = 'ACTION_OUTPUT'
-        actions.append(action)
-      #
-    elif arg1 == "actionSetVlanId":
-      vlanId = {}
-      vlanId['vlanId'] = int(arg2, 0)
-      action['actionSetVlanId'] = vlanId
-      # action['actionType'] = 'ACTION_SET_VLAN_VID'
-      actions.append(copy.deepcopy(action))
-    elif arg1 == "actionSetVlanPriority":
-      vlanPriority = {}
-      vlanPriority['vlanPriority'] = int(arg2, 0)
-      action['actionSetVlanPriority'] = vlanPriority
-      # action['actionType'] = 'ACTION_SET_VLAN_PCP'
-      actions.append(copy.deepcopy(action))
-    elif arg1 == "actionStripVlan":
-      stripVlan = {}
-      stripVlan['stripVlan'] = arg2 in ['True', 'true']
-      action['actionStripVlan'] = stripVlan
-      # action['actionType'] = 'ACTION_STRIP_VLAN'
-      actions.append(copy.deepcopy(action))
-    elif arg1 == "actionSetEthernetSrcAddr":
-      ethernetSrcAddr = {}
-      ethernetSrcAddr['value'] = arg2
-      setEthernetSrcAddr = {}
-      setEthernetSrcAddr['addr'] = ethernetSrcAddr
-      action['actionSetEthernetSrcAddr'] = setEthernetSrcAddr
-      # action['actionType'] = 'ACTION_SET_DL_SRC'
-      actions.append(copy.deepcopy(action))
-    elif arg1 == "actionSetEthernetDstAddr":
-      ethernetDstAddr = {}
-      ethernetDstAddr['value'] = arg2
-      setEthernetDstAddr = {}
-      setEthernetDstAddr['addr'] = ethernetDstAddr
-      action['actionSetEthernetDstAddr'] = setEthernetDstAddr
-      # action['actionType'] = 'ACTION_SET_DL_DST'
-      actions.append(copy.deepcopy(action))
-    elif arg1 == "actionSetIPv4SrcAddr":
-      IPv4SrcAddr = {}
-      IPv4SrcAddr['value'] = arg2
-      setIPv4SrcAddr = {}
-      setIPv4SrcAddr['addr'] = IPv4SrcAddr
-      action['actionSetIPv4SrcAddr'] = setIPv4SrcAddr
-      # action['actionType'] = 'ACTION_SET_NW_SRC'
-      actions.append(copy.deepcopy(action))
-    elif arg1 == "actionSetIPv4DstAddr":
-      IPv4DstAddr = {}
-      IPv4DstAddr['value'] = arg2
-      setIPv4DstAddr = {}
-      setIPv4DstAddr['addr'] = IPv4DstAddr
-      action['actionSetIPv4DstAddr'] = setIPv4DstAddr
-      # action['actionType'] = 'ACTION_SET_NW_DST'
-      actions.append(copy.deepcopy(action))
-    elif arg1 == "actionSetIpToS":
-      ipToS = {}
-      ipToS['ipToS'] = int(arg2, 0)
-      action['actionSetIpToS'] = ipToS
-      # action['actionType'] = 'ACTION_SET_NW_TOS'
-      actions.append(copy.deepcopy(action))
-    elif arg1 == "actionSetTcpUdpSrcPort":
-      tcpUdpSrcPort = {}
-      tcpUdpSrcPort['port'] = int(arg2, 0)
-      action['actionSetTcpUdpSrcPort'] = tcpUdpSrcPort
-      # action['actionType'] = 'ACTION_SET_TP_SRC'
-      actions.append(copy.deepcopy(action))
-    elif arg1 == "actionSetTcpUdpDstPort":
-      tcpUdpDstPort = {}
-      tcpUdpDstPort['port'] = int(arg2, 0)
-      action['actionSetTcpUdpDstPort'] = tcpUdpDstPort
-      # action['actionType'] = 'ACTION_SET_TP_DST'
-      actions.append(copy.deepcopy(action))
-    elif arg1 == "actionEnqueue":
-      # TODO: Implement ACTION_ENQUEUE
-      actionEnqueue = {}
-      #   actionEnqueue['queueId'] = int(arg2, 0)
-      #   enqueuePort = {}
-      #   enqueuePort['value'] = int(arg3, 0)
-      #   actionEnqueue['port'] = enqueuePort
-      #   action['actionEnqueue'] = actionEnqueue
-      #   # action['actionType'] = 'ACTION_ENQUEUE'
-      #   actions.append(copy.deepcopy(action))
-      #
-    else:
-      log_error("ERROR: Unknown argument '%s'" % (arg1))
-      log_error(usage_msg)
-      exit(1)
-
-  return {
-    'my_flow_id' : my_flow_id,
-    'my_installer_id' : my_installer_id,
-    'my_src_dpid' : my_src_dpid,
-    'my_src_port' : my_src_port,
-    'my_dst_dpid' : my_dst_dpid,
-    'my_dst_port' : my_dst_port,
-    'flowPathFlags' : flowPathFlags,
-    'match' : match,
-    'matchInPortEnabled' : matchInPortEnabled,
-    'actions' : actions,
-    'actionOutputEnabled' : actionOutputEnabled
-    }
-
-def compute_flow_path(parsed_args, data_path):
-
-  my_flow_id = parsed_args['my_flow_id']
-  my_installer_id = parsed_args['my_installer_id']
-  myFlowPathFlags = parsed_args['flowPathFlags']
-  match = parsed_args['match']
-  matchInPortEnabled = parsed_args['matchInPortEnabled']
-  actions = parsed_args['actions']
-  actionOutputEnabled = parsed_args['actionOutputEnabled']
-  my_data_path = copy.deepcopy(data_path)
-
-  flow_id = {}
-  flow_id['value'] = my_flow_id
-  installer_id = {}
-  installer_id['value'] = my_installer_id
-  flowPathFlags = {}
-  flowPathFlags['flags'] = myFlowPathFlags
-
-  flowEntryActions = {}
-
-  flow_path = {}
-  flow_path['flowId'] = flow_id
-  flow_path['installerId'] = installer_id
-  flow_path['flowPathFlags'] = flowPathFlags
-
-  if (len(match) > 0):
-    flow_path['flowEntryMatch'] = copy.deepcopy(match)
-
-  #
-  # Add the match conditions to each flow entry
-  #
-  if (len(match) > 0) or matchInPortEnabled:
-    idx = 0
-    while idx < len(my_data_path['flowEntries']):
-      if matchInPortEnabled:
-	inPort = my_data_path['flowEntries'][idx]['inPort']
-	match['inPort'] = copy.deepcopy(inPort)
-	# match['matchInPort'] = True
-      my_data_path['flowEntries'][idx]['flowEntryMatch'] = copy.deepcopy(match)
-      idx = idx + 1
-
-
-  if (len(actions) > 0):
-    flowEntryActions['actions'] = copy.deepcopy(actions)
-    flow_path['flowEntryActions'] = flowEntryActions
-
-  #
-  # Set the actions for each flow entry
-  # NOTE: The actions from the command line are aplied
-  # ONLY to the first flow entry.
-  #
-  # If ACTION_OUTPUT action is enabled, then apply it
-  # to each flow entry.
-  #
-  if (len(actions) > 0) or actionOutputEnabled:
-    idx = 0
-    while idx < len(my_data_path['flowEntries']):
-      if idx > 0:
-	actions = []	# Reset the actions for all but first entry
-      action = {}
-      outPort = my_data_path['flowEntries'][idx]['outPort']
-      actionOutput = {}
-      actionOutput['port'] = copy.deepcopy(outPort)
-      # actionOutput['maxLen'] = 0	# TODO: not used for now
-      action['actionOutput'] = copy.deepcopy(actionOutput)
-      # action['actionType'] = 'ACTION_OUTPUT'
-      actions.append(copy.deepcopy(action))
-      flowEntryActions = {}
-      flowEntryActions['actions'] = copy.deepcopy(actions)
-
-      my_data_path['flowEntries'][idx]['flowEntryActions'] = flowEntryActions
-      idx = idx + 1
-
-  flow_path['dataPath'] = my_data_path
-  debug("Flow Path: %s" % flow_path)
-  return flow_path
-
-def measurement_store_paths(parsed_args):
-  idx = 0
-  while idx < len(parsed_args):
-    data_path = {}
-    src_dpid = {}
-    src_port = {}
-    dst_dpid = {}
-    dst_port = {}
-    src_switch_port = {}
-    dst_switch_port = {}
-    flow_entries = []
-
-    src_dpid['value'] = parsed_args[idx]['my_src_dpid']
-    src_port['value'] = parsed_args[idx]['my_src_port']
-    dst_dpid['value'] = parsed_args[idx]['my_dst_dpid']
-    dst_port['value'] = parsed_args[idx]['my_dst_port']
-    src_switch_port['dpid'] = src_dpid
-    src_switch_port['port'] = src_port
-    dst_switch_port['dpid'] = dst_dpid
-    dst_switch_port['port'] = dst_port
-
-    data_path['srcPort'] = copy.deepcopy(src_switch_port)
-    data_path['dstPort'] = copy.deepcopy(dst_switch_port)
-    data_path['flowEntries'] = copy.deepcopy(flow_entries)
-
-    #
-    # XXX: Explicitly disable the InPort matching, and
-    # the Output action, because they get in the way
-    # during the compute_flow_path() processing.
-    #
-    parsed_args[idx]['matchInPortEnabled'] = False
-    parsed_args[idx]['actionOutputEnabled'] = False
-
-    flow_path = compute_flow_path(parsed_args[idx], data_path)
-    measurement_store_path_flow(flow_path)
-
-    idx = idx + 1
-
-
-if __name__ == "__main__":
-  usage_msg = "Store Flow Paths into ONOS for measurement purpose.\n"
-  usage_msg = usage_msg + "\n"
-  usage_msg = usage_msg + "Usage: %s [Flags] <flow-id> <installer-id> <src-dpid> <src-port> <dest-dpid> <dest-port> [Flow Path Flags] [Match Conditions] [Actions]\n" % (sys.argv[0])
-  usage_msg = usage_msg + "\n"
-  usage_msg = usage_msg + "    Flags:\n"
-  usage_msg = usage_msg + "        -f <filename>     Read the flow(s) to install from a file\n"
-  usage_msg = usage_msg + "                          File format: one line per flow starting with <flow-id>\n"
-  usage_msg = usage_msg + "\n"
-  usage_msg = usage_msg + "    Flow Path Flags:\n"
-  usage_msg = usage_msg + "        flowPathFlags <Flags> (flag names separated by ',')\n"
-  usage_msg = usage_msg + "\n"
-  usage_msg = usage_msg + "        Known flags:\n"
-  usage_msg = usage_msg + "            DISCARD_FIRST_HOP_ENTRY    : Discard the first-hop flow entry\n"
-  usage_msg = usage_msg + "            KEEP_ONLY_FIRST_HOP_ENTRY  : Keep only the first-hop flow entry\n"
-  usage_msg = usage_msg + "\n"
-  usage_msg = usage_msg + "    Match Conditions:\n"
-  usage_msg = usage_msg + "        matchInPort <True|False> (default to True)\n"
-  usage_msg = usage_msg + "        matchSrcMac <source MAC address>\n"
-  usage_msg = usage_msg + "        matchDstMac <destination MAC address>\n"
-  usage_msg = usage_msg + "        matchEthernetFrameType <Ethernet frame type>\n"
-  usage_msg = usage_msg + "        matchVlanId <VLAN ID>\n"
-  usage_msg = usage_msg + "        matchVlanPriority <VLAN priority>\n"
-  usage_msg = usage_msg + "        matchSrcIPv4Net <source IPv4 network address>\n"
-  usage_msg = usage_msg + "        matchDstIPv4Net <destination IPv4 network address>\n"
-  usage_msg = usage_msg + "        matchIpProto <IP protocol>\n"
-  usage_msg = usage_msg + "        matchIpToS <IP ToS (DSCP field, 6 bits)>\n"
-  usage_msg = usage_msg + "        matchSrcTcpUdpPort <source TCP/UDP port>\n"
-  usage_msg = usage_msg + "        matchDstTcpUdpPort <destination TCP/UDP port>\n"
-  usage_msg = usage_msg + "\n"
-  usage_msg = usage_msg + "    Actions:\n"
-  usage_msg = usage_msg + "        actionOutput <True|False> (default to True)\n"
-  usage_msg = usage_msg + "        actionSetVlanId <VLAN ID>\n"
-  usage_msg = usage_msg + "        actionSetVlanPriority <VLAN priority>\n"
-  usage_msg = usage_msg + "        actionStripVlan <True|False>\n"
-  usage_msg = usage_msg + "        actionSetEthernetSrcAddr <source MAC address>\n"
-  usage_msg = usage_msg + "        actionSetEthernetDstAddr <destination MAC address>\n"
-  usage_msg = usage_msg + "        actionSetIPv4SrcAddr <source IPv4 address>\n"
-  usage_msg = usage_msg + "        actionSetIPv4DstAddr <destination IPv4 address>\n"
-  usage_msg = usage_msg + "        actionSetIpToS <IP ToS (DSCP field, 6 bits)>\n"
-  usage_msg = usage_msg + "        actionSetTcpUdpSrcPort <source TCP/UDP port>\n"
-  usage_msg = usage_msg + "        actionSetTcpUdpDstPort <destination TCP/UDP port>\n"
-  usage_msg = usage_msg + "    Actions (not implemented yet):\n"
-  usage_msg = usage_msg + "        actionEnqueue <dummy argument>\n"
-
-  # app.debug = False;
-
-  # Usage info
-  if len(sys.argv) > 1 and (sys.argv[1] == "-h" or sys.argv[1] == "--help"):
-    print(usage_msg)
-    exit(0)
-
-  #
-  # Check the flags
-  #
-  start_argv_index = 1
-  idx = 1
-  while idx < len(sys.argv):
-    arg1 = sys.argv[idx]
-    idx = idx + 1
-    if arg1 == "-f":
-      if idx >= len(sys.argv):
-	error_arg = "ERROR: Missing or invalid '" + arg1 + "' argument"
-	log_error(error_arg)
-	log_error(usage_msg)
-	exit(1)
-      ReadFromFile = sys.argv[idx]
-      idx = idx + 1
-      start_argv_index = idx
-    else:
-      break;
-
-  #
-  # Read the arguments from a file or from the remaining command line options
-  #
-  my_lines = []
-  if len(ReadFromFile) > 0:
-    f = open(ReadFromFile, "rt")
-    my_line = f.readline()
-    while my_line:
-      if len(my_line.rstrip()) > 0 and my_line[0] != "#":
-	my_token_line = my_line.rstrip().split()
-	my_lines.append(my_token_line)
-      my_line = f.readline()
-  else:
-    my_lines.append(copy.deepcopy(sys.argv[start_argv_index:]))
-
-  #
-  # Initialization
-  #
-  last_data_paths = []
-  parsed_args = []
-  idx = 0
-  while idx < len(my_lines):
-    last_data_path = []
-    last_data_paths.append(copy.deepcopy(last_data_path))
-    #
-    # Parse the flow arguments
-    #
-    my_args = my_lines[idx]
-    parsed_args.append(copy.deepcopy(extract_flow_args(my_args)))
-
-    idx = idx + 1
-
-  #
-  measurement_store_paths(parsed_args)