diff --git a/src/main/java/net/floodlightcontroller/virtualnetwork/IVirtualNetworkService.java b/src/main/java/net/floodlightcontroller/virtualnetwork/IVirtualNetworkService.java index ac60d098daf1435df4c7437e424523c17a76aaaf..7c5564fd6150e6a8d331d9bf039df819bcfb29e0 100644 --- a/src/main/java/net/floodlightcontroller/virtualnetwork/IVirtualNetworkService.java +++ b/src/main/java/net/floodlightcontroller/virtualnetwork/IVirtualNetworkService.java @@ -5,30 +5,35 @@ import net.floodlightcontroller.util.MACAddress; public interface IVirtualNetworkService extends IFloodlightService { /** - * Creates a new virtual network. + * Creates a new virtual network. This can also be called + * to modify a virtual network. To update a network you specify the GUID + * and the fields you want to update. * @param network The network name. Must be unique. * @param guid The ID of the network. Must be unique. - * @param gateway The IP address of the network gateway, 0 if none. Must be unique. + * @param gateway The IP address of the network gateway, null if none. Must be unique. */ - public void createNetwork(String network, String guid, Integer gateway); + public void createNetwork(String guid, String network, Integer gateway); /** * Deletes a virtual network. - * @param network The virtual network to delete. + * @param guid The ID (not name) of virtual network to delete. */ - public void deleteNetwork(String network); + public void deleteNetwork(String guid); /** - * Adds a host to a virtual network. + * Adds a host to a virtual network. If a mapping already exists the + * new one will override the old mapping. * @param mac The MAC address of the host to add. - * @param network The network to add the host to.. + * @param network The network to add the host to. + * @param port The logical port name to attach the host to. Must be unique. */ - public void addHost(MACAddress mac, String network); + public void addHost(MACAddress mac, String network, String port); /** - * Deletes a host from a virtual network. + * Deletes a host from a virtual network. Either the MAC or Port must + * be specified. * @param mac The MAC address to delete. - * @param network The network the host belongs to. + * @param port The logical port the host is attached to. */ - public void deleteHost(MACAddress mac, String network); + public void deleteHost(MACAddress mac, String port); } diff --git a/src/main/java/net/floodlightcontroller/virtualnetwork/forwarding/HostResource.java b/src/main/java/net/floodlightcontroller/virtualnetwork/forwarding/HostResource.java new file mode 100644 index 0000000000000000000000000000000000000000..1dacddce1e8fe3357e72f9fba78c9f91bc81434f --- /dev/null +++ b/src/main/java/net/floodlightcontroller/virtualnetwork/forwarding/HostResource.java @@ -0,0 +1,89 @@ +package net.floodlightcontroller.virtualnetwork.forwarding; + +import java.io.IOException; + +import net.floodlightcontroller.util.MACAddress; +import net.floodlightcontroller.virtualnetwork.IVirtualNetworkService; + +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonToken; +import org.codehaus.jackson.map.MappingJsonFactory; +import org.restlet.resource.Delete; +import org.restlet.resource.Put; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HostResource extends org.restlet.resource.ServerResource { + protected static Logger log = LoggerFactory.getLogger(HostResource.class); + + public class HostDefinition { + String port = null; // Logical port name + String guid = null; // Network ID + String mac = null; // MAC Address + } + + protected void jsonToHostDefinition(String json, HostDefinition host) throws IOException { + MappingJsonFactory f = new MappingJsonFactory(); + JsonParser jp; + + try { + jp = f.createJsonParser(json); + } catch (JsonParseException e) { + throw new IOException(e); + } + + jp.nextToken(); + if (jp.getCurrentToken() != JsonToken.START_OBJECT) { + throw new IOException("Expected START_OBJECT"); + } + + while (jp.nextToken() != JsonToken.END_OBJECT) { + if (jp.getCurrentToken() != JsonToken.FIELD_NAME) { + throw new IOException("Expected FIELD_NAME"); + } + + String n = jp.getCurrentName(); + jp.nextToken(); + if (jp.getText().equals("")) + continue; + else if (n.equals("attachment")) { + while (jp.nextToken() != JsonToken.END_OBJECT) { + String field = jp.getCurrentName(); + if (field.equals("id")) { + host.guid = jp.getText(); + } else if (field.equals("mac")) { + host.mac = jp.getText(); + } + } + } + } + + jp.close(); + } + + @Put + public void addHost(String postData) { + IVirtualNetworkService vns = + (IVirtualNetworkService)getContext().getAttributes(). + get(IVirtualNetworkService.class.getCanonicalName()); + HostDefinition host = new HostDefinition(); + host.port = (String) getRequestAttributes().get("port"); + try { + jsonToHostDefinition(postData, host); + } catch (IOException e) { + log.error("Could not parse JSON {}", e.getMessage()); + } + vns.addHost(MACAddress.valueOf(host.mac), host.guid, host.port); + } + + + @Delete + public void deleteHost() { + String port = (String) getRequestAttributes().get("port"); + IVirtualNetworkService vns = + (IVirtualNetworkService)getContext().getAttributes(). + get(IVirtualNetworkService.class.getCanonicalName()); + vns.deleteHost(null, port); + } +} diff --git a/src/main/java/net/floodlightcontroller/virtualnetwork/forwarding/NetworkResource.java b/src/main/java/net/floodlightcontroller/virtualnetwork/forwarding/NetworkResource.java new file mode 100644 index 0000000000000000000000000000000000000000..7faf454d2a4c051a220507f99ca7265c43e539b2 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/virtualnetwork/forwarding/NetworkResource.java @@ -0,0 +1,105 @@ +package net.floodlightcontroller.virtualnetwork.forwarding; + +import java.io.IOException; + +import net.floodlightcontroller.packet.IPv4; +import net.floodlightcontroller.virtualnetwork.IVirtualNetworkService; + +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonToken; +import org.codehaus.jackson.map.MappingJsonFactory; +import org.restlet.resource.Delete; +import org.restlet.resource.Post; +import org.restlet.resource.Put; +import org.restlet.resource.ServerResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NetworkResource extends ServerResource { + protected static Logger log = LoggerFactory.getLogger(NetworkResource.class); + + public class NetworkDefinition { + public String name = null; + public String guid = null; + public String gateway = null; + } + + protected void jsonToNetworkDefinition(String json, NetworkDefinition network) throws IOException { + MappingJsonFactory f = new MappingJsonFactory(); + JsonParser jp; + + try { + jp = f.createJsonParser(json); + } catch (JsonParseException e) { + throw new IOException(e); + } + + jp.nextToken(); + if (jp.getCurrentToken() != JsonToken.START_OBJECT) { + throw new IOException("Expected START_OBJECT"); + } + + while (jp.nextToken() != JsonToken.END_OBJECT) { + if (jp.getCurrentToken() != JsonToken.FIELD_NAME) { + throw new IOException("Expected FIELD_NAME"); + } + + String n = jp.getCurrentName(); + jp.nextToken(); + if (jp.getText().equals("")) + continue; + else if (n.equals("network")) { + while (jp.nextToken() != JsonToken.END_OBJECT) { + String field = jp.getCurrentName(); + if (field.equals("name")) { + network.name = jp.getText(); + } else if (field.equals("gateway")) { + network.gateway = jp.getText(); + } + } + } + } + + jp.close(); + } + + + @Put + @Post + public void createNetwork(String postData) { + String guid = (String) getRequestAttributes().get("network"); + NetworkDefinition network = new NetworkDefinition(); + network.guid = guid; + + try { + jsonToNetworkDefinition(postData, network); + } catch (IOException e) { + log.error("Could not parse JSON {}", e.getMessage()); + } + + IVirtualNetworkService vns = + (IVirtualNetworkService)getContext().getAttributes(). + get(IVirtualNetworkService.class.getCanonicalName()); + + Integer gw = null; + if (network.gateway != null) { + try { + gw = IPv4.toIPv4Address(network.gateway); + } catch (IllegalArgumentException e) { + log.warn("Could not parse gateway {} as IP for network {}", + network.name, network.gateway); + } + } + vns.createNetwork(network.guid, network.name, gw); + } + + @Delete + public void deleteNetwork() { + IVirtualNetworkService vns = + (IVirtualNetworkService)getContext().getAttributes(). + get(IVirtualNetworkService.class.getCanonicalName()); + String guid = (String) getRequestAttributes().get("network"); + vns.deleteNetwork(guid); + } +} diff --git a/src/main/java/net/floodlightcontroller/virtualnetwork/forwarding/VirtualNetworkFilter.java b/src/main/java/net/floodlightcontroller/virtualnetwork/forwarding/VirtualNetworkFilter.java index 403c56bac8436e59c12980f4a1905b0ac2df5652..94678900a23a6d8a64598b71094f928326f81017 100644 --- a/src/main/java/net/floodlightcontroller/virtualnetwork/forwarding/VirtualNetworkFilter.java +++ b/src/main/java/net/floodlightcontroller/virtualnetwork/forwarding/VirtualNetworkFilter.java @@ -3,9 +3,13 @@ package net.floodlightcontroller.virtualnetwork.forwarding; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.openflow.protocol.OFFlowMod; @@ -56,83 +60,140 @@ public class VirtualNetworkFilter // Our internal state protected Map<String, String> nameToGuid; // Logical name -> Network ID protected Map<String, Integer> guidToGateway; // Network ID -> Gateway IP - protected Map<Integer, String> gatewayToGuid; // Gateway IP -> Network ID + protected Map<Integer, Set<String>> gatewayToGuid; // Gateway IP -> Network ID protected Map<MACAddress, String> macToGuid; // Host MAC -> Network ID - + protected Map<String, MACAddress> portToMac; // Host MAC -> logical port name protected void addGateway(String guid, Integer ip) { if (ip.intValue() != 0) { guidToGateway.put(guid, ip); - gatewayToGuid.put(ip, guid); + if (gatewayToGuid.containsKey(ip)) { + Set<String> gSet = gatewayToGuid.get(ip); + gSet.add(guid); + } else { + Set<String> gSet = Collections.synchronizedSet(new HashSet<String>()); + gSet.add(guid); + gatewayToGuid.put(ip, gSet); + } } } protected void deleteGateway(String guid) { Integer gwIp = guidToGateway.remove(guid); if (gwIp == null) return; - gatewayToGuid.remove(gwIp); + Set<String> gSet = gatewayToGuid.get(gwIp); + gSet.remove(guid); } // IVirtualNetworkService @Override - public void createNetwork(String network, String guid, Integer gateway) { - if (log.isDebugEnabled()) + public void createNetwork(String guid, String network, Integer gateway) { + if (log.isDebugEnabled()) { + String gw = null; + try { + gw = IPv4.fromIPv4Address(gateway); + } catch (Exception e) { + // fail silently + } log.debug("Creating network {} with ID {} and gateway {}", - new Object[] {network, guid, IPv4.fromIPv4Address(gateway)}); - if (!nameToGuid.containsKey(network)) { - nameToGuid.put(network, guid); - addGateway(guid, gateway); - } else { - if (log.isDebugEnabled()) - log.debug("Network {} already exists, ignoring", network); + new Object[] {network, guid, gw}); + } + + if (!nameToGuid.isEmpty()) { + // We have to iterate all the networks to handle name/gateway changes + for (Entry<String, String> entry : nameToGuid.entrySet()) { + if (entry.getValue().equals(guid)) { + nameToGuid.remove(entry.getKey()); + break; + } + } } + nameToGuid.put(network, guid); + // If they don't specify a new gateway the old one will be preserved + if ((gateway != null) && (gateway != 0)) + addGateway(guid, gateway); } @Override - public void deleteNetwork(String name) { - if (log.isDebugEnabled()) log.debug("Deleting network name {}", name); - if (nameToGuid.containsKey(name)) { - String guid = nameToGuid.remove(name); - deleteGateway(guid); - Collection<MACAddress> deleteList = new ArrayList<MACAddress>(); - for (MACAddress host : macToGuid.keySet()) { - if (macToGuid.get(host).equals(guid)) { - deleteList.add(host); - } + public void deleteNetwork(String guid) { + String name = null; + if (nameToGuid.isEmpty()) { + log.warn("Could not delete network with ID {}, network doesn't exist", + guid); + return; + } + for (Entry<String, String> entry : nameToGuid.entrySet()) { + if (entry.getValue().equals(guid)) { + name = entry.getKey(); + break; + } + log.warn("Could not delete network with ID {}, network doesn't exist", + guid); + } + + if (log.isDebugEnabled()) + log.debug("Deleting network with name {} ID {}", name, guid); + + nameToGuid.remove(name); + deleteGateway(guid); + Collection<MACAddress> deleteList = new ArrayList<MACAddress>(); + for (MACAddress host : macToGuid.keySet()) { + if (macToGuid.get(host).equals(guid)) { + deleteList.add(host); } - for (MACAddress mac : deleteList) { - if (log.isDebugEnabled()) { - log.debug("Removing host {} from network {}", - HexString.toHexString(mac.toBytes()), guid); + } + for (MACAddress mac : deleteList) { + if (log.isDebugEnabled()) { + log.debug("Removing host {} from network {}", + HexString.toHexString(mac.toBytes()), guid); + } + macToGuid.remove(mac); + for (Entry<String, MACAddress> entry : portToMac.entrySet()) { + if (entry.getValue().equals(mac)) { + portToMac.remove(entry.getKey()); + break; } - macToGuid.remove(mac); } } } @Override - public void addHost(MACAddress mac, String network) { + public void addHost(MACAddress mac, String network, String port) { String guid = nameToGuid.get(network); if (guid != null) { if (log.isDebugEnabled()) { - log.debug("Adding {} to network {}", mac, network); + log.debug("Adding {} to network {} on port {}", + new Object[] {mac, network, port}); } - macToGuid.put(mac, guid); // TODO what if a mapping exists? + // We ignore old mappings + macToGuid.put(mac, guid); + portToMac.put(port, mac); } else { - // TODO - throw an exception + log.warn("Could not add MAC {} to network {} on port {}, the network does not exist", + new Object[] {mac, network, port}); } } @Override - public void deleteHost(MACAddress mac, String network) { - if (macToGuid.remove(mac) != null) { - if (log.isDebugEnabled()) { - log.debug("Removing {} from network {}", mac, network); + public void deleteHost(MACAddress mac, String port) { + if (log.isDebugEnabled()) { + log.debug("Removing host {} from port {}", mac, port); + } + if (mac == null && port == null) return; + if (port != null) { + MACAddress host = portToMac.remove(port); + macToGuid.remove(host); + } else if (mac != null) { + if (!portToMac.isEmpty()) { + for (Entry<String, MACAddress> entry : portToMac.entrySet()) { + if (entry.getValue().equals(mac)) { + portToMac.remove(entry.getKey()); + macToGuid.remove(entry.getValue()); + return; + } + } } - } else { - log.warn("Tried to remove {} from network {}, but no mapping was found", - mac, network); } } @@ -156,8 +217,7 @@ public class VirtualNetworkFilter } @Override - public Collection<Class<? extends IFloodlightService>> - getModuleDependencies() { + public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); l.add(IFloodlightProviderService.class); @@ -173,8 +233,9 @@ public class VirtualNetworkFilter nameToGuid = new ConcurrentHashMap<String, String>(); guidToGateway = new ConcurrentHashMap<String, Integer>(); - gatewayToGuid = new ConcurrentHashMap<Integer, String>(); + gatewayToGuid = new ConcurrentHashMap<Integer, Set<String>>(); macToGuid = new ConcurrentHashMap<MACAddress, String>(); + portToMac = new ConcurrentHashMap<String, MACAddress>(); } @Override @@ -192,8 +253,8 @@ public class VirtualNetworkFilter @Override public boolean isCallbackOrderingPrereq(OFType type, String name) { - // We don't care who goes before us - return false; + // Link discovery should go before us so we don't block LLDPs + return (type.equals(OFType.PACKET_IN) && name.equals("linkdiscovery")); } @Override @@ -228,30 +289,39 @@ public class VirtualNetworkFilter protected Command processPacketIn(IOFSwitch sw, OFPacketIn msg, FloodlightContext cntx) { Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD); - if (eth.isBroadcast() || eth.isMulticast()) { - // TODO - handle this shit somehow - return Command.CONTINUE; - } - Command ret = Command.STOP; String srcNetwork = macToGuid.get(eth.getSourceMAC()); - if (oneSameNetwork(eth.getSourceMAC(), eth.getDestinationMAC())) { - // if they are on the same network continue - ret = Command.CONTINUE; - } else if ((eth.getPayload() instanceof IPv4) - && isDefaultGatewayIp(srcNetwork, (IPv4)eth.getPayload())) { - // or if the host is talking to the gateway continue - ret = Command.CONTINUE; - } - - if (ret == Command.CONTINUE) { - if (log.isTraceEnabled()) { - log.trace("Allowing flow between {} and {} on network {}", - new Object[] {eth.getSourceMAC(), eth.getDestinationMAC(), srcNetwork}); + if (srcNetwork == null) { + log.debug("Blocking traffic from host {} because it is not attached to any network.", + HexString.toHexString(eth.getSourceMACAddress())); + ret = Command.STOP; + } else { + if (eth.isBroadcast() || eth.isMulticast()) { + return Command.CONTINUE; + } + + if (oneSameNetwork(eth.getSourceMAC(), eth.getDestinationMAC())) { + // if they are on the same network continue + ret = Command.CONTINUE; + } else if ((eth.getPayload() instanceof IPv4) + && isDefaultGatewayIp(srcNetwork, (IPv4)eth.getPayload())) { + // or if the host is talking to the gateway continue + ret = Command.CONTINUE; + } + + if (ret == Command.CONTINUE) { + if (log.isTraceEnabled()) { + log.trace("Allowing flow between {} and {} on network {}", + new Object[] {eth.getSourceMAC(), eth.getDestinationMAC(), srcNetwork}); + } + } else if (ret == Command.STOP) { + // they are on different virtual networks so we drop the flow + if (log.isTraceEnabled()) { + log.trace("Dropping flow between {} and {} because they are on different networks", + new Object[] {eth.getSourceMAC(), eth.getDestinationMAC()}); + } + doDropFlow(sw, msg, cntx); } - } else if (ret == Command.STOP) { - // they are on different virtual networks so we drop the flow - doDropFlow(sw, msg, cntx); } return ret; diff --git a/src/main/java/net/floodlightcontroller/virtualnetwork/forwarding/VirtualNetworkWebRoutable.java b/src/main/java/net/floodlightcontroller/virtualnetwork/forwarding/VirtualNetworkWebRoutable.java index aeef390cd08674a96a4f1f2fd4f5e6c70752e30d..157d2816c2c224faaceb805f92bec4a8f5a2815a 100644 --- a/src/main/java/net/floodlightcontroller/virtualnetwork/forwarding/VirtualNetworkWebRoutable.java +++ b/src/main/java/net/floodlightcontroller/virtualnetwork/forwarding/VirtualNetworkWebRoutable.java @@ -11,8 +11,8 @@ public class VirtualNetworkWebRoutable implements RestletRoutable { @Override public Restlet getRestlet(Context context) { Router router = new Router(context); - //router.attach("/tenants/{tenant}/networks", NetworkResource.java); - //router.attach("/"); + router.attach("/tenants/{tenant}/networks/{network}", NetworkResource.class); + router.attach("/tenants/{tenant}/networks/{network}/ports/{port}/attachment", HostResource.class); return router; } @@ -20,4 +20,4 @@ public class VirtualNetworkWebRoutable implements RestletRoutable { public String basePath() { return "/quantum/1.0"; } -} +} \ No newline at end of file diff --git a/src/test/java/net/floodlightcontroller/virtualnetwork/forwarding/VirtualNetworkFilterTest.java b/src/test/java/net/floodlightcontroller/virtualnetwork/forwarding/VirtualNetworkFilterTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f59183b7a21d6e5f2627359d409b4571d6945760 --- /dev/null +++ b/src/test/java/net/floodlightcontroller/virtualnetwork/forwarding/VirtualNetworkFilterTest.java @@ -0,0 +1,293 @@ +package net.floodlightcontroller.virtualnetwork.forwarding; + +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; + +import org.easymock.EasyMock; +import org.junit.Before; +import org.junit.Test; +import org.openflow.protocol.OFMatch; +import org.openflow.protocol.OFPacketIn; +import org.openflow.protocol.OFType; +import org.openflow.protocol.OFPacketIn.OFPacketInReason; + +import net.floodlightcontroller.core.FloodlightContext; +import net.floodlightcontroller.core.IFloodlightProviderService; +import net.floodlightcontroller.core.IOFMessageListener; +import net.floodlightcontroller.core.IOFMessageListener.Command; +import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.module.FloodlightModuleContext; +import net.floodlightcontroller.packet.Data; +import net.floodlightcontroller.packet.Ethernet; +import net.floodlightcontroller.packet.IPacket; +import net.floodlightcontroller.packet.IPv4; +import net.floodlightcontroller.packet.UDP; +import net.floodlightcontroller.restserver.IRestApiService; +import net.floodlightcontroller.restserver.RestApiServer; +import net.floodlightcontroller.test.FloodlightTestCase; +import net.floodlightcontroller.util.MACAddress; + +public class VirtualNetworkFilterTest extends FloodlightTestCase { + protected VirtualNetworkFilter vns; + + protected static String guid1 = "guid1"; + protected static String net1 = "net1"; + protected static String gw1 = "1.1.1.1"; + protected static String guid2 = "guid2"; + protected static String net2 = "net2"; + protected static String guid3 = "guid3"; + protected static String net3 = "net3"; + protected static String gw2 = "2.2.2.2"; + + protected static MACAddress mac1 = + new MACAddress(Ethernet.toMACAddress("00:11:22:33:44:55")); + protected static MACAddress mac2 = + new MACAddress(Ethernet.toMACAddress("00:11:22:33:44:66")); + protected static MACAddress mac3 = + new MACAddress(Ethernet.toMACAddress("00:11:22:33:44:77")); + protected static MACAddress mac4 = + new MACAddress(Ethernet.toMACAddress("00:11:22:33:44:88")); + protected static String hostPort1 = "port1"; + protected static String hostPort2 = "port2"; + protected static String hostPort3 = "port3"; + protected static String hostPort4 = "port4"; + + // For testing forwarding behavior + protected IOFSwitch sw1; + protected FloodlightContext cntx; + protected OFPacketIn mac1ToMac2PacketIn; + protected IPacket mac1ToMac2PacketIntestPacket; + protected byte[] mac1ToMac2PacketIntestPacketSerialized; + protected OFPacketIn mac1ToMac4PacketIn; + protected IPacket mac1ToMac4PacketIntestPacket; + protected byte[] mac1ToMac4PacketIntestPacketSerialized; + protected OFPacketIn mac1ToGwPacketIn; + protected IPacket mac1ToGwPacketIntestPacket; + protected byte[] mac1ToGwPacketIntestPacketSerialized; + + @Before + public void setUp() throws Exception { + super.setUp(); + + // Module loading stuff + FloodlightModuleContext fmc = new FloodlightModuleContext(); + RestApiServer restApi = new RestApiServer(); + vns = new VirtualNetworkFilter(); + fmc.addService(IRestApiService.class, restApi); + fmc.addService(IFloodlightProviderService.class, getMockFloodlightProvider()); + restApi.init(fmc); + getMockFloodlightProvider().init(fmc); + vns.init(fmc); + restApi.startUp(fmc); + getMockFloodlightProvider().startUp(fmc); + vns.startUp(fmc); + + // Mock switches + //fastWilcards mocked as this constant + int fastWildcards = + OFMatch.OFPFW_IN_PORT | + OFMatch.OFPFW_NW_PROTO | + OFMatch.OFPFW_TP_SRC | + OFMatch.OFPFW_TP_DST | + OFMatch.OFPFW_NW_SRC_ALL | + OFMatch.OFPFW_NW_DST_ALL | + OFMatch.OFPFW_NW_TOS; + sw1 = EasyMock.createNiceMock(IOFSwitch.class); + expect(sw1.getId()).andReturn(1L).anyTimes(); + expect(sw1.getAttribute(IOFSwitch.PROP_FASTWILDCARDS)).andReturn((Integer)fastWildcards).anyTimes(); + expect(sw1.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_TABLE)).andReturn(true).anyTimes(); + replay(sw1); + + // Mock packets + // Mock from MAC1 -> MAC2 + mac1ToMac2PacketIntestPacket = new Ethernet() + .setDestinationMACAddress(mac2.toBytes()) + .setSourceMACAddress(mac1.toBytes()) + .setEtherType(Ethernet.TYPE_IPv4) + .setPayload( + new IPv4() + .setTtl((byte) 128) + .setSourceAddress("192.168.1.1") + .setDestinationAddress("192.168.1.2") + .setPayload(new UDP() + .setSourcePort((short) 5000) + .setDestinationPort((short) 5001) + .setPayload(new Data(new byte[] {0x01})))); + mac1ToMac2PacketIntestPacketSerialized = mac1ToMac2PacketIntestPacket.serialize(); + mac1ToMac2PacketIn = + ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory(). + getMessage(OFType.PACKET_IN)) + .setBufferId(-1) + .setInPort((short) 1) + .setPacketData(mac1ToMac2PacketIntestPacketSerialized) + .setReason(OFPacketInReason.NO_MATCH) + .setTotalLength((short) mac1ToMac2PacketIntestPacketSerialized.length); + + // Mock from MAC1 -> MAC4 + mac1ToMac4PacketIntestPacket = new Ethernet() + .setDestinationMACAddress(mac4.toBytes()) + .setSourceMACAddress(mac1.toBytes()) + .setEtherType(Ethernet.TYPE_IPv4) + .setPayload( + new IPv4() + .setTtl((byte) 128) + .setSourceAddress("192.168.1.1") + .setDestinationAddress("192.168.1.2") + .setPayload(new UDP() + .setSourcePort((short) 5000) + .setDestinationPort((short) 5001) + .setPayload(new Data(new byte[] {0x01})))); + mac1ToMac4PacketIntestPacketSerialized = mac1ToMac4PacketIntestPacket.serialize(); + mac1ToMac4PacketIn = + ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory(). + getMessage(OFType.PACKET_IN)) + .setBufferId(-1) + .setInPort((short) 1) + .setPacketData(mac1ToMac4PacketIntestPacketSerialized) + .setReason(OFPacketInReason.NO_MATCH) + .setTotalLength((short) mac1ToMac4PacketIntestPacketSerialized.length); + + // Mock from MAC1 to gateway1 + mac1ToGwPacketIntestPacket = new Ethernet() + .setDestinationMACAddress("00:11:44:33:44:55") // mac shouldn't matter, can't be other host + .setSourceMACAddress(mac1.toBytes()) + .setEtherType(Ethernet.TYPE_IPv4) + .setPayload( + new IPv4() + .setTtl((byte) 128) + .setSourceAddress("192.168.1.1") + .setDestinationAddress(gw1) + .setPayload(new UDP() + .setSourcePort((short) 5000) + .setDestinationPort((short) 5001) + .setPayload(new Data(new byte[] {0x01})))); + mac1ToGwPacketIntestPacketSerialized = mac1ToGwPacketIntestPacket.serialize(); + mac1ToGwPacketIn = + ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory(). + getMessage(OFType.PACKET_IN)) + .setBufferId(-1) + .setInPort((short) 1) + .setPacketData(mac1ToGwPacketIntestPacketSerialized) + .setReason(OFPacketInReason.NO_MATCH) + .setTotalLength((short) mac1ToGwPacketIntestPacketSerialized.length); + } + + @Test + public void testCreateNetwork() { + // Test creating a network with all parameters + vns.createNetwork(guid1, net1, IPv4.toIPv4Address(gw1)); + assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid1)); + assertTrue(vns.nameToGuid.get(net1).equals(guid1)); + assertTrue(vns.guidToGateway.get(guid1).equals(IPv4.toIPv4Address(gw1))); + + // Test creating network without a gateway + vns.createNetwork(guid2, net2, null); + assertTrue(vns.nameToGuid.get(net2).equals(guid2)); + assertTrue(vns.guidToGateway.get(guid2) == null); + assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).size() == 1); + + // Test creating a network that shares the gateway with net1 + vns.createNetwork(guid3, net3, IPv4.toIPv4Address(gw1)); + assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid1)); + assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid3)); + assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).size() == 2); + assertTrue(vns.nameToGuid.get(net3).equals(guid3)); + assertTrue(vns.guidToGateway.get(guid3).equals(IPv4.toIPv4Address(gw1))); + } + + @Test + public void testModifyNetwork() { + // Create some networks + testCreateNetwork(); + // Modify net2 to add a gateway + vns.createNetwork(guid2, net2, IPv4.toIPv4Address(gw1)); + assertTrue(vns.nameToGuid.get(net2).equals(guid2)); + assertTrue(vns.guidToGateway.get(guid2).equals(IPv4.toIPv4Address(gw1))); + assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid1)); + assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid2)); + assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid3)); + assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).size() == 3); + // Modify net2 to change it's name + vns.createNetwork(guid2, "newnet2", null); + // Make sure the gateway is still there + assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid2)); + // make sure the new name mapping was learned + assertTrue(vns.nameToGuid.get("newnet2").equals(guid2)); + // and the old one was deleted + assertFalse(vns.nameToGuid.containsKey(net2)); + } + + @Test + public void testDeleteNetwork() { + testModifyNetwork(); + // Delete newnet2 + vns.deleteNetwork(guid2); + assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid1)); + assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).contains(guid3)); + assertTrue(vns.gatewayToGuid.get(IPv4.toIPv4Address(gw1)).size() == 2); + assertFalse(vns.nameToGuid.containsKey(net2)); + assertFalse(vns.guidToGateway.containsKey(net2)); + } + + @Test + public void testAddHost() { + testModifyNetwork(); + vns.addHost(mac1, net1, hostPort1); + assertTrue(vns.macToGuid.get(mac1).equals(guid1)); + assertTrue(vns.portToMac.get(hostPort1).equals(mac1)); + vns.addHost(mac2, net1, hostPort2); + assertTrue(vns.macToGuid.get(mac2).equals(guid1)); + assertTrue(vns.portToMac.get(hostPort2).equals(mac2)); + vns.addHost(mac3, net3, hostPort3); + vns.addHost(mac4, net3, hostPort4); + } + + @Test + public void testDeleteHost() { + testAddHost(); + vns.deleteHost(mac1, null); + assertFalse(vns.macToGuid.containsKey(mac1)); + assertFalse(vns.portToMac.containsKey(hostPort1)); + vns.deleteHost(null, hostPort2); + assertFalse(vns.macToGuid.containsKey(mac2)); + assertFalse(vns.portToMac.containsKey(hostPort2)); + vns.deleteHost(mac3, hostPort3); + assertFalse(vns.macToGuid.containsKey(mac3)); + assertFalse(vns.portToMac.containsKey(hostPort3)); + } + + @Test + public void testForwarding() { + testAddHost(); + // make sure mac1 can communicate with mac2 + IOFMessageListener listener = mockFloodlightProvider.getListeners(). + get(OFType.PACKET_IN).get(0); + cntx = new FloodlightContext(); + IFloodlightProviderService.bcStore.put(cntx, + IFloodlightProviderService.CONTEXT_PI_PAYLOAD, + (Ethernet)mac1ToMac2PacketIntestPacket); + Command ret = listener.receive(sw1, mac1ToMac2PacketIn, cntx); + assertTrue(ret == Command.CONTINUE); + //reset(sw1); + // make sure mac1 can't communicate with mac4 + cntx = new FloodlightContext(); + IFloodlightProviderService.bcStore.put(cntx, + IFloodlightProviderService.CONTEXT_PI_PAYLOAD, + (Ethernet)mac1ToMac4PacketIntestPacket); + ret = listener.receive(sw1, mac1ToMac4PacketIn, cntx); + assertTrue(ret == Command.STOP); + } + + @Test + public void testDefaultGateway() { + testAddHost(); + IOFMessageListener listener = mockFloodlightProvider.getListeners(). + get(OFType.PACKET_IN).get(0); + cntx = new FloodlightContext(); + IFloodlightProviderService.bcStore.put(cntx, + IFloodlightProviderService.CONTEXT_PI_PAYLOAD, + (Ethernet)mac1ToGwPacketIntestPacket); + Command ret = listener.receive(sw1, mac1ToGwPacketIn, cntx); + assertTrue(ret == Command.CONTINUE); + } +}