Thomas Vachuska

ONOS-745 Refactoring topology to compute only broadcast tree and not pre-compute paths.

ONOS-744 Refactoring graph search to allow requesting max number of paths.

Change-Id: I28467246b92df32ebb3155c45774ecc051fdd3dd
Showing 23 changed files with 186 additions and 213 deletions
......@@ -69,20 +69,18 @@ public class SummaryCommand extends AbstractShellCommand {
.put("links", topology.linkCount())
.put("hosts", get(HostService.class).getHostCount())
.put("SCC(s)", topology.clusterCount())
.put("paths", topology.pathCount())
.put("flows", get(FlowRuleService.class).getFlowRuleCount())
.put("intents", get(IntentService.class).getIntentCount()));
} else {
print("node=%s, version=%s",
get(ClusterService.class).getLocalNode().ip(),
get(CoreService.class).version().toString());
print("nodes=%d, devices=%d, links=%d, hosts=%d, SCC(s)=%s, paths=%d, flows=%d, intents=%d",
print("nodes=%d, devices=%d, links=%d, hosts=%d, SCC(s)=%s, flows=%d, intents=%d",
activeNodes(get(ClusterService.class).getNodes()),
get(DeviceService.class).getDeviceCount(),
get(LinkService.class).getLinkCount(),
get(HostService.class).getHostCount(),
topologyService.getClusters(topology).size(),
topology.pathCount(),
get(FlowRuleService.class).getFlowRuleCount(),
get(IntentService.class).getIntentCount());
}
......
......@@ -31,7 +31,7 @@ import org.onosproject.net.topology.TopologyService;
public class TopologyCommand extends AbstractShellCommand {
private static final String FMT =
"time=%s, devices=%d, links=%d, clusters=%d, paths=%d";
"time=%s, devices=%d, links=%d, clusters=%d";
@Option(name = "-r", aliases = "--recompute", description = "Trigger topology re-computation",
required = false, multiValued = false)
......@@ -59,11 +59,10 @@ public class TopologyCommand extends AbstractShellCommand {
.put("time", topology.time())
.put("deviceCount", topology.deviceCount())
.put("linkCount", topology.linkCount())
.put("clusterCount", topology.clusterCount())
.put("pathCount", topology.pathCount()));
.put("clusterCount", topology.clusterCount()));
} else {
print(FMT, topology.time(), topology.deviceCount(), topology.linkCount(),
topology.clusterCount(), topology.pathCount());
topology.clusterCount());
}
}
......
......@@ -15,8 +15,6 @@
*/
package org.onosproject.net.topology;
import org.onosproject.net.DeviceId;
import java.util.Objects;
import static com.google.common.base.MoreObjects.toStringHelper;
......@@ -29,7 +27,7 @@ public class DefaultTopologyCluster implements TopologyCluster {
private final ClusterId id;
private final int deviceCount;
private final int linkCount;
private final DeviceId root;
private final TopologyVertex root;
/**
* Creates a new topology cluster descriptor with the specified attributes.
......@@ -40,7 +38,7 @@ public class DefaultTopologyCluster implements TopologyCluster {
* @param root cluster root node
*/
public DefaultTopologyCluster(ClusterId id, int deviceCount, int linkCount,
DeviceId root) {
TopologyVertex root) {
this.id = id;
this.deviceCount = deviceCount;
this.linkCount = linkCount;
......@@ -63,7 +61,7 @@ public class DefaultTopologyCluster implements TopologyCluster {
}
@Override
public DeviceId root() {
public TopologyVertex root() {
return root;
}
......
......@@ -61,13 +61,4 @@ public interface Topology extends Provided {
*/
int linkCount();
/**
* Returns the number of infrastructure paths computed between devices
* in the topology. This means the number of all the shortest paths
* (hop-count) between all device pairs.
*
* @return number of paths
*/
int pathCount();
}
......
......@@ -15,8 +15,6 @@
*/
package org.onosproject.net.topology;
import org.onosproject.net.DeviceId;
/**
* Representation of an SCC (strongly-connected component) in a network topology.
*/
......@@ -44,10 +42,10 @@ public interface TopologyCluster {
int linkCount();
/**
* Returns the device identifier of the cluster root device.
* Returns the cluster root vertex.
*
* @return cluster root device identifier
* @return cluster root vertex
*/
DeviceId root();
TopologyVertex root();
}
......
......@@ -43,11 +43,12 @@ public class DefaultTopologyClusterTest {
assertEquals("incorrect id", clusterId(6), cluster.id());
assertEquals("incorrect id", 5, cluster.deviceCount());
assertEquals("incorrect id", 4, cluster.linkCount());
assertEquals("incorrect id", deviceId("of:111"), cluster.root());
assertEquals("incorrect id", deviceId("of:111"), cluster.root().deviceId());
}
private TopologyCluster cluster(int id, int dc, int lc, String root) {
return new DefaultTopologyCluster(clusterId(id), dc, lc, deviceId(root));
return new DefaultTopologyCluster(clusterId(id), dc, lc,
new DefaultTopologyVertex(deviceId(root)));
}
}
......
......@@ -31,7 +31,6 @@ import org.onosproject.net.topology.GraphDescription;
import org.onosproject.net.topology.LinkWeight;
import org.onosproject.net.topology.Topology;
import org.onosproject.net.topology.TopologyCluster;
import org.onosproject.net.topology.TopologyEdge;
import org.onosproject.net.topology.TopologyEvent;
import org.onosproject.net.topology.TopologyGraph;
import org.onosproject.net.topology.TopologyListener;
......@@ -127,7 +126,6 @@ public class TopologyManagerTest {
assertEquals("wrong cluster count", 2, topology.clusterCount());
assertEquals("wrong device count", 6, topology.deviceCount());
assertEquals("wrong link count", 10, topology.linkCount());
assertEquals("wrong path count", 18, topology.pathCount());
assertEquals("wrong cluster count", 2, service.getClusters(topology).size());
......@@ -148,12 +146,6 @@ public class TopologyManagerTest {
assertFalse("should not be infrastructure point",
service.isInfrastructure(topology, new ConnectPoint(did("a"), portNumber(3))));
// One of these cannot be a broadcast point... or we have a loop...
// assertFalse("should not be broadcast point",
// service.isBroadcastPoint(topology, new ConnectPoint(did("a"), portNumber(1))) &&
// service.isBroadcastPoint(topology, new ConnectPoint(did("b"), portNumber(1))) &&
// service.isBroadcastPoint(topology, new ConnectPoint(did("c"), portNumber(1))) &&
// service.isBroadcastPoint(topology, new ConnectPoint(did("d"), portNumber(1))));
assertTrue("should be broadcast point",
service.isBroadcastPoint(topology, new ConnectPoint(did("a"), portNumber(3))));
}
......@@ -182,12 +174,7 @@ public class TopologyManagerTest {
public void onDemandPath() {
submitTopologyGraph();
Topology topology = service.currentTopology();
LinkWeight weight = new LinkWeight() {
@Override
public double weight(TopologyEdge edge) {
return 3.3;
}
};
LinkWeight weight = edge -> 3.3;
Set<Path> paths = service.getPaths(topology, did("a"), did("c"), weight);
assertEquals("wrong path count", 2, paths.size());
......
......@@ -20,7 +20,6 @@ import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import org.onlab.graph.DijkstraGraphSearch;
import org.onlab.graph.GraphPathSearch;
import org.onlab.graph.GraphPathSearch.Result;
......@@ -51,11 +50,13 @@ import java.util.Set;
import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.collect.ImmutableSetMultimap.Builder;
import static org.onlab.graph.GraphPathSearch.ALL_PATHS;
import static org.onosproject.core.CoreService.CORE_PROVIDER_ID;
import static org.onosproject.net.Link.State.ACTIVE;
import static org.onosproject.net.Link.State.INACTIVE;
import static org.onosproject.net.Link.Type.INDIRECT;
// FIXME: Move to onos-core-common when ready
/**
* Default implementation of the topology descriptor. This carries the
* backing topology data.
......@@ -71,10 +72,8 @@ public class DefaultTopology extends AbstractModel implements Topology {
private final long computeCost;
private final TopologyGraph graph;
private final LinkWeight weight;
private final Supplier<SCCResult<TopologyVertex, TopologyEdge>> clusterResults;
private final Supplier<ImmutableMap<DeviceId, Result<TopologyVertex, TopologyEdge>>> results;
private final Supplier<ImmutableSetMultimap<PathKey, Path>> paths;
private final Supplier<ImmutableMap<ClusterId, TopologyCluster>> clusters;
private final Supplier<ImmutableSet<ConnectPoint>> infrastructurePoints;
private final Supplier<ImmutableSetMultimap<ClusterId, ConnectPoint>> broadcastSets;
......@@ -95,15 +94,12 @@ public class DefaultTopology extends AbstractModel implements Topology {
this.graph = new DefaultTopologyGraph(description.vertexes(),
description.edges());
this.results = Suppliers.memoize(() -> searchForShortestPaths());
this.paths = Suppliers.memoize(() -> buildPaths());
this.clusterResults = Suppliers.memoize(() -> searchForClusters());
this.clusters = Suppliers.memoize(() -> buildTopologyClusters());
this.clusterIndexes = Suppliers.memoize(() -> buildIndexes());
this.weight = new HopCountLinkWeight(graph.getVertexes().size());
this.broadcastSets = Suppliers.memoize(() -> buildBroadcastSets());
this.infrastructurePoints = Suppliers.memoize(() -> findInfrastructurePoints());
this.computeCost = Math.max(0, System.nanoTime() - time);
......@@ -134,11 +130,6 @@ public class DefaultTopology extends AbstractModel implements Topology {
return graph.getEdges().size();
}
@Override
public int pathCount() {
return paths.get().size();
}
private ImmutableMap<DeviceId, TopologyCluster> clustersByDevice() {
return clusterIndexes.get().clustersByDevice;
}
......@@ -262,7 +253,7 @@ public class DefaultTopology extends AbstractModel implements Topology {
* @return set of shortest paths
*/
Set<Path> getPaths(DeviceId src, DeviceId dst) {
return paths.get().get(new PathKey(src, dst));
return getPaths(src, dst, null);
}
/**
......@@ -284,7 +275,7 @@ public class DefaultTopology extends AbstractModel implements Topology {
}
GraphPathSearch.Result<TopologyVertex, TopologyEdge> result =
DIJKSTRA.search(graph, srcV, dstV, weight);
DIJKSTRA.search(graph, srcV, dstV, weight, ALL_PATHS);
ImmutableSet.Builder<Path> builder = ImmutableSet.builder();
for (org.onlab.graph.Path<TopologyVertex, TopologyEdge> path : result.paths()) {
builder.add(networkPath(path));
......@@ -293,31 +284,6 @@ public class DefaultTopology extends AbstractModel implements Topology {
}
// Searches the graph for all shortest paths and returns the search results.
private ImmutableMap<DeviceId, Result<TopologyVertex, TopologyEdge>> searchForShortestPaths() {
ImmutableMap.Builder<DeviceId, Result<TopologyVertex, TopologyEdge>> builder = ImmutableMap.builder();
// Search graph paths for each source to all destinations.
LinkWeight weight = new HopCountLinkWeight(graph.getVertexes().size());
for (TopologyVertex src : graph.getVertexes()) {
builder.put(src.deviceId(), DIJKSTRA.search(graph, src, null, weight));
}
return builder.build();
}
// Builds network paths from the graph path search results
private ImmutableSetMultimap<PathKey, Path> buildPaths() {
Builder<PathKey, Path> builder = ImmutableSetMultimap.builder();
for (DeviceId deviceId : results.get().keySet()) {
Result<TopologyVertex, TopologyEdge> result = results.get().get(deviceId);
for (org.onlab.graph.Path<TopologyVertex, TopologyEdge> path : result.paths()) {
builder.put(new PathKey(path.src().deviceId(), path.dst().deviceId()),
networkPath(path));
}
}
return builder.build();
}
// Converts graph path to a network path with the same cost.
private Path networkPath(org.onlab.graph.Path<TopologyVertex, TopologyEdge> path) {
List<Link> links = new ArrayList<>();
......@@ -337,23 +303,21 @@ public class DefaultTopology extends AbstractModel implements Topology {
// Builds the topology clusters and returns the id-cluster bindings.
private ImmutableMap<ClusterId, TopologyCluster> buildTopologyClusters() {
ImmutableMap.Builder<ClusterId, TopologyCluster> clusterBuilder = ImmutableMap.builder();
SCCResult<TopologyVertex, TopologyEdge> result =
TARJAN.search(graph, new NoIndirectLinksWeight());
SCCResult<TopologyVertex, TopologyEdge> results = clusterResults.get();
// Extract both vertexes and edges from the results; the lists form
// pairs along the same index.
List<Set<TopologyVertex>> clusterVertexes = result.clusterVertexes();
List<Set<TopologyEdge>> clusterEdges = result.clusterEdges();
List<Set<TopologyVertex>> clusterVertexes = results.clusterVertexes();
List<Set<TopologyEdge>> clusterEdges = results.clusterEdges();
// Scan over the lists and create a cluster from the results.
for (int i = 0, n = result.clusterCount(); i < n; i++) {
for (int i = 0, n = results.clusterCount(); i < n; i++) {
Set<TopologyVertex> vertexSet = clusterVertexes.get(i);
Set<TopologyEdge> edgeSet = clusterEdges.get(i);
ClusterId cid = ClusterId.clusterId(i);
DefaultTopologyCluster cluster =
new DefaultTopologyCluster(cid, vertexSet.size(), edgeSet.size(),
findRoot(vertexSet).deviceId());
findRoot(vertexSet));
clusterBuilder.put(cid, cluster);
}
return clusterBuilder.build();
......@@ -388,7 +352,8 @@ public class DefaultTopology extends AbstractModel implements Topology {
private void addClusterBroadcastSet(TopologyCluster cluster,
Builder<ClusterId, ConnectPoint> builder) {
// Use the graph root search results to build the broadcast set.
Result<TopologyVertex, TopologyEdge> result = results.get().get(cluster.root());
Result<TopologyVertex, TopologyEdge> result =
DIJKSTRA.search(graph, cluster.root(), null, weight, 1);
for (Map.Entry<TopologyVertex, Set<TopologyEdge>> entry : result.parents().entrySet()) {
TopologyVertex vertex = entry.getKey();
......@@ -498,7 +463,6 @@ public class DefaultTopology extends AbstractModel implements Topology {
.add("clusters", clusterCount())
.add("devices", deviceCount())
.add("links", linkCount())
.add("pathCount", pathCount())
.toString();
}
}
......
......@@ -15,12 +15,16 @@
*/
package org.onosproject.store.trivial.impl;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import org.onlab.graph.DijkstraGraphSearch;
import org.onlab.graph.GraphPathSearch;
import org.onlab.graph.GraphPathSearch.Result;
import org.onlab.graph.TarjanGraphSearch;
import org.onlab.graph.TarjanGraphSearch.SCCResult;
import org.onosproject.net.AbstractModel;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DefaultPath;
......@@ -46,13 +50,13 @@ import java.util.Set;
import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.collect.ImmutableSetMultimap.Builder;
import static org.onlab.graph.GraphPathSearch.Result;
import static org.onlab.graph.TarjanGraphSearch.SCCResult;
import static org.onlab.graph.GraphPathSearch.ALL_PATHS;
import static org.onosproject.core.CoreService.CORE_PROVIDER_ID;
import static org.onosproject.net.Link.State.ACTIVE;
import static org.onosproject.net.Link.State.INACTIVE;
import static org.onosproject.net.Link.Type.INDIRECT;
// FIXME: Move to onos-core-common when ready
/**
* Default implementation of the topology descriptor. This carries the
* backing topology data.
......@@ -68,18 +72,13 @@ public class DefaultTopology extends AbstractModel implements Topology {
private final long computeCost;
private final TopologyGraph graph;
private final SCCResult<TopologyVertex, TopologyEdge> clusterResults;
private final ImmutableMap<DeviceId, Result<TopologyVertex, TopologyEdge>> results;
private final ImmutableSetMultimap<PathKey, Path> paths;
private final ImmutableMap<ClusterId, TopologyCluster> clusters;
private final ImmutableSet<ConnectPoint> infrastructurePoints;
private final ImmutableSetMultimap<ClusterId, ConnectPoint> broadcastSets;
private ImmutableMap<DeviceId, TopologyCluster> clustersByDevice;
private ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster;
private ImmutableSetMultimap<TopologyCluster, Link> linksByCluster;
private final LinkWeight weight;
private final Supplier<SCCResult<TopologyVertex, TopologyEdge>> clusterResults;
private final Supplier<ImmutableMap<ClusterId, TopologyCluster>> clusters;
private final Supplier<ImmutableSet<ConnectPoint>> infrastructurePoints;
private final Supplier<ImmutableSetMultimap<ClusterId, ConnectPoint>> broadcastSets;
private final Supplier<ClusterIndexes> clusterIndexes;
/**
* Creates a topology descriptor attributed to the specified provider.
......@@ -95,16 +94,14 @@ public class DefaultTopology extends AbstractModel implements Topology {
this.graph = new DefaultTopologyGraph(description.vertexes(),
description.edges());
this.results = searchForShortestPaths();
this.paths = buildPaths();
this.clusterResults = searchForClusters();
this.clusters = buildTopologyClusters();
this.clusterResults = Suppliers.memoize(() -> searchForClusters());
this.clusters = Suppliers.memoize(() -> buildTopologyClusters());
buildIndexes();
this.clusterIndexes = Suppliers.memoize(() -> buildIndexes());
this.broadcastSets = buildBroadcastSets();
this.infrastructurePoints = findInfrastructurePoints();
this.weight = new HopCountLinkWeight(graph.getVertexes().size());
this.broadcastSets = Suppliers.memoize(() -> buildBroadcastSets());
this.infrastructurePoints = Suppliers.memoize(() -> findInfrastructurePoints());
this.computeCost = Math.max(0, System.nanoTime() - time);
}
......@@ -120,7 +117,7 @@ public class DefaultTopology extends AbstractModel implements Topology {
@Override
public int clusterCount() {
return clusters.size();
return clusters.get().size();
}
@Override
......@@ -133,9 +130,16 @@ public class DefaultTopology extends AbstractModel implements Topology {
return graph.getEdges().size();
}
@Override
public int pathCount() {
return paths.size();
private ImmutableMap<DeviceId, TopologyCluster> clustersByDevice() {
return clusterIndexes.get().clustersByDevice;
}
private ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster() {
return clusterIndexes.get().devicesByCluster;
}
private ImmutableSetMultimap<TopologyCluster, Link> linksByCluster() {
return clusterIndexes.get().linksByCluster;
}
/**
......@@ -153,7 +157,7 @@ public class DefaultTopology extends AbstractModel implements Topology {
* @return set of clusters
*/
Set<TopologyCluster> getClusters() {
return ImmutableSet.copyOf(clusters.values());
return ImmutableSet.copyOf(clusters.get().values());
}
/**
......@@ -163,7 +167,7 @@ public class DefaultTopology extends AbstractModel implements Topology {
* @return topology cluster
*/
TopologyCluster getCluster(ClusterId clusterId) {
return clusters.get(clusterId);
return clusters.get().get(clusterId);
}
/**
......@@ -173,7 +177,7 @@ public class DefaultTopology extends AbstractModel implements Topology {
* @return topology cluster
*/
TopologyCluster getCluster(DeviceId deviceId) {
return clustersByDevice.get(deviceId);
return clustersByDevice().get(deviceId);
}
/**
......@@ -183,7 +187,7 @@ public class DefaultTopology extends AbstractModel implements Topology {
* @return cluster devices
*/
Set<DeviceId> getClusterDevices(TopologyCluster cluster) {
return devicesByCluster.get(cluster);
return devicesByCluster().get(cluster);
}
/**
......@@ -193,7 +197,7 @@ public class DefaultTopology extends AbstractModel implements Topology {
* @return cluster links
*/
Set<Link> getClusterLinks(TopologyCluster cluster) {
return linksByCluster.get(cluster);
return linksByCluster().get(cluster);
}
/**
......@@ -203,7 +207,7 @@ public class DefaultTopology extends AbstractModel implements Topology {
* @return true if infrastructure
*/
boolean isInfrastructure(ConnectPoint connectPoint) {
return infrastructurePoints.contains(connectPoint);
return infrastructurePoints.get().contains(connectPoint);
}
/**
......@@ -219,14 +223,14 @@ public class DefaultTopology extends AbstractModel implements Topology {
}
// Find the cluster to which the device belongs.
TopologyCluster cluster = clustersByDevice.get(connectPoint.deviceId());
TopologyCluster cluster = clustersByDevice().get(connectPoint.deviceId());
if (cluster == null) {
throw new IllegalArgumentException("No cluster found for device " + connectPoint.deviceId());
}
// If the broadcast set is null or empty, or if the point explicitly
// belongs to it, return true;
Set<ConnectPoint> points = broadcastSets.get(cluster.id());
Set<ConnectPoint> points = broadcastSets.get().get(cluster.id());
return points == null || points.isEmpty() || points.contains(connectPoint);
}
......@@ -237,7 +241,7 @@ public class DefaultTopology extends AbstractModel implements Topology {
* @return size of the cluster broadcast set
*/
int broadcastSetSize(ClusterId clusterId) {
return broadcastSets.get(clusterId).size();
return broadcastSets.get().get(clusterId).size();
}
/**
......@@ -249,7 +253,7 @@ public class DefaultTopology extends AbstractModel implements Topology {
* @return set of shortest paths
*/
Set<Path> getPaths(DeviceId src, DeviceId dst) {
return paths.get(new PathKey(src, dst));
return getPaths(src, dst, null);
}
/**
......@@ -258,7 +262,7 @@ public class DefaultTopology extends AbstractModel implements Topology {
*
* @param src source device
* @param dst destination device
* @param weight edge weight function
* @param weight link weight function
* @return set of shortest paths
*/
Set<Path> getPaths(DeviceId src, DeviceId dst, LinkWeight weight) {
......@@ -271,7 +275,7 @@ public class DefaultTopology extends AbstractModel implements Topology {
}
GraphPathSearch.Result<TopologyVertex, TopologyEdge> result =
DIJKSTRA.search(graph, srcV, dstV, weight);
DIJKSTRA.search(graph, srcV, dstV, weight, ALL_PATHS);
ImmutableSet.Builder<Path> builder = ImmutableSet.builder();
for (org.onlab.graph.Path<TopologyVertex, TopologyEdge> path : result.paths()) {
builder.add(networkPath(path));
......@@ -280,31 +284,6 @@ public class DefaultTopology extends AbstractModel implements Topology {
}
// Searches the graph for all shortest paths and returns the search results.
private ImmutableMap<DeviceId, Result<TopologyVertex, TopologyEdge>> searchForShortestPaths() {
ImmutableMap.Builder<DeviceId, Result<TopologyVertex, TopologyEdge>> builder = ImmutableMap.builder();
// Search graph paths for each source to all destinations.
LinkWeight weight = new HopCountLinkWeight(graph.getVertexes().size());
for (TopologyVertex src : graph.getVertexes()) {
builder.put(src.deviceId(), DIJKSTRA.search(graph, src, null, weight));
}
return builder.build();
}
// Builds network paths from the graph path search results
private ImmutableSetMultimap<PathKey, Path> buildPaths() {
Builder<PathKey, Path> builder = ImmutableSetMultimap.builder();
for (DeviceId deviceId : results.keySet()) {
Result<TopologyVertex, TopologyEdge> result = results.get(deviceId);
for (org.onlab.graph.Path<TopologyVertex, TopologyEdge> path : result.paths()) {
builder.put(new PathKey(path.src().deviceId(), path.dst().deviceId()),
networkPath(path));
}
}
return builder.build();
}
// Converts graph path to a network path with the same cost.
private Path networkPath(org.onlab.graph.Path<TopologyVertex, TopologyEdge> path) {
List<Link> links = new ArrayList<>();
......@@ -324,23 +303,21 @@ public class DefaultTopology extends AbstractModel implements Topology {
// Builds the topology clusters and returns the id-cluster bindings.
private ImmutableMap<ClusterId, TopologyCluster> buildTopologyClusters() {
ImmutableMap.Builder<ClusterId, TopologyCluster> clusterBuilder = ImmutableMap.builder();
SCCResult<TopologyVertex, TopologyEdge> result =
TARJAN.search(graph, new NoIndirectLinksWeight());
SCCResult<TopologyVertex, TopologyEdge> results = clusterResults.get();
// Extract both vertexes and edges from the results; the lists form
// pairs along the same index.
List<Set<TopologyVertex>> clusterVertexes = result.clusterVertexes();
List<Set<TopologyEdge>> clusterEdges = result.clusterEdges();
List<Set<TopologyVertex>> clusterVertexes = results.clusterVertexes();
List<Set<TopologyEdge>> clusterEdges = results.clusterEdges();
// Scan over the lists and create a cluster from the results.
for (int i = 0, n = result.clusterCount(); i < n; i++) {
for (int i = 0, n = results.clusterCount(); i < n; i++) {
Set<TopologyVertex> vertexSet = clusterVertexes.get(i);
Set<TopologyEdge> edgeSet = clusterEdges.get(i);
ClusterId cid = ClusterId.clusterId(i);
DefaultTopologyCluster cluster =
new DefaultTopologyCluster(cid, vertexSet.size(), edgeSet.size(),
findRoot(vertexSet).deviceId());
findRoot(vertexSet));
clusterBuilder.put(cid, cluster);
}
return clusterBuilder.build();
......@@ -363,7 +340,7 @@ public class DefaultTopology extends AbstractModel implements Topology {
// Processes a map of broadcast sets for each cluster.
private ImmutableSetMultimap<ClusterId, ConnectPoint> buildBroadcastSets() {
Builder<ClusterId, ConnectPoint> builder = ImmutableSetMultimap.builder();
for (TopologyCluster cluster : clusters.values()) {
for (TopologyCluster cluster : clusters.get().values()) {
addClusterBroadcastSet(cluster, builder);
}
return builder.build();
......@@ -375,12 +352,13 @@ public class DefaultTopology extends AbstractModel implements Topology {
private void addClusterBroadcastSet(TopologyCluster cluster,
Builder<ClusterId, ConnectPoint> builder) {
// Use the graph root search results to build the broadcast set.
Result<TopologyVertex, TopologyEdge> result = results.get(cluster.root());
Result<TopologyVertex, TopologyEdge> result =
DIJKSTRA.search(graph, cluster.root(), null, weight, 1);
for (Map.Entry<TopologyVertex, Set<TopologyEdge>> entry : result.parents().entrySet()) {
TopologyVertex vertex = entry.getKey();
// Ignore any parents that lead outside the cluster.
if (clustersByDevice.get(vertex.deviceId()) != cluster) {
if (clustersByDevice().get(vertex.deviceId()) != cluster) {
continue;
}
......@@ -409,32 +387,32 @@ public class DefaultTopology extends AbstractModel implements Topology {
}
// Builds cluster-devices, cluster-links and device-cluster indexes.
private void buildIndexes() {
private ClusterIndexes buildIndexes() {
// Prepare the index builders
ImmutableMap.Builder<DeviceId, TopologyCluster> clusterBuilder = ImmutableMap.builder();
ImmutableSetMultimap.Builder<TopologyCluster, DeviceId> devicesBuilder = ImmutableSetMultimap.builder();
ImmutableSetMultimap.Builder<TopologyCluster, Link> linksBuilder = ImmutableSetMultimap.builder();
// Now scan through all the clusters
for (TopologyCluster cluster : clusters.values()) {
for (TopologyCluster cluster : clusters.get().values()) {
int i = cluster.id().index();
// Scan through all the cluster vertexes.
for (TopologyVertex vertex : clusterResults.clusterVertexes().get(i)) {
for (TopologyVertex vertex : clusterResults.get().clusterVertexes().get(i)) {
devicesBuilder.put(cluster, vertex.deviceId());
clusterBuilder.put(vertex.deviceId(), cluster);
}
// Scan through all the cluster edges.
for (TopologyEdge edge : clusterResults.clusterEdges().get(i)) {
for (TopologyEdge edge : clusterResults.get().clusterEdges().get(i)) {
linksBuilder.put(cluster, edge.link());
}
}
// Finalize all indexes.
clustersByDevice = clusterBuilder.build();
devicesByCluster = devicesBuilder.build();
linksByCluster = linksBuilder.build();
return new ClusterIndexes(clusterBuilder.build(),
devicesBuilder.build(),
linksBuilder.build());
}
// Link weight for measuring link cost as hop count with indirect links
......@@ -463,6 +441,20 @@ public class DefaultTopology extends AbstractModel implements Topology {
}
}
static final class ClusterIndexes {
final ImmutableMap<DeviceId, TopologyCluster> clustersByDevice;
final ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster;
final ImmutableSetMultimap<TopologyCluster, Link> linksByCluster;
public ClusterIndexes(ImmutableMap<DeviceId, TopologyCluster> clustersByDevice,
ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster,
ImmutableSetMultimap<TopologyCluster, Link> linksByCluster) {
this.clustersByDevice = clustersByDevice;
this.devicesByCluster = devicesByCluster;
this.linksByCluster = linksByCluster;
}
}
@Override
public String toString() {
return toStringHelper(this)
......@@ -471,7 +463,6 @@ public class DefaultTopology extends AbstractModel implements Topology {
.add("clusters", clusterCount())
.add("devices", deviceCount())
.add("links", linkCount())
.add("pathCount", pathCount())
.toString();
}
}
......
......@@ -17,6 +17,7 @@ package org.onosproject.store.trivial.impl;
import org.junit.Before;
import org.junit.Test;
import org.onlab.packet.ChassisId;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DefaultDevice;
import org.onosproject.net.DefaultLink;
......@@ -31,8 +32,6 @@ import org.onosproject.net.topology.DefaultGraphDescription;
import org.onosproject.net.topology.GraphDescription;
import org.onosproject.net.topology.LinkWeight;
import org.onosproject.net.topology.TopologyCluster;
import org.onosproject.net.topology.TopologyEdge;
import org.onlab.packet.ChassisId;
import java.util.Set;
......@@ -57,13 +56,9 @@ public class DefaultTopologyTest {
public static final PortNumber P1 = portNumber(1);
public static final PortNumber P2 = portNumber(2);
public static final LinkWeight WEIGHT = new LinkWeight() {
@Override
public double weight(TopologyEdge edge) {
return edge.src().deviceId().equals(D4) ||
edge.dst().deviceId().equals(D4) ? 2.0 : 1.0;
}
};
public static final LinkWeight WEIGHT = edge ->
edge.src().deviceId().equals(D4) || edge.dst().deviceId().equals(D4)
? 2.0 : 1.0;
private DefaultTopology dt;
......
......@@ -65,17 +65,31 @@ public abstract class AbstractGraphPathSearch<V extends Vertex, E extends Edge<V
protected final Set<Path<V, E>> paths = new HashSet<>();
protected final Map<V, Double> costs = new HashMap<>();
protected final Map<V, Set<E>> parents = new HashMap<>();
protected final int maxPaths;
/**
* Creates the result of path search.
* Creates the result of a single-path search.
*
* @param src path source
* @param dst optional path destination
*/
public DefaultResult(V src, V dst) {
this(src, dst, 1);
}
/**
* Creates the result of path search.
*
* @param src path source
* @param dst optional path destination
* @param maxPaths optional limit of number of paths;
* {@link GraphPathSearch#ALL_PATHS} if no limit
*/
public DefaultResult(V src, V dst, int maxPaths) {
checkNotNull(src, "Source cannot be null");
this.src = src;
this.dst = dst;
this.maxPaths = maxPaths;
}
@Override
......@@ -126,7 +140,8 @@ public abstract class AbstractGraphPathSearch<V extends Vertex, E extends Edge<V
/**
* Updates the cost of the vertex using its existing cost plus the
* cost to traverse the specified edge.
* cost to traverse the specified edge. If the search is in single
* path mode, only one path will be accrued.
*
* @param vertex vertex to update
* @param edge edge through which vertex is reached
......@@ -147,9 +162,11 @@ public abstract class AbstractGraphPathSearch<V extends Vertex, E extends Edge<V
if (replace) {
edges.clear();
}
if (maxPaths == ALL_PATHS || edges.size() < maxPaths) {
edges.add(edge);
}
}
}
/**
* Removes the set of parent edges for the specified vertex.
......@@ -203,7 +220,7 @@ public abstract class AbstractGraphPathSearch<V extends Vertex, E extends Edge<V
for (V v : destinations) {
// Ignore the source, if it is among the destinations.
if (!v.equals(src)) {
buildAllPaths(this, src, v);
buildAllPaths(this, src, v, maxPaths);
}
}
}
......@@ -218,15 +235,18 @@ public abstract class AbstractGraphPathSearch<V extends Vertex, E extends Edge<V
* @param result graph search result
* @param src source vertex
* @param dst destination vertex
* @param maxPaths limit on the number of paths built;
* {@link GraphPathSearch#ALL_PATHS} if no limit
*/
private void buildAllPaths(DefaultResult result, V src, V dst) {
private void buildAllPaths(DefaultResult result, V src, V dst, int maxPaths) {
DefaultMutablePath<V, E> basePath = new DefaultMutablePath<>();
basePath.setCost(result.cost(dst));
Set<DefaultMutablePath<V, E>> pendingPaths = new HashSet<>();
pendingPaths.add(basePath);
while (!pendingPaths.isEmpty()) {
while (!pendingPaths.isEmpty() &&
(maxPaths == ALL_PATHS || result.paths.size() < maxPaths)) {
Set<DefaultMutablePath<V, E>> frontier = new HashSet<>();
for (DefaultMutablePath<V, E> path : pendingPaths) {
......
......@@ -24,11 +24,11 @@ public class BellmanFordGraphSearch<V extends Vertex, E extends Edge<V>>
@Override
public Result<V, E> search(Graph<V, E> graph, V src, V dst,
EdgeWeight<V, E> weight) {
EdgeWeight<V, E> weight, int maxPaths) {
checkArguments(graph, src, dst);
// Prepare the graph search result.
DefaultResult result = new DefaultResult(src, dst);
DefaultResult result = new DefaultResult(src, dst, maxPaths);
// The source vertex has cost 0, of course.
result.updateVertex(src, null, 0.0, true);
......
......@@ -26,11 +26,11 @@ public class BreadthFirstSearch<V extends Vertex, E extends Edge<V>>
@Override
public Result<V, E> search(Graph<V, E> graph, V src, V dst,
EdgeWeight<V, E> weight) {
EdgeWeight<V, E> weight, int maxPaths) {
checkArguments(graph, src, dst);
// Prepare the graph result.
DefaultResult result = new DefaultResult(src, dst);
DefaultResult result = new DefaultResult(src, dst, maxPaths);
// Setup the starting frontier with the source as the sole vertex.
Set<V> frontier = new HashSet<>();
......
......@@ -36,11 +36,11 @@ public class DepthFirstSearch<V extends Vertex, E extends Edge<V>>
@Override
public SpanningTreeResult search(Graph<V, E> graph, V src, V dst,
EdgeWeight<V, E> weight) {
EdgeWeight<V, E> weight, int maxPaths) {
checkArguments(graph, src, dst);
// Prepare the search result.
SpanningTreeResult result = new SpanningTreeResult(src, dst);
SpanningTreeResult result = new SpanningTreeResult(src, dst, maxPaths);
// The source vertex has cost 0, of course.
result.updateVertex(src, null, 0.0, true);
......@@ -143,9 +143,10 @@ public class DepthFirstSearch<V extends Vertex, E extends Edge<V>>
*
* @param src search source
* @param dst optional search destination
* @param maxPaths limit on the number of paths
*/
public SpanningTreeResult(V src, V dst) {
super(src, dst);
public SpanningTreeResult(V src, V dst, int maxPaths) {
super(src, dst, maxPaths);
}
/**
......
......@@ -28,12 +28,12 @@ public class DijkstraGraphSearch<V extends Vertex, E extends Edge<V>>
@Override
public Result<V, E> search(Graph<V, E> graph, V src, V dst,
EdgeWeight<V, E> weight) {
EdgeWeight<V, E> weight, int maxPaths) {
checkArguments(graph, src, dst);
// Use the default result to remember cumulative costs and parent
// edges to each each respective vertex.
DefaultResult result = new DefaultResult(src, dst);
DefaultResult result = new DefaultResult(src, dst, maxPaths);
// Cost to reach the source vertex is 0 of course.
result.updateVertex(src, null, 0.0, false);
......
......@@ -26,6 +26,8 @@ import java.util.Set;
*/
public interface GraphPathSearch<V extends Vertex, E extends Edge<V>> {
public static int ALL_PATHS = -1;
/**
* Abstraction of a path search result.
*/
......@@ -68,7 +70,7 @@ public interface GraphPathSearch<V extends Vertex, E extends Edge<V>> {
}
/**
* Searches the specified graph.
* Searches the specified graph for paths between vertices.
*
* @param graph graph to be searched
* @param src optional source vertex
......@@ -76,8 +78,10 @@ public interface GraphPathSearch<V extends Vertex, E extends Edge<V>> {
* destinations will be searched
* @param weight optional edge-weight; if null cost of each edge will be
* assumed to be 1.0
* @param maxPaths limit on number of paths; {@link GraphPathSearch#ALL_PATHS} if no limit
* @return search results
*/
Result<V, E> search(Graph<V, E> graph, V src, V dst, EdgeWeight<V, E> weight);
Result<V, E> search(Graph<V, E> graph, V src, V dst,
EdgeWeight<V, E> weight, int maxPaths);
}
......
......@@ -23,6 +23,8 @@ import java.util.List;
//import java.util.PriorityQueue;
import java.util.Set;
import static org.onlab.graph.GraphPathSearch.ALL_PATHS;
/**
* K-shortest-path graph search algorithm capable of finding not just one,
* but K shortest paths with ascending order between the source and destinations.
......@@ -137,7 +139,7 @@ public class KshortestPathSearch<V extends Vertex, E extends Edge<V>> {
private List<E> searchShortestPath(Graph<V, E> graph, V src, V dst) {
// Determine the shortest path from the source to the destination by using the Dijkstra algorithm.
DijkstraGraphSearch dijkstraAlg = new DijkstraGraphSearch();
Set<Path> paths = dijkstraAlg.search(graph, src, dst, weight).paths();
Set<Path> paths = dijkstraAlg.search(graph, src, dst, weight, ALL_PATHS).paths();
Iterator<Path> itr = paths.iterator();
if (!itr.hasNext()) {
return null;
......
......@@ -36,19 +36,19 @@ public abstract class AbstractGraphPathSearchTest extends GraphTest {
public void noSuchSourceArgument() {
graphSearch().search(new AdjacencyListsGraph<>(of(B, C),
of(new TestEdge(B, C, 1))),
A, H, weight);
A, H, weight, 1);
}
@Test(expected = NullPointerException.class)
public void nullGraphArgument() {
graphSearch().search(null, A, H, weight);
graphSearch().search(null, A, H, weight, 1);
}
@Test(expected = NullPointerException.class)
public void nullSourceArgument() {
graphSearch().search(new AdjacencyListsGraph<>(of(B, C),
of(new TestEdge(B, C, 1))),
null, H, weight);
null, H, weight, 1);
}
@Test
......
......@@ -21,6 +21,7 @@ import java.util.HashSet;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.onlab.graph.GraphPathSearch.ALL_PATHS;
/**
* Test of the Bellman-Ford algorithm.
......@@ -56,7 +57,7 @@ public class BellmanFordGraphSearchTest extends BreadthFirstSearchTest {
graph = new AdjacencyListsGraph<>(vertexes, edges);
GraphPathSearch<TestVertex, TestEdge> search = graphSearch();
Set<Path<TestVertex, TestEdge>> paths = search.search(graph, A, H, weight).paths();
Set<Path<TestVertex, TestEdge>> paths = search.search(graph, A, H, weight, ALL_PATHS).paths();
assertEquals("incorrect paths count", 1, paths.size());
Path p = paths.iterator().next();
......@@ -65,10 +66,10 @@ public class BellmanFordGraphSearchTest extends BreadthFirstSearchTest {
assertEquals("incorrect path length", 5, p.edges().size());
assertEquals("incorrect path cost", 5.0, p.cost(), 0.1);
paths = search.search(graph, A, G, weight).paths();
paths = search.search(graph, A, G, weight, ALL_PATHS).paths();
assertEquals("incorrect paths count", 0, paths.size());
paths = search.search(graph, A, null, weight).paths();
paths = search.search(graph, A, null, weight, ALL_PATHS).paths();
printPaths(paths);
assertEquals("incorrect paths count", 6, paths.size());
}
......
......@@ -20,6 +20,7 @@ import org.junit.Test;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.onlab.graph.GraphPathSearch.ALL_PATHS;
/**
* Test of the BFS and similar path search algorithms.
......@@ -47,7 +48,8 @@ public class BreadthFirstSearchTest extends AbstractGraphPathSearchTest {
graph = new AdjacencyListsGraph<>(vertexes(), edges());
GraphPathSearch<TestVertex, TestEdge> search = graphSearch();
Set<Path<TestVertex, TestEdge>> paths = search.search(graph, A, H, weight).paths();
Set<Path<TestVertex, TestEdge>> paths =
search.search(graph, A, H, weight, ALL_PATHS).paths();
assertEquals("incorrect paths count", 1, paths.size());
Path p = paths.iterator().next();
......@@ -56,7 +58,7 @@ public class BreadthFirstSearchTest extends AbstractGraphPathSearchTest {
assertEquals("incorrect path length", pathLength, p.edges().size());
assertEquals("incorrect path cost", pathCost, p.cost(), 0.1);
paths = search.search(graph, A, null, weight).paths();
paths = search.search(graph, A, null, weight, ALL_PATHS).paths();
printPaths(paths);
assertEquals("incorrect paths count", pathCount, paths.size());
}
......@@ -68,7 +70,7 @@ public class BreadthFirstSearchTest extends AbstractGraphPathSearchTest {
EdgeWeight<TestVertex, TestEdge> weight,
int pathCount, double pathCost) {
GraphPathSearch.Result<TestVertex, TestEdge> result =
search.search(graph, src, dst, weight);
search.search(graph, src, dst, weight, ALL_PATHS);
Set<Path<TestVertex, TestEdge>> paths = result.paths();
printPaths(paths);
assertEquals("incorrect paths count", pathCount, paths.size());
......@@ -78,4 +80,21 @@ public class BreadthFirstSearchTest extends AbstractGraphPathSearchTest {
}
}
// Executes the single-path search and validates its results.
protected void executeSinglePathSearch(GraphPathSearch<TestVertex, TestEdge> search,
Graph<TestVertex, TestEdge> graph,
TestVertex src, TestVertex dst,
EdgeWeight<TestVertex, TestEdge> weight,
int pathCount, double pathCost) {
GraphPathSearch.Result<TestVertex, TestEdge> result =
search.search(graph, src, dst, weight, 1);
Set<Path<TestVertex, TestEdge>> paths = result.paths();
printPaths(paths);
assertEquals("incorrect paths count", Math.min(pathCount, 1), paths.size());
if (pathCount > 0) {
Path<TestVertex, TestEdge> path = paths.iterator().next();
assertEquals("incorrect path cost", pathCost, path.cost(), 0.1);
}
}
}
......
......@@ -22,6 +22,7 @@ import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.onlab.graph.DepthFirstSearch.EdgeType;
import static org.onlab.graph.GraphPathSearch.ALL_PATHS;
/**
* Test of the DFS algorithm.
......@@ -52,7 +53,7 @@ public class DepthFirstSearchTest extends AbstractGraphPathSearchTest {
DepthFirstSearch<TestVertex, TestEdge> search = graphSearch();
DepthFirstSearch<TestVertex, TestEdge>.SpanningTreeResult result =
search.search(graph, A, H, weight);
search.search(graph, A, H, weight, 1);
Set<Path<TestVertex, TestEdge>> paths = result.paths();
assertEquals("incorrect path count", 1, paths.size());
......@@ -77,7 +78,7 @@ public class DepthFirstSearchTest extends AbstractGraphPathSearchTest {
// Perform narrow path search to a specific destination.
DepthFirstSearch<TestVertex, TestEdge>.SpanningTreeResult result =
search.search(graph, A, null, weight);
search.search(graph, A, null, weight, ALL_PATHS);
assertEquals("incorrect paths count", 7, result.paths().size());
int[] types = new int[]{0, 0, 0, 0};
......
......@@ -55,16 +55,16 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest {
new TestEdge(C, D, 1),
new TestEdge(D, C, 1)));
GraphPathSearch<TestVertex, TestEdge> gs = graphSearch();
Set<Path<TestVertex, TestEdge>> paths = gs.search(graph, A, B, weight).paths();
Set<Path<TestVertex, TestEdge>> paths = gs.search(graph, A, B, weight, 1).paths();
printPaths(paths);
assertEquals("incorrect paths count", 1, paths.size());
assertEquals("incorrect path cost", 1.0, paths.iterator().next().cost(), 0.1);
paths = gs.search(graph, A, D, weight).paths();
paths = gs.search(graph, A, D, weight, 1).paths();
printPaths(paths);
assertEquals("incorrect paths count", 0, paths.size());
paths = gs.search(graph, A, null, weight).paths();
paths = gs.search(graph, A, null, weight, 1).paths();
printPaths(paths);
assertEquals("incorrect paths count", 1, paths.size());
assertEquals("incorrect path cost", 1.0, paths.iterator().next().cost(), 0.1);
......@@ -78,6 +78,7 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest {
new TestEdge(B, D, 1),
new TestEdge(C, D, 1)));
executeSearch(graphSearch(), graph, A, D, weight, 2, 2.0);
executeSinglePathSearch(graphSearch(), graph, A, D, weight, 1, 2.0);
}
@Test
......@@ -93,6 +94,7 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest {
new TestEdge(F, G, 1),
new TestEdge(A, G, 4)));
executeSearch(graphSearch(), graph, A, G, weight, 5, 4.0);
executeSinglePathSearch(graphSearch(), graph, A, G, weight, 1, 4.0);
}
@Test
......@@ -106,6 +108,7 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest {
new TestEdge(F, G, 1), new TestEdge(F, H, 1),
new TestEdge(A, E, 3), new TestEdge(B, D, 1)));
executeSearch(graphSearch(), graph, A, E, weight, 3, 3.0);
executeSinglePathSearch(graphSearch(), graph, A, E, weight, 1, 3.0);
}
@Test
......@@ -123,6 +126,7 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest {
new TestEdge(G, A, -5),
new TestEdge(A, G, 4)));
executeSearch(graphSearch(), graph, A, G, weight, 3, 4.0);
executeSinglePathSearch(graphSearch(), graph, A, G, weight, 1, 4.0);
}
@Test
......
......@@ -454,7 +454,6 @@ public abstract class TopologyViewMessages {
new Prop("Links", format(topology.linkCount())),
new Prop("Hosts", format(hostService.getHostCount())),
new Prop("Topology SCCs", format(topology.clusterCount())),
new Prop("Paths", format(topology.pathCount())),
new Separator(),
new Prop("Intents", format(intentService.getIntentCount())),
new Prop("Flows", format(flowService.getFlowRuleCount())),
......