Redirecting Caught and Uncaught Exceptions to OSGI Console and Log File
[controller.git] / opendaylight / samples / loadbalancer / src / main / java / org / opendaylight / controller / samples / loadbalancer / internal / LoadBalancerService.java
1 /*
2  * Copyright IBM Corporation, 2013.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.controller.samples.loadbalancer.internal;
9
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;
15 import java.util.Set;
16
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;
57
58 /**
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)
67  * 
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).
71  * Assumptions:
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).
74  *      
75  *      2. Only one server pool can be be assigned to a VIP.
76  *      
77  *      3. All flow rules are installed with an idle timeout of 5 seconds.
78  *      
79  *      4. Packets to a VIP must leave the OpenFlow  cluster from the same switch from where
80  *      it entered it.
81  *      
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. 
85  *
86  */
87 public class LoadBalancerService implements IListenDataPacket, IConfigManager{
88     
89     /*
90      * Logger instance
91      */
92     private static Logger lbsLogger = LoggerFactory.getLogger(LoadBalancerService.class);
93     
94     /*
95      * Single instance of the configuration manager. Application passes this reference to all
96      * the new policies implemented for load balancing.
97      */
98     private static ConfigManager configManager = new ConfigManager();
99     
100     /*
101      * Round robing policy instance. Need to implement factory patterns to get
102      * policy instance.
103      */
104     private static RoundRobinLBPolicy rrLBMethod= new RoundRobinLBPolicy(configManager);
105     
106     /*
107      * Random policy instance.
108      */
109     private static RandomLBPolicy ranLBMethod= new RandomLBPolicy(configManager);
110     
111     /*
112      * Reference to the data packet service
113      */
114     private IDataPacketService dataPacketService = null;
115     
116     /*
117      * Reference to the host tracker service
118      */
119     private IfIptoHost hostTracker;
120     
121     /*
122      * Reference to the forwarding manager
123      */
124     private IForwardingRulesManager ruleManager;
125     
126     /*
127      * Reference to the routing service
128      */
129     private IRouting routing;
130     
131     /*
132      * Load balancer application installs all flows with priority 2.
133      */
134     private static short LB_IPSWITCH_PRIORITY = 2;
135
136     /*
137      * Name of the container where this application will register.
138      */
139     private String containerName = null;
140
141     /*
142      * Set/unset methods for the service instance that load balancer 
143      * service requires
144      */
145     public String getContainerName() {
146         if (containerName == null)
147             return GlobalConstants.DEFAULT.toString();
148         return containerName;
149     }
150     
151     void setDataPacketService(IDataPacketService s) {
152         this.dataPacketService = s;
153     }
154
155     void unsetDataPacketService(IDataPacketService s) {
156         if (this.dataPacketService == s) {
157             this.dataPacketService = null;
158         }
159     }
160     
161     public void setRouting(IRouting routing) {
162         this.routing = routing;
163     }
164
165     public void unsetRouting(IRouting routing) {
166         if (this.routing == routing) {
167             this.routing = null;
168         }
169     }
170
171     public void setHostTracker(IfIptoHost hostTracker) {
172         lbsLogger.debug("Setting HostTracker");
173         this.hostTracker = hostTracker;
174     }
175
176     public void unsetHostTracker(IfIptoHost hostTracker) {
177         if (this.hostTracker == hostTracker) {
178             this.hostTracker = null;
179         }
180     }
181
182     public void setForwardingRulesManager(
183             IForwardingRulesManager forwardingRulesManager) {
184         lbsLogger.debug("Setting ForwardingRulesManager");
185         this.ruleManager = forwardingRulesManager;
186     }
187
188     public void unsetForwardingRulesManager(
189             IForwardingRulesManager forwardingRulesManager) {
190         if (this.ruleManager == forwardingRulesManager) {
191             this.ruleManager = null;
192         }
193     }
194
195     /**
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. 
205      */
206     @Override
207     public PacketResult receiveDataPacket(RawPacket inPkt){
208         
209         if (inPkt == null) {
210             return PacketResult.IGNORED;
211         }
212         
213         Packet formattedPak = this.dataPacketService.decodeDataPacket(inPkt);
214         
215         if (formattedPak instanceof Ethernet) {
216             byte[] vipMacAddr = ((Ethernet) formattedPak).getDestinationMACAddress();
217             Object ipPkt = formattedPak.getPayload();
218             
219             if (ipPkt instanceof IPv4) {
220                 
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.TCP.toString())){
225                     
226                     lbsLogger.debug("Packet protocol : {}",IPProtocols.getProtocolName(ipv4Pkt.getProtocol()));
227                     Client client = new LBUtil().getClientFromPacket(ipv4Pkt);
228                     VIP vip = new LBUtil().getVIPFromPacket(ipv4Pkt);
229                     
230                     if(configManager.vipExists(vip)){
231                         VIP vipWithPoolName = configManager.getVIPWithPoolName(vip);
232                         String poolMemberIp = null;
233                         if(configManager.getPool(vipWithPoolName.getPoolName()).getLbMethod().equalsIgnoreCase(LBConst.ROUND_ROBIN_LB_METHOD)){
234                             
235                             poolMemberIp = rrLBMethod.getPoolMemberForClient(client,vipWithPoolName);
236                         }
237                         
238                         if(configManager.getPool(vipWithPoolName.getPoolName()).getLbMethod().equalsIgnoreCase(LBConst.RANDOM_LB_METHOD)){
239                             poolMemberIp = ranLBMethod.getPoolMemberForClient(client,vipWithPoolName);
240                         }
241                         
242                         try {
243                             
244                             Node clientNode = inPkt.getIncomingNodeConnector().getNode();
245                             HostNodeConnector hnConnector = this.hostTracker.hostFind(InetAddress.getByName(poolMemberIp));
246                             
247                             Node destNode = hnConnector.getnodeconnectorNode();
248                             
249                             lbsLogger.debug("Client is connected to switch : {}",clientNode.toString());
250                             lbsLogger.debug("Destination pool machine is connected to switch : {}",destNode.toString());
251                             
252                             //Get path between both the nodes
253                             Path route = this.routing.getRoute(clientNode, destNode);
254                             
255                             lbsLogger.info("Path between source (client) and destination switch nodes : {}",route.toString());
256                             
257                             NodeConnector forwardPort = route.getEdges().get(0).getTailNodeConnector();
258                             
259                             if(installLoadBalancerFlow(client,
260                                                             vip,
261                                                             clientNode,
262                                                             poolMemberIp,
263                                                             hnConnector.getDataLayerAddressBytes(),
264                                                             forwardPort,
265                                                             LBConst.FORWARD_DIRECTION_LB_FLOW)){
266                                 lbsLogger.info("Traffic from client : {} will be routed " +
267                                                             "to pool machine : {}",client,poolMemberIp);
268                             }else{
269                                 lbsLogger.error("Not able to route traffic from client : {}",client );
270                             }
271                             
272                             if(installLoadBalancerFlow(client,
273                                                             vip,
274                                                             clientNode,
275                                                             poolMemberIp,
276                                                             vipMacAddr,
277                                                             inPkt.getIncomingNodeConnector(),
278                                                             LBConst.REVERSE_DIRECTION_LB_FLOW)){
279                                 lbsLogger.info("Flow rule installed to change the source ip/mac from " +
280                                                             "pool machine ip {} to VIP {} for traffic coming pool machine",poolMemberIp,vip);
281                             }else{
282                                 lbsLogger.error("Not able to route traffic from client : {}",client );
283                             }
284                         }catch (UnknownHostException e) {
285                             lbsLogger.error("Pool member not found  in the network : {}",e.getMessage());
286                             lbsLogger.error("",e);
287                         }
288                     }
289                 }
290             }
291         }
292         return PacketResult.IGNORED;
293     }
294     
295     /*
296      * This method installs the flow rule for routing the traffic between two hosts.
297      * @param source    Traffic is sent by this source
298      * @param dest      Traffic is destined to this destination (VIP)
299      * @param sourceSwitch      Switch from where controller received the packet
300      * @param destMachineIp     IP address of the pool member where traffic needs to be routed
301      * @param destMachineMac    MAC address of the pool member where traffic needs to be routed
302      * @param outport   Use this port to send out traffic
303      * @param flowDirection     FORWARD_DIRECTION_LB_FLOW or REVERSE_DIRECTION_LB_FLOW
304      * @return  true     If flow installation was successful
305      *          false   else
306      *          @throws UnknownHostException
307      */
308     private boolean installLoadBalancerFlow(Client source,
309                                             VIP dest,
310                                             Node sourceSwitch,
311                                             String destMachineIp,
312                                             byte[] destMachineMac, 
313                                             NodeConnector outport,
314                                             int flowDirection) throws UnknownHostException{
315         
316         Match match = new Match();
317         List<Action> actions = new ArrayList<Action>();
318         
319         if(flowDirection == LBConst.FORWARD_DIRECTION_LB_FLOW){
320             match.setField(MatchType.DL_TYPE, EtherTypes.IPv4.shortValue());
321             match.setField(MatchType.NW_SRC, InetAddress.getByName(source.getIp()));
322             match.setField(MatchType.NW_DST, InetAddress.getByName(dest.getIp()));
323             match.setField(MatchType.NW_PROTO, IPProtocols.getProtocolNumberByte(dest.getProtocol()));
324             match.setField(MatchType.TP_SRC, source.getPort());
325             match.setField(MatchType.TP_DST, dest.getPort());
326             
327             actions.add(new SetNwDst(InetAddress.getByName(destMachineIp)));
328             actions.add(new SetDlDst(destMachineMac));
329         }
330         
331         if(flowDirection == LBConst.REVERSE_DIRECTION_LB_FLOW){
332             match.setField(MatchType.DL_TYPE, EtherTypes.IPv4.shortValue());
333             match.setField(MatchType.NW_SRC, InetAddress.getByName(destMachineIp));
334             match.setField(MatchType.NW_DST, InetAddress.getByName(source.getIp()));
335             match.setField(MatchType.NW_PROTO, IPProtocols.getProtocolNumberByte(source.getProtocol()));
336             match.setField(MatchType.TP_SRC, dest.getPort());
337             match.setField(MatchType.TP_DST,source.getPort());
338             
339             actions.add(new SetNwSrc(InetAddress.getByName(dest.getIp())));
340             actions.add(new SetDlSrc(destMachineMac));
341         }
342         
343         actions.add(new Output(outport));
344         
345         // Make sure the priority for IP switch entries is
346         // set to a level just above default drop entries
347         
348         Flow flow = new Flow(match, actions);
349         flow.setIdleTimeout((short) 5);
350         flow.setHardTimeout((short) 0);
351         flow.setPriority(LB_IPSWITCH_PRIORITY);
352         
353         String policyName = source.getIp()+":"+source.getProtocol()+":"+source.getPort();
354         String flowName =null;
355         
356         if(flowDirection == LBConst.FORWARD_DIRECTION_LB_FLOW){
357             flowName = "["+policyName+":"+source.getIp() + ":"+dest.getIp()+"]";
358         }
359         
360         if(flowDirection == LBConst.REVERSE_DIRECTION_LB_FLOW){
361             
362             flowName = "["+policyName+":"+dest.getIp() + ":"+source.getIp()+"]";
363         }
364         
365         FlowEntry fEntry = new FlowEntry(policyName, flowName, flow, sourceSwitch);
366         
367         lbsLogger.info("Install flow entry {} on node {}",fEntry.toString(),sourceSwitch.toString());
368         
369         if(!this.ruleManager.checkFlowEntryConflict(fEntry)){
370             if(this.ruleManager.installFlowEntry(fEntry).isSuccess()){
371                 return true;
372             }else{
373                 lbsLogger.error("Error in installing flow entry to node : {}",sourceSwitch);
374             }
375         }else{
376             lbsLogger.error("Conflicting flow entry exists : {}",fEntry.toString());
377         }
378         return false;
379     }
380     
381     /**
382      * Function called by the dependency manager when all the required
383      * dependencies are satisfied
384      *
385      */
386     void init(Component c) {
387         Dictionary<?, ?> props = c.getServiceProperties();
388         if (props != null) {
389             this.containerName = (String) props.get("containerName");
390             
391             lbsLogger.info("Running container name:" + this.containerName);
392         }else {
393             
394             // In the Global instance case the containerName is empty
395             this.containerName = "";
396         }
397         lbsLogger.info(configManager.toString());
398     }
399     
400     /**
401      * Function called by the dependency manager when at least one
402      * dependency become unsatisfied or when the component is shutting
403      * down because for example bundle is being stopped.
404      *
405      */
406     void destroy() {
407     }
408
409     /**
410      * Function called by dependency manager after "init ()" is called
411      * and after the services provided by the class are registered in
412      * the service registry
413      *
414      */
415     void start() {
416     }
417
418     /**
419      * Function called by the dependency manager before the services
420      * exported by the component are unregistered, this will be
421      * followed by a "destroy ()" calls
422      *
423      */
424     void stop() {
425     }
426
427     /*
428      * All the methods below are just proxy methods to direct the REST API requests to configuration
429      * manager. We need this redirection as currently, opendaylight supports only one 
430      * implementation of the service. 
431      */
432     @Override
433     public Set<VIP> getAllVIPs() {
434         return configManager.getAllVIPs();
435     }
436     
437     @Override
438     public boolean vipExists(String name, String ip, String protocol,
439                                 short protocolPort, String poolName) {
440         return configManager.vipExists(name, ip, protocol, protocolPort, poolName);
441     }
442     
443     @Override
444     public boolean vipExists(VIP vip) {
445         return configManager.vipExists(vip);
446     }
447     
448     @Override
449     public VIP createVIP(String name, String ip, String protocol,
450                             short protocolPort, String poolName) {
451         return configManager.createVIP(name, ip, protocol, protocolPort, poolName);
452     }
453     
454     @Override
455     public VIP updateVIP(String name, String poolName) {
456         return configManager.updateVIP(name, poolName);
457     }
458     
459     @Override
460     public VIP deleteVIP(String name) {
461         return configManager.deleteVIP(name);
462     }
463     
464     @Override
465     public boolean memberExists(String name, String memberIP, String poolName) {
466         return configManager.memberExists(name, memberIP, poolName);
467     }
468     
469     @Override
470     public Set<PoolMember> getAllPoolMembers(String poolName) {
471         
472         return configManager.getAllPoolMembers(poolName);
473     }
474     
475     @Override
476     public PoolMember addPoolMember(String name, 
477                                     String memberIP,
478                                     String poolName) {
479         return configManager.addPoolMember(name, memberIP, poolName);
480     }
481     
482     @Override
483     public PoolMember removePoolMember(String name, String poolName) {
484         
485         return configManager.removePoolMember(name, poolName);
486     }
487     
488     @Override
489     public Set<Pool> getAllPools() {
490         
491         return configManager.getAllPools();
492     }
493     
494     @Override
495     public Pool getPool(String poolName) {
496         return configManager.getPool(poolName);
497     }
498     
499     @Override
500     public boolean poolExists(String name, String lbMethod) {
501         return configManager.poolExists(name, lbMethod);
502     }
503     
504     @Override
505     public Pool createPool(String name, String lbMethod) {
506         return configManager.createPool(name, lbMethod);
507     }
508     
509     @Override
510     public Pool deletePool(String poolName) {
511         return configManager.deletePool(poolName);
512     }
513     
514     @Override
515     public boolean vipExists(String name) {
516         return configManager.vipExists(name);
517     }
518     
519     @Override
520     public boolean memberExists(String name, String poolName) {
521         return configManager.memberExists(name, poolName);
522     }
523     
524     @Override
525     public boolean poolExists(String name) {
526         return configManager.poolExists(name);
527     }
528     
529     @Override
530     public String getVIPAttachedPool(String name) {
531         return configManager.getVIPAttachedPool(name);
532     }
533 }