/**:: * Copyright 2013, Big Switch Networks, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. **/ package net.floodlightcontroller.topology; import com.google.common.collect.ImmutableSet; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.types.NodePortTuple; import net.floodlightcontroller.linkdiscovery.Link; import net.floodlightcontroller.routing.BroadcastTree; import net.floodlightcontroller.routing.Path; import net.floodlightcontroller.routing.PathId; import net.floodlightcontroller.statistics.SwitchPortBandwidth; import net.floodlightcontroller.util.ClusterDFS; import org.projectfloodlight.openflow.protocol.OFPortDesc; import org.projectfloodlight.openflow.types.DatapathId; import org.projectfloodlight.openflow.types.OFPort; import org.projectfloodlight.openflow.types.U64; import org.python.google.common.collect.ImmutableList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; import java.util.Map.Entry; import java.util.stream.Collectors; /** * A representation of a network topology. Used internally by * {@link TopologyManager} */ public class TopologyInstance { public static final short LT_SH_LINK = 1; public static final short LT_BD_LINK = 2; public static final short LT_TUNNEL = 3; public static final int MAX_LINK_WEIGHT = 10000; public static final int MAX_PATH_WEIGHT = Integer.MAX_VALUE - MAX_LINK_WEIGHT - 1; public static final int PATH_CACHE_SIZE = 1000; private static final Logger log = LoggerFactory.getLogger(TopologyInstance.class); /* Global: general switch, port, link */ private Set<DatapathId> switches; private Map<DatapathId, Set<OFPort>> portsWithLinks; /* only ports with links */ private Map<DatapathId, Set<OFPort>> portsPerSwitch; /* every port on the switch */ private Set<NodePortTuple> portsTunnel; /* all tunnel ports in topology */ private Set<NodePortTuple> portsBroadcastAll; /* all broadcast ports in topology */ private Map<DatapathId, Set<OFPort>> portsBroadcastPerSwitch; /* broadcast ports mapped per DPID */ private Set<NodePortTuple> portsWithMoreThanTwoLinks; /* a.k.a. "broadcast domain" non-P2P ports */ private Map<NodePortTuple, Set<Link>> links; /* every link in entire topology */ private Map<NodePortTuple, Set<Link>> linksNonBcastNonTunnel; /* only non-broadcast and non-tunnel links */ private Map<NodePortTuple, Set<Link>> linksExternal; /* BDDP links b/t clusters */ private Set<Link> linksNonExternalInterCluster; /* Blocked */ private Set<NodePortTuple> portsBlocked; private Set<Link> linksBlocked; /* Per-cluster */ private Set<Cluster> clusters; private Map<DatapathId, Set<NodePortTuple>> clusterPorts; /* ports in the cluster ID */ private Map<DatapathId, Cluster> clusterFromSwitch; /* cluster for each switch */ /* Per-archipelago */ private List<Archipelago> archipelagos; /* connected clusters */ private Map<Cluster, Archipelago> archipelagoFromCluster; private Map<DatapathId, Set<NodePortTuple>> portsBroadcastPerArchipelago; /* broadcast ports in each archipelago ID */ private Map<PathId, List<Path>> pathcache; /* contains computed paths ordered best to worst */ protected TopologyInstance(Map<DatapathId, Set<OFPort>> portsWithLinks, Set<NodePortTuple> portsBlocked, Map<NodePortTuple, Set<Link>> linksNonBcastNonTunnel, Set<NodePortTuple> portsWithMoreThanTwoLinks, Set<NodePortTuple> portsTunnel, Map<NodePortTuple, Set<Link>> links, Map<DatapathId, Set<OFPort>> portsPerSwitch, Map<NodePortTuple, Set<Link>> linksExternal) { this.switches = new HashSet<DatapathId>(portsWithLinks.keySet()); this.portsWithLinks = new HashMap<DatapathId, Set<OFPort>>(); for (DatapathId sw : portsWithLinks.keySet()) { this.portsWithLinks.put(sw, new HashSet<OFPort>(portsWithLinks.get(sw))); } this.portsPerSwitch = new HashMap<DatapathId, Set<OFPort>>(); for (DatapathId sw : portsPerSwitch.keySet()) { this.portsPerSwitch.put(sw, new HashSet<OFPort>(portsPerSwitch.get(sw))); } this.portsBlocked = new HashSet<NodePortTuple>(portsBlocked); this.linksNonBcastNonTunnel = new HashMap<NodePortTuple, Set<Link>>(); for (NodePortTuple npt : linksNonBcastNonTunnel.keySet()) { this.linksNonBcastNonTunnel.put(npt, new HashSet<Link>(linksNonBcastNonTunnel.get(npt))); } this.links = new HashMap<NodePortTuple, Set<Link>>(); for (NodePortTuple npt : links.keySet()) { this.links.put(npt, new HashSet<Link>(links.get(npt))); } this.linksExternal = new HashMap<NodePortTuple, Set<Link>>(); for (NodePortTuple npt : linksExternal.keySet()) { this.linksExternal.put(npt, new HashSet<Link>(linksExternal.get(npt))); } this.linksNonExternalInterCluster = new HashSet<Link>(); this.archipelagos = new ArrayList<Archipelago>(); this.portsWithMoreThanTwoLinks = new HashSet<NodePortTuple>(portsWithMoreThanTwoLinks); this.portsTunnel = new HashSet<NodePortTuple>(portsTunnel); this.linksBlocked = new HashSet<Link>(); this.clusters = new HashSet<Cluster>(); this.clusterFromSwitch = new HashMap<DatapathId, Cluster>(); this.portsBroadcastAll= new HashSet<NodePortTuple>(); this.portsBroadcastPerSwitch = new HashMap<DatapathId,Set<OFPort>>(); this.pathcache = new HashMap<PathId, List<Path>>(); this.portsBroadcastPerArchipelago = new HashMap<DatapathId, Set<NodePortTuple>>(); this.archipelagoFromCluster = new HashMap<Cluster, Archipelago>(); } protected void compute() { /* * Step 1: Compute clusters ignoring ports with > 2 links and * blocked links. */ identifyClusters(); /* * Step 2: Associate non-blocked links within clusters to the cluster * in which they reside. The remaining links are inter-cluster links. */ identifyIntraClusterLinks(); /* * Step 3: Compute the archipelagos. (Def: group of conneccted clusters) * Each archipelago will have its own broadcast tree, chosen by running * dijkstra's algorithm from the archipelago ID switch (lowest switch * DPID). We need a broadcast tree per archipelago since each * archipelago is by definition isolated from all other archipelagos. */ identifyArchipelagos(); /* * Step 4: Use Yens algorithm to permute through each node combination * within each archipelago and compute multiple paths. The shortest * path located (i.e. first run of dijkstra's algorithm) will be used * as the broadcast tree for the archipelago. */ computeOrderedPaths(); /* * Step 5: Determine the broadcast ports for each archipelago. These are * the ports that reside on the broadcast tree computed and saved when * performing path-finding. These are saved into multiple data structures * to aid in quick lookup per archipelago, per-switch, and topology-global. */ computeBroadcastPortsPerArchipelago(); /* * Step 6: Optionally, print topology to log for added verbosity or when debugging. */ printTopology(); } /* * Checks if OF port is edge port */ public boolean isEdge(DatapathId sw, OFPort portId) { NodePortTuple np = new NodePortTuple(sw, portId); if (links.get(np) == null || links.get(np).isEmpty()) { return true; } else { return false; } } /* * Returns broadcast ports for the given DatapathId */ public Set<OFPort> swBroadcastPorts(DatapathId sw) { if (!portsBroadcastPerSwitch.containsKey(sw) || portsBroadcastPerSwitch.get(sw) == null) { log.debug("Could not locate broadcast ports for switch {}", sw); return Collections.emptySet(); } else { if (log.isDebugEnabled()) { log.debug("Found broadcast ports {} for switch {}", portsBroadcastPerSwitch.get(sw), sw); } return portsBroadcastPerSwitch.get(sw); } } private void printTopology() { log.debug("-----------------Topology-----------------------"); log.debug("All Links: {}", links); log.debug("Tunnel Ports: {}", portsTunnel); log.debug("Clusters: {}", clusters); log.debug("Broadcast Ports Per Node (!!): {}", portsBroadcastPerSwitch); log.debug("3+ Link Ports: {}", portsWithMoreThanTwoLinks); log.debug("Archipelagos: {}", archipelagos); log.debug("-----------------------------------------------"); } private void identifyIntraClusterLinks() { for (DatapathId s : switches) { if (portsWithLinks.get(s) == null) continue; for (OFPort p : portsWithLinks.get(s)) { NodePortTuple np = new NodePortTuple(s, p); if (linksNonBcastNonTunnel.get(np) == null) continue; if (isBroadcastPort(np)) continue; for (Link l : linksNonBcastNonTunnel.get(np)) { if (isBlockedLink(l)) continue; if (isBroadcastLink(l)) continue; Cluster c1 = clusterFromSwitch.get(l.getSrc()); Cluster c2 = clusterFromSwitch.get(l.getDst()); if (c1 == c2) { c1.addLink(l); /* link is within cluster */ } else { linksNonExternalInterCluster.add(l); } } } } } /** * @author Srinivasan Ramasubramanian * * This function divides the network into clusters. Every cluster is * a strongly connected component. The network may contain unidirectional * links. The function calls dfsTraverse for performing depth first * search and cluster formation. * * The computation of strongly connected components is based on * Tarjan's algorithm. For more details, please see the Wikipedia * link below. * * http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm */ private void identifyClusters() { Map<DatapathId, ClusterDFS> dfsList = new HashMap<DatapathId, ClusterDFS>(); if (switches == null) return; for (DatapathId key : switches) { ClusterDFS cdfs = new ClusterDFS(); dfsList.put(key, cdfs); } Set<DatapathId> currSet = new HashSet<DatapathId>(); for (DatapathId sw : switches) { ClusterDFS cdfs = dfsList.get(sw); if (cdfs == null) { log.error("No DFS object for switch {} found.", sw); } else if (!cdfs.isVisited()) { dfsTraverse(0, 1, sw, dfsList, currSet); } } } /** * @author Srinivasan Ramasubramanian * * This algorithm computes the depth first search (DFS) traversal of the * switches in the network, computes the lowpoint, and creates clusters * (of strongly connected components). * * The computation of strongly connected components is based on * Tarjan's algorithm. For more details, please see the Wikipedia * link below. * * http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm * * The initialization of lowpoint and the check condition for when a * cluster should be formed is modified as we do not remove switches that * are already part of a cluster. * * A return value of -1 indicates that dfsTraverse failed somewhere in the middle * of computation. This could happen when a switch is removed during the cluster * computation procedure. * * @param parentIndex: DFS index of the parent node * @param currIndex: DFS index to be assigned to a newly visited node * @param currSw: ID of the current switch * @param dfsList: HashMap of DFS data structure for each switch * @param currSet: Set of nodes in the current cluster in formation * @return long: DSF index to be used when a new node is visited */ private long dfsTraverse (long parentIndex, long currIndex, DatapathId currSw, Map<DatapathId, ClusterDFS> dfsList, Set<DatapathId> currSet) { //Get the DFS object corresponding to the current switch ClusterDFS currDFS = dfsList.get(currSw); Set<DatapathId> nodesInMyCluster = new HashSet<DatapathId>(); Set<DatapathId> myCurrSet = new HashSet<DatapathId>(); //Assign the DFS object with right values. currDFS.setVisited(true); currDFS.setDfsIndex(currIndex); currDFS.setParentDFSIndex(parentIndex); currIndex++; // Traverse the graph through every outgoing link. if (portsWithLinks.get(currSw) != null){ for (OFPort p : portsWithLinks.get(currSw)) { Set<Link> lset = linksNonBcastNonTunnel.get(new NodePortTuple(currSw, p)); if (lset == null) continue; for (Link l : lset) { DatapathId dstSw = l.getDst(); // ignore incoming links. if (dstSw.equals(currSw)) continue; // ignore if the destination is already added to // another cluster if (clusterFromSwitch.get(dstSw) != null) continue; // ignore the link if it is blocked. if (isBlockedLink(l)) continue; // ignore this link if it is in broadcast domain if (isBroadcastLink(l)) continue; // Get the DFS object corresponding to the dstSw ClusterDFS dstDFS = dfsList.get(dstSw); if (dstDFS.getDfsIndex() < currDFS.getDfsIndex()) { // could be a potential lowpoint if (dstDFS.getDfsIndex() < currDFS.getLowpoint()) { currDFS.setLowpoint(dstDFS.getDfsIndex()); } } else if (!dstDFS.isVisited()) { // make a DFS visit currIndex = dfsTraverse( currDFS.getDfsIndex(), currIndex, dstSw, dfsList, myCurrSet); if (currIndex < 0) return -1; // update lowpoint after the visit if (dstDFS.getLowpoint() < currDFS.getLowpoint()) { currDFS.setLowpoint(dstDFS.getLowpoint()); } nodesInMyCluster.addAll(myCurrSet); myCurrSet.clear(); } // else, it is a node already visited with a higher // dfs index, just ignore. } } } nodesInMyCluster.add(currSw); currSet.addAll(nodesInMyCluster); // Cluster computation. // If the node's lowpoint is greater than its parent's DFS index, // we need to form a new cluster with all the switches in the // currSet. if (currDFS.getLowpoint() > currDFS.getParentDFSIndex()) { // The cluster thus far forms a strongly connected component. // create a new switch cluster and the switches in the current // set to the switch cluster. Cluster sc = new Cluster(); for (DatapathId sw : currSet) { sc.add(sw); clusterFromSwitch.put(sw, sc); } // delete all the nodes in the current set. currSet.clear(); // add the newly formed switch clusters to the cluster set. clusters.add(sc); } return currIndex; } public Set<NodePortTuple> getBlockedPorts() { return this.portsBlocked; } public Set<Link> getBlockedLinks() { return this.linksBlocked; } /** Returns true if a link has either one of its switch ports * blocked. * @param l * @return */ public boolean isBlockedLink(Link l) { NodePortTuple n1 = new NodePortTuple(l.getSrc(), l.getSrcPort()); NodePortTuple n2 = new NodePortTuple(l.getDst(), l.getDstPort()); return (isBlockedPort(n1) || isBlockedPort(n2)); } public boolean isBlockedPort(NodePortTuple npt) { return portsBlocked.contains(npt); } public boolean isTunnelPort(NodePortTuple npt) { return portsTunnel.contains(npt); } public boolean isTunnelLink(Link l) { NodePortTuple n1 = new NodePortTuple(l.getSrc(), l.getSrcPort()); NodePortTuple n2 = new NodePortTuple(l.getDst(), l.getDstPort()); return (isTunnelPort(n1) || isTunnelPort(n2)); } public boolean isBroadcastLink(Link l) { NodePortTuple n1 = new NodePortTuple(l.getSrc(), l.getSrcPort()); NodePortTuple n2 = new NodePortTuple(l.getDst(), l.getDstPort()); return (isBroadcastPort(n1) || isBroadcastPort(n2)); } public boolean isBroadcastPort(NodePortTuple npt) { return portsBroadcastAll.contains(npt); } private class NodeDist implements Comparable<NodeDist> { private final DatapathId node; public DatapathId getNode() { return node; } private final int dist; public int getDist() { return dist; } public NodeDist(DatapathId node, int dist) { this.node = node; this.dist = dist; } @Override public int compareTo(NodeDist o) { if (o.dist == this.dist) { return (int)(this.node.getLong() - o.node.getLong()); } return this.dist - o.dist; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; NodeDist other = (NodeDist) obj; if (!getOuterType().equals(other.getOuterType())) return false; if (node == null) { if (other.node != null) return false; } else if (!node.equals(other.node)) return false; return true; } @Override public int hashCode() { assert false : "hashCode not designed"; return 42; } private TopologyInstance getOuterType() { return TopologyInstance.this; } } protected void identifyArchipelagos() { // Iterate through each external link and create/merge archipelagos based on the // islands that each link is connected to Cluster srcCluster = null; Cluster dstCluster = null; Archipelago srcArchipelago = null; Archipelago dstArchipelago = null; Set<Link> links = new HashSet<Link>(); for (Set<Link> linkset : linksExternal.values()) { links.addAll(linkset); } links.addAll(linksNonExternalInterCluster); /* Base case of 1:1 mapping b/t clusters and archipelagos */ if (links.isEmpty()) { if (!clusters.isEmpty()) { clusters.forEach(c -> archipelagos.add(new Archipelago().add(c))); } } else { /* Only for two or more adjacent clusters that form archipelagos */ for (Link l : links) { for (Cluster c : clusters) { if (c.getNodes().contains(l.getSrc())) srcCluster = c; if (c.getNodes().contains(l.getDst())) dstCluster = c; } for (Archipelago a : archipelagos) { // Is source cluster a part of an existing archipelago? if (a.isMember(srcCluster)) srcArchipelago = a; // Is destination cluster a part of an existing archipelago? if (a.isMember(dstCluster)) dstArchipelago = a; } // Are they both found in an archipelago? If so, then merge the two. if (srcArchipelago != null && dstArchipelago != null && !srcArchipelago.equals(dstArchipelago)) { srcArchipelago.merge(dstArchipelago); archipelagos.remove(dstArchipelago); archipelagoFromCluster.put(dstCluster, srcArchipelago); } // If neither were found in an existing, then form a new archipelago. else if (srcArchipelago == null && dstArchipelago == null) { Archipelago a = new Archipelago().add(srcCluster).add(dstCluster); archipelagos.add(a); archipelagoFromCluster.put(srcCluster, a); archipelagoFromCluster.put(dstCluster, a); } // If only one is found in an existing, then add the one not found to the existing. else if (srcArchipelago != null && dstArchipelago == null) { srcArchipelago.add(dstCluster); archipelagoFromCluster.put(dstCluster, srcArchipelago); } else if (srcArchipelago == null && dstArchipelago != null) { dstArchipelago.add(srcCluster); archipelagoFromCluster.put(srcCluster, dstArchipelago); } srcCluster = null; dstCluster = null; srcArchipelago = null; dstArchipelago = null; } } } /* * Dijkstra that calculates destination rooted trees over the entire topology. */ private BroadcastTree dijkstra(Map<DatapathId, Set<Link>> links, DatapathId root, Map<Link, Integer> linkCost, boolean isDstRooted) { HashMap<DatapathId, Link> nexthoplinks = new HashMap<DatapathId, Link>(); HashMap<DatapathId, Integer> cost = new HashMap<DatapathId, Integer>(); int w; for (DatapathId node : links.keySet()) { nexthoplinks.put(node, null); cost.put(node, MAX_PATH_WEIGHT); //log.debug("Added max cost to {}", node); } HashMap<DatapathId, Boolean> seen = new HashMap<DatapathId, Boolean>(); PriorityQueue<NodeDist> nodeq = new PriorityQueue<NodeDist>(); nodeq.add(new NodeDist(root, 0)); cost.put(root, 0); //log.debug("{}", links); while (nodeq.peek() != null) { NodeDist n = nodeq.poll(); DatapathId cnode = n.getNode(); int cdist = n.getDist(); if (cdist >= MAX_PATH_WEIGHT) break; if (seen.containsKey(cnode)) continue; seen.put(cnode, true); //log.debug("cnode {} and links {}", cnode, links.get(cnode)); if (links.get(cnode) == null) continue; for (Link link : links.get(cnode)) { DatapathId neighbor; if (isDstRooted == true) { neighbor = link.getSrc(); } else { neighbor = link.getDst(); } // links directed toward cnode will result in this condition if (neighbor.equals(cnode)) continue; if (seen.containsKey(neighbor)) continue; if (linkCost == null || linkCost.get(link) == null) { w = 1; } else { w = linkCost.get(link); } int ndist = cdist + w; // the weight of the link, always 1 in current version of floodlight. log.debug("Neighbor: {}", neighbor); log.debug("Cost: {}", cost); log.debug("Neighbor cost: {}", cost.get(neighbor)); if (ndist < cost.get(neighbor)) { cost.put(neighbor, ndist); nexthoplinks.put(neighbor, link); NodeDist ndTemp = new NodeDist(neighbor, ndist); // Remove an object that's already in there. // Note that the comparison is based on only the node id, // and not node id and distance. nodeq.remove(ndTemp); // add the current object to the queue. nodeq.add(ndTemp); } } } BroadcastTree ret = new BroadcastTree(nexthoplinks, cost); return ret; } /* * Creates a map of links and the cost associated with each link */ public Map<Link,Integer> initLinkCostMap() { Map<Link, Integer> linkCost = new HashMap<Link, Integer>(); int tunnel_weight = portsWithLinks.size() + 1; switch (TopologyManager.getPathMetricInternal()){ case HOPCOUNT_AVOID_TUNNELS: log.debug("Using hop count with tunnel bias for metrics"); for (NodePortTuple npt : portsTunnel) { if (links.get(npt) == null) { continue; } for (Link link : links.get(npt)) { if (link == null) { continue; } linkCost.put(link, tunnel_weight); } } return linkCost; case HOPCOUNT: log.debug("Using hop count w/o tunnel bias for metrics"); for (NodePortTuple npt : links.keySet()) { if (links.get(npt) == null) { continue; } for (Link link : links.get(npt)) { if (link == null) { continue; } linkCost.put(link,1); } } return linkCost; case LATENCY: log.debug("Using latency for path metrics"); for (NodePortTuple npt : links.keySet()) { if (links.get(npt) == null) { continue; } for (Link link : links.get(npt)) { if (link == null) { continue; } if ((int)link.getLatency().getValue() < 0 || (int)link.getLatency().getValue() > MAX_LINK_WEIGHT) { linkCost.put(link, MAX_LINK_WEIGHT); } else { linkCost.put(link,(int)link.getLatency().getValue()); } } } return linkCost; case LINK_SPEED: TopologyManager.statisticsService.collectStatistics(true); log.debug("Using link speed for path metrics"); for (NodePortTuple npt : links.keySet()) { if (links.get(npt) == null) { continue; } long rawLinkSpeed = 0; IOFSwitch s = TopologyManager.switchService.getSwitch(npt.getNodeId()); if (s != null) { OFPortDesc p = s.getPort(npt.getPortId()); if (p != null) { rawLinkSpeed = p.getCurrSpeed(); } } for (Link link : links.get(npt)) { if (link == null) { continue; } if ((rawLinkSpeed / 10^6) / 8 > 1) { int linkSpeedMBps = (int)(rawLinkSpeed / 10^6) / 8; linkCost.put(link, (1/linkSpeedMBps)*1000); } else { linkCost.put(link, MAX_LINK_WEIGHT); } } } return linkCost; case UTILIZATION: TopologyManager.statisticsService.collectStatistics(true); log.debug("Using utilization for path metrics"); for (NodePortTuple npt : links.keySet()) { if (links.get(npt) == null) continue; SwitchPortBandwidth spb = TopologyManager.statisticsService .getBandwidthConsumption(npt.getNodeId(), npt.getPortId()); long bpsTx = 0; if (spb != null) { bpsTx = spb.getBitsPerSecondTx().getValue(); } for (Link link : links.get(npt)) { if (link == null) { continue; } if ((bpsTx / 10^6) / 8 > 1) { int cost = (int) (bpsTx / 10^6) / 8; linkCost.put(link, cost); } else { linkCost.put(link, MAX_LINK_WEIGHT); } } } return linkCost; default: log.debug("Invalid Selection: Using Default Hop Count with Tunnel Bias for Metrics"); for (NodePortTuple npt : portsTunnel) { if (links.get(npt) == null) continue; for (Link link : links.get(npt)) { if (link == null) continue; linkCost.put(link, tunnel_weight); } } return linkCost; } } /* * Calculates and stores n possible paths using Yen's algorithm, * looping through every switch. These lists of routes are stored * in the pathcache. */ private void computeOrderedPaths() { List<Path> paths; PathId pathId; pathcache.clear(); for (Archipelago a : archipelagos) { /* for each archipelago */ Set<DatapathId> srcSws = a.getSwitches(); Set<DatapathId> dstSws = a.getSwitches(); log.debug("SRC {}", srcSws); log.debug("DST {}", dstSws); for (DatapathId src : srcSws) { /* permute all member switches */ for (DatapathId dst : dstSws) { log.debug("Calling Yens {} {}", src, dst); paths = yens(src, dst, TopologyManager.getMaxPathsToComputeInternal(), getArchipelago(src), getArchipelago(dst)); pathId = new PathId(src, dst); pathcache.put(pathId, paths); log.debug("Adding paths {}", paths); } } } } private Path buildPath(PathId id, BroadcastTree tree) { NodePortTuple npt; DatapathId srcId = id.getSrc(); DatapathId dstId = id.getDst(); //set of NodePortTuples on the route LinkedList<NodePortTuple> sPorts = new LinkedList<NodePortTuple>(); if (tree == null) return new Path(id, ImmutableList.of()); /* empty route */ Map<DatapathId, Link> nexthoplinks = tree.getLinks(); if (!switches.contains(srcId) || !switches.contains(dstId)) { // This is a switch that is not connected to any other switch // hence there was no update for links (and hence it is not // in the network) log.debug("buildpath: Standalone switch: {}", srcId); // The only possible non-null path for this case is // if srcId equals dstId --- and that too is an 'empty' path [] } else if ((nexthoplinks != null) && (nexthoplinks.get(srcId) != null)) { while (!srcId.equals(dstId)) { Link l = nexthoplinks.get(srcId); npt = new NodePortTuple(l.getSrc(), l.getSrcPort()); sPorts.addLast(npt); npt = new NodePortTuple(l.getDst(), l.getDstPort()); sPorts.addLast(npt); srcId = nexthoplinks.get(srcId).getDst(); } } // else, no path exists, and path equals null Path result = null; if (sPorts != null && !sPorts.isEmpty()) { result = new Path(id, sPorts); } log.trace("buildpath: {}", result); return result == null ? new Path(id, ImmutableList.of()) : result; } /* * Getter Functions */ public boolean pathExists(DatapathId srcId, DatapathId dstId) { Archipelago srcA = getArchipelago(srcId); Archipelago dstA = getArchipelago(dstId); if (!srcA.getId().equals(dstA.getId())) { return false; } BroadcastTree bt = srcA.getBroadcastTree(); if (bt == null) { return false; } Link link = bt.getLinks().get(srcId); if (link == null) { return false; } return true; } private Map<DatapathId, Set<Link>> buildLinkDpidMap(Set<DatapathId> switches, Map<DatapathId, Set<OFPort>> portsWithLinks, Map<NodePortTuple, Set<Link>> links) { Map<DatapathId, Set<Link>> linkDpidMap = new HashMap<DatapathId, Set<Link>>(); for (DatapathId s : switches) { if (portsWithLinks.get(s) == null) continue; for (OFPort p : portsWithLinks.get(s)) { NodePortTuple np = new NodePortTuple(s, p); if (links.get(np) == null) continue; for (Link l : links.get(np)) { if (switches.contains(l.getSrc()) && switches.contains(l.getDst())) { if (linkDpidMap.containsKey(s)) { linkDpidMap.get(s).add(l); } else { linkDpidMap.put(s, new HashSet<Link>(Arrays.asList(l))); } } } } } return linkDpidMap; } /** * * This function returns K number of routes between a source and destination IF THEY EXIST IN THE ROUTECACHE. * If the user requests more routes than available, only the routes already stored in memory will be returned. * This value can be adjusted in floodlightdefault.properties. * * * @param src: DatapathId of the route source. * @param dst: DatapathId of the route destination. * @param k: The number of routes that you want. Must be positive integer. * @return ArrayList of Routes or null if bad parameters */ public List<Path> getPathsFast(DatapathId src, DatapathId dst, int k) { PathId routeId = new PathId(src, dst); List<Path> routes = pathcache.get(routeId); if (routes == null || k < 1) { return ImmutableList.of(); } if (k >= TopologyManager.getMaxPathsToComputeInternal() || k >= routes.size()) { return routes; } else { return routes.subList(0, k); } } /** * * This function returns K number of paths between a source and destination. It will attempt to retrieve * these paths from the pathcache. If the user requests more paths than are stored, Yen's algorithm will be * run using the K value passed in. * * * @param src: DatapathId of the path source. * @param dst: DatapathId of the path destination. * @param k: The number of path that you want. Must be positive integer. * @return list of paths or empty */ public List<Path> getPathsSlow(DatapathId src, DatapathId dst, int k) { PathId pathId = new PathId(src, dst); List<Path> paths = pathcache.get(pathId); if (paths == null || k < 1) return ImmutableList.of(); if (k >= TopologyManager.getMaxPathsToComputeInternal() || k >= paths.size()) { return yens(src, dst, k, getArchipelago(src), getArchipelago(dst)); /* heavy computation */ } else { return new ArrayList<Path>(paths.subList(0, k)); } } private Archipelago getArchipelago(DatapathId d) { for (Archipelago a : archipelagos) { if (a.getSwitches().contains(d)) { return a; } } return null; } public void setPathCosts(Path p) { U64 cost = U64.ZERO; // Set number of hops. Assuming the list of NPTs is always even. p.setHopCount(p.getPath().size()/2); for (int i = 0; i <= p.getPath().size() - 2; i = i + 2) { DatapathId src = p.getPath().get(i).getNodeId(); DatapathId dst = p.getPath().get(i + 1).getNodeId(); OFPort srcPort = p.getPath().get(i).getPortId(); OFPort dstPort = p.getPath().get(i + 1).getPortId(); for (Link l : links.get(p.getPath().get(i))) { if (l.getSrc().equals(src) && l.getDst().equals(dst) && l.getSrcPort().equals(srcPort) && l.getDstPort().equals(dstPort)) { log.debug("Matching link found: {}", l); cost = cost.add(l.getLatency()); } } } p.setLatency(cost); log.debug("Total cost is {}", cost); log.debug(p.toString()); } private List<Path> yens(DatapathId src, DatapathId dst, Integer K, Archipelago aSrc, Archipelago aDst) { log.debug("YENS ALGORITHM -----------------"); log.debug("Asking for paths from {} to {}", src, dst); log.debug("Asking for {} paths", K); // Find link costs Map<Link, Integer> linkCost = initLinkCostMap(); Map<DatapathId, Set<Link>> linkDpidMap = buildLinkDpidMap(switches, portsWithLinks, links); Map<DatapathId, Set<Link>> copyOfLinkDpidMap = new HashMap<DatapathId, Set<Link>>(linkDpidMap); // A is the list of shortest paths. The number in the list at the end should be less than or equal to K // B is the list of possible shortest paths found in this function. List<Path> A = new ArrayList<Path>(); List<Path> B = new ArrayList<Path>(); // The number of paths requested should never be less than 1. if (K < 1) { return A; } /* The switch is not a member of an archipelago. It must not be connected */ if (aSrc == null || aDst == null) { log.warn("One or more switches not connected. Cannot compute path b/t {} and {}", src, dst); return A; } if (!aSrc.equals(aDst)) { log.warn("Switches {} and {} not in same archipelago. Cannot compute path", src, dst); return A; } /* Use Dijkstra's to find the shortest path, which will also be the first path in A */ BroadcastTree bt = dijkstra(copyOfLinkDpidMap, dst, linkCost, true); /* add this initial tree as our archipelago's broadcast tree (aSrc == aDst) */ aSrc.setBroadcastTree(bt); /* now add the shortest path */ log.debug("src {} dst {} tree {}", new Object[] {src, dst, bt}); Path newroute = buildPath(new PathId(src, dst), bt); /* guaranteed to be in same tree */ if (newroute != null && !newroute.getPath().isEmpty()) { /* should never be null, but might be empty */ setPathCosts(newroute); A.add(newroute); log.debug("Found shortest path in Yens {}", newroute); } else { log.debug("No paths found in Yen's!"); return A; } // Loop through K - 1 times to get other possible shortest paths for (int k = 1; k < K; k++) { log.trace("k: {}", k); if (log.isTraceEnabled()){ log.trace("Path Length 'A.get(k-1).getPath().size()-2': {}", A.get(k - 1).getPath().size() - 2); } // Iterate through i, which is the number of links in the most recent path added to A for (int i = 0; i <= A.get(k - 1).getPath().size() - 2; i = i + 2) { log.trace("i: {}", i); List<NodePortTuple> path = A.get(k - 1).getPath(); //log.debug("A(k-1): {}", A.get(k - 1).getPath()); // The spur node is the point in the topology where Dijkstra's is called again to find another path DatapathId spurNode = path.get(i).getNodeId(); // rootPath is the path along the previous shortest path that is before the spur node Path rootPath = new Path(new PathId(path.get(0).getNodeId(), path.get(i).getNodeId()), path.subList(0, i)); Map<NodePortTuple, Set<Link>> allLinksCopy = new HashMap<NodePortTuple, Set<Link>>(links); // Remove the links after the spur node that are part of other paths in A so that new paths // found are unique for (Path r : A) { if (r.getPath().size() > (i + 1) && r.getPath().subList(0, i).equals(rootPath.getPath())) { allLinksCopy.remove(r.getPath().get(i)); allLinksCopy.remove(r.getPath().get(i+1)); } } // Removes the root path so Dijkstra's doesn't try to go through it to find a path Set<DatapathId> switchesCopy = new HashSet<DatapathId>(switches); for (NodePortTuple npt : rootPath.getPath()) { if (!npt.getNodeId().equals(spurNode)) { switchesCopy.remove(npt.getNodeId()); } } // Builds the new topology without the parts we want removed copyOfLinkDpidMap = buildLinkDpidMap(switchesCopy, portsWithLinks, allLinksCopy); // Uses Dijkstra's to try to find a shortest path from the spur node to the destination Path spurPath = buildPath(new PathId(spurNode, dst), dijkstra(copyOfLinkDpidMap, dst, linkCost, true)); if (spurPath == null || spurPath.getPath().isEmpty()) { log.debug("spurPath is null"); continue; } // Adds the root path and spur path together to get a possible shortest path List<NodePortTuple> totalNpt = new LinkedList<NodePortTuple>(); totalNpt.addAll(rootPath.getPath()); totalNpt.addAll(spurPath.getPath()); Path totalPath = new Path(new PathId(src, dst), totalNpt); setPathCosts(totalPath); log.trace("Spur Node: {}", spurNode); log.trace("Root Path: {}", rootPath); log.trace("Spur Path: {}", spurPath); log.trace("Total Path: {}", totalPath); // Adds the new path into B int flag = 0; for (Path r_B : B) { for (Path r_A : A) { if (r_B.getPath().equals(totalPath.getPath()) || r_A.getPath().equals(totalPath.getPath())) { flag = 1; } } } if (flag == 0) { B.add(totalPath); } // Restore edges and nodes to graph } // If we get out of the loop and there isn't a path in B to add to A, all possible paths have been // found and return A if (B.isEmpty()) { //log.debug("B list is empty in Yen's"); break; } log.debug("Removing shortest path from {}", B); // Find the shortest path in B, remove it, and put it in A log.debug("--------------BEFORE------------------------"); for (Path r : B) { log.debug(r.toString()); } log.debug("--------------------------------------------"); Path shortestPath = removeShortestPath(B, linkCost); log.debug("--------------AFTER------------------------"); for (Path r : B) { log.debug(r.toString()); } log.debug("--------------------------------------------"); if (shortestPath != null) { log.debug("Adding new shortest path to {} in Yen's", shortestPath); A.add(shortestPath); log.debug("A: {}", A); } else { log.debug("removeShortestPath returned {}", shortestPath); } } // Set the route counts for (Path path : A) { path.setPathIndex(A.indexOf(path)); } //log.debug("END OF YEN'S --------------------"); return A; } private Path removeShortestPath(List<Path> routes, Map<Link, Integer> linkCost) { log.debug("REMOVE SHORTEST PATH -------------"); // If there is nothing in B, return if(routes == null){ log.debug("Routes == null"); return null; } Path shortestPath = null; // Set the default shortest path to the max value Integer shortestPathCost = Integer.MAX_VALUE; // Iterate through B and find the shortest path for (Path r : routes) { Integer pathCost = 0; // Add up the weights of each link in the path // TODO Get the path cost from the route object for (NodePortTuple npt : r.getPath()) { if (links.get(npt) == null || linkCost.get(links.get(npt).iterator().next()) == null) { pathCost++; } else { pathCost += linkCost.get(links.get(npt).iterator().next()); } } log.debug("Path {} with cost {}", r, pathCost); // If it is smaller than the current smallest, replace variables with the path just found if (pathCost < shortestPathCost) { log.debug("New shortest path {} with cost {}", r, pathCost); shortestPathCost = pathCost; shortestPath = r; } } log.debug("Remove {} from {}", shortestPath, routes); // Remove the route from B and return it routes.remove(shortestPath); log.debug("Shortest path: {}", shortestPath); return shortestPath; } /** * Computes end-to-end path including src/dst switch * ports in addition to the switches. This chains into * {@link #getPath(DatapathId, DatapathId)} below. * @param srcId * @param srcPort * @param dstId * @param dstPort * @return */ public Path getPath(DatapathId srcId, OFPort srcPort, DatapathId dstId, OFPort dstPort) { Path r = getPath(srcId, dstId); /* Path cannot be null, but empty b/t 2 diff DPIDs -> not found */ if (! srcId.equals(dstId) && r.getPath().isEmpty()) { return r; } /* Else, path is valid (len=0) on the same DPID or (len>0) diff DPIDs */ List<NodePortTuple> nptList = new ArrayList<NodePortTuple>(r.getPath()); NodePortTuple npt = new NodePortTuple(srcId, srcPort); nptList.add(0, npt); // add src port to the front npt = new NodePortTuple(dstId, dstPort); nptList.add(npt); // add dst port to the end PathId id = new PathId(srcId, dstId); r = new Path(id, nptList); return r; } /** * Get the fastest path from the pathcache. * @param srcId * @param dstId * @return */ public Path getPath(DatapathId srcId, DatapathId dstId) { PathId id = new PathId(srcId, dstId); /* Return empty route if srcId equals dstId */ if (srcId.equals(dstId)) { return new Path(id, ImmutableList.of()); } Path result = null; try { if (!pathcache.get(id).isEmpty()) { result = pathcache.get(id).get(0); } } catch (Exception e) { log.warn("Could not find route from {} to {}. If the path exists, wait for the topology to settle, and it will be detected", srcId, dstId); } if (log.isTraceEnabled()) { log.trace("getPath: {} -> {}", id, result); } return result == null ? new Path(id, ImmutableList.of()) : result; } // // ITopologyService interface method helpers. // public boolean isInternalLinkInCluster(DatapathId switchid, OFPort port) { return !isAttachmentPointPort(switchid, port); } public boolean isAttachmentPointPort(DatapathId switchid, OFPort port) { NodePortTuple npt = new NodePortTuple(switchid, port); if (linksNonBcastNonTunnel.containsKey(npt)) { return false; } return true; } public DatapathId getClusterId(DatapathId switchId) { Cluster c = clusterFromSwitch.get(switchId); if (c != null) { return c.getId(); } return switchId; } public DatapathId getArchipelagoId(DatapathId switchId) { Cluster c = clusterFromSwitch.get(switchId); if (c != null) { return archipelagoFromCluster.get(c).getId(); } return switchId; } public Set<DatapathId> getSwitchesInCluster(DatapathId switchId) { Cluster c = clusterFromSwitch.get(switchId); if (c != null) { return c.getNodes(); } /* The switch is not known to topology as there are no links connected to it. */ return ImmutableSet.of(switchId); } public boolean isInSameCluster(DatapathId switch1, DatapathId switch2) { Cluster c1 = clusterFromSwitch.get(switch1); Cluster c2 = clusterFromSwitch.get(switch2); if (c1 != null && c2 != null) { return c1.getId().equals(c2.getId()); } return (switch1.equals(switch2)); } public boolean isNotBlocked(DatapathId sw, OFPort portId) { return !isBlockedPort(new NodePortTuple(sw, portId)); } /* * Takes finiteBroadcastTree into account to prevent loops in the network */ public boolean isBroadcastAllowedOnSwitchPort(DatapathId sw, OFPort portId) { if (!isEdge(sw, portId)){ NodePortTuple npt = new NodePortTuple(sw, portId); if (portsBroadcastAll.contains(npt)) return true; else return false; } return true; } public boolean isConsistent(DatapathId oldSw, OFPort oldPort, DatapathId newSw, OFPort newPort) { if (isInternalLinkInCluster(newSw, newPort)) return true; return (oldSw.equals(newSw) && oldPort.equals(newPort)); } public boolean isInSameArchipelago(DatapathId s1, DatapathId s2) { for (Archipelago a : archipelagos) { if (a.getSwitches().contains(s1) && a.getSwitches().contains(s2)) { return true; } } return false; } public Set<DatapathId> getSwitches() { return switches; } public Set<OFPort> getPortsWithLinks(DatapathId sw) { return portsWithLinks.get(sw); } public Set<OFPort> getBroadcastPorts(DatapathId targetSw, DatapathId src, OFPort srcPort) { Set<OFPort> result = new HashSet<OFPort>(); DatapathId clusterId = getClusterId(targetSw); for (NodePortTuple npt : clusterPorts.get(clusterId)) { if (npt.getNodeId().equals(targetSw)) { result.add(npt.getPortId()); } } return result; } public Set<Link> getInternalInterClusterLinks() { return linksNonExternalInterCluster; } public Set<NodePortTuple> getAllBroadcastPorts() { return portsBroadcastAll; } public Set<DatapathId> getClusterIdsInArchipelago(DatapathId sw) { Archipelago a = getArchipelago(sw); if (a != null) { return a.getClusters().stream().map(c -> c.getId()).collect(Collectors.toSet()); } return ImmutableSet.of(); } private void addBroadcastPortToPerSwitchMap(NodePortTuple npt) { if (portsBroadcastPerSwitch.containsKey(npt.getNodeId())) { portsBroadcastPerSwitch.get(npt.getNodeId()).add(npt.getPortId()); } else { portsBroadcastPerSwitch.put(npt.getNodeId(), new HashSet<OFPort>(Arrays.asList(npt.getPortId()))); } } private void computeBroadcastPortsPerArchipelago() { Set<NodePortTuple> s; for (Archipelago a : archipelagos) { s = new HashSet<NodePortTuple>(); for (Entry<DatapathId, Link> e : a.getBroadcastTree().getLinks().entrySet()) { Link l = e.getValue(); if (l != null) { /* null indicates root node (leads nowhere "up") */ NodePortTuple src = new NodePortTuple(l.getSrc(), l.getSrcPort()); NodePortTuple dst = new NodePortTuple(l.getDst(), l.getDstPort()); /* Accumulate for per-archipelago NPT map */ s.add(src); s.add(dst); /* Add to global NPT set */ this.portsBroadcastAll.add(src); this.portsBroadcastAll.add(dst); /* Add to per-switch NPT map */ addBroadcastPortToPerSwitchMap(src); addBroadcastPortToPerSwitchMap(dst); } } /* Set accumulated per-this-archipelago NPTs */ portsBroadcastPerArchipelago.put(a.getId(), s); /* guaranteed non-null set per archipelago */ } /* Add ports that hosts connect to */ for (DatapathId sw : this.switches) { for (OFPort p : this.portsPerSwitch.get(sw)) { /* includes edge and link ports */ NodePortTuple npt = new NodePortTuple(sw, p); if (isEdge(sw, p)) { /* Add to per-archipelago NPT map */ DatapathId aId = getArchipelago(sw).getId(); if (portsBroadcastPerArchipelago.containsKey(aId)) { portsBroadcastPerArchipelago.get(aId).add(npt); } else { s = new HashSet<NodePortTuple>(); s.add(npt); portsBroadcastPerArchipelago.put(aId, s); } /* Add to global NPT set */ this.portsBroadcastAll.add(npt); /* Add to per-switch NPT map */ addBroadcastPortToPerSwitchMap(npt); } } } } public Set<NodePortTuple> getBroadcastPortsInArchipelago(DatapathId sw) { Archipelago a = getArchipelago(sw); if (a != null) { return portsBroadcastPerArchipelago.get(a.getId()); } return ImmutableSet.of(); } public Set<DatapathId> getArchipelagoIds() { return archipelagos.stream().map(Archipelago::getId).collect(Collectors.toSet()); } }