/* * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ /** * @file DijkstraImplementation.java * * * @brief Implementation of a routing engine using * dijkstra. Implementation of dijkstra come from Jung2 library * */ package org.opendaylight.controller.routing.dijkstra_implementation.internal; import org.opendaylight.controller.clustering.services.IClusterContainerServices; import org.opendaylight.controller.sal.core.Bandwidth; import org.opendaylight.controller.sal.core.ConstructionException; import org.opendaylight.controller.sal.core.Edge; import org.opendaylight.controller.sal.core.Node; import org.opendaylight.controller.sal.core.NodeConnector; import org.opendaylight.controller.sal.core.Path; import org.opendaylight.controller.sal.core.Property; import org.opendaylight.controller.sal.core.UpdateType; import org.opendaylight.controller.sal.routing.IListenRoutingUpdates; import org.opendaylight.controller.sal.routing.IRouting; import org.opendaylight.controller.sal.topology.TopoEdgeUpdate; import org.opendaylight.controller.switchmanager.ISwitchManager; import org.opendaylight.controller.topologymanager.ITopologyManager; import org.opendaylight.controller.topologymanager.ITopologyManagerClusterWideAware; import edu.uci.ics.jung.algorithms.shortestpath.DijkstraShortestPath; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.SparseMultigraph; import edu.uci.ics.jung.graph.util.EdgeType; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ArrayList; import java.util.Set; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.commons.collections15.Transformer; @SuppressWarnings("rawtypes") public class DijkstraImplementation implements IRouting, ITopologyManagerClusterWideAware { private static Logger log = LoggerFactory.getLogger(DijkstraImplementation.class); private ConcurrentMap> topologyBWAware; private ConcurrentMap> sptBWAware; DijkstraShortestPath mtp; // Max Throughput Path private Set routingAware; private ISwitchManager switchManager; private ITopologyManager topologyManager; private static final long DEFAULT_LINK_SPEED = Bandwidth.BW1Gbps; private IClusterContainerServices clusterContainerService; public void setListenRoutingUpdates(final IListenRoutingUpdates i) { if (this.routingAware == null) { this.routingAware = new HashSet(); } if (this.routingAware != null) { log.debug("Adding routingAware listener: {}", i); this.routingAware.add(i); } } public void unsetListenRoutingUpdates(final IListenRoutingUpdates i) { if (this.routingAware == null) { return; } log.debug("Removing routingAware listener"); this.routingAware.remove(i); if (this.routingAware.isEmpty()) { // We don't have any listener lets dereference this.routingAware = null; } } @Override public synchronized void initMaxThroughput( final Map EdgeWeightMap) { if (mtp != null) { log.error("Max Throughput Dijkstra is already enabled!"); return; } Transformer mtTransformer = null; if (EdgeWeightMap == null) { mtTransformer = new Transformer() { @Override public Double transform(Edge e) { if (switchManager == null) { log.error("switchManager is null"); return (double) -1; } NodeConnector srcNC = e.getTailNodeConnector(); NodeConnector dstNC = e.getHeadNodeConnector(); if ((srcNC == null) || (dstNC == null)) { log.error("srcNC:{} or dstNC:{} is null", srcNC, dstNC); return (double) -1; } Bandwidth bwSrc = (Bandwidth) switchManager.getNodeConnectorProp(srcNC, Bandwidth.BandwidthPropName); Bandwidth bwDst = (Bandwidth) switchManager.getNodeConnectorProp(dstNC, Bandwidth.BandwidthPropName); long srcLinkSpeed = 0, dstLinkSpeed = 0; if ((bwSrc == null) || ((srcLinkSpeed = bwSrc.getValue()) == 0)) { log.debug("srcNC: {} - Setting srcLinkSpeed to Default!", srcNC); srcLinkSpeed = DEFAULT_LINK_SPEED; } if ((bwDst == null) || ((dstLinkSpeed = bwDst.getValue()) == 0)) { log.debug("dstNC: {} - Setting dstLinkSpeed to Default!", dstNC); dstLinkSpeed = DEFAULT_LINK_SPEED; } // TODO: revisit the logic below with the real use case in // mind // For now we assume the throughput to be the speed of the // link itself // this kind of logic require information that should be // polled by statistic manager and are not yet available, // also this service at the moment is not used, so to be // revisited later on long avlSrcThruPut = srcLinkSpeed; long avlDstThruPut = dstLinkSpeed; // Use lower of the 2 available throughput as the available // throughput long avlThruPut = avlSrcThruPut < avlDstThruPut ? avlSrcThruPut : avlDstThruPut; if (avlThruPut <= 0) { log.debug("Edge {}: Available Throughput {} <= 0!", e, avlThruPut); return (double) -1; } return (double) (Bandwidth.BW1Pbps / avlThruPut); } }; } else { mtTransformer = new Transformer() { @Override public Number transform(Edge e) { return EdgeWeightMap.get(e); } }; } Short baseBW = Short.valueOf((short) 0); // Initialize mtp also using the default topo Graph g = this.topologyBWAware.get(baseBW); if (g == null) { log.error("Default Topology Graph is null"); return; } mtp = new DijkstraShortestPath(g, mtTransformer); } @Override public Path getRoute(final Node src, final Node dst) { if ((src == null) || (dst == null)) { return null; } return getRoute(src, dst, (short) 0); } @Override public synchronized Path getMaxThroughputRoute(Node src, Node dst) { if (mtp == null) { log.error("Max Throughput Path Calculation Uninitialized!"); return null; } List path; try { path = mtp.getMaxThroughputPath(src, dst); } catch (IllegalArgumentException ie) { log.debug("A vertex is yet not known between {} {}", src, dst); return null; } Path res; try { res = new Path(path); } catch (ConstructionException e) { log.debug("A vertex is yet not known between {} {}", src, dst); return null; } return res; } @Override public synchronized Path getRoute(final Node src, final Node dst, final Short Bw) { DijkstraShortestPath spt = this.sptBWAware.get(Bw); if (spt == null) { return null; } List path; try { path = spt.getPath(src, dst); } catch (IllegalArgumentException ie) { log.debug("A vertex is yet not known between {} {}", src, dst); return null; } Path res; try { res = new Path(path); } catch (ConstructionException e) { log.debug("A vertex is yet not known between {} {}", src, dst); return null; } return res; } @Override public synchronized void clear() { DijkstraShortestPath spt; for (Short bw : this.sptBWAware.keySet()) { spt = this.sptBWAware.get(bw); if (spt != null) { spt.reset(); } } clearMaxThroughput(); } @Override public synchronized void clearMaxThroughput() { if (mtp != null) { mtp.reset(); // reset max throughput path } } @SuppressWarnings({ "unchecked" }) private synchronized boolean updateTopo(Edge edge, Short bw, UpdateType type) { Graph topo = this.topologyBWAware.get(bw); DijkstraShortestPath spt = this.sptBWAware.get(bw); boolean edgePresentInGraph = false; Short baseBW = Short.valueOf((short) 0); if (topo == null) { // Create topology for this BW Graph g = new SparseMultigraph(); this.topologyBWAware.put(bw, g); topo = this.topologyBWAware.get(bw); this.sptBWAware.put(bw, new DijkstraShortestPath(g)); spt = this.sptBWAware.get(bw); } if (topo != null) { NodeConnector src = edge.getTailNodeConnector(); NodeConnector dst = edge.getHeadNodeConnector(); if (spt == null) { spt = new DijkstraShortestPath(topo); this.sptBWAware.put(bw, spt); } switch (type) { case ADDED: // Make sure the vertex are there before adding the edge topo.addVertex(src.getNode()); topo.addVertex(dst.getNode()); // Add the link between edgePresentInGraph = topo.containsEdge(edge); if (edgePresentInGraph == false) { try { topo.addEdge(new Edge(src, dst), src.getNode(), dst.getNode(), EdgeType.DIRECTED); } catch (final ConstructionException e) { log.error("", e); return edgePresentInGraph; } } case CHANGED: // Mainly raised only on properties update, so not really useful // in this case break; case REMOVED: // Remove the edge try { topo.removeEdge(new Edge(src, dst)); } catch (final ConstructionException e) { log.error("", e); return edgePresentInGraph; } // If the src and dst vertex don't have incoming or // outgoing links we can get ride of them if (topo.containsVertex(src.getNode()) && (topo.inDegree(src.getNode()) == 0) && (topo.outDegree(src.getNode()) == 0)) { log.debug("Removing vertex {}", src); topo.removeVertex(src.getNode()); } if (topo.containsVertex(dst.getNode()) && (topo.inDegree(dst.getNode()) == 0) && (topo.outDegree(dst.getNode()) == 0)) { log.debug("Removing vertex {}", dst); topo.removeVertex(dst.getNode()); } break; } spt.reset(); if (bw.equals(baseBW)) { clearMaxThroughput(); } } else { log.error("Cannot find topology for BW {} this is unexpected!", bw); } return edgePresentInGraph; } private boolean edgeUpdate(Edge e, UpdateType type, Set props, boolean local) { String srcType = null; String dstType = null; log.trace("Got an edgeUpdate: {} props: {} update type: {} local: {}", new Object[] { e, props, type, local }); if ((e == null) || (type == null)) { log.error("Edge or Update type are null!"); return false; } else { srcType = e.getTailNodeConnector().getType(); dstType = e.getHeadNodeConnector().getType(); if (srcType.equals(NodeConnector.NodeConnectorIDType.PRODUCTION)) { log.debug("Skip updates for {}", e); return false; } if (dstType.equals(NodeConnector.NodeConnectorIDType.PRODUCTION)) { log.debug("Skip updates for {}", e); return false; } } Bandwidth bw = new Bandwidth(0); boolean newEdge = false; if (props != null) { props.remove(bw); } Short baseBW = Short.valueOf((short) 0); // Update base topo newEdge = !updateTopo(e, baseBW, type); if (newEdge == true) { if (bw.getValue() != baseBW) { // Update BW topo updateTopo(e, (short) bw.getValue(), type); } } return newEdge; } @Override public void edgeUpdate(List topoedgeupdateList) { log.trace("Start of a Bulk EdgeUpdate with " + topoedgeupdateList.size() + " elements"); boolean callListeners = false; for (int i = 0; i < topoedgeupdateList.size(); i++) { Edge e = topoedgeupdateList.get(i).getEdge(); Set p = topoedgeupdateList.get(i) .getProperty(); UpdateType type = topoedgeupdateList.get(i) .getUpdateType(); boolean isLocal = topoedgeupdateList.get(i) .isLocal(); if ((edgeUpdate(e, type, p, isLocal)) && (!callListeners)) { callListeners = true; } } // The routing listeners should only be called on the coordinator, to // avoid multiple controller cluster nodes to actually do the // recalculation when only one need to react boolean amICoordinator = true; if (this.clusterContainerService != null) { amICoordinator = this.clusterContainerService.amICoordinator(); } if ((callListeners) && (this.routingAware != null) && amICoordinator) { log.trace("Calling the routing listeners"); for (IListenRoutingUpdates ra : this.routingAware) { try { ra.recalculateDone(); } catch (Exception ex) { log.error("Exception on routingAware listener call", ex); } } } log.trace("End of a Bulk EdgeUpdate"); } /** * Function called by the dependency manager when all the required * dependencies are satisfied * */ @SuppressWarnings({ "unchecked", "rawtypes" }) public void init() { log.debug("Routing init() is called"); this.topologyBWAware = new ConcurrentHashMap>(); this.sptBWAware = new ConcurrentHashMap>(); // Now create the default topology, which doesn't consider the // BW, also create the corresponding Dijkstra calculation Graph g = new SparseMultigraph(); Short sZero = Short.valueOf((short) 0); this.topologyBWAware.put(sZero, g); this.sptBWAware.put(sZero, new DijkstraShortestPath(g)); // Topologies for other BW will be added on a needed base } /** * Function called by the dependency manager when at least one dependency * become unsatisfied or when the component is shutting down because for * example bundle is being stopped. * */ void destroy() { log.debug("Routing destroy() is called"); } /** * Function called by dependency manager after "init ()" is called and after * the services provided by the class are registered in the service registry * */ void start() { log.debug("Routing start() is called"); // build the routing database from the topology if it exists. Map> edges = topologyManager.getEdges(); if (edges.isEmpty()) { return; } List topoedgeupdateList = new ArrayList(); log.debug("Creating routing database from the topology"); for (Iterator>> i = edges.entrySet() .iterator(); i.hasNext();) { Map.Entry> entry = i.next(); Edge e = entry.getKey(); Set props = entry.getValue(); TopoEdgeUpdate topoedgeupdate = new TopoEdgeUpdate(e, props, UpdateType.ADDED); topoedgeupdateList.add(topoedgeupdate); } edgeUpdate(topoedgeupdateList); } /** * Function called by the dependency manager before the services exported by * the component are unregistered, this will be followed by a "destroy ()" * calls * */ public void stop() { log.debug("Routing stop() is called"); } public void setSwitchManager(ISwitchManager switchManager) { this.switchManager = switchManager; } public void unsetSwitchManager(ISwitchManager switchManager) { if (this.switchManager == switchManager) { this.switchManager = null; } } public void setTopologyManager(ITopologyManager tm) { this.topologyManager = tm; } public void unsetTopologyManager(ITopologyManager tm) { if (this.topologyManager == tm) { this.topologyManager = null; } } void setClusterContainerService(IClusterContainerServices s) { log.debug("Cluster Service set"); this.clusterContainerService = s; } void unsetClusterContainerService(IClusterContainerServices s) { if (this.clusterContainerService == s) { log.debug("Cluster Service removed!"); this.clusterContainerService = null; } } @Override public void edgeOverUtilized(Edge edge) { // TODO Auto-generated method stub } @Override public void edgeUtilBackToNormal(Edge edge) { // TODO Auto-generated method stub } }