/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.communication.routing.internal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import de.rcenvironment.core.communication.common.InstanceNodeSessionId; import de.rcenvironment.core.communication.common.NetworkGraph; import de.rcenvironment.core.communication.model.InitialNodeInformation; import de.rcenvironment.core.communication.model.impl.InitialNodeInformationImpl; import de.rcenvironment.core.communication.model.internal.NetworkGraphImpl; import de.rcenvironment.core.communication.model.internal.NetworkGraphLinkImpl; import de.rcenvironment.core.utils.common.StringUtils; import edu.uci.ics.jung.algorithms.shortestpath.DijkstraShortestPath; import edu.uci.ics.jung.graph.DirectedSparseMultigraph; /** * Represents a model of the entire network (i.e. topology map or link state database). A node ( {@link TopologyNode}) in the graph * corresponds to a RCE instance and an edge represents a connection/link ({@link TopologyLink}) between two RCE instances. The graph is a * directed sparse multigraph {@link DirectedSparseMultigraph}. * * TODO review: reduce synchronization * * @see <a href="http://jung.sourceforge.net/doc/api/index.html">JUNG2</a> * * @author Phillip Kroll * @author Robert Mischke */ public final class TopologyMap { /** * TODO Remove logger. */ private static final Log LOGGER = LogFactory.getLog(TopologyMap.class); /** * @see <a href="http://jung.sourceforge.net/doc/api/edu/uci/ics/jung/graph/DirectedSparseMultigraph.html">DirectedSparseMultigraph</a> */ private final DirectedSparseMultigraph<TopologyNode, TopologyLink> networkModel = new DirectedSparseMultigraph<TopologyNode, TopologyLink>(); private final InitialNodeInformation localNodeInformation; private final InstanceNodeSessionId localNodeId; /** * The constructor. * * @param ownNodeInformation The node that uses the {@link TopologyMap} instance to represent its view on the network. */ public TopologyMap(InitialNodeInformation ownNodeInformation) { this.localNodeInformation = ownNodeInformation; this.localNodeId = ownNodeInformation.getInstanceNodeSessionId(); TopologyNode localNode = addNode(localNodeId); // initialize sequence number localNode.invalidateSequenceNumber(); } /** * Convenience constructor for unit tests. */ protected TopologyMap(InstanceNodeSessionId nodeId) { this(new InitialNodeInformationImpl(nodeId)); } /** * Convert a list of network nodes to a list of their platform identifiers. * * @param networkNodes List of network nodes. * @return The list of platform identifiers. */ public static Collection<InstanceNodeSessionId> toNodeIdentifiers(Collection<TopologyNode> networkNodes) { Collection<InstanceNodeSessionId> result = new ArrayList<InstanceNodeSessionId>(); for (TopologyNode networkNode : networkNodes) { result.add(networkNode.getNodeIdentifier()); } return result; } /** * Integrates information from an {@link LinkStateAdvertisement} into the graph. * * @param lsa The link state advertisement. * @return Whether the link state advertisement was "accepted" and therefore integrated in the graph or considered as being to old. */ public synchronized boolean update(LinkStateAdvertisement lsa) { // TODO optimize algorithm --krol_ph InstanceNodeSessionId lsaOwner = lsa.getOwner(); if (lsaOwner.equals(localNodeId)) { TopologyNode ownNode = getNode(localNodeId); // TODO eliminate these from being sent; at least as part of the initial batch response // -- misc_ro // LOGGER.debug("Ignored an update LSA for the local node: LSA seqNo=" + lsa.getSequenceNumber() + ", local seqNo=" // + ownNode.getSequenceNumber()); return false; } boolean lsaRootPresent = containsNode(lsaOwner); TopologyNode lsaRoot = getNode(lsaOwner); if ((lsaRootPresent && lsaRoot.getSequenceNumber() >= lsa.getSequenceNumber()) || (!lsaRootPresent && LinkStateAdvertisement.REASON_SHUTDOWN.equals(lsa.getReason()))) { // lsa is too old, discard it, do not forward it // we know already that the node is not there anymore return false; } // TODO Maybe removing and re-inserting is not the best way to update the graph. // remove node and edges if (lsaRootPresent) { for (TopologyNode successor : networkModel.getSuccessors(lsaRoot)) { for (TopologyLink link : networkModel.findEdgeSet(lsaRoot, successor)) { removeLink(link); } } } // the LSA tells something about a node that has new or updated information if (LinkStateAdvertisement.REASON_STARTUP.equals(lsa.getReason()) || LinkStateAdvertisement.REASON_UPDATE.equals(lsa.getReason())) { // add node and set properties TopologyNode node = addNode(lsaOwner); node.setRouting(lsa.isRouting()); node.setLastSequenceNumber(lsa.getSequenceNumber()); node.setLastGraphHashCode(lsa.getGraphHashCode()); node.setDisplayName(lsa.getDisplayName()); node.setIsWorkflowHost(false); // not used anymore // add links (and remote nodes) for (TopologyLink link : lsa.getLinks()) { if (!containsNode(link.getDestination())) { addNode(link.getDestination()); } addLink(link); } } // if the purpose of the LSA was to tell that an instance is shutting down if (LinkStateAdvertisement.REASON_SHUTDOWN.equals(lsa.getReason())) { // LOGGER.debug(StringUtils.format("%s has been informed that %s will shut down soon.", // owner, // lsa.getOwner())); // TODO check: are both incoming and outgoing links removed from topology map? LOGGER.debug("Received a shutdown notice for node " + lsaRoot.getNodeIdentifier() + "; removing it from local topology"); removeNode(lsaRoot); } return true; } /** * Computes the shortest path from the source node to the destination node. The Dijkstra shortest path algorithm is used to determine * the shortest path. * * @see <a href="http://jung.sourceforge.net/doc/api/edu/uci/ics/jung/algorithms/shortestpath/DijkstraShortestPath.html"> * DijkstraShortestPath</a> * @param source The source platform * @param destination The destination platform * @return The shortest path between source and destination. */ public synchronized NetworkRoute getShortestPath(InstanceNodeSessionId source, InstanceNodeSessionId destination) { // TODO optimization: provide method that only returns the next step (if rest is irrelevant) // TODO optimization: add caching! long elapsed = 0; List<TopologyLink> path = new ArrayList<TopologyLink>(); List<InstanceNodeSessionId> nodes = new ArrayList<InstanceNodeSessionId>(); TopologyNode sourceNode = getNode(source); if (sourceNode == null) { throw new IllegalStateException("Consistency error: The local node is not part of the known topology"); } TopologyNode destinationNode = getNode(destination); if (destinationNode != null) { DijkstraShortestPath<TopologyNode, TopologyLink> alg = new DijkstraShortestPath<TopologyNode, TopologyLink>(networkModel); long start = System.nanoTime(); path = alg.getPath(sourceNode, destinationNode); elapsed = System.nanoTime() - start; for (TopologyLink link : path) { nodes.add(networkModel.getEndpoints(link).getSecond().getNodeIdentifier()); } return new NetworkRoute(source, destination, path, nodes, elapsed); // LOGGER.debug(StringUtils.format("Route computation: %s ms", elapsed * 10e-9)); } else { LOGGER.warn("Could not determine route to node " + destination + " as it is not part of the known topology"); return null; } } /** * Get the ids of all reachable nodes. * * @param restrictToWorkflowHostsAndSelf No description available. * @return Set of platform identifiers. */ public synchronized Set<InstanceNodeSessionId> getIdsOfReachableNodes(boolean restrictToWorkflowHostsAndSelf) { Set<InstanceNodeSessionId> result = new HashSet<InstanceNodeSessionId>(); // get the map of reachable nodes by (ab)using the Dijkstra algorithm; // TODO is there a more efficient approach available in JUNG? DijkstraShortestPath<TopologyNode, TopologyLink> alg = new DijkstraShortestPath<TopologyNode, TopologyLink>(networkModel); Map<TopologyNode, Number> distanceMap = alg.getDistanceMap(getNode(localNodeId)); for (TopologyNode node : distanceMap.keySet()) { if (restrictToWorkflowHostsAndSelf) { boolean isLocalNode = localNodeId.equals(node.getNodeIdentifier()); if (!(isLocalNode || node.getIsWorkflowHost())) { continue; } } // TODO remove typecast once NetworkIdentifier vs. NodeIdentifier is done result.add((InstanceNodeSessionId) node.getNodeIdentifier()); } return result; } /** * FIXME unspecified semantics; rename or replace for clarity -- misc_ro. * * @return The link state advertisement. */ public synchronized LinkStateAdvertisement generateNewLocalLSA() { return generateLsa(getLocalNodeId(), true); } /** * * @return The LSA cache. */ public synchronized LinkStateAdvertisementBatch generateLsaBatchOfAllNodes() { LinkStateAdvertisementBatch lsaCache = new LinkStateAdvertisementBatch(); for (TopologyNode node : networkModel.getVertices()) { lsaCache.put(node.getNodeIdentifier(), generateLsa(node.getNodeIdentifier(), false)); } return lsaCache; } private synchronized LinkStateAdvertisement generateLsa(InstanceNodeSessionId root, boolean incSequenceNumber) { TopologyNode rootNode = getNode(root); // TODO This is not the right place to update the graph hash code // TODO review: shouldn't this happen *after* incrementing the sequence number? -- misc_ro rootNode.setLastGraphHashCode(hashCode()); long sequenceNumber; if (incSequenceNumber) { sequenceNumber = rootNode.invalidateSequenceNumber(); } else { sequenceNumber = rootNode.getSequenceNumber(); } return LinkStateAdvertisement.createUpdateLsa( root, rootNode.getDisplayName(), rootNode.getIsWorkflowHost(), sequenceNumber, hashCode(), rootNode.isRouting(), networkModel.getOutEdges(rootNode)); } /** * @return The link state advertisement. */ public synchronized LinkStateAdvertisement generateShutdownLSA() { TopologyNode myself = getNode(getLocalNodeId()); myself.setLastGraphHashCode(hashCode()); return LinkStateAdvertisement.createShutDownLsa( getLocalNodeId(), localNodeInformation.getDisplayName(), false, myself.invalidateSequenceNumber()); } /** * Produces a startup LSA. * * @return The startup LSAs. */ public synchronized LinkStateAdvertisement generateStartupLSA() { TopologyNode myself = getNode(getLocalNodeId()); myself.setLastGraphHashCode(hashCode()); return LinkStateAdvertisement.createStartUpLsa( getLocalNodeId(), localNodeInformation.getDisplayName(), false, myself.invalidateSequenceNumber(), myself.isRouting(), networkModel.getOutEdges(myself)); } /** * Tests whether every node in the network has exactly the same model of the network. * * @return Whether the network models are fully converged. */ public synchronized boolean hasSameTopologyHashesForAllNodes() { for (TopologyNode node : getNodes()) { if (node.getLastGraphHashCode() != hashCode()) { return false; } } return true; } /** * Returns all {@link TopologyLink}s between two nodes that are present in the network graph. * * @param source The source node. * @param destination The destination node. * @return The list of all edges/links in the network/graph. */ public synchronized Collection<TopologyLink> getAllLinksBetween(TopologyNode source, TopologyNode destination) { if (source != null && destination != null) { return networkModel.findEdgeSet(source, destination); } return new ArrayList<TopologyLink>(); } /** * {@inheritDoc} * * @see java.lang.Object#equals(java.lang.Object) */ @Override // TODO synchronized equals() might be risky public synchronized boolean equals(Object object) { if (object instanceof TopologyMap) { return hashCode() == object.hashCode(); } else { return false; } } /** * {@inheritDoc} * * @see java.lang.Object#hashCode() */ @Override public synchronized int hashCode() { // TODO Review hash code generation. Maybe this can be done simpler. String graphSerialization = "NetworkGraph:"; List<TopologyNode> networkNodes = new ArrayList<TopologyNode>(networkModel.getVertices()); Collections.sort(networkNodes); for (TopologyNode networkNode : networkNodes) { graphSerialization += networkNode.hashCode(); } List<TopologyLink> networkLinks = new ArrayList<TopologyLink>(networkModel.getEdges()); Collections.sort(networkLinks); for (TopologyLink networkLink : networkLinks) { graphSerialization += networkLink.hashCode(); } return graphSerialization.hashCode(); } /** * @param nodeId The node identifier. * @return The network node. */ public synchronized TopologyNode getNode(InstanceNodeSessionId nodeId) { // TODO use map instead of linear search here! -- misc_ro for (TopologyNode networkNode : networkModel.getVertices()) { if (networkNode.getNodeIdentifier().equals(nodeId)) { return networkNode; } } return null; } /** * Convenience method to get the sequence number for a node by its id. If no matching node exists, a {@link IllegalArgumentException} is * thrown. * * @param id the id of the node * @return the current sequence number of that node */ public long getSequenceNumberOfNode(InstanceNodeSessionId id) { TopologyNode node = getNode(id); if (node == null) { throw new IllegalArgumentException("No such node: " + id.getInstanceNodeSessionIdString()); } return node.getSequenceNumber(); } /** * @param nodeId The node identifier. * @return Whether graph contains the node. */ public synchronized boolean containsNode(InstanceNodeSessionId nodeId) { return containsNode(new TopologyNode(nodeId)); } /** * @param networkNode The network node. * @return Whether graph contains the node. */ public synchronized boolean containsNode(TopologyNode networkNode) { return networkModel.containsVertex(networkNode); } /** * @param source The source platform. * @param destination The destination platform. * @return Whether the graph contains <code>source</code> and <code>destination</code> platform AND at least one edge between both * platforms. */ public synchronized boolean containsLinkBetween(InstanceNodeSessionId source, InstanceNodeSessionId destination) { TopologyNode sourceNode = getNode(source); TopologyNode destinationNode = getNode(destination); if (sourceNode != null && destinationNode != null) { return networkModel.getSuccessors(sourceNode).contains(destinationNode); } return false; } /** * Tests whether a link is present in the network. * * @param source The source of the link. * @param destination The destination of the link. * @param connectionId The id of the connection. * @return Whether the graph contains the link. */ public synchronized boolean containsLink(InstanceNodeSessionId source, InstanceNodeSessionId destination, String connectionId) { return networkModel.containsEdge(new TopologyLink(source, destination, connectionId)); } /** * Tests whether a link is present in the network. * * @see {@link TopologyLink#equals(Object)} * @param networkLink The network link. * @return Whether the graph contains the link. */ public synchronized boolean containsLink(TopologyLink networkLink) { return networkModel.containsEdge(networkLink); } /** * @param source The source node. * @param destination The destination node * @param connectionId The id of the connection * @param string * @return Whether adding the link was successful. */ public synchronized TopologyLink addLink(InstanceNodeSessionId source, InstanceNodeSessionId destination, String connectionId) { TopologyLink newLink = new TopologyLink(source, destination, connectionId); if (!addLink(newLink)) { throw new IllegalStateException("Failed to add new topology link"); } return newLink; } /** * Add a link into the network topology. TODO clean up. * * @param networkLink The network link. * @return true */ public synchronized boolean addLink(TopologyLink networkLink) { if (networkLink.getConnectionId() == null) { throw new NullPointerException("Connection id must not be null"); } TopologyNode sourceNode = getNode(networkLink.getSource()); TopologyNode destinationNode = getNode(networkLink.getDestination()); // new: if (sourceNode == null) { // add node implicitly sourceNode = addNode(networkLink.getSource()); } if (destinationNode == null) { // add node implicitly destinationNode = addNode(networkLink.getDestination()); } if (!networkModel.addEdge(networkLink, sourceNode, destinationNode)) { LOGGER.warn(StringUtils.format("Link edge %s was not added to graph -- duplicate?", networkLink)); return false; } return true; // old: // if (sourceNode != null && destinationNode != null && !sourceNode.equals(destinationNode)) // { // if (containsLink(networkLink)) { // removeLink(networkLink); // } // networkModel.addEdge(networkLink, sourceNode, destinationNode); // return true; // } // return false; } /** * @param source The source node. * @param destination The destination node * @param connectionId The connection id * @return Whether adding the link was successful. */ public synchronized boolean removeLink(InstanceNodeSessionId source, InstanceNodeSessionId destination, String connectionId) { return removeLink(new TopologyLink(source, destination, connectionId)); } /** * @param link The network link. * @return Whether adding the link was successful. */ public synchronized boolean removeLink(TopologyLink link) { if (containsLink(link)) { return networkModel.removeEdge(link); } else { LOGGER.warn("Edge removal requested for non-existant link: " + link); return false; } } /** * Remove a node. * * @param node The node */ public synchronized void removeNode(TopologyNode node) { networkModel.removeVertex(node); } /** * * Remove a node. * * @param node The node */ public synchronized void removeNode(InstanceNodeSessionId node) { removeNode(new TopologyNode(node)); } /** * @param nodeId The platform identifier. * @return Whether the platform existed in the graph before. */ public synchronized TopologyNode addNode(InstanceNodeSessionId nodeId) { TopologyNode existingNetworkNode = getNode(nodeId); if (existingNetworkNode == null) { TopologyNode node = new TopologyNode(nodeId); networkModel.addVertex(node); return node; } return existingNetworkNode; } /** * @return The number of platforms in the graph. */ public synchronized int getNodeCount() { return networkModel.getVertexCount(); } /** * @return The number of links in the graph. */ public synchronized int getLinkCount() { return networkModel.getEdgeCount(); } /** * @return A list of all links. */ public synchronized Collection<TopologyLink> getAllLinks() { return networkModel.getEdges(); } /** * Searches for a network link that is outgoing from the {@link TopologyMap#localNodeId} and contains the network contact. * * @param channelId The network connection id * @return The found network link or null. */ public synchronized TopologyLink getLinkForConnection(String channelId) { // TODO review: replace by map? -- misc_ro for (TopologyLink link : networkModel.getOutEdges(getNode(getLocalNodeId()))) { if (link.getConnectionId().equals(channelId)) { return link; } } return null; } /** * TODO Enter comment! * * @param connectionId The connection id. * @return Is there a link for the connection. */ public synchronized boolean hasLinkForConnection(String connectionId) { return getLinkForConnection(connectionId) != null; } /** * @return A list of all nodes. */ public synchronized Collection<TopologyNode> getNodes() { return networkModel.getVertices(); } /** * @param networkNode The network node. * @return All successor nodes (platforms) in the current graph. */ public synchronized Collection<TopologyNode> getSuccessors(TopologyNode networkNode) { return networkModel.getSuccessors(networkNode); } /** * @return All successor nodes of the graph owner. */ public synchronized Collection<TopologyNode> getSuccessors() { return getSuccessors(getLocalNodeId()); } /** * @param nodeId The platform identifier. * @return All successor nodes (platforms) in the current graph. */ public synchronized Collection<TopologyNode> getSuccessors(InstanceNodeSessionId nodeId) { return getSuccessors(getNode(nodeId)); } /** * @param nodeId the id of the queried node * @return all links starting at the given node */ public synchronized Collection<TopologyLink> getAllOutgoingLinks(InstanceNodeSessionId nodeId) { return getOutgoingLinks(getNode(nodeId)); } /** * @param node the queried {@link TopologyNode} * @return all links starting at the given node */ public Collection<TopologyLink> getOutgoingLinks(TopologyNode node) { return networkModel.getOutEdges(node); } /** * @param networkNode The network node. * @return All predecessor nodes (platforms) in the current graph. */ public synchronized Collection<TopologyNode> getPredecessors(TopologyNode networkNode) { return networkModel.getPredecessors(networkNode); } public InitialNodeInformation getLocalNodeInformation() { return localNodeInformation; } /** * @return Returns the owner. */ public synchronized InstanceNodeSessionId getLocalNodeId() { return localNodeId; } /** * @return a new {@link NetworkGraph} containing all (unfiltered) nodes and links of this {@link TopologyMap} */ public synchronized NetworkGraph toRawNetworkGraph() { NetworkGraphImpl rawGraph = new NetworkGraphImpl(localNodeId); for (TopologyNode node : getNodes()) { InstanceNodeSessionId nodeId = node.getNodeIdentifier(); rawGraph.addNode(nodeId); } for (TopologyLink link : getAllLinks()) { NetworkGraphLinkImpl graphLink = new NetworkGraphLinkImpl(link.getConnectionId(), link.getSource(), link.getDestination()); rawGraph.addLink(graphLink); } // rawGraph.getNodeById(localNodeId).setIsLocalNode(true); return rawGraph; } }