Fixed two-phase commit bug when change originated in DOM Broker
[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.UDP.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(vipWithPoolName.getPoolName() == null){
234                             lbsLogger.error("No pool attached. Please attach pool with the VIP -- {}",vip);
235                             return PacketResult.IGNORED;
236                         }
237                         if(configManager.getPool(vipWithPoolName.getPoolName()).getLbMethod().equalsIgnoreCase(LBConst.ROUND_ROBIN_LB_METHOD)){
238
239                             poolMemberIp = rrLBMethod.getPoolMemberForClient(client,vipWithPoolName);
240                         }
241
242                         if(configManager.getPool(vipWithPoolName.getPoolName()).getLbMethod().equalsIgnoreCase(LBConst.RANDOM_LB_METHOD)){
243                             poolMemberIp = ranLBMethod.getPoolMemberForClient(client,vipWithPoolName);
244                         }
245
246                         try {
247
248                             Node clientNode = inPkt.getIncomingNodeConnector().getNode();
249                             HostNodeConnector hnConnector = this.hostTracker.hostFind(InetAddress.getByName(poolMemberIp));
250
251                             Node destNode = hnConnector.getnodeconnectorNode();
252
253                             lbsLogger.debug("Client is connected to switch : {}",clientNode.toString());
254                             lbsLogger.debug("Destination pool machine is connected to switch : {}",destNode.toString());
255
256                             //Get path between both the nodes
257                             NodeConnector forwardPort = null;
258
259                             if(clientNode.getNodeIDString().equals(destNode.getNodeIDString())){
260
261                                 forwardPort = hnConnector.getnodeConnector();
262
263                                 lbsLogger.info("Both source (client) and destination pool machine is connected to same switch nodes. Respective ports are - {},{}",forwardPort,inPkt.getIncomingNodeConnector());
264
265                             }else{
266
267                                 Path route = this.routing.getRoute(clientNode, destNode);
268
269                                 lbsLogger.info("Path between source (client) and destination switch nodes : {}",route.toString());
270
271                                 forwardPort = route.getEdges().get(0).getTailNodeConnector();
272
273                             }
274
275                             if(installLoadBalancerFlow(client,
276                                                             vip,
277                                                             clientNode,
278                                                             poolMemberIp,
279                                                             hnConnector.getDataLayerAddressBytes(),
280                                                             forwardPort,
281                                                             LBConst.FORWARD_DIRECTION_LB_FLOW)){
282                                 lbsLogger.info("Traffic from client : {} will be routed " +
283                                                             "to pool machine : {}",client,poolMemberIp);
284                             }else{
285                                 lbsLogger.error("Not able to route traffic from client : {}",client );
286                             }
287
288                             if(installLoadBalancerFlow(client,
289                                                             vip,
290                                                             clientNode,
291                                                             poolMemberIp,
292                                                             vipMacAddr,
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);
297                             }else{
298                                 lbsLogger.error("Not able to route traffic from client : {}",client );
299                             }
300                         }catch (UnknownHostException e) {
301                             lbsLogger.error("Pool member not found  in the network : {}",e.getMessage());
302                             lbsLogger.error("",e);
303                         }
304                     }
305                 }
306             }
307         }
308         return PacketResult.IGNORED;
309     }
310
311     /*
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
321      *          false   else
322      *          @throws UnknownHostException
323      */
324     private boolean installLoadBalancerFlow(Client source,
325                                             VIP dest,
326                                             Node sourceSwitch,
327                                             String destMachineIp,
328                                             byte[] destMachineMac,
329                                             NodeConnector outport,
330                                             int flowDirection) throws UnknownHostException{
331
332         Match match = new Match();
333         List<Action> actions = new ArrayList<Action>();
334
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());
342
343             actions.add(new SetNwDst(InetAddress.getByName(destMachineIp)));
344             actions.add(new SetDlDst(destMachineMac));
345         }
346
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());
354
355             actions.add(new SetNwSrc(InetAddress.getByName(dest.getIp())));
356             actions.add(new SetDlSrc(destMachineMac));
357         }
358
359         actions.add(new Output(outport));
360
361         // Make sure the priority for IP switch entries is
362         // set to a level just above default drop entries
363
364         Flow flow = new Flow(match, actions);
365         flow.setIdleTimeout((short) 5);
366         flow.setHardTimeout((short) 0);
367         flow.setPriority(LB_IPSWITCH_PRIORITY);
368
369         String policyName = source.getIp()+":"+source.getProtocol()+":"+source.getPort();
370         String flowName =null;
371
372         if(flowDirection == LBConst.FORWARD_DIRECTION_LB_FLOW){
373             flowName = "["+policyName+":"+source.getIp() + ":"+dest.getIp()+"]";
374         }
375
376         if(flowDirection == LBConst.REVERSE_DIRECTION_LB_FLOW){
377
378             flowName = "["+policyName+":"+dest.getIp() + ":"+source.getIp()+"]";
379         }
380
381         FlowEntry fEntry = new FlowEntry(policyName, flowName, flow, sourceSwitch);
382
383         lbsLogger.info("Install flow entry {} on node {}",fEntry.toString(),sourceSwitch.toString());
384
385         if(!this.ruleManager.checkFlowEntryConflict(fEntry)){
386             if(this.ruleManager.installFlowEntry(fEntry).isSuccess()){
387                 return true;
388             }else{
389                 lbsLogger.error("Error in installing flow entry to node : {}",sourceSwitch);
390             }
391         }else{
392             lbsLogger.error("Conflicting flow entry exists : {}",fEntry.toString());
393         }
394         return false;
395     }
396
397     /**
398      * Function called by the dependency manager when all the required
399      * dependencies are satisfied
400      *
401      */
402     void init(Component c) {
403         Dictionary<?, ?> props = c.getServiceProperties();
404         if (props != null) {
405             this.containerName = (String) props.get("containerName");
406
407             lbsLogger.info("Running container name:" + this.containerName);
408         }else {
409
410             // In the Global instance case the containerName is empty
411             this.containerName = "";
412         }
413         lbsLogger.info(configManager.toString());
414     }
415
416     /**
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.
420      *
421      */
422     void destroy() {
423     }
424
425     /**
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
429      *
430      */
431     void start() {
432     }
433
434     /**
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
438      *
439      */
440     void stop() {
441     }
442
443     /*
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.
447      */
448     @Override
449     public Set<VIP> getAllVIPs() {
450         return configManager.getAllVIPs();
451     }
452
453     @Override
454     public boolean vipExists(String name, String ip, String protocol,
455                                 short protocolPort, String poolName) {
456         return configManager.vipExists(name, ip, protocol, protocolPort, poolName);
457     }
458
459     @Override
460     public boolean vipExists(VIP vip) {
461         return configManager.vipExists(vip);
462     }
463
464     @Override
465     public VIP createVIP(String name, String ip, String protocol,
466                             short protocolPort, String poolName) {
467         return configManager.createVIP(name, ip, protocol, protocolPort, poolName);
468     }
469
470     @Override
471     public VIP updateVIP(String name, String poolName) {
472         return configManager.updateVIP(name, poolName);
473     }
474
475     @Override
476     public VIP deleteVIP(String name) {
477         return configManager.deleteVIP(name);
478     }
479
480     @Override
481     public boolean memberExists(String name, String memberIP, String poolName) {
482         return configManager.memberExists(name, memberIP, poolName);
483     }
484
485     @Override
486     public Set<PoolMember> getAllPoolMembers(String poolName) {
487
488         return configManager.getAllPoolMembers(poolName);
489     }
490
491     @Override
492     public PoolMember addPoolMember(String name,
493                                     String memberIP,
494                                     String poolName) {
495         return configManager.addPoolMember(name, memberIP, poolName);
496     }
497
498     @Override
499     public PoolMember removePoolMember(String name, String poolName) {
500
501         return configManager.removePoolMember(name, poolName);
502     }
503
504     @Override
505     public Set<Pool> getAllPools() {
506
507         return configManager.getAllPools();
508     }
509
510     @Override
511     public Pool getPool(String poolName) {
512         return configManager.getPool(poolName);
513     }
514
515     @Override
516     public boolean poolExists(String name, String lbMethod) {
517         return configManager.poolExists(name, lbMethod);
518     }
519
520     @Override
521     public Pool createPool(String name, String lbMethod) {
522         return configManager.createPool(name, lbMethod);
523     }
524
525     @Override
526     public Pool deletePool(String poolName) {
527         return configManager.deletePool(poolName);
528     }
529
530     @Override
531     public boolean vipExists(String name) {
532         return configManager.vipExists(name);
533     }
534
535     @Override
536     public boolean memberExists(String name, String poolName) {
537         return configManager.memberExists(name, poolName);
538     }
539
540     @Override
541     public boolean poolExists(String name) {
542         return configManager.poolExists(name);
543     }
544
545     @Override
546     public String getVIPAttachedPool(String name) {
547         return configManager.getVIPAttachedPool(name);
548     }
549 }