Merge "Increase mdsal's notification queue size, make it configurable."
[controller.git] / opendaylight / samples / simpleforwarding / src / main / java / org / opendaylight / controller / samples / simpleforwarding / internal / SimpleForwardingImpl.java
1
2 /*
3  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
4  *
5  * This program and the accompanying materials are made available under the
6  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
7  * and is available at http://www.eclipse.org/legal/epl-v10.html
8  */
9
10 package org.opendaylight.controller.samples.simpleforwarding.internal;
11
12 import java.net.InetAddress;
13 import java.util.ArrayList;
14 import java.util.EnumSet;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.Iterator;
18 import java.util.LinkedList;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.concurrent.ConcurrentHashMap;
23 import java.util.concurrent.ConcurrentMap;
24 import java.util.Timer;
25 import java.util.TimerTask;
26
27 import org.opendaylight.controller.clustering.services.CacheConfigException;
28 import org.opendaylight.controller.clustering.services.CacheExistException;
29 import org.opendaylight.controller.clustering.services.IClusterContainerServices;
30 import org.opendaylight.controller.clustering.services.IClusterServices;
31 import org.opendaylight.controller.forwardingrulesmanager.FlowEntry;
32 import org.opendaylight.controller.forwardingrulesmanager.IForwardingRulesManager;
33 import org.opendaylight.controller.hosttracker.IfIptoHost;
34 import org.opendaylight.controller.hosttracker.IfNewHostNotify;
35 import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
36 import org.opendaylight.controller.sal.action.Action;
37 import org.opendaylight.controller.sal.action.Output;
38 import org.opendaylight.controller.sal.action.PopVlan;
39 import org.opendaylight.controller.sal.action.SetDlDst;
40 import org.opendaylight.controller.sal.action.SetVlanId;
41 import org.opendaylight.controller.sal.core.Edge;
42 import org.opendaylight.controller.sal.core.Node;
43 import org.opendaylight.controller.sal.core.NodeConnector;
44 import org.opendaylight.controller.sal.core.NodeConnector.NodeConnectorIDType;
45 import org.opendaylight.controller.sal.core.Path;
46 import org.opendaylight.controller.sal.core.Property;
47 import org.opendaylight.controller.sal.core.State;
48 import org.opendaylight.controller.sal.core.UpdateType;
49 import org.opendaylight.controller.sal.flowprogrammer.Flow;
50 import org.opendaylight.controller.sal.match.Match;
51 import org.opendaylight.controller.sal.match.MatchType;
52 import org.opendaylight.controller.sal.packet.Ethernet;
53 import org.opendaylight.controller.sal.packet.IDataPacketService;
54 import org.opendaylight.controller.sal.packet.IListenDataPacket;
55 import org.opendaylight.controller.sal.packet.IPv4;
56 import org.opendaylight.controller.sal.packet.Packet;
57 import org.opendaylight.controller.sal.packet.PacketResult;
58 import org.opendaylight.controller.sal.packet.RawPacket;
59 import org.opendaylight.controller.sal.routing.IListenRoutingUpdates;
60 import org.opendaylight.controller.sal.routing.IRouting;
61 import org.opendaylight.controller.sal.utils.EtherTypes;
62 import org.opendaylight.controller.sal.utils.NetUtils;
63 import org.opendaylight.controller.sal.utils.NodeConnectorCreator;
64 import org.opendaylight.controller.sal.utils.Status;
65 import org.opendaylight.controller.samples.simpleforwarding.HostNodePair;
66 import org.opendaylight.controller.switchmanager.IInventoryListener;
67 import org.opendaylight.controller.switchmanager.ISwitchManager;
68 import org.opendaylight.controller.topologymanager.ITopologyManager;
69 import org.slf4j.Logger;
70 import org.slf4j.LoggerFactory;
71
72 /**
73  * This class implements basic L3 forwarding within the managed devices.
74  * Forwarding is only done within configured subnets.</br>
75  * <br/>
76  * The basic flow is that the module listens for new hosts from the
77  * {@link org.opendaylight.controller.hosttracker.IfIptoHost HostTracker}
78  * service and on discovering a new host it first calls
79  * <tt>preparePerHostRules()</tt> to create a set of new rules that must be
80  * installed in the network. This is done by repeatedly calling
81  * <tt>updatePerHostRuleInSW()</tt> for each switch in the network. Then it
82  * installs those rules using <tt>installPerHostRules()</tt>.
83  */
84 public class SimpleForwardingImpl implements IfNewHostNotify,
85         IListenRoutingUpdates, IInventoryListener, IListenDataPacket {
86     private static Logger log = LoggerFactory.getLogger(SimpleForwardingImpl.class);
87     private static short DEFAULT_IPSWITCH_PRIORITY = 1;
88     static final String FORWARDING_RULES_CACHE_NAME = "forwarding.ipswitch.rules";
89     private IfIptoHost hostTracker;
90     private IForwardingRulesManager frm;
91     private ITopologyManager topologyManager;
92     private IRouting routing;
93
94     /**
95      * The set of all forwarding rules: (host) -> (switch -> flowmod). Note that
96      * the host includes an attachment point and that while the switch appears
97      * to be a switch's port, in actuality it is a special port which just
98      * represents the switch.
99      */
100     private ConcurrentMap<HostNodePair, HashMap<NodeConnector, FlowEntry>> rulesDB;
101     private Map<Node, List<FlowEntry>> tobePrunedPos = new HashMap<Node, List<FlowEntry>>();
102     private IClusterContainerServices clusterContainerService = null;
103     private ISwitchManager switchManager;
104     private IDataPacketService dataPacketService;
105
106     /**
107      * Ip packets that are punted may not have their destination known by hostTracker at the time it
108      * is presented to SimpleForwardingImpl. Instead of dropping the packet, we will keep it around
109      * for a 'little' while, to accommodate any transients. See bug 590 for more details.
110      */
111     private class PendingPacketData {
112         private final static byte MAX_AGE = 2;
113
114         public final IPv4 pkt;
115         public final NodeConnector incomingNodeConnector;
116         private byte age;
117
118         public PendingPacketData(IPv4 pkt, NodeConnector incomingNodeConnector) {
119             this.pkt = pkt;
120             this.incomingNodeConnector = incomingNodeConnector;
121             this.age = 0;
122         }
123         boolean bumpAgeAndCheckIfTooOld() { return ++age > MAX_AGE; }
124     }
125     private static final int MAX_PENDING_PACKET_DESTINATIONS = 64;
126     private ConcurrentMap<InetAddress, PendingPacketData> pendingPacketDestinations;
127     private Timer pendingPacketsAgerTimer;
128
129     private class PendingPacketsAgerTimerHandler extends TimerTask {
130         @Override
131         public void run() {
132             if (pendingPacketDestinations == null) {
133                 return;
134             }
135             try {
136                 Iterator<ConcurrentMap.Entry<InetAddress, PendingPacketData>> iterator =
137                         pendingPacketDestinations.entrySet().iterator();
138                 while (iterator.hasNext()) {
139                     ConcurrentHashMap.Entry<InetAddress, PendingPacketData> entry = iterator.next();
140                     InetAddress dIP = entry.getKey();
141                     PendingPacketData pendingPacketData = entry.getValue();
142
143                     if (pendingPacketData.bumpAgeAndCheckIfTooOld()) {
144                         iterator.remove(); // safe to remove while iterating...
145                         log.debug("Pending packet for {} has been aged out", dIP);
146                     } else {
147                         /** Replace the entry for a key only if currently mapped to some value.
148                          * This will protect the concurrent map against a race where this thread
149                          * would be re-adding an entry that just got taken out.
150                          */
151                         pendingPacketDestinations.replace(dIP, pendingPacketData);
152                     }
153                 }
154             } catch (IllegalStateException e) {
155                 log.debug("IllegalStateException Received by PendingPacketsAgerTimerHandler from: {}",
156                         e.getMessage());
157             }
158         }
159     }
160
161     /**
162      * Add punted packet to pendingPackets
163      */
164     private void addToPendingPackets(InetAddress dIP, IPv4 pkt, NodeConnector incomingNodeConnector) {
165         if (pendingPacketDestinations.size() >= MAX_PENDING_PACKET_DESTINATIONS) {
166             log.info("Will not pend packet for {}: Too many destinations", dIP);
167             return;
168         }
169
170         /** TODO: The current implementation allows for up to 1 pending packet per InetAddress.
171          * This limitation is done for sake of simplicity. A potential enhancement could be to use a
172          * ConcurrentMultiMap instead of ConcurrentMap.
173          */
174         if (pendingPacketDestinations.containsKey(dIP)) {
175             log.trace("Will not pend packet for {}: Already have a packet pending", dIP);
176             return;
177         }
178
179         PendingPacketData pendingPacketData = new PendingPacketData(pkt, incomingNodeConnector);
180         pendingPacketDestinations.put(dIP, pendingPacketData);
181         log.debug("Pending packet for {}", dIP);
182     }
183
184     /**
185      * Send punted packet to given destination. This is invoked when there is a certain level of
186      * hope that the destination is known by hostTracker.
187      */
188     private void sendPendingPacket(InetAddress dIP) {
189         pendingPacketDestinations.get(dIP);
190         PendingPacketData pendingPacketData = pendingPacketDestinations.get(dIP);
191         if (pendingPacketData != null) {
192             handlePuntedIPPacket(pendingPacketData.pkt, pendingPacketData.incomingNodeConnector, false);
193             log.trace("Packet for {} is no longer pending", dIP);
194             pendingPacketDestinations.remove(dIP);
195         }
196     }
197
198     /**
199      * Return codes from the programming of the perHost rules in HW
200      */
201     public enum RulesProgrammingReturnCode {
202         SUCCESS, FAILED_FEW_SWITCHES, FAILED_ALL_SWITCHES, FAILED_WRONG_PARAMS
203     }
204     public void setDataPacketService(IDataPacketService s) {
205         log.debug("Setting dataPacketService");
206         this.dataPacketService = s;
207     }
208
209     public void unsetDataPacketService(IDataPacketService s) {
210         if (this.dataPacketService == s) {
211             this.dataPacketService = null;
212         }
213     }
214
215     public void setRouting(IRouting routing) {
216         log.debug("Setting routing");
217         this.routing = routing;
218     }
219
220     public void unsetRouting(IRouting routing) {
221         if (this.routing == routing) {
222             this.routing = null;
223         }
224     }
225
226     public void setTopologyManager(ITopologyManager topologyManager) {
227         log.debug("Setting topologyManager");
228         this.topologyManager = topologyManager;
229     }
230
231     public void unsetTopologyManager(ITopologyManager topologyManager) {
232         if (this.topologyManager == topologyManager) {
233             this.topologyManager = null;
234         }
235     }
236
237     public void setHostTracker(IfIptoHost hostTracker) {
238         log.debug("Setting HostTracker");
239         this.hostTracker = hostTracker;
240     }
241
242     public void setForwardingRulesManager(
243             IForwardingRulesManager forwardingRulesManager) {
244         log.debug("Setting ForwardingRulesManager");
245         this.frm = forwardingRulesManager;
246     }
247
248     public void unsetHostTracker(IfIptoHost hostTracker) {
249         if (this.hostTracker == hostTracker) {
250             this.hostTracker = null;
251         }
252     }
253
254     public void unsetForwardingRulesManager(
255             IForwardingRulesManager forwardingRulesManager) {
256         if (this.frm == forwardingRulesManager) {
257             this.frm = null;
258         }
259     }
260
261     /**
262      * Function called when the bundle gets activated
263      *
264      */
265     public void startUp() {
266         allocateCaches();
267         retrieveCaches();
268         nonClusterObjectCreate();
269     }
270
271     public void nonClusterObjectCreate() {
272         pendingPacketDestinations = new ConcurrentHashMap<InetAddress, PendingPacketData>();
273
274         /* Pending Packets Ager Timer to go off every 6 seconds to implement pending packet aging */
275         pendingPacketsAgerTimer = new Timer();
276         pendingPacketsAgerTimer.schedule(new PendingPacketsAgerTimerHandler(), 6000, 6000);
277     }
278
279     /**
280      * Function called when the bundle gets stopped
281      *
282      */
283     public void shutDown() {
284         log.debug("Destroy all the host Rules given we are shutting down");
285         uninstallPerHostRules();
286         destroyCaches();
287     }
288
289     private void allocateCaches() {
290         if (this.clusterContainerService == null) {
291             log.trace("un-initialized clusterContainerService, can't create cache");
292             return;
293         }
294
295         try {
296             clusterContainerService.createCache(FORWARDING_RULES_CACHE_NAME,
297                     EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
298         } catch (CacheExistException cee) {
299             log.error("\nCache already exists - destroy and recreate if needed");
300         } catch (CacheConfigException cce) {
301             log.error("\nCache configuration invalid - check cache mode");
302         }
303     }
304
305     @SuppressWarnings({ "unchecked" })
306     private void retrieveCaches() {
307         if (this.clusterContainerService == null) {
308             log.trace("un-initialized clusterContainerService, can't retrieve cache");
309             return;
310         }
311
312         rulesDB = (ConcurrentMap<HostNodePair, HashMap<NodeConnector, FlowEntry>>) clusterContainerService
313                 .getCache(FORWARDING_RULES_CACHE_NAME);
314         if (rulesDB == null) {
315             log.error("\nFailed to get rulesDB handle");
316         }
317     }
318
319     private void destroyCaches() {
320         if (this.clusterContainerService == null) {
321             log.trace("un-initialized clusterContainerService, can't destroy cache");
322             return;
323         }
324
325         clusterContainerService.destroyCache(FORWARDING_RULES_CACHE_NAME);
326     }
327
328     /**
329      * Populates <tt>rulesDB</tt> with rules specifying how to reach
330      * <tt>host</tt> from <tt>currNode</tt> assuming that:
331      * <ul>
332      * <li><tt>host</tt> is attached to <tt>rootNode</tt>
333      * <li><tt>link</tt> is the next part of the path to reach <tt>rootNode</tt>
334      * from <tt>currNode</tt>
335      * <li><tt>rulesDB.get(key)</tt> represents the list of rules stored about
336      * <tt>host</tt> at <tt>currNode</tt>
337      * </ul>
338      *
339      * @param host
340      *            The host to be reached.
341      * @param currNode
342      *            The current node being processed.
343      * @param rootNode
344      *            The node to be reached. Really, the switch which host is
345      *            attached to.
346      * @param link
347      *            The link to follow from curNode to get to rootNode
348      * @param key
349      *            The key to store computed rules at in the rulesDB. For now,
350      *            this is a {@link HostNodePair} of host and currNode.
351      */
352     private void updatePerHostRuleInSW(HostNodeConnector host, Node currNode,
353             Node rootNode, Edge link, HostNodePair key) {
354
355         // only the link parameter is optional
356         if (host == null || key == null || currNode == null || rootNode == null) {
357             return;
358         }
359
360         Set<NodeConnector> ports = new HashSet<NodeConnector>();
361         // add a special port of type ALL and port 0 to represent the node
362         // without specifying a port on that node
363         ports.add(NodeConnectorCreator.createNodeConnector(
364                 NodeConnectorIDType.ALL, NodeConnector.SPECIALNODECONNECTORID,
365                 currNode));
366
367         HashMap<NodeConnector, FlowEntry> pos = this.rulesDB.get(key);
368         if (pos == null) {
369             pos = new HashMap<NodeConnector, FlowEntry>();
370         }
371
372         for (NodeConnector inPort : ports) {
373             // skip the port connected to the target host
374             if (currNode.equals(rootNode)
375                     && (host.getnodeConnector().equals(inPort))) {
376                 continue;
377             }
378
379             // remove the current rule, if any
380             FlowEntry removed_po = pos.remove(inPort);
381             Match match = new Match();
382             List<Action> actions = new ArrayList<Action>();
383
384             // IP destination based forwarding on /32 entries only!
385             match.setField(MatchType.DL_TYPE, EtherTypes.IPv4.shortValue());
386             match.setField(MatchType.NW_DST, host.getNetworkAddress());
387
388             /* Action for the policy is to forward to a port except on the
389              * switch where the host sits, which is to rewrite also the MAC
390              * and to forward on the Host port */
391             NodeConnector outPort = null;
392
393             if (currNode.equals(rootNode)) {
394                 /* If we're at the root node, then rewrite the DL addr and
395                  * possibly pop the VLAN tag. This allows for MAC rewriting
396                  * in the core of the network assuming we can uniquely ID
397                  * packets based on IP address. */
398
399                 outPort = host.getnodeConnector();
400                 if (inPort.equals(outPort)) {
401                     // TODO: isn't this code skipped already by the above continue?
402                     // skip the host port
403                     continue;
404                 }
405                 actions.add(new SetDlDst(host.getDataLayerAddressBytes()));
406
407                 if (!inPort.getType().equals(NodeConnectorIDType.ALL)) {
408                     // Container mode: at the destination switch, we need to strip out the tag (VLAN)
409                     actions.add(new PopVlan());
410                 }
411             } else {
412                 // currNode is NOT the rootNode, find the next hop and create a rule
413                 if (link != null) {
414                     outPort = link.getTailNodeConnector();
415                     if (inPort.equals(outPort)) {
416                         // skip the outgoing port
417                         continue;
418                     }
419
420                     // If outPort is network link, add VLAN tag
421                     if (topologyManager.isInternal(outPort)) {
422                         log.debug("outPort {}/{} is internal uplink port",
423                                 currNode, outPort);
424                     } else {
425                         log.debug("outPort {}/{} is host facing port",
426                                 currNode, outPort);
427                     }
428
429                     if ((!inPort.getType().equals(NodeConnectorIDType.ALL))
430                         && (topologyManager.isInternal(outPort))) {
431                         Node nextNode = link.getHeadNodeConnector()
432                                             .getNode();
433                         // TODO: Replace this with SAL equivalent
434                         //short tag = container.getTag((Long)nextNode.getNodeID());
435                         short tag = 0;
436                         if (tag != 0) {
437                             log.debug("adding SET_VLAN {} for traffic " +
438                                     "leaving {}/{} toward switch {}",
439                                     new Object[] { tag, currNode, outPort,
440                                     nextNode});
441                             actions.add(new SetVlanId(tag));
442                         } else {
443                             log.debug("No tag assigned to switch {}", nextNode);
444                         }
445                     }
446                 }
447             }
448             if (outPort != null) {
449                 actions.add(new Output(outPort));
450             }
451             if (!inPort.getType().equals(NodeConnectorIDType.ALL)) {
452                 // include input port in the flow match field
453                 match.setField(MatchType.IN_PORT, inPort);
454
455                 if (topologyManager.isInternal(inPort)) {
456                     log.debug("inPort {}/{} is internal uplink port", currNode,
457                             inPort);
458                 } else {
459                     log.debug("inPort {}/{} is host facing port", currNode,
460                             inPort);
461                 }
462
463                 // for incoming network link; if the VLAN tag is defined, include it for incoming flow matching
464                 if (topologyManager.isInternal(inPort)) {
465                     // TODO: Replace this with SAL equivalent
466                     //short tag = container.getTag((Long)currNode.getNodeID());
467                     short tag = 0;
468                     if (tag != 0) {
469                         log.debug("adding MATCH VLAN {} for traffic entering" +
470                                 "  {}/{}",
471                                 new Object[] {tag, currNode, inPort});
472                         match.setField(MatchType.DL_VLAN, tag);
473                     } else {
474                         log.debug("No tag assigned to switch {}", currNode);
475                     }
476                 }
477             }
478             // Make sure the priority for IP switch entries is
479             // set to a level just above default drop entries
480             Flow flow = new Flow(match, actions);
481             flow.setIdleTimeout((short) 0);
482             flow.setHardTimeout((short) 0);
483             flow.setPriority(DEFAULT_IPSWITCH_PRIORITY);
484
485             String policyName = host.getNetworkAddress().getHostAddress()
486                     + "/32";
487             String flowName = "["
488                     + (!inPort.getType().equals(NodeConnectorIDType.ALL) ?
489                        (inPort.getID()).toString()
490                        + "," : "")
491                     + host.getNetworkAddress().getHostAddress() + "/32 on N "
492                     + currNode + "]";
493             FlowEntry po = new FlowEntry(policyName, flowName, flow, currNode);
494
495             /* Now save the rule in the DB rule, so on updates from topology we
496              * can selectively */
497             pos.put(inPort, po);
498             this.rulesDB.put(key, pos);
499             if (!inPort.getType().equals(NodeConnectorIDType.ALL)) {
500                 log.debug("Adding Match(inPort = {} , DIP = {})" +
501                         " Action(outPort= {}) to node {}",
502                         new Object[] { inPort,
503                         host.getNetworkAddress().getHostAddress(),
504                         outPort, currNode});
505                 if ((removed_po != null)
506                         && (!po.getFlow().getMatch().equals(
507                                 removed_po.getFlow().getMatch()))) {
508                     log.debug("Old Flow match: {}, New Flow match: {}",
509                             removed_po.getFlow().getMatch(), po.getFlow()
510                                     .getMatch());
511                     addTobePrunedPolicy(currNode, removed_po, po);
512                 }
513
514             } else {
515                 log.debug("Adding policyMatch(DIP = {}) Action(outPort= {}) " +
516                         "to node {}", new Object[] {
517                         host.getNetworkAddress().getHostAddress(), outPort,
518                         currNode});
519             }
520         }
521     }
522
523     /**
524      * Calculate the per-Host rules to be installed in the rulesDB,
525      * and that will later on be installed in HW, this routine will
526      * implicitly calculate the shortest path tree among the switch
527      * to which the host is attached and all the other switches in the
528      * network and will automatically create all the rules that allow
529      * a /32 destination IP based forwarding, as in traditional IP
530      * networks.
531      *
532      * @param host Host for which we are going to prepare the rules in the rulesDB
533      *
534      * @return A set of switches touched by the calculation
535      */
536     private Set<Node> preparePerHostRules(HostNodeConnector host) {
537         if (host == null) {
538             return null;
539         }
540
541         //TODO: race condition! unset* functions can make these null.
542         if (this.routing == null) {
543             return null;
544         }
545         if (this.switchManager == null) {
546             return null;
547         }
548         if (this.rulesDB == null) {
549             return null;
550         }
551
552         Node rootNode = host.getnodeconnectorNode();
553         Set<Node> nodes = this.switchManager.getNodes();
554         Set<Node> switchesToProgram = new HashSet<Node>();
555         HostNodePair key;
556         HashMap<NodeConnector, FlowEntry> pos;
557         FlowEntry po;
558
559         // for all nodes in the system
560         for (Node node : nodes) {
561             if (node.equals(rootNode)) {
562                 // We skip it because for the node with host attached
563                 // we will process in every case even if there are no
564                 // routes
565                 continue;
566             }
567             List<Edge> links;
568             Path res = this.routing.getRoute(node, rootNode);
569             if ((res == null) || ((links = res.getEdges()) == null)) {
570                 // No route from node to rootNode can be found, back out any
571                 // existing forwarding rules if they exist.
572                 log.debug("NO Route/Path between SW[{}] --> SW[{}] cleaning " +
573                         "potentially existing entries", node, rootNode);
574                 key = new HostNodePair(host, node);
575                 pos = this.rulesDB.get(key);
576                 if (pos != null) {
577                     for (Map.Entry<NodeConnector, FlowEntry> e : pos.entrySet()) {
578                         po = e.getValue();
579                         if (po != null) {
580                             // uninstall any existing rules we put in the
581                             // ForwardingRulesManager
582                             this.frm.uninstallFlowEntry(po);
583                         }
584                     }
585                     this.rulesDB.remove(key);
586                 }
587                 continue;
588             }
589
590             log.debug("Route between SW[{}] --> SW[{}]", node, rootNode);
591             Node currNode = node;
592             key = new HostNodePair(host, currNode);
593
594             // for each link in the route from here to there
595             for (Edge link : links) {
596                 if (link == null) {
597                     log.error("Could not retrieve the Link");
598                     // TODO: should we keep going?
599                     continue;
600                 }
601
602                 log.debug(link.toString());
603
604                 // Index all the switches to be programmed
605                 updatePerHostRuleInSW(host, currNode, rootNode, link, key);
606                 if ((this.rulesDB.get(key)) != null) {
607                     /* Calling updatePerHostRuleInSW() doesn't guarantee that
608                      * rules will be added in currNode (e.g, there is only one
609                      * link from currNode to rootNode This check makes sure that
610                      * there are some rules in the rulesDB for the given key
611                      * prior to adding switch to switchesToProgram
612                      */
613                     switchesToProgram.add(currNode);
614                 }
615                 currNode = link.getHeadNodeConnector().getNode();
616                 key = new HostNodePair(host, currNode);
617             }
618         }
619
620         // This rule will be added no matter if any topology is built
621         // or no, it serve as a way to handle the case of a node with
622         // multiple hosts attached to it but not yet connected to the
623         // rest of the world
624         switchesToProgram.add(rootNode);
625         updatePerHostRuleInSW(host, rootNode, rootNode, null,
626                               new HostNodePair(host, rootNode));
627
628         //      log.debug("Getting out at the end!");
629         return switchesToProgram;
630     }
631
632     /**
633      * Calculate the per-Host rules to be installed in the rulesDB
634      * from a specific switch when a host facing port comes up.
635      * These rules will later on be installed in HW. This routine
636      * will implicitly calculate the shortest path from the switch
637      * where the port has come up to the switch where host is ,
638      * attached and will automatically create all the rules that allow
639      * a /32 destination IP based forwarding, as in traditional IP
640      * networks.
641      *
642      * @param host Host for which we are going to prepare the rules in the rulesDB
643      * @param swId Switch ID where the port has come up
644      *
645      * @return A set of switches touched by the calculation
646      */
647     private Set<Node> preparePerHostPerSwitchRules(HostNodeConnector host,
648             Node node, NodeConnector swport) {
649         if ((host == null) || (node == null)) {
650             return null;
651         }
652         if (this.routing == null) {
653             return null;
654         }
655         if (this.switchManager == null) {
656             return null;
657         }
658         if (this.rulesDB == null) {
659             return null;
660         }
661
662         Node rootNode = host.getnodeconnectorNode();
663         Set<Node> switchesToProgram = new HashSet<Node>();
664         HostNodePair key;
665         Map<NodeConnector, FlowEntry> pos;
666         FlowEntry po;
667         List<Edge> links;
668
669         Path res = this.routing.getRoute(node, rootNode);
670         if ((res == null) || ((links = res.getEdges()) == null)) {
671             // the routing service doesn't know how to get there from here
672             log.debug("NO Route/Path between SW[{}] --> SW[{}] cleaning " +
673                     "potentially existing entries", node, rootNode);
674             key = new HostNodePair(host, node);
675             pos = this.rulesDB.get(key);
676             if (pos != null) {
677                 for (Map.Entry<NodeConnector, FlowEntry> e : pos.entrySet()) {
678                     po = e.getValue();
679                     if (po != null) {
680                         //Uninstall the policy
681                         this.frm.uninstallFlowEntry(po);
682                     }
683                 }
684                 this.rulesDB.remove(key);
685             }
686             return null;
687         }
688
689         log.debug("Route between SW[{}] --> SW[{}]", node, rootNode);
690         Integer curr;
691         Node currNode = node;
692         key = new HostNodePair(host, currNode);
693         Edge link;
694         for (curr = 0; curr < links.size(); curr++) {
695             link = links.get(curr);
696             if (link == null) {
697                 log.error("Could not retrieve the Link");
698                 continue;
699             }
700
701             log.debug("Link [{}/{}] --> [{}/{}]", new Object[] {
702                     currNode, link.getHeadNodeConnector(),
703                     link.getHeadNodeConnector().getNode(),
704                     link.getTailNodeConnector()});
705
706             // Index all the switches to be programmed
707             switchesToProgram.add(currNode);
708             updatePerHostRuleInSW(host, currNode, rootNode, link, key);
709             break; // come out of the loop for port up case, interested only in programming one switch
710         }
711
712         // This rule will be added no matter if any topology is built
713         // or no, it serve as a way to handle the case of a node with
714         // multiple hosts attached to it but not yet connected to the
715         // rest of the world
716         // switchesToProgram.add(rootNode);
717         //updatePerHostRuleInSW(host, rootNode,
718         //                                        rootNode, null,
719         //                                        new HostNodePair(host, rootNode),ports);
720
721         //      log.debug("Getting out at the end!");
722         return switchesToProgram;
723     }
724
725     /**
726      * Routine that fetch the per-Host rules from the rulesDB and
727      * install in HW, the one having the same match rules will be
728      * overwritten silently.
729      *
730      * @param host host for which we want to install in HW the per-Host rules
731      * @param switchesToProgram list of switches to be programmed in
732      * HW, usually are them all, but better to be explicit, that list
733      * may change with time based on new switch addition/removal
734      *
735      * @return a return code that convey the programming status of the HW
736      */
737     private RulesProgrammingReturnCode installPerHostRules(
738             HostNodeConnector host, Set<Node> switchesToProgram) {
739         RulesProgrammingReturnCode retCode = RulesProgrammingReturnCode.SUCCESS;
740         if (host == null || switchesToProgram == null) {
741             return RulesProgrammingReturnCode.FAILED_WRONG_PARAMS;
742         }
743         Map<NodeConnector, FlowEntry> pos;
744         FlowEntry po;
745         // Now program every single switch
746         log.debug("Inside installPerHostRules");
747         for (Node swId : switchesToProgram) {
748             HostNodePair key = new HostNodePair(host, swId);
749             pos = this.rulesDB.get(key);
750             if (pos == null) {
751                 continue;
752             }
753             for (Map.Entry<NodeConnector, FlowEntry> e : pos.entrySet()) {
754                 po = e.getValue();
755                 if (po != null) {
756                     // Populate the Policy field now
757                     Status poStatus = this.frm.modifyOrAddFlowEntry(po);
758                     if (!poStatus.isSuccess()) {
759                         log.error("Failed to install policy: "
760                                 + po.getGroupName() + " ("
761                                 + poStatus.getDescription() + ")");
762
763                         retCode = RulesProgrammingReturnCode.FAILED_FEW_SWITCHES;
764                         // Remove the entry from the DB, it was not installed!
765                         this.rulesDB.remove(key);
766                     } else {
767                         log.debug("Successfully installed policy "
768                                 + po.toString() + " on switch " + swId);
769                     }
770                 } else {
771                     log.error("Cannot find a policy for SW:({}) Host: ({})",
772                               swId, host);
773                     /* // Now dump every single rule */
774                     /* for (HostNodePair dumpkey : this.rulesDB.keySet()) { */
775                     /*  po = this.rulesDB.get(dumpkey); */
776                     /*  log.debug("Dumping entry H{" + dumpkey.getHost() + "} S{" + dumpkey.getSwitchId() + "} = {" + (po == null ? "null policy" : po)); */
777                     /* } */
778                 }
779             }
780         }
781         log.debug("Leaving installPerHostRules");
782         return retCode;
783     }
784
785     /**
786      * Cleanup all the host rules for a given host
787      *
788      * @param host Host for which the host rules need to be cleaned
789      * up, the host could be null in that case it match all the hosts
790      *
791      * @return a return code that convey the programming status of the HW
792      */
793     private RulesProgrammingReturnCode uninstallPerHostRules(HostNodeConnector host) {
794         RulesProgrammingReturnCode retCode = RulesProgrammingReturnCode.SUCCESS;
795         Map<NodeConnector, FlowEntry> pos;
796         FlowEntry po;
797         // Now program every single switch
798         for (HostNodePair key : this.rulesDB.keySet()) {
799             if (host == null || key.getHost().equals(host)) {
800                 pos = this.rulesDB.get(key);
801                 for (Map.Entry<NodeConnector, FlowEntry> e : pos.entrySet()) {
802                     po = e.getValue();
803                     if (po != null) {
804                         // Uninstall the policy
805                         this.frm.uninstallFlowEntry(po);
806                     }
807                 }
808                 this.rulesDB.remove(key);
809             }
810         }
811         return retCode;
812     }
813
814     /**
815      * Cleanup all the host rules for a given node, triggered when the
816      * switch disconnects, so there is no reason for Hw cleanup
817      * because it's disconnected anyhow
818      * TBD - Revisit above stmt in light of CSCus88743
819      * @param targetNode Node for which we want to do cleanup
820      *
821      */
822     private void uninstallPerNodeRules(Node targetNode) {
823         //RulesProgrammingReturnCode retCode = RulesProgrammingReturnCode.SUCCESS;
824         Map<NodeConnector, FlowEntry> pos;
825         FlowEntry po;
826
827         // Now program every single switch
828         for (HostNodePair key : this.rulesDB.keySet()) {
829             Node node = key.getNode();
830             if (targetNode == null || node.equals(targetNode)) {
831                 log.debug("Work on {} host {}", node, key.getHost());
832                 pos = this.rulesDB.get(key);
833                 for (Map.Entry<NodeConnector, FlowEntry> e : pos.entrySet()) {
834                     po = e.getValue();
835                     if (po != null) {
836                         // Uninstall the policy
837                         this.frm.uninstallFlowEntry(po);
838                     }
839                 }
840                 log.debug("Remove {}", key);
841                 this.rulesDB.remove(key);
842             }
843         }
844     }
845
846     /**
847      * Cleanup all the host rules currently present in the rulesDB
848      *
849      * @return a return code that convey the programming status of the HW
850      */
851     private RulesProgrammingReturnCode uninstallPerHostRules() {
852         return uninstallPerHostRules(null);
853     }
854
855     @Override
856     public void recalculateDone() {
857         if (this.hostTracker == null) {
858             //Not yet ready to process all the updates
859             //TODO: we should make sure that this call is executed eventually
860             return;
861         }
862         Set<HostNodeConnector> allHosts = this.hostTracker.getAllHosts();
863         for (HostNodeConnector host : allHosts) {
864             Set<Node> switches = preparePerHostRules(host);
865             if (switches != null) {
866                 // This will refresh existing rules, by overwriting
867                 // the previous ones
868                 installPerHostRules(host, switches);
869                 pruneExcessRules(switches);
870             }
871         }
872     }
873
874     void addTobePrunedPolicy(Node swId, FlowEntry po, FlowEntry new_po) {
875         List<FlowEntry> pl = tobePrunedPos.get(swId);
876         if (pl == null) {
877             pl = new LinkedList<FlowEntry>();
878             tobePrunedPos.put(swId, pl);
879         }
880         pl.add(po);
881         log.debug("Adding Pruned Policy for SwId: {}", swId);
882         log.debug("Old Policy: {}", po);
883         log.debug("New Policy: {}", new_po);
884     }
885
886     private void pruneExcessRules(Set<Node> switches) {
887         for (Node swId : switches) {
888             List<FlowEntry> pl = tobePrunedPos.get(swId);
889             if (pl != null) {
890                 log.debug("Policies for Switch: {} in the list to be deleted: {}", swId, pl);
891                 Iterator<FlowEntry> plIter = pl.iterator();
892                 //for (Policy po: pl) {
893                 while (plIter.hasNext()) {
894                     FlowEntry po = plIter.next();
895                     log.error("Removing Policy, Switch: {} Policy: {}", swId, po);
896                     this.frm.uninstallFlowEntry(po);
897                     plIter.remove();
898                 }
899             }
900             // tobePrunedPos.remove(swId);
901         }
902     }
903
904     /**
905      * A Host facing port has come up in a container. Add rules on the switch where this
906      * port has come up for all the known hosts to the controller.
907      * @param swId switch id of the port where port came up
908      * @param swPort port which came up
909      */
910     private void updateRulesforHIFup(Node node, NodeConnector swPort) {
911         if (this.hostTracker == null) {
912             //Not yet ready to process all the updates
913             return;
914         }
915         log.debug("Host Facing Port in a container came up, install the rules for all hosts from this port !");
916         Set<HostNodeConnector> allHosts = this.hostTracker.getAllHosts();
917         for (HostNodeConnector host : allHosts) {
918             if (node.equals(host.getnodeconnectorNode())) {
919                 /*
920                  * This host resides behind the same switch and port for which a port up
921                  * message is received. Ideally this should not happen, but if it does,
922                  * don't program any rules for this host
923                  */
924                 continue;
925             }
926             Set<Node> switches = preparePerHostPerSwitchRules(host, node,
927                     swPort);
928             if (switches != null) {
929                 // This will refresh existing rules, by overwriting
930                 // the previous ones
931                 installPerHostRules(host, switches);
932             }
933         }
934
935     }
936
937     @Override
938     public void notifyHTClient(HostNodeConnector host) {
939         if (host == null) {
940             return;
941         }
942         Set<Node> switches = preparePerHostRules(host);
943         if (switches != null) {
944             installPerHostRules(host, switches);
945
946             // Green light for sending pending packet to this host. Safe to call if there are none.
947             sendPendingPacket(host.getNetworkAddress());
948         }
949     }
950
951     @Override
952     public void notifyHTClientHostRemoved(HostNodeConnector host) {
953         if (host == null) {
954             return;
955         }
956         uninstallPerHostRules(host);
957     }
958
959     @Override
960     public void notifyNode(Node node, UpdateType type,
961             Map<String, Property> propMap) {
962         if (node == null) {
963             return;
964         }
965
966         switch (type) {
967         case REMOVED:
968             log.debug("Node {} gone, doing a cleanup", node);
969             uninstallPerNodeRules(node);
970             break;
971         default:
972             break;
973         }
974     }
975
976     @Override
977     public void notifyNodeConnector(NodeConnector nodeConnector,
978             UpdateType type, Map<String, Property> propMap) {
979         if (nodeConnector == null) {
980             return;
981         }
982
983         boolean up = false;
984         switch (type) {
985         case ADDED:
986             up = true;
987             break;
988         case REMOVED:
989             break;
990         case CHANGED:
991             State state = (State) propMap.get(State.StatePropName);
992             if ((state != null) && (state.getValue() == State.EDGE_UP)) {
993                 up = true;
994             }
995             break;
996         default:
997             return;
998         }
999
1000         if (up) {
1001             handleNodeConnectorStatusUp(nodeConnector);
1002         } else {
1003             handleNodeConnectorStatusDown(nodeConnector);
1004         }
1005     }
1006
1007     private void handleNodeConnectorStatusUp(NodeConnector nodeConnector) {
1008         if (topologyManager == null) {
1009             log.debug("topologyManager is not set yet");
1010             return;
1011         }
1012
1013         if (topologyManager.isInternal(nodeConnector)) {
1014             log.debug("{} is not a host facing link", nodeConnector);
1015             return;
1016         }
1017
1018         log.debug("{} is up", nodeConnector);
1019         updateRulesforHIFup(nodeConnector.getNode(), nodeConnector);
1020     }
1021
1022     private void handleNodeConnectorStatusDown(NodeConnector nodeConnector) {
1023         log.debug("{} is down", nodeConnector);
1024     }
1025
1026     void setClusterContainerService(IClusterContainerServices s) {
1027         log.debug("Cluster Service set");
1028         this.clusterContainerService = s;
1029     }
1030
1031     void unsetClusterContainerService(IClusterContainerServices s) {
1032         if (this.clusterContainerService == s) {
1033             log.debug("Cluster Service removed!");
1034             this.clusterContainerService = null;
1035         }
1036     }
1037
1038     /**
1039      * Function called by the dependency manager when all the required
1040      * dependencies are satisfied
1041      *
1042      */
1043     void init() {
1044         startUp();
1045     }
1046
1047     /**
1048      * Function called by the dependency manager when at least one
1049      * dependency become unsatisfied or when the component is shutting
1050      * down because for example bundle is being stopped.
1051      *
1052      */
1053     void destroy() {
1054     }
1055
1056     /**
1057      * Function called by dependency manager after "init ()" is called
1058      * and after the services provided by the class are registered in
1059      * the service registry
1060      *
1061      */
1062     void start() {
1063     }
1064
1065     /**
1066      * Function called by the dependency manager before the services
1067      * exported by the component are unregistered, this will be
1068      * followed by a "destroy ()" calls
1069      *
1070      */
1071     void stop() {
1072         pendingPacketsAgerTimer.cancel();
1073         pendingPacketDestinations.clear();
1074     }
1075
1076     public void setSwitchManager(ISwitchManager switchManager) {
1077         this.switchManager = switchManager;
1078     }
1079
1080     public void unsetSwitchManager(ISwitchManager switchManager) {
1081         if (this.switchManager == switchManager) {
1082             this.switchManager = null;
1083         }
1084     }
1085
1086     @Override
1087     public PacketResult receiveDataPacket(RawPacket inPkt) {
1088         if (inPkt == null) {
1089             return PacketResult.IGNORED;
1090         }
1091         log.trace("Received a frame of size: {}", inPkt.getPacketData().length);
1092         Packet formattedPak = this.dataPacketService.decodeDataPacket(inPkt);
1093         if (formattedPak instanceof Ethernet) {
1094             Object nextPak = formattedPak.getPayload();
1095             if (nextPak instanceof IPv4) {
1096                 log.trace("Handle punted IP packet: {}", formattedPak);
1097                 handlePuntedIPPacket((IPv4) nextPak, inPkt.getIncomingNodeConnector(), true);
1098             }
1099         }
1100         return PacketResult.IGNORED;
1101
1102     }
1103
1104     private void handlePuntedIPPacket(IPv4 pkt, NodeConnector incomingNodeConnector, boolean allowAddPending) {
1105         InetAddress dIP = NetUtils.getInetAddress(pkt.getDestinationAddress());
1106         if (dIP == null || hostTracker == null) {
1107             log.debug("Invalid param(s) in handlePuntedIPPacket.. DestIP: {}. hostTracker: {}", dIP, hostTracker);
1108             return;
1109         }
1110         HostNodeConnector destHost = hostTracker.hostFind(dIP);
1111         /*
1112          * In cases when incoming and outgoing connectors are in the same node, there is no need
1113          * to verify that there is a route. Because of that, we will only need routing.getRoute()
1114          * if we know that src and dst nodes are different.
1115          */
1116         if (destHost != null
1117                 && (incomingNodeConnector.getNode().equals(destHost.getnodeconnectorNode()) ||
1118                     routing == null ||
1119                     routing.getRoute(incomingNodeConnector.getNode(), destHost.getnodeconnectorNode()) != null)) {
1120
1121             log.trace("Host {} is at {}", dIP, destHost.getnodeConnector());
1122
1123             // If SimpleForwarding is aware of this host, it will try to install
1124             // a path. Forward packet until it's done.
1125             if (dataPacketService != null) {
1126
1127                 /*
1128                  * if we know where the host is and there's a path from where this
1129                  * packet was punted to where the host is, then attempt best effort delivery to the host
1130                  */
1131                 NodeConnector nc = destHost.getnodeConnector();
1132                 log.trace("Forwarding punted IP received at {} to {}", incomingNodeConnector, nc);
1133                 // re-encode the Ethernet packet (the parent of the IPv4 packet)
1134                 RawPacket rp = this.dataPacketService.encodeDataPacket(pkt.getParent());
1135                 rp.setOutgoingNodeConnector(nc);
1136                 this.dataPacketService.transmitDataPacket(rp);
1137             }
1138         } else if (allowAddPending) {
1139             // If we made it here, let's hang on to the punted packet, with hopes that its destination
1140             // will become available soon.
1141             addToPendingPackets(dIP, pkt, incomingNodeConnector);
1142         } else {
1143             log.warn("Dropping punted IP packet received at {} to Host {}", incomingNodeConnector, dIP);
1144         }
1145     }
1146 }