blob: dd2f3a9c29b8e612a11041ce4fa631b2a587a712 [file] [log] [blame]
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Thomas Vachuska930a8ee2015-08-04 18:49:36 -070016package org.onosproject.common;
alshabib339a3d92014-09-26 17:54:32 -070017
Thomas Vachuskab2c47a72015-08-05 14:22:54 -070018import com.google.common.base.Function;
Thomas Vachuska930a8ee2015-08-04 18:49:36 -070019import com.google.common.base.Supplier;
20import com.google.common.base.Suppliers;
21import com.google.common.collect.ImmutableMap;
22import com.google.common.collect.ImmutableSet;
23import com.google.common.collect.ImmutableSetMultimap;
24import com.google.common.collect.ImmutableSetMultimap.Builder;
Andrey Komarov2398d962016-09-26 15:11:23 +030025import org.onlab.graph.DefaultEdgeWeigher;
alshabib339a3d92014-09-26 17:54:32 -070026import org.onlab.graph.DijkstraGraphSearch;
Thomas Vachuska48e64e42015-09-22 15:32:55 -070027import org.onlab.graph.DisjointPathPair;
alshabib339a3d92014-09-26 17:54:32 -070028import org.onlab.graph.GraphPathSearch;
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -080029import org.onlab.graph.GraphPathSearch.Result;
Yuta HIGUCHIac8b2292017-03-30 19:21:57 -070030import org.onlab.graph.KShortestPathsSearch;
31import org.onlab.graph.LazyKShortestPathsSearch;
Andrey Komarov2398d962016-09-26 15:11:23 +030032import org.onlab.graph.ScalarWeight;
Jonathan Hartd9df7bd2015-11-10 17:10:25 -080033import org.onlab.graph.SrlgGraphSearch;
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -070034import org.onlab.graph.SuurballeGraphSearch;
alshabib339a3d92014-09-26 17:54:32 -070035import org.onlab.graph.TarjanGraphSearch;
Jonathan Hartd9df7bd2015-11-10 17:10:25 -080036import org.onlab.graph.TarjanGraphSearch.SccResult;
Andrey Komarov2398d962016-09-26 15:11:23 +030037import org.onlab.graph.Weight;
Yuta HIGUCHIac8b2292017-03-30 19:21:57 -070038import org.onlab.util.GuavaCollectors;
Brian O'Connorabafb502014-12-02 22:26:20 -080039import org.onosproject.net.AbstractModel;
40import org.onosproject.net.ConnectPoint;
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -070041import org.onosproject.net.DefaultDisjointPath;
Brian O'Connorabafb502014-12-02 22:26:20 -080042import org.onosproject.net.DefaultPath;
43import org.onosproject.net.DeviceId;
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -070044import org.onosproject.net.DisjointPath;
Brian O'Connorabafb502014-12-02 22:26:20 -080045import org.onosproject.net.Link;
Yuta HIGUCHIb440ef42016-07-15 09:09:09 -070046import org.onosproject.net.Link.Type;
Brian O'Connorabafb502014-12-02 22:26:20 -080047import org.onosproject.net.Path;
48import org.onosproject.net.provider.ProviderId;
49import org.onosproject.net.topology.ClusterId;
50import org.onosproject.net.topology.DefaultTopologyCluster;
51import org.onosproject.net.topology.DefaultTopologyVertex;
52import org.onosproject.net.topology.GraphDescription;
Thomas Vachuska41fe1ec2015-12-03 23:17:02 -080053import org.onosproject.net.topology.HopCountLinkWeight;
Andrey Komarov2398d962016-09-26 15:11:23 +030054import org.onosproject.net.topology.LinkWeigher;
Brian O'Connorabafb502014-12-02 22:26:20 -080055import org.onosproject.net.topology.Topology;
56import org.onosproject.net.topology.TopologyCluster;
57import org.onosproject.net.topology.TopologyEdge;
58import org.onosproject.net.topology.TopologyGraph;
59import org.onosproject.net.topology.TopologyVertex;
Thomas Vachuska41fe1ec2015-12-03 23:17:02 -080060import org.slf4j.Logger;
61import org.slf4j.LoggerFactory;
alshabib339a3d92014-09-26 17:54:32 -070062
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -070063import java.util.HashMap;
Thomas Vachuska930a8ee2015-08-04 18:49:36 -070064import java.util.List;
65import java.util.Map;
66import java.util.Set;
Thomas Vachuska48e64e42015-09-22 15:32:55 -070067import java.util.stream.Collectors;
Yuta HIGUCHIac8b2292017-03-30 19:21:57 -070068import java.util.stream.Stream;
alshabib339a3d92014-09-26 17:54:32 -070069
Thomas Vachuska930a8ee2015-08-04 18:49:36 -070070import static com.google.common.base.MoreObjects.toStringHelper;
Thomas Vachuska320c58f2015-08-05 10:42:32 -070071import static com.google.common.base.Preconditions.checkArgument;
Thomas Vachuska930a8ee2015-08-04 18:49:36 -070072import static org.onlab.graph.GraphPathSearch.ALL_PATHS;
Thomas Vachuska320c58f2015-08-05 10:42:32 -070073import static org.onlab.util.Tools.isNullOrEmpty;
Thomas Vachuska930a8ee2015-08-04 18:49:36 -070074import static org.onosproject.core.CoreService.CORE_PROVIDER_ID;
Thomas Vachuska930a8ee2015-08-04 18:49:36 -070075import static org.onosproject.net.Link.State.INACTIVE;
76import static org.onosproject.net.Link.Type.INDIRECT;
Andrey Komarov2398d962016-09-26 15:11:23 +030077import static org.onosproject.net.topology.AdapterLinkWeigher.adapt;
Thomas Vachuska930a8ee2015-08-04 18:49:36 -070078
alshabib339a3d92014-09-26 17:54:32 -070079/**
Abhishek Dwaraki1e5873e2015-03-08 00:01:17 -050080 * Default implementation of the topology descriptor. This carries the backing
81 * topology data.
alshabib339a3d92014-09-26 17:54:32 -070082 */
83public class DefaultTopology extends AbstractModel implements Topology {
84
Thomas Vachuska41fe1ec2015-12-03 23:17:02 -080085 private static final Logger log = LoggerFactory.getLogger(DefaultTopology.class);
86
Andrey Komarov2398d962016-09-26 15:11:23 +030087 private static final DijkstraGraphSearch<TopologyVertex, TopologyEdge> DIJKSTRA =
88 new DijkstraGraphSearch<>();
89 private static final TarjanGraphSearch<TopologyVertex, TopologyEdge> TARJAN =
90 new TarjanGraphSearch<>();
91 private static final SuurballeGraphSearch<TopologyVertex, TopologyEdge> SUURBALLE =
92 new SuurballeGraphSearch<>();
Yuta HIGUCHIac8b2292017-03-30 19:21:57 -070093 private static final KShortestPathsSearch<TopologyVertex, TopologyEdge> KSHORTEST =
94 new KShortestPathsSearch<>();
95 private static final LazyKShortestPathsSearch<TopologyVertex, TopologyEdge> LAZY_KSHORTEST =
96 new LazyKShortestPathsSearch<>();
97
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -070098
Andrey Komarov2398d962016-09-26 15:11:23 +030099 private static LinkWeigher defaultLinkWeigher = null;
Thomas Vachuska41fe1ec2015-12-03 23:17:02 -0800100 private static GraphPathSearch<TopologyVertex, TopologyEdge> defaultGraphPathSearch = null;
alshabib339a3d92014-09-26 17:54:32 -0700101
alshabib339a3d92014-09-26 17:54:32 -0700102 private final long time;
Abhishek Dwaraki1e5873e2015-03-08 00:01:17 -0500103 private final long creationTime;
Thomas Vachuska6b7920d2014-11-25 19:48:39 -0800104 private final long computeCost;
alshabib339a3d92014-09-26 17:54:32 -0700105 private final TopologyGraph graph;
106
Andrey Komarov2398d962016-09-26 15:11:23 +0300107 private final LinkWeigher hopCountWeigher;
Thomas Vachuska41fe1ec2015-12-03 23:17:02 -0800108
Jonathan Hartd9df7bd2015-11-10 17:10:25 -0800109 private final Supplier<SccResult<TopologyVertex, TopologyEdge>> clusterResults;
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800110 private final Supplier<ImmutableMap<ClusterId, TopologyCluster>> clusters;
111 private final Supplier<ImmutableSet<ConnectPoint>> infrastructurePoints;
112 private final Supplier<ImmutableSetMultimap<ClusterId, ConnectPoint>> broadcastSets;
Thomas Vachuskab2c47a72015-08-05 14:22:54 -0700113 private final Function<ConnectPoint, Boolean> broadcastFunction;
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800114 private final Supplier<ClusterIndexes> clusterIndexes;
alshabib339a3d92014-09-26 17:54:32 -0700115
116 /**
Thomas Vachuska41fe1ec2015-12-03 23:17:02 -0800117 * Sets the default link-weight to be used when computing paths. If null is
118 * specified, the builtin default link-weight measuring hop-counts will be
119 * used.
120 *
Andrey Komarov2398d962016-09-26 15:11:23 +0300121 * @param linkWeigher new default link-weight
Thomas Vachuska41fe1ec2015-12-03 23:17:02 -0800122 */
Andrey Komarov2398d962016-09-26 15:11:23 +0300123 public static void setDefaultLinkWeigher(LinkWeigher linkWeigher) {
124 log.info("Setting new default link-weight function to {}", linkWeigher);
125 defaultLinkWeigher = linkWeigher;
Thomas Vachuska41fe1ec2015-12-03 23:17:02 -0800126 }
127
128 /**
129 * Sets the default lpath search algorighm to be used when computing paths.
130 * If null is specified, the builtin default Dijkstra will be used.
131 *
132 * @param graphPathSearch new default algorithm
133 */
Andrey Komarov2398d962016-09-26 15:11:23 +0300134 public static void setDefaultGraphPathSearch(
135 GraphPathSearch<TopologyVertex, TopologyEdge> graphPathSearch) {
Thomas Vachuska41fe1ec2015-12-03 23:17:02 -0800136 log.info("Setting new default graph path algorithm to {}", graphPathSearch);
137 defaultGraphPathSearch = graphPathSearch;
138 }
139
140
141 /**
alshabib339a3d92014-09-26 17:54:32 -0700142 * Creates a topology descriptor attributed to the specified provider.
143 *
Thomas Vachuskab2c47a72015-08-05 14:22:54 -0700144 * @param providerId identity of the provider
145 * @param description data describing the new topology
146 * @param broadcastFunction broadcast point function
alshabib339a3d92014-09-26 17:54:32 -0700147 */
Thomas Vachuskab2c47a72015-08-05 14:22:54 -0700148 public DefaultTopology(ProviderId providerId, GraphDescription description,
149 Function<ConnectPoint, Boolean> broadcastFunction) {
alshabib339a3d92014-09-26 17:54:32 -0700150 super(providerId);
Thomas Vachuskab2c47a72015-08-05 14:22:54 -0700151 this.broadcastFunction = broadcastFunction;
alshabib339a3d92014-09-26 17:54:32 -0700152 this.time = description.timestamp();
Abhishek Dwaraki1e5873e2015-03-08 00:01:17 -0500153 this.creationTime = description.creationTime();
alshabib339a3d92014-09-26 17:54:32 -0700154
155 // Build the graph
156 this.graph = new DefaultTopologyGraph(description.vertexes(),
Andrey Komarov2398d962016-09-26 15:11:23 +0300157 description.edges());
alshabib339a3d92014-09-26 17:54:32 -0700158
Andrey Komarov2398d962016-09-26 15:11:23 +0300159 this.clusterResults = Suppliers.memoize(this::searchForClusters);
160 this.clusters = Suppliers.memoize(this::buildTopologyClusters);
alshabib339a3d92014-09-26 17:54:32 -0700161
Andrey Komarov2398d962016-09-26 15:11:23 +0300162 this.clusterIndexes = Suppliers.memoize(this::buildIndexes);
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800163
Andrey Komarov2398d962016-09-26 15:11:23 +0300164 this.hopCountWeigher = adapt(new HopCountLinkWeight(graph.getVertexes().size()));
165 this.broadcastSets = Suppliers.memoize(this::buildBroadcastSets);
166 this.infrastructurePoints = Suppliers.memoize(this::findInfrastructurePoints);
Thomas Vachuska6b7920d2014-11-25 19:48:39 -0800167 this.computeCost = Math.max(0, System.nanoTime() - time);
alshabib339a3d92014-09-26 17:54:32 -0700168 }
169
Thomas Vachuskab2c47a72015-08-05 14:22:54 -0700170 /**
171 * Creates a topology descriptor attributed to the specified provider.
172 *
173 * @param providerId identity of the provider
174 * @param description data describing the new topology
175 */
176 public DefaultTopology(ProviderId providerId, GraphDescription description) {
177 this(providerId, description, null);
178 }
179
alshabib339a3d92014-09-26 17:54:32 -0700180 @Override
181 public long time() {
182 return time;
183 }
184
185 @Override
Abhishek Dwaraki1e5873e2015-03-08 00:01:17 -0500186 public long creationTime() {
187 return creationTime;
188 }
189
190 @Override
Thomas Vachuska6b7920d2014-11-25 19:48:39 -0800191 public long computeCost() {
192 return computeCost;
193 }
194
195 @Override
alshabib339a3d92014-09-26 17:54:32 -0700196 public int clusterCount() {
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800197 return clusters.get().size();
alshabib339a3d92014-09-26 17:54:32 -0700198 }
199
200 @Override
201 public int deviceCount() {
202 return graph.getVertexes().size();
203 }
204
205 @Override
206 public int linkCount() {
207 return graph.getEdges().size();
208 }
209
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800210 private ImmutableMap<DeviceId, TopologyCluster> clustersByDevice() {
211 return clusterIndexes.get().clustersByDevice;
212 }
213
214 private ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster() {
215 return clusterIndexes.get().devicesByCluster;
216 }
217
218 private ImmutableSetMultimap<TopologyCluster, Link> linksByCluster() {
219 return clusterIndexes.get().linksByCluster;
alshabib339a3d92014-09-26 17:54:32 -0700220 }
221
222 /**
223 * Returns the backing topology graph.
224 *
225 * @return topology graph
226 */
Thomas Vachuska930a8ee2015-08-04 18:49:36 -0700227 public TopologyGraph getGraph() {
alshabib339a3d92014-09-26 17:54:32 -0700228 return graph;
229 }
230
231 /**
232 * Returns the set of topology clusters.
233 *
234 * @return set of clusters
235 */
Thomas Vachuska930a8ee2015-08-04 18:49:36 -0700236 public Set<TopologyCluster> getClusters() {
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800237 return ImmutableSet.copyOf(clusters.get().values());
alshabib339a3d92014-09-26 17:54:32 -0700238 }
239
240 /**
241 * Returns the specified topology cluster.
242 *
243 * @param clusterId cluster identifier
244 * @return topology cluster
245 */
Thomas Vachuska930a8ee2015-08-04 18:49:36 -0700246 public TopologyCluster getCluster(ClusterId clusterId) {
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800247 return clusters.get().get(clusterId);
alshabib339a3d92014-09-26 17:54:32 -0700248 }
249
250 /**
251 * Returns the topology cluster that contains the given device.
252 *
253 * @param deviceId device identifier
254 * @return topology cluster
255 */
Thomas Vachuska930a8ee2015-08-04 18:49:36 -0700256 public TopologyCluster getCluster(DeviceId deviceId) {
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800257 return clustersByDevice().get(deviceId);
alshabib339a3d92014-09-26 17:54:32 -0700258 }
259
260 /**
261 * Returns the set of cluster devices.
262 *
263 * @param cluster topology cluster
264 * @return cluster devices
265 */
Thomas Vachuska930a8ee2015-08-04 18:49:36 -0700266 public Set<DeviceId> getClusterDevices(TopologyCluster cluster) {
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800267 return devicesByCluster().get(cluster);
alshabib339a3d92014-09-26 17:54:32 -0700268 }
269
270 /**
271 * Returns the set of cluster links.
272 *
273 * @param cluster topology cluster
274 * @return cluster links
275 */
Thomas Vachuska930a8ee2015-08-04 18:49:36 -0700276 public Set<Link> getClusterLinks(TopologyCluster cluster) {
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800277 return linksByCluster().get(cluster);
alshabib339a3d92014-09-26 17:54:32 -0700278 }
279
280 /**
281 * Indicates whether the given point is an infrastructure link end-point.
282 *
283 * @param connectPoint connection point
284 * @return true if infrastructure
285 */
Thomas Vachuska930a8ee2015-08-04 18:49:36 -0700286 public boolean isInfrastructure(ConnectPoint connectPoint) {
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800287 return infrastructurePoints.get().contains(connectPoint);
alshabib339a3d92014-09-26 17:54:32 -0700288 }
289
290 /**
291 * Indicates whether the given point is part of a broadcast set.
292 *
293 * @param connectPoint connection point
294 * @return true if in broadcast set
295 */
Thomas Vachuska930a8ee2015-08-04 18:49:36 -0700296 public boolean isBroadcastPoint(ConnectPoint connectPoint) {
Thomas Vachuskab2c47a72015-08-05 14:22:54 -0700297 if (broadcastFunction != null) {
298 return broadcastFunction.apply(connectPoint);
299 }
300
alshabib339a3d92014-09-26 17:54:32 -0700301 // Any non-infrastructure, i.e. edge points are assumed to be OK.
302 if (!isInfrastructure(connectPoint)) {
303 return true;
304 }
305
306 // Find the cluster to which the device belongs.
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800307 TopologyCluster cluster = clustersByDevice().get(connectPoint.deviceId());
Andrey Komarov2398d962016-09-26 15:11:23 +0300308 checkArgument(cluster != null,
309 "No cluster found for device %s", connectPoint.deviceId());
alshabib339a3d92014-09-26 17:54:32 -0700310
311 // If the broadcast set is null or empty, or if the point explicitly
Thomas Vachuska320c58f2015-08-05 10:42:32 -0700312 // belongs to it, return true.
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800313 Set<ConnectPoint> points = broadcastSets.get().get(cluster.id());
Thomas Vachuska320c58f2015-08-05 10:42:32 -0700314 return isNullOrEmpty(points) || points.contains(connectPoint);
alshabib339a3d92014-09-26 17:54:32 -0700315 }
316
317 /**
318 * Returns the size of the cluster broadcast set.
319 *
320 * @param clusterId cluster identifier
321 * @return size of the cluster broadcast set
322 */
Thomas Vachuska930a8ee2015-08-04 18:49:36 -0700323 public int broadcastSetSize(ClusterId clusterId) {
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800324 return broadcastSets.get().get(clusterId).size();
alshabib339a3d92014-09-26 17:54:32 -0700325 }
326
327 /**
Thomas Vachuska320c58f2015-08-05 10:42:32 -0700328 * Returns the set of the cluster broadcast points.
329 *
330 * @param clusterId cluster identifier
331 * @return set of cluster broadcast points
332 */
333 public Set<ConnectPoint> broadcastPoints(ClusterId clusterId) {
334 return broadcastSets.get().get(clusterId);
335 }
336
337 /**
alshabib339a3d92014-09-26 17:54:32 -0700338 * Returns the set of pre-computed shortest paths between source and
339 * destination devices.
340 *
341 * @param src source device
342 * @param dst destination device
343 * @return set of shortest paths
344 */
Thomas Vachuska930a8ee2015-08-04 18:49:36 -0700345 public Set<Path> getPaths(DeviceId src, DeviceId dst) {
Aaron Kruglikovf6aed992016-11-08 15:16:03 -0800346 return getPaths(src, dst, linkWeight(), ALL_PATHS);
alshabib339a3d92014-09-26 17:54:32 -0700347 }
348
349 /**
350 * Computes on-demand the set of shortest paths between source and
351 * destination devices.
352 *
Andrey Komarov2398d962016-09-26 15:11:23 +0300353 * @param src source device
354 * @param dst destination device
355 * @param weigher link weight function
alshabib339a3d92014-09-26 17:54:32 -0700356 * @return set of shortest paths
357 */
Andrey Komarov2398d962016-09-26 15:11:23 +0300358 public Set<Path> getPaths(DeviceId src, DeviceId dst, LinkWeigher weigher) {
359 return getPaths(src, dst, weigher, ALL_PATHS);
Aaron Kruglikovf6aed992016-11-08 15:16:03 -0800360 }
361
362 /**
363 * Computes on-demand the set of shortest paths between source and
364 * destination devices, the set of returned paths will be no more than,
365 * maxPaths in size. The first {@code maxPaths} paths will be returned
366 * maintaining any ordering guarantees provided by the underlying
367 * (default or if no default is specified {@link DijkstraGraphSearch})
368 * search. If returning all paths of a given length would exceed
369 * {@code maxPaths} a subset of paths of that length will be returned,
370 * which paths will be returned depends on the currently specified
371 * {@code GraphPathSearch}. See {@link #setDefaultGraphPathSearch}.
372 *
373 * @param src source device
374 * @param dst destination device
Andrey Komarov2398d962016-09-26 15:11:23 +0300375 * @param weigher link weight function
Aaron Kruglikovf6aed992016-11-08 15:16:03 -0800376 * @param maxPaths maximum number of paths
377 * @return set of shortest paths
378 */
Andrey Komarov2398d962016-09-26 15:11:23 +0300379 public Set<Path> getPaths(DeviceId src, DeviceId dst, LinkWeigher weigher,
Aaron Kruglikovf6aed992016-11-08 15:16:03 -0800380 int maxPaths) {
Thomas Vachuska41fe1ec2015-12-03 23:17:02 -0800381 DefaultTopologyVertex srcV = new DefaultTopologyVertex(src);
382 DefaultTopologyVertex dstV = new DefaultTopologyVertex(dst);
Yuta HIGUCHI82e53262014-11-27 10:28:51 -0800383 Set<TopologyVertex> vertices = graph.getVertexes();
384 if (!vertices.contains(srcV) || !vertices.contains(dstV)) {
385 // src or dst not part of the current graph
386 return ImmutableSet.of();
387 }
388
alshabib339a3d92014-09-26 17:54:32 -0700389 GraphPathSearch.Result<TopologyVertex, TopologyEdge> result =
Andrey Komarov2398d962016-09-26 15:11:23 +0300390 graphPathSearch().search(graph, srcV, dstV, weigher, maxPaths);
alshabib339a3d92014-09-26 17:54:32 -0700391 ImmutableSet.Builder<Path> builder = ImmutableSet.builder();
392 for (org.onlab.graph.Path<TopologyVertex, TopologyEdge> path : result.paths()) {
393 builder.add(networkPath(path));
394 }
395 return builder.build();
396 }
Nikhil Cheerla2ec191f2015-07-09 12:34:54 -0700397
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700398 /**
Yuta HIGUCHIac8b2292017-03-30 19:21:57 -0700399 * Computes on-demand the k-shortest paths between source and
400 * destination devices.
401 *
402 * @param src source device
403 * @param dst destination device
404 * @param maxPaths maximum number of paths (k)
405 * @return set of k-shortest paths
406 */
407 public Set<Path> getKShortestPaths(DeviceId src, DeviceId dst,
408 int maxPaths) {
409
410 return getKShortestPaths(src, dst, linkWeight(), maxPaths);
411 }
412
413 /**
414 * Computes on-demand the k-shortest paths between source and
415 * destination devices.
416 *
417 * The first {@code maxPaths} paths will be returned
418 * in ascending order according to the provided {@code weigher}
419 *
420 * @param src source device
421 * @param dst destination device
422 * @param weigher link weight function
423 * @param maxPaths maximum number of paths (k)
424 * @return set of k-shortest paths
425 */
426 public Set<Path> getKShortestPaths(DeviceId src, DeviceId dst,
427 LinkWeigher weigher,
428 int maxPaths) {
429 DefaultTopologyVertex srcV = new DefaultTopologyVertex(src);
430 DefaultTopologyVertex dstV = new DefaultTopologyVertex(dst);
431 Set<TopologyVertex> vertices = graph.getVertexes();
432 if (!vertices.contains(srcV) || !vertices.contains(dstV)) {
433 // src or dst not part of the current graph
434 return ImmutableSet.of();
435 }
436
437 return KSHORTEST.search(graph, srcV, dstV, weigher, maxPaths)
438 .paths().stream()
439 .map(this::networkPath)
440 .collect(GuavaCollectors.toImmutableSet());
441 }
442
443 /**
444 * Lazily computes on-demand the k-shortest paths between source and
445 * destination devices.
446 *
447 *
448 * @param src source device
449 * @param dst destination device
450 * @return stream of k-shortest paths
451 */
452 public Stream<Path> getKShortestPaths(DeviceId src, DeviceId dst) {
453 return getKShortestPaths(src, dst, linkWeight());
454 }
455
456 /**
457 * Lazily computes on-demand the k-shortest paths between source and
458 * destination devices.
459 *
460 *
461 * @param src source device
462 * @param dst destination device
463 * @param weigher link weight function
464 * @return stream of k-shortest paths
465 */
466 public Stream<Path> getKShortestPaths(DeviceId src, DeviceId dst,
467 LinkWeigher weigher) {
468 DefaultTopologyVertex srcV = new DefaultTopologyVertex(src);
469 DefaultTopologyVertex dstV = new DefaultTopologyVertex(dst);
470 Set<TopologyVertex> vertices = graph.getVertexes();
471 if (!vertices.contains(srcV) || !vertices.contains(dstV)) {
472 // src or dst not part of the current graph
473 return Stream.empty();
474 }
475
476 return LAZY_KSHORTEST.lazyPathSearch(graph, srcV, dstV, weigher)
477 .map(this::networkPath);
478 }
479
480 /**
Andrey Komarov2398d962016-09-26 15:11:23 +0300481 * Returns the set of pre-computed shortest disjoint path pairs between
482 * source and destination devices.
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700483 *
484 * @param src source device
485 * @param dst destination device
486 * @return set of shortest disjoint path pairs
487 */
Nikhil Cheerla2ec191f2015-07-09 12:34:54 -0700488 public Set<DisjointPath> getDisjointPaths(DeviceId src, DeviceId dst) {
Thomas Vachuska41fe1ec2015-12-03 23:17:02 -0800489 return getDisjointPaths(src, dst, linkWeight());
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700490 }
491
492 /**
Andrey Komarov2398d962016-09-26 15:11:23 +0300493 * Computes on-demand the set of shortest disjoint path pairs between
494 * source and destination devices.
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700495 *
Andrey Komarov2398d962016-09-26 15:11:23 +0300496 * @param src source device
497 * @param dst destination device
498 * @param weigher link weight function
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700499 * @return set of disjoint shortest path pairs
500 */
Andrey Komarov2398d962016-09-26 15:11:23 +0300501 public Set<DisjointPath> getDisjointPaths(DeviceId src, DeviceId dst,
502 LinkWeigher weigher) {
Thomas Vachuska41fe1ec2015-12-03 23:17:02 -0800503 DefaultTopologyVertex srcV = new DefaultTopologyVertex(src);
504 DefaultTopologyVertex dstV = new DefaultTopologyVertex(dst);
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700505 Set<TopologyVertex> vertices = graph.getVertexes();
506 if (!vertices.contains(srcV) || !vertices.contains(dstV)) {
507 // src or dst not part of the current graph
508 return ImmutableSet.of();
509 }
510
511 GraphPathSearch.Result<TopologyVertex, TopologyEdge> result =
Andrey Komarov2398d962016-09-26 15:11:23 +0300512 SUURBALLE.search(graph, srcV, dstV, weigher, ALL_PATHS);
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700513 ImmutableSet.Builder<DisjointPath> builder = ImmutableSet.builder();
514 for (org.onlab.graph.Path<TopologyVertex, TopologyEdge> path : result.paths()) {
helenyrwue3c43342016-08-11 10:33:12 -0700515 DisjointPath disjointPath =
Andrey Komarov2398d962016-09-26 15:11:23 +0300516 networkDisjointPath((DisjointPathPair<TopologyVertex, TopologyEdge>) path);
helenyrwue3c43342016-08-11 10:33:12 -0700517 if (disjointPath.backup() != null) {
518 builder.add(disjointPath);
519 }
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700520 }
521 return builder.build();
522 }
523
524 /**
Andrey Komarov2398d962016-09-26 15:11:23 +0300525 * Computes on-demand the set of shortest disjoint risk groups path pairs
526 * between source and destination devices.
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700527 *
Nikhil Cheerla2ec191f2015-07-09 12:34:54 -0700528 * @param src source device
529 * @param dst destination device
Andrey Komarov2398d962016-09-26 15:11:23 +0300530 * @param weigher edge weight object
Nikhil Cheerla2ec191f2015-07-09 12:34:54 -0700531 * @param riskProfile map representing risk groups for each edge
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700532 * @return set of shortest disjoint paths
533 */
Andrey Komarov2398d962016-09-26 15:11:23 +0300534 private Set<DisjointPath> disjointPaths(DeviceId src, DeviceId dst,
535 LinkWeigher weigher,
Thomas Vachuska41fe1ec2015-12-03 23:17:02 -0800536 Map<TopologyEdge, Object> riskProfile) {
Thomas Vachuska48e64e42015-09-22 15:32:55 -0700537 DefaultTopologyVertex srcV = new DefaultTopologyVertex(src);
538 DefaultTopologyVertex dstV = new DefaultTopologyVertex(dst);
539
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700540 Set<TopologyVertex> vertices = graph.getVertexes();
541 if (!vertices.contains(srcV) || !vertices.contains(dstV)) {
542 // src or dst not part of the current graph
543 return ImmutableSet.of();
544 }
545
Andrey Komarov2398d962016-09-26 15:11:23 +0300546 SrlgGraphSearch<TopologyVertex, TopologyEdge> srlg =
547 new SrlgGraphSearch<>(riskProfile);
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700548 GraphPathSearch.Result<TopologyVertex, TopologyEdge> result =
Andrey Komarov2398d962016-09-26 15:11:23 +0300549 srlg.search(graph, srcV, dstV, weigher, ALL_PATHS);
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700550 ImmutableSet.Builder<DisjointPath> builder = ImmutableSet.builder();
551 for (org.onlab.graph.Path<TopologyVertex, TopologyEdge> path : result.paths()) {
helenyrwue3c43342016-08-11 10:33:12 -0700552 DisjointPath disjointPath =
Andrey Komarov2398d962016-09-26 15:11:23 +0300553 networkDisjointPath((DisjointPathPair<TopologyVertex, TopologyEdge>) path);
helenyrwue3c43342016-08-11 10:33:12 -0700554 if (disjointPath.backup() != null) {
555 builder.add(disjointPath);
556 }
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700557 }
558 return builder.build();
559 }
560
561 /**
Andrey Komarov2398d962016-09-26 15:11:23 +0300562 * Computes on-demand the set of shortest disjoint risk groups path pairs
563 * between source and destination devices.
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700564 *
Nikhil Cheerla2ec191f2015-07-09 12:34:54 -0700565 * @param src source device
566 * @param dst destination device
Andrey Komarov2398d962016-09-26 15:11:23 +0300567 * @param weigher edge weight object
Nikhil Cheerla2ec191f2015-07-09 12:34:54 -0700568 * @param riskProfile map representing risk groups for each link
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700569 * @return set of shortest disjoint paths
570 */
Andrey Komarov2398d962016-09-26 15:11:23 +0300571 public Set<DisjointPath> getDisjointPaths(DeviceId src, DeviceId dst,
572 LinkWeigher weigher,
Thomas Vachuska48e64e42015-09-22 15:32:55 -0700573 Map<Link, Object> riskProfile) {
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700574 Map<TopologyEdge, Object> riskProfile2 = new HashMap<>();
575 for (Link l : riskProfile.keySet()) {
576 riskProfile2.put(new TopologyEdge() {
Thomas Vachuska48e64e42015-09-22 15:32:55 -0700577 Link cur = l;
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700578
Yuta HIGUCHIb440ef42016-07-15 09:09:09 -0700579 @Override
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700580 public Link link() {
581 return cur;
582 }
583
Yuta HIGUCHIb440ef42016-07-15 09:09:09 -0700584 @Override
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700585 public TopologyVertex src() {
Thomas Vachuska48e64e42015-09-22 15:32:55 -0700586 return () -> src;
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700587 }
588
Yuta HIGUCHIb440ef42016-07-15 09:09:09 -0700589 @Override
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700590 public TopologyVertex dst() {
Thomas Vachuska48e64e42015-09-22 15:32:55 -0700591 return () -> dst;
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700592 }
593 }, riskProfile.get(l));
594 }
Andrey Komarov2398d962016-09-26 15:11:23 +0300595 return disjointPaths(src, dst, weigher, riskProfile2);
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700596 }
597
598 /**
Andrey Komarov2398d962016-09-26 15:11:23 +0300599 * Computes on-demand the set of shortest disjoint risk groups path pairs
600 * between source and destination devices.
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700601 *
Nikhil Cheerla2ec191f2015-07-09 12:34:54 -0700602 * @param src source device
603 * @param dst destination device
604 * @param riskProfile map representing risk groups for each link
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700605 * @return set of shortest disjoint paths
606 */
Andrey Komarov2398d962016-09-26 15:11:23 +0300607 public Set<DisjointPath> getDisjointPaths(DeviceId src, DeviceId dst,
608 Map<Link, Object> riskProfile) {
Thomas Vachuska41fe1ec2015-12-03 23:17:02 -0800609 return getDisjointPaths(src, dst, linkWeight(), riskProfile);
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700610 }
alshabib339a3d92014-09-26 17:54:32 -0700611
alshabib339a3d92014-09-26 17:54:32 -0700612 // Converts graph path to a network path with the same cost.
613 private Path networkPath(org.onlab.graph.Path<TopologyVertex, TopologyEdge> path) {
Andrey Komarov2398d962016-09-26 15:11:23 +0300614 List<Link> links = path.edges().stream().map(TopologyEdge::link)
615 .collect(Collectors.toList());
Thomas Vachuska6acd3bb2014-11-09 23:44:22 -0800616 return new DefaultPath(CORE_PROVIDER_ID, links, path.cost());
alshabib339a3d92014-09-26 17:54:32 -0700617 }
618
Andrey Komarov2398d962016-09-26 15:11:23 +0300619 private DisjointPath networkDisjointPath(
620 DisjointPathPair<TopologyVertex, TopologyEdge> path) {
Yuta HIGUCHIc3d69f52016-08-08 11:52:52 -0700621 if (!path.hasBackup()) {
622 // There was no secondary path available.
623 return new DefaultDisjointPath(CORE_PROVIDER_ID,
Andrey Komarov2398d962016-09-26 15:11:23 +0300624 (DefaultPath) networkPath(path.primary()),
625 null);
Yuta HIGUCHIc3d69f52016-08-08 11:52:52 -0700626 }
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700627 return new DefaultDisjointPath(CORE_PROVIDER_ID,
Andrey Komarov2398d962016-09-26 15:11:23 +0300628 (DefaultPath) networkPath(path.primary()),
629 (DefaultPath) networkPath(path.secondary()));
Nikhil Cheerla1cf0f9a2015-07-09 12:26:48 -0700630 }
631
alshabib339a3d92014-09-26 17:54:32 -0700632 // Searches for SCC clusters in the network topology graph using Tarjan
633 // algorithm.
Jonathan Hartd9df7bd2015-11-10 17:10:25 -0800634 private SccResult<TopologyVertex, TopologyEdge> searchForClusters() {
Andrey Komarov2398d962016-09-26 15:11:23 +0300635 return TARJAN.search(graph, new NoIndirectLinksWeigher());
alshabib339a3d92014-09-26 17:54:32 -0700636 }
637
638 // Builds the topology clusters and returns the id-cluster bindings.
639 private ImmutableMap<ClusterId, TopologyCluster> buildTopologyClusters() {
Andrey Komarov2398d962016-09-26 15:11:23 +0300640 ImmutableMap.Builder<ClusterId, TopologyCluster> clusterBuilder =
641 ImmutableMap.builder();
Jonathan Hartd9df7bd2015-11-10 17:10:25 -0800642 SccResult<TopologyVertex, TopologyEdge> results = clusterResults.get();
Thomas Vachuska48e64e42015-09-22 15:32:55 -0700643
alshabib339a3d92014-09-26 17:54:32 -0700644 // Extract both vertexes and edges from the results; the lists form
645 // pairs along the same index.
Thomas Vachuskac31d9f12015-01-22 12:33:27 -0800646 List<Set<TopologyVertex>> clusterVertexes = results.clusterVertexes();
647 List<Set<TopologyEdge>> clusterEdges = results.clusterEdges();
alshabib339a3d92014-09-26 17:54:32 -0700648
649 // Scan over the lists and create a cluster from the results.
Thomas Vachuskac31d9f12015-01-22 12:33:27 -0800650 for (int i = 0, n = results.clusterCount(); i < n; i++) {
alshabib339a3d92014-09-26 17:54:32 -0700651 Set<TopologyVertex> vertexSet = clusterVertexes.get(i);
652 Set<TopologyEdge> edgeSet = clusterEdges.get(i);
653
654 ClusterId cid = ClusterId.clusterId(i);
Abhishek Dwaraki1e5873e2015-03-08 00:01:17 -0500655 DefaultTopologyCluster cluster = new DefaultTopologyCluster(cid,
Andrey Komarov2398d962016-09-26 15:11:23 +0300656 vertexSet.size(),
657 edgeSet.size(),
658 findRoot(vertexSet));
alshabib339a3d92014-09-26 17:54:32 -0700659 clusterBuilder.put(cid, cluster);
660 }
661 return clusterBuilder.build();
662 }
663
664 // Finds the vertex whose device id is the lexicographical minimum in the
665 // specified set.
666 private TopologyVertex findRoot(Set<TopologyVertex> vertexSet) {
667 TopologyVertex minVertex = null;
668 for (TopologyVertex vertex : vertexSet) {
Jon Halle7539632016-05-11 15:44:31 -0700669 if ((minVertex == null) || (vertex.deviceId()
Abhishek Dwaraki1e5873e2015-03-08 00:01:17 -0500670 .toString().compareTo(minVertex.deviceId().toString()) < 0)) {
alshabib339a3d92014-09-26 17:54:32 -0700671 minVertex = vertex;
672 }
673 }
674 return minVertex;
675 }
676
677 // Processes a map of broadcast sets for each cluster.
678 private ImmutableSetMultimap<ClusterId, ConnectPoint> buildBroadcastSets() {
Thomas Vachuska41fe1ec2015-12-03 23:17:02 -0800679 Builder<ClusterId, ConnectPoint> builder = ImmutableSetMultimap.builder();
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800680 for (TopologyCluster cluster : clusters.get().values()) {
alshabib339a3d92014-09-26 17:54:32 -0700681 addClusterBroadcastSet(cluster, builder);
682 }
683 return builder.build();
684 }
685
686 // Finds all broadcast points for the cluster. These are those connection
687 // points which lie along the shortest paths between the cluster root and
688 // all other devices within the cluster.
Andrey Komarov2398d962016-09-26 15:11:23 +0300689 private void addClusterBroadcastSet(TopologyCluster cluster,
690 Builder<ClusterId, ConnectPoint> builder) {
alshabib339a3d92014-09-26 17:54:32 -0700691 // Use the graph root search results to build the broadcast set.
Andrey Komarov2398d962016-09-26 15:11:23 +0300692 Result<TopologyVertex, TopologyEdge> result =
693 DIJKSTRA.search(graph, cluster.root(), null, hopCountWeigher, 1);
694 for (Map.Entry<TopologyVertex, Set<TopologyEdge>> entry :
695 result.parents().entrySet()) {
alshabib339a3d92014-09-26 17:54:32 -0700696 TopologyVertex vertex = entry.getKey();
697
698 // Ignore any parents that lead outside the cluster.
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800699 if (clustersByDevice().get(vertex.deviceId()) != cluster) {
alshabib339a3d92014-09-26 17:54:32 -0700700 continue;
701 }
702
703 // Ignore any back-link sets that are empty.
704 Set<TopologyEdge> parents = entry.getValue();
705 if (parents.isEmpty()) {
706 continue;
707 }
708
709 // Use the first back-link source and destinations to add to the
710 // broadcast set.
711 Link link = parents.iterator().next().link();
712 builder.put(cluster.id(), link.src());
713 builder.put(cluster.id(), link.dst());
714 }
715 }
716
717 // Collects and returns an set of all infrastructure link end-points.
718 private ImmutableSet<ConnectPoint> findInfrastructurePoints() {
719 ImmutableSet.Builder<ConnectPoint> builder = ImmutableSet.builder();
720 for (TopologyEdge edge : graph.getEdges()) {
Yuta HIGUCHIb440ef42016-07-15 09:09:09 -0700721 if (edge.link().type() == Type.EDGE) {
722 // exclude EDGE link from infrastructure link
723 // - Device <-> Host
724 // - Device <-> remote domain Device
725 continue;
726 }
alshabib339a3d92014-09-26 17:54:32 -0700727 builder.add(edge.link().src());
728 builder.add(edge.link().dst());
729 }
730 return builder.build();
731 }
732
733 // Builds cluster-devices, cluster-links and device-cluster indexes.
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800734 private ClusterIndexes buildIndexes() {
alshabib339a3d92014-09-26 17:54:32 -0700735 // Prepare the index builders
Abhishek Dwaraki1e5873e2015-03-08 00:01:17 -0500736 ImmutableMap.Builder<DeviceId, TopologyCluster> clusterBuilder =
737 ImmutableMap.builder();
738 ImmutableSetMultimap.Builder<TopologyCluster, DeviceId> devicesBuilder =
739 ImmutableSetMultimap.builder();
740 ImmutableSetMultimap.Builder<TopologyCluster, Link> linksBuilder =
741 ImmutableSetMultimap.builder();
alshabib339a3d92014-09-26 17:54:32 -0700742
743 // Now scan through all the clusters
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800744 for (TopologyCluster cluster : clusters.get().values()) {
alshabib339a3d92014-09-26 17:54:32 -0700745 int i = cluster.id().index();
746
747 // Scan through all the cluster vertexes.
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800748 for (TopologyVertex vertex : clusterResults.get().clusterVertexes().get(i)) {
alshabib339a3d92014-09-26 17:54:32 -0700749 devicesBuilder.put(cluster, vertex.deviceId());
750 clusterBuilder.put(vertex.deviceId(), cluster);
751 }
752
753 // Scan through all the cluster edges.
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800754 for (TopologyEdge edge : clusterResults.get().clusterEdges().get(i)) {
alshabib339a3d92014-09-26 17:54:32 -0700755 linksBuilder.put(cluster, edge.link());
756 }
757 }
758
759 // Finalize all indexes.
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800760 return new ClusterIndexes(clusterBuilder.build(),
Andrey Komarov2398d962016-09-26 15:11:23 +0300761 devicesBuilder.build(),
762 linksBuilder.build());
alshabib339a3d92014-09-26 17:54:32 -0700763 }
764
Thomas Vachuska41fe1ec2015-12-03 23:17:02 -0800765 private GraphPathSearch<TopologyVertex, TopologyEdge> graphPathSearch() {
766 return defaultGraphPathSearch != null ? defaultGraphPathSearch : DIJKSTRA;
767 }
alshabib339a3d92014-09-26 17:54:32 -0700768
Andrey Komarov2398d962016-09-26 15:11:23 +0300769 private LinkWeigher linkWeight() {
770 return defaultLinkWeigher != null ? defaultLinkWeigher : hopCountWeigher;
alshabib339a3d92014-09-26 17:54:32 -0700771 }
772
773 // Link weight for preventing traversal over indirect links.
Andrey Komarov2398d962016-09-26 15:11:23 +0300774 private static class NoIndirectLinksWeigher
775 extends DefaultEdgeWeigher<TopologyVertex, TopologyEdge>
776 implements LinkWeigher {
alshabib339a3d92014-09-26 17:54:32 -0700777 @Override
Andrey Komarov2398d962016-09-26 15:11:23 +0300778 public Weight weight(TopologyEdge edge) {
779 return (edge.link().state() == INACTIVE) ||
780 (edge.link().type() == INDIRECT) ?
781 getNonViableWeight() : new ScalarWeight(HOP_WEIGHT_VALUE);
alshabib339a3d92014-09-26 17:54:32 -0700782 }
783 }
784
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800785 static final class ClusterIndexes {
786 final ImmutableMap<DeviceId, TopologyCluster> clustersByDevice;
787 final ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster;
788 final ImmutableSetMultimap<TopologyCluster, Link> linksByCluster;
789
Thomas Vachuska930a8ee2015-08-04 18:49:36 -0700790 public ClusterIndexes(ImmutableMap<DeviceId, TopologyCluster> clustersByDevice,
791 ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster,
792 ImmutableSetMultimap<TopologyCluster, Link> linksByCluster) {
Yuta HIGUCHIb2e74a02014-11-30 13:54:38 -0800793 this.clustersByDevice = clustersByDevice;
794 this.devicesByCluster = devicesByCluster;
795 this.linksByCluster = linksByCluster;
796 }
797 }
798
alshabib339a3d92014-09-26 17:54:32 -0700799 @Override
800 public String toString() {
801 return toStringHelper(this)
802 .add("time", time)
Abhishek Dwaraki1e5873e2015-03-08 00:01:17 -0500803 .add("creationTime", creationTime)
Thomas Vachuska6b7920d2014-11-25 19:48:39 -0800804 .add("computeCost", computeCost)
alshabib339a3d92014-09-26 17:54:32 -0700805 .add("clusters", clusterCount())
806 .add("devices", deviceCount())
Abhishek Dwaraki1e5873e2015-03-08 00:01:17 -0500807 .add("links", linkCount()).toString();
alshabib339a3d92014-09-26 17:54:32 -0700808 }
809}