diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/ILoadBalancerService.java b/src/main/java/net/floodlightcontroller/loadbalancer/ILoadBalancerService.java index a335bf4daf1459b7de2764c475ad045121fd761f..0ef66f58f5bf2712bc8daad234df84bb9f2f1186 100644 --- a/src/main/java/net/floodlightcontroller/loadbalancer/ILoadBalancerService.java +++ b/src/main/java/net/floodlightcontroller/loadbalancer/ILoadBalancerService.java @@ -154,5 +154,21 @@ public interface ILoadBalancerService extends IFloodlightService { * @return int: removal status */ public int removeMonitor(String monitorId); + + /** + * Set member weight for WRR algorithm. + * @param String memberId: the Id of the member + * @param String weight: the weight to use in the WRR + * @return int: removal status + */ + public int setMemberWeight(String memberId, String weight); + /** + * Set member to prioritize for WRR algorithm. + * @param String memberId: the Id of the member + * @param String poolId: the id of the pool + * @return int: operation status + */ + public int setPriorityToMember(String poolId, String memberId); + } diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LBMember.java b/src/main/java/net/floodlightcontroller/loadbalancer/LBMember.java index 40569b478bd171f212d969b6e61cf9d1da3c5c12..0d2aff0039cf1ab2b9ec16679acb5e68f45ef7af 100644 --- a/src/main/java/net/floodlightcontroller/loadbalancer/LBMember.java +++ b/src/main/java/net/floodlightcontroller/loadbalancer/LBMember.java @@ -38,6 +38,7 @@ public class LBMember { protected String poolId; protected String vipId; + protected short weight; public LBMember() { id = String.valueOf((int) (Math.random()*10000)); @@ -50,5 +51,6 @@ public class LBMember { status = 0; poolId = null; vipId = null; + weight = 1; } } diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LBMemberSerializer.java b/src/main/java/net/floodlightcontroller/loadbalancer/LBMemberSerializer.java index f77e8d30c38e1707241f2e54ee75558fbe19ba24..36a326e6420a9a49f8046b5016d134ad60d4f8cb 100644 --- a/src/main/java/net/floodlightcontroller/loadbalancer/LBMemberSerializer.java +++ b/src/main/java/net/floodlightcontroller/loadbalancer/LBMemberSerializer.java @@ -36,6 +36,7 @@ public class LBMemberSerializer extends JsonSerializer<LBMember>{ jGen.writeStringField("port", Short.toString(member.port)); jGen.writeStringField("poolId", member.poolId); jGen.writeStringField("vipId", member.vipId); + jGen.writeStringField("weight", Short.toString(member.weight)); jGen.writeEndObject(); } diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LBPool.java b/src/main/java/net/floodlightcontroller/loadbalancer/LBPool.java index 1f842aea7f12c7492eb5298e6665829817c7ce6f..2259a64d50c3e1c56ebaa1aafc66f293da0464bc 100644 --- a/src/main/java/net/floodlightcontroller/loadbalancer/LBPool.java +++ b/src/main/java/net/floodlightcontroller/loadbalancer/LBPool.java @@ -16,7 +16,16 @@ package net.floodlightcontroller.loadbalancer; + import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Random; + +import org.projectfloodlight.openflow.types.U64; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.annotation.JsonSerialize; @@ -32,43 +41,86 @@ import net.floodlightcontroller.loadbalancer.LoadBalancer.IPClient; @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; - } - } + protected static Logger log = LoggerFactory.getLogger(LBPool.class); + 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 final static short ROUND_ROBIN = 1; + protected final static short STATISTICS = 2; + protected final static short WEIGHTED_RR = 3; + + 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, HashMap<String,U64> membersBandwidth,HashMap<String,Short> membersWeight) { + + // Get the members that belong to this pool and the statistics for them + if(members.size() > 0){ + if (lbMethod == STATISTICS && !membersBandwidth.isEmpty() && membersBandwidth.values() !=null) { + ArrayList<String> poolMembersId = new ArrayList<String>(); + for(String memberId: membersBandwidth.keySet()){ + for(int i=0;i<members.size();i++){ + if(members.get(i).equals(memberId)){ + poolMembersId.add(memberId); + } + } + } + // return the member which has the minimum bandwidth usage, out of this pool members + if(!poolMembersId.isEmpty()){ + ArrayList<U64> bandwidthValues = new ArrayList<U64>(); + + for(int j=0;j<poolMembersId.size();j++){ + bandwidthValues.add(membersBandwidth.get(poolMembersId.get(j))); + } + log.debug("Member picked using LB statistics: {}", poolMembersId.get(bandwidthValues.indexOf(Collections.min(bandwidthValues)))); + return poolMembersId.get(bandwidthValues.indexOf(Collections.min(bandwidthValues))); + } + return null; + } else if(lbMethod == WEIGHTED_RR && !membersWeight.isEmpty()){ + Random randomNumb = new Random(); + short totalWeight = 0; + for(Short weight: membersWeight.values()){ + totalWeight += weight; + } + int rand = randomNumb.nextInt(totalWeight); + short val = 0; + for(String memberId: membersWeight.keySet()){ + val += membersWeight.get(memberId); + if(val > rand){ + log.debug("Member picked using WRR: {}",memberId); + return memberId; + } + } + return null; + }else { + // simple round robin + previousMemberIndex = (previousMemberIndex + 1) % members.size(); + return members.get(previousMemberIndex); + } + } + return null; + } } diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LoadBalancer.java b/src/main/java/net/floodlightcontroller/loadbalancer/LoadBalancer.java index de0dbc10c24278460216dbf458988ba284e2a676..fef0276326de333fd77934a141c87eb046075f70 100644 --- a/src/main/java/net/floodlightcontroller/loadbalancer/LoadBalancer.java +++ b/src/main/java/net/floodlightcontroller/loadbalancer/LoadBalancer.java @@ -16,6 +16,7 @@ package net.floodlightcontroller.loadbalancer; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -74,6 +75,8 @@ import net.floodlightcontroller.restserver.IRestApiService; import net.floodlightcontroller.routing.IRoutingService; import net.floodlightcontroller.routing.Path; import net.floodlightcontroller.staticentry.IStaticEntryPusherService; +import net.floodlightcontroller.statistics.IStatisticsService; +import net.floodlightcontroller.statistics.SwitchPortBandwidth; import net.floodlightcontroller.topology.ITopologyService; import net.floodlightcontroller.util.FlowModUtils; import net.floodlightcontroller.util.OFMessageUtils; @@ -93,758 +96,851 @@ import net.floodlightcontroller.util.OFMessageUtils; * @edited Ryan Izard, rizard@g.clemson.edu, ryan.izard@bigswitch.com */ public class LoadBalancer implements IFloodlightModule, - ILoadBalancerService, IOFMessageListener { - - protected static Logger log = LoggerFactory.getLogger(LoadBalancer.class); - - // Our dependencies - protected IFloodlightProviderService floodlightProviderService; - protected IRestApiService restApiService; - - protected IDebugCounterService debugCounterService; - private IDebugCounter counterPacketOut; - protected IDeviceService deviceManagerService; - protected IRoutingService routingEngineService; - protected ITopologyService topologyService; - protected IStaticEntryPusherService sfpService; - protected IOFSwitchService switchService; - - 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 - protected static String LB_ETHER_TYPE = "0x800"; - protected static int LB_PRIORITY = 32768; - - // Comparator for sorting by SwitchCluster - public Comparator<SwitchPort> clusterIdComparator = - new Comparator<SwitchPort>() { - @Override - public int compare(SwitchPort d1, SwitchPort d2) { - DatapathId d1ClusterId = topologyService.getClusterId(d1.getNodeId()); - DatapathId d2ClusterId = topologyService.getClusterId(d2.getNodeId()); - return d1ClusterId.compareTo(d2ClusterId); - } - }; - - // data structure for storing connected - public class IPClient { - IPv4Address ipAddress; - IpProtocol nw_proto; - TransportPort srcPort; // tcp/udp src port. icmp type (OFMatch convention) - TransportPort targetPort; // tcp/udp dst port, icmp code (OFMatch convention) - - public IPClient() { - ipAddress = IPv4Address.NONE; - nw_proto = IpProtocol.NONE; - srcPort = TransportPort.NONE; - targetPort = TransportPort.NONE; - } - } - - @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") || - name.equals("virtualizer"))); - } - - @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 (pkt instanceof ARP) { - // retrieve arp to determine target IP address - ARP arpRequest = (ARP) eth.getPayload(); - - IPv4Address targetProtocolAddress = arpRequest.getTargetProtocolAddress(); - - if (vipIpToId.containsKey(targetProtocolAddress.getInt())) { - String vipId = vipIpToId.get(targetProtocolAddress.getInt()); - 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().getInt(); - - if (vipIpToId.containsKey(destIpAddress)){ - IPClient client = new IPClient(); - client.ipAddress = ip_pkt.getSourceAddress(); - client.nw_proto = ip_pkt.getProtocol(); - if (ip_pkt.getPayload() instanceof TCP) { - TCP tcp_pkt = (TCP) ip_pkt.getPayload(); - client.srcPort = tcp_pkt.getSourcePort(); - client.targetPort = tcp_pkt.getDestinationPort(); - } - if (ip_pkt.getPayload() instanceof UDP) { - UDP udp_pkt = (UDP) ip_pkt.getPayload(); - client.srcPort = udp_pkt.getSourcePort(); - client.targetPort = udp_pkt.getDestinationPort(); - } - if (ip_pkt.getPayload() instanceof ICMP) { - client.srcPort = TransportPort.of(8); - client.targetPort = TransportPort.of(0); - } - - LBVip vip = vips.get(vipIpToId.get(destIpAddress)); - if (vip == null) // fix dereference violations - return Command.CONTINUE; - LBPool pool = pools.get(vip.pickPool(client)); - if (pool == null) // fix dereference violations - return Command.CONTINUE; - LBMember member = members.get(pool.pickMember(client)); - if(member == null) //fix dereference violations - return Command.CONTINUE; - - // 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.getVersion().compareTo(OFVersion.OF_12) < 0) ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT), OFPort.TABLE, - 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 - if (! (eth.getPayload() instanceof ARP)) - return; - ARP arpRequest = (ARP) eth.getPayload(); - - // have to do proxy arp reply since at this point we cannot determine the requesting application type - - // generate proxy ARP reply - IPacket arpReply = new Ethernet() - .setSourceMACAddress(vips.get(vipId).proxyMac) - .setDestinationMACAddress(eth.getSourceMACAddress()) - .setEtherType(EthType.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(vips.get(vipId).proxyMac) - .setSenderProtocolAddress(arpRequest.getTargetProtocolAddress()) - .setTargetHardwareAddress(eth.getSourceMACAddress()) - .setTargetProtocolAddress(arpRequest.getSenderProtocolAddress())); - - // push ARP reply out - pushPacket(arpReply, sw, OFBufferId.NO_BUFFER, OFPort.ANY, (pi.getVersion().compareTo(OFVersion.OF_12) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT)), 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, - OFBufferId bufferId, - OFPort inPort, - OFPort outPort, - FloodlightContext cntx, - boolean flush) { - if (log.isTraceEnabled()) { - log.trace("PacketOut srcSwitch={} inPort={} outPort={}", - new Object[] {sw, inPort, outPort}); - } - - OFPacketOut.Builder pob = sw.getOFFactory().buildPacketOut(); - - // set actions - List<OFAction> actions = new ArrayList<OFAction>(); - actions.add(sw.getOFFactory().actions().buildOutput().setPort(outPort).setMaxLen(Integer.MAX_VALUE).build()); - - pob.setActions(actions); - - // set buffer_id, in_port - pob.setBufferId(bufferId); - pob.setInPort(inPort); - - // set data - only if buffer_id == -1 - if (pob.getBufferId() == OFBufferId.NO_BUFFER) { - 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(); - pob.setData(packetData); - } - - counterPacketOut.increment(); - sw.write(pob.build()); - } - - /** - * 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 = deviceManagerService.getAllDevices(); - - for (IDevice d : allDevices) { - for (int j = 0; j < d.getIPv4Addresses().length; j++) { - if (srcDevice == null && client.ipAddress.equals(d.getIPv4Addresses()[j])) - srcDevice = d; - if (dstDevice == null && member.address == d.getIPv4Addresses()[j].getInt()) { - dstDevice = d; - member.macString = dstDevice.getMACAddressString(); - } - if (srcDevice != null && dstDevice != null) - break; - } - } - - // srcDevice and/or dstDevice is null, no route can be pushed - if (srcDevice == null || dstDevice == null) return; - - DatapathId srcIsland = topologyService.getClusterId(sw.getId()); - - if (srcIsland == null) { - log.debug("No openflow island found for source {}/{}", - sw.getId().toString(), 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()) { - DatapathId dstSwDpid = dstDap.getNodeId(); - DatapathId dstIsland = topologyService.getClusterId(dstSwDpid); - if ((dstIsland != null) && dstIsland.equals(srcIsland)) { - on_same_island = true; - if ((sw.getId().equals(dstSwDpid)) && OFMessageUtils.getInPort(pi).equals(dstDap.getPortId())) { - 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]; - DatapathId srcCluster = - topologyService.getClusterId(srcDap.getNodeId()); - DatapathId dstCluster = - topologyService.getClusterId(dstDap.getNodeId()); - - int srcVsDest = srcCluster.compareTo(dstCluster); - if (srcVsDest == 0) { - if (!srcDap.equals(dstDap) && - (srcCluster != null) && - (dstCluster != null)) { - Path routeIn = - routingEngineService.getPath(srcDap.getNodeId(), - srcDap.getPortId(), - dstDap.getNodeId(), - dstDap.getPortId()); - Path routeOut = - routingEngineService.getPath(dstDap.getNodeId(), - dstDap.getPortId(), - srcDap.getNodeId(), - srcDap.getPortId()); - - // 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.getPath().isEmpty()) { - pushStaticVipRoute(true, routeIn, client, member, sw); - } - - if (! routeOut.getPath().isEmpty()) { - pushStaticVipRoute(false, routeOut, client, member, sw); - } - - } - iSrcDaps++; - iDstDaps++; - } else if (srcVsDest < 0) { - iSrcDaps++; - } else { - iDstDaps++; - } - } - return; - } - - /** - * used to push given route using static flow entry pusher - * @param boolean inBound - * @param Path route - * @param IPClient client - * @param LBMember member - * @param long pinSwitch - */ - public void pushStaticVipRoute(boolean inBound, Path route, IPClient client, LBMember member, IOFSwitch pinSwitch) { - List<NodePortTuple> path = route.getPath(); - if (path.size() > 0) { - for (int i = 0; i < path.size(); i+=2) { - DatapathId sw = path.get(i).getNodeId(); - String entryName; - Match.Builder mb = pinSwitch.getOFFactory().buildMatch(); - ArrayList<OFAction> actions = new ArrayList<OFAction>(); - - OFFlowMod.Builder fmb = pinSwitch.getOFFactory().buildFlowAdd(); - - fmb.setIdleTimeout(FlowModUtils.INFINITE_TIMEOUT); - fmb.setHardTimeout(FlowModUtils.INFINITE_TIMEOUT); - fmb.setBufferId(OFBufferId.NO_BUFFER); - fmb.setOutPort(OFPort.ANY); - fmb.setCookie(U64.of(0)); - fmb.setPriority(FlowModUtils.PRIORITY_MAX); - - - if (inBound) { - entryName = "inbound-vip-"+ member.vipId+"-client-"+client.ipAddress - +"-srcport-"+client.srcPort+"-dstport-"+client.targetPort - +"-srcswitch-"+path.get(0).getNodeId()+"-sw-"+sw; - mb.setExact(MatchField.ETH_TYPE, EthType.IPv4) - .setExact(MatchField.IP_PROTO, client.nw_proto) - .setExact(MatchField.IPV4_SRC, client.ipAddress) - .setExact(MatchField.IN_PORT, path.get(i).getPortId()); - if (client.nw_proto.equals(IpProtocol.TCP)) { - mb.setExact(MatchField.TCP_SRC, client.srcPort); - } else if (client.nw_proto.equals(IpProtocol.UDP)) { - mb.setExact(MatchField.UDP_SRC, client.srcPort); - } else if (client.nw_proto.equals(IpProtocol.SCTP)) { - mb.setExact(MatchField.SCTP_SRC, client.srcPort); - } else if (client.nw_proto.equals(IpProtocol.ICMP)) { - /* no-op */ - } else { - log.error("Unknown IpProtocol {} detected during inbound static VIP route push.", client.nw_proto); - } - - - if (sw.equals(pinSwitch.getId())) { - if (pinSwitch.getOFFactory().getVersion().compareTo(OFVersion.OF_12) < 0) { - actions.add(pinSwitch.getOFFactory().actions().setDlDst(MacAddress.of(member.macString))); - actions.add(pinSwitch.getOFFactory().actions().setNwDst(IPv4Address.of(member.address))); - actions.add(pinSwitch.getOFFactory().actions().output(path.get(i+1).getPortId(), Integer.MAX_VALUE)); - } else { // OXM introduced in OF1.2 - actions.add(pinSwitch.getOFFactory().actions().setField(pinSwitch.getOFFactory().oxms().ethDst(MacAddress.of(member.macString)))); - actions.add(pinSwitch.getOFFactory().actions().setField(pinSwitch.getOFFactory().oxms().ipv4Dst(IPv4Address.of(member.address)))); - actions.add(pinSwitch.getOFFactory().actions().output(path.get(i+1).getPortId(), Integer.MAX_VALUE)); - } - } else { - //fix concurrency errors - try{ - actions.add(switchService.getSwitch(path.get(i+1).getNodeId()).getOFFactory().actions().output(path.get(i+1).getPortId(), Integer.MAX_VALUE)); - } - catch(NullPointerException e){ - log.error("Fail to install loadbalancer flow rules to offline switch {}.", path.get(i+1).getNodeId()); - } - } - } else { - entryName = "outbound-vip-"+ member.vipId+"-client-"+client.ipAddress - +"-srcport-"+client.srcPort+"-dstport-"+client.targetPort - +"-srcswitch-"+path.get(0).getNodeId()+"-sw-"+sw; - mb.setExact(MatchField.ETH_TYPE, EthType.IPv4) - .setExact(MatchField.IP_PROTO, client.nw_proto) - .setExact(MatchField.IPV4_DST, client.ipAddress) - .setExact(MatchField.IN_PORT, path.get(i).getPortId()); - if (client.nw_proto.equals(IpProtocol.TCP)) { - mb.setExact(MatchField.TCP_DST, client.srcPort); - } else if (client.nw_proto.equals(IpProtocol.UDP)) { - mb.setExact(MatchField.UDP_DST, client.srcPort); - } else if (client.nw_proto.equals(IpProtocol.SCTP)) { - mb.setExact(MatchField.SCTP_DST, client.srcPort); - } else if (client.nw_proto.equals(IpProtocol.ICMP)) { - /* no-op */ - } else { - log.error("Unknown IpProtocol {} detected during outbound static VIP route push.", client.nw_proto); - } - - if (sw.equals(pinSwitch.getId())) { - if (pinSwitch.getOFFactory().getVersion().compareTo(OFVersion.OF_12) < 0) { - actions.add(pinSwitch.getOFFactory().actions().setDlSrc(vips.get(member.vipId).proxyMac)); - actions.add(pinSwitch.getOFFactory().actions().setNwSrc(IPv4Address.of(vips.get(member.vipId).address))); - actions.add(pinSwitch.getOFFactory().actions().output(path.get(i+1).getPortId(), Integer.MAX_VALUE)); - } else { // OXM introduced in OF1.2 - actions.add(pinSwitch.getOFFactory().actions().setField(pinSwitch.getOFFactory().oxms().ethSrc(vips.get(member.vipId).proxyMac))); - actions.add(pinSwitch.getOFFactory().actions().setField(pinSwitch.getOFFactory().oxms().ipv4Src(IPv4Address.of(vips.get(member.vipId).address)))); - actions.add(pinSwitch.getOFFactory().actions().output(path.get(i+1).getPortId(), Integer.MAX_VALUE)); - } - } else { - //fix concurrency errors - try{ - actions.add(switchService.getSwitch(path.get(i+1).getNodeId()).getOFFactory().actions().output(path.get(i+1).getPortId(), Integer.MAX_VALUE)); - } - catch(NullPointerException e){ - log.error("Fail to install loadbalancer flow rules to offline switches {}.", path.get(i+1).getNodeId()); - } - } - - } - - - fmb.setActions(actions); - fmb.setPriority(U16.t(LB_PRIORITY)); - fmb.setMatch(mb.build()); - sfpService.addFlow(entryName, fmb.build(), sw); - } - } - 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.containsKey(pool.vipId)) - vips.get(pool.vipId).pools.add(pool.id); - else { - log.error("specified vip-id must exist"); - pool.vipId = null; - pools.put(pool.id, pool); - } - return pool; - } - - @Override - public LBPool updatePool(LBPool pool) { - pools.put(pool.id, pool); - return null; - } - - @Override - public int removePool(String poolId) { - LBPool pool; - if (pools != null) { - pool = pools.get(poolId); - if (pool == null) // fix dereference violations - return -1; - if (pool.vipId != null && vips.containsKey(pool.vipId)) - vips.get(pool.vipId).pools.remove(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(); - - members.put(member.id, member); - memberIpToId.put(member.address, member.id); - - if (member.poolId != null && pools.get(member.poolId) != null) { - member.vipId = pools.get(member.poolId).vipId; - if (!pools.get(member.poolId).members.contains(member.id)) - pools.get(member.poolId).members.add(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) { - LBMember member; - member = members.get(memberId); - - if(member != null){ - if (member.poolId != null && pools.containsKey(member.poolId)) - pools.get(member.poolId).members.remove(memberId); - members.remove(memberId); - return 0; - } else { - return -1; - } - } - - @Override - public Collection<LBMonitor> listMonitors() { - return null; - } - - @Override - public Collection<LBMonitor> listMonitor(String monitorId) { - return null; - } - - @Override - public LBMonitor createMonitor(LBMonitor monitor) { - return null; - } - - @Override - public LBMonitor updateMonitor(LBMonitor monitor) { - return null; - } - - @Override - public int removeMonitor(String monitorId) { - 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(IOFSwitchService.class); - l.add(IDeviceService.class); - l.add(IDebugCounterService.class); - l.add(ITopologyService.class); - l.add(IRoutingService.class); - l.add(IStaticEntryPusherService.class); - - return l; - } - - @Override - public void init(FloodlightModuleContext context) - throws FloodlightModuleException { - floodlightProviderService = context.getServiceImpl(IFloodlightProviderService.class); - restApiService = context.getServiceImpl(IRestApiService.class); - debugCounterService = context.getServiceImpl(IDebugCounterService.class); - deviceManagerService = context.getServiceImpl(IDeviceService.class); - routingEngineService = context.getServiceImpl(IRoutingService.class); - topologyService = context.getServiceImpl(ITopologyService.class); - sfpService = context.getServiceImpl(IStaticEntryPusherService.class); - switchService = context.getServiceImpl(IOFSwitchService.class); - - 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) { - floodlightProviderService.addOFMessageListener(OFType.PACKET_IN, this); - restApiService.addRestletRoutable(new LoadBalancerWebRoutable()); - debugCounterService.registerModule(this.getName()); - counterPacketOut = debugCounterService.registerCounter(this.getName(), "packet-outs-written", "Packet outs written by the LoadBalancer", MetaData.WARN); - } +ILoadBalancerService, IOFMessageListener { + + protected static Logger log = LoggerFactory.getLogger(LoadBalancer.class); + + // Our dependencies + protected IFloodlightProviderService floodlightProviderService; + protected IRestApiService restApiService; + + protected IDebugCounterService debugCounterService; + private IDebugCounter counterPacketOut; + protected IDeviceService deviceManagerService; + protected IRoutingService routingEngineService; + protected ITopologyService topologyService; + protected IStaticEntryPusherService sfpService; + protected IOFSwitchService switchService; + protected IStatisticsService statisticsService; + + 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 String LB_ETHER_TYPE = "0x800"; + protected static int LB_PRIORITY = 32768; + + // Comparator for sorting by SwitchCluster + public Comparator<SwitchPort> clusterIdComparator = + new Comparator<SwitchPort>() { + @Override + public int compare(SwitchPort d1, SwitchPort d2) { + DatapathId d1ClusterId = topologyService.getClusterId(d1.getNodeId()); + DatapathId d2ClusterId = topologyService.getClusterId(d2.getNodeId()); + return d1ClusterId.compareTo(d2ClusterId); + } + }; + + // data structure for storing connected + public class IPClient { + IPv4Address ipAddress; + IpProtocol nw_proto; + TransportPort srcPort; // tcp/udp src port. icmp type (OFMatch convention) + TransportPort targetPort; // tcp/udp dst port, icmp code (OFMatch convention) + + public IPClient() { + ipAddress = IPv4Address.NONE; + nw_proto = IpProtocol.NONE; + srcPort = TransportPort.NONE; + targetPort = TransportPort.NONE; + } + } + + @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") || + name.equals("virtualizer"))); + } + + @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 (pkt instanceof ARP) { + // retrieve arp to determine target IP address + ARP arpRequest = (ARP) eth.getPayload(); + + IPv4Address targetProtocolAddress = arpRequest.getTargetProtocolAddress(); + + if (vipIpToId.containsKey(targetProtocolAddress.getInt())) { + String vipId = vipIpToId.get(targetProtocolAddress.getInt()); + 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().getInt(); + + if (vipIpToId.containsKey(destIpAddress)){ + + IPClient client = new IPClient(); + client.ipAddress = ip_pkt.getSourceAddress(); + client.nw_proto = ip_pkt.getProtocol(); + if (ip_pkt.getPayload() instanceof TCP) { + TCP tcp_pkt = (TCP) ip_pkt.getPayload(); + + client.srcPort = tcp_pkt.getSourcePort(); + client.targetPort = tcp_pkt.getDestinationPort(); + } + if (ip_pkt.getPayload() instanceof UDP) { + UDP udp_pkt = (UDP) ip_pkt.getPayload(); + client.srcPort = udp_pkt.getSourcePort(); + client.targetPort = udp_pkt.getDestinationPort(); + } + if (ip_pkt.getPayload() instanceof ICMP) { + client.srcPort = TransportPort.of(8); + client.targetPort = TransportPort.of(0); + } + + LBVip vip = vips.get(vipIpToId.get(destIpAddress)); + if (vip == null) // fix dereference violations + return Command.CONTINUE; + LBPool pool = pools.get(vip.pickPool(client)); + if (pool == null) // fix dereference violations + return Command.CONTINUE; + + HashMap<String, Short> memberWeights = new HashMap<String, Short>(); + HashMap<String, U64> memberPortBandwidth = new HashMap<String, U64>(); + + if(pool.lbMethod == LBPool.WEIGHTED_RR){ + for(String memberId: pool.members){ + memberWeights.put(memberId,members.get(memberId).weight); + } + } + // Switch statistics collection + if(pool.lbMethod == LBPool.STATISTICS && statisticsService != null) + memberPortBandwidth = collectSwitchPortBandwidth(); + + LBMember member = members.get(pool.pickMember(client,memberPortBandwidth,memberWeights)); + if(member == null) //fix dereference violations + return Command.CONTINUE; + + // 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.getVersion().compareTo(OFVersion.OF_12) < 0) ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT), OFPort.TABLE, + cntx, true); + + + return Command.STOP; + } + } + } + // bypass non-load-balanced traffic for normal processing (forwarding) + return Command.CONTINUE; + } + + /** + * used to collect statistics from members switch port + * @return HashMap<String, U64> portBandwidth <memberId,bitsPerSecond RX> of port connected to member + */ + public HashMap<String, U64> collectSwitchPortBandwidth(){ + HashMap<String,U64> memberPortBandwidth = new HashMap<String, U64>(); + HashMap<IDevice,String> deviceToMemberId = new HashMap<IDevice, String>(); + + // retrieve all known devices to know which ones are attached to the members + Collection<? extends IDevice> allDevices = deviceManagerService.getAllDevices(); + + for (IDevice d : allDevices) { + for (int j = 0; j < d.getIPv4Addresses().length; j++) { + if(members != null){ + for(LBMember member: members.values()){ + if (member.address == d.getIPv4Addresses()[j].getInt()) + deviceToMemberId.put(d, member.id); + } + } + } + } + // collect statistics of the switch ports attached to the members + if(deviceToMemberId !=null){ + for(IDevice membersDevice: deviceToMemberId.keySet()){ + String memberId = deviceToMemberId.get(membersDevice); + for(SwitchPort dstDap: membersDevice.getAttachmentPoints()){ + SwitchPortBandwidth bandwidthOfPort = statisticsService.getBandwidthConsumption(dstDap.getNodeId(), dstDap.getPortId()); + if(bandwidthOfPort != null) // needs time for 1st collection, this avoids nullPointerException + memberPortBandwidth.put(memberId, bandwidthOfPort.getBitsPerSecondRx()); + } + } + } + return memberPortBandwidth; + } + + /** + * 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 + if (! (eth.getPayload() instanceof ARP)) + return; + ARP arpRequest = (ARP) eth.getPayload(); + + // have to do proxy arp reply since at this point we cannot determine the requesting application type + + // generate proxy ARP reply + IPacket arpReply = new Ethernet() + .setSourceMACAddress(vips.get(vipId).proxyMac) + .setDestinationMACAddress(eth.getSourceMACAddress()) + .setEtherType(EthType.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(vips.get(vipId).proxyMac) + .setSenderProtocolAddress(arpRequest.getTargetProtocolAddress()) + .setTargetHardwareAddress(eth.getSourceMACAddress()) + .setTargetProtocolAddress(arpRequest.getSenderProtocolAddress())); + + // push ARP reply out + pushPacket(arpReply, sw, OFBufferId.NO_BUFFER, OFPort.ANY, (pi.getVersion().compareTo(OFVersion.OF_12) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT)), 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, + OFBufferId bufferId, + OFPort inPort, + OFPort outPort, + FloodlightContext cntx, + boolean flush) { + if (log.isTraceEnabled()) { + log.trace("PacketOut srcSwitch={} inPort={} outPort={}", + new Object[] {sw, inPort, outPort}); + } + + OFPacketOut.Builder pob = sw.getOFFactory().buildPacketOut(); + + // set actions + List<OFAction> actions = new ArrayList<OFAction>(); + actions.add(sw.getOFFactory().actions().buildOutput().setPort(outPort).setMaxLen(Integer.MAX_VALUE).build()); + + pob.setActions(actions); + + // set buffer_id, in_port + pob.setBufferId(bufferId); + pob.setInPort(inPort); + + // set data - only if buffer_id == -1 + if (pob.getBufferId() == OFBufferId.NO_BUFFER) { + 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(); + pob.setData(packetData); + } + + + counterPacketOut.increment(); + sw.write(pob.build()); + + } + + /** + * 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 = deviceManagerService.getAllDevices(); + + for (IDevice d : allDevices) { + for (int j = 0; j < d.getIPv4Addresses().length; j++) { + if (srcDevice == null && client.ipAddress.equals(d.getIPv4Addresses()[j])) + srcDevice = d; + if (dstDevice == null && member.address == d.getIPv4Addresses()[j].getInt()) { + dstDevice = d; + member.macString = dstDevice.getMACAddressString(); + } + if (srcDevice != null && dstDevice != null) + break; + } + } + + // srcDevice and/or dstDevice is null, no route can be pushed + if (srcDevice == null || dstDevice == null) return; + + DatapathId srcIsland = topologyService.getClusterId(sw.getId()); + + if (srcIsland == null) { + log.debug("No openflow island found for source {}/{}", + sw.getId().toString(), 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; + //Switch + for (SwitchPort dstDap : dstDevice.getAttachmentPoints()) { + DatapathId dstSwDpid = dstDap.getNodeId(); + DatapathId dstIsland = topologyService.getClusterId(dstSwDpid); + if ((dstIsland != null) && dstIsland.equals(srcIsland)) { + on_same_island = true; + if ((sw.getId().equals(dstSwDpid)) && OFMessageUtils.getInPort(pi).equals(dstDap.getPortId())) { + 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]; + DatapathId srcCluster = + topologyService.getClusterId(srcDap.getNodeId()); + DatapathId dstCluster = + topologyService.getClusterId(dstDap.getNodeId()); + + int srcVsDest = srcCluster.compareTo(dstCluster); + if (srcVsDest == 0) { + if (!srcDap.equals(dstDap) && + (srcCluster != null) && + (dstCluster != null)) { + Path routeIn = + routingEngineService.getPath(srcDap.getNodeId(), + srcDap.getPortId(), + dstDap.getNodeId(), + dstDap.getPortId()); + Path routeOut = + routingEngineService.getPath(dstDap.getNodeId(), + dstDap.getPortId(), + srcDap.getNodeId(), + srcDap.getPortId()); + + // 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.getPath().isEmpty()) { + pushStaticVipRoute(true, routeIn, client, member, sw); + } + + if (! routeOut.getPath().isEmpty()) { + pushStaticVipRoute(false, routeOut, client, member, sw); + } + + } + iSrcDaps++; + iDstDaps++; + } else if (srcVsDest < 0) { + iSrcDaps++; + } else { + iDstDaps++; + } + } + return; + } + + /** + * used to push given route using static flow entry pusher + * @param boolean inBound + * @param Path route + * @param IPClient client + * @param LBMember member + * @param long pinSwitch + */ + public void pushStaticVipRoute(boolean inBound, Path route, IPClient client, LBMember member, IOFSwitch pinSwitch) { + + List<NodePortTuple> path = route.getPath(); + if (path.size() > 0) { + for (int i = 0; i < path.size(); i+=2) { + DatapathId sw = path.get(i).getNodeId(); + String entryName; + Match.Builder mb = pinSwitch.getOFFactory().buildMatch(); + ArrayList<OFAction> actions = new ArrayList<OFAction>(); + + OFFlowMod.Builder fmb = pinSwitch.getOFFactory().buildFlowAdd(); + + fmb.setIdleTimeout(FlowModUtils.INFINITE_TIMEOUT); + fmb.setHardTimeout(FlowModUtils.INFINITE_TIMEOUT); + fmb.setBufferId(OFBufferId.NO_BUFFER); + fmb.setOutPort(OFPort.ANY); + fmb.setCookie(U64.of(0)); + fmb.setPriority(FlowModUtils.PRIORITY_MAX); + + + if (inBound) { + entryName = "inbound-vip-"+ member.vipId+"-client-"+client.ipAddress + +"-srcport-"+client.srcPort+"-dstport-"+client.targetPort + +"-srcswitch-"+path.get(0).getNodeId()+"-sw-"+sw; + mb.setExact(MatchField.ETH_TYPE, EthType.IPv4) + .setExact(MatchField.IP_PROTO, client.nw_proto) + .setExact(MatchField.IPV4_SRC, client.ipAddress) + .setExact(MatchField.IN_PORT, path.get(i).getPortId()); + if (client.nw_proto.equals(IpProtocol.TCP)) { + mb.setExact(MatchField.TCP_SRC, client.srcPort); + } else if (client.nw_proto.equals(IpProtocol.UDP)) { + mb.setExact(MatchField.UDP_SRC, client.srcPort); + } else if (client.nw_proto.equals(IpProtocol.SCTP)) { + mb.setExact(MatchField.SCTP_SRC, client.srcPort); + } else if (client.nw_proto.equals(IpProtocol.ICMP)) { + /* no-op */ + } else { + log.error("Unknown IpProtocol {} detected during inbound static VIP route push.", client.nw_proto); + } + + + if (sw.equals(pinSwitch.getId())) { + if (pinSwitch.getOFFactory().getVersion().compareTo(OFVersion.OF_12) < 0) { + actions.add(pinSwitch.getOFFactory().actions().setDlDst(MacAddress.of(member.macString))); + actions.add(pinSwitch.getOFFactory().actions().setNwDst(IPv4Address.of(member.address))); + actions.add(pinSwitch.getOFFactory().actions().output(path.get(i+1).getPortId(), Integer.MAX_VALUE)); + } else { // OXM introduced in OF1.2 + actions.add(pinSwitch.getOFFactory().actions().setField(pinSwitch.getOFFactory().oxms().ethDst(MacAddress.of(member.macString)))); + actions.add(pinSwitch.getOFFactory().actions().setField(pinSwitch.getOFFactory().oxms().ipv4Dst(IPv4Address.of(member.address)))); + actions.add(pinSwitch.getOFFactory().actions().output(path.get(i+1).getPortId(), Integer.MAX_VALUE)); + } + } else { + //fix concurrency errors + try{ + actions.add(switchService.getSwitch(path.get(i+1).getNodeId()).getOFFactory().actions().output(path.get(i+1).getPortId(), Integer.MAX_VALUE)); + } + catch(NullPointerException e){ + log.error("Fail to install loadbalancer flow rules to offline switch {}.", path.get(i+1).getNodeId()); + } + } + } else { + entryName = "outbound-vip-"+ member.vipId+"-client-"+client.ipAddress + +"-srcport-"+client.srcPort+"-dstport-"+client.targetPort + +"-srcswitch-"+path.get(0).getNodeId()+"-sw-"+sw; + mb.setExact(MatchField.ETH_TYPE, EthType.IPv4) + .setExact(MatchField.IP_PROTO, client.nw_proto) + .setExact(MatchField.IPV4_DST, client.ipAddress) + .setExact(MatchField.IN_PORT, path.get(i).getPortId()); + if (client.nw_proto.equals(IpProtocol.TCP)) { + mb.setExact(MatchField.TCP_DST, client.srcPort); + } else if (client.nw_proto.equals(IpProtocol.UDP)) { + mb.setExact(MatchField.UDP_DST, client.srcPort); + } else if (client.nw_proto.equals(IpProtocol.SCTP)) { + mb.setExact(MatchField.SCTP_DST, client.srcPort); + } else if (client.nw_proto.equals(IpProtocol.ICMP)) { + /* no-op */ + } else { + log.error("Unknown IpProtocol {} detected during outbound static VIP route push.", client.nw_proto); + } + + if (sw.equals(pinSwitch.getId())) { + if (pinSwitch.getOFFactory().getVersion().compareTo(OFVersion.OF_12) < 0) { + actions.add(pinSwitch.getOFFactory().actions().setDlSrc(vips.get(member.vipId).proxyMac)); + actions.add(pinSwitch.getOFFactory().actions().setNwSrc(IPv4Address.of(vips.get(member.vipId).address))); + actions.add(pinSwitch.getOFFactory().actions().output(path.get(i+1).getPortId(), Integer.MAX_VALUE)); + } else { // OXM introduced in OF1.2 + actions.add(pinSwitch.getOFFactory().actions().setField(pinSwitch.getOFFactory().oxms().ethSrc(vips.get(member.vipId).proxyMac))); + actions.add(pinSwitch.getOFFactory().actions().setField(pinSwitch.getOFFactory().oxms().ipv4Src(IPv4Address.of(vips.get(member.vipId).address)))); + actions.add(pinSwitch.getOFFactory().actions().output(path.get(i+1).getPortId(), Integer.MAX_VALUE)); + + } + } else { + //fix concurrency errors + try{ + actions.add(switchService.getSwitch(path.get(i+1).getNodeId()).getOFFactory().actions().output(path.get(i+1).getPortId(), Integer.MAX_VALUE)); + } + catch(NullPointerException e){ + log.error("Fail to install loadbalancer flow rules to offline switches {}.", path.get(i+1).getNodeId()); + } + } + + } + + + fmb.setActions(actions); + fmb.setPriority(U16.t(LB_PRIORITY)); + fmb.setMatch(mb.build()); + sfpService.addFlow(entryName, fmb.build(), sw); + } + } + + 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.containsKey(pool.vipId)) + vips.get(pool.vipId).pools.add(pool.id); + else { + log.error("specified vip-id must exist"); + pool.vipId = null; + pools.put(pool.id, pool); + } + return pool; + } + + @Override + public LBPool updatePool(LBPool pool) { + pools.put(pool.id, pool); + return null; + } + + @Override + public int removePool(String poolId) { + LBPool pool; + if (pools != null) { + pool = pools.get(poolId); + if (pool == null) // fix dereference violations + return -1; + if (pool.vipId != null && vips.containsKey(pool.vipId)) + vips.get(pool.vipId).pools.remove(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; + if(memberIds !=null && members != null){ + 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(); + + members.put(member.id, member); + memberIpToId.put(member.address, member.id); + + if (member.poolId != null && pools.get(member.poolId) != null) { + member.vipId = pools.get(member.poolId).vipId; + if (!pools.get(member.poolId).members.contains(member.id)) + pools.get(member.poolId).members.add(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) { + LBMember member; + member = members.get(memberId); + + if(member != null){ + if (member.poolId != null && pools.containsKey(member.poolId)) + pools.get(member.poolId).members.remove(memberId); + members.remove(memberId); + return 0; + } else { + return -1; + } + } + + @Override + public int setMemberWeight(String memberId, String weight){ + LBMember member; + short value; + member = members.get(memberId); + + try{ + value = Short.parseShort(weight); + } catch(Exception e){ + log.error("Invalid value for member weight " + e.getMessage()); + return -1; + } + if(member != null && (value <= 10 && value >= 1)){ + member.weight = value; + return 0; + } + return -1; + } + + public int setPriorityToMember(String poolId ,String memberId){ + if(pools.containsKey(poolId)) { + ArrayList<String> memberIds = pools.get(poolId).members; + if(memberIds !=null && members != null && memberIds.contains(memberId)){ + for (int i = 0; i<memberIds.size(); i++){ + if(members.get(memberIds.get(i)).id.equals(memberId)){ + members.get(memberId).weight=(short) (1 + memberIds.size()/2); + }else + members.get(memberIds.get(i)).weight=1; + } + return 0; + } + } + return -1; + } + @Override + public Collection<LBMonitor> listMonitors() { + return null; + } + + @Override + public Collection<LBMonitor> listMonitor(String monitorId) { + return null; + } + + @Override + public LBMonitor createMonitor(LBMonitor monitor) { + return null; + } + + @Override + public LBMonitor updateMonitor(LBMonitor monitor) { + return null; + } + + @Override + public int removeMonitor(String monitorId) { + 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(IOFSwitchService.class); + l.add(IDeviceService.class); + l.add(IDebugCounterService.class); + l.add(ITopologyService.class); + l.add(IRoutingService.class); + l.add(IStaticEntryPusherService.class); + l.add(IStatisticsService.class); + + return l; + } + + @Override + public void init(FloodlightModuleContext context) + throws FloodlightModuleException { + floodlightProviderService = context.getServiceImpl(IFloodlightProviderService.class); + restApiService = context.getServiceImpl(IRestApiService.class); + debugCounterService = context.getServiceImpl(IDebugCounterService.class); + deviceManagerService = context.getServiceImpl(IDeviceService.class); + routingEngineService = context.getServiceImpl(IRoutingService.class); + topologyService = context.getServiceImpl(ITopologyService.class); + sfpService = context.getServiceImpl(IStaticEntryPusherService.class); + switchService = context.getServiceImpl(IOFSwitchService.class); + statisticsService = context.getServiceImpl(IStatisticsService.class); + + 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) { + floodlightProviderService.addOFMessageListener(OFType.PACKET_IN, this); + restApiService.addRestletRoutable(new LoadBalancerWebRoutable()); + debugCounterService.registerModule(this.getName()); + counterPacketOut = debugCounterService.registerCounter(this.getName(), "packet-outs-written", "Packet outs written by the LoadBalancer", MetaData.WARN); + } } diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/LoadBalancerWebRoutable.java b/src/main/java/net/floodlightcontroller/loadbalancer/LoadBalancerWebRoutable.java index 52c5f567d0b7b59443de7c615dc7832317554c9a..eeb3a9a8a8ac7773712ec473585f5e925bb634a9 100644 --- a/src/main/java/net/floodlightcontroller/loadbalancer/LoadBalancerWebRoutable.java +++ b/src/main/java/net/floodlightcontroller/loadbalancer/LoadBalancerWebRoutable.java @@ -34,9 +34,11 @@ public class LoadBalancerWebRoutable implements RestletRoutable { 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("/members/{member}/{weight}", WRRResource.class); // PUT, POST router.attach("/pools/{pool}/members", PoolMemberResource.class); //GET + router.attach("/pools/{pool}/members/{member}", PoolMemberResource.class); // PUT, POST router.attach("/health_monitors/", MonitorsResource.class); //GET, POST - router.attach("/health_monitors/{monitor}", MonitorsResource.class); //GET, PUT, DELETE + router.attach("/health_monitors/{monitor}", MonitorsResource.class); //GET, PUT, DELETE router.attachDefault(NoOp.class); return router; } diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/MembersResource.java b/src/main/java/net/floodlightcontroller/loadbalancer/MembersResource.java index c7301aca513d6ef6d22d93f87699e35d47197970..746d2dd915f40d3efebae64414a672ba791a6e41 100644 --- a/src/main/java/net/floodlightcontroller/loadbalancer/MembersResource.java +++ b/src/main/java/net/floodlightcontroller/loadbalancer/MembersResource.java @@ -136,7 +136,11 @@ public class MembersResource extends ServerResource { if (n.equals("pool_id")) { member.poolId = jp.getText(); continue; - } + } + if(n.equals("weight")){ + member.weight = Short.parseShort(jp.getText()); + continue; + } log.warn("Unrecognized field {} in " + "parsing Members", diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/PoolMemberResource.java b/src/main/java/net/floodlightcontroller/loadbalancer/PoolMemberResource.java index 481baeabedce85af632cb7c2e53efb3f55e94416..4f553f799a1f0e91c4509789653417f8ea171698 100644 --- a/src/main/java/net/floodlightcontroller/loadbalancer/PoolMemberResource.java +++ b/src/main/java/net/floodlightcontroller/loadbalancer/PoolMemberResource.java @@ -19,6 +19,8 @@ package net.floodlightcontroller.loadbalancer; import java.util.Collection; 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; @@ -38,4 +40,19 @@ public class PoolMemberResource extends ServerResource { else return null; } + + @Put + @Post + public int setPriorityToMember(){ + + String poolId = (String) getRequestAttributes().get("pool"); + String memberId = (String) getRequestAttributes().get("member"); + + ILoadBalancerService lbs = + (ILoadBalancerService)getContext().getAttributes(). + get(ILoadBalancerService.class.getCanonicalName()); + + return lbs.setPriorityToMember(poolId,memberId); + + } } diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/PoolsResource.java b/src/main/java/net/floodlightcontroller/loadbalancer/PoolsResource.java index 82a041a20751fe32b5556c80cc5683234e9efb43..433de10946215f5f8e2bd1430bdcfe710802424b 100644 --- a/src/main/java/net/floodlightcontroller/loadbalancer/PoolsResource.java +++ b/src/main/java/net/floodlightcontroller/loadbalancer/PoolsResource.java @@ -128,7 +128,15 @@ public class PoolsResource extends ServerResource { continue; } if (n.equals("lb_method")) { - pool.lbMethod = Short.parseShort(jp.getText()); + String method = jp.getText(); + if(method.equalsIgnoreCase("RR")){ + pool.lbMethod = (short) 1; + } else if(method.equalsIgnoreCase("STATISTICS")){ + pool.lbMethod = (short) 2; + } else if(method.equalsIgnoreCase("WRR")){ + pool.lbMethod = (short) 3; + } + continue; } if (n.equals("protocol")) { diff --git a/src/main/java/net/floodlightcontroller/loadbalancer/WRRResource.java b/src/main/java/net/floodlightcontroller/loadbalancer/WRRResource.java new file mode 100644 index 0000000000000000000000000000000000000000..78c2c71d1f68ffe51d8db2bc624c25038c469754 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/loadbalancer/WRRResource.java @@ -0,0 +1,27 @@ +package net.floodlightcontroller.loadbalancer; + + +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 WRRResource extends ServerResource{ + protected static Logger log = LoggerFactory.getLogger(WRRResource.class); + + @Put + @Post + public int setMemberWeight(){ + + String memberId = (String) getRequestAttributes().get("member"); + String weight = (String) getRequestAttributes().get("weight"); + + ILoadBalancerService lbs = + (ILoadBalancerService)getContext().getAttributes(). + get(ILoadBalancerService.class.getCanonicalName()); + + return lbs.setMemberWeight(memberId,weight); + + } +} diff --git a/src/main/java/net/floodlightcontroller/packet/TCP.java b/src/main/java/net/floodlightcontroller/packet/TCP.java index 1fdf1aafd933a67c61d45213693c96dfcfba9614..3d2f4805e43f2febeaebddf0a21be3d83c3d9858 100644 --- a/src/main/java/net/floodlightcontroller/packet/TCP.java +++ b/src/main/java/net/floodlightcontroller/packet/TCP.java @@ -178,6 +178,7 @@ public class TCP extends BasePacket { byte[] payloadData = null; if (payload != null) { payload.setParent(this); + payloadData = payload.serialize(); length += payloadData.length; } @@ -379,7 +380,7 @@ public class TCP extends BasePacket { @Override public IPacket deserialize(byte[] data, int offset, int length) throws PacketParsingException { - ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + ByteBuffer bb = ByteBuffer.wrap(data, offset, length); this.sourcePort = TransportPort.of((int) (bb.getShort() & 0xffff)); // short will be signed, pos or neg this.destinationPort = TransportPort.of((int) (bb.getShort() & 0xffff)); // convert range 0 to 65534, not -32768 to 32767 this.sequence = bb.getInt(); @@ -405,11 +406,12 @@ public class TCP extends BasePacket { this.options = null; } } - + this.payload = new Data(); int remLength = bb.limit()-bb.position(); this.payload = payload.deserialize(data, bb.position(), remLength); + this.payload.setParent(this); return this; - } + } } diff --git a/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java b/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java index a624f2119f8e89ad66e11179d6892bd8a4e1a749..aa104bc19590a4c528af4e738bfd2664b3e4e2a2 100644 --- a/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java +++ b/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java @@ -341,7 +341,6 @@ public abstract class ForwardingBase implements IOFMessageListener { } pob.setInPort((pi.getVersion().compareTo(OFVersion.OF_12) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT))); - messageDamper.write(sw, pob.build()); } diff --git a/src/test/java/net/floodlightcontroller/loadbalancer/LoadBalancerTest.java b/src/test/java/net/floodlightcontroller/loadbalancer/LoadBalancerTest.java index df26f7c614bdb46e4dac1a10a885bdbf38006920..c1551b86e1da5d25e3357b22cf6e13b7d4258ed5 100644 --- a/src/test/java/net/floodlightcontroller/loadbalancer/LoadBalancerTest.java +++ b/src/test/java/net/floodlightcontroller/loadbalancer/LoadBalancerTest.java @@ -51,6 +51,7 @@ import org.projectfloodlight.openflow.types.IpProtocol; import org.projectfloodlight.openflow.types.MacAddress; import org.projectfloodlight.openflow.types.OFBufferId; import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.U64; import org.projectfloodlight.openflow.types.VlanVid; import org.projectfloodlight.openflow.protocol.OFPacketInReason; import org.projectfloodlight.openflow.protocol.action.OFAction; @@ -72,6 +73,7 @@ import net.floodlightcontroller.devicemanager.IDeviceService; import net.floodlightcontroller.devicemanager.IEntityClassifierService; import net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier; import net.floodlightcontroller.devicemanager.test.MockDeviceManager; +import net.floodlightcontroller.loadbalancer.LoadBalancer.IPClient; import net.floodlightcontroller.packet.ARP; import net.floodlightcontroller.packet.Ethernet; import net.floodlightcontroller.packet.ICMP; @@ -105,12 +107,14 @@ public class LoadBalancerTest extends FloodlightTestCase { protected RestApiServer restApi; protected VipsResource vipsResource; protected PoolsResource poolsResource; + protected WRRResource wrrResource; + protected PoolMemberResource poolMemberResource; protected MembersResource membersResource; private MockSyncService mockSyncService; protected IDebugCounterService debugCounterService; protected LBVip vip1, vip2; protected LBPool pool1, pool2, pool3; - protected LBMember member1, member2, member3, member4; + protected LBMember member1, member2, member3, member4, member5, member6; private OFFactory factory; @Override @@ -119,7 +123,7 @@ public class LoadBalancerTest extends FloodlightTestCase { super.setUp(); factory = OFFactories.getFactory(OFVersion.OF_13); - + lb = new LoadBalancer(); cntx = new FloodlightContext(); @@ -178,6 +182,8 @@ public class LoadBalancerTest extends FloodlightTestCase { vipsResource = new VipsResource(); poolsResource = new PoolsResource(); membersResource = new MembersResource(); + wrrResource = new WRRResource(); + poolMemberResource = new PoolMemberResource(); vip1=null; vip2=null; @@ -190,6 +196,8 @@ public class LoadBalancerTest extends FloodlightTestCase { member2=null; member3=null; member4=null; + member5=null; + member6=null; } @Test @@ -249,8 +257,8 @@ public class LoadBalancerTest extends FloodlightTestCase { testCreateVip(); - postData1 = "{\"id\":\"1\",\"name\":\"pool1\",\"protocol\":\"icmp\",\"vip_id\":\"1\"}"; - postData2 = "{\"id\":\"2\",\"name\":\"pool2\",\"protocol\":\"tcp\",\"vip_id\":\"2\"}"; + postData1 = "{\"id\":\"1\",\"name\":\"pool1\",\"protocol\":\"icmp\",\"lb_method\":\"STATISTICS\",\"vip_id\":\"1\"}"; + postData2 = "{\"id\":\"2\",\"name\":\"pool2\",\"protocol\":\"tcp\",\"lb_method\":\"WRR\",\"vip_id\":\"2\"}"; postData3 = "{\"id\":\"3\",\"name\":\"pool3\",\"protocol\":\"udp\",\"vip_id\":\"3\"}"; try { @@ -317,16 +325,19 @@ public class LoadBalancerTest extends FloodlightTestCase { @Test public void testCreateMember() { - String postData1, postData2, postData3, postData4; + String postData1, postData2, postData3, postData4,postData5,postData6; IOException error = null; testCreateVip(); testCreatePool(); - postData1 = "{\"id\":\"1\",\"address\":\"10.0.0.3\",\"port\":\"8\",\"pool_id\":\"1\"}"; - postData2 = "{\"id\":\"2\",\"address\":\"10.0.0.4\",\"port\":\"8\",\"pool_id\":\"1\"}"; - postData3 = "{\"id\":\"3\",\"address\":\"10.0.0.5\",\"port\":\"100\",\"pool_id\":\"2\"}"; + postData1 = "{\"id\":\"1\",\"address\":\"10.0.0.3\",\"port\":\"8\",\"pool_id\":\"1\",\"weight\":\"2\"}"; + postData2 = "{\"id\":\"2\",\"address\":\"10.0.0.4\",\"port\":\"8\",\"pool_id\":\"1\",\"weight\":\"3\"}"; + postData3 = "{\"id\":\"3\",\"address\":\"10.0.0.5\",\"port\":\"100\",\"pool_id\":\"2\",\"weight\":\"4\"}"; postData4 = "{\"id\":\"4\",\"address\":\"10.0.0.6\",\"port\":\"100\",\"pool_id\":\"2\"}"; + postData5 = "{\"id\":\"5\",\"address\":\"10.0.0.7\",\"port\":\"100\",\"pool_id\":\"1\",\"weight\":\"5\"}"; + postData6 = "{\"id\":\"6\",\"address\":\"10.0.0.8\",\"port\":\"100\",\"pool_id\":\"1\",\"weight\":\"5\"}"; + try { member1 = membersResource.jsonToMember(postData1); @@ -348,18 +359,33 @@ public class LoadBalancerTest extends FloodlightTestCase { } catch (IOException e) { error = e; } + try { + member5= membersResource.jsonToMember(postData5); + } catch (IOException e) { + error = e; + } + try { + member6= membersResource.jsonToMember(postData6); + } catch (IOException e) { + error = e; + } + // verify correct parsing assertFalse(member1==null); assertFalse(member2==null); assertFalse(member3==null); assertFalse(member4==null); + assertFalse(member5==null); + assertFalse(member6==null); assertTrue(error==null); lb.createMember(member1); lb.createMember(member2); lb.createMember(member3); lb.createMember(member4); + lb.createMember(member5); + lb.createMember(member6); // add the same server a second time lb.createMember(member1); @@ -369,12 +395,23 @@ public class LoadBalancerTest extends FloodlightTestCase { assertTrue(lb.members.containsKey(member2.id)); assertTrue(lb.members.containsKey(member3.id)); assertTrue(lb.members.containsKey(member4.id)); + assertTrue(lb.members.containsKey(member5.id)); + assertTrue(lb.members.containsKey(member6.id)); - assertTrue(lb.pools.get(member1.poolId).members.size()==2); + assertTrue(lb.pools.get(member1.poolId).members.size()==4); assertTrue(lb.pools.get(member3.poolId).members.size()==2); // member1 should inherit valid vipId from pool assertTrue(lb.vips.get(member1.vipId)!=null); + + assertTrue(member1.weight==2); + assertTrue(member2.weight==3); + assertTrue(member3.weight==4); + assertTrue(member5.weight==5); + assertTrue(member6.weight==5); + + // default weight value + assertTrue(member4.weight==1); } @Test @@ -439,10 +476,10 @@ public class LoadBalancerTest extends FloodlightTestCase { expect(sw1.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_TABLE)).andReturn(true).anyTimes(); expect(sw1.getOFFactory()).andReturn(factory).anyTimes(); expect(sw1.write(capture(wc1))).andReturn(true).anyTimes(); - + replay(sw1); sfp.switchAdded(DatapathId.of(1L)); - + verify(sw1); /* Test plan: @@ -469,22 +506,22 @@ public class LoadBalancerTest extends FloodlightTestCase { // Build arp packets arpRequest1 = new Ethernet() - .setSourceMACAddress("00:00:00:00:00:01") - .setDestinationMACAddress("ff:ff:ff:ff:ff:ff") - .setEtherType(EthType.ARP) - .setVlanID((short) 0) - .setPriorityCode((byte) 0) - .setPayload( - new ARP() - .setHardwareType(ARP.HW_TYPE_ETHERNET) - .setProtocolType(ARP.PROTO_TYPE_IP) - .setHardwareAddressLength((byte) 6) - .setProtocolAddressLength((byte) 4) - .setOpCode(ARP.OP_REQUEST) - .setSenderHardwareAddress(MacAddress.of("00:00:00:00:00:01")) - .setSenderProtocolAddress(IPv4Address.of("10.0.0.1")) - .setTargetHardwareAddress(MacAddress.of("00:00:00:00:00:00")) - .setTargetProtocolAddress(IPv4Address.of("10.0.0.100"))); + .setSourceMACAddress("00:00:00:00:00:01") + .setDestinationMACAddress("ff:ff:ff:ff:ff:ff") + .setEtherType(EthType.ARP) + .setVlanID((short) 0) + .setPriorityCode((byte) 0) + .setPayload( + new ARP() + .setHardwareType(ARP.HW_TYPE_ETHERNET) + .setProtocolType(ARP.PROTO_TYPE_IP) + .setHardwareAddressLength((byte) 6) + .setProtocolAddressLength((byte) 4) + .setOpCode(ARP.OP_REQUEST) + .setSenderHardwareAddress(MacAddress.of("00:00:00:00:00:01")) + .setSenderProtocolAddress(IPv4Address.of("10.0.0.1")) + .setTargetHardwareAddress(MacAddress.of("00:00:00:00:00:00")) + .setTargetProtocolAddress(IPv4Address.of("10.0.0.100"))); arpRequest1Serialized = arpRequest1.serialize(); @@ -501,22 +538,22 @@ public class LoadBalancerTest extends FloodlightTestCase { // Mock proxy arp packet-out arpReply1 = new Ethernet() - .setSourceMACAddress(LBVip.LB_PROXY_MAC) - .setDestinationMACAddress(MacAddress.of("00:00:00:00:00:01")) - .setEtherType(EthType.ARP) - .setVlanID((short) 0) - .setPriorityCode((byte) 0) - .setPayload( - new ARP() - .setHardwareType(ARP.HW_TYPE_ETHERNET) - .setProtocolType(ARP.PROTO_TYPE_IP) - .setHardwareAddressLength((byte) 6) - .setProtocolAddressLength((byte) 4) - .setOpCode(ARP.OP_REPLY) - .setSenderHardwareAddress(MacAddress.of(LBVip.LB_PROXY_MAC)) - .setSenderProtocolAddress(IPv4Address.of("10.0.0.100")) - .setTargetHardwareAddress(MacAddress.of("00:00:00:00:00:01")) - .setTargetProtocolAddress(IPv4Address.of("10.0.0.1"))); + .setSourceMACAddress(LBVip.LB_PROXY_MAC) + .setDestinationMACAddress(MacAddress.of("00:00:00:00:00:01")) + .setEtherType(EthType.ARP) + .setVlanID((short) 0) + .setPriorityCode((byte) 0) + .setPayload( + new ARP() + .setHardwareType(ARP.HW_TYPE_ETHERNET) + .setProtocolType(ARP.PROTO_TYPE_IP) + .setHardwareAddressLength((byte) 6) + .setProtocolAddressLength((byte) 4) + .setOpCode(ARP.OP_REPLY) + .setSenderHardwareAddress(MacAddress.of(LBVip.LB_PROXY_MAC)) + .setSenderProtocolAddress(IPv4Address.of("10.0.0.100")) + .setTargetHardwareAddress(MacAddress.of("00:00:00:00:00:01")) + .setTargetProtocolAddress(IPv4Address.of("10.0.0.1"))); arpReply1Serialized = arpReply1.serialize(); @@ -530,7 +567,7 @@ public class LoadBalancerTest extends FloodlightTestCase { .setXid(22) .build(); sw1.write(arpReplyPacketOut1); - + lb.receive(sw1, arpRequestPacketIn1, cntx); verify(sw1, topology); @@ -540,7 +577,7 @@ public class LoadBalancerTest extends FloodlightTestCase { for (OFMessage m: msglist1) { if (m instanceof OFPacketOut) - assertEquals(OFMessageUtils.OFMessageIgnoreXid.of(arpReplyPacketOut1), OFMessageUtils.OFMessageIgnoreXid.of(m)); + assertEquals(OFMessageUtils.OFMessageIgnoreXid.of(arpReplyPacketOut1), OFMessageUtils.OFMessageIgnoreXid.of(m)); else assertTrue(false); // unexpected message } @@ -549,7 +586,7 @@ public class LoadBalancerTest extends FloodlightTestCase { // Skip arpRequest2 test - in reality this will happen, but for unit test the same logic // is already validated with arpRequest1 test above // - + // Keep the StaticFlowEntryPusher happy with a switch in the switch service Map<DatapathId, IOFSwitch> switches = new HashMap<DatapathId, IOFSwitch>(1); switches.put(DatapathId.of(1), sw1); @@ -558,19 +595,19 @@ public class LoadBalancerTest extends FloodlightTestCase { // Build icmp packets icmpPacket1 = new Ethernet() - .setSourceMACAddress("00:00:00:00:00:01") - .setDestinationMACAddress(LBVip.LB_PROXY_MAC) - .setEtherType(EthType.IPv4) - .setVlanID((short) 0) - .setPriorityCode((byte) 0) - .setPayload( - new IPv4() - .setSourceAddress("10.0.0.1") - .setDestinationAddress("10.0.0.100") - .setProtocol(IpProtocol.ICMP) - .setPayload(new ICMP() - .setIcmpCode((byte) 0) - .setIcmpType((byte) 0))); + .setSourceMACAddress("00:00:00:00:00:01") + .setDestinationMACAddress(LBVip.LB_PROXY_MAC) + .setEtherType(EthType.IPv4) + .setVlanID((short) 0) + .setPriorityCode((byte) 0) + .setPayload( + new IPv4() + .setSourceAddress("10.0.0.1") + .setDestinationAddress("10.0.0.100") + .setProtocol(IpProtocol.ICMP) + .setPayload(new ICMP() + .setIcmpCode((byte) 0) + .setIcmpType((byte) 0))); icmpPacket1Serialized = icmpPacket1.serialize(); @@ -581,19 +618,19 @@ public class LoadBalancerTest extends FloodlightTestCase { .setReason(OFPacketInReason.NO_MATCH) .build(); icmpPacket2 = new Ethernet() - .setSourceMACAddress("00:00:00:00:00:02") - .setDestinationMACAddress(LBVip.LB_PROXY_MAC) - .setEtherType(EthType.IPv4) - .setVlanID((short) 0) - .setPriorityCode((byte) 0) - .setPayload( - new IPv4() - .setSourceAddress("10.0.0.2") - .setDestinationAddress("10.0.0.100") - .setProtocol(IpProtocol.ICMP) - .setPayload(new ICMP() - .setIcmpCode((byte) 0) - .setIcmpType((byte) 0))); + .setSourceMACAddress("00:00:00:00:00:02") + .setDestinationMACAddress(LBVip.LB_PROXY_MAC) + .setEtherType(EthType.IPv4) + .setVlanID((short) 0) + .setPriorityCode((byte) 0) + .setPayload( + new IPv4() + .setSourceAddress("10.0.0.2") + .setDestinationAddress("10.0.0.100") + .setProtocol(IpProtocol.ICMP) + .setPayload(new ICMP() + .setIcmpCode((byte) 0) + .setIcmpType((byte) 0))); icmpPacket2Serialized = icmpPacket2.serialize(); @@ -683,5 +720,72 @@ public class LoadBalancerTest extends FloodlightTestCase { assertTrue(map.size()==4); } + @Test + public void testSetMemberWeight() { + testCreateVip(); + testCreatePool(); + testCreateMember(); + + lb.setMemberWeight(member1.id, "5"); + lb.setMemberWeight(member2.id, "2"); + lb.setMemberWeight(member3.id, "2"); + lb.setMemberWeight(member4.id, "9"); + + assertTrue(member1.weight==5); + assertTrue(member2.weight==2); + assertTrue(member3.weight==2); + assertTrue(member4.weight==9); + int inf_limit = lb.setMemberWeight(member1.id,"0"); + + int sup_limit = lb.setMemberWeight(member1.id,"11"); + + assertTrue(inf_limit == -1); + assertTrue(sup_limit == -1); + } + + @Test + public void testSetPriorityMember() { + testCreateVip(); + testCreatePool(); + testCreateMember(); + + lb.setPriorityToMember(member1.id,member1.poolId); + + assertTrue(member1.weight==3); + assertTrue(member2.weight==1); + assertTrue(member5.weight==1); + assertTrue(member6.weight==1); + } + + @Test + public void testPoolAlgorithms() { + testCreateVip(); + testCreatePool(); + testCreateMember(); + + IPClient client = lb.new IPClient(); + + HashMap<String, U64> membersBandwidth = new HashMap<String, U64>(); + membersBandwidth.put(member1.id,U64.of(4999)); + membersBandwidth.put(member2.id,U64.of(1344)); + membersBandwidth.put(member3.id,U64.ZERO); + membersBandwidth.put(member4.id,U64.of(230)); + membersBandwidth.put(member5.id,U64.of(2002)); + membersBandwidth.put(member6.id,U64.of(1345)); + + HashMap<String, Short> membersWeight = new HashMap<String, Short>(); + + String memberPickedStats = pool1.pickMember(client, membersBandwidth, membersWeight); + + String noMembers = pool3.pickMember(client, membersBandwidth, membersWeight); + + membersBandwidth.clear(); + String memberPickedNoData = pool1.pickMember(client, membersBandwidth, membersWeight); + + assertTrue(memberPickedStats.equals("2")); + assertTrue(memberPickedNoData.equals("1")); // simple round robin + + assertTrue(noMembers==null); + } }