package de.tum.in.www1.jReto.routing.algorithm;
import org.jgrapht.alg.DijkstraShortestPath;
import org.jgrapht.graph.DefaultWeightedEdge;
import org.jgrapht.graph.DirectedWeightedPseudograph;
import de.tum.in.www1.jReto.routing.algorithm.LinkStateRoutingTable.Change.NowReachableInformation;
import de.tum.in.www1.jReto.routing.algorithm.LinkStateRoutingTable.Change.RouteChangedInformation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Map;
/**
* A LinkStateRoutingTable manages a graph of nodes in the network with type T.
*
* Link state routing works by gathering information about the full network topology; i.e. for each node in the network,
* all of its neighbors are known eventually. Based on this information, the next hop to a node can be computed using a shortest path algorithm.
*
* Advantages of link state routing (as opposed to distance vector routing) include that link state routing converges rather quickly and
* is not subject to the count-to-infinity problem; hence, no measures to combat this problem need to be taken. As the full network topology
* is known to every node, rather advanced routing techniques can be implemented.
*
* Disadvantages include that the link state information needs to be flooded through the network, causing higher overhead than link state protocols.
* The memory and computational requirements are also higher.
*
* The LinkStateRoutingTable class is not responsible for distributing link state information across the network;
* however, it processes received link state information and can provide link state information for the local peer.
*
* This routing table is designed to compute all next hops and path costs for all known nodes every time when new
* network topology information becomes available (e.g. neighbors added, updated or lost, and link state information received from
* any peer).
*
* These changes in the routing table are returned as a LinkStateRoutingTable.Change object. This object includes information about
* nodes that became reachable or unreachable, or information about route changes to nodes that were reachable before.
* */
public class LinkStateRoutingTable<T> {
/**
* A Change object contains changes that occurred in the routing table caused by some operation.
* */
public static class Change<T> {
/**
* Contains information about nodes that became reachable.
* */
public static class NowReachableInformation<T> {
/** The node that became reachable */
public final T node;
/** The node that is the next hop for reaching this node. */
public final T nextHop;
/** The total cost for reaching this node. */
public final double cost;
/** Constructs a new NowReachableInformation object. */
public NowReachableInformation(T node, T nextHop, double cost) {
this.node = node;
this.nextHop = nextHop;
this.cost = cost;
}
}
/**
* Contains informatiown about nodes that have changed routes.
* */
public static class RouteChangedInformation<T> {
/** The node that became reachable */
public final T node;
/** The node that is the next hop for reaching this node. */
public final T nextHop;
/** The previous total cost for reaching this node. */
public final double oldCost;
/** The total cost for reaching this node. */
public final double cost;
/** Constructs a new RouteChangedInformation object. */
public RouteChangedInformation(T node, T nextHop, double oldCost, double cost) {
this.node = node;
this.nextHop = nextHop;
this.oldCost = oldCost;
this.cost = cost;
}
}
/** Contains information about all nodes that are now reachable. */
public final List<NowReachableInformation<T>> nowReachable;
/** Contains all nodes that are now unreachable. */
public final List<T> nowUnreachable;
/** Contains information about all nodes that have changed routes. */
public final List<RouteChangedInformation<T>> routeChanged;
/** Constructs a new Change object. */
public Change(List<NowReachableInformation<T>> nowReachable, List<T> nowUnreachable, List<RouteChangedInformation<T>> routeChanged) {
this.nowReachable = nowReachable;
this.nowUnreachable = nowUnreachable;
this.routeChanged = routeChanged;
}
/** Returns whether this Change object is actually empty. */
public boolean isEmpty() {
return nowReachable.isEmpty() && nowUnreachable.isEmpty() && routeChanged.isEmpty();
}
}
/**
* Stores neighbor information (the neighbor itself and the cost of reaching the neighbor).
* */
public static class NeighborInformation<T> {
/** The node object representing the neighbor. */
public final T node;
/** The cost of reaching this neighbor. */
public final double cost;
/** Constructs a new neighbor information object. */
public NeighborInformation(T node, double cost) {
this.node = node;
this.cost = cost;
}
}
/** A directed, weighted multigraph used to represent the network of nodes and their link states. */
private final DirectedWeightedPseudograph<T, DefaultWeightedEdge> graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class);
/** The local node. In all neighbor related operations, the neighbor is considered a neighbor of this node. */
private final T localNode;
/** Constructs a new LinkStateRoutingTable. */
public LinkStateRoutingTable(T localNode) {
this.localNode = localNode;
this.graph.addVertex(localNode);
}
/**
* Computes the changes to the routing table when updating or adding a new neighbor.
* If the neighbor is not yet known to the routing table, it is added.
*
* @param neighbor The neighbor to update or add.
* @param cost The cost to reach that neighbor.
* @return A LinkStateRoutingTable.Change object representing the changes that occurred in the routing table.
* */
public Change<T> getRoutingTableChangeForNeighborUpdate(final T neighbor, final double cost) {
return this.trackGraphChanges(new Runnable() {
@Override
public void run() {
LinkStateRoutingTable.this.updateNeighbor(neighbor, cost);
}
});
}
/**
* Computes the changes to the routing table when removing a neighbor.
*
* @param neighbor The neighbor to remove
* @return A LinkStateRoutingTable.Change object representing the changes that occurred in the routing table.
* */
public Change<T> getRoutingTableChangeForNeighborRemoval(final T neighbor) {
return this.trackGraphChanges(new Runnable() {
@Override
public void run() {
LinkStateRoutingTable.this.removeNeighbor(neighbor);
}
});
}
/**
* Computes the changes to the routing table when link state information is received for a given node.
*
* @param node The node for which a list of neighbors (ie. link state information) was received.
* @param neighbors The node's neighbors.
* @return A LinkStateRoutingTable.Change object representing the changes that occurred in the routing table.
* */
public Change<T> getRoutingTableChangeForLinkStateInformationUpdate(final T node, final List<NeighborInformation<T>> neighbors) {
return this.trackGraphChanges(new Runnable() {
@Override
public void run() {
LinkStateRoutingTable.this.updateLinkStateInformation(node, neighbors);
}
});
}
/** Returns a list of neighbors for the local node (ie. link state information). */
public List<NeighborInformation<T>> getLinkStateInformation() {
List<NeighborInformation<T>> linkStateInformation = new ArrayList<>();
for (DefaultWeightedEdge edge : this.graph.outgoingEdgesOf(this.localNode)) {
linkStateInformation.add(new NeighborInformation<T>(this.graph.getEdgeTarget(edge), this.graph.getEdgeWeight(edge)));
}
return linkStateInformation;
}
public Tree<T> getNextHopTree(Set<T> destinations) {
for (T destination : destinations) {
if (!this.graph.containsVertex(destination)) {
System.err.println("You have attempted to connect to a destination that has not yet been added to the routing table. Most likely, it will be discovered soon.");
return null;
}
}
Set<T> participatingNodes = new HashSet<>(destinations);
participatingNodes.add(this.localNode);
return MinimumSteinerTreeApproximation.approximateSteinerTree(this.graph, localNode, participatingNodes);
}
/** Updates or adds a neighbor. */
private void updateNeighbor(T neighbor, double cost) {
if (this.graph.edgeSet().contains(neighbor)) this.graph.removeAllEdges(this.localNode, neighbor);
this.graph.addVertex(neighbor);
DefaultWeightedEdge edge = this.graph.addEdge(this.localNode, neighbor);
this.graph.setEdgeWeight(edge, cost);
}
/** Removes a neighbor. */
private void removeNeighbor(T neighbor) {
this.graph.removeAllEdges(this.localNode, neighbor);
}
/** Updates link state information for a given node. */
private void updateLinkStateInformation(T node, List<NeighborInformation<T>> neighbors) {
// Remove all edges
if (this.graph.vertexSet().contains(node)) {
Set<DefaultWeightedEdge> outgoingEdges = new HashSet<>(this.graph.outgoingEdgesOf(node));
this.graph.removeAllEdges(outgoingEdges);
} else {
this.graph.addVertex(node);
}
// Add new edges
for (NeighborInformation<T> neighbor : neighbors) {
this.graph.addVertex(neighbor.node);
DefaultWeightedEdge edge = this.graph.addEdge(node, neighbor.node);
this.graph.setEdgeWeight(edge, neighbor.cost);
}
}
/**
* Computes a Change object for arbitrary modifications of the graph.
*
* This method first computes the shortest paths to all reachable nodes in the graph, then runs the graph action, and then calculates all shortest paths again.
*
* From changes in which nodes are reachable, and changes in the paths, a LinkStateRoutingTable.Change object is created.
*
* @param graphAction A Runnable that is expected to perform some changes on the graph.
* @return A LinkStateRoutingTable.Change object representing the changes caused by the changes performed by the graphAction.
* */
private Change<T> trackGraphChanges(Runnable graphAction) {
// Compute shortest paths before and after execting the graph action
Map<T, DijkstraShortestPath<T, DefaultWeightedEdge>> previousShortestPaths = new HashMap<>();
for (T node : graph.vertexSet()) {
DijkstraShortestPath<T, DefaultWeightedEdge> shortestPath = new DijkstraShortestPath<T, DefaultWeightedEdge>(this.graph, this.localNode, node);
if (shortestPath.getPath() != null) previousShortestPaths.put(node, shortestPath);
}
graphAction.run();
Map<T, DijkstraShortestPath<T, DefaultWeightedEdge>> updatedShortestPaths = new HashMap<>();
for (T node : graph.vertexSet()) {
DijkstraShortestPath<T, DefaultWeightedEdge> shortestPath = new DijkstraShortestPath<T, DefaultWeightedEdge>(this.graph, this.localNode, node);
if (shortestPath.getPath() != null) updatedShortestPaths.put(node, shortestPath);
}
// Compute routing table changes from shortest paths.
// 1. Nodes that are now reachable but weren't before.
List<NowReachableInformation<T>> nowReachable = new ArrayList<>();
Set<T> nowReachableNodes = new HashSet<T>(updatedShortestPaths.keySet());
nowReachableNodes.removeAll(previousShortestPaths.keySet());
for (T nowReachableNode : nowReachableNodes) {
DijkstraShortestPath<T, DefaultWeightedEdge> shortestPath = updatedShortestPaths.get(nowReachableNode);
DefaultWeightedEdge firstEdge = shortestPath.getPathEdgeList().get(0);
nowReachable.add(new NowReachableInformation<T>(
nowReachableNode,
this.graph.getEdgeTarget(firstEdge),
shortestPath.getPathLength())
);
}
// 2. Nodes that were reachable before, but are now unreachable.
Set<T> nowUnreachableNodes = new HashSet<T>(previousShortestPaths.keySet());
nowUnreachableNodes.removeAll(updatedShortestPaths.keySet());
List<T> nowUnreachable = new ArrayList<T>(nowUnreachableNodes);
// 3. Nodes that were and are still reachable, but have a changed route.
Set<T> stillReachable = new HashSet<T>(previousShortestPaths.keySet());
stillReachable.retainAll(updatedShortestPaths.keySet());
List<RouteChangedInformation<T>> routeChanged = new ArrayList<>();
for (T node : stillReachable) {
if (node == this.localNode) continue;
DijkstraShortestPath<T, DefaultWeightedEdge> previousShortestPath = previousShortestPaths.get(node);
DijkstraShortestPath<T, DefaultWeightedEdge> updatedShortestPath = updatedShortestPaths.get(node);
boolean pathLengthChanged = previousShortestPath.getPathLength() != updatedShortestPath.getPathLength();
boolean nextHopChanged = this.graph.getEdgeTarget(previousShortestPath.getPathEdgeList().get(0)) != this.graph.getEdgeTarget(updatedShortestPath.getPathEdgeList().get(0));
if (pathLengthChanged || nextHopChanged) {
routeChanged.add(new RouteChangedInformation<T>(
node,
this.graph.getEdgeTarget(updatedShortestPath.getPathEdgeList().get(0)),
previousShortestPath.getPathLength(),
updatedShortestPath.getPathLength())
);
}
}
return new Change<>(nowReachable, nowUnreachable, routeChanged);
}
}