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