3 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
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
10 package org.opendaylight.controller.samples.simpleforwarding.internal;
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;
22 import java.util.concurrent.ConcurrentHashMap;
23 import java.util.concurrent.ConcurrentMap;
24 import java.util.Timer;
25 import java.util.TimerTask;
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;
73 * This class implements basic L3 forwarding within the managed devices.
74 * Forwarding is only done within configured subnets.</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>.
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;
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.
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;
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.
111 private class PendingPacketData {
112 private final static byte MAX_AGE = 2;
114 public final IPv4 pkt;
115 public final NodeConnector incomingNodeConnector;
118 public PendingPacketData(IPv4 pkt, NodeConnector incomingNodeConnector) {
120 this.incomingNodeConnector = incomingNodeConnector;
123 boolean bumpAgeAndCheckIfTooOld() { return ++age > MAX_AGE; }
125 private static final int MAX_PENDING_PACKET_DESTINATIONS = 64;
126 private ConcurrentMap<InetAddress, PendingPacketData> pendingPacketDestinations;
127 private Timer pendingPacketsAgerTimer;
129 private class PendingPacketsAgerTimerHandler extends TimerTask {
132 if (pendingPacketDestinations == null) {
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();
143 if (pendingPacketData.bumpAgeAndCheckIfTooOld()) {
144 iterator.remove(); // safe to remove while iterating...
145 log.debug("Pending packet for {} has been aged out", dIP);
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.
151 pendingPacketDestinations.replace(dIP, pendingPacketData);
154 } catch (IllegalStateException e) {
155 log.warn("IllegalStateException Received by PendingPacketsAgerTimerHandler from: {}",
162 * Add punted packet to pendingPackets
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);
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.
174 if (pendingPacketDestinations.containsKey(dIP)) {
175 log.trace("Will not pend packet for {}: Already have a packet pending", dIP);
179 PendingPacketData pendingPacketData = new PendingPacketData(pkt, incomingNodeConnector);
180 pendingPacketDestinations.put(dIP, pendingPacketData);
181 log.debug("Pending packet for {}", dIP);
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.
188 private void sendPendingPacket(InetAddress dIP) {
189 PendingPacketData pendingPacketData = pendingPacketDestinations.get(dIP);
190 if (pendingPacketData != null) {
191 handlePuntedIPPacket(pendingPacketData.pkt, pendingPacketData.incomingNodeConnector, false);
192 log.trace("Packet for {} is no longer pending", dIP);
193 pendingPacketDestinations.remove(dIP);
198 * Return codes from the programming of the perHost rules in HW
200 public enum RulesProgrammingReturnCode {
201 SUCCESS, FAILED_FEW_SWITCHES, FAILED_ALL_SWITCHES, FAILED_WRONG_PARAMS
203 public void setDataPacketService(IDataPacketService s) {
204 log.debug("Setting dataPacketService");
205 this.dataPacketService = s;
208 public void unsetDataPacketService(IDataPacketService s) {
209 if (this.dataPacketService == s) {
210 this.dataPacketService = null;
214 public void setRouting(IRouting routing) {
215 log.debug("Setting routing");
216 this.routing = routing;
219 public void unsetRouting(IRouting routing) {
220 if (this.routing == routing) {
225 public void setTopologyManager(ITopologyManager topologyManager) {
226 log.debug("Setting topologyManager");
227 this.topologyManager = topologyManager;
230 public void unsetTopologyManager(ITopologyManager topologyManager) {
231 if (this.topologyManager == topologyManager) {
232 this.topologyManager = null;
236 public void setHostTracker(IfIptoHost hostTracker) {
237 log.debug("Setting HostTracker");
238 this.hostTracker = hostTracker;
241 public void setForwardingRulesManager(
242 IForwardingRulesManager forwardingRulesManager) {
243 log.debug("Setting ForwardingRulesManager");
244 this.frm = forwardingRulesManager;
247 public void unsetHostTracker(IfIptoHost hostTracker) {
248 if (this.hostTracker == hostTracker) {
249 this.hostTracker = null;
253 public void unsetForwardingRulesManager(
254 IForwardingRulesManager forwardingRulesManager) {
255 if (this.frm == forwardingRulesManager) {
261 * Function called when the bundle gets activated
264 public void startUp() {
267 nonClusterObjectCreate();
270 public void nonClusterObjectCreate() {
271 pendingPacketDestinations = new ConcurrentHashMap<InetAddress, PendingPacketData>();
273 /* Pending Packets Ager Timer to go off every 6 seconds to implement pending packet aging */
274 pendingPacketsAgerTimer = new Timer();
275 pendingPacketsAgerTimer.schedule(new PendingPacketsAgerTimerHandler(), 6000, 6000);
279 * Function called when the bundle gets stopped
282 public void shutDown() {
283 log.debug("Destroy all the host Rules given we are shutting down");
284 uninstallPerHostRules();
288 private void allocateCaches() {
289 if (this.clusterContainerService == null) {
290 log.trace("un-initialized clusterContainerService, can't create cache");
295 clusterContainerService.createCache(FORWARDING_RULES_CACHE_NAME,
296 EnumSet.of(IClusterServices.cacheMode.TRANSACTIONAL));
297 } catch (CacheExistException cee) {
298 log.error("\nCache already exists - destroy and recreate if needed");
299 } catch (CacheConfigException cce) {
300 log.error("\nCache configuration invalid - check cache mode");
304 @SuppressWarnings({ "unchecked" })
305 private void retrieveCaches() {
306 if (this.clusterContainerService == null) {
307 log.trace("un-initialized clusterContainerService, can't retrieve cache");
311 rulesDB = (ConcurrentMap<HostNodePair, HashMap<NodeConnector, FlowEntry>>) clusterContainerService
312 .getCache(FORWARDING_RULES_CACHE_NAME);
313 if (rulesDB == null) {
314 log.error("\nFailed to get rulesDB handle");
318 private void destroyCaches() {
319 if (this.clusterContainerService == null) {
320 log.trace("un-initialized clusterContainerService, can't destroy cache");
324 clusterContainerService.destroyCache(FORWARDING_RULES_CACHE_NAME);
328 * Populates <tt>rulesDB</tt> with rules specifying how to reach
329 * <tt>host</tt> from <tt>currNode</tt> assuming that:
331 * <li><tt>host</tt> is attached to <tt>rootNode</tt>
332 * <li><tt>link</tt> is the next part of the path to reach <tt>rootNode</tt>
333 * from <tt>currNode</tt>
334 * <li><tt>rulesDB.get(key)</tt> represents the list of rules stored about
335 * <tt>host</tt> at <tt>currNode</tt>
339 * The host to be reached.
341 * The current node being processed.
343 * The node to be reached. Really, the switch which host is
346 * The link to follow from curNode to get to rootNode
348 * The key to store computed rules at in the rulesDB. For now,
349 * this is a {@link HostNodePair} of host and currNode.
351 private void updatePerHostRuleInSW(HostNodeConnector host, Node currNode,
352 Node rootNode, Edge link, HostNodePair key) {
354 // only the link parameter is optional
355 if (host == null || key == null || currNode == null || rootNode == null) {
359 Set<NodeConnector> ports = new HashSet<NodeConnector>();
360 // add a special port of type ALL and port 0 to represent the node
361 // without specifying a port on that node
362 ports.add(NodeConnectorCreator.createNodeConnector(
363 NodeConnectorIDType.ALL, NodeConnector.SPECIALNODECONNECTORID,
366 HashMap<NodeConnector, FlowEntry> pos = this.rulesDB.get(key);
368 pos = new HashMap<NodeConnector, FlowEntry>();
371 for (NodeConnector inPort : ports) {
372 // skip the port connected to the target host
373 if (currNode.equals(rootNode)
374 && (host.getnodeConnector().equals(inPort))) {
378 // remove the current rule, if any
379 FlowEntry removed_po = pos.remove(inPort);
380 Match match = new Match();
381 List<Action> actions = new ArrayList<Action>();
383 // IP destination based forwarding on /32 entries only!
384 match.setField(MatchType.DL_TYPE, EtherTypes.IPv4.shortValue());
385 match.setField(MatchType.NW_DST, host.getNetworkAddress());
387 /* Action for the policy is to forward to a port except on the
388 * switch where the host sits, which is to rewrite also the MAC
389 * and to forward on the Host port */
390 NodeConnector outPort = null;
392 if (currNode.equals(rootNode)) {
393 /* If we're at the root node, then rewrite the DL addr and
394 * possibly pop the VLAN tag. This allows for MAC rewriting
395 * in the core of the network assuming we can uniquely ID
396 * packets based on IP address. */
398 outPort = host.getnodeConnector();
399 if (inPort.equals(outPort)) {
400 // TODO: isn't this code skipped already by the above continue?
401 // skip the host port
404 actions.add(new SetDlDst(host.getDataLayerAddressBytes()));
406 if (!inPort.getType().equals(NodeConnectorIDType.ALL)) {
407 // Container mode: at the destination switch, we need to strip out the tag (VLAN)
408 actions.add(new PopVlan());
411 // currNode is NOT the rootNode, find the next hop and create a rule
413 outPort = link.getTailNodeConnector();
414 if (inPort.equals(outPort)) {
415 // skip the outgoing port
419 // If outPort is network link, add VLAN tag
420 if (topologyManager.isInternal(outPort)) {
421 log.debug("outPort {}/{} is internal uplink port",
424 log.debug("outPort {}/{} is host facing port",
428 if ((!inPort.getType().equals(NodeConnectorIDType.ALL))
429 && (topologyManager.isInternal(outPort))) {
430 Node nextNode = link.getHeadNodeConnector()
432 // TODO: Replace this with SAL equivalent
433 //short tag = container.getTag((Long)nextNode.getNodeID());
436 log.debug("adding SET_VLAN {} for traffic " +
437 "leaving {}/{} toward switch {}",
438 new Object[] { tag, currNode, outPort,
440 actions.add(new SetVlanId(tag));
442 log.debug("No tag assigned to switch {}", nextNode);
447 if (outPort != null) {
448 actions.add(new Output(outPort));
450 if (!inPort.getType().equals(NodeConnectorIDType.ALL)) {
451 // include input port in the flow match field
452 match.setField(MatchType.IN_PORT, inPort);
454 if (topologyManager.isInternal(inPort)) {
455 log.debug("inPort {}/{} is internal uplink port", currNode,
458 log.debug("inPort {}/{} is host facing port", currNode,
462 // for incoming network link; if the VLAN tag is defined, include it for incoming flow matching
463 if (topologyManager.isInternal(inPort)) {
464 // TODO: Replace this with SAL equivalent
465 //short tag = container.getTag((Long)currNode.getNodeID());
468 log.debug("adding MATCH VLAN {} for traffic entering" +
470 new Object[] {tag, currNode, inPort});
471 match.setField(MatchType.DL_VLAN, tag);
473 log.debug("No tag assigned to switch {}", currNode);
477 // Make sure the priority for IP switch entries is
478 // set to a level just above default drop entries
479 Flow flow = new Flow(match, actions);
480 flow.setIdleTimeout((short) 0);
481 flow.setHardTimeout((short) 0);
482 flow.setPriority(DEFAULT_IPSWITCH_PRIORITY);
484 String policyName = host.getNetworkAddress().getHostAddress()
486 String flowName = "["
487 + (!inPort.getType().equals(NodeConnectorIDType.ALL) ?
488 (inPort.getID()).toString()
490 + host.getNetworkAddress().getHostAddress() + "/32 on N "
492 FlowEntry po = new FlowEntry(policyName, flowName, flow, currNode);
494 /* Now save the rule in the DB rule, so on updates from topology we
497 this.rulesDB.put(key, pos);
498 if (!inPort.getType().equals(NodeConnectorIDType.ALL)) {
499 log.debug("Adding Match(inPort = {} , DIP = {})" +
500 " Action(outPort= {}) to node {}",
501 new Object[] { inPort,
502 host.getNetworkAddress().getHostAddress(),
504 if ((removed_po != null)
505 && (!po.getFlow().getMatch().equals(
506 removed_po.getFlow().getMatch()))) {
507 log.debug("Old Flow match: {}, New Flow match: {}",
508 removed_po.getFlow().getMatch(), po.getFlow()
510 addTobePrunedPolicy(currNode, removed_po, po);
514 log.debug("Adding policyMatch(DIP = {}) Action(outPort= {}) " +
515 "to node {}", new Object[] {
516 host.getNetworkAddress().getHostAddress(), outPort,
523 * Calculate the per-Host rules to be installed in the rulesDB,
524 * and that will later on be installed in HW, this routine will
525 * implicitly calculate the shortest path tree among the switch
526 * to which the host is attached and all the other switches in the
527 * network and will automatically create all the rules that allow
528 * a /32 destination IP based forwarding, as in traditional IP
531 * @param host Host for which we are going to prepare the rules in the rulesDB
533 * @return A set of switches touched by the calculation
535 private Set<Node> preparePerHostRules(HostNodeConnector host) {
540 //TODO: race condition! unset* functions can make these null.
541 if (this.routing == null) {
544 if (this.switchManager == null) {
547 if (this.rulesDB == null) {
551 Node rootNode = host.getnodeconnectorNode();
552 Set<Node> nodes = this.switchManager.getNodes();
553 Set<Node> switchesToProgram = new HashSet<Node>();
555 HashMap<NodeConnector, FlowEntry> pos;
558 // for all nodes in the system
559 for (Node node : nodes) {
560 if (node.equals(rootNode)) {
561 // We skip it because for the node with host attached
562 // we will process in every case even if there are no
567 Path res = this.routing.getRoute(node, rootNode);
568 if ((res == null) || ((links = res.getEdges()) == null)) {
569 // No route from node to rootNode can be found, back out any
570 // existing forwarding rules if they exist.
571 log.debug("NO Route/Path between SW[{}] --> SW[{}] cleaning " +
572 "potentially existing entries", node, rootNode);
573 key = new HostNodePair(host, node);
574 pos = this.rulesDB.get(key);
576 for (Map.Entry<NodeConnector, FlowEntry> e : pos.entrySet()) {
579 // uninstall any existing rules we put in the
580 // ForwardingRulesManager
581 this.frm.uninstallFlowEntry(po);
584 this.rulesDB.remove(key);
589 log.debug("Route between SW[{}] --> SW[{}]", node, rootNode);
590 Node currNode = node;
591 key = new HostNodePair(host, currNode);
593 // for each link in the route from here to there
594 for (Edge link : links) {
596 log.error("Could not retrieve the Link");
597 // TODO: should we keep going?
601 log.debug(link.toString());
603 // Index all the switches to be programmed
604 updatePerHostRuleInSW(host, currNode, rootNode, link, key);
605 if ((this.rulesDB.get(key)) != null) {
606 /* Calling updatePerHostRuleInSW() doesn't guarantee that
607 * rules will be added in currNode (e.g, there is only one
608 * link from currNode to rootNode This check makes sure that
609 * there are some rules in the rulesDB for the given key
610 * prior to adding switch to switchesToProgram
612 switchesToProgram.add(currNode);
614 currNode = link.getHeadNodeConnector().getNode();
615 key = new HostNodePair(host, currNode);
619 // This rule will be added no matter if any topology is built
620 // or no, it serve as a way to handle the case of a node with
621 // multiple hosts attached to it but not yet connected to the
623 switchesToProgram.add(rootNode);
624 updatePerHostRuleInSW(host, rootNode, rootNode, null,
625 new HostNodePair(host, rootNode));
627 // log.debug("Getting out at the end!");
628 return switchesToProgram;
632 * Calculate the per-Host rules to be installed in the rulesDB
633 * from a specific switch when a host facing port comes up.
634 * These rules will later on be installed in HW. This routine
635 * will implicitly calculate the shortest path from the switch
636 * where the port has come up to the switch where host is ,
637 * attached and will automatically create all the rules that allow
638 * a /32 destination IP based forwarding, as in traditional IP
641 * @param host Host for which we are going to prepare the rules in the rulesDB
642 * @param swId Switch ID where the port has come up
644 * @return A set of switches touched by the calculation
646 private Set<Node> preparePerHostPerSwitchRules(HostNodeConnector host,
647 Node node, NodeConnector swport) {
648 if ((host == null) || (node == null)) {
651 if (this.routing == null) {
654 if (this.switchManager == null) {
657 if (this.rulesDB == null) {
661 Node rootNode = host.getnodeconnectorNode();
662 Set<Node> switchesToProgram = new HashSet<Node>();
664 Map<NodeConnector, FlowEntry> pos;
668 Path res = this.routing.getRoute(node, rootNode);
669 if ((res == null) || ((links = res.getEdges()) == null)) {
670 // the routing service doesn't know how to get there from here
671 log.debug("NO Route/Path between SW[{}] --> SW[{}] cleaning " +
672 "potentially existing entries", node, rootNode);
673 key = new HostNodePair(host, node);
674 pos = this.rulesDB.get(key);
676 for (Map.Entry<NodeConnector, FlowEntry> e : pos.entrySet()) {
679 //Uninstall the policy
680 this.frm.uninstallFlowEntry(po);
683 this.rulesDB.remove(key);
688 log.debug("Route between SW[{}] --> SW[{}]", node, rootNode);
690 Node currNode = node;
691 key = new HostNodePair(host, currNode);
693 for (curr = 0; curr < links.size(); curr++) {
694 link = links.get(curr);
696 log.error("Could not retrieve the Link");
700 log.debug("Link [{}/{}] --> [{}/{}]", new Object[] {
701 currNode, link.getHeadNodeConnector(),
702 link.getHeadNodeConnector().getNode(),
703 link.getTailNodeConnector()});
705 // Index all the switches to be programmed
706 switchesToProgram.add(currNode);
707 updatePerHostRuleInSW(host, currNode, rootNode, link, key);
708 break; // come out of the loop for port up case, interested only in programming one switch
711 // This rule will be added no matter if any topology is built
712 // or no, it serve as a way to handle the case of a node with
713 // multiple hosts attached to it but not yet connected to the
715 // switchesToProgram.add(rootNode);
716 //updatePerHostRuleInSW(host, rootNode,
718 // new HostNodePair(host, rootNode),ports);
720 // log.debug("Getting out at the end!");
721 return switchesToProgram;
725 * Routine that fetch the per-Host rules from the rulesDB and
726 * install in HW, the one having the same match rules will be
727 * overwritten silently.
729 * @param host host for which we want to install in HW the per-Host rules
730 * @param switchesToProgram list of switches to be programmed in
731 * HW, usually are them all, but better to be explicit, that list
732 * may change with time based on new switch addition/removal
734 * @return a return code that convey the programming status of the HW
736 private RulesProgrammingReturnCode installPerHostRules(
737 HostNodeConnector host, Set<Node> switchesToProgram) {
738 RulesProgrammingReturnCode retCode = RulesProgrammingReturnCode.SUCCESS;
739 if (host == null || switchesToProgram == null) {
740 return RulesProgrammingReturnCode.FAILED_WRONG_PARAMS;
742 Map<NodeConnector, FlowEntry> pos;
744 // Now program every single switch
745 log.debug("Inside installPerHostRules");
746 for (Node swId : switchesToProgram) {
747 HostNodePair key = new HostNodePair(host, swId);
748 pos = this.rulesDB.get(key);
752 for (Map.Entry<NodeConnector, FlowEntry> e : pos.entrySet()) {
755 // Populate the Policy field now
756 Status poStatus = this.frm.modifyOrAddFlowEntry(po);
757 if (!poStatus.isSuccess()) {
758 log.error("Failed to install policy: "
759 + po.getGroupName() + " ("
760 + poStatus.getDescription() + ")");
762 retCode = RulesProgrammingReturnCode.FAILED_FEW_SWITCHES;
763 // Remove the entry from the DB, it was not installed!
764 this.rulesDB.remove(key);
766 log.debug("Successfully installed policy "
767 + po.toString() + " on switch " + swId);
770 log.error("Cannot find a policy for SW:({}) Host: ({})",
772 /* // Now dump every single rule */
773 /* for (HostNodePair dumpkey : this.rulesDB.keySet()) { */
774 /* po = this.rulesDB.get(dumpkey); */
775 /* log.debug("Dumping entry H{" + dumpkey.getHost() + "} S{" + dumpkey.getSwitchId() + "} = {" + (po == null ? "null policy" : po)); */
780 log.debug("Leaving installPerHostRules");
785 * Cleanup all the host rules for a given host
787 * @param host Host for which the host rules need to be cleaned
788 * up, the host could be null in that case it match all the hosts
790 * @return a return code that convey the programming status of the HW
792 private RulesProgrammingReturnCode uninstallPerHostRules(HostNodeConnector host) {
793 RulesProgrammingReturnCode retCode = RulesProgrammingReturnCode.SUCCESS;
794 Map<NodeConnector, FlowEntry> pos;
796 // Now program every single switch
797 for (HostNodePair key : this.rulesDB.keySet()) {
798 if (host == null || key.getHost().equals(host)) {
799 pos = this.rulesDB.get(key);
800 for (Map.Entry<NodeConnector, FlowEntry> e : pos.entrySet()) {
803 // Uninstall the policy
804 this.frm.uninstallFlowEntry(po);
807 this.rulesDB.remove(key);
814 * Cleanup all the host rules for a given node, triggered when the
815 * switch disconnects, so there is no reason for Hw cleanup
816 * because it's disconnected anyhow
817 * TBD - Revisit above stmt in light of CSCus88743
818 * @param targetNode Node for which we want to do cleanup
821 private void uninstallPerNodeRules(Node targetNode) {
822 //RulesProgrammingReturnCode retCode = RulesProgrammingReturnCode.SUCCESS;
823 Map<NodeConnector, FlowEntry> pos;
826 // Now program every single switch
827 for (HostNodePair key : this.rulesDB.keySet()) {
828 Node node = key.getNode();
829 if (targetNode == null || node.equals(targetNode)) {
830 log.debug("Work on {} host {}", node, key.getHost());
831 pos = this.rulesDB.get(key);
832 for (Map.Entry<NodeConnector, FlowEntry> e : pos.entrySet()) {
835 // Uninstall the policy
836 this.frm.uninstallFlowEntry(po);
839 log.debug("Remove {}", key);
840 this.rulesDB.remove(key);
846 * Cleanup all the host rules currently present in the rulesDB
848 * @return a return code that convey the programming status of the HW
850 private RulesProgrammingReturnCode uninstallPerHostRules() {
851 return uninstallPerHostRules(null);
855 public void recalculateDone() {
856 if (this.hostTracker == null) {
857 //Not yet ready to process all the updates
858 //TODO: we should make sure that this call is executed eventually
861 Set<HostNodeConnector> allHosts = this.hostTracker.getAllHosts();
862 for (HostNodeConnector host : allHosts) {
863 Set<Node> switches = preparePerHostRules(host);
864 if (switches != null) {
865 // This will refresh existing rules, by overwriting
867 installPerHostRules(host, switches);
868 pruneExcessRules(switches);
873 void addTobePrunedPolicy(Node swId, FlowEntry po, FlowEntry new_po) {
874 List<FlowEntry> pl = tobePrunedPos.get(swId);
876 pl = new LinkedList<FlowEntry>();
877 tobePrunedPos.put(swId, pl);
880 log.debug("Adding Pruned Policy for SwId: {}", swId);
881 log.debug("Old Policy: {}", po);
882 log.debug("New Policy: {}", new_po);
885 private void pruneExcessRules(Set<Node> switches) {
886 for (Node swId : switches) {
887 List<FlowEntry> pl = tobePrunedPos.get(swId);
889 log.debug("Policies for Switch: {} in the list to be deleted: {}", swId, pl);
890 Iterator<FlowEntry> plIter = pl.iterator();
891 //for (Policy po: pl) {
892 while (plIter.hasNext()) {
893 FlowEntry po = plIter.next();
894 log.error("Removing Policy, Switch: {} Policy: {}", swId, po);
895 this.frm.uninstallFlowEntry(po);
899 // tobePrunedPos.remove(swId);
904 * A Host facing port has come up in a container. Add rules on the switch where this
905 * port has come up for all the known hosts to the controller.
906 * @param swId switch id of the port where port came up
907 * @param swPort port which came up
909 private void updateRulesforHIFup(Node node, NodeConnector swPort) {
910 if (this.hostTracker == null) {
911 //Not yet ready to process all the updates
914 log.debug("Host Facing Port in a container came up, install the rules for all hosts from this port !");
915 Set<HostNodeConnector> allHosts = this.hostTracker.getAllHosts();
916 for (HostNodeConnector host : allHosts) {
917 if (node.equals(host.getnodeconnectorNode())) {
919 * This host resides behind the same switch and port for which a port up
920 * message is received. Ideally this should not happen, but if it does,
921 * don't program any rules for this host
925 Set<Node> switches = preparePerHostPerSwitchRules(host, node,
927 if (switches != null) {
928 // This will refresh existing rules, by overwriting
930 installPerHostRules(host, switches);
937 public void notifyHTClient(HostNodeConnector host) {
941 Set<Node> switches = preparePerHostRules(host);
942 if (switches != null) {
943 installPerHostRules(host, switches);
945 // Green light for sending pending packet to this host. Safe to call if there are none.
946 sendPendingPacket(host.getNetworkAddress());
951 public void notifyHTClientHostRemoved(HostNodeConnector host) {
955 uninstallPerHostRules(host);
959 public void notifyNode(Node node, UpdateType type,
960 Map<String, Property> propMap) {
967 log.debug("Node {} gone, doing a cleanup", node);
968 uninstallPerNodeRules(node);
976 public void notifyNodeConnector(NodeConnector nodeConnector,
977 UpdateType type, Map<String, Property> propMap) {
978 if (nodeConnector == null) {
990 State state = (State) propMap.get(State.StatePropName);
991 if ((state != null) && (state.getValue() == State.EDGE_UP)) {
1000 handleNodeConnectorStatusUp(nodeConnector);
1002 handleNodeConnectorStatusDown(nodeConnector);
1006 private void handleNodeConnectorStatusUp(NodeConnector nodeConnector) {
1007 if (topologyManager == null) {
1008 log.debug("topologyManager is not set yet");
1012 if (topologyManager.isInternal(nodeConnector)) {
1013 log.debug("{} is not a host facing link", nodeConnector);
1017 log.debug("{} is up", nodeConnector);
1018 updateRulesforHIFup(nodeConnector.getNode(), nodeConnector);
1021 private void handleNodeConnectorStatusDown(NodeConnector nodeConnector) {
1022 log.debug("{} is down", nodeConnector);
1025 void setClusterContainerService(IClusterContainerServices s) {
1026 log.debug("Cluster Service set");
1027 this.clusterContainerService = s;
1030 void unsetClusterContainerService(IClusterContainerServices s) {
1031 if (this.clusterContainerService == s) {
1032 log.debug("Cluster Service removed!");
1033 this.clusterContainerService = null;
1038 * Function called by the dependency manager when all the required
1039 * dependencies are satisfied
1047 * Function called by the dependency manager when at least one
1048 * dependency become unsatisfied or when the component is shutting
1049 * down because for example bundle is being stopped.
1056 * Function called by dependency manager after "init ()" is called
1057 * and after the services provided by the class are registered in
1058 * the service registry
1065 * Function called by the dependency manager before the services
1066 * exported by the component are unregistered, this will be
1067 * followed by a "destroy ()" calls
1071 pendingPacketsAgerTimer.cancel();
1072 pendingPacketDestinations.clear();
1075 public void setSwitchManager(ISwitchManager switchManager) {
1076 this.switchManager = switchManager;
1079 public void unsetSwitchManager(ISwitchManager switchManager) {
1080 if (this.switchManager == switchManager) {
1081 this.switchManager = null;
1086 public PacketResult receiveDataPacket(RawPacket inPkt) {
1087 if (inPkt == null) {
1088 return PacketResult.IGNORED;
1090 log.trace("Received a frame of size: {}", inPkt.getPacketData().length);
1091 Packet formattedPak = this.dataPacketService.decodeDataPacket(inPkt);
1092 if (formattedPak instanceof Ethernet) {
1093 Object nextPak = formattedPak.getPayload();
1094 if (nextPak instanceof IPv4) {
1095 log.trace("Handle punted IP packet: {}", formattedPak);
1096 handlePuntedIPPacket((IPv4) nextPak, inPkt.getIncomingNodeConnector(), true);
1099 return PacketResult.IGNORED;
1103 private void handlePuntedIPPacket(IPv4 pkt, NodeConnector incomingNodeConnector, boolean allowAddPending) {
1104 InetAddress dIP = NetUtils.getInetAddress(pkt.getDestinationAddress());
1105 if (dIP == null || hostTracker == null) {
1106 log.debug("Invalid param(s) in handlePuntedIPPacket.. DestIP: {}. hostTracker: {}", dIP, hostTracker);
1109 HostNodeConnector destHost = hostTracker.hostFind(dIP);
1111 * In cases when incoming and outgoing connectors are in the same node, there is no need
1112 * to verify that there is a route. Because of that, we will only need routing.getRoute()
1113 * if we know that src and dst nodes are different.
1115 if (destHost != null
1116 && (incomingNodeConnector.getNode().equals(destHost.getnodeconnectorNode()) ||
1118 routing.getRoute(incomingNodeConnector.getNode(), destHost.getnodeconnectorNode()) != null)) {
1120 log.trace("Host {} is at {}", dIP, destHost.getnodeConnector());
1122 // If SimpleForwarding is aware of this host, it will try to install
1123 // a path. Forward packet until it's done.
1124 if (dataPacketService != null) {
1127 * if we know where the host is and there's a path from where this
1128 * packet was punted to where the host is, then attempt best effort delivery to the host
1130 NodeConnector nc = destHost.getnodeConnector();
1131 log.trace("Forwarding punted IP received at {} to {}", incomingNodeConnector, nc);
1132 // re-encode the Ethernet packet (the parent of the IPv4 packet)
1133 RawPacket rp = this.dataPacketService.encodeDataPacket(pkt.getParent());
1134 rp.setOutgoingNodeConnector(nc);
1135 this.dataPacketService.transmitDataPacket(rp);
1137 } else if (allowAddPending) {
1138 // If we made it here, let's hang on to the punted packet, with hopes that its destination
1139 // will become available soon.
1140 addToPendingPackets(dIP, pkt, incomingNodeConnector);
1142 log.warn("Dropping punted IP packet received at {} to Host {}", incomingNodeConnector, dIP);