alshabib

Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next

Showing 23 changed files with 1227 additions and 70 deletions
package org.onlab.onos.event;
import static com.google.common.base.MoreObjects.toStringHelper;
/**
* Base event implementation.
*/
......@@ -48,4 +50,10 @@ public class AbstractEvent<T extends Enum, S extends Object> implements Event<T,
return subject;
}
@Override
public String toString() {
return toStringHelper(this).add("time", time).add("type", type())
.add("subject", subject()).toString();
}
}
......
package org.onlab.onos.event;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Base implementation of an event accumulator. It allows triggering based on
* event inter-arrival time threshold, maximum batch life threshold and maximum
* batch size.
*/
public abstract class AbstractEventAccumulator implements EventAccumulator {
private final Timer timer;
private final int maxEvents;
private final int maxBatchMillis;
private final int maxIdleMillis;
private TimerTask idleTask = new ProcessorTask();
private TimerTask maxTask = new ProcessorTask();
private List<Event> events = Lists.newArrayList();
/**
* Creates an event accumulator capable of triggering on the specified
* thresholds.
*
* @param timer timer to use for scheduling check-points
* @param maxEvents maximum number of events to accumulate before
* processing is triggered
* @param maxBatchMillis maximum number of millis allowed since the first
* event before processing is triggered
* @param maxIdleMillis maximum number millis between events before
* processing is triggered
*/
protected AbstractEventAccumulator(Timer timer, int maxEvents,
int maxBatchMillis, int maxIdleMillis) {
this.timer = checkNotNull(timer, "Timer cannot be null");
checkArgument(maxEvents > 1, "Maximum number of events must be > 1");
checkArgument(maxBatchMillis > 0, "Maximum millis must be positive");
checkArgument(maxIdleMillis > 0, "Maximum idle millis must be positive");
this.maxEvents = maxEvents;
this.maxBatchMillis = maxBatchMillis;
this.maxIdleMillis = maxIdleMillis;
}
@Override
public void add(Event event) {
idleTask = cancelIfActive(idleTask);
events.add(event);
// Did we hit the max event threshold?
if (events.size() == maxEvents) {
maxTask = cancelIfActive(maxTask);
schedule(1);
} else {
// Otherwise, schedule idle task and if this is a first event
// also schedule the max batch age task.
idleTask = schedule(maxIdleMillis);
if (events.size() == 1) {
maxTask = schedule(maxBatchMillis);
}
}
}
// Schedules a new processor task given number of millis in the future.
private TimerTask schedule(int millis) {
TimerTask task = new ProcessorTask();
timer.schedule(task, millis);
return task;
}
// Cancels the specified task if it is active.
private TimerTask cancelIfActive(TimerTask task) {
if (task != null) {
task.cancel();
}
return task;
}
// Task for triggering processing of accumulated events
private class ProcessorTask extends TimerTask {
@Override
public void run() {
idleTask = cancelIfActive(idleTask);
maxTask = cancelIfActive(maxTask);
processEvents(finalizeCurrentBatch());
}
}
// Demotes and returns the current batch of events and promotes a new one.
private synchronized List<Event> finalizeCurrentBatch() {
List<Event> toBeProcessed = events;
events = Lists.newArrayList();
return toBeProcessed;
}
/**
* Returns the backing timer.
*
* @return backing timer
*/
public Timer timer() {
return timer;
}
/**
* Returns the maximum number of events allowed to accumulate before
* processing is triggered.
*
* @return max number of events
*/
public int maxEvents() {
return maxEvents;
}
/**
* Returns the maximum number of millis allowed to expire since the first
* event before processing is triggered.
*
* @return max number of millis a batch is allowed to last
*/
public int maxBatchMillis() {
return maxBatchMillis;
}
/**
* Returns the maximum number of millis allowed to expire since the last
* event arrival before processing is triggered.
*
* @return max number of millis since the last event
*/
public int maxIdleMillis() {
return maxIdleMillis;
}
}
package org.onlab.onos.event;
import java.util.List;
/**
* Abstraction of an accumulator capable of collecting events and at some
* point in time triggers processing of all previously accumulated events.
*/
public interface EventAccumulator {
/**
* Adds an event to the current batch. This operation may, or may not
* trigger processing of the current batch of events.
*
* @param event event to be added to the current batch
*/
void add(Event event);
/**
* Processes the specified list of accumulated events.
*
* @param events list of accumulated events
*/
void processEvents(List<Event> events);
}
......@@ -20,6 +20,13 @@ public interface TopologyService {
Topology currentTopology();
/**
* Indicates whether the specified topology is the latest or not.
* @param topology topology descriptor
* @return true if the topology is the most recent; false otherwise
*/
boolean isLatest(Topology topology);
/**
* Returns the set of clusters in the specified topology.
*
* @param topology topology descriptor
......
package org.onlab.onos.net.trivial.impl;
import org.onlab.onos.net.AbstractModel;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.net.topology.Topology;
/**
* Default implementation of the topology descriptor. This carries the
* backing topology data.
*/
public class DefaultTopology extends AbstractModel implements Topology {
private final long time;
private final int clusterCount;
private final int deviceCount;
private final int linkCount;
private final int pathCount;
/**
* Creates a topology descriptor attributed to the specified provider.
*
* @param providerId identity of the provider
* @param time creation time in system nanos
* @param clusterCount number of clusters
* @param deviceCount number of devices
* @param linkCount number of links
* @param pathCount number of pre-computed paths
*/
DefaultTopology(ProviderId providerId, long time, int clusterCount,
int deviceCount, int linkCount, int pathCount) {
super(providerId);
this.time = time;
this.clusterCount = clusterCount;
this.deviceCount = deviceCount;
this.linkCount = linkCount;
this.pathCount = pathCount;
}
@Override
public long time() {
return time;
}
@Override
public int clusterCount() {
return clusterCount;
}
@Override
public int deviceCount() {
return deviceCount;
}
@Override
public int linkCount() {
return linkCount;
}
@Override
public int pathCount() {
return pathCount;
}
}
package org.onlab.onos.net.trivial.impl;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.onlab.graph.AdjacencyListsGraph;
import org.onlab.graph.DijkstraGraphSearch;
import org.onlab.graph.Graph;
import org.onlab.graph.GraphPathSearch;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.Device;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.topology.ClusterId;
import org.onlab.onos.net.topology.LinkWeight;
import org.onlab.onos.net.topology.TopoEdge;
import org.onlab.onos.net.topology.TopoVertex;
import org.onlab.onos.net.topology.TopologyCluster;
import org.onlab.onos.net.topology.TopologyDescription;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import static com.google.common.base.MoreObjects.toStringHelper;
import static org.onlab.graph.GraphPathSearch.Result;
import static org.onlab.onos.net.Link.Type.INDIRECT;
/**
* Default implementation of an immutable topology data carrier.
*/
public class DefaultTopologyDescription implements TopologyDescription {
class DefaultTopologyDescription implements TopologyDescription {
private static final GraphPathSearch<TopoVertex, TopoEdge> DIJKSTRA =
new DijkstraGraphSearch<>();
private final long nanos;
private final Map<DeviceId, TopoVertex> vertexesById = Maps.newHashMap();
private final Graph<TopoVertex, TopoEdge> graph;
private final Map<DeviceId, GraphPathSearch.Result<TopoVertex, TopoEdge>> results;
private final Map<DeviceId, Result<TopoVertex, TopoEdge>> results;
private final Map<ClusterId, TopologyCluster> clusters;
private final Multimap<ClusterId, DeviceId> clusterDevices;
private final Multimap<ClusterId, Link> clusterLinks;
private final Map<DeviceId, TopologyCluster> deviceClusters;
public DefaultTopologyDescription(long nanos, Graph<TopoVertex, TopoEdge> graph,
Map<DeviceId, GraphPathSearch.Result<TopoVertex, TopoEdge>> results,
Map<ClusterId, TopologyCluster> clusters,
Multimap<ClusterId, DeviceId> clusterDevices,
Multimap<ClusterId, Link> clusterLinks,
Map<DeviceId, TopologyCluster> deviceClusters) {
// private final Multimap<ClusterId, DeviceId> clusterDevices;
// private final Multimap<ClusterId, Link> clusterLinks;
// private final Map<DeviceId, TopologyCluster> deviceClusters;
DefaultTopologyDescription(long nanos, Iterable<Device> devices, Iterable<Link> links) {
this.nanos = nanos;
this.graph = graph;
this.results = results;
this.clusters = clusters;
this.clusterDevices = clusterDevices;
this.clusterLinks = clusterLinks;
this.deviceClusters = deviceClusters;
this.graph = buildGraph(devices, links);
this.results = computeDefaultPaths();
this.clusters = computeClusters();
// this.clusterDevices = clusterDevices;
// this.clusterLinks = clusterLinks;
// this.deviceClusters = deviceClusters;
}
// Constructs the topology graph using the supplied devices and links.
private Graph<TopoVertex, TopoEdge> buildGraph(Iterable<Device> devices,
Iterable<Link> links) {
Graph<TopoVertex, TopoEdge> graph =
new AdjacencyListsGraph<>(buildVertexes(devices),
buildEdges(links));
return graph;
}
// Builds a set of topology vertexes from the specified list of devices
private Set<TopoVertex> buildVertexes(Iterable<Device> devices) {
Set<TopoVertex> vertexes = Sets.newHashSet();
for (Device device : devices) {
TopoVertex vertex = new TVertex(device.id());
vertexesById.put(vertex.deviceId(), vertex);
vertexes.add(vertex);
}
return vertexes;
}
// Builds a set of topology vertexes from the specified list of links
private Set<TopoEdge> buildEdges(Iterable<Link> links) {
Set<TopoEdge> edges = Sets.newHashSet();
for (Link link : links) {
edges.add(new TEdge(vertexOf(link.src()), vertexOf(link.dst()), link));
}
return edges;
}
// Computes the default shortest paths for all source/dest pairs using
// the multi-path Dijkstra and hop-count as path cost.
private Map<DeviceId, Result<TopoVertex, TopoEdge>> computeDefaultPaths() {
LinkWeight weight = new HopCountLinkWeight(graph.getVertexes().size());
Map<DeviceId, Result<TopoVertex, TopoEdge>> results = Maps.newHashMap();
// Search graph paths for each source to all destinations.
for (TopoVertex src : vertexesById.values()) {
results.put(src.deviceId(), DIJKSTRA.search(graph, src, null, weight));
}
return results;
}
// Computes topology SCC clusters using Tarjan algorithm.
private Map<ClusterId, TopologyCluster> computeClusters() {
Map<ClusterId, TopologyCluster> clusters = Maps.newHashMap();
return clusters;
}
// Fetches a vertex corresponding to the given connection point device.
private TopoVertex vertexOf(ConnectPoint connectPoint) {
DeviceId id = connectPoint.deviceId();
TopoVertex vertex = vertexesById.get(id);
if (vertex == null) {
// If vertex does not exist, create one and register it.
vertex = new TVertex(id);
vertexesById.put(id, vertex);
}
return vertex;
}
@Override
......@@ -54,7 +125,7 @@ public class DefaultTopologyDescription implements TopologyDescription {
}
@Override
public GraphPathSearch.Result<TopoVertex, TopoEdge> pathResults(DeviceId srcDeviceId) {
public Result<TopoVertex, TopoEdge> pathResults(DeviceId srcDeviceId) {
return results.get(srcDeviceId);
}
......@@ -75,6 +146,105 @@ public class DefaultTopologyDescription implements TopologyDescription {
@Override
public TopologyCluster clusterFor(DeviceId deviceId) {
return deviceClusters.get(deviceId);
return null; // deviceClusters.get(deviceId);
}
// Implementation of the topology vertex backed by a device id
private static class TVertex implements TopoVertex {
private final DeviceId deviceId;
public TVertex(DeviceId deviceId) {
this.deviceId = deviceId;
}
@Override
public DeviceId deviceId() {
return deviceId;
}
@Override
public int hashCode() {
return Objects.hash(deviceId);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof TVertex) {
final TVertex other = (TVertex) obj;
return Objects.equals(this.deviceId, other.deviceId);
}
return false;
}
@Override
public String toString() {
return deviceId.toString();
}
}
// Implementation of the topology edge backed by a link
private class TEdge implements TopoEdge {
private final Link link;
private final TopoVertex src;
private final TopoVertex dst;
public TEdge(TopoVertex src, TopoVertex dst, Link link) {
this.src = src;
this.dst = dst;
this.link = link;
}
@Override
public Link link() {
return link;
}
@Override
public TopoVertex src() {
return src;
}
@Override
public TopoVertex dst() {
return dst;
}
@Override
public int hashCode() {
return Objects.hash(link);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof TEdge) {
final TEdge other = (TEdge) obj;
return Objects.equals(this.link, other.link);
}
return false;
}
@Override
public String toString() {
return toStringHelper(this).add("src", src).add("dst", dst).toString();
}
}
// Link weight for measuring link cost as hop count with indirect links
// being as expensive as traversing the entire graph to assume the worst.
private class HopCountLinkWeight implements LinkWeight {
private final int indirectLinkCost;
public HopCountLinkWeight(int indirectLinkCost) {
this.indirectLinkCost = indirectLinkCost;
}
@Override
public double weight(TopoEdge edge) {
// To force preference to use direct paths first, make indirect
// links as expensive as the linear vertex traversal.
return edge.link().type() == INDIRECT ? indirectLinkCost : 1;
}
}
}
......
......@@ -150,7 +150,8 @@ public class SimpleDeviceManager
}
// Personalized device provider service issued to the supplied provider.
private class InternalDeviceProviderService extends AbstractProviderService<DeviceProvider>
private class InternalDeviceProviderService
extends AbstractProviderService<DeviceProvider>
implements DeviceProviderService {
InternalDeviceProviderService(DeviceProvider provider) {
......
......@@ -28,7 +28,8 @@ import static com.google.common.base.Preconditions.checkArgument;
import static org.onlab.onos.net.device.DeviceEvent.Type.*;
/**
* Manages inventory of infrastructure DEVICES using trivial in-memory
* structures implementation.
*/
class SimpleDeviceStore {
......
......@@ -158,7 +158,7 @@ public class SimpleHostManager
// Posts the specified event to the local event dispatcher.
private void post(HostEvent event) {
if (event != null && eventDispatcher != null) {
if (event != null) {
eventDispatcher.post(event);
}
}
......
......@@ -36,8 +36,8 @@ import com.google.common.collect.Sets;
@Component(immediate = true)
@Service
public class SimpleLinkManager
extends AbstractProviderRegistry<LinkProvider, LinkProviderService>
implements LinkService, LinkAdminService, LinkProviderRegistry {
extends AbstractProviderRegistry<LinkProvider, LinkProviderService>
implements LinkService, LinkAdminService, LinkProviderRegistry {
private static final String DEVICE_ID_NULL = "Device ID cannot be null";
private static final String LINK_DESC_NULL = "Link description cannot be null";
......@@ -146,7 +146,8 @@ implements LinkService, LinkAdminService, LinkProviderRegistry {
}
// Personalized link provider service issued to the supplied provider.
private class InternalLinkProviderService extends AbstractProviderService<LinkProvider>
private class InternalLinkProviderService
extends AbstractProviderService<LinkProvider>
implements LinkProviderService {
InternalLinkProviderService(LinkProvider provider) {
......@@ -157,27 +158,31 @@ implements LinkService, LinkAdminService, LinkProviderRegistry {
public void linkDetected(LinkDescription linkDescription) {
checkNotNull(linkDescription, LINK_DESC_NULL);
checkValidity();
log.debug("Link {} detected", linkDescription);
LinkEvent event = store.createOrUpdateLink(provider().id(),
linkDescription);
if (event != null) {
log.debug("Link {} detected", linkDescription);
post(event);
}
}
@Override
public void linkVanished(LinkDescription linkDescription) {
checkNotNull(linkDescription, LINK_DESC_NULL);
checkValidity();
log.info("Link {} vanished", linkDescription);
LinkEvent event = store.removeLink(linkDescription.src(),
linkDescription.dst());
if (event != null) {
log.info("Link {} vanished", linkDescription);
post(event);
}
}
@Override
public void linksVanished(ConnectPoint connectPoint) {
checkNotNull(connectPoint, "Connect point cannot be null");
checkValidity();
log.info("Link for connection point {} vanished", connectPoint);
log.info("Links for connection point {} vanished", connectPoint);
removeLinks(getLinks(connectPoint));
}
......@@ -185,7 +190,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry {
public void linksVanished(DeviceId deviceId) {
checkNotNull(deviceId, DEVICE_ID_NULL);
checkValidity();
log.info("Link for device {} vanished", deviceId);
log.info("Links for device {} vanished", deviceId);
removeLinks(getDeviceLinks(deviceId));
}
}
......@@ -200,7 +205,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry {
// Posts the specified event to the local event dispatcher.
private void post(LinkEvent event) {
if (event != null && eventDispatcher != null) {
if (event != null) {
eventDispatcher.post(event);
}
}
......
......@@ -25,7 +25,7 @@ import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED;
import static org.onlab.onos.net.link.LinkEvent.Type.LINK_UPDATED;
/**
* Manages inventory of infrastructure links using trivial in-memory link
* Manages inventory of infrastructure links using trivial in-memory structures
* implementation.
*/
class SimpleLinkStore {
......
......@@ -72,25 +72,26 @@ public class SimpleTopologyManager
}
@Override
protected TopologyProviderService createProviderService(TopologyProvider provider) {
return new InternalTopologyProviderService(provider);
public Topology currentTopology() {
return store.currentTopology();
}
@Override
public Topology currentTopology() {
return null;
public boolean isLatest(Topology topology) {
checkNotNull(topology, TOPOLOGY_NULL);
return store.isLatest(topology);
}
@Override
public Set<TopologyCluster> getClusters(Topology topology) {
checkNotNull(topology, TOPOLOGY_NULL);
return null;
return store.getClusters(topology);
}
@Override
public Graph<TopoVertex, TopoEdge> getGraph(Topology topology) {
checkNotNull(topology, TOPOLOGY_NULL);
return null;
return store.getGraph(topology);
}
@Override
......@@ -98,7 +99,7 @@ public class SimpleTopologyManager
checkNotNull(topology, TOPOLOGY_NULL);
checkNotNull(src, DEVICE_ID_NULL);
checkNotNull(dst, DEVICE_ID_NULL);
return null;
return store.getPaths(topology, src, dst);
}
@Override
......@@ -107,21 +108,21 @@ public class SimpleTopologyManager
checkNotNull(src, DEVICE_ID_NULL);
checkNotNull(dst, DEVICE_ID_NULL);
checkNotNull(weight, "Link weight cannot be null");
return null;
return store.getPaths(topology, src, dst, weight);
}
@Override
public boolean isInfrastructure(Topology topology, ConnectPoint connectPoint) {
checkNotNull(topology, TOPOLOGY_NULL);
checkNotNull(connectPoint, CONNECTION_POINT_NULL);
return false;
return store.isInfrastructure(topology, connectPoint);
}
@Override
public boolean isInBroadcastTree(Topology topology, ConnectPoint connectPoint) {
checkNotNull(topology, TOPOLOGY_NULL);
checkNotNull(connectPoint, CONNECTION_POINT_NULL);
return false;
return store.isInBroadcastTree(topology, connectPoint);
}
@Override
......@@ -135,6 +136,11 @@ public class SimpleTopologyManager
}
// Personalized host provider service issued to the supplied provider.
@Override
protected TopologyProviderService createProviderService(TopologyProvider provider) {
return new InternalTopologyProviderService(provider);
}
private class InternalTopologyProviderService
extends AbstractProviderService<TopologyProvider>
implements TopologyProviderService {
......@@ -147,8 +153,15 @@ public class SimpleTopologyManager
public void topologyChanged(TopologyDescription topoDescription,
List<Event> reasons) {
checkNotNull(topoDescription, "Topology description cannot be null");
log.info("Topology changed due to: {}", // to be removed soon
reasons == null ? "initial compute" : reasons);
TopologyEvent event = store.updateTopology(topoDescription, reasons);
if (event != null) {
log.info("Topology changed due to: {}",
reasons == null ? "initial compute" : reasons);
eventDispatcher.post(event);
}
}
}
......
package org.onlab.onos.net.trivial.impl;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onlab.onos.event.AbstractEventAccumulator;
import org.onlab.onos.event.Event;
import org.onlab.onos.event.EventAccumulator;
import org.onlab.onos.net.device.DeviceEvent;
import org.onlab.onos.net.device.DeviceListener;
import org.onlab.onos.net.device.DeviceService;
import org.onlab.onos.net.link.LinkEvent;
import org.onlab.onos.net.link.LinkListener;
import org.onlab.onos.net.link.LinkService;
import org.onlab.onos.net.provider.AbstractProvider;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.net.topology.TopologyDescription;
import org.onlab.onos.net.topology.TopologyProvider;
import org.onlab.onos.net.topology.TopologyProviderRegistry;
import org.onlab.onos.net.topology.TopologyProviderService;
import org.slf4j.Logger;
import java.util.List;
import java.util.Timer;
import java.util.concurrent.ExecutorService;
import static java.util.concurrent.Executors.newFixedThreadPool;
import static org.onlab.onos.net.device.DeviceEvent.Type.*;
import static org.onlab.util.Tools.namedThreads;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Simple implementation of a network topology provider/computor.
*/
@Component(immediate = true)
public class SimpleTopologyProvider extends AbstractProvider
implements TopologyProvider {
// TODO: make these configurable
private static final int MAX_EVENTS = 100;
private static final int MAX_IDLE_MS = 50;
private static final int MAX_BATCH_MS = 200;
private static final int MAX_THREADS = 8;
// FIXME: Replace with a system-wide timer instance
private static final Timer TIMER = new Timer();
private final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected TopologyProviderRegistry providerRegistry;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected LinkService linkService;
private volatile boolean isStarted = false;
private TopologyProviderService providerService;
private DeviceListener deviceListener = new InnerDeviceListener();
private LinkListener linkListener = new InnerLinkListener();
private EventAccumulator accumulator;
private ExecutorService executor;
/**
* Creates a provider with the supplier identifier.
*/
public SimpleTopologyProvider() {
super(new ProviderId("org.onlab.onos.provider.topology"));
}
@Activate
public synchronized void activate() {
executor = newFixedThreadPool(MAX_THREADS, namedThreads("topo-compute-%d"));
accumulator = new TopologyChangeAccumulator();
providerService = providerRegistry.register(this);
deviceService.addListener(deviceListener);
linkService.addListener(linkListener);
isStarted = true;
triggerTopologyBuild(null);
log.info("Started");
}
@Deactivate
public synchronized void deactivate() {
deviceService.removeListener(deviceListener);
linkService.removeListener(linkListener);
providerRegistry.unregister(this);
providerService = null;
executor.shutdownNow();
executor = null;
isStarted = false;
log.info("Stopped");
}
/**
* Triggers assembly of topology data citing the specified events as the
* reason.
*
* @param reasons events which triggered the topology change
*/
private void triggerTopologyBuild(List<Event> reasons) {
executor.execute(new TopologyBuilderTask(reasons));
}
// Builds the topology using the latest device and link information
// and citing the specified events as reasons for the change.
private void buildTopology(List<Event> reasons) {
log.info("YO! Computing topology");
if (isStarted) {
TopologyDescription desc =
new DefaultTopologyDescription(System.nanoTime(),
deviceService.getDevices(),
linkService.getLinks());
providerService.topologyChanged(desc, reasons);
}
}
// Callback for device events
private class InnerDeviceListener implements DeviceListener {
@Override
public void event(DeviceEvent event) {
DeviceEvent.Type type = event.type();
if (type == DEVICE_ADDED || type == DEVICE_REMOVED ||
type == DEVICE_AVAILABILITY_CHANGED) {
accumulator.add(event);
}
}
}
// Callback for link events
private class InnerLinkListener implements LinkListener {
@Override
public void event(LinkEvent event) {
accumulator.add(event);
}
}
// Event accumulator for paced triggering of topology assembly.
private class TopologyChangeAccumulator
extends AbstractEventAccumulator implements EventAccumulator {
TopologyChangeAccumulator() {
super(TIMER, MAX_EVENTS, MAX_BATCH_MS, MAX_IDLE_MS);
}
@Override
public void processEvents(List<Event> events) {
triggerTopologyBuild(events);
}
}
// Task for building topology data in a separate thread.
private class TopologyBuilderTask implements Runnable {
private final List<Event> reasons;
public TopologyBuilderTask(List<Event> reasons) {
this.reasons = reasons;
}
@Override
public void run() {
buildTopology(reasons);
}
}
}
package org.onlab.onos.net.trivial.impl;
import org.onlab.graph.Graph;
import org.onlab.onos.event.Event;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Path;
import org.onlab.onos.net.topology.LinkWeight;
import org.onlab.onos.net.topology.TopoEdge;
import org.onlab.onos.net.topology.TopoVertex;
import org.onlab.onos.net.topology.Topology;
import org.onlab.onos.net.topology.TopologyCluster;
import org.onlab.onos.net.topology.TopologyDescription;
import org.onlab.onos.net.topology.TopologyEvent;
import java.util.List;
import java.util.Set;
/**
* Manages inventory of topology snapshots using trivial in-memory
* implementation.
* structures implementation.
*/
class SimpleTopologyStore {
private volatile DefaultTopology current;
/**
* Returns the current topology snapshot.
*
* @return current topology descriptor
*/
Topology currentTopology() {
return current;
}
/**
* Indicates whether the topology is the latest.
*
* @param topology topology descriptor
* @return true if topology is the most recent one
*/
boolean isLatest(Topology topology) {
// Topology is current only if it is the same as our current topology
return topology == current;
}
/**
* Returns the set of topology SCC clusters.
*
* @param topology topology descriptor
* @return set of clusters
*/
Set<TopologyCluster> getClusters(Topology topology) {
return null;
}
/**
* Returns the immutable graph view of the current topology.
*
* @param topology topology descriptor
* @return graph view
*/
public class SimpleTopologyStore {
Graph<TopoVertex, TopoEdge> getGraph(Topology topology) {
return null;
}
/**
* Returns the set of pre-computed shortest paths between src and dest.
*
* @param topology topology descriptor
* @param src source device
* @param dst destination device
* @return set of shortest paths
*/
Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst) {
return null;
}
/**
* Computes and returns the set of shortest paths between src and dest.
*
* @param topology topology descriptor
* @param src source device
* @param dst destination device
* @param weight link weight function
* @return set of shortest paths
*/
Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst,
LinkWeight weight) {
return null;
}
/**
* Indicates whether the given connect point is part of the network fabric.
*
* @param topology topology descriptor
* @param connectPoint connection point
* @return true if infrastructure; false otherwise
*/
boolean isInfrastructure(Topology topology, ConnectPoint connectPoint) {
return false;
}
/**
* Indicates whether the given connect point is part of the broadcast tree.
*
* @param topology topology descriptor
* @param connectPoint connection point
* @return true if in broadcast tree; false otherwise
*/
boolean isInBroadcastTree(Topology topology, ConnectPoint connectPoint) {
return false;
}
/**
* Generates a new topology snapshot from the specified description.
*
* @param topoDescription topology description
* @param reasons list of events that triggered the update
* @return topology update event or null if the description is old
*/
TopologyEvent updateTopology(TopologyDescription topoDescription,
List<Event> reasons) {
return null;
}
}
......
package org.onlab.onos.event.impl;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.onlab.onos.event.AbstractEvent;
import org.onlab.onos.event.EventSink;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
/**
* Test of the even dispatcher mechanism.
*/
public class SimpleEventDispatcherTest {
private final SimpleEventDispatcher dispatcher = new SimpleEventDispatcher();
private final PrickleSink prickleSink = new PrickleSink();
private final GooSink gooSink = new GooSink();
@Before
public void setUp() {
dispatcher.activate();
dispatcher.addSink(Prickle.class, prickleSink);
dispatcher.addSink(Goo.class, gooSink);
}
@After
public void tearDown() {
dispatcher.removeSink(Goo.class);
dispatcher.removeSink(Prickle.class);
dispatcher.deactivate();
}
@Test
public void post() throws Exception {
prickleSink.latch = new CountDownLatch(1);
dispatcher.post(new Prickle("yo"));
prickleSink.latch.await(100, TimeUnit.MILLISECONDS);
validate(prickleSink, "yo");
validate(gooSink);
}
@Test
public void postEventWithBadSink() throws Exception {
gooSink.latch = new CountDownLatch(1);
dispatcher.post(new Goo("boom"));
gooSink.latch.await(100, TimeUnit.MILLISECONDS);
validate(gooSink, "boom");
validate(prickleSink);
}
@Test
public void postEventWithNoSink() throws Exception {
dispatcher.post(new Thing("boom"));
validate(gooSink);
validate(prickleSink);
}
private void validate(Sink sink, String... strings) {
int i = 0;
assertEquals("incorrect event count", strings.length, sink.subjects.size());
for (String string : strings) {
assertEquals("incorrect event", string, sink.subjects.get(i++));
}
}
private enum Type { FOO };
private static class Thing extends AbstractEvent<Type, String> {
protected Thing(String subject) {
super(Type.FOO, subject);
}
}
private static class Prickle extends Thing {
protected Prickle(String subject) {
super(subject);
}
}
private static class Goo extends Thing {
protected Goo(String subject) {
super(subject);
}
}
private static class Sink {
final List<String> subjects = new ArrayList<>();
CountDownLatch latch;
protected void process(String subject) {
subjects.add(subject);
latch.countDown();
}
}
private static class PrickleSink extends Sink implements EventSink<Prickle> {
@Override
public void process(Prickle event) {
process(event.subject());
}
}
private static class GooSink extends Sink implements EventSink<Goo> {
@Override
public void process(Goo event) {
process(event.subject());
throw new IllegalStateException("BOOM!");
}
}
}
......@@ -15,7 +15,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
* @param <V> vertex type
* @param <E> edge type
*/
public class AdjacencyListsGraph<V extends Vertex, E extends Edge<V>> implements Graph<V, E> {
public class AdjacencyListsGraph<V extends Vertex, E extends Edge<V>>
implements Graph<V, E> {
private final Set<V> vertexes;
private final Set<E> edges;
......
package org.onlab.graph;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Tarjan algorithm for searching a graph and producing results describing
* the graph SCC (strongly-connected components).
*/
public class TarjanGraphSearch<V extends Vertex, E extends Edge<V>>
implements GraphSearch<V, E> {
/**
* {@inheritDoc}
* <p/>
* This implementation produces results augmented with information on
* SCCs within the graph.
* <p/>
* To prevent traversal of an edge, the {@link EdgeWeight#weight} should
* return a negative value as an edge weight.
*/
@Override
public SCCResult<V, E> search(Graph<V, E> graph, EdgeWeight<V, E> weight) {
SCCResult<V, E> result = new SCCResult<>(graph);
for (V vertex : graph.getVertexes()) {
VertexData data = result.data(vertex);
if (data == null) {
connect(graph, vertex, weight, result);
}
}
return result.build();
}
/**
* Scans the specified graph, using recursion, and produces SCC results.
*
* @param graph graph to search
* @param vertex current vertex to scan and connect
* @param weight optional edge weight
* @param result graph search result
* @return augmentation vertexData for the current vertex
*/
private VertexData<V> connect(Graph<V, E> graph, V vertex,
EdgeWeight<V, E> weight,
SCCResult<V, E> result) {
VertexData<V> data = result.addData(vertex);
// Scan through all egress edges of the current vertex.
for (E edge : graph.getEdgesFrom(vertex)) {
V nextVertex = edge.dst();
// If edge weight is negative, skip it.
if (weight != null && weight.weight(edge) < 0) {
continue;
}
// Attempt to get the augmentation vertexData for the next vertex.
VertexData<V> nextData = result.data(nextVertex);
if (nextData == null) {
// Next vertex has not been visited yet, so do this now.
nextData = connect(graph, nextVertex, weight, result);
data.lowLink = Math.min(data.lowLink, nextData.lowLink);
} else if (result.visited(nextData)) {
// Next vertex has been visited, which means it is in the
// same cluster as the current vertex.
data.lowLink = Math.min(data.lowLink, nextData.index);
}
}
if (data.lowLink == data.index) {
result.addCluster(data);
}
return data;
}
/**
* Graph search result augmented with SCC vertexData.
*/
public static final class SCCResult<V extends Vertex, E extends Edge<V>>
implements Result {
private final Graph<V, E> graph;
private List<Set<V>> clusterVertexes = new ArrayList<>();
private List<Set<E>> clusterEdges = new ArrayList<>();
private int index = 0;
private final Map<V, VertexData<V>> vertexData = new HashMap<>();
private final List<VertexData<V>> visited = new ArrayList<>();
private SCCResult(Graph<V, E> graph) {
this.graph = graph;
}
/**
* Returns the number of SCC clusters in the graph.
*
* @return number of clusters
*/
public int clusterCount() {
return clusterEdges.size();
}
/**
* Returns the list of strongly connected vertex clusters.
*
* @return list of strongly connected vertex sets
*/
public List<Set<V>> clusterVertexes() {
return clusterVertexes;
}
/**
* Returns the list of edges linking strongly connected vertex clusters.
*
* @return list of strongly connected edge sets
*/
public List<Set<E>> clusterEdges() {
return clusterEdges;
}
// Gets the augmentation vertexData for the specified vertex
private VertexData<V> data(V vertex) {
return vertexData.get(vertex);
}
// Adds augmentation vertexData for the specified vertex
private VertexData<V> addData(V vertex) {
VertexData<V> d = new VertexData<>(vertex, index);
vertexData.put(vertex, d);
visited.add(0, d);
index++;
return d;
}
// Indicates whether the given vertex has been visited
private boolean visited(VertexData data) {
return visited.contains(data);
}
// Adds a new cluster for the specified vertex
private void addCluster(VertexData data) {
Set<V> vertexes = findClusterVertices(data);
clusterVertexes.add(vertexes);
clusterEdges.add(findClusterEdges(vertexes));
}
private Set<V> findClusterVertices(VertexData data) {
VertexData<V> nextVertexData;
Set<V> vertexes = new HashSet<>();
do {
nextVertexData = visited.remove(0);
vertexes.add(nextVertexData.vertex);
} while (data != nextVertexData);
return Collections.unmodifiableSet(vertexes);
}
private Set<E> findClusterEdges(Set<V> vertexes) {
Set<E> edges = new HashSet<>();
for (V vertex : vertexes) {
for (E edge : graph.getEdgesFrom(vertex)) {
if (vertexes.contains((edge.dst()))) {
edges.add(edge);
}
}
}
return Collections.unmodifiableSet(edges);
}
public SCCResult<V, E> build() {
clusterVertexes = Collections.unmodifiableList(clusterVertexes);
clusterEdges = Collections.unmodifiableList(clusterEdges);
return this;
}
}
// Augments the vertex to assist in determining SCC clusters.
private static final class VertexData<V extends Vertex> {
final V vertex;
int index;
int lowLink;
private VertexData(V vertex, int index) {
this.vertex = vertex;
this.index = index;
this.lowLink = index;
}
}
}
......@@ -31,17 +31,17 @@ public class BellmanFordGraphSearchTest extends BreadthFirstSearchTest {
@Test
public void searchGraphWithNegativeCycles() {
Set<TestVertex> vertexes = new HashSet<>(vertices());
Set<TestVertex> vertexes = new HashSet<>(vertexes());
vertexes.add(Z);
Set<TestEdge> edges = new HashSet<>(edges());
edges.add(new TestEdge(G, Z, 1.0));
edges.add(new TestEdge(Z, G, -2.0));
g = new AdjacencyListsGraph<>(vertexes, edges);
graph = new AdjacencyListsGraph<>(vertexes, edges);
GraphPathSearch<TestVertex, TestEdge> search = graphSearch();
Set<Path<TestVertex, TestEdge>> paths = search.search(g, A, H, weight).paths();
Set<Path<TestVertex, TestEdge>> paths = search.search(graph, A, H, weight).paths();
assertEquals("incorrect paths count", 1, paths.size());
Path p = paths.iterator().next();
......@@ -50,10 +50,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(g, A, G, weight).paths();
paths = search.search(graph, A, G, weight).paths();
assertEquals("incorrect paths count", 0, paths.size());
paths = search.search(g, A, null, weight).paths();
paths = search.search(graph, A, null, weight).paths();
printPaths(paths);
assertEquals("incorrect paths count", 6, paths.size());
}
......
......@@ -29,10 +29,10 @@ public class BreadthFirstSearchTest extends AbstractGraphPathSearchTest {
// Executes the default test
protected void executeDefaultTest(int pathCount, int pathLength, double pathCost) {
g = new AdjacencyListsGraph<>(vertices(), edges());
graph = new AdjacencyListsGraph<>(vertexes(), edges());
GraphPathSearch<TestVertex, TestEdge> search = graphSearch();
Set<Path<TestVertex, TestEdge>> paths = search.search(g, A, H, weight).paths();
Set<Path<TestVertex, TestEdge>> paths = search.search(graph, A, H, weight).paths();
assertEquals("incorrect paths count", 1, paths.size());
Path p = paths.iterator().next();
......@@ -41,7 +41,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(g, A, null, weight).paths();
paths = search.search(graph, A, null, weight).paths();
printPaths(paths);
assertEquals("incorrect paths count", pathCount, paths.size());
}
......
......@@ -33,11 +33,11 @@ public class DepthFirstSearchTest extends AbstractGraphPathSearchTest {
protected void executeDefaultTest(int minLength, int maxLength,
double minCost, double maxCost) {
g = new AdjacencyListsGraph<>(vertices(), edges());
graph = new AdjacencyListsGraph<>(vertexes(), edges());
DepthFirstSearch<TestVertex, TestEdge> search = graphSearch();
DepthFirstSearch<TestVertex, TestEdge>.SpanningTreeResult result =
search.search(g, A, H, weight);
search.search(graph, A, H, weight);
Set<Path<TestVertex, TestEdge>> paths = result.paths();
assertEquals("incorrect path count", 1, paths.size());
......@@ -57,12 +57,12 @@ public class DepthFirstSearchTest extends AbstractGraphPathSearchTest {
}
public void executeBroadSearch() {
g = new AdjacencyListsGraph<>(vertices(), edges());
graph = new AdjacencyListsGraph<>(vertexes(), edges());
DepthFirstSearch<TestVertex, TestEdge> search = graphSearch();
// Perform narrow path search to a specific destination.
DepthFirstSearch<TestVertex, TestEdge>.SpanningTreeResult result =
search.search(g, A, null, weight);
search.search(graph, A, null, weight);
assertEquals("incorrect paths count", 7, result.paths().size());
int[] types = new int[]{0, 0, 0, 0};
......
......@@ -32,22 +32,22 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest {
@Test
public void noPath() {
g = new AdjacencyListsGraph<>(of(A, B, C, D),
graph = new AdjacencyListsGraph<>(of(A, B, C, D),
of(new TestEdge(A, B, 1),
new TestEdge(B, A, 1),
new TestEdge(C, D, 1),
new TestEdge(D, C, 1)));
GraphPathSearch<TestVertex, TestEdge> gs = graphSearch();
Set<Path<TestVertex, TestEdge>> paths = gs.search(g, A, B, weight).paths();
Set<Path<TestVertex, TestEdge>> paths = gs.search(graph, A, B, weight).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(g, A, D, weight).paths();
paths = gs.search(graph, A, D, weight).paths();
printPaths(paths);
assertEquals("incorrect paths count", 0, paths.size());
paths = gs.search(g, A, null, weight).paths();
paths = gs.search(graph, A, null, weight).paths();
printPaths(paths);
assertEquals("incorrect paths count", 1, paths.size());
assertEquals("incorrect path cost", 1.0, paths.iterator().next().cost(), 0.1);
......@@ -55,17 +55,17 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest {
@Test
public void simpleMultiplePath() {
g = new AdjacencyListsGraph<>(of(A, B, C, D),
graph = new AdjacencyListsGraph<>(of(A, B, C, D),
of(new TestEdge(A, B, 1),
new TestEdge(A, C, 1),
new TestEdge(B, D, 1),
new TestEdge(C, D, 1)));
executeSearch(graphSearch(), g, A, D, weight, 2, 2.0);
executeSearch(graphSearch(), graph, A, D, weight, 2, 2.0);
}
@Test
public void denseMultiplePath() {
g = new AdjacencyListsGraph<>(of(A, B, C, D, E, F, G),
graph = new AdjacencyListsGraph<>(of(A, B, C, D, E, F, G),
of(new TestEdge(A, B, 1),
new TestEdge(A, C, 1),
new TestEdge(B, D, 1),
......@@ -75,12 +75,12 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest {
new TestEdge(E, G, 1),
new TestEdge(F, G, 1),
new TestEdge(A, G, 4)));
executeSearch(graphSearch(), g, A, G, weight, 5, 4.0);
executeSearch(graphSearch(), graph, A, G, weight, 5, 4.0);
}
@Test
public void dualEdgeMultiplePath() {
g = new AdjacencyListsGraph<>(of(A, B, C, D, E, F, G, H),
graph = new AdjacencyListsGraph<>(of(A, B, C, D, E, F, G, H),
of(new TestEdge(A, B, 1), new TestEdge(A, C, 3),
new TestEdge(B, D, 2), new TestEdge(B, C, 1),
new TestEdge(B, E, 4), new TestEdge(C, E, 1),
......@@ -88,7 +88,7 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest {
new TestEdge(E, F, 1), new TestEdge(F, D, 1),
new TestEdge(F, G, 1), new TestEdge(F, H, 1),
new TestEdge(A, E, 3), new TestEdge(B, D, 1)));
executeSearch(graphSearch(), g, A, E, weight, 3, 3.0);
executeSearch(graphSearch(), graph, A, E, weight, 3, 3.0);
}
}
......
......@@ -19,7 +19,7 @@ public class GraphTest {
static final TestVertex H = new TestVertex("H");
static final TestVertex Z = new TestVertex("Z");
protected Graph<TestVertex, TestEdge> g;
protected Graph<TestVertex, TestEdge> graph;
protected EdgeWeight<TestVertex, TestEdge> weight =
new EdgeWeight<TestVertex, TestEdge>() {
......@@ -35,7 +35,7 @@ public class GraphTest {
}
}
protected Set<TestVertex> vertices() {
protected Set<TestVertex> vertexes() {
return of(A, B, C, D, E, F, G, H);
}
......
package org.onlab.graph;
import org.junit.Test;
import static com.google.common.collect.ImmutableSet.of;
import static org.junit.Assert.assertEquals;
import static org.onlab.graph.TarjanGraphSearch.SCCResult;
/**
* Tarjan graph search tests.
*/
public class TarjanGraphSearchTest extends GraphTest {
private void validate(SCCResult<TestVertex, TestEdge> result, int cc) {
System.out.println("Cluster count: " + result.clusterVertexes().size());
System.out.println("Clusters: " + result.clusterVertexes());
assertEquals("incorrect cluster count", cc, result.clusterCount());
}
private void validate(SCCResult<TestVertex, TestEdge> result,
int i, int vc, int ec) {
assertEquals("incorrect cluster count", vc, result.clusterVertexes().get(i).size());
assertEquals("incorrect edge count", ec, result.clusterEdges().get(i).size());
}
@Test
public void basic() {
graph = new AdjacencyListsGraph<>(vertexes(), edges());
TarjanGraphSearch<TestVertex, TestEdge> gs = new TarjanGraphSearch<>();
SCCResult<TestVertex, TestEdge> result = gs.search(graph, null);
validate(result, 6);
}
@Test
public void singleCluster() {
graph = new AdjacencyListsGraph<>(vertexes(),
of(new TestEdge(A, B, 1),
new TestEdge(B, C, 1),
new TestEdge(C, D, 1),
new TestEdge(D, E, 1),
new TestEdge(E, F, 1),
new TestEdge(F, G, 1),
new TestEdge(G, H, 1),
new TestEdge(H, A, 1)));
TarjanGraphSearch<TestVertex, TestEdge> gs = new TarjanGraphSearch<>();
SCCResult<TestVertex, TestEdge> result = gs.search(graph, null);
validate(result, 1);
validate(result, 0, 8, 8);
}
@Test
public void twoUnconnectedCluster() {
graph = new AdjacencyListsGraph<>(vertexes(),
of(new TestEdge(A, B, 1),
new TestEdge(B, C, 1),
new TestEdge(C, D, 1),
new TestEdge(D, A, 1),
new TestEdge(E, F, 1),
new TestEdge(F, G, 1),
new TestEdge(G, H, 1),
new TestEdge(H, E, 1)));
TarjanGraphSearch<TestVertex, TestEdge> gs = new TarjanGraphSearch<>();
SCCResult<TestVertex, TestEdge> result = gs.search(graph, null);
validate(result, 2);
validate(result, 0, 4, 4);
validate(result, 1, 4, 4);
}
@Test
public void twoWeaklyConnectedClusters() {
graph = new AdjacencyListsGraph<>(vertexes(),
of(new TestEdge(A, B, 1),
new TestEdge(B, C, 1),
new TestEdge(C, D, 1),
new TestEdge(D, A, 1),
new TestEdge(E, F, 1),
new TestEdge(F, G, 1),
new TestEdge(G, H, 1),
new TestEdge(H, E, 1),
new TestEdge(B, E, 1)));
TarjanGraphSearch<TestVertex, TestEdge> gs = new TarjanGraphSearch<>();
SCCResult<TestVertex, TestEdge> result = gs.search(graph, null);
validate(result, 2);
validate(result, 0, 4, 4);
validate(result, 1, 4, 4);
}
@Test
public void twoClustersConnectedWithIgnoredEdges() {
graph = new AdjacencyListsGraph<>(vertexes(),
of(new TestEdge(A, B, 1),
new TestEdge(B, C, 1),
new TestEdge(C, D, 1),
new TestEdge(D, A, 1),
new TestEdge(E, F, 1),
new TestEdge(F, G, 1),
new TestEdge(G, H, 1),
new TestEdge(H, E, 1),
new TestEdge(B, E, -1),
new TestEdge(E, B, -1)));
TarjanGraphSearch<TestVertex, TestEdge> gs = new TarjanGraphSearch<>();
SCCResult<TestVertex, TestEdge> result = gs.search(graph, weight);
validate(result, 2);
validate(result, 0, 4, 4);
validate(result, 1, 4, 4);
}
}