Merge pull request #5 from OPENNETWORKINGLAB/master

Merge with Master changes
diff --git a/pom.xml b/pom.xml
index 44b7f07..4899f34 100644
--- a/pom.xml
+++ b/pom.xml
@@ -80,6 +80,8 @@
             <exclude>**/*TestCase.java</exclude>
           -->
           </excludes>
+          <argLine>-XX:MaxPermSize=512m</argLine>
+          <reuseFork>false</reuseFork>
         </configuration>
       </plugin>
       <!-- exec:java -->
@@ -184,6 +186,11 @@
           <locale>en</locale>
         </configuration>
       </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>cobertura-maven-plugin</artifactId>
+        <version>2.5.2</version>
+      </plugin>
     </plugins>
   </reporting>
   <dependencies>
diff --git a/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpPeer.java b/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpPeer.java
index 7425a07..e98c3e8 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpPeer.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpPeer.java
@@ -2,8 +2,6 @@
 
 import java.net.InetAddress;
 
-import net.floodlightcontroller.util.MACAddress;
-
 import org.codehaus.jackson.annotate.JsonProperty;
 
 import com.google.common.net.InetAddresses;
@@ -11,7 +9,6 @@
 public class BgpPeer {
 	private String interfaceName;
 	private InetAddress ipAddress;
-	private MACAddress macAddress;
 	
 	public String getInterfaceName() {
 		return interfaceName;
@@ -30,13 +27,4 @@
 	public void setIpAddress(String ipAddress) {
 		this.ipAddress = InetAddresses.forString(ipAddress);
 	}
-	
-	public MACAddress getMacAddress() {
-		return macAddress;
-	}
-	
-	@JsonProperty("macAddress")
-	public void setMacAddress(String macAddress) {
-		this.macAddress = MACAddress.valueOf(macAddress);
-	}
 }
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 acdf185..9936cb7 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRoute.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/BgpRoute.java
@@ -10,6 +10,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -32,11 +33,14 @@
 import net.floodlightcontroller.routing.Link;
 import net.floodlightcontroller.topology.ITopologyListener;
 import net.floodlightcontroller.topology.ITopologyService;
+import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.ofcontroller.bgproute.RibUpdate.Operation;
 import net.onrc.onos.ofcontroller.core.INetMapTopologyService.ITopoLinkService;
 import net.onrc.onos.ofcontroller.core.INetMapTopologyService.ITopoRouteService;
 import net.onrc.onos.ofcontroller.core.internal.TopoLinkServiceImpl;
 import net.onrc.onos.ofcontroller.linkdiscovery.ILinkDiscovery;
 import net.onrc.onos.ofcontroller.linkdiscovery.ILinkDiscovery.LDUpdate;
+import net.onrc.onos.ofcontroller.proxyarp.IArpRequester;
 import net.onrc.onos.ofcontroller.proxyarp.ProxyArpManager;
 import net.onrc.onos.ofcontroller.routing.TopoRouteService;
 import net.onrc.onos.ofcontroller.util.DataPath;
@@ -64,10 +68,15 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
 import com.google.common.util.concurrent.ThreadFactoryBuilder;
 
 public class BgpRoute implements IFloodlightModule, IBgpRouteService, 
-									ITopologyListener, IOFSwitchListener {
+									ITopologyListener, IOFSwitchListener,
+									IArpRequester {
 	
 	protected static Logger log = LoggerFactory.getLogger(BgpRoute.class);
 
@@ -117,6 +126,29 @@
 	protected ArrayList<LDUpdate> linkUpdates;
 	protected SingletonTask topologyChangeDetectorTask;
 	
+	protected SetMultimap<InetAddress, RibUpdate> prefixesWaitingOnArp;
+	protected SetMultimap<InetAddress, PathUpdate> pathsWaitingOnArp;
+	
+	protected Multimap<Prefix, PushedFlowMod> pushedFlows;
+	
+	private class PushedFlowMod {
+		private long dpid;
+		private OFFlowMod flowMod;
+		
+		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() {
@@ -126,9 +158,6 @@
 				ITopoLinkService topoLinkService = new TopoLinkServiceImpl();
 				
 				List<Link> activeLinks = topoLinkService.getActiveLinks();
-				//for (Link l : activeLinks){
-					//log.debug("active link: {}", l);
-				//}
 				
 				Iterator<LDUpdate> it = linkUpdates.iterator();
 				while (it.hasNext()){
@@ -241,6 +270,13 @@
 
 		topoRouteService = new TopoRouteService("");
 		
+		pathsWaitingOnArp = Multimaps.synchronizedSetMultimap(
+				HashMultimap.<InetAddress, PathUpdate>create());
+		prefixesWaitingOnArp = Multimaps.synchronizedSetMultimap(
+				HashMultimap.<InetAddress, RibUpdate>create());
+		
+		pushedFlows = HashMultimap.<Prefix, PushedFlowMod>create();
+		
 		//Read in config values
 		bgpdRestIp = context.getConfigParams(this).get("BgpdRestIp");
 		if (bgpdRestIp == null){
@@ -368,6 +404,7 @@
 
 	}
 	
+	//TODO once the Ptree is object oriented this can go
 	private String getPrefixFromPtree(PtreeNode node){
         InetAddress address = null;
         try {
@@ -428,7 +465,7 @@
 			
 			node.rib = rib;
 			
-			prefixAdded(node);
+			addPrefixFlows(p, rib);
 		} 
 	}
 	
@@ -437,23 +474,29 @@
 		ribUpdates.add(update);
 	}
 	
-	//TODO temporary
-	public void wrapPrefixAdded(RibUpdate update) {
+	public void processRibAdd(RibUpdate update) {
 		Prefix prefix = update.getPrefix();
 		
 		PtreeNode node = ptree.acquire(prefix.getAddress(), prefix.getPrefixLength());
 		
 		if (node.rib != null) {
+			//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);
 		}
+		
+		//Put the new nexthop in the Ptree
 		node.rib = update.getRibEntry();
 
-		prefixAdded(node);
+		//Push flows for the new <prefix, nexthop>
+		addPrefixFlows(prefix, update.getRibEntry());
 	}
 	
-	//TODO temporary
-	public void wrapPrefixDeleted(RibUpdate update) {
+	public void processRibDelete(RibUpdate update) {
 		Prefix prefix = update.getPrefix();
 		
 		PtreeNode node = ptree.lookup(prefix.getAddress(), prefix.getPrefixLength());
@@ -466,36 +509,49 @@
 		 * 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){
-			prefixDeleted(node);
-		}
 
 		if (node != null && node.rib != null) {
 			if (update.getRibEntry().equals(node.rib)) {
 				node.rib = null;
-				ptree.delReference(node);					
+				ptree.delReference(node);
+				
+				deletePrefixFlows(update.getPrefix());
 			}
 		}
 	}
 	
-	@Override
+	//TODO compatibility layer, used by beginRouting()
 	public void prefixAdded(PtreeNode node) {
+		Prefix prefix = null;
+		try {
+			prefix = new Prefix(node.key, node.rib.masklen);
+		} catch (UnknownHostException e) {
+			log.error(" ", e);
+		}
+
+		addPrefixFlows(prefix, node.rib);
+	}
+
+	private void addPrefixFlows(Prefix prefix, Rib rib) {
 		if (!topologyReady){
 			return;
 		}
 		
-		String prefix = getPrefixFromPtree(node);
-		
+		//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 {}, routerId {}", 
-				new Object[] {prefix, node.rib.nextHop.toString(), 
-				node.rib.routerId.getHostAddress()});
+				new Object[] {prefix, rib.nextHop.getHostAddress(),
+				rib.routerId.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(node.rib.nextHop);
+		BgpPeer peer = bgpPeers.get(rib.nextHop);
 		
 		if (peer == null){
 			//TODO local router isn't in peers list so this will get thrown
@@ -503,11 +559,22 @@
 			
 			//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"
-					, node.rib.nextHop.toString());
+			log.error("Couldn't find next hop router in router {} in config",
+					rib.nextHop.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
@@ -528,9 +595,6 @@
 				return; // just quit here?
 			}
 			
-			//TODO check the shortest path against the cached version we
-			//calculated before. If they don't match up that's a problem
-			
 			//Set up the flow mod
 			OFFlowMod fm =
 	                (OFFlowMod) floodlightProvider.getOFMessageFactory()
@@ -550,27 +614,23 @@
 	        match.setDataLayerType(Ethernet.TYPE_IPv4);
 	        match.setWildcards(match.getWildcards() & ~OFMatch.OFPFW_DL_TYPE);
 	        
-	        //match.setDataLayerSource(ingressRouter.getRouterMac().toBytes());
-	        //match.setDataLayerSource(peer.getMacAddress().toBytes());
-	        //match.setWildcards(match.getWildcards() & ~OFMatch.OFPFW_DL_SRC);
-
 	        InetAddress address = null;
 	        try {
-				address = InetAddress.getByAddress(node.key);
+	        	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() + "/" + node.rib.masklen, OFMatch.STR_NW_DST);
+	        match.setFromCIDR(address.getHostAddress() + "/" + 
+	        		prefix.getPrefixLength(), OFMatch.STR_NW_DST);
 	        fm.setMatch(match);
 	        
 	        //Set up MAC rewrite action
 	        OFActionDataLayerDestination macRewriteAction = new OFActionDataLayerDestination();
-	        //TODO use ARP module rather than configured mac addresses
 	        //TODO the peer's mac address is not necessarily the next hop's...
-	        macRewriteAction.setDataLayerAddress(peer.getMacAddress().toBytes());
+	        macRewriteAction.setDataLayerAddress(peerMacAddress);
 	        
 	        //Set up output action
 	        OFActionOutput outputAction = new OFActionOutput();
@@ -593,6 +653,9 @@
             	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>();
             msglist.add(fm);
             try {
@@ -604,81 +667,47 @@
 		}
 	}
 	
-	//TODO this is largely untested
-	@Override
-	public void prefixDeleted(PtreeNode node) {
+	//TODO test next-hop changes
+	//TODO check delete/add synchronization
+		
+	private void deletePrefixFlows(Prefix prefix) {
 		if (!topologyReady) {
 			return;
 		}
 		
-		String prefix = getPrefixFromPtree(node);
+		log.debug("In deletePrefixFlows for {}", prefix);
 		
-		log.debug("Prefix {} deleted, next hop {}", 
-				prefix, node.rib.nextHop.toString());
+		/*for (Map.Entry<Prefix, PushedFlowMod> entry : pushedFlows.entries()) {
+			log.debug("Pushed flow: {} => {}", entry.getKey(), entry.getValue());
+		}*/
 		
-		//Remove MAC rewriting flows from other border switches
-		BgpPeer peer = bgpPeers.get(node.rib.nextHop);
-		if (peer == null){
-			//either a router server route or local route. Can't handle right now
-			return;
-		}
+		Collection<PushedFlowMod> pushedFlowMods 
+				= pushedFlows.removeAll(prefix);
 		
-		Interface peerInterface = interfaces.get(peer.getInterfaceName());
-		
-		for (Interface srcInterface : interfaces.values()) {
-			if (srcInterface == peerInterface) {
-				continue;
-			}
+		for (PushedFlowMod pfm : pushedFlowMods) {
+			log.debug("Pushing a DELETE flow mod to {}, matches prefix {} with mac-rewrite {}",
+					new Object[] {HexString.toHexString(pfm.getDpid()),
+					pfm.getFlowMod().getMatch().getNetworkDestination() + 
+					pfm.getFlowMod().getMatch().getNetworkDestinationMaskLen(),
+					HexString.toHexString(((OFActionDataLayerDestination)pfm.getFlowMod().getActions().get(0))
+							.getDataLayerAddress())});
 			
-			//Set up the flow mod
-			OFFlowMod fm =
-	                (OFFlowMod) floodlightProvider.getOFMessageFactory()
-	                                              .getMessage(OFType.FLOW_MOD);
+			OFFlowMod fm = pfm.getFlowMod();
 			
-	        fm.setIdleTimeout((short)0)
-	        .setHardTimeout((short)0)
-	        .setBufferId(OFPacketOut.BUFFER_ID_NONE)
-	        .setCookie(MAC_RW_COOKIE)
-	        .setCommand(OFFlowMod.OFPFC_DELETE)
-	        .setOutPort(OFPort.OFPP_NONE)
-	        .setPriority(SDNIP_PRIORITY)
-	        .setLengthU(OFFlowMod.MINIMUM_LENGTH);
-	        		//+ OFActionDataLayerDestination.MINIMUM_LENGTH
-	        		//+ OFActionOutput.MINIMUM_LENGTH);
-	        
-	        OFMatch match = new OFMatch();
-	        match.setDataLayerType(Ethernet.TYPE_IPv4);
-	        match.setWildcards(match.getWildcards() & ~OFMatch.OFPFW_DL_TYPE);
-	        
-	        //match.setDataLayerSource(ingressRouter.getRouterMac().toBytes());
-	        //match.setDataLayerSource(peer.getMacAddress().toBytes());
-	        //match.setWildcards(match.getWildcards() & ~OFMatch.OFPFW_DL_SRC);
-	        
-	        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;
-			}
-	        
-	        match.setFromCIDR(address.getHostAddress() + "/" + node.rib.masklen, OFMatch.STR_NW_DST);
-	        fm.setMatch(match);
-	        
-	        //Write to switch
-	        IOFSwitch sw = floodlightProvider.getSwitches()
-	        		.get(srcInterface.getDpid());
-	        
-            if (sw == null){
-            	log.warn("Switch not found when pushing flow mod");
+			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;
-            }
-            
-            List<OFMessage> msglist = new ArrayList<OFMessage>();
-            msglist.add(fm);
-            try {
-				sw.write(msglist, null);
+			}
+			
+			try {
+				sw.write(fm, null);
 				sw.flush();
 			} catch (IOException e) {
 				log.error("Failure writing flow mod", e);
@@ -699,30 +728,45 @@
 		
 		for (BgpPeer peer : bgpPeers.values()) {
 			Interface peerInterface = interfaces.get(peer.getInterfaceName());
-			//for (Map.Entry<String, Interface> intfEntry : interfaces.entrySet()) {
-			for (Interface srcInterface : interfaces.values()) {
-				//Interface srcInterface = intfEntry.getValue();
-				//if (peer.getInterfaceName().equals(intfEntry.getKey())){
-				if (peer.getInterfaceName().equals(srcInterface.getName())){
-					continue;
-				}
+			
+			//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()));
 				
-				DataPath shortestPath = topoRouteService.getShortestPath(
-							srcInterface.getSwitchPort(), peerInterface.getSwitchPort()); 
-				
-				if (shortestPath == null){
-					log.debug("Shortest path between {} and {} not found",
-							srcInterface.getSwitchPort(), peerInterface.getSwitchPort());
-					return; // just quit here?
-				}
-				
-				//install flows
-				installPath(shortestPath.flowEntries(), peer);
+				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));
 		}
 	}
 	
-	private void installPath(List<FlowEntry> flowEntries, BgpPeer peer){
+	private void calculateAndPushPath(Interface dstInterface, MACAddress dstMacAddress) {
+		for (Interface srcInterface : interfaces.values()) {
+			if (dstInterface.equals(srcInterface.getName())){
+				continue;
+			}
+			
+			DataPath shortestPath = topoRouteService.getShortestPath(
+						srcInterface.getSwitchPort(), dstInterface.getSwitchPort()); 
+			
+			if (shortestPath == null){
+				log.debug("Shortest path between {} and {} not found",
+						srcInterface.getSwitchPort(), dstInterface.getSwitchPort());
+				return; // just quit here?
+			}
+			
+			installPath(shortestPath.flowEntries(), dstMacAddress);
+		}
+	}
+	
+	private void installPath(List<FlowEntry> flowEntries, MACAddress dstMacAddress){
 		//Set up the flow mod
 		OFFlowMod fm =
                 (OFFlowMod) floodlightProvider.getOFMessageFactory()
@@ -748,7 +792,7 @@
            
             OFMatch match = new OFMatch();
             //TODO Again using MAC address from configuration
-            match.setDataLayerDestination(peer.getMacAddress().toBytes());
+            match.setDataLayerDestination(dstMacAddress.toBytes());
             match.setWildcards(match.getWildcards() & ~OFMatch.OFPFW_DL_DST);
             ((OFActionOutput) fm.getActions().get(0)).setPort(flowEntry.outPort().value());
             
@@ -904,6 +948,32 @@
 		}
 	}
 	
+	@Override
+	public void arpResponse(InetAddress ipAddress, byte[] macAddress) {
+		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);
+		
+		for (RibUpdate update : prefixesToPush) {
+			//These will always be adds
+			log.debug("Pushing prefix {} next hop {}", update.getPrefix(), 
+					update.getRibEntry().nextHop.getHostAddress());
+			addPrefixFlows(update.getPrefix(), update.getRibEntry());
+		}
+	}
+	
 	private void beginRouting(){
 		log.debug("Topology is now ready, beginning routing function");
 		setupBgpPaths();
@@ -995,10 +1065,10 @@
 					RibUpdate update = ribUpdates.take();
 					switch (update.getOperation()){
 					case UPDATE:
-						wrapPrefixAdded(update);
+						processRibAdd(update);
 						break;
 					case DELETE:
-						wrapPrefixDeleted(update);
+						processRibDelete(update);
 						break;
 					}
 				} catch (InterruptedException e) {
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 c3c8cbb..4b623e4 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/bgproute/Configuration.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/Configuration.java
@@ -1,5 +1,6 @@
 package net.onrc.onos.ofcontroller.bgproute;
 
+import java.util.Collections;
 import java.util.List;
 
 import org.codehaus.jackson.annotate.JsonProperty;
@@ -11,7 +12,6 @@
 	private List<String> switches;
 	private List<Interface> interfaces;
 	private List<BgpPeer> peers;
-	//private Map<String, GatewayRouter> gateways;
 	
 	public Configuration() {
 		// TODO Auto-generated constructor stub
@@ -36,7 +36,7 @@
 	}
 
 	public List<String> getSwitches() {
-		return switches;
+		return Collections.unmodifiableList(switches);
 	}
 
 	@JsonProperty("switches")
@@ -45,7 +45,7 @@
 	}
 
 	public List<Interface> getInterfaces() {
-		return interfaces;
+		return Collections.unmodifiableList(interfaces);
 	}
 
 	@JsonProperty("interfaces")
@@ -54,7 +54,7 @@
 	}
 	
 	public List<BgpPeer> getPeers() {
-		return peers;
+		return Collections.unmodifiableList(peers);
 	}
 
 	@JsonProperty("bgpPeers")
@@ -62,14 +62,4 @@
 		this.peers = peers;
 	}
 
-	/*
-	public Map<String, GatewayRouter> getGateways() {
-		return gateways;
-	}
-
-	@JsonProperty("gateways")
-	public void setGateways(Map<String, GatewayRouter> gateways) {
-		this.gateways = gateways;
-	}*/
-
 }
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 3dbc940..d865e6e 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/bgproute/IBgpRouteService.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/IBgpRouteService.java
@@ -21,6 +21,6 @@
 	public void newRibUpdate(RibUpdate update);
 	
 	//TODO This functionality should be provided by some sort of Ptree listener framework
-	public void prefixAdded(PtreeNode node);
-	public void prefixDeleted(PtreeNode node);
+	//public void prefixAdded(PtreeNode node);
+	//public void prefixDeleted(PtreeNode node);
 }
diff --git a/src/main/java/net/onrc/onos/ofcontroller/bgproute/PathUpdate.java b/src/main/java/net/onrc/onos/ofcontroller/bgproute/PathUpdate.java
new file mode 100644
index 0000000..1d2a47b
--- /dev/null
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/PathUpdate.java
@@ -0,0 +1,27 @@
+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/Prefix.java b/src/main/java/net/onrc/onos/ofcontroller/bgproute/Prefix.java
index 4d7c53a..54775df 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/bgproute/Prefix.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/bgproute/Prefix.java
@@ -37,6 +37,26 @@
 	}
 	
 	@Override
+	public boolean equals(Object other) {
+		if (other == null || !(other instanceof Prefix)) {
+			return false;
+		}
+		
+		Prefix otherPrefix = (Prefix) other;
+		
+		return (address.equals(otherPrefix.address)) && 
+				(prefixLength == otherPrefix.prefixLength);
+	}
+	
+	@Override
+	public int hashCode() {
+		int hash = 17;
+		hash = 31 * hash + prefixLength;
+		hash = 31 * hash + (address == null ? 0 : address.hashCode());
+		return hash;
+	}
+	
+	@Override
 	public String toString() {
 		return address.getHostAddress() + "/" + prefixLength;
 	}
diff --git a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/HostArpRequester.java b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/HostArpRequester.java
index 20c6a28..1474d02 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/HostArpRequester.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/HostArpRequester.java
@@ -1,5 +1,7 @@
 package net.onrc.onos.ofcontroller.proxyarp;
 
+import java.net.InetAddress;
+
 import net.floodlightcontroller.packet.ARP;
 
 public class HostArpRequester implements IArpRequester {
@@ -8,7 +10,6 @@
 	private ARP arpRequest;
 	private long dpid;
 	private short port;
-	//private long requestTime; //in ms
 	
 	public HostArpRequester(IProxyArpService arpService, ARP arpRequest, 
 			long dpid, short port) {
@@ -17,12 +18,11 @@
 		this.arpRequest = arpRequest;
 		this.dpid = dpid;
 		this.port = port;
-		//this.requestTime = System.currentTimeMillis();
 	}
 
 	@Override
-	public void arpResponse(byte[] mac) {
-		arpService.sendArpReply(arpRequest, dpid, port, mac);
+	public void arpResponse(InetAddress ipAddress, byte[] macAddress) {
+		arpService.sendArpReply(arpRequest, dpid, port, macAddress);
 	}
 
 }
diff --git a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IArpRequester.java b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IArpRequester.java
index 2a74944..90da2ba 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IArpRequester.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IArpRequester.java
@@ -1,5 +1,7 @@
 package net.onrc.onos.ofcontroller.proxyarp;
 
+import java.net.InetAddress;
+
 public interface IArpRequester {
-	public void arpResponse(byte[] mac);
+	public void arpResponse(InetAddress ipAddress, byte[] macAddress);
 }
diff --git a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IProxyArpService.java b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IProxyArpService.java
index 4632aba..2bb32f4 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IProxyArpService.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/IProxyArpService.java
@@ -29,13 +29,11 @@
 	/**
 	 * Tell the IProxyArpService to send an ARP request for the IP address.
 	 * The request will be broadcast out all edge ports in the network.
-	 * As an optimization, the IProxyArpService will first check its cache and
-	 * return the MAC address if it is already known. If not, the request will be
-	 * sent and the callback will be called when the MAC address is known
-	 * (or if the request times out). 
 	 * @param ipAddress
 	 * @param requester
+	 * @param retry Whether to keep sending requests until the MAC is learnt
 	 * @return
 	 */
-	public byte[] sendArpRequest(InetAddress ipAddress, IArpRequester requester);
+	public void sendArpRequest(InetAddress ipAddress, IArpRequester requester,
+			boolean retry);
 }
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 5c2a2b9..f56934d 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ProxyArpManager.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/proxyarp/ProxyArpManager.java
@@ -6,14 +6,12 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.Timer;
 import java.util.TimerTask;
-import java.util.concurrent.ConcurrentHashMap;
 
 import net.floodlightcontroller.core.FloodlightContext;
 import net.floodlightcontroller.core.IFloodlightProviderService;
@@ -36,45 +34,55 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+
 public class ProxyArpManager implements IProxyArpService, IOFMessageListener {
 	private static Logger log = LoggerFactory.getLogger(ProxyArpManager.class);
 	
 	private final long ARP_ENTRY_TIMEOUT = 600000; //ms (== 10 mins)
 	
-	private final long ARP_REQUEST_TIMEOUT_THREAD_PERIOD = 60000; //ms (== 1 min) 
+	private final long ARP_TIMER_PERIOD = 60000; //ms (== 1 min) 
 			
 	protected IFloodlightProviderService floodlightProvider;
 	protected ITopologyService topology;
 	protected IDeviceService devices;
 	
 	protected Map<InetAddress, ArpTableEntry> arpTable;
-	
-	//protected ConcurrentHashMap<InetAddress, Set<ArpRequest>> arpRequests;
-	protected ConcurrentHashMap<InetAddress, ArpRequest> arpRequests;
+
+	protected SetMultimap<InetAddress, ArpRequest> arpRequests;
 	
 	private class ArpRequest {
-		private Set<IArpRequester> requesters;
+		private IArpRequester requester;
+		private boolean retry;
 		private long requestTime;
 		
-		public ArpRequest(){
-			this.requesters = new HashSet<IArpRequester>();
+		public ArpRequest(IArpRequester requester, boolean retry){
+			this.requester = requester;
+			this.retry = retry;
 			this.requestTime = System.currentTimeMillis();
 		}
 		
-		public synchronized void addRequester(IArpRequester requester){
-			requestTime = System.currentTimeMillis();
-			requesters.add(requester);
+		public ArpRequest(ArpRequest old) {
+			this.requester = old.requester;
+			this.retry = old.retry;
+			this.requestTime = System.currentTimeMillis();
 		}
 		
-		public boolean isExpired(){
+		public boolean isExpired() {
 			return (System.currentTimeMillis() - requestTime) 
 					> IProxyArpService.ARP_REQUEST_TIMEOUT;
 		}
 		
-		public synchronized void dispatchReply(byte[] replyMacAddress){
-			for (IArpRequester requester : requesters){
-				requester.arpResponse(replyMacAddress);
-			}
+		public boolean shouldRetry() {
+			return retry;
+		}
+		
+		public synchronized void dispatchReply(InetAddress ipAddress, byte[] replyMacAddress) {
+			log.debug("Dispatching reply for {} to {}", ipAddress.getHostAddress(), 
+					requester);
+			requester.arpResponse(ipAddress, replyMacAddress);
 		}
 	}
 	
@@ -85,43 +93,67 @@
 		this.devices = devices;
 		
 		arpTable = new HashMap<InetAddress, ArpTableEntry>();
-		//arpRequests = new ConcurrentHashMap<InetAddress, Set<ArpRequest>>();
-		arpRequests = new ConcurrentHashMap<InetAddress, ArpRequest>();
+
+		arpRequests = Multimaps.synchronizedSetMultimap(
+				HashMultimap.<InetAddress, ArpRequest>create());
 		
-		Timer arpRequestTimeoutTimer = new Timer();
-		arpRequestTimeoutTimer.scheduleAtFixedRate(new TimerTask() {
+		Timer arpTimer = new Timer();
+		arpTimer.scheduleAtFixedRate(new TimerTask() {
 			@Override
 			public void run() {
-				synchronized (arpRequests) {
-					log.debug("Current have {} outstanding requests", 
-							arpRequests.size());
+				doPeriodicArpProcessing();
+			}
+		}, 0, ARP_TIMER_PERIOD);
+	}
+	
+	/*
+	 * Function that runs periodically to manage the asynchronous request mechanism.
+	 * It basically cleans up old ARP requests if we don't get a response for them.
+	 * The caller can designate that a request should be retried indefinitely, and
+	 * this task will handle that as well.
+	 */
+	private void doPeriodicArpProcessing() {
+		SetMultimap<InetAddress, ArpRequest> retryList 
+				= HashMultimap.<InetAddress, ArpRequest>create();
+
+		//Have to synchronize externally on the Multimap while using an iterator,
+		//even though it's a synchronizedMultimap
+		synchronized (arpRequests) {
+			log.debug("Current have {} outstanding requests", 
+					arpRequests.size());
+			
+			Iterator<Map.Entry<InetAddress, ArpRequest>> it 
+				= arpRequests.entries().iterator();
+			
+			while (it.hasNext()) {
+				Map.Entry<InetAddress, ArpRequest> entry
+						= it.next();
+				ArpRequest request = entry.getValue();
+				if (request.isExpired()) {
+					log.debug("Cleaning expired ARP request for {}", 
+							entry.getKey().getHostAddress());
+		
+					it.remove();
 					
-					Iterator<Map.Entry<InetAddress, ArpRequest>> it 
-							= arpRequests.entrySet().iterator();
-					
-					while (it.hasNext()){
-						Map.Entry<InetAddress, ArpRequest> entry
-								= it.next();
-						
-						if (entry.getValue().isExpired()){
-							log.debug("Cleaning expired ARP request for {}", 
-									entry.getKey().getHostAddress());
-							it.remove();
-						}
+					if (request.shouldRetry()) {
+						retryList.put(entry.getKey(), request);
 					}
 				}
 			}
-		}, 0, ARP_REQUEST_TIMEOUT_THREAD_PERIOD);
-	}
-	
-	private void storeRequester(InetAddress address, IArpRequester requester) {
-		synchronized (arpRequests) {
-			if (arpRequests.get(address) == null) {
-				arpRequests.put(address, new ArpRequest());
+		}
+		
+		for (Map.Entry<InetAddress, Collection<ArpRequest>> entry 
+				: retryList.asMap().entrySet()) {
+			
+			InetAddress address = entry.getKey();
+			
+			log.debug("Resending ARP request for {}", address.getHostAddress());
+			
+			sendArpRequestForAddress(address);
+			
+			for (ArpRequest request : entry.getValue()) {
+				arpRequests.put(address, new ArpRequest(request));
 			}
-			ArpRequest request = arpRequests.get(address);
-							
-			request.addRequester(requester);
 		}
 	}
 	
@@ -190,23 +222,32 @@
 				return;
 			}
 			
-			storeRequester(target, new HostArpRequester(this, arp, sw.getId(), 
-					pi.getInPort()));
-			
+			boolean shouldBroadcastRequest = false;
+			synchronized (arpRequests) {
+				if (!arpRequests.containsKey(target)) {
+					shouldBroadcastRequest = true;
+				}
+				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());
+			if (shouldBroadcastRequest) {
+				broadcastArpRequestOutEdge(pi.getPacketData(), sw.getId(), pi.getInPort());
+			}
 		}
 		else {
 			//We know the address, so send a reply
 			log.debug("Sending reply of {}", MACAddress.valueOf(mac).toString());
-			//sendArpReply(arp, pi, mac, sw);
 			sendArpReply(arp, sw.getId(), pi.getInPort(), mac);
 		}
 	}
 	
 	protected void handleArpReply(IOFSwitch sw, OFPacketIn pi, ARP arp){
-		log.debug("ARP reply recieved for {}", 
-				bytesToStringAddr(arp.getSenderProtocolAddress()));
+		log.debug("ARP reply recieved for {}, is {}, on {}/{}", new Object[] { 
+				bytesToStringAddr(arp.getSenderProtocolAddress()),
+				HexString.toHexString(arp.getSenderHardwareAddress()),
+				HexString.toHexString(sw.getId()), pi.getInPort()});
 		
 		updateArpTable(arp);
 		
@@ -218,30 +259,17 @@
 			return;
 		}
 		
-		ArpRequest request = null;
-		synchronized (arpRequests) {
-			request = arpRequests.get(addr);
-			if (request != null) {
-				arpRequests.remove(addr);
-			}
-		}
-		if (request != null && !request.isExpired()) {
-			request.dispatchReply(arp.getSenderHardwareAddress());
-		}
-		
-		/*
 		Set<ArpRequest> requests = arpRequests.get(addr);
-		if (requests != null){
-			
-			synchronized (requests) {
-				for (ArpRequest request : requests) {
-					if (!request.isExpired()){
-						request.getRequester().arpResponse(
-								arp.getSenderHardwareAddress());
-					}
-				}
+		
+		//Synchronize on the Multimap while using an iterator for one of the sets
+		synchronized (arpRequests) {
+			Iterator<ArpRequest> it = requests.iterator();
+			while (it.hasNext()) {
+				ArpRequest request = it.next();
+				it.remove();
+				request.dispatchReply(addr, arp.getSenderHardwareAddress());
 			}
-		}*/
+		}
 	}
 
 	private synchronized byte[] lookupArpTable(byte[] ipAddress){
@@ -256,12 +284,14 @@
 		ArpTableEntry arpEntry = arpTable.get(addr);
 		
 		if (arpEntry == null){
+			log.debug("MAC for {} unknown", bytesToStringAddr(ipAddress));
 			return null;
 		}
 		
 		if (System.currentTimeMillis() - arpEntry.getTimeLastSeen() 
 				> ARP_ENTRY_TIMEOUT){
 			//Entry has timed out so we'll remove it and return null
+			log.debug("Timing out old ARP entry for {}", bytesToStringAddr(ipAddress));
 			arpTable.remove(addr);
 			return null;
 		}
@@ -292,9 +322,11 @@
 	}
 	
 	private void sendArpRequestForAddress(InetAddress ipAddress) {
+		//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[] broadcastMac = {(byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, 
+		byte[] bgpdMac = {0x0, 0x0, 0x0, 0x0, 0x0, 0x01};
+		byte[] broadcastMac = {(byte)0xff, (byte)0xff, (byte)0xff, 
 				(byte)0xff, (byte)0xff, (byte)0xff};
 		
 		ARP arpRequest = new ARP();
@@ -304,21 +336,21 @@
 			.setHardwareAddressLength((byte)Ethernet.DATALAYER_ADDRESS_LENGTH)
 			.setProtocolAddressLength((byte)4) //can't find the constant anywhere
 			.setOpCode(ARP.OP_REQUEST)
-			.setSenderHardwareAddress(zeroMac)
+			.setSenderHardwareAddress(bgpdMac)
 			.setSenderProtocolAddress(zeroIpv4)
 			.setTargetHardwareAddress(zeroMac)
 			.setTargetProtocolAddress(ipAddress.getAddress());
 	
 		Ethernet eth = new Ethernet();
-		eth.setDestinationMACAddress(arpRequest.getSenderHardwareAddress())
-			.setSourceMACAddress(broadcastMac)
+		//eth.setDestinationMACAddress(arpRequest.getSenderHardwareAddress())
+		eth.setSourceMACAddress(bgpdMac)
+			.setDestinationMACAddress(broadcastMac)
 			.setEtherType(Ethernet.TYPE_ARP)
 			.setPayload(arpRequest);
 		
 		broadcastArpRequestOutEdge(eth.serialize(), 0, OFPort.OFPP_NONE.getValue());
 	}
 	
-	//private void broadcastArpRequestOutEdge(OFPacketIn pi, long inSwitch, short inPort){
 	private void broadcastArpRequestOutEdge(byte[] arpRequest, long inSwitch, short inPort) {
 		for (IOFSwitch sw : floodlightProvider.getSwitches().values()){
 			Collection<Short> enabledPorts = sw.getEnabledPortNumbers();
@@ -346,7 +378,7 @@
 				}
 				
 				actions.add(new OFActionOutput(portNum));
-				log.debug("Broadcasting out {}/{}", HexString.toHexString(sw.getId()), portNum);
+				//log.debug("Broadcasting out {}/{}", HexString.toHexString(sw.getId()), portNum);
 			}
 			
 			po.setActions(actions);
@@ -368,14 +400,12 @@
 	}
 	
 	public void sendArpReply(ARP arpRequest, long dpid, short port, byte[] targetMac) {
-	//private void sendArpReply(ARP arpRequest, OFPacketIn pi, byte[] macRequested, IOFSwitch sw){
 		ARP arpReply = new ARP();
 		arpReply.setHardwareType(ARP.HW_TYPE_ETHERNET)
 			.setProtocolType(ARP.PROTO_TYPE_IP)
 			.setHardwareAddressLength((byte)Ethernet.DATALAYER_ADDRESS_LENGTH)
 			.setProtocolAddressLength((byte)4) //can't find the constant anywhere
 			.setOpCode(ARP.OP_REPLY)
-			//.setSenderHardwareAddress(macRequested)
 			.setSenderHardwareAddress(targetMac)
 			.setSenderProtocolAddress(arpRequest.getTargetProtocolAddress())
 			.setTargetHardwareAddress(arpRequest.getSenderHardwareAddress())
@@ -383,13 +413,11 @@
 		
 		Ethernet eth = new Ethernet();
 		eth.setDestinationMACAddress(arpRequest.getSenderHardwareAddress())
-			//.setSourceMACAddress(macRequested)
 			.setSourceMACAddress(targetMac)
 			.setEtherType(Ethernet.TYPE_ARP)
 			.setPayload(arpReply);
 		
 		List<OFAction> actions = new ArrayList<OFAction>();
-		//actions.add(new OFActionOutput(pi.getInPort()));
 		actions.add(new OFActionOutput(port));
 		
 		OFPacketOut po = new OFPacketOut();
@@ -433,21 +461,17 @@
 		else return addr.getHostAddress();
 	}
 	
-	
+	@Override
 	public byte[] getMacAddress(InetAddress ipAddress) {
 		return lookupArpTable(ipAddress.getAddress());
 	}
-	
-	public byte[] sendArpRequest(InetAddress ipAddress, IArpRequester requester) {
-		byte[] lookupMac;
-		if ((lookupMac = lookupArpTable(ipAddress.getAddress())) == null) {
-			return lookupMac;
-		}
+
+	@Override
+	public void sendArpRequest(InetAddress ipAddress, IArpRequester requester,
+			boolean retry) {
+		arpRequests.put(ipAddress, new ArpRequest(requester, retry));
+		//storeRequester(ipAddress, requester, retry);
 		
 		sendArpRequestForAddress(ipAddress);
-		
-		storeRequester(ipAddress, requester);
-		
-		return null;
 	}
 }
diff --git a/src/main/java/net/onrc/onos/ofcontroller/util/FlowEntry.java b/src/main/java/net/onrc/onos/ofcontroller/util/FlowEntry.java
index 049e783..762d272 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/util/FlowEntry.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/util/FlowEntry.java
@@ -1,6 +1,5 @@
 package net.onrc.onos.ofcontroller.util;
 
-import java.util.ArrayList;
 
 import org.codehaus.jackson.annotate.JsonIgnore;
 import org.codehaus.jackson.annotate.JsonProperty;
@@ -326,17 +325,32 @@
      */
     @Override
     public String toString() {
-	String ret = "[flowEntryId=" + this.flowEntryId.toString();
-	ret += " flowEntryMatch=" + this.flowEntryMatch.toString();
-	ret += " flowEntryActions=" + this.flowEntryActions.toString();
-	ret += " dpid=" + this.dpid.toString();
-	ret += " inPort=" + this.inPort.toString();
-	ret += " outPort=" + this.outPort.toString();
-	ret += " flowEntryUserState=" + this.flowEntryUserState;
-	ret += " flowEntrySwitchState=" + this.flowEntrySwitchState;
-	ret += " flowEntryErrorState=" + this.flowEntryErrorState.toString();
-	ret += "]";
+	StringBuilder ret = new StringBuilder();
+	if ( flowEntryId != null ) {
+		ret.append("[flowEntryId=" + this.flowEntryId.toString());
+	} else {
+		ret.append("[");
+	}
+	if ( flowEntryMatch != null ) {
+		ret.append(" flowEntryMatch=" + this.flowEntryMatch.toString());
+	}
+	ret.append( " flowEntryActions=" + this.flowEntryActions.toString() );
+	if ( dpid != null ) {
+		ret.append(" dpid=" + this.dpid.toString());
+	}
+	if ( inPort != null ) {
+		ret.append(" inPort=" + this.inPort.toString());
+	}
+	if ( outPort != null ) {
+		ret.append(" outPort=" + this.outPort.toString());
+	}
+	ret.append(" flowEntryUserState=" + this.flowEntryUserState);
+	ret.append(" flowEntrySwitchState=" + this.flowEntrySwitchState);
+	if ( flowEntryErrorState != null ) {
+		ret.append(" flowEntryErrorState=" + this.flowEntryErrorState.toString());
+	}
+	ret.append("]");
 
-	return ret;
+	return ret.toString();
     }
 }
diff --git a/src/main/java/net/onrc/onos/ofcontroller/util/FlowPath.java b/src/main/java/net/onrc/onos/ofcontroller/util/FlowPath.java
index 153e184..c1b2f9f 100644
--- a/src/main/java/net/onrc/onos/ofcontroller/util/FlowPath.java
+++ b/src/main/java/net/onrc/onos/ofcontroller/util/FlowPath.java
@@ -168,7 +168,7 @@
 	    {
 		FlowEntryActions actions = new FlowEntryActions();
 
-		String actionsStr = flowObj.getActions();
+		String actionsStr = flowEntryObj.getActions();
 		if (actions != null)
 		    actions = new FlowEntryActions(actionsStr);
 
diff --git a/src/test/java/net/onrc/onos/ofcontroller/util/FlowEntryActionTest.java b/src/test/java/net/onrc/onos/ofcontroller/util/FlowEntryActionTest.java
new file mode 100644
index 0000000..4db734c
--- /dev/null
+++ b/src/test/java/net/onrc/onos/ofcontroller/util/FlowEntryActionTest.java
@@ -0,0 +1,427 @@
+package net.onrc.onos.ofcontroller.util;
+
+import static org.junit.Assert.assertEquals;
+import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.ofcontroller.util.FlowEntryAction.ActionEnqueue;
+import net.onrc.onos.ofcontroller.util.FlowEntryAction.ActionOutput;
+import net.onrc.onos.ofcontroller.util.FlowEntryAction.ActionSetEthernetAddr;
+import net.onrc.onos.ofcontroller.util.FlowEntryAction.ActionSetIPv4Addr;
+import net.onrc.onos.ofcontroller.util.FlowEntryAction.ActionSetIpToS;
+import net.onrc.onos.ofcontroller.util.FlowEntryAction.ActionSetTcpUdpPort;
+import net.onrc.onos.ofcontroller.util.FlowEntryAction.ActionSetVlanId;
+import net.onrc.onos.ofcontroller.util.FlowEntryAction.ActionSetVlanPriority;
+import net.onrc.onos.ofcontroller.util.FlowEntryAction.ActionStripVlan;
+
+import org.junit.Test;
+
+public class FlowEntryActionTest {
+
+	@Test
+	public void testSetActionOutputActionOutput(){
+		FlowEntryAction act = new FlowEntryAction();
+		ActionOutput actout = act.new ActionOutput(new Port((short)42));
+		act.setActionOutput(actout);
+
+		assertEquals("action output",FlowEntryAction.ActionValues.ACTION_OUTPUT , act.actionType());
+		assertEquals("actionOutput port should be the same", actout.port(), act.actionOutput().port());
+		assertEquals("actionOutput maxlen should be the same", actout.maxLen(), act.actionOutput().maxLen());
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionOutputPort(){
+		FlowEntryAction act = new FlowEntryAction();
+		act.setActionOutput(new Port((short)42));
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionOutputToController(){
+		FlowEntryAction act = new FlowEntryAction();
+		act.setActionOutputToController((short)0);
+
+		FlowEntryAction act_copy = new FlowEntryAction();
+		act_copy.setActionOutput(new Port(Port.PortValues.PORT_CONTROLLER));
+		;
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionSetVlanIdActionSetVlanId(){
+		FlowEntryAction act = new FlowEntryAction();
+		ActionSetVlanId actVlan = act.new ActionSetVlanId((short)42);
+		act.setActionSetVlanId(actVlan);
+
+		assertEquals("action type",FlowEntryAction.ActionValues.ACTION_SET_VLAN_VID , act.actionType());
+		assertEquals("vlanid should be the same", actVlan.vlanId(), act.actionSetVlanId().vlanId());
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionSetVlanIdShort(){
+		FlowEntryAction act = new FlowEntryAction();
+		act.setActionSetVlanId((short)42);
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionSetVlanPriorityActionSetVlanPriority(){
+		FlowEntryAction act = new FlowEntryAction();
+		ActionSetVlanPriority actVlan = act.new ActionSetVlanPriority((byte)42);
+		act.setActionSetVlanPriority(actVlan);
+
+		assertEquals("action type",FlowEntryAction.ActionValues.ACTION_SET_VLAN_PCP , act.actionType());
+		assertEquals("vlan priority should be the same", actVlan.vlanPriority(), act.actionSetVlanPriority().vlanPriority());
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionSetVlanPriorityByte(){
+		FlowEntryAction act = new FlowEntryAction();
+		act.setActionSetVlanPriority((byte)42);
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionStripVlanActionStripVlan(){
+		FlowEntryAction act = new FlowEntryAction();
+		ActionStripVlan actVlan = act.new ActionStripVlan();
+		act.setActionStripVlan(actVlan);
+
+		assertEquals("action type",FlowEntryAction.ActionValues.ACTION_STRIP_VLAN , act.actionType());
+		assertEquals("vlanid should be the same", actVlan.stripVlan(), act.actionStripVlan().stripVlan());
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionStripVlanBoolean(){
+		FlowEntryAction act = new FlowEntryAction();
+		act.setActionStripVlan(true);
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionSetEthernetSrcAddrActionSetEthernetAddr(){
+		FlowEntryAction act = new FlowEntryAction();
+		byte[] mac = { 1, 2, 3, 4, 5, 6 };
+		ActionSetEthernetAddr setEth = act.new ActionSetEthernetAddr(new MACAddress(mac));
+		act.setActionSetEthernetSrcAddr( setEth );
+
+		assertEquals("action type",FlowEntryAction.ActionValues.ACTION_SET_DL_SRC , act.actionType());
+		assertEquals("addr should be the same", setEth.addr(), act.actionSetEthernetSrcAddr().addr());
+
+		
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionSetEthernetSrcAddrMACAddress(){
+		FlowEntryAction act = new FlowEntryAction();
+		byte[] mac = { 1, 2, 3, 4, 5, 6 };
+		act.setActionSetEthernetSrcAddr(new MACAddress(mac));
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionSetEthernetDstAddrActionSetEthernetAddr(){
+		FlowEntryAction act = new FlowEntryAction();
+		byte[] mac = { 1, 2, 3, 4, 5, 6 };
+		ActionSetEthernetAddr setEth = act.new ActionSetEthernetAddr(new MACAddress(mac));
+		act.setActionSetEthernetDstAddr( setEth );
+
+		assertEquals("action type",FlowEntryAction.ActionValues.ACTION_SET_DL_DST , act.actionType());
+		assertEquals("addr should be the same", setEth.addr(), act.actionSetEthernetDstAddr().addr());
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionSetEthernetDstAddrMACAddress(){
+		FlowEntryAction act = new FlowEntryAction();
+		byte[] mac = { 1, 2, 3, 4, 5, 6 };
+		act.setActionSetEthernetDstAddr(new MACAddress(mac));
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionSetIPv4SrcAddrActionSetIPv4Addr(){
+		FlowEntryAction act = new FlowEntryAction();
+		ActionSetIPv4Addr setIp = act.new ActionSetIPv4Addr(new IPv4("127.0.0.1"));
+		act.setActionSetIPv4SrcAddr( setIp );
+
+		assertEquals("action type",FlowEntryAction.ActionValues.ACTION_SET_NW_SRC , act.actionType());
+		assertEquals("addr should be the same", setIp.addr(), act.actionSetIPv4SrcAddr().addr());
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionSetIPv4SrcAddrIPv4(){
+		FlowEntryAction act = new FlowEntryAction();
+		act.setActionSetIPv4SrcAddr(new IPv4("127.0.0.1"));
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionSetIPv4DstAddrActionSetIPv4Addr(){
+		FlowEntryAction act = new FlowEntryAction();
+		ActionSetIPv4Addr setIp = act.new ActionSetIPv4Addr(new IPv4("127.0.0.1"));
+		act.setActionSetIPv4DstAddr( setIp );
+
+		assertEquals("action type",FlowEntryAction.ActionValues.ACTION_SET_NW_DST , act.actionType());
+		assertEquals("addr should be the same", setIp.addr(), act.actionSetIPv4DstAddr().addr());
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionSetIPv4DstAddrIPv4(){
+		FlowEntryAction act = new FlowEntryAction();
+		act.setActionSetIPv4DstAddr(new IPv4("127.0.0.1"));
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionSetIpToSActionSetIpToS(){
+		FlowEntryAction act = new FlowEntryAction();
+		ActionSetIpToS setIpTos = act.new ActionSetIpToS((byte)42);
+		act.setActionSetIpToS( setIpTos );
+
+		assertEquals("action type",FlowEntryAction.ActionValues.ACTION_SET_NW_TOS , act.actionType());
+		assertEquals("tos should be the same", setIpTos.ipToS(), act.actionSetIpToS().ipToS());
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionSetIpToSByte(){
+		FlowEntryAction act = new FlowEntryAction();
+		act.setActionSetIpToS((byte)1);
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionSetTcpUdpSrcPortActionSetTcpUdpPort(){
+		FlowEntryAction act = new FlowEntryAction();
+		ActionSetTcpUdpPort setPorts = act.new ActionSetTcpUdpPort((short)42);
+		act.setActionSetTcpUdpSrcPort( setPorts );
+
+		assertEquals("action type",FlowEntryAction.ActionValues.ACTION_SET_TP_SRC , act.actionType());
+		assertEquals("port should be the same", setPorts.port(), act.actionSetTcpUdpSrcPort().port());
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionSetTcpUdpSrcPortShort(){
+		FlowEntryAction act = new FlowEntryAction();
+		act.setActionSetTcpUdpSrcPort((short)1);
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionSetTcpUdpDstPortActionSetTcpUdpPort(){
+		FlowEntryAction act = new FlowEntryAction();
+		ActionSetTcpUdpPort setPorts = act.new ActionSetTcpUdpPort((short)42);
+		act.setActionSetTcpUdpDstPort( setPorts );
+
+		assertEquals("action type",FlowEntryAction.ActionValues.ACTION_SET_TP_DST , act.actionType());
+		assertEquals("port should be the same", setPorts.port(), act.actionSetTcpUdpDstPort().port());
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionSetTcpUdpDstPortShort(){
+		FlowEntryAction act = new FlowEntryAction();
+		act.setActionSetTcpUdpDstPort((short)1);
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionEnqueueActionEnqueue(){
+		FlowEntryAction act = new FlowEntryAction();
+		ActionEnqueue enq = act.new ActionEnqueue(new Port((short)42), 1);
+		act.setActionEnqueue( enq );
+
+		assertEquals("action type",FlowEntryAction.ActionValues.ACTION_ENQUEUE , act.actionType());
+		assertEquals("port should be the same", enq.port(), act.actionEnqueue().port());
+		assertEquals("queue id should be the same", enq.queueId(), act.actionEnqueue().queueId());
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+	@Test
+	public void testSetActionEnqueuePortInt(){
+		FlowEntryAction act = new FlowEntryAction();
+		act.setActionEnqueue(new Port((short)42), 1);
+
+		FlowEntryAction act_copy = new FlowEntryAction(act);
+		FlowEntryAction act_copy2 = new FlowEntryAction(act.toString());
+
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy.toString());
+		assertEquals("toString must match between copies", act.toString(),
+				act_copy2.toString());
+	}
+
+}
diff --git a/src/test/java/net/onrc/onos/ofcontroller/util/FlowEntryMatchTest.java b/src/test/java/net/onrc/onos/ofcontroller/util/FlowEntryMatchTest.java
new file mode 100644
index 0000000..4381208
--- /dev/null
+++ b/src/test/java/net/onrc/onos/ofcontroller/util/FlowEntryMatchTest.java
@@ -0,0 +1,312 @@
+package net.onrc.onos.ofcontroller.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import net.floodlightcontroller.util.MACAddress;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class FlowEntryMatchTest {
+
+	FlowEntryMatch match;
+	
+	Port inport = new Port((short)1);
+	byte[] byte1 = { 1, 2, 3, 4, 5, 6 };
+	byte[] byte2 = { 6, 5, 4, 3, 2, 1 };
+	MACAddress mac1 = new MACAddress(byte1);
+	MACAddress mac2 = new MACAddress(byte2);
+	Short ether = Short.valueOf((short)2);
+	Short vlanid = Short.valueOf((short)3);
+	Byte vlanprio = Byte.valueOf((byte)4);
+	IPv4Net ip1 = new IPv4Net("127.0.0.1/32");
+	IPv4Net ip2 = new IPv4Net("127.0.0.2/32");
+	Byte ipproto = Byte.valueOf((byte)5);
+	Byte ipToS = Byte.valueOf((byte)6);
+	Short tport1 = Short.valueOf((short)7);
+	Short tport2 = Short.valueOf((short)8);
+	
+	@Before
+	public void setUp() throws Exception{
+		match = new FlowEntryMatch();
+		match.enableInPort( inport);
+		match.enableSrcMac( mac1 );
+		match.enableDstMac( mac2 );
+		match.enableEthernetFrameType( ether );
+		match.enableVlanId( vlanid );
+		match.enableVlanPriority( vlanprio );
+		match.enableSrcIPv4Net( ip1 );
+		match.enableDstIPv4Net( ip2 );
+		match.enableIpProto( ipproto );
+		match.enableIpToS( ipToS );
+		match.enableSrcTcpUdpPort( tport1 );
+		match.enableDstTcpUdpPort( tport2 );
+	}
+
+	@Test
+	public void testFlowEntryMatch(){
+		FlowEntryMatch def = new FlowEntryMatch();
+		
+		assertEquals("default null", null, def.inPort() );
+		assertEquals("default null", null, def.srcMac() );
+		assertEquals("default null", null, def.dstMac() );
+		assertEquals("default null", null, def.ethernetFrameType() );
+		assertEquals("default null", null, def.vlanId() );
+		assertEquals("default null", null, def.vlanPriority() );
+		assertEquals("default null", null, def.srcIPv4Net() );
+		assertEquals("default null", null, def.dstIPv4Net() );
+		assertEquals("default null", null, def.ipProto() );
+		assertEquals("default null", null, def.ipToS() );
+		assertEquals("default null", null, def.srcTcpUdpPort() );
+		assertEquals("default null", null, def.dstTcpUdpPort() );
+	}
+
+	@Test
+	public void testFlowEntryMatchFlowEntryMatch(){
+		FlowEntryMatch def_base = new FlowEntryMatch();
+		FlowEntryMatch def = new FlowEntryMatch(def_base);
+
+		assertEquals("default null", null, def.inPort() );
+		assertEquals("default null", null, def.srcMac() );
+		assertEquals("default null", null, def.dstMac() );
+		assertEquals("default null", null, def.ethernetFrameType() );
+		assertEquals("default null", null, def.vlanId() );
+		assertEquals("default null", null, def.vlanPriority() );
+		assertEquals("default null", null, def.srcIPv4Net() );
+		assertEquals("default null", null, def.dstIPv4Net() );
+		assertEquals("default null", null, def.ipProto() );
+		assertEquals("default null", null, def.ipToS() );
+		assertEquals("default null", null, def.srcTcpUdpPort() );
+		assertEquals("default null", null, def.dstTcpUdpPort() );
+		
+		FlowEntryMatch copy = new FlowEntryMatch( match );
+		
+		assertEquals("inport", inport, copy.inPort() );
+		assertEquals("mac1", mac1, copy.srcMac() );
+		assertEquals("mac2", mac2, copy.dstMac() );
+		assertEquals("ether", ether, copy.ethernetFrameType() );
+		assertEquals("vlan id", vlanid, copy.vlanId() );
+		assertEquals("vlan prio", vlanprio, copy.vlanPriority() );
+		assertEquals("ip1", ip1, copy.srcIPv4Net() );
+		assertEquals("ip2", ip2, copy.dstIPv4Net() );
+		assertEquals("ip proto", ipproto, copy.ipProto() );
+		assertEquals("tos", ipToS, copy.ipToS() );
+		assertEquals("src port", tport1, copy.srcTcpUdpPort() );
+		assertEquals("dst port", tport2, copy.dstTcpUdpPort() );
+
+	}
+
+	@Test
+	public void testInPort(){
+		assertEquals("inport", inport, match.inPort() );
+	}
+
+	@Test
+	public void testDisableInPort(){
+		match.disableInPort();
+		assertEquals("inport", null, match.inPort() );
+		assertFalse( match.matchInPort() );
+	}
+
+	@Test
+	public void testMatchInPort(){
+		assertTrue( match.matchInPort() );
+	}
+
+	@Test
+	public void testSrcMac(){
+		assertEquals("mac1", mac1, match.srcMac() );
+	}
+
+	@Test
+	public void testDisableSrcMac(){
+		match.disableSrcMac();
+		assertEquals("srcMac", null, match.srcMac() );
+		assertFalse( match.matchSrcMac() );
+	}
+
+	@Test
+	public void testMatchSrcMac(){
+		assertTrue( match.matchSrcMac() );
+	}
+
+	@Test
+	public void testDstMac(){
+		assertEquals("mac2", mac2, match.dstMac() );
+	}
+
+	@Test
+	public void testDisableDstMac(){
+		match.disableDstMac();
+		assertEquals("dstMac", null, match.dstMac() );
+		assertFalse( match.matchDstMac() );
+	}
+
+	@Test
+	public void testMatchDstMac(){
+		assertTrue( match.matchDstMac() );
+	}
+
+	@Test
+	public void testEthernetFrameType(){
+		assertEquals("ether", ether, match.ethernetFrameType() );
+	}
+
+	@Test
+	public void testDisableEthernetFrameType(){
+		match.disableEthernetFrameType();
+		assertEquals("ethernetFrameType", null, match.ethernetFrameType() );
+		assertFalse( match.matchEthernetFrameType() );
+	}
+
+	@Test
+	public void testMatchEthernetFrameType(){
+		assertTrue( match.matchEthernetFrameType() );
+	}
+
+	@Test
+	public void testVlanId(){
+		assertEquals("vlan id", vlanid, match.vlanId() );
+	}
+
+	@Test
+	public void testDisableVlanId(){
+		match.disableVlanId();
+		assertEquals("vlanId", null, match.vlanId() );
+		assertFalse( match.matchVlanId() );
+	}
+
+	@Test
+	public void testMatchVlanId(){
+		assertTrue( match.matchVlanId() );
+	}
+
+	@Test
+	public void testVlanPriority(){
+		assertEquals("vlan prio", vlanprio, match.vlanPriority() );
+	}
+
+	@Test
+	public void testDisableVlanPriority(){
+		match.disableVlanPriority();
+		assertEquals("vlanPriority", null, match.vlanPriority() );
+		assertFalse( match.matchVlanPriority() );
+	}
+
+	@Test
+	public void testMatchVlanPriority(){
+		assertTrue( match.matchVlanPriority() );
+	}
+
+	@Test
+	public void testSrcIPv4Net(){
+		assertEquals("ip1", ip1, match.srcIPv4Net() );
+	}
+
+	@Test
+	public void testDisableSrcIPv4Net(){
+		match.disableSrcIPv4Net();
+		assertEquals("srcIPv4Net", null, match.srcIPv4Net() );
+		assertFalse( match.matchSrcIPv4Net() );
+	}
+
+	@Test
+	public void testMatchSrcIPv4Net(){
+		assertTrue( match.matchSrcIPv4Net() );
+	}
+
+	@Test
+	public void testDstIPv4Net(){
+		assertEquals("ip2", ip2, match.dstIPv4Net() );
+	}
+
+	@Test
+	public void testDisableDstIPv4Net(){
+		match.disableDstIPv4Net();
+		assertEquals("dstIPv4Net", null, match.dstIPv4Net() );
+		assertFalse( match.matchDstIPv4Net() );
+	}
+
+	@Test
+	public void testMatchDstIPv4Net(){
+		assertTrue( match.matchDstIPv4Net() );
+	}
+
+	@Test
+	public void testIpProto(){
+		assertEquals("ip proto", ipproto, match.ipProto() );
+	}
+
+	@Test
+	public void testDisableIpProto(){
+		match.disableIpProto();
+		assertEquals("ipProto", null, match.ipProto() );
+		assertFalse( match.matchIpProto() );
+	}
+
+	@Test
+	public void testMatchIpProto(){
+		assertTrue( match.matchIpProto() );
+	}
+
+	@Test
+	public void testIpToS(){
+		assertEquals("tos", ipToS, match.ipToS() );
+	}
+
+	@Test
+	public void testDisableIpToS(){
+		match.disableIpToS();
+		assertEquals("ipToS", null, match.ipToS() );
+		assertFalse( match.matchIpToS() );
+	}
+
+	@Test
+	public void testMatchIpToS(){
+		assertTrue( match.matchIpToS() );
+	}
+
+	@Test
+	public void testSrcTcpUdpPort(){
+		assertEquals("src port", tport1, match.srcTcpUdpPort() );
+	}
+
+	@Test
+	public void testDisableSrcTcpUdpPort(){
+		match.disableSrcTcpUdpPort();
+		assertEquals("srcTcpUdpPort", null, match.srcTcpUdpPort() );
+		assertFalse( match.matchSrcTcpUdpPort() );
+	}
+
+	@Test
+	public void testMatchSrcTcpUdpPort(){
+		assertTrue( match.matchSrcTcpUdpPort() );
+	}
+
+	@Test
+	public void testDstTcpUdpPort(){
+		assertEquals("dst port", tport2, match.dstTcpUdpPort() );
+	}
+
+	@Test
+	public void testDisableDstTcpUdpPort(){
+		match.disableDstTcpUdpPort();
+		assertEquals("dstTcpUdpPort", null, match.dstTcpUdpPort() );
+		assertFalse( match.matchDstTcpUdpPort() );
+	}
+
+	@Test
+	public void testMatchDstTcpUdpPort(){
+		assertTrue( match.matchDstTcpUdpPort() );
+	}
+
+	@Test
+	public void testToString(){
+		FlowEntryMatch def = new FlowEntryMatch();
+		assertEquals("match default", def.toString(), "[]");
+		
+		assertEquals("match set", match.toString(), "[inPort=1 srcMac=01:02:03:04:05:06 dstMac=06:05:04:03:02:01 ethernetFrameType=2 vlanId=3 vlanPriority=4 srcIPv4Net=127.0.0.1/32 dstIPv4Net=127.0.0.2/32 ipProto=5 ipToS=6 srcTcpUdpPort=7 dstTcpUdpPort=8]");
+	}
+
+}
diff --git a/src/test/java/net/onrc/onos/ofcontroller/util/FlowEntryTest.java b/src/test/java/net/onrc/onos/ofcontroller/util/FlowEntryTest.java
new file mode 100644
index 0000000..1d193c4
--- /dev/null
+++ b/src/test/java/net/onrc/onos/ofcontroller/util/FlowEntryTest.java
@@ -0,0 +1,210 @@
+package net.onrc.onos.ofcontroller.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import net.floodlightcontroller.util.MACAddress;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class FlowEntryTest {
+
+	FlowEntry entry;
+	
+	FlowId flowId = new FlowId(0x1234);
+	FlowEntryId flowEntryId = new FlowEntryId(0x5678);
+	FlowEntryMatch match;
+	FlowEntryActions actions;
+	
+	Dpid dpid = new Dpid(0xCAFE);
+	
+	Port inport = new Port((short)1);
+	byte[] byte1 = { 1, 2, 3, 4, 5, 6 };
+	byte[] byte2 = { 6, 5, 4, 3, 2, 1 };
+	MACAddress mac1 = new MACAddress(byte1);
+	MACAddress mac2 = new MACAddress(byte2);
+	Short ether = Short.valueOf((short)2);
+	Short vlanid = Short.valueOf((short)3);
+	Byte vlanprio = Byte.valueOf((byte)4);
+	IPv4Net ip1 = new IPv4Net("127.0.0.1/32");
+	IPv4Net ip2 = new IPv4Net( new IPv4("127.0.0.2"), (short)32);
+	IPv4 ipaddr1 = new IPv4("127.0.0.3");
+	IPv4 ipaddr2 = new IPv4("127.0.0.4");
+	Byte ipproto = Byte.valueOf((byte)5);
+	Byte ipToS = Byte.valueOf((byte)6);
+	Short tport1 = Short.valueOf((short)7);
+	Short tport2 = Short.valueOf((short)8);
+	Port outport = new Port((short)9);
+	Port queueport = new Port((short)10);
+	int queueId = 11;
+	
+	FlowEntryErrorState errorState = new FlowEntryErrorState( (short)12, (short)13);
+
+	
+	@Before
+	public void setUp() throws Exception{
+		entry = new FlowEntry();
+
+		flowId = new FlowId("0x1234");
+		entry.setFlowId( flowId );
+
+		flowEntryId = new FlowEntryId("0x5678");
+		entry.setFlowEntryId(flowEntryId);
+		
+		dpid = new Dpid("CA:FE");
+		entry.setDpid( dpid );
+		
+		entry.setInPort( inport );
+		entry.setOutPort( outport );
+
+		match = new FlowEntryMatch();
+		match.enableInPort( inport);
+		match.enableSrcMac( mac1 );
+		match.enableDstMac( mac2 );
+		match.enableEthernetFrameType( ether );
+		match.enableVlanId( vlanid );
+		match.enableVlanPriority( vlanprio );
+		match.enableSrcIPv4Net( ip1 );
+		match.enableDstIPv4Net( ip2 );
+		match.enableIpProto( ipproto );
+		match.enableIpToS( ipToS );
+		match.enableSrcTcpUdpPort( tport1 );
+		match.enableDstTcpUdpPort( tport2 );
+		
+		entry.setFlowEntryMatch( match );
+		
+		FlowEntryAction action = null;
+		actions = entry.flowEntryActions();
+		
+		action = new FlowEntryAction();
+		action.setActionOutput(outport);
+		actions.addAction(action);
+
+		action = new FlowEntryAction();
+		action.setActionOutputToController((short)0);
+		actions.addAction(action);
+
+		action = new FlowEntryAction();
+		action.setActionSetVlanId(vlanid);
+		actions.addAction(action);
+
+		action = new FlowEntryAction();
+		action.setActionSetVlanPriority(vlanprio);
+		actions.addAction(action);
+
+		action = new FlowEntryAction();
+		action.setActionStripVlan(true);
+		actions.addAction(action);
+
+		action = new FlowEntryAction();
+		action.setActionSetEthernetSrcAddr(mac1);
+		actions.addAction(action);
+
+		action = new FlowEntryAction();
+		action.setActionSetEthernetDstAddr(mac2);
+		actions.addAction(action);
+
+		action = new FlowEntryAction();
+		action.setActionSetIPv4SrcAddr(ipaddr1);
+		actions.addAction(action);
+
+		action = new FlowEntryAction();
+		action.setActionSetIPv4DstAddr(ipaddr2);
+		actions.addAction(action);
+
+		action = new FlowEntryAction();
+		action.setActionSetIpToS(ipToS);
+		actions.addAction(action);
+
+		action = new FlowEntryAction();
+		action.setActionSetTcpUdpSrcPort(tport1);
+		actions.addAction(action);
+
+		action = new FlowEntryAction();
+		action.setActionSetTcpUdpDstPort(tport2);
+		actions.addAction(action);
+
+		action = new FlowEntryAction();
+		action.setActionEnqueue(queueport, queueId);
+		actions.addAction(action);
+		
+		entry.setFlowEntryUserState( FlowEntryUserState.FE_USER_ADD );
+		entry.setFlowEntrySwitchState( FlowEntrySwitchState.FE_SWITCH_UPDATED );
+		entry.setFlowEntryErrorState( errorState );
+
+	}
+
+	@Test
+	public void testFlowEntry(){
+		FlowEntry e = new FlowEntry();
+		
+		assertTrue( e.flowEntryActions().isEmpty() );
+		assertEquals("flowEntryUserState", FlowEntryUserState.FE_USER_UNKNOWN, e.flowEntryUserState() );
+		assertEquals("flowEntrySwitchState", FlowEntrySwitchState.FE_SWITCH_UNKNOWN, e.flowEntrySwitchState() );
+	}
+
+	@Test
+	public void testGetFlowId(){
+		assertEquals("flowId", flowId, entry.getFlowId() );
+	}
+
+	@Test
+	public void testFlowEntryId(){
+		assertEquals("flowEntryId", flowEntryId, entry.flowEntryId() );
+	}
+
+	@Test
+	public void testFlowEntryMatch(){
+		assertEquals("flowEntryMatch", match, entry.flowEntryMatch() );
+	}
+
+	@Test
+	public void testFlowEntryActions(){
+		assertEquals("flowEntryActions", actions, entry.flowEntryActions() );
+	}
+
+	@Test
+	public void testSetFlowEntryActions(){
+		FlowEntryActions actions = new FlowEntryActions();
+		entry.setFlowEntryActions( actions );
+		assertEquals("flowEntryActions", actions, entry.flowEntryActions() );
+	}
+
+	@Test
+	public void testDpid(){
+		assertEquals("dpid", dpid, entry.dpid() );
+	}
+
+	@Test
+	public void testInPort(){
+		assertEquals("inPort", inport, entry.inPort() );
+	}
+
+	@Test
+	public void testOutPort(){
+		assertEquals("outPort", outport, entry.outPort() );
+	}
+
+	@Test
+	public void testFlowEntryUserState(){
+		assertEquals("flowEntryUserState", FlowEntryUserState.FE_USER_ADD, entry.flowEntryUserState() );
+	}
+
+	@Test
+	public void testFlowEntrySwitchState(){
+		assertEquals("flowEntrySwitchState", FlowEntrySwitchState.FE_SWITCH_UPDATED, entry.flowEntrySwitchState() );
+	}
+
+	@Test
+	public void testFlowEntryErrorState(){
+		assertEquals("flowEntryErrorState", errorState, entry.flowEntryErrorState() );
+	}
+
+	@Test
+	public void testToString(){
+		FlowEntry def = new FlowEntry();
+		assertEquals( def.toString(), "[ flowEntryActions=[] flowEntryUserState=FE_USER_UNKNOWN flowEntrySwitchState=FE_SWITCH_UNKNOWN]" );
+		assertEquals( entry.toString(), "[flowEntryId=0x5678 flowEntryMatch=[inPort=1 srcMac=01:02:03:04:05:06 dstMac=06:05:04:03:02:01 ethernetFrameType=2 vlanId=3 vlanPriority=4 srcIPv4Net=127.0.0.1/32 dstIPv4Net=127.0.0.2/32 ipProto=5 ipToS=6 srcTcpUdpPort=7 dstTcpUdpPort=8] flowEntryActions=[[type=ACTION_OUTPUT action=[port=9 maxLen=0]];[type=ACTION_OUTPUT action=[port=-3 maxLen=0]];[type=ACTION_SET_VLAN_VID action=[vlanId=3]];[type=ACTION_SET_VLAN_PCP action=[vlanPriority=4]];[type=ACTION_STRIP_VLAN action=[stripVlan=true]];[type=ACTION_SET_DL_SRC action=[addr=01:02:03:04:05:06]];[type=ACTION_SET_DL_DST action=[addr=06:05:04:03:02:01]];[type=ACTION_SET_NW_SRC action=[addr=127.0.0.3]];[type=ACTION_SET_NW_DST action=[addr=127.0.0.4]];[type=ACTION_SET_NW_TOS action=[ipToS=6]];[type=ACTION_SET_TP_SRC action=[port=7]];[type=ACTION_SET_TP_DST action=[port=8]];[type=ACTION_ENQUEUE action=[port=10 queueId=11]];] dpid=00:00:00:00:00:00:ca:fe inPort=1 outPort=9 flowEntryUserState=FE_USER_ADD flowEntrySwitchState=FE_SWITCH_UPDATED flowEntryErrorState=[type=12 code=13]]" );
+	}
+
+}
diff --git a/src/test/java/net/onrc/onos/ofcontroller/util/FlowPathTest.java b/src/test/java/net/onrc/onos/ofcontroller/util/FlowPathTest.java
new file mode 100644
index 0000000..89a12e5
--- /dev/null
+++ b/src/test/java/net/onrc/onos/ofcontroller/util/FlowPathTest.java
@@ -0,0 +1,182 @@
+package net.onrc.onos.ofcontroller.util;
+
+import static org.junit.Assert.*;
+import net.onrc.onos.ofcontroller.core.internal.TestableGraphDBOperation.TestFlowEntry;
+import net.onrc.onos.ofcontroller.core.internal.TestableGraphDBOperation.TestFlowPath;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class FlowPathTest {
+
+	FlowPath flowPath;
+	
+	@Before
+	public void setUp() throws Exception{
+		TestFlowPath iFlowPath = new TestFlowPath();
+		iFlowPath.setFlowIdForTest("0x1234");
+		iFlowPath.setInstallerIdForTest("installerId");
+		iFlowPath.setFlowPathFlagsForTest(0L);
+		iFlowPath.setSrcSwForTest("CA:FE");
+		iFlowPath.setSrcPortForTest((short)1);
+		iFlowPath.setDstSwForTest("BA:BE");
+		iFlowPath.setDstPortForTest((short)2);
+		
+		iFlowPath.setActionsForTest("[[type=ACTION_OUTPUT action=[port=10 maxLen=11]];[type=ACTION_OUTPUT action=[port=12 maxLen=13]];]");
+		
+		TestFlowEntry iFlowEntry = new TestFlowEntry();
+		iFlowEntry.setEntryIdForTest("0x14");
+		iFlowEntry.setDpidForTest("BE:EF");
+		iFlowEntry.setActionsForTest("[[type=ACTION_OUTPUT action=[port=23 maxLen=24]];[type=ACTION_OUTPUT action=[port=25 maxLen=26]];]");
+		iFlowEntry.setUserStateForTest("FE_USER_MODIFY");
+		iFlowEntry.setSwitchStateForTest("FE_SWITCH_UPDATE_IN_PROGRESS");
+		iFlowPath.addFlowEntryForTest(iFlowEntry);
+		
+		flowPath = new FlowPath(iFlowPath);
+	}
+
+	@Test
+	public void testFlowPath(){
+		FlowPath flowPath = new FlowPath();
+		assertFalse( flowPath.flowPathFlags().isDiscardFirstHopEntry() );
+		assertFalse( flowPath.flowPathFlags().isKeepOnlyFirstHopEntry() );
+		assertTrue( flowPath.flowEntryActions().isEmpty() );
+	}
+
+	@Test
+	public void testFlowPathIFlowPath(){
+		TestFlowPath iFlowPath = new TestFlowPath();
+		iFlowPath.setFlowIdForTest("0x1234");
+		iFlowPath.setInstallerIdForTest("installerId");
+		iFlowPath.setFlowPathFlagsForTest(0L);
+		iFlowPath.setSrcSwForTest("CA:FE");
+		iFlowPath.setSrcPortForTest((short)1);
+		iFlowPath.setDstSwForTest("BA:BE");
+		iFlowPath.setDstPortForTest((short)2);
+		
+		iFlowPath.setMatchSrcMacForTest("01:02:03:04:05:06");
+		iFlowPath.setMatchDstMacForTest("06:05:04:03:02:01");
+		iFlowPath.setMatchEthernetFrameTypeForTest((short)3);
+		iFlowPath.setMatchVlanIdForTest((short)4);
+		iFlowPath.setMatchVlanPriorityForTest((byte)5);
+		iFlowPath.setMatchSrcIpaddrForTest("127.0.0.1/32");
+		iFlowPath.setMatchDstIpaddrForTest("127.0.0.2/32");
+		iFlowPath.setMatchIpProtoForTest((byte)6);
+		iFlowPath.setMatchIpToSForTest((byte)7);
+		iFlowPath.setMatchSrcTcpUdpPortForTest((short)8);
+		iFlowPath.setMatchDstTcpUdpPortForTest((short)9);
+		
+		iFlowPath.setActionsForTest("[[type=ACTION_OUTPUT action=[port=10 maxLen=11]];[type=ACTION_OUTPUT action=[port=12 maxLen=13]];]");
+		
+		TestFlowEntry iFlowEntry = new TestFlowEntry();
+		iFlowEntry.setEntryIdForTest("0x14");
+		iFlowEntry.setDpidForTest("BE:EF");
+		iFlowEntry.setMatchInPortForTest((short)15);
+		iFlowEntry.setMatchSrcMacForTest("11:22:33:44:55:66");
+		iFlowEntry.setMatchDstMacForTest("66:55:44:33:22:11");
+		iFlowEntry.setMatchEtherFrameTypeForTest((short)16);
+		iFlowEntry.setMatchVlanIdForTest((short)17);
+		iFlowEntry.setMatchVlanPriorityForTest((byte)18);
+		iFlowEntry.setMatchSrcIpaddrForTest("127.0.0.3/32");
+		iFlowEntry.setMatchDstIpaddrForTest("127.0.0.4/32");
+		iFlowEntry.setMatchIpProtoForTest((byte)19);
+		iFlowEntry.setMatchIpToSForTest((byte)20);
+		iFlowEntry.setMatchSrcTcpUdpPortForTest((short)21);
+		iFlowEntry.setMatchDstTcpUdpPortForTest((short)22);
+		iFlowEntry.setActionsForTest("[[type=ACTION_OUTPUT action=[port=23 maxLen=24]];[type=ACTION_OUTPUT action=[port=25 maxLen=26]];]");
+		iFlowEntry.setUserStateForTest("FE_USER_MODIFY");
+		iFlowEntry.setSwitchStateForTest("FE_SWITCH_UPDATE_IN_PROGRESS");
+		iFlowPath.addFlowEntryForTest(iFlowEntry);
+		
+		FlowPath flowPath = new FlowPath(iFlowPath);
+		assertEquals(flowPath.flowId().value(), 0x1234);
+		assertEquals(flowPath.installerId().value(), "installerId");
+		assertEquals(flowPath.flowPathFlags().flags(), 0);
+		assertEquals(flowPath.dataPath().srcPort().dpid().value(), 0xCAFE);
+		assertEquals(flowPath.dataPath().srcPort().port().value(), 1);
+		assertEquals(flowPath.dataPath().dstPort().dpid().value(), 0xBABE);
+		assertEquals(flowPath.dataPath().dstPort().port().value(), 2);
+		
+		assertEquals(flowPath.flowEntryMatch().srcMac().toString(), "01:02:03:04:05:06");
+		assertEquals(flowPath.flowEntryMatch().dstMac().toString(), "06:05:04:03:02:01");
+		assertEquals(flowPath.flowEntryMatch().ethernetFrameType().shortValue(), 3);
+		assertEquals(flowPath.flowEntryMatch().vlanId().shortValue(), 4);
+		assertEquals(flowPath.flowEntryMatch().vlanPriority().shortValue(), 5);
+		assertEquals(flowPath.flowEntryMatch().srcIPv4Net().address().toString(), "127.0.0.1");
+		assertEquals(flowPath.flowEntryMatch().srcIPv4Net().prefixLen() , 32);
+		assertEquals(flowPath.flowEntryMatch().dstIPv4Net().address().toString(), "127.0.0.2");
+		assertEquals(flowPath.flowEntryMatch().dstIPv4Net().prefixLen() , 32);
+		assertEquals(flowPath.flowEntryMatch().ipProto().byteValue(), 6);
+		assertEquals(flowPath.flowEntryMatch().ipToS().byteValue(), 7);
+		assertEquals(flowPath.flowEntryMatch().srcTcpUdpPort().shortValue(), 8);
+		assertEquals(flowPath.flowEntryMatch().dstTcpUdpPort().shortValue(), 9);
+		
+		assertEquals(flowPath.flowEntryActions().toString(),"[[type=ACTION_OUTPUT action=[port=10 maxLen=11]];[type=ACTION_OUTPUT action=[port=12 maxLen=13]];]");
+		
+		assertEquals(0x14, flowPath.dataPath().flowEntries().get(0).flowEntryId().value() );
+		assertEquals(0xBEEF, flowPath.dataPath().flowEntries().get(0).dpid().value() );
+		assertEquals(15, flowPath.dataPath().flowEntries().get(0).flowEntryMatch().inPort().value() );
+		assertEquals("11:22:33:44:55:66", flowPath.dataPath().flowEntries().get(0).flowEntryMatch().srcMac().toString());
+		assertEquals("66:55:44:33:22:11", flowPath.dataPath().flowEntries().get(0).flowEntryMatch().dstMac().toString());
+		assertEquals(16, flowPath.dataPath().flowEntries().get(0).flowEntryMatch().ethernetFrameType().shortValue());
+		assertEquals(17, flowPath.dataPath().flowEntries().get(0).flowEntryMatch().vlanId().shortValue());
+		assertEquals(18, flowPath.dataPath().flowEntries().get(0).flowEntryMatch().vlanPriority().byteValue());
+		assertEquals("127.0.0.3", flowPath.dataPath().flowEntries().get(0).flowEntryMatch().srcIPv4Net().address().toString());
+		assertEquals(32, flowPath.dataPath().flowEntries().get(0).flowEntryMatch().srcIPv4Net().prefixLen());
+		assertEquals("127.0.0.4", flowPath.dataPath().flowEntries().get(0).flowEntryMatch().dstIPv4Net().address().toString());
+		assertEquals(32, flowPath.dataPath().flowEntries().get(0).flowEntryMatch().dstIPv4Net().prefixLen());
+		assertEquals(19, flowPath.dataPath().flowEntries().get(0).flowEntryMatch().ipProto().byteValue());
+		assertEquals(20, flowPath.dataPath().flowEntries().get(0).flowEntryMatch().ipToS().byteValue());
+		assertEquals(21, flowPath.dataPath().flowEntries().get(0).flowEntryMatch().srcTcpUdpPort().shortValue());
+		assertEquals(22, flowPath.dataPath().flowEntries().get(0).flowEntryMatch().dstTcpUdpPort().shortValue());
+		assertEquals("[[type=ACTION_OUTPUT action=[port=23 maxLen=24]];[type=ACTION_OUTPUT action=[port=25 maxLen=26]];]", flowPath.dataPath().flowEntries().get(0).flowEntryActions().toString());
+		assertEquals("FE_USER_MODIFY", flowPath.dataPath().flowEntries().get(0).flowEntryUserState().toString());
+		assertEquals("FE_SWITCH_UPDATE_IN_PROGRESS", flowPath.dataPath().flowEntries().get(0).flowEntrySwitchState().toString());
+	}
+
+
+	@Test
+	public void testFlowPathFlags(){
+		FlowPath flowPath = new FlowPath();
+		FlowPathFlags flags = new FlowPathFlags();
+		flags.setFlags(0);
+		flowPath.setFlowPathFlags( flags );
+		assertFalse( flowPath.flowPathFlags().isDiscardFirstHopEntry() );
+		assertFalse( flowPath.flowPathFlags().isKeepOnlyFirstHopEntry() );
+	}
+
+	@Test
+	public void testSetFlowPathFlags(){
+		FlowPath flowPath = new FlowPath();
+		FlowPathFlags flags = new FlowPathFlags("DISCARD_FIRST_HOP_ENTRY");
+		flags.setFlagsStr("KEEP_ONLY_FIRST_HOP_ENTRY");
+		flowPath.setFlowPathFlags( flags );
+		assertFalse( flowPath.flowPathFlags().isDiscardFirstHopEntry() );
+		assertTrue( flowPath.flowPathFlags().isKeepOnlyFirstHopEntry() );
+	}
+
+	@Test
+	public void testSetDataPath(){
+		FlowPath flowPath = new FlowPath();
+		DataPath dataPath = new DataPath();
+		flowPath.setDataPath( dataPath );
+		assertEquals(flowPath.dataPath(), dataPath );
+	}
+
+	@Test
+	public void testToString(){
+
+		assertEquals("[flowId=0x1234 installerId=installerId flowPathFlags=[flags=] dataPath=[src=00:00:00:00:00:00:ca:fe/1 flowEntry=[flowEntryId=0x14 flowEntryMatch=[] flowEntryActions=[[type=ACTION_OUTPUT action=[port=23 maxLen=24]];[type=ACTION_OUTPUT action=[port=25 maxLen=26]];] dpid=00:00:00:00:00:00:be:ef flowEntryUserState=FE_USER_MODIFY flowEntrySwitchState=FE_SWITCH_UPDATE_IN_PROGRESS] dst=00:00:00:00:00:00:ba:be/2] flowEntryMatch=[] flowEntryActions=[[type=ACTION_OUTPUT action=[port=10 maxLen=11]];[type=ACTION_OUTPUT action=[port=12 maxLen=13]];]]", flowPath.toString());
+	}
+
+	@Test
+	public void testCompareTo(){
+		FlowPath flowPath1 = new FlowPath();
+		flowPath1.setFlowId( new FlowId(1));
+		FlowPath flowPath2 = new FlowPath();
+		flowPath2.setFlowId( new FlowId(2));
+		
+		assertTrue( flowPath1.compareTo(flowPath2) < 0);
+	}
+
+}