2 * Copyright IBM Corporation, 2013. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.controller.samples.loadbalancer.internal;
10 import java.net.InetAddress;
11 import java.net.UnknownHostException;
12 import java.util.ArrayList;
13 import java.util.Dictionary;
14 import java.util.List;
17 import org.apache.felix.dm.Component;
18 import org.opendaylight.controller.forwardingrulesmanager.FlowEntry;
19 import org.opendaylight.controller.forwardingrulesmanager.IForwardingRulesManager;
20 import org.opendaylight.controller.hosttracker.IfIptoHost;
21 import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
22 import org.opendaylight.controller.sal.action.Action;
23 import org.opendaylight.controller.sal.action.Output;
24 import org.opendaylight.controller.sal.action.SetDlDst;
25 import org.opendaylight.controller.sal.action.SetDlSrc;
26 import org.opendaylight.controller.sal.action.SetNwDst;
27 import org.opendaylight.controller.sal.action.SetNwSrc;
28 import org.opendaylight.controller.sal.core.Node;
29 import org.opendaylight.controller.sal.core.NodeConnector;
30 import org.opendaylight.controller.sal.core.Path;
31 import org.opendaylight.controller.sal.flowprogrammer.Flow;
32 import org.opendaylight.controller.sal.match.Match;
33 import org.opendaylight.controller.sal.match.MatchType;
34 import org.opendaylight.controller.sal.packet.Ethernet;
35 import org.opendaylight.controller.sal.packet.IDataPacketService;
36 import org.opendaylight.controller.sal.packet.IListenDataPacket;
37 import org.opendaylight.controller.sal.packet.IPv4;
38 import org.opendaylight.controller.sal.packet.Packet;
39 import org.opendaylight.controller.sal.packet.PacketResult;
40 import org.opendaylight.controller.sal.packet.RawPacket;
41 import org.opendaylight.controller.sal.routing.IRouting;
42 import org.opendaylight.controller.sal.utils.EtherTypes;
43 import org.opendaylight.controller.sal.utils.GlobalConstants;
44 import org.opendaylight.controller.sal.utils.IPProtocols;
45 import org.opendaylight.controller.samples.loadbalancer.ConfigManager;
46 import org.opendaylight.controller.samples.loadbalancer.IConfigManager;
47 import org.opendaylight.controller.samples.loadbalancer.LBConst;
48 import org.opendaylight.controller.samples.loadbalancer.LBUtil;
49 import org.opendaylight.controller.samples.loadbalancer.entities.Client;
50 import org.opendaylight.controller.samples.loadbalancer.entities.Pool;
51 import org.opendaylight.controller.samples.loadbalancer.entities.PoolMember;
52 import org.opendaylight.controller.samples.loadbalancer.entities.VIP;
53 import org.opendaylight.controller.samples.loadbalancer.policies.RandomLBPolicy;
54 import org.opendaylight.controller.samples.loadbalancer.policies.RoundRobinLBPolicy;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
59 * This class is the main class that represents the load balancer service.
60 * This is a sample load balancer application that balances traffic to backend servers
61 * based on the source address and source port on each incoming packet. The service
62 * reactively installs OpenFlow rules to direct all packets with a specific source address
63 * and source port to one of the appropriate backend servers. The servers may be chosen
64 * using a round robin policy or a random policy. This service can be configured via a
65 * REST APIs which are similar to the OpenStack Quantum LBaaS (Load-balancer-as-a-Service)
66 * v1.0 API proposal (http://wiki.openstack.org/Quantum/LBaaS)
68 * To use this service, a virtual IP (or VIP) should be exposed to the clients of this service
69 * and used as the destination address. A VIP is a entity that comprises of a virtual IP, port
70 * and protocol (TCP or UDP).
72 * 1. One or more VIPs may be mapped to the same server pool. All VIPs that share the same
73 * pool must also share the same load balancing policy (random or round robin).
75 * 2. Only one server pool can be be assigned to a VIP.
77 * 3. All flow rules are installed with an idle timeout of 5 seconds.
79 * 4. Packets to a VIP must leave the OpenFlow cluster from the same switch from where
82 * 5. When you delete a VIP or a server pool or a server from a pool, the service does not
83 * delete the flow rules it has already installed. The flow rules should automatically
84 * time out after the idle timeout of 5 seconds.
87 public class LoadBalancerService implements IListenDataPacket, IConfigManager{
92 private static Logger lbsLogger = LoggerFactory.getLogger(LoadBalancerService.class);
95 * Single instance of the configuration manager. Application passes this reference to all
96 * the new policies implemented for load balancing.
98 private static ConfigManager configManager = new ConfigManager();
101 * Round robing policy instance. Need to implement factory patterns to get
104 private static RoundRobinLBPolicy rrLBMethod= new RoundRobinLBPolicy(configManager);
107 * Random policy instance.
109 private static RandomLBPolicy ranLBMethod= new RandomLBPolicy(configManager);
112 * Reference to the data packet service
114 private IDataPacketService dataPacketService = null;
117 * Reference to the host tracker service
119 private IfIptoHost hostTracker;
122 * Reference to the forwarding manager
124 private IForwardingRulesManager ruleManager;
127 * Reference to the routing service
129 private IRouting routing;
132 * Load balancer application installs all flows with priority 2.
134 private static short LB_IPSWITCH_PRIORITY = 2;
137 * Name of the container where this application will register.
139 private String containerName = null;
142 * Set/unset methods for the service instance that load balancer
145 public String getContainerName() {
146 if (containerName == null)
147 return GlobalConstants.DEFAULT.toString();
148 return containerName;
151 void setDataPacketService(IDataPacketService s) {
152 this.dataPacketService = s;
155 void unsetDataPacketService(IDataPacketService s) {
156 if (this.dataPacketService == s) {
157 this.dataPacketService = null;
161 public void setRouting(IRouting routing) {
162 this.routing = routing;
165 public void unsetRouting(IRouting routing) {
166 if (this.routing == routing) {
171 public void setHostTracker(IfIptoHost hostTracker) {
172 lbsLogger.debug("Setting HostTracker");
173 this.hostTracker = hostTracker;
176 public void unsetHostTracker(IfIptoHost hostTracker) {
177 if (this.hostTracker == hostTracker) {
178 this.hostTracker = null;
182 public void setForwardingRulesManager(
183 IForwardingRulesManager forwardingRulesManager) {
184 lbsLogger.debug("Setting ForwardingRulesManager");
185 this.ruleManager = forwardingRulesManager;
188 public void unsetForwardingRulesManager(
189 IForwardingRulesManager forwardingRulesManager) {
190 if (this.ruleManager == forwardingRulesManager) {
191 this.ruleManager = null;
196 * This method receives first packet of flows for which there is no
197 * matching flow rule installed on the switch. IP addresses used for VIPs
198 * are not supposed to be used by any real/virtual host in the network.
199 * Hence, any forwarding/routing service will not install any flows rules matching
200 * these VIPs. This ensures that all the flows destined for VIPs will not find a match
201 * in the switch and will be forwarded to the load balancing service.
202 * Service will decide where to route this traffic based on the load balancing
203 * policy of the VIP's attached pool and will install appropriate flow rules
204 * in a reactive manner.
207 public PacketResult receiveDataPacket(RawPacket inPkt){
210 return PacketResult.IGNORED;
213 Packet formattedPak = this.dataPacketService.decodeDataPacket(inPkt);
215 if (formattedPak instanceof Ethernet) {
216 byte[] vipMacAddr = ((Ethernet) formattedPak).getDestinationMACAddress();
217 Object ipPkt = formattedPak.getPayload();
219 if (ipPkt instanceof IPv4) {
221 lbsLogger.debug("Packet recieved from switch : {}",inPkt.getIncomingNodeConnector().getNode().toString());
222 IPv4 ipv4Pkt = (IPv4)ipPkt;
223 if(IPProtocols.getProtocolName(ipv4Pkt.getProtocol()).equals(IPProtocols.TCP.toString())
224 || IPProtocols.getProtocolName(ipv4Pkt.getProtocol()).equals(IPProtocols.UDP.toString())){
226 lbsLogger.debug("Packet protocol : {}",IPProtocols.getProtocolName(ipv4Pkt.getProtocol()));
227 Client client = new LBUtil().getClientFromPacket(ipv4Pkt);
228 VIP vip = new LBUtil().getVIPFromPacket(ipv4Pkt);
230 if(configManager.vipExists(vip)){
231 VIP vipWithPoolName = configManager.getVIPWithPoolName(vip);
232 String poolMemberIp = null;
233 if(vipWithPoolName.getPoolName() == null){
234 lbsLogger.error("No pool attached. Please attach pool with the VIP -- {}",vip);
235 return PacketResult.IGNORED;
237 if(configManager.getPool(vipWithPoolName.getPoolName()).getLbMethod().equalsIgnoreCase(LBConst.ROUND_ROBIN_LB_METHOD)){
239 poolMemberIp = rrLBMethod.getPoolMemberForClient(client,vipWithPoolName);
242 if(configManager.getPool(vipWithPoolName.getPoolName()).getLbMethod().equalsIgnoreCase(LBConst.RANDOM_LB_METHOD)){
243 poolMemberIp = ranLBMethod.getPoolMemberForClient(client,vipWithPoolName);
248 Node clientNode = inPkt.getIncomingNodeConnector().getNode();
249 HostNodeConnector hnConnector = this.hostTracker.hostFind(InetAddress.getByName(poolMemberIp));
251 Node destNode = hnConnector.getnodeconnectorNode();
253 lbsLogger.debug("Client is connected to switch : {}",clientNode.toString());
254 lbsLogger.debug("Destination pool machine is connected to switch : {}",destNode.toString());
256 //Get path between both the nodes
257 NodeConnector forwardPort = null;
259 if(clientNode.getNodeIDString().equals(destNode.getNodeIDString())){
261 forwardPort = hnConnector.getnodeConnector();
263 lbsLogger.info("Both source (client) and destination pool machine is connected to same switch nodes. Respective ports are - {},{}",forwardPort,inPkt.getIncomingNodeConnector());
267 Path route = this.routing.getRoute(clientNode, destNode);
269 lbsLogger.info("Path between source (client) and destination switch nodes : {}",route.toString());
271 forwardPort = route.getEdges().get(0).getTailNodeConnector();
275 if(installLoadBalancerFlow(client,
279 hnConnector.getDataLayerAddressBytes(),
281 LBConst.FORWARD_DIRECTION_LB_FLOW)){
282 lbsLogger.info("Traffic from client : {} will be routed " +
283 "to pool machine : {}",client,poolMemberIp);
285 lbsLogger.error("Not able to route traffic from client : {}",client );
288 if(installLoadBalancerFlow(client,
293 inPkt.getIncomingNodeConnector(),
294 LBConst.REVERSE_DIRECTION_LB_FLOW)){
295 lbsLogger.info("Flow rule installed to change the source ip/mac from " +
296 "pool machine ip {} to VIP {} for traffic coming pool machine",poolMemberIp,vip);
298 lbsLogger.error("Not able to route traffic from client : {}",client );
300 }catch (UnknownHostException e) {
301 lbsLogger.error("Pool member not found in the network : {}",e.getMessage());
302 lbsLogger.error("",e);
308 return PacketResult.IGNORED;
312 * This method installs the flow rule for routing the traffic between two hosts.
313 * @param source Traffic is sent by this source
314 * @param dest Traffic is destined to this destination (VIP)
315 * @param sourceSwitch Switch from where controller received the packet
316 * @param destMachineIp IP address of the pool member where traffic needs to be routed
317 * @param destMachineMac MAC address of the pool member where traffic needs to be routed
318 * @param outport Use this port to send out traffic
319 * @param flowDirection FORWARD_DIRECTION_LB_FLOW or REVERSE_DIRECTION_LB_FLOW
320 * @return true If flow installation was successful
322 * @throws UnknownHostException
324 private boolean installLoadBalancerFlow(Client source,
327 String destMachineIp,
328 byte[] destMachineMac,
329 NodeConnector outport,
330 int flowDirection) throws UnknownHostException{
332 Match match = new Match();
333 List<Action> actions = new ArrayList<Action>();
335 if(flowDirection == LBConst.FORWARD_DIRECTION_LB_FLOW){
336 match.setField(MatchType.DL_TYPE, EtherTypes.IPv4.shortValue());
337 match.setField(MatchType.NW_SRC, InetAddress.getByName(source.getIp()));
338 match.setField(MatchType.NW_DST, InetAddress.getByName(dest.getIp()));
339 match.setField(MatchType.NW_PROTO, IPProtocols.getProtocolNumberByte(dest.getProtocol()));
340 match.setField(MatchType.TP_SRC, source.getPort());
341 match.setField(MatchType.TP_DST, dest.getPort());
343 actions.add(new SetNwDst(InetAddress.getByName(destMachineIp)));
344 actions.add(new SetDlDst(destMachineMac));
347 if(flowDirection == LBConst.REVERSE_DIRECTION_LB_FLOW){
348 match.setField(MatchType.DL_TYPE, EtherTypes.IPv4.shortValue());
349 match.setField(MatchType.NW_SRC, InetAddress.getByName(destMachineIp));
350 match.setField(MatchType.NW_DST, InetAddress.getByName(source.getIp()));
351 match.setField(MatchType.NW_PROTO, IPProtocols.getProtocolNumberByte(source.getProtocol()));
352 match.setField(MatchType.TP_SRC, dest.getPort());
353 match.setField(MatchType.TP_DST,source.getPort());
355 actions.add(new SetNwSrc(InetAddress.getByName(dest.getIp())));
356 actions.add(new SetDlSrc(destMachineMac));
359 actions.add(new Output(outport));
361 // Make sure the priority for IP switch entries is
362 // set to a level just above default drop entries
364 Flow flow = new Flow(match, actions);
365 flow.setIdleTimeout((short) 5);
366 flow.setHardTimeout((short) 0);
367 flow.setPriority(LB_IPSWITCH_PRIORITY);
369 String policyName = source.getIp()+":"+source.getProtocol()+":"+source.getPort();
370 String flowName =null;
372 if(flowDirection == LBConst.FORWARD_DIRECTION_LB_FLOW){
373 flowName = "["+policyName+":"+source.getIp() + ":"+dest.getIp()+"]";
376 if(flowDirection == LBConst.REVERSE_DIRECTION_LB_FLOW){
378 flowName = "["+policyName+":"+dest.getIp() + ":"+source.getIp()+"]";
381 FlowEntry fEntry = new FlowEntry(policyName, flowName, flow, sourceSwitch);
383 lbsLogger.info("Install flow entry {} on node {}",fEntry.toString(),sourceSwitch.toString());
385 if(!this.ruleManager.checkFlowEntryConflict(fEntry)){
386 if(this.ruleManager.installFlowEntry(fEntry).isSuccess()){
389 lbsLogger.error("Error in installing flow entry to node : {}",sourceSwitch);
392 lbsLogger.error("Conflicting flow entry exists : {}",fEntry.toString());
398 * Function called by the dependency manager when all the required
399 * dependencies are satisfied
402 void init(Component c) {
403 Dictionary<?, ?> props = c.getServiceProperties();
405 this.containerName = (String) props.get("containerName");
407 lbsLogger.info("Running container name:" + this.containerName);
410 // In the Global instance case the containerName is empty
411 this.containerName = "";
413 lbsLogger.info(configManager.toString());
417 * Function called by the dependency manager when at least one
418 * dependency become unsatisfied or when the component is shutting
419 * down because for example bundle is being stopped.
426 * Function called by dependency manager after "init ()" is called
427 * and after the services provided by the class are registered in
428 * the service registry
435 * Function called by the dependency manager before the services
436 * exported by the component are unregistered, this will be
437 * followed by a "destroy ()" calls
444 * All the methods below are just proxy methods to direct the REST API requests to configuration
445 * manager. We need this redirection as currently, opendaylight supports only one
446 * implementation of the service.
449 public Set<VIP> getAllVIPs() {
450 return configManager.getAllVIPs();
454 public boolean vipExists(String name, String ip, String protocol,
455 short protocolPort, String poolName) {
456 return configManager.vipExists(name, ip, protocol, protocolPort, poolName);
460 public boolean vipExists(VIP vip) {
461 return configManager.vipExists(vip);
465 public VIP createVIP(String name, String ip, String protocol,
466 short protocolPort, String poolName) {
467 return configManager.createVIP(name, ip, protocol, protocolPort, poolName);
471 public VIP updateVIP(String name, String poolName) {
472 return configManager.updateVIP(name, poolName);
476 public VIP deleteVIP(String name) {
477 return configManager.deleteVIP(name);
481 public boolean memberExists(String name, String memberIP, String poolName) {
482 return configManager.memberExists(name, memberIP, poolName);
486 public Set<PoolMember> getAllPoolMembers(String poolName) {
488 return configManager.getAllPoolMembers(poolName);
492 public PoolMember addPoolMember(String name,
495 return configManager.addPoolMember(name, memberIP, poolName);
499 public PoolMember removePoolMember(String name, String poolName) {
501 return configManager.removePoolMember(name, poolName);
505 public Set<Pool> getAllPools() {
507 return configManager.getAllPools();
511 public Pool getPool(String poolName) {
512 return configManager.getPool(poolName);
516 public boolean poolExists(String name, String lbMethod) {
517 return configManager.poolExists(name, lbMethod);
521 public Pool createPool(String name, String lbMethod) {
522 return configManager.createPool(name, lbMethod);
526 public Pool deletePool(String poolName) {
527 return configManager.deletePool(poolName);
531 public boolean vipExists(String name) {
532 return configManager.vipExists(name);
536 public boolean memberExists(String name, String poolName) {
537 return configManager.memberExists(name, poolName);
541 public boolean poolExists(String name) {
542 return configManager.poolExists(name);
546 public String getVIPAttachedPool(String name) {
547 return configManager.getVIPAttachedPool(name);