diff --git a/build.xml b/build.xml index cc45e43ca1cdcb87492d0bc0f0a0ffef9bb71dce..0f95be469b708e6b9763abb7c48783df3a302d98 100644 --- a/build.xml +++ b/build.xml @@ -67,6 +67,10 @@ <include name="jython-2.5.2.jar"/> <include name="libthrift-0.7.0.jar"/> <include name="guava-13.0.1.jar" /> + <include name="commons-logging-1.1.1.jar" /> + <include name="httpclient-4.2.2.jar" /> + <include name="httpcore-4.2.2.jar" /> + <include name="json-simple-1.1.1.jar" /> </patternset> <path id="classpath"> @@ -140,7 +144,7 @@ </pathconvert> <echo message="Running thrift generator on ${thrift.file.list}"/> <exec executable="thrift" dir="${basedir}" failonerror="true"> - <arg line="--strict -v --gen java -o ${thrift.out.dir}/.. ${thrift.file.list}"/> + <arg line="--strict -v --gen java -o ${thrift.out.dir}/.. '${thrift.file.list}'"/> </exec> <!-- Get rid of annoying warnings in thrift java: at annotations --> <echo message="Adding @SuppressWarning annotations"/> diff --git a/lib/commons-logging-1.1.1.jar b/lib/commons-logging-1.1.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..1deef144cb17ed2c11c6cdcdcb2d9530fa8d0b47 Binary files /dev/null and b/lib/commons-logging-1.1.1.jar differ diff --git a/lib/gen-java/net/floodlightcontroller/packetstreamer/thrift/OFMessageType.java b/lib/gen-java/net/floodlightcontroller/packetstreamer/thrift/OFMessageType.java index b9b2843485cfbb2e925eae132a5c4ff127853b32..ebbb6c166dd2a844acfe188dea42886f88372ff4 100644 --- a/lib/gen-java/net/floodlightcontroller/packetstreamer/thrift/OFMessageType.java +++ b/lib/gen-java/net/floodlightcontroller/packetstreamer/thrift/OFMessageType.java @@ -34,7 +34,9 @@ import org.apache.thrift.TEnum; STATS_REQUEST(16), STATS_REPLY(17), BARRIER_REQUEST(18), - BARRIER_REPLY(19); + BARRIER_REPLY(19), + QUEUE_GET_CONFIG_REQUEST(20), + QUEUE_GET_CONFIG_REPLY(21); private final int value; @@ -95,6 +97,10 @@ import org.apache.thrift.TEnum; return BARRIER_REQUEST; case 19: return BARRIER_REPLY; + case 20: + return QUEUE_GET_CONFIG_REQUEST; + case 21: + return QUEUE_GET_CONFIG_REPLY; default: return null; } diff --git a/lib/httpclient-4.2.2.jar b/lib/httpclient-4.2.2.jar new file mode 100644 index 0000000000000000000000000000000000000000..5f768c46407a6506d46766d1011f2fac342ec333 Binary files /dev/null and b/lib/httpclient-4.2.2.jar differ diff --git a/lib/httpcore-4.2.2.jar b/lib/httpcore-4.2.2.jar new file mode 100644 index 0000000000000000000000000000000000000000..a64cd2f5e3eff5ea0ff91a545ba2da4dc3fd612b Binary files /dev/null and b/lib/httpcore-4.2.2.jar differ diff --git a/lib/json-simple-1.1.1.jar b/lib/json-simple-1.1.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..66347a6c86b7d6442358ca7643e4dc484fb01866 Binary files /dev/null and b/lib/json-simple-1.1.1.jar differ diff --git a/setup-eclipse.sh b/setup-eclipse.sh index a39dc62d692e958b5747c35392e2851a150d306b..de92c3493d07eef70b89d8f721153bd134490fad 100755 --- a/setup-eclipse.sh +++ b/setup-eclipse.sh @@ -1,12 +1,11 @@ #!/bin/bash - -d=$(dirname $0) +d=$(dirname "$0") MAIN_CLASS=$1 LIBRARIES=$2 [ "${MAIN_CLASS}" ] || { echo "Run 'ant eclipse' to generate Eclipse project files"; exit 1; } -cat >$d/.project <<EOF +cat >"$d/.project" <<EOF <?xml version="1.0" encoding="UTF-8"?> <projectDescription> <name>floodlight</name> @@ -27,7 +26,7 @@ cat >$d/.project <<EOF EOF -cat >$d/.classpath <<EOF +cat >"$d/.classpath" <<EOF <?xml version="1.0" encoding="UTF-8"?> <classpath> <classpathentry kind="src" path="src/main/java" output="target/bin"/> @@ -43,7 +42,7 @@ cat >>$d/.classpath <<EOF EOF done ) -cat >>$d/.classpath <<EOF +cat >>"$d/.classpath" <<EOF <classpathentry exported="true" kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry kind="output" path="target/bin"/> </classpath> diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/ILoadBalancerService.java b/src/main/java/net/floodlightcontroller/loadbalancer/ILoadBalancerService.java new file mode 100644 index 0000000000000000000000000000000000000000..c88ee382a232f4113520227925a57a8f41d1ee07 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/loadbalancer/ILoadBalancerService.java @@ -0,0 +1,142 @@ +package net.floodlightcontroller.loadbalancer; + +import java.util.Collection; + +import net.floodlightcontroller.core.module.IFloodlightService; + +public interface ILoadBalancerService extends IFloodlightService { + + /** + * List all current Vips. + */ + public Collection<LBVip> listVips(); + + /** + * List selected Vip by its ID. + * @param vipId Id of requested Vip + */ + public Collection<LBVip> listVip(String vipId); + + /** + * Create and return a new Vip. + * @param LBVip vip: data structure with caller provided Vip attributes + * @return LBVip: Created Vip + */ + public LBVip createVip(LBVip vip); + + /** + * Update and return an existing Vip. + * @param LBVip vip: data structure with caller provided Vip attributes + * @return LBVip: Updated Vip + */ + public LBVip updateVip(LBVip vip); + + /** + * Remove an existing Vip. + * @param String vipId + * @return int: removal status + */ + public int removeVip(String vipId); + + /** + * List all current pools. + */ + public Collection<LBPool> listPools(); + + /** + * List selected pool by its ID. + * @param poolId Id of requested pool + */ + public Collection<LBPool> listPool(String poolId); + + /** + * Create and return a new pool. + * @param LBPool pool: data structure with caller provided pool attributes + * @return LBPool: Created pool + */ + public LBPool createPool(LBPool pool); + + /** + * Update and return an existing pool. + * @param LBPool pool: data structure with caller provided pool attributes + * @return LBPool: Updated pool + */ + public LBPool updatePool(LBPool pool); + + /** + * Remove an existing pool. + * @param String poolId + * @return int: removal status + */ + public int removePool(String poolId); + + /** + * List all current members. + */ + public Collection<LBMember> listMembers(); + + /** + * List selected member by its ID. + * @param memberId Id of requested member + */ + public Collection<LBMember> listMember(String memberId); + + /** + * List all members in a specified pool. + */ + public Collection<LBMember> listMembersByPool(String poolId); + + /** + * Create and return a new member. + * @param LBMember member: data structure with caller provided member attributes + * @return LBMember: Created member + */ + public LBMember createMember(LBMember member); + + /** + * Update and return an existing member. + * @param LBMember member: data structure with caller provided member attributes + * @return LBMember: Updated member + */ + public LBMember updateMember(LBMember member); + + /** + * Remove an existing member. + * @param String memberId + * @return int: removal status + */ + public int removeMember(String memberId); + + /** + * List all current monitors. + */ + public Collection<LBMonitor> listMonitors(); + + /** + * List selected monitor by its ID. + * @param monitorId Id of requested monitor + */ + public Collection<LBMonitor> listMonitor(String monitorId); + + /** + * Create and return a new monitor. + * @param LBMonitor monitor: data structure with caller provided monitor attributes + * @return LBMonitor: Created monitor + */ + public LBMonitor createMonitor(LBMonitor monitor); + + /** + * Update and return an existing monitor. + * @param LBMonitor monitor: data structure with caller provided pool attributes + * @return LBMonitor: Updated monitor + */ + public LBMonitor updateMonitor(LBMonitor monitor); + + /** + * Remove an existing monitor. + * @param String monitorId + * @return int: removal status + */ + public int removeMonitor(String monitorId); + +} diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LBMember.java b/src/main/java/net/floodlightcontroller/loadbalancer/LBMember.java new file mode 100644 index 0000000000000000000000000000000000000000..d1109dea262c7b111876353968dd7096b2039188 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/loadbalancer/LBMember.java @@ -0,0 +1,38 @@ +package net.floodlightcontroller.loadbalancer; + +import org.codehaus.jackson.map.annotate.JsonSerialize; + +/** + * Data structure for Load Balancer based on + * Quantum proposal http://wiki.openstack.org/LBaaS/CoreResourceModel/proposal + * + * @author KC Wang + */ + +@JsonSerialize(using=LBMemberSerializer.class) +public class LBMember { + protected String id; + protected int address; + protected short port; + protected String macString; + + protected int connectionLimit; + protected short adminState; + protected short status; + + protected String poolId; + protected String vipId; + + public LBMember() { + id = String.valueOf((int) (Math.random()*10000)); + address = 0; + macString = null; + port = 0; + + connectionLimit = 0; + adminState = 0; + status = 0; + poolId = null; + vipId = null; + } +} diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LBMemberSerializer.java b/src/main/java/net/floodlightcontroller/loadbalancer/LBMemberSerializer.java new file mode 100644 index 0000000000000000000000000000000000000000..a0726df2eb1b4a28b9d7fb817c281c1b65d5f64a --- /dev/null +++ b/src/main/java/net/floodlightcontroller/loadbalancer/LBMemberSerializer.java @@ -0,0 +1,27 @@ +package net.floodlightcontroller.loadbalancer; + +import java.io.IOException; + +import org.codehaus.jackson.JsonGenerator; +import org.codehaus.jackson.JsonProcessingException; +import org.codehaus.jackson.map.JsonSerializer; +import org.codehaus.jackson.map.SerializerProvider; + +public class LBMemberSerializer extends JsonSerializer<LBMember>{ + + @Override + public void serialize(LBMember member, JsonGenerator jGen, + SerializerProvider serializer) throws IOException, + JsonProcessingException { + jGen.writeStartObject(); + + jGen.writeStringField("id", member.id); + jGen.writeStringField("address", String.valueOf(member.address)); + jGen.writeStringField("port", Short.toString(member.port)); + jGen.writeStringField("poolId", member.poolId); + jGen.writeStringField("vipId", member.vipId); + + jGen.writeEndObject(); + } + +} diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LBMonitor.java b/src/main/java/net/floodlightcontroller/loadbalancer/LBMonitor.java new file mode 100644 index 0000000000000000000000000000000000000000..f246f962266554236bdeea9ef1bc486012159cdd --- /dev/null +++ b/src/main/java/net/floodlightcontroller/loadbalancer/LBMonitor.java @@ -0,0 +1,44 @@ +package net.floodlightcontroller.loadbalancer; + +/** + * Data structure for Load Balancer based on + * Quantum proposal http://wiki.openstack.org/LBaaS/CoreResourceModel/proposal + * + * @author KC Wang + */ + +public class LBMonitor { + protected String id; + protected String name; + protected short type; + protected short delay; + protected short timeout; + protected short attemptsBeforeDeactivation; + + protected String netId; + protected int address; + protected byte protocol; + protected short port; + + //protected path?? + + protected short adminState; + protected short status; + + public LBMonitor() { + id = null; + name = null; + type = 0; + delay = 0; + timeout = 0; + attemptsBeforeDeactivation = 0; + netId = null; + address = 0; + protocol = 0; + port = 0; + adminState = 0; + status = 0; + + } + +} diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LBPool.java b/src/main/java/net/floodlightcontroller/loadbalancer/LBPool.java new file mode 100644 index 0000000000000000000000000000000000000000..f628765ed2ccdc98b90572e0dd990029ffe4eee6 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/loadbalancer/LBPool.java @@ -0,0 +1,58 @@ +package net.floodlightcontroller.loadbalancer; + +import java.util.ArrayList; + +import org.codehaus.jackson.map.annotate.JsonSerialize; + +import net.floodlightcontroller.loadbalancer.LoadBalancer.IPClient; + +/** + * Data structure for Load Balancer based on + * Quantum proposal http://wiki.openstack.org/LBaaS/CoreResourceModel/proposal + * + * @author KC Wang + */ + + +@JsonSerialize(using=LBPoolSerializer.class) +public class LBPool { + protected String id; + protected String name; + protected String tenantId; + protected String netId; + protected short lbMethod; + protected byte protocol; + protected ArrayList<String> members; + protected ArrayList<String> monitors; + protected short adminState; + protected short status; + + protected String vipId; + + protected int previousMemberIndex; + + public LBPool() { + id = String.valueOf((int) (Math.random()*10000)); + name = null; + tenantId = null; + netId = null; + lbMethod = 0; + protocol = 0; + members = new ArrayList<String>(); + monitors = new ArrayList<String>(); + adminState = 0; + status = 0; + previousMemberIndex = -1; + } + + public String pickMember(IPClient client) { + // simple round robin for now; add different lbmethod later + if (members.size() > 0) { + previousMemberIndex = (previousMemberIndex + 1) % members.size(); + return members.get(previousMemberIndex); + } else { + return null; + } + } + +} diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LBPoolSerializer.java b/src/main/java/net/floodlightcontroller/loadbalancer/LBPoolSerializer.java new file mode 100644 index 0000000000000000000000000000000000000000..b637eb53b151409c326ea5b76eac78f7a1f959e3 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/loadbalancer/LBPoolSerializer.java @@ -0,0 +1,28 @@ +package net.floodlightcontroller.loadbalancer; + +import java.io.IOException; + +import org.codehaus.jackson.JsonGenerator; +import org.codehaus.jackson.JsonProcessingException; +import org.codehaus.jackson.map.JsonSerializer; +import org.codehaus.jackson.map.SerializerProvider; + +public class LBPoolSerializer extends JsonSerializer<LBPool>{ + + @Override + public void serialize(LBPool pool, JsonGenerator jGen, + SerializerProvider serializer) throws IOException, + JsonProcessingException { + jGen.writeStartObject(); + + jGen.writeStringField("name", pool.name); + jGen.writeStringField("id", pool.id); + jGen.writeStringField("vipId", pool.vipId); + + for (int i=0; i<pool.members.size(); i++) + jGen.writeStringField("pool", pool.members.get(i)); + + jGen.writeEndObject(); + } + +} diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LBStats.java b/src/main/java/net/floodlightcontroller/loadbalancer/LBStats.java new file mode 100644 index 0000000000000000000000000000000000000000..74e55d47df410f79046e6861dc8fd90f1d870b37 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/loadbalancer/LBStats.java @@ -0,0 +1,22 @@ +package net.floodlightcontroller.loadbalancer; + +/** + * Data structure for Load Balancer based on + * Quantum proposal http://wiki.openstack.org/LBaaS/CoreResourceModel/proposal + * + * @author KC Wang + */ + +public class LBStats { + protected int bytesIn; + protected int bytesOut; + protected int activeConnections; + protected int totalConnections; + + public LBStats() { + bytesIn = 0; + bytesOut = 0; + activeConnections = 0; + totalConnections = 0; + } +} diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LBVip.java b/src/main/java/net/floodlightcontroller/loadbalancer/LBVip.java new file mode 100644 index 0000000000000000000000000000000000000000..fd8c0bd451ab87c75bdecbcc94016126006bb945 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/loadbalancer/LBVip.java @@ -0,0 +1,61 @@ +package net.floodlightcontroller.loadbalancer; + +import java.util.ArrayList; + +import org.codehaus.jackson.map.annotate.JsonSerialize; + +import net.floodlightcontroller.loadbalancer.LoadBalancer.IPClient; +import net.floodlightcontroller.util.MACAddress; + +/** + * Data structure for Load Balancer based on + * Quantum proposal http://wiki.openstack.org/LBaaS/CoreResourceModel/proposal + * + * @author KC Wang + */ + +@JsonSerialize(using=LBVipSerializer.class) +public class LBVip { + protected String id; + protected String name; + protected String tenantId; + protected String netId; + protected int address; + protected byte protocol; + protected short lbMethod; + protected short port; + protected ArrayList<String> pools; + protected boolean sessionPersistence; + protected int connectionLimit; + protected short adminState; + protected short status; + + protected MACAddress proxyMac; + + public LBVip() { + this.id = String.valueOf((int) (Math.random()*10000)); + this.name = null; + this.tenantId = null; + this.netId = null; + this.address = 0; + this.protocol = 0; + this.lbMethod = 0; + this.port = 0; + this.pools = new ArrayList<String>(); + this.sessionPersistence = false; + this.connectionLimit = 0; + this.address = 0; + this.status = 0; + + this.proxyMac = MACAddress.valueOf("12:34:56:78:90:12"); + } + + public String pickPool(IPClient client) { + // for now, return the first pool; consider different pool choice policy later + if (pools.size() > 0) + return pools.get(0); + else + return null; + } + +} diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LBVipSerializer.java b/src/main/java/net/floodlightcontroller/loadbalancer/LBVipSerializer.java new file mode 100644 index 0000000000000000000000000000000000000000..acbecf614ce627efa89cd67b25871cdf8211d912 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/loadbalancer/LBVipSerializer.java @@ -0,0 +1,27 @@ +package net.floodlightcontroller.loadbalancer; + +import java.io.IOException; +import org.codehaus.jackson.JsonGenerator; +import org.codehaus.jackson.JsonProcessingException; +import org.codehaus.jackson.map.JsonSerializer; +import org.codehaus.jackson.map.SerializerProvider; + +public class LBVipSerializer extends JsonSerializer<LBVip>{ + + @Override + public void serialize(LBVip vip, JsonGenerator jGen, + SerializerProvider serializer) throws IOException, + JsonProcessingException { + jGen.writeStartObject(); + + jGen.writeStringField("name", vip.name); + jGen.writeStringField("id", vip.id); + jGen.writeStringField("address", String.valueOf(vip.address)); + jGen.writeStringField("protocol", Byte.toString(vip.protocol)); + jGen.writeStringField("port", Short.toString(vip.port)); + + jGen.writeEndObject(); + } + + +} diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LoadBalancer.java b/src/main/java/net/floodlightcontroller/loadbalancer/LoadBalancer.java new file mode 100644 index 0000000000000000000000000000000000000000..a9870db97a8502436bceb7ad523312b4bfeff248 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/loadbalancer/LoadBalancer.java @@ -0,0 +1,810 @@ +package net.floodlightcontroller.loadbalancer; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.message.BasicHeader; +import org.apache.http.protocol.HTTP; +import org.json.simple.JSONObject; +import org.openflow.protocol.OFMessage; +import org.openflow.protocol.OFPacketIn; +import org.openflow.protocol.OFPacketOut; +import org.openflow.protocol.OFPort; +import org.openflow.protocol.OFType; +import org.openflow.protocol.action.OFAction; +import org.openflow.protocol.action.OFActionOutput; +import org.openflow.util.HexString; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.floodlightcontroller.core.FloodlightContext; +import net.floodlightcontroller.core.IFloodlightProviderService; +import net.floodlightcontroller.core.IOFMessageListener; +import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.module.FloodlightModuleContext; +import net.floodlightcontroller.core.module.FloodlightModuleException; +import net.floodlightcontroller.core.module.IFloodlightModule; +import net.floodlightcontroller.core.module.IFloodlightService; +import net.floodlightcontroller.counter.ICounterStoreService; +import net.floodlightcontroller.devicemanager.IDevice; +import net.floodlightcontroller.devicemanager.IDeviceService; +import net.floodlightcontroller.devicemanager.SwitchPort; +import net.floodlightcontroller.packet.ARP; +import net.floodlightcontroller.packet.Ethernet; +import net.floodlightcontroller.packet.IPacket; +import net.floodlightcontroller.packet.IPv4; +import net.floodlightcontroller.packet.TCP; +import net.floodlightcontroller.packet.UDP; +import net.floodlightcontroller.restserver.IRestApiService; +import net.floodlightcontroller.routing.IRoutingService; +import net.floodlightcontroller.routing.Route; +import net.floodlightcontroller.topology.ITopologyService; +import net.floodlightcontroller.topology.NodePortTuple; +import net.floodlightcontroller.util.MACAddress; +import net.floodlightcontroller.util.OFMessageDamper; + +/** + * A simple load balancer module for ping, tcp, and udp flows. This module is accessed + * via a REST API defined close to the OpenStack Quantum LBaaS (Load-balancer-as-a-Service) + * v1.0 API proposal. Since the proposal has not been final, no efforts have yet been + * made to confirm compatibility at this time. + * + * Limitations: + * - client records and static flows not purged after use, will exhaust switch flow tables over time + * - round robin policy among servers based on connections, not traffic volume + * - health monitoring feature not implemented yet + * + * @author kcwang + */ +public class LoadBalancer implements IFloodlightModule, + ILoadBalancerService, IOFMessageListener { + + protected static Logger log = LoggerFactory.getLogger(LoadBalancer.class); + + // Our dependencies + protected IFloodlightProviderService floodlightProvider; + protected IRestApiService restApi; + + protected ICounterStoreService counterStore; + protected OFMessageDamper messageDamper; + protected IDeviceService deviceManager; + protected IRoutingService routingEngine; + protected ITopologyService topology; + + protected HashMap<String, LBVip> vips; + protected HashMap<String, LBPool> pools; + protected HashMap<String, LBMember> members; + protected HashMap<Integer, String> vipIpToId; + protected HashMap<Integer, MACAddress> vipIpToMac; + protected HashMap<Integer, String> memberIpToId; + protected HashMap<IPClient, LBMember> clientToMember; + + //Copied from Forwarding with message damper routine for pushing proxy Arp + protected static int OFMESSAGE_DAMPER_CAPACITY = 10000; // ms. + protected static int OFMESSAGE_DAMPER_TIMEOUT = 250; // ms + // Comparator for sorting by SwitchCluster + public Comparator<SwitchPort> clusterIdComparator = + new Comparator<SwitchPort>() { + @Override + public int compare(SwitchPort d1, SwitchPort d2) { + Long d1ClusterId = + topology.getL2DomainId(d1.getSwitchDPID()); + Long d2ClusterId = + topology.getL2DomainId(d2.getSwitchDPID()); + return d1ClusterId.compareTo(d2ClusterId); + } + }; + + // data structure for storing connected + public class IPClient { + int ipAddress; + byte nw_proto; + short srcPort; // tcp/udp src port. icmp type (OFMatch convention) + short targetPort; // tcp/udp dst port, icmp code (OFMatch convention) + + public IPClient() { + ipAddress = 0; + nw_proto = 0; + srcPort = -1; + targetPort = -1; + } + } + + @Override + public String getName() { + return "loadbalancer"; + } + + @Override + public boolean isCallbackOrderingPrereq(OFType type, String name) { + return (type.equals(OFType.PACKET_IN) && + (name.equals("topology") || + name.equals("devicemanager"))); + } + + @Override + public boolean isCallbackOrderingPostreq(OFType type, String name) { + return (type.equals(OFType.PACKET_IN) && name.equals("forwarding")); + } + + @Override + public net.floodlightcontroller.core.IListener.Command + receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) { + switch (msg.getType()) { + case PACKET_IN: + return processPacketIn(sw, (OFPacketIn)msg, cntx); + default: + break; + } + log.warn("Received unexpected message {}", msg); + return Command.CONTINUE; + } + + private net.floodlightcontroller.core.IListener.Command + processPacketIn(IOFSwitch sw, OFPacketIn pi, + FloodlightContext cntx) { + + Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, + IFloodlightProviderService.CONTEXT_PI_PAYLOAD); + IPacket pkt = eth.getPayload(); + + if (eth.isBroadcast() || eth.isMulticast()) { + // handle ARP for VIP + if (eth.getEtherType() == Ethernet.TYPE_ARP) { + // retrieve arp to determine target IP address + ARP arpRequest = (ARP) eth.getPayload(); + + int targetProtocolAddress = IPv4.toIPv4Address(arpRequest + .getTargetProtocolAddress()); + + if (vipIpToId.containsKey(targetProtocolAddress)) { + String vipId = vipIpToId.get(targetProtocolAddress); + vipProxyArpReply(sw, pi, cntx, vipId); + return Command.STOP; + } + } + } else { + // currently only load balance IPv4 packets - no-op for other traffic + if (pkt instanceof IPv4) { + IPv4 ip_pkt = (IPv4) pkt; + + // If match Vip and port, check pool and choose member + int destIpAddress = ip_pkt.getDestinationAddress(); + + if (vipIpToId.containsKey(destIpAddress)){ + IPClient client = new IPClient(); + client.ipAddress = ip_pkt.getSourceAddress(); + client.nw_proto = ip_pkt.getProtocol(); + if (client.nw_proto == IPv4.PROTOCOL_TCP) { + TCP tcp_pkt = (TCP) ip_pkt.getPayload(); + client.srcPort = tcp_pkt.getSourcePort(); + client.targetPort = tcp_pkt.getDestinationPort(); + } + if (client.nw_proto == IPv4.PROTOCOL_UDP) { + UDP udp_pkt = (UDP) ip_pkt.getPayload(); + client.srcPort = udp_pkt.getSourcePort(); + client.targetPort = udp_pkt.getDestinationPort(); + } + if (client.nw_proto == IPv4.PROTOCOL_ICMP) { + client.srcPort = 8; + client.targetPort = 0; + } + + LBVip vip = vips.get(vipIpToId.get(destIpAddress)); + LBPool pool = pools.get(vip.pickPool(client)); + LBMember member = members.get(pool.pickMember(client)); + + // for chosen member, check device manager and find and push routes, in both directions + pushBidirectionalVipRoutes(sw, pi, cntx, client, member); + + // packet out based on table rule + pushPacket(pkt, sw, pi.getBufferId(), pi.getInPort(), OFPort.OFPP_TABLE.getValue(), + cntx, true); + + return Command.STOP; + } + } + } + // bypass non-load-balanced traffic for normal processing (forwarding) + return Command.CONTINUE; + } + + /** + * used to send proxy Arp for load balanced service requests + * @param IOFSwitch sw + * @param OFPacketIn pi + * @param FloodlightContext cntx + * @param String vipId + */ + + protected void vipProxyArpReply(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx, String vipId) { + log.debug("vipProxyArpReply"); + + Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, + IFloodlightProviderService.CONTEXT_PI_PAYLOAD); + + // retrieve original arp to determine host configured gw IP address + ARP arpRequest = (ARP) eth.getPayload(); + + // have to do proxy arp reply since at this point we cannot determine the requesting application type + byte[] vipProxyMacBytes = vips.get(vipId).proxyMac.toBytes(); + + // generate proxy ARP reply + IPacket arpReply = new Ethernet() + .setSourceMACAddress(vipProxyMacBytes) + .setDestinationMACAddress(eth.getSourceMACAddress()) + .setEtherType(Ethernet.TYPE_ARP) + .setVlanID(eth.getVlanID()) + .setPriorityCode(eth.getPriorityCode()) + .setPayload( + new ARP() + .setHardwareType(ARP.HW_TYPE_ETHERNET) + .setProtocolType(ARP.PROTO_TYPE_IP) + .setHardwareAddressLength((byte) 6) + .setProtocolAddressLength((byte) 4) + .setOpCode(ARP.OP_REPLY) + .setSenderHardwareAddress(vipProxyMacBytes) + .setSenderProtocolAddress( + arpRequest.getTargetProtocolAddress()) + .setTargetHardwareAddress( + eth.getSourceMACAddress()) + .setTargetProtocolAddress( + arpRequest.getSenderProtocolAddress())); + + // push ARP reply out + pushPacket(arpReply, sw, OFPacketOut.BUFFER_ID_NONE, OFPort.OFPP_NONE.getValue(), + pi.getInPort(), cntx, true); + log.debug("proxy ARP reply pushed as {}", IPv4.fromIPv4Address(vips.get(vipId).address)); + + return; + } + + /** + * used to push any packet - borrowed routine from Forwarding + * + * @param OFPacketIn pi + * @param IOFSwitch sw + * @param int bufferId + * @param short inPort + * @param short outPort + * @param FloodlightContext cntx + * @param boolean flush + */ + public void pushPacket(IPacket packet, + IOFSwitch sw, + int bufferId, + short inPort, + short outPort, + FloodlightContext cntx, + boolean flush) { + if (log.isTraceEnabled()) { + log.trace("PacketOut srcSwitch={} inPort={} outPort={}", + new Object[] {sw, inPort, outPort}); + } + + OFPacketOut po = + (OFPacketOut) floodlightProvider.getOFMessageFactory() + .getMessage(OFType.PACKET_OUT); + + // set actions + List<OFAction> actions = new ArrayList<OFAction>(); + actions.add(new OFActionOutput(outPort, (short) 0xffff)); + + po.setActions(actions) + .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH); + short poLength = + (short) (po.getActionsLength() + OFPacketOut.MINIMUM_LENGTH); + + // set buffer_id, in_port + po.setBufferId(bufferId); + po.setInPort(inPort); + + // set data - only if buffer_id == -1 + if (po.getBufferId() == OFPacketOut.BUFFER_ID_NONE) { + if (packet == null) { + log.error("BufferId is not set and packet data is null. " + + "Cannot send packetOut. " + + "srcSwitch={} inPort={} outPort={}", + new Object[] {sw, inPort, outPort}); + return; + } + byte[] packetData = packet.serialize(); + poLength += packetData.length; + po.setPacketData(packetData); + } + + po.setLength(poLength); + + try { + counterStore.updatePktOutFMCounterStoreLocal(sw, po); + messageDamper.write(sw, po, cntx, flush); + } catch (IOException e) { + log.error("Failure writing packet out", e); + } + } + + /** + * used to find and push in-bound and out-bound routes using StaticFlowEntryPusher + * @param IOFSwitch sw + * @param OFPacketIn pi + * @param FloodlightContext cntx + * @param IPClient client + * @param LBMember member + */ + protected void pushBidirectionalVipRoutes(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx, IPClient client, LBMember member) { + + // borrowed code from Forwarding to retrieve src and dst device entities + // Check if we have the location of the destination + IDevice srcDevice = null; + IDevice dstDevice = null; + + // retrieve all known devices + Collection<? extends IDevice> allDevices = deviceManager + .getAllDevices(); + + for (IDevice d : allDevices) { + for (int j = 0; j < d.getIPv4Addresses().length; j++) { + if (srcDevice == null && client.ipAddress == d.getIPv4Addresses()[j]) + srcDevice = d; + if (dstDevice == null && member.address == d.getIPv4Addresses()[j]) { + dstDevice = d; + member.macString = dstDevice.getMACAddressString(); + } + if (srcDevice != null && dstDevice != null) + break; + } + } + + Long srcIsland = topology.getL2DomainId(sw.getId()); + + if (srcIsland == null) { + log.debug("No openflow island found for source {}/{}", + sw.getStringId(), pi.getInPort()); + return; + } + + // Validate that we have a destination known on the same island + // Validate that the source and destination are not on the same switchport + boolean on_same_island = false; + boolean on_same_if = false; + for (SwitchPort dstDap : dstDevice.getAttachmentPoints()) { + long dstSwDpid = dstDap.getSwitchDPID(); + Long dstIsland = topology.getL2DomainId(dstSwDpid); + if ((dstIsland != null) && dstIsland.equals(srcIsland)) { + on_same_island = true; + if ((sw.getId() == dstSwDpid) && + (pi.getInPort() == dstDap.getPort())) { + on_same_if = true; + } + break; + } + } + + if (!on_same_island) { + // Flood since we don't know the dst device + if (log.isTraceEnabled()) { + log.trace("No first hop island found for destination " + + "device {}, Action = flooding", dstDevice); + } + return; + } + + if (on_same_if) { + if (log.isTraceEnabled()) { + log.trace("Both source and destination are on the same " + + "switch/port {}/{}, Action = NOP", + sw.toString(), pi.getInPort()); + } + return; + } + + // Install all the routes where both src and dst have attachment + // points. Since the lists are stored in sorted order we can + // traverse the attachment points in O(m+n) time + SwitchPort[] srcDaps = srcDevice.getAttachmentPoints(); + Arrays.sort(srcDaps, clusterIdComparator); + SwitchPort[] dstDaps = dstDevice.getAttachmentPoints(); + Arrays.sort(dstDaps, clusterIdComparator); + + int iSrcDaps = 0, iDstDaps = 0; + + // following Forwarding's same routing routine, retrieve both in-bound and out-bound routes for + // all clusters. + while ((iSrcDaps < srcDaps.length) && (iDstDaps < dstDaps.length)) { + SwitchPort srcDap = srcDaps[iSrcDaps]; + SwitchPort dstDap = dstDaps[iDstDaps]; + Long srcCluster = + topology.getL2DomainId(srcDap.getSwitchDPID()); + Long dstCluster = + topology.getL2DomainId(dstDap.getSwitchDPID()); + + int srcVsDest = srcCluster.compareTo(dstCluster); + if (srcVsDest == 0) { + if (!srcDap.equals(dstDap) && + (srcCluster != null) && + (dstCluster != null)) { + Route routeIn = + routingEngine.getRoute(srcDap.getSwitchDPID(), + (short)srcDap.getPort(), + dstDap.getSwitchDPID(), + (short)dstDap.getPort()); + Route routeOut = + routingEngine.getRoute(dstDap.getSwitchDPID(), + (short)dstDap.getPort(), + srcDap.getSwitchDPID(), + (short)srcDap.getPort()); + + // use static flow entry pusher to push flow mod along in and out path + // in: match src client (ip, port), rewrite dest from vip ip/port to member ip/port, forward + // out: match dest client (ip, port), rewrite src from member ip/port to vip ip/port, forward + + if (routeIn != null) { + pushStaticVipRoute(true, routeIn, client, member, sw.getId()); + } + + if (routeOut != null) { + pushStaticVipRoute(false, routeOut, client, member, sw.getId()); + } + + } + iSrcDaps++; + iDstDaps++; + } else if (srcVsDest < 0) { + iSrcDaps++; + } else { + iDstDaps++; + } + } + return; + } + + /** + * used to push given route using static flow entry pusher + * @param boolean inBound + * @param Route route + * @param IPClient client + * @param LBMember member + * @param long pinSwitch + */ + @SuppressWarnings("unchecked") + public void pushStaticVipRoute(boolean inBound, Route route, IPClient client, LBMember member, long pinSwitch) { + List<NodePortTuple> path = route.getPath(); + if (path.size()>0) { + + for (int i = 0; i < path.size(); i+=2) { + JSONObject json = new JSONObject(); + long sw = path.get(i).getNodeId(); + + json.put("switch", HexString.toHexString(sw)); + if (inBound) { + json.put("name","inbound-vip-"+ member.vipId+"client-"+client.ipAddress+"-port-"+client.targetPort + +"srcswitch-"+path.get(0).getNodeId()+"sw-"+sw); + json.put("src-ip",IPv4.fromIPv4Address(client.ipAddress)); + json.put("protocol",String.valueOf(client.nw_proto)); + json.put("src-port",String.valueOf(client.srcPort & 0xffff)); + json.put("ether-type","0x800"); + json.put("priority","32768"); + json.put("ingress-port",String.valueOf(path.get(i).getPortId())); + json.put("active","true"); + if (sw == pinSwitch) { + json.put("actions","set-dst-ip="+IPv4.fromIPv4Address(member.address)+"," + + "set-dst-mac="+member.macString+"," + + "output="+path.get(i+1).getPortId()); + } else { + json.put("actions", + "output="+path.get(i+1).getPortId()); + } + } else { + json.put("name","outbound-vip-"+ member.vipId+"client-"+client.ipAddress+"-port-"+client.targetPort + +"srcswitch-"+path.get(0).getNodeId()+"sw-"+sw); + json.put("dst-ip",IPv4.fromIPv4Address(client.ipAddress)); + json.put("protocol",String.valueOf(client.nw_proto)); + json.put("dst-port",String.valueOf(client.srcPort & 0xffff)); + json.put("ether-type","0x800"); + json.put("priority","32768"); + json.put("ingress-port",String.valueOf(path.get(i).getPortId())); + json.put("active","true"); + if (sw == pinSwitch) { + json.put("actions","set-src-ip="+IPv4.fromIPv4Address(vips.get(member.vipId).address)+"," + + "set-src-mac="+vips.get(member.vipId).proxyMac.toString()+"," + + "output="+path.get(i+1).getPortId()); + } else { + json.put("actions", + "output="+path.get(i+1).getPortId()); + } + + } + try { + HttpClient httpclient = new DefaultHttpClient(); + HttpPost httpPost = new HttpPost("http://127.0.0.1:8080/wm/staticflowentrypusher/json"); + StringEntity se = new StringEntity(json.toString()); + se.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, "application/json")); + httpPost.setEntity(se); + + HttpResponse response = null; + + try { + response = httpclient.execute(httpPost); + } catch (ClientProtocolException cliente) { + cliente.printStackTrace(); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + + BufferedReader rd = null; + try { + if (response !=null) + rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); + } catch (IllegalStateException statee) { + statee.printStackTrace(); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + + String line = ""; + try { + if (rd!= null) { + while ((line = rd.readLine()) != null) { + System.out.println(line); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + } + + return; + } + + + @Override + public Collection<LBVip> listVips() { + return vips.values(); + } + + @Override + public Collection<LBVip> listVip(String vipId) { + Collection<LBVip> result = new HashSet<LBVip>(); + result.add(vips.get(vipId)); + return result; } + + @Override + public LBVip createVip(LBVip vip) { + if (vip == null) + vip = new LBVip(); + + vips.put(vip.id, vip); + vipIpToId.put(vip.address, vip.id); + vipIpToMac.put(vip.address, vip.proxyMac); + + return vip; + } + + @Override + public LBVip updateVip(LBVip vip) { + vips.put(vip.id, vip); + return vip; + } + + @Override + public int removeVip(String vipId) { + if(vips.containsKey(vipId)){ + vips.remove(vipId); + return 0; + } else { + return -1; + } + } + + @Override + public Collection<LBPool> listPools() { + return pools.values(); + } + + @Override + public Collection<LBPool> listPool(String poolId) { + Collection<LBPool> result = new HashSet<LBPool>(); + result.add(pools.get(poolId)); + return result; + } + + @Override + public LBPool createPool(LBPool pool) { + if (pool==null) + pool = new LBPool(); + + pools.put(pool.id, pool); + if (pool.vipId != null) + vips.get(pool.vipId).pools.add(pool.id); + else + log.error("pool must be specified with non-null vip-id"); + + return pool; + } + + @Override + public LBPool updatePool(LBPool pool) { + pools.put(pool.id, pool); + return null; + } + + @Override + public int removePool(String poolId) { + if(pools.containsKey(poolId)){ + pools.remove(poolId); + return 0; + } else { + return -1; + } + } + + @Override + public Collection<LBMember> listMembers() { + return members.values(); + } + + @Override + public Collection<LBMember> listMember(String memberId) { + Collection<LBMember> result = new HashSet<LBMember>(); + result.add(members.get(memberId)); + return result; + } + + @Override + public Collection<LBMember> listMembersByPool(String poolId) { + Collection<LBMember> result = new HashSet<LBMember>(); + + if(pools.containsKey(poolId)) { + ArrayList<String> memberIds = pools.get(poolId).members; + for (int i=0; i<memberIds.size(); i++) + result.add(members.get(memberIds.get(i))); + } + return result; + } + + @Override + public LBMember createMember(LBMember member) { + if (member == null) + member = new LBMember(); + + if (member.poolId != null && pools.get(member.poolId) != null) { + member.vipId = pools.get(member.poolId).vipId; + members.put(member.id, member); + pools.get(member.poolId).members.add(member.id); + memberIpToId.put(member.address, member.id); + } else + log.error("member must be specified with non-null pool_id"); + + return member; + } + + @Override + public LBMember updateMember(LBMember member) { + members.put(member.id, member); + return member; + } + + @Override + public int removeMember(String memberId) { + if(members.containsKey(memberId)){ + members.remove(memberId); + return 0; + } else { + return -1; + } + } + + @Override + public Collection<LBMonitor> listMonitors() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Collection<LBMonitor> listMonitor(String monitorId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public LBMonitor createMonitor(LBMonitor monitor) { + // TODO Auto-generated method stub + return null; + } + + @Override + public LBMonitor updateMonitor(LBMonitor monitor) { + // TODO Auto-generated method stub + return null; + } + + @Override + public int removeMonitor(String monitorId) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public Collection<Class<? extends IFloodlightService>> + getModuleServices() { + Collection<Class<? extends IFloodlightService>> l = + new ArrayList<Class<? extends IFloodlightService>>(); + l.add(ILoadBalancerService.class); + return l; + } + + @Override + public Map<Class<? extends IFloodlightService>, IFloodlightService> + getServiceImpls() { + Map<Class<? extends IFloodlightService>, IFloodlightService> m = + new HashMap<Class<? extends IFloodlightService>, + IFloodlightService>(); + m.put(ILoadBalancerService.class, this); + return m; + } + + @Override + public Collection<Class<? extends IFloodlightService>> + getModuleDependencies() { + Collection<Class<? extends IFloodlightService>> l = + new ArrayList<Class<? extends IFloodlightService>>(); + l.add(IFloodlightProviderService.class); + l.add(IRestApiService.class); + l.add(ICounterStoreService.class); + l.add(IDeviceService.class); + l.add(ITopologyService.class); + l.add(IRoutingService.class); + + return l; + } + + @Override + public void init(FloodlightModuleContext context) + throws FloodlightModuleException { + floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class); + restApi = context.getServiceImpl(IRestApiService.class); + counterStore = context.getServiceImpl(ICounterStoreService.class); + deviceManager = context.getServiceImpl(IDeviceService.class); + routingEngine = context.getServiceImpl(IRoutingService.class); + topology = context.getServiceImpl(ITopologyService.class); + + messageDamper = new OFMessageDamper(OFMESSAGE_DAMPER_CAPACITY, + EnumSet.of(OFType.FLOW_MOD), + OFMESSAGE_DAMPER_TIMEOUT); + + vips = new HashMap<String, LBVip>(); + pools = new HashMap<String, LBPool>(); + members = new HashMap<String, LBMember>(); + vipIpToId = new HashMap<Integer, String>(); + vipIpToMac = new HashMap<Integer, MACAddress>(); + memberIpToId = new HashMap<Integer, String>(); + } + + @Override + public void startUp(FloodlightModuleContext context) { + floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this); + restApi.addRestletRoutable(new LoadBalancerWebRoutable()); + } + +} diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LoadBalancerWebRoutable.java b/src/main/java/net/floodlightcontroller/loadbalancer/LoadBalancerWebRoutable.java new file mode 100644 index 0000000000000000000000000000000000000000..170346aa13529cd9032a834605a9cd1db130d410 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/loadbalancer/LoadBalancerWebRoutable.java @@ -0,0 +1,33 @@ +package net.floodlightcontroller.loadbalancer; + +import org.restlet.Context; +import org.restlet.Restlet; +import org.restlet.routing.Router; + +import net.floodlightcontroller.restserver.RestletRoutable; +import net.floodlightcontroller.virtualnetwork.NoOp; + +public class LoadBalancerWebRoutable implements RestletRoutable { + + @Override + public Restlet getRestlet(Context context) { + Router router = new Router(context); + router.attach("/vips/", VipsResource.class); // GET, POST + router.attach("/vips/{vip}", VipsResource.class); // GET, PUT, DELETE + router.attach("/pools/", PoolsResource.class); // GET, POST + router.attach("/pools/{pool}", PoolsResource.class); // GET, PUT, DELETE + router.attach("/members/", MembersResource.class); // GET, POST + router.attach("/members/{member}", MembersResource.class); // GET, PUT, DELETE + router.attach("/pools/{pool}/members", PoolMemberResource.class); //GET + router.attach("/health_monitors/", MonitorsResource.class); //GET, POST + router.attach("/health_monitors/{monitor}", MonitorsResource.class); //GET, PUT, DELETE + router.attachDefault(NoOp.class); + return router; + } + + @Override + public String basePath() { + return "/quantum/v1.0"; + } + +} diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/MembersResource.java b/src/main/java/net/floodlightcontroller/loadbalancer/MembersResource.java new file mode 100644 index 0000000000000000000000000000000000000000..0cd9a3d5668f8bb0a75ea34a54d08e1d5e3eec38 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/loadbalancer/MembersResource.java @@ -0,0 +1,133 @@ +package net.floodlightcontroller.loadbalancer; + +import java.io.IOException; +import java.util.Collection; + +import net.floodlightcontroller.packet.IPv4; + +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.Get; +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 MembersResource extends ServerResource { + + protected static Logger log = LoggerFactory.getLogger(MembersResource.class); + + @Get("json") + public Collection <LBMember> retrieve() { + ILoadBalancerService lbs = + (ILoadBalancerService)getContext().getAttributes(). + get(ILoadBalancerService.class.getCanonicalName()); + + String memberId = (String) getRequestAttributes().get("member"); + if (memberId!=null) + return lbs.listMember(memberId); + else + return lbs.listMembers(); + } + + @Put + @Post + public LBMember createMember(String postData) { + + LBMember member=null; + try { + member=jsonToMember(postData); + } catch (IOException e) { + log.error("Could not parse JSON {}", e.getMessage()); + } + + ILoadBalancerService lbs = + (ILoadBalancerService)getContext().getAttributes(). + get(ILoadBalancerService.class.getCanonicalName()); + + String memberId = (String) getRequestAttributes().get("member"); + if (memberId != null) + return lbs.updateMember(member); + else + return lbs.createMember(member); + } + + @Delete + public int removeMember() { + + String memberId = (String) getRequestAttributes().get("member"); + + ILoadBalancerService lbs = + (ILoadBalancerService)getContext().getAttributes(). + get(ILoadBalancerService.class.getCanonicalName()); + + return lbs.removeMember(memberId); + } + + protected LBMember jsonToMember(String json) throws IOException { + MappingJsonFactory f = new MappingJsonFactory(); + JsonParser jp; + LBMember member = new LBMember(); + + 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; + if (n.equals("id")) { + member.id = jp.getText(); + continue; + } else + if (n.equals("address")) { + member.address = IPv4.toIPv4Address(jp.getText()); + continue; + } else + if (n.equals("port")) { + member.port = Short.parseShort(jp.getText()); + continue; + } else + if (n.equals("connection_limit")) { + member.connectionLimit = Integer.parseInt(jp.getText()); + continue; + } else + if (n.equals("admin_state")) { + member.adminState = Short.parseShort(jp.getText()); + continue; + } else + if (n.equals("status")) { + member.status = Short.parseShort(jp.getText()); + continue; + } else + if (n.equals("pool_id")) { + member.poolId = jp.getText(); + continue; + } + + log.warn("Unrecognized field {} in " + + "parsing Members", + jp.getText()); + } + jp.close(); + + return member; + } +} diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/MonitorsResource.java b/src/main/java/net/floodlightcontroller/loadbalancer/MonitorsResource.java new file mode 100644 index 0000000000000000000000000000000000000000..a726325454dda0de57b98e1ed4f1d9c409eb81b6 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/loadbalancer/MonitorsResource.java @@ -0,0 +1,156 @@ +package net.floodlightcontroller.loadbalancer; + +import java.io.IOException; +import java.util.Collection; + +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.Get; +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 MonitorsResource extends ServerResource { + protected static Logger log = LoggerFactory.getLogger(MonitorsResource.class); + + @Get("json") + public Collection <LBMonitor> retrieve() { + ILoadBalancerService lbs = + (ILoadBalancerService)getContext().getAttributes(). + get(ILoadBalancerService.class.getCanonicalName()); + + String monitorId = (String) getRequestAttributes().get("monitor"); + if (monitorId!=null) + return lbs.listMonitor(monitorId); + else + return lbs.listMonitors(); + } + + @Put + @Post + public LBMonitor createMonitor(String postData) { + + LBMonitor monitor=null; + try { + monitor=jsonToMonitor(postData); + } catch (IOException e) { + log.error("Could not parse JSON {}", e.getMessage()); + } + + ILoadBalancerService lbs = + (ILoadBalancerService)getContext().getAttributes(). + get(ILoadBalancerService.class.getCanonicalName()); + + String monitorId = (String) getRequestAttributes().get("monitor"); + if (monitorId != null) + return lbs.updateMonitor(monitor); + else + return lbs.createMonitor(monitor); + } + + @Delete + public int removeMonitor() { + + String monitorId = (String) getRequestAttributes().get("monitor"); + + ILoadBalancerService lbs = + (ILoadBalancerService)getContext().getAttributes(). + get(ILoadBalancerService.class.getCanonicalName()); + + return lbs.removeMonitor(monitorId); + } + + protected LBMonitor jsonToMonitor(String json) throws IOException { + MappingJsonFactory f = new MappingJsonFactory(); + JsonParser jp; + LBMonitor monitor = new LBMonitor(); + + 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("monitor")) { + while (jp.nextToken() != JsonToken.END_OBJECT) { + String field = jp.getCurrentName(); + + if (field.equals("id")) { + monitor.id = jp.getText(); + continue; + } + if (field.equals("name")) { + monitor.name = jp.getText(); + continue; + } + if (field.equals("type")) { + monitor.type = Short.parseShort(jp.getText()); + continue; + } + if (field.equals("delay")) { + monitor.delay = Short.parseShort(jp.getText()); + continue; + } + if (field.equals("timeout")) { + monitor.timeout = Short.parseShort(jp.getText()); + continue; + } + if (field.equals("attempts_before_deactivation")) { + monitor.attemptsBeforeDeactivation = Short.parseShort(jp.getText()); + continue; + } + if (field.equals("network_id")) { + monitor.netId = jp.getText(); + continue; + } + if (field.equals("address")) { + monitor.address = Integer.parseInt(jp.getText()); + continue; + } + if (field.equals("protocol")) { + monitor.protocol = Byte.parseByte(jp.getText()); + continue; + } + if (field.equals("port")) { + monitor.port = Short.parseShort(jp.getText()); + continue; + } + if (field.equals("admin_state")) { + monitor.adminState = Short.parseShort(jp.getText()); + continue; + } + if (field.equals("status")) { + monitor.status = Short.parseShort(jp.getText()); + continue; + } + + log.warn("Unrecognized field {} in " + + "parsing Vips", + jp.getText()); + } + } + } + jp.close(); + + return monitor; + } +} diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/PoolMemberResource.java b/src/main/java/net/floodlightcontroller/loadbalancer/PoolMemberResource.java new file mode 100644 index 0000000000000000000000000000000000000000..21f0444122e574c3fa0a6e289c292ee66861a854 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/loadbalancer/PoolMemberResource.java @@ -0,0 +1,25 @@ +package net.floodlightcontroller.loadbalancer; + +import java.util.Collection; + +import org.restlet.resource.Get; +import org.restlet.resource.ServerResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PoolMemberResource extends ServerResource { + protected static Logger log = LoggerFactory.getLogger(PoolMemberResource.class); + + @Get("json") + public Collection <LBMember> retrieve() { + ILoadBalancerService lbs = + (ILoadBalancerService)getContext().getAttributes(). + get(ILoadBalancerService.class.getCanonicalName()); + + String poolId = (String) getRequestAttributes().get("pool"); + if (poolId!=null) + return lbs.listMembersByPool(poolId); + else + return null; + } +} diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/PoolsResource.java b/src/main/java/net/floodlightcontroller/loadbalancer/PoolsResource.java new file mode 100644 index 0000000000000000000000000000000000000000..9efb50c451bb7e976fdf18b114af7d90bb0bc88b --- /dev/null +++ b/src/main/java/net/floodlightcontroller/loadbalancer/PoolsResource.java @@ -0,0 +1,143 @@ +package net.floodlightcontroller.loadbalancer; + +import java.io.IOException; +import java.util.Collection; + +import net.floodlightcontroller.packet.IPv4; + +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.Get; +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 PoolsResource extends ServerResource { + + protected static Logger log = LoggerFactory.getLogger(PoolsResource.class); + + @Get("json") + public Collection <LBPool> retrieve() { + ILoadBalancerService lbs = + (ILoadBalancerService)getContext().getAttributes(). + get(ILoadBalancerService.class.getCanonicalName()); + + String poolId = (String) getRequestAttributes().get("pool"); + if (poolId!=null) + return lbs.listPool(poolId); + else + return lbs.listPools(); + } + + @Put + @Post + public LBPool createPool(String postData) { + + LBPool pool=null; + try { + pool=jsonToPool(postData); + } catch (IOException e) { + log.error("Could not parse JSON {}", e.getMessage()); + } + + ILoadBalancerService lbs = + (ILoadBalancerService)getContext().getAttributes(). + get(ILoadBalancerService.class.getCanonicalName()); + + String poolId = (String) getRequestAttributes().get("pool"); + if (poolId != null) + return lbs.updatePool(pool); + else + return lbs.createPool(pool); + } + + @Delete + public int removePool() { + + String poolId = (String) getRequestAttributes().get("pool"); + + ILoadBalancerService lbs = + (ILoadBalancerService)getContext().getAttributes(). + get(ILoadBalancerService.class.getCanonicalName()); + + return lbs.removePool(poolId); + } + + protected LBPool jsonToPool(String json) throws IOException { + if (json==null) return null; + + MappingJsonFactory f = new MappingJsonFactory(); + JsonParser jp; + LBPool pool = new LBPool(); + + 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; + if (n.equals("id")) { + pool.id = jp.getText(); + continue; + } + if (n.equals("tenant_id")) { + pool.tenantId = jp.getText(); + continue; + } + if (n.equals("name")) { + pool.name = jp.getText(); + continue; + } + if (n.equals("network_id")) { + pool.netId = jp.getText(); + continue; + } + if (n.equals("lb_method")) { + pool.lbMethod = Short.parseShort(jp.getText()); + continue; + } + if (n.equals("protocol")) { + String tmp = jp.getText(); + if (tmp.equalsIgnoreCase("TCP")) { + pool.protocol = IPv4.PROTOCOL_TCP; + } else if (tmp.equalsIgnoreCase("UDP")) { + pool.protocol = IPv4.PROTOCOL_UDP; + } else if (tmp.equalsIgnoreCase("ICMP")) { + pool.protocol = IPv4.PROTOCOL_ICMP; + } + continue; + } + if (n.equals("vip_id")) { + pool.vipId = jp.getText(); + continue; + } + + log.warn("Unrecognized field {} in " + + "parsing Pools", + jp.getText()); + } + jp.close(); + + return pool; + } + +} diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/VipsResource.java b/src/main/java/net/floodlightcontroller/loadbalancer/VipsResource.java new file mode 100644 index 0000000000000000000000000000000000000000..20bf882cdbc2d2f1c72954875a674902d97da1fd --- /dev/null +++ b/src/main/java/net/floodlightcontroller/loadbalancer/VipsResource.java @@ -0,0 +1,148 @@ +package net.floodlightcontroller.loadbalancer; + +import java.io.IOException; +import java.util.Collection; + +import net.floodlightcontroller.packet.IPv4; + +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.Get; +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 VipsResource extends ServerResource { + protected static Logger log = LoggerFactory.getLogger(VipsResource.class); + + @Get("json") + public Collection <LBVip> retrieve() { + ILoadBalancerService lbs = + (ILoadBalancerService)getContext().getAttributes(). + get(ILoadBalancerService.class.getCanonicalName()); + + String vipId = (String) getRequestAttributes().get("vip"); + if (vipId!=null) + return lbs.listVip(vipId); + else + return lbs.listVips(); + } + + @Put + @Post + public LBVip createVip(String postData) { + + LBVip vip=null; + try { + vip=jsonToVip(postData); + } catch (IOException e) { + log.error("Could not parse JSON {}", e.getMessage()); + } + + ILoadBalancerService lbs = + (ILoadBalancerService)getContext().getAttributes(). + get(ILoadBalancerService.class.getCanonicalName()); + + String vipId = (String) getRequestAttributes().get("vip"); + if (vipId != null) + return lbs.updateVip(vip); + else + return lbs.createVip(vip); + } + + @Delete + public int removeVip() { + + String vipId = (String) getRequestAttributes().get("vip"); + + ILoadBalancerService lbs = + (ILoadBalancerService)getContext().getAttributes(). + get(ILoadBalancerService.class.getCanonicalName()); + + return lbs.removeVip(vipId); + } + + protected LBVip jsonToVip(String json) throws IOException { + + if (json==null) return null; + + MappingJsonFactory f = new MappingJsonFactory(); + JsonParser jp; + LBVip vip = new LBVip(); + + 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; + + if (n.equals("id")) { + vip.id = jp.getText(); + continue; + } + if (n.equals("tenant_id")) { + vip.tenantId = jp.getText(); + continue; + } + if (n.equals("name")) { + vip.name = jp.getText(); + continue; + } + if (n.equals("network_id")) { + vip.netId = jp.getText(); + continue; + } + if (n.equals("protocol")) { + String tmp = jp.getText(); + if (tmp.equalsIgnoreCase("TCP")) { + vip.protocol = IPv4.PROTOCOL_TCP; + } else if (tmp.equalsIgnoreCase("UDP")) { + vip.protocol = IPv4.PROTOCOL_UDP; + } else if (tmp.equalsIgnoreCase("ICMP")) { + vip.protocol = IPv4.PROTOCOL_ICMP; + } + continue; + } + if (n.equals("address")) { + vip.address = IPv4.toIPv4Address(jp.getText()); + continue; + } + if (n.equals("port")) { + vip.port = Short.parseShort(jp.getText()); + continue; + } + if (n.equals("pool_id")) { + vip.pools.add(jp.getText()); + continue; + } + + log.warn("Unrecognized field {} in " + + "parsing Vips", + jp.getText()); + } + jp.close(); + + return vip; + } + +} diff --git a/src/main/java/org/openflow/protocol/OFPacketQueue.java b/src/main/java/org/openflow/protocol/OFPacketQueue.java new file mode 100644 index 0000000000000000000000000000000000000000..e8de1af56b950465c23cc140ad4991e0922aa34f --- /dev/null +++ b/src/main/java/org/openflow/protocol/OFPacketQueue.java @@ -0,0 +1,142 @@ +/** +* Copyright 2012, Andrew Ferguson, Brown University +* +* Licensed under the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. You may obtain +* a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +**/ + +package org.openflow.protocol; + +import java.util.ArrayList; +import java.util.List; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.openflow.util.U16; + +/** + * Represents ofp_packet_queue + * @author Andrew Ferguson (adf@cs.brown.edu) + */ +public class OFPacketQueue { + public static int MINIMUM_LENGTH = 8; + + protected int queueId; + protected short length; + protected List<OFQueueProp> properties = new ArrayList<OFQueueProp>(); + + public OFPacketQueue() { + this.queueId = -1; + this.length = U16.t(MINIMUM_LENGTH); + } + + public OFPacketQueue(int queueId) { + this.queueId = queueId; + this.length = U16.t(MINIMUM_LENGTH); + } + + /** + * @return the queueId + */ + public long getQueueId() { + return queueId; + } + + /** + * @param queueId the queueId to set + */ + public void setQueueId(int queueId) { + this.queueId = queueId; + } + + /** + * @return the queue's properties + */ + public List<OFQueueProp> getProperties() { + return properties; + } + + /** + * @param properties the properties to set + */ + public void setProperties(List<OFQueueProp> properties) { + this.properties = properties; + + this.length = U16.t(MINIMUM_LENGTH); + for (OFQueueProp prop : properties) { + this.length += prop.getLength(); + } + } + + /** + * @return the length + */ + public short getLength() { + return length; + } + + public void readFrom(ChannelBuffer data) { + this.queueId = data.readInt(); + this.length = data.readShort(); + data.readShort(); // pad + + int availLength = (this.length - MINIMUM_LENGTH); + this.properties.clear(); + + while (availLength > 0) { + OFQueueProp prop = new OFQueueProp(); + prop.readFrom(data); + properties.add(prop); + availLength -= prop.getLength(); + } + } + + public void writeTo(ChannelBuffer data) { + data.writeInt(queueId); + data.writeShort(length); + data.writeShort(0); // pad + + for (OFQueueProp prop : properties) { + prop.writeTo(data); + } + } + + @Override + public int hashCode() { + final int prime = 359; + int result = super.hashCode(); + result = prime * result + queueId; + result = prime * result + length; + result = prime * result + properties.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (!(obj instanceof OFPacketQueue)) { + return false; + } + OFPacketQueue other = (OFPacketQueue) obj; + if (queueId != other.queueId) { + return false; + } + if (! properties.equals(other.properties)) { + return false; + } + return true; + } +} diff --git a/src/main/java/org/openflow/protocol/OFQueueGetConfigReply.java b/src/main/java/org/openflow/protocol/OFQueueGetConfigReply.java new file mode 100644 index 0000000000000000000000000000000000000000..62be90d59f4106e8a6581dcf81bc5153cee83ce1 --- /dev/null +++ b/src/main/java/org/openflow/protocol/OFQueueGetConfigReply.java @@ -0,0 +1,125 @@ +/** +* Copyright 2012, Andrew Ferguson, Brown University +* +* Licensed under the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. You may obtain +* a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +**/ + +package org.openflow.protocol; + +import java.util.ArrayList; +import java.util.List; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.openflow.util.U16; + +/** + * Represents an ofp_queue_get_config_request message + * @author Andrew Ferguson (adf@cs.brown.edu) + */ +public class OFQueueGetConfigReply extends OFMessage { + public static int MINIMUM_LENGTH = 16; + + protected short portNumber; + protected List<OFPacketQueue> queues = new ArrayList<OFPacketQueue>(); + + public OFQueueGetConfigReply() { + super(); + this.type = OFType.QUEUE_GET_CONFIG_REPLY; + this.length = U16.t(MINIMUM_LENGTH); + } + + /** + * @return the portNumber + */ + public short getPortNumber() { + return portNumber; + } + + /** + * @param portNumber the portNumber to set + */ + public void setPortNumber(short portNumber) { + this.portNumber = portNumber; + } + + /** + * @return the port's queues + */ + public List<OFPacketQueue> getQueues() { + return queues; + } + + /** + * @param queues the queues to set + */ + public void setQueues(List<OFPacketQueue> queues) { + this.queues.clear(); + this.queues.addAll(queues); + } + + @Override + public void readFrom(ChannelBuffer data) { + super.readFrom(data); + this.portNumber = data.readShort(); + data.readInt(); // pad + data.readShort(); // pad + + int availLength = (this.length - MINIMUM_LENGTH); + this.queues.clear(); + + while (availLength > 0) { + OFPacketQueue queue = new OFPacketQueue(); + queue.readFrom(data); + queues.add(queue); + availLength -= queue.getLength(); + } + } + + @Override + public void writeTo(ChannelBuffer data) { + super.writeTo(data); + data.writeShort(this.portNumber); + data.writeInt(0); // pad + data.writeShort(0); // pad + + for (OFPacketQueue queue : queues) { + queue.writeTo(data); + } + } + + @Override + public int hashCode() { + final int prime = 349; + int result = super.hashCode(); + result = prime * result + portNumber; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (!(obj instanceof OFQueueGetConfigReply)) { + return false; + } + OFQueueGetConfigReply other = (OFQueueGetConfigReply) obj; + if (portNumber != other.portNumber) { + return false; + } + return true; + } +} diff --git a/src/main/java/org/openflow/protocol/OFQueueGetConfigRequest.java b/src/main/java/org/openflow/protocol/OFQueueGetConfigRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..cbb4a3734b9bc6510362afa237b6121398d9e4cd --- /dev/null +++ b/src/main/java/org/openflow/protocol/OFQueueGetConfigRequest.java @@ -0,0 +1,95 @@ +/** +* Copyright 2012, Andrew Ferguson, Brown University +* +* Licensed under the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. You may obtain +* a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +**/ + +package org.openflow.protocol; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.openflow.util.U16; + +/** + * Represents an ofp_queue_get_config_request message + * @author Andrew Ferguson (adf@cs.brown.edu) + */ +public class OFQueueGetConfigRequest extends OFMessage { + public static int MINIMUM_LENGTH = 12; + + protected short portNumber; + + public OFQueueGetConfigRequest(short portNumber) { + super(); + this.type = OFType.QUEUE_GET_CONFIG_REQUEST; + this.length = U16.t(MINIMUM_LENGTH); + this.portNumber = portNumber; + } + + public OFQueueGetConfigRequest() { + this((short) 0); + } + + /** + * @return the portNumber + */ + public short getPortNumber() { + return portNumber; + } + + /** + * @param portNumber the portNumber to set + */ + public void setPortNumber(short portNumber) { + this.portNumber = portNumber; + } + + @Override + public void readFrom(ChannelBuffer data) { + super.readFrom(data); + this.portNumber = data.readShort(); + data.readShort(); // pad + } + + @Override + public void writeTo(ChannelBuffer data) { + super.writeTo(data); + data.writeShort(this.portNumber); + data.writeShort(0); // pad + } + + @Override + public int hashCode() { + final int prime = 347; + int result = super.hashCode(); + result = prime * result + portNumber; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (!(obj instanceof OFQueueGetConfigRequest)) { + return false; + } + OFQueueGetConfigRequest other = (OFQueueGetConfigRequest) obj; + if (portNumber != other.portNumber) { + return false; + } + return true; + } +} diff --git a/src/main/java/org/openflow/protocol/OFQueueProp.java b/src/main/java/org/openflow/protocol/OFQueueProp.java new file mode 100644 index 0000000000000000000000000000000000000000..55c4d33e40f7b4cd4ed191f980fbf584d2e9000f --- /dev/null +++ b/src/main/java/org/openflow/protocol/OFQueueProp.java @@ -0,0 +1,166 @@ +/** +* Copyright 2012, Andrew Ferguson, Brown University +* +* Licensed under the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. You may obtain +* a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +**/ + +package org.openflow.protocol; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.openflow.util.U16; + +public class OFQueueProp { + private int NONE_MINIMUM_LENGTH = 8; + private int MIN_RATE_MINIMUM_LENGTH = 16; + + public enum OFQueuePropType { + OFPQT_NONE (0), + OFPQT_MIN_RATE (1); + + protected int value; + + private OFQueuePropType(int value) { + this.value = value; + } + + /** + * @return the value + */ + public int getValue() { + return value; + } + + public static OFQueuePropType fromShort(short x) { + switch (x) { + case 0: + return OFPQT_NONE; + case 1: + return OFPQT_MIN_RATE; + } + return null; + } + } + + protected OFQueuePropType type; + protected short length; + protected short rate = -1; // not valid unless type == OFPQT_MIN_RATE + + public OFQueueProp() { + this.type = OFQueuePropType.OFPQT_NONE; + this.length = U16.t(NONE_MINIMUM_LENGTH); + } + + /** + * @return the type + */ + public OFQueuePropType getType() { + return type; + } + + /** + * @param type the type to set + */ + public void setType(OFQueuePropType type) { + this.type = type; + + switch (type) { + case OFPQT_NONE: + this.length = U16.t(NONE_MINIMUM_LENGTH); + break; + case OFPQT_MIN_RATE: + this.length = U16.t(MIN_RATE_MINIMUM_LENGTH); + break; + } + } + + /** + * @return the rate + */ + public short getRate() { + return rate; + } + + /** + * @param rate the rate to set + */ + public void setRate(short rate) { + this.rate = rate; + } + + /** + * @return the length + */ + public short getLength() { + return length; + } + + public void readFrom(ChannelBuffer data) { + this.type = OFQueuePropType.fromShort(data.readShort()); + this.length = data.readShort(); + data.readInt(); // pad + + if (this.type == OFQueuePropType.OFPQT_MIN_RATE) { + assert(this.length == MIN_RATE_MINIMUM_LENGTH); + + this.rate = data.readShort(); + data.readInt(); // pad + data.readShort(); // pad + } else { + assert(this.length == NONE_MINIMUM_LENGTH); + } + } + + public void writeTo(ChannelBuffer data) { + data.writeShort(this.type.getValue()); + data.writeShort(this.length); + data.writeInt(0); // pad + + if (this.type == OFQueuePropType.OFPQT_MIN_RATE) { + data.writeShort(this.rate); + data.writeInt(0); // pad + data.writeShort(0); // pad + } + } + + @Override + public int hashCode() { + final int prime = 353; + int result = super.hashCode(); + result = prime * result + type.getValue(); + result = prime * result + rate; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (!(obj instanceof OFQueueProp)) { + return false; + } + OFQueueProp other = (OFQueueProp) obj; + if (type != other.type) { + return false; + } + if (type == OFQueuePropType.OFPQT_MIN_RATE) { + if (rate != other.rate) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/org/openflow/protocol/OFType.java b/src/main/java/org/openflow/protocol/OFType.java index c828f0a928ec4ae798a2445972ee9d0681ad0ca1..f1c81e2884b0f850efe420aa54c1fe810869442e 100644 --- a/src/main/java/org/openflow/protocol/OFType.java +++ b/src/main/java/org/openflow/protocol/OFType.java @@ -127,7 +127,17 @@ public enum OFType { @Override public OFMessage instantiate() { return new OFBarrierReply(); - }}); + }}), + QUEUE_GET_CONFIG_REQUEST (20, OFQueueGetConfigRequest.class, new Instantiable<OFMessage>() { + @Override + public OFMessage instantiate() { + return new OFQueueGetConfigRequest(); + }}), + QUEUE_GET_CONFIG_REPLY (21, OFQueueGetConfigReply.class, new Instantiable<OFMessage>() { + @Override + public OFMessage instantiate() { + return new OFQueueGetConfigReply(); + }}); static OFType[] mapping; diff --git a/src/main/java/org/openflow/vendor/openflow/OFOpenFlowVendorData.java b/src/main/java/org/openflow/vendor/openflow/OFOpenFlowVendorData.java new file mode 100644 index 0000000000000000000000000000000000000000..9be821f2ace4fb624a0a2314e0b997ec9d086f14 --- /dev/null +++ b/src/main/java/org/openflow/vendor/openflow/OFOpenFlowVendorData.java @@ -0,0 +1,98 @@ +/** +* Copyright 2012, Andrew Ferguson, Brown University +* +* Licensed under the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. You may obtain +* a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +**/ + +package org.openflow.vendor.openflow; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.openflow.protocol.vendor.OFVendorData; + +/** + * Base class for vendor data corresponding to extensions to OpenFlow 1.0. + * Based on org.openflow.vendor.nicira + * + * @author Andrew Ferguson (adf@cs.brown.edu) + */ +public class OFOpenFlowVendorData implements OFVendorData { + + public static final int OF_VENDOR_ID = 0x000026e1; + + /** + * The value of the integer data type at the beginning of the vendor data + */ + protected int dataType; + + /** + * Construct empty (i.e. unspecified data type) OpenFlow vendor data. + */ + public OFOpenFlowVendorData() { + } + + /** + * Construct OpenFlow vendor data with the specified data type + * @param dataType the data type value at the beginning of the vendor data. + */ + public OFOpenFlowVendorData(int dataType) { + this.dataType = dataType; + } + + /** + * Get the data type value at the beginning of the vendor data + * @return the integer data type value + */ + public int getDataType() { + return dataType; + } + + /** + * Set the data type value + * @param dataType the integer data type value at the beginning of the + * vendor data. + */ + public void setDataType(int dataType) { + this.dataType = dataType; + } + + /** + * Get the length of the vendor data. This implementation will normally + * be the superclass for another class that will override this to return + * the overall vendor data length. This implementation just returns the + * length of the part that includes the 4-byte integer data type value + * at the beginning of the vendor data. + */ + @Override + public int getLength() { + return 4; + } + + /** + * Read the vendor data from the ChannelBuffer + * @param data the channel buffer from which we're deserializing + * @param length the length to the end of the enclosing message + */ + @Override + public void readFrom(ChannelBuffer data, int length) { + dataType = data.readInt(); + } + + /** + * Write the vendor data to the ChannelBuffer + * @param data the channel buffer to which we're serializing + */ + @Override + public void writeTo(ChannelBuffer data) { + data.writeInt(dataType); + } +} diff --git a/src/main/java/org/openflow/vendor/openflow/OFOpenFlowVendorExtensions.java b/src/main/java/org/openflow/vendor/openflow/OFOpenFlowVendorExtensions.java new file mode 100644 index 0000000000000000000000000000000000000000..3fa10298cbc0ad4307368983cd157e51116e20ef --- /dev/null +++ b/src/main/java/org/openflow/vendor/openflow/OFOpenFlowVendorExtensions.java @@ -0,0 +1,47 @@ +/** +* Copyright 2012, Andrew Ferguson, Brown University +* +* Licensed under the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. You may obtain +* a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +**/ + +package org.openflow.vendor.openflow; + +import org.openflow.protocol.vendor.OFBasicVendorDataType; +import org.openflow.protocol.vendor.OFBasicVendorId; +import org.openflow.protocol.vendor.OFVendorId; + +public class OFOpenFlowVendorExtensions { + private static boolean initialized = false; + + public static synchronized void initialize() { + if (initialized) + return; + + // Configure openflowj to be able to parse the OpenFlow extensions. + OFBasicVendorId openflowVendorId = + new OFBasicVendorId(OFOpenFlowVendorData.OF_VENDOR_ID, 4); + OFVendorId.registerVendorId(openflowVendorId); + + OFBasicVendorDataType queueModifyVendorData = + new OFBasicVendorDataType(OFQueueModifyVendorData.OFP_EXT_QUEUE_MODIFY, + OFQueueModifyVendorData.getInstantiable()); + openflowVendorId.registerVendorDataType(queueModifyVendorData); + + OFBasicVendorDataType queueDeleteVendorData = + new OFBasicVendorDataType(OFQueueDeleteVendorData.OFP_EXT_QUEUE_DELETE, + OFQueueModifyVendorData.getInstantiable()); + openflowVendorId.registerVendorDataType(queueDeleteVendorData); + + initialized = true; + } +} diff --git a/src/main/java/org/openflow/vendor/openflow/OFQueueDeleteVendorData.java b/src/main/java/org/openflow/vendor/openflow/OFQueueDeleteVendorData.java new file mode 100644 index 0000000000000000000000000000000000000000..4fc52bacb337733d19647ab89f3ee04f553ef018 --- /dev/null +++ b/src/main/java/org/openflow/vendor/openflow/OFQueueDeleteVendorData.java @@ -0,0 +1,52 @@ +/** +* Copyright 2012, Andrew Ferguson, Brown University +* +* Licensed under the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. You may obtain +* a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +**/ + +package org.openflow.vendor.openflow; + +import org.openflow.protocol.Instantiable; +import org.openflow.protocol.vendor.OFVendorData; + +/** + * Class that represents the vendor data in the queue delete request + * + * @author Andrew Ferguson (adf@cs.brown.edu) + */ +public class OFQueueDeleteVendorData extends OFQueueVendorData { + + protected static Instantiable<OFVendorData> instantiable = + new Instantiable<OFVendorData>() { + public OFVendorData instantiate() { + return new OFQueueDeleteVendorData(); + } + }; + + /** + * @return a subclass of Instantiable<OFVendorData> that instantiates + * an instance of OFQueueDeleteVendorData. + */ + public static Instantiable<OFVendorData> getInstantiable() { + return instantiable; + } + + /** + * The data type value for a queue delete request + */ + public static final int OFP_EXT_QUEUE_DELETE = 1; + + public OFQueueDeleteVendorData() { + super(OFP_EXT_QUEUE_DELETE); + } +} diff --git a/src/main/java/org/openflow/vendor/openflow/OFQueueModifyVendorData.java b/src/main/java/org/openflow/vendor/openflow/OFQueueModifyVendorData.java new file mode 100644 index 0000000000000000000000000000000000000000..0d2f31b524c22f2e1152735eb5db26d8ae210914 --- /dev/null +++ b/src/main/java/org/openflow/vendor/openflow/OFQueueModifyVendorData.java @@ -0,0 +1,52 @@ +/** +* Copyright 2012, Andrew Ferguson, Brown University +* +* Licensed under the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. You may obtain +* a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +**/ + +package org.openflow.vendor.openflow; + +import org.openflow.protocol.Instantiable; +import org.openflow.protocol.vendor.OFVendorData; + +/** + * Class that represents the vendor data in the queue modify request + * + * @author Andrew Ferguson (adf@cs.brown.edu) + */ +public class OFQueueModifyVendorData extends OFQueueVendorData { + + protected static Instantiable<OFVendorData> instantiable = + new Instantiable<OFVendorData>() { + public OFVendorData instantiate() { + return new OFQueueModifyVendorData(); + } + }; + + /** + * @return a subclass of Instantiable<OFVendorData> that instantiates + * an instance of OFQueueModifyVendorData. + */ + public static Instantiable<OFVendorData> getInstantiable() { + return instantiable; + } + + /** + * The data type value for a queue modify request + */ + public static final int OFP_EXT_QUEUE_MODIFY = 0; + + public OFQueueModifyVendorData() { + super(OFP_EXT_QUEUE_MODIFY); + } +} diff --git a/src/main/java/org/openflow/vendor/openflow/OFQueueVendorData.java b/src/main/java/org/openflow/vendor/openflow/OFQueueVendorData.java new file mode 100644 index 0000000000000000000000000000000000000000..eeae9aace6436f42de2c26151aba1f125e26d83b --- /dev/null +++ b/src/main/java/org/openflow/vendor/openflow/OFQueueVendorData.java @@ -0,0 +1,119 @@ +/** +* Copyright 2012, Andrew Ferguson, Brown University +* +* Licensed under the Apache License, Version 2.0 (the "License"); you may +* not use this file except in compliance with the License. You may obtain +* a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations +* under the License. +**/ + +package org.openflow.vendor.openflow; + +import java.util.ArrayList; +import java.util.List; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.openflow.protocol.OFPacketQueue; + +/** + * Class that represents the vendor data in a queue modify or delete request + * + * @author Andrew Ferguson (adf@cs.brown.edu) + */ +public class OFQueueVendorData extends OFOpenFlowVendorData { + public static int MINIMUM_LENGTH = 8; + + protected short portNumber; + protected List<OFPacketQueue> queues = new ArrayList<OFPacketQueue>(); + + public OFQueueVendorData(int dataType) { + super(dataType); + } + + /** + * @return the portNumber + */ + public short getPortNumber() { + return portNumber; + } + + /** + * @param port the port on which the queue is + */ + public void setPortNumber(short portNumber) { + this.portNumber = portNumber; + } + + + /** + * @return the queues + */ + public List<OFPacketQueue> getQueues() { + return queues; + } + + /** + * @param queues the queues to modify or delete + */ + public void setQueues(List<OFPacketQueue> queues) { + this.queues = queues; + } + + /** + * @return the total length of the queue modify or delete msg + */ + @Override + public int getLength() { + int queuesLength = 0; + + for (OFPacketQueue queue : queues) { + queuesLength += queue.getLength(); + } + + return super.getLength() + MINIMUM_LENGTH + queuesLength; + } + + /** + * Read the queue message data from the ChannelBuffer + * @param data the channel buffer from which we're deserializing + * @param length the length to the end of the enclosing message + */ + public void readFrom(ChannelBuffer data, int length) { + super.readFrom(data, length); + portNumber = data.readShort(); + data.readInt(); // pad + data.readShort(); // pad + + int availLength = (length - MINIMUM_LENGTH); + this.queues.clear(); + + while (availLength > 0) { + OFPacketQueue queue = new OFPacketQueue(); + queue.readFrom(data); + queues.add(queue); + availLength -= queue.getLength(); + } + } + + /** + * Write the queue message data to the ChannelBuffer + * @param data the channel buffer to which we're serializing + */ + public void writeTo(ChannelBuffer data) { + super.writeTo(data); + data.writeShort(this.portNumber); + data.writeInt(0); // pad + data.writeShort(0); // pad + + for (OFPacketQueue queue : queues) { + queue.writeTo(data); + } + } +} diff --git a/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule b/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule index 07985a1268a9a66755c1745fc9696c9272c290ec..935fe105c9f6a40b0415d21ef18816bc084042bf 100644 --- a/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule +++ b/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule @@ -23,4 +23,5 @@ net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier net.floodlightcontroller.devicemanager.test.MockDeviceManager net.floodlightcontroller.core.test.MockFloodlightProvider net.floodlightcontroller.core.test.MockThreadPoolService -net.floodlightcontroller.firewall.Firewall \ No newline at end of file +net.floodlightcontroller.firewall.Firewall +net.floodlightcontroller.loadbalancer.LoadBalancer \ No newline at end of file diff --git a/src/main/resources/floodlightdefault.properties b/src/main/resources/floodlightdefault.properties index 5bb2e28832f44fd7c4db3984002c102f71bc7933..30dfdcaaa06d8e73d98a80a0fc9cb43b827ef347 100644 --- a/src/main/resources/floodlightdefault.properties +++ b/src/main/resources/floodlightdefault.properties @@ -13,7 +13,8 @@ net.floodlightcontroller.flowcache.FlowReconcileManager, \ net.floodlightcontroller.jython.JythonDebugInterface,\ net.floodlightcontroller.counter.CounterStore,\ net.floodlightcontroller.perfmon.PktInProcessingTime,\ -net.floodlightcontroller.ui.web.StaticWebRoutable +net.floodlightcontroller.ui.web.StaticWebRoutable,\ +net.floodlightcontroller.loadbalancer.LoadBalancer net.floodlightcontroller.restserver.RestApiServer.port = 8080 net.floodlightcontroller.core.FloodlightProvider.openflowport = 6633 net.floodlightcontroller.jython.JythonDebugInterface.port = 6655 diff --git a/src/main/thrift/packetstreamer.thrift b/src/main/thrift/packetstreamer.thrift index 827dd8525d1a9a22f0a218911e05d4669cafe2e4..ca683014219af978cafbba7bd1a579be89415298 100644 --- a/src/main/thrift/packetstreamer.thrift +++ b/src/main/thrift/packetstreamer.thrift @@ -38,6 +38,8 @@ enum OFMessageType { STATS_REPLY = 17, BARRIER_REQUEST = 18, BARRIER_REPLY = 19, + QUEUE_GET_CONFIG_REQUEST = 20, + QUEUE_GET_CONFIG_REPLY = 21, } /**