alshabib

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

Showing 23 changed files with 1260 additions and 103 deletions
1 package org.onlab.onos.event; 1 package org.onlab.onos.event;
2 2
3 +import static com.google.common.base.MoreObjects.toStringHelper;
4 +
3 /** 5 /**
4 * Base event implementation. 6 * Base event implementation.
5 */ 7 */
...@@ -48,4 +50,10 @@ public class AbstractEvent<T extends Enum, S extends Object> implements Event<T, ...@@ -48,4 +50,10 @@ public class AbstractEvent<T extends Enum, S extends Object> implements Event<T,
48 return subject; 50 return subject;
49 } 51 }
50 52
53 + @Override
54 + public String toString() {
55 + return toStringHelper(this).add("time", time).add("type", type())
56 + .add("subject", subject()).toString();
57 + }
58 +
51 } 59 }
......
1 +package org.onlab.onos.event;
2 +
3 +import com.google.common.collect.Lists;
4 +
5 +import java.util.List;
6 +import java.util.Timer;
7 +import java.util.TimerTask;
8 +
9 +import static com.google.common.base.Preconditions.checkArgument;
10 +import static com.google.common.base.Preconditions.checkNotNull;
11 +
12 +/**
13 + * Base implementation of an event accumulator. It allows triggering based on
14 + * event inter-arrival time threshold, maximum batch life threshold and maximum
15 + * batch size.
16 + */
17 +public abstract class AbstractEventAccumulator implements EventAccumulator {
18 +
19 + private final Timer timer;
20 + private final int maxEvents;
21 + private final int maxBatchMillis;
22 + private final int maxIdleMillis;
23 +
24 + private TimerTask idleTask = new ProcessorTask();
25 + private TimerTask maxTask = new ProcessorTask();
26 +
27 + private List<Event> events = Lists.newArrayList();
28 +
29 + /**
30 + * Creates an event accumulator capable of triggering on the specified
31 + * thresholds.
32 + *
33 + * @param timer timer to use for scheduling check-points
34 + * @param maxEvents maximum number of events to accumulate before
35 + * processing is triggered
36 + * @param maxBatchMillis maximum number of millis allowed since the first
37 + * event before processing is triggered
38 + * @param maxIdleMillis maximum number millis between events before
39 + * processing is triggered
40 + */
41 + protected AbstractEventAccumulator(Timer timer, int maxEvents,
42 + int maxBatchMillis, int maxIdleMillis) {
43 + this.timer = checkNotNull(timer, "Timer cannot be null");
44 +
45 + checkArgument(maxEvents > 1, "Maximum number of events must be > 1");
46 + checkArgument(maxBatchMillis > 0, "Maximum millis must be positive");
47 + checkArgument(maxIdleMillis > 0, "Maximum idle millis must be positive");
48 +
49 + this.maxEvents = maxEvents;
50 + this.maxBatchMillis = maxBatchMillis;
51 + this.maxIdleMillis = maxIdleMillis;
52 + }
53 +
54 + @Override
55 + public void add(Event event) {
56 + idleTask = cancelIfActive(idleTask);
57 + events.add(event);
58 +
59 + // Did we hit the max event threshold?
60 + if (events.size() == maxEvents) {
61 + maxTask = cancelIfActive(maxTask);
62 + schedule(1);
63 + } else {
64 + // Otherwise, schedule idle task and if this is a first event
65 + // also schedule the max batch age task.
66 + idleTask = schedule(maxIdleMillis);
67 + if (events.size() == 1) {
68 + maxTask = schedule(maxBatchMillis);
69 + }
70 + }
71 + }
72 +
73 + // Schedules a new processor task given number of millis in the future.
74 + private TimerTask schedule(int millis) {
75 + TimerTask task = new ProcessorTask();
76 + timer.schedule(task, millis);
77 + return task;
78 + }
79 +
80 + // Cancels the specified task if it is active.
81 + private TimerTask cancelIfActive(TimerTask task) {
82 + if (task != null) {
83 + task.cancel();
84 + }
85 + return task;
86 + }
87 +
88 + // Task for triggering processing of accumulated events
89 + private class ProcessorTask extends TimerTask {
90 + @Override
91 + public void run() {
92 + idleTask = cancelIfActive(idleTask);
93 + maxTask = cancelIfActive(maxTask);
94 + processEvents(finalizeCurrentBatch());
95 + }
96 + }
97 +
98 + // Demotes and returns the current batch of events and promotes a new one.
99 + private synchronized List<Event> finalizeCurrentBatch() {
100 + List<Event> toBeProcessed = events;
101 + events = Lists.newArrayList();
102 + return toBeProcessed;
103 + }
104 +
105 + /**
106 + * Returns the backing timer.
107 + *
108 + * @return backing timer
109 + */
110 + public Timer timer() {
111 + return timer;
112 + }
113 +
114 + /**
115 + * Returns the maximum number of events allowed to accumulate before
116 + * processing is triggered.
117 + *
118 + * @return max number of events
119 + */
120 + public int maxEvents() {
121 + return maxEvents;
122 + }
123 +
124 + /**
125 + * Returns the maximum number of millis allowed to expire since the first
126 + * event before processing is triggered.
127 + *
128 + * @return max number of millis a batch is allowed to last
129 + */
130 + public int maxBatchMillis() {
131 + return maxBatchMillis;
132 + }
133 +
134 + /**
135 + * Returns the maximum number of millis allowed to expire since the last
136 + * event arrival before processing is triggered.
137 + *
138 + * @return max number of millis since the last event
139 + */
140 + public int maxIdleMillis() {
141 + return maxIdleMillis;
142 + }
143 +}
1 +package org.onlab.onos.event;
2 +
3 +import java.util.List;
4 +
5 +/**
6 + * Abstraction of an accumulator capable of collecting events and at some
7 + * point in time triggers processing of all previously accumulated events.
8 + */
9 +public interface EventAccumulator {
10 +
11 + /**
12 + * Adds an event to the current batch. This operation may, or may not
13 + * trigger processing of the current batch of events.
14 + *
15 + * @param event event to be added to the current batch
16 + */
17 + void add(Event event);
18 +
19 + /**
20 + * Processes the specified list of accumulated events.
21 + *
22 + * @param events list of accumulated events
23 + */
24 + void processEvents(List<Event> events);
25 +
26 +}
...@@ -20,6 +20,13 @@ public interface TopologyService { ...@@ -20,6 +20,13 @@ public interface TopologyService {
20 Topology currentTopology(); 20 Topology currentTopology();
21 21
22 /** 22 /**
23 + * Indicates whether the specified topology is the latest or not.
24 + * @param topology topology descriptor
25 + * @return true if the topology is the most recent; false otherwise
26 + */
27 + boolean isLatest(Topology topology);
28 +
29 + /**
23 * Returns the set of clusters in the specified topology. 30 * Returns the set of clusters in the specified topology.
24 * 31 *
25 * @param topology topology descriptor 32 * @param topology topology descriptor
......
1 +package org.onlab.onos.net.trivial.impl;
2 +
3 +import org.onlab.onos.net.AbstractModel;
4 +import org.onlab.onos.net.provider.ProviderId;
5 +import org.onlab.onos.net.topology.Topology;
6 +
7 +/**
8 + * Default implementation of the topology descriptor. This carries the
9 + * backing topology data.
10 + */
11 +public class DefaultTopology extends AbstractModel implements Topology {
12 +
13 + private final long time;
14 + private final int clusterCount;
15 + private final int deviceCount;
16 + private final int linkCount;
17 + private final int pathCount;
18 +
19 + /**
20 + * Creates a topology descriptor attributed to the specified provider.
21 + *
22 + * @param providerId identity of the provider
23 + * @param time creation time in system nanos
24 + * @param clusterCount number of clusters
25 + * @param deviceCount number of devices
26 + * @param linkCount number of links
27 + * @param pathCount number of pre-computed paths
28 + */
29 + DefaultTopology(ProviderId providerId, long time, int clusterCount,
30 + int deviceCount, int linkCount, int pathCount) {
31 + super(providerId);
32 + this.time = time;
33 + this.clusterCount = clusterCount;
34 + this.deviceCount = deviceCount;
35 + this.linkCount = linkCount;
36 + this.pathCount = pathCount;
37 + }
38 +
39 + @Override
40 + public long time() {
41 + return time;
42 + }
43 +
44 + @Override
45 + public int clusterCount() {
46 + return clusterCount;
47 + }
48 +
49 + @Override
50 + public int deviceCount() {
51 + return deviceCount;
52 + }
53 +
54 + @Override
55 + public int linkCount() {
56 + return linkCount;
57 + }
58 +
59 + @Override
60 + public int pathCount() {
61 + return pathCount;
62 + }
63 +
64 +}
1 package org.onlab.onos.net.trivial.impl; 1 package org.onlab.onos.net.trivial.impl;
2 2
3 import com.google.common.collect.ImmutableSet; 3 import com.google.common.collect.ImmutableSet;
4 -import com.google.common.collect.Multimap; 4 +import com.google.common.collect.Maps;
5 +import com.google.common.collect.Sets;
6 +import org.onlab.graph.AdjacencyListsGraph;
7 +import org.onlab.graph.DijkstraGraphSearch;
5 import org.onlab.graph.Graph; 8 import org.onlab.graph.Graph;
6 import org.onlab.graph.GraphPathSearch; 9 import org.onlab.graph.GraphPathSearch;
10 +import org.onlab.onos.net.ConnectPoint;
11 +import org.onlab.onos.net.Device;
7 import org.onlab.onos.net.DeviceId; 12 import org.onlab.onos.net.DeviceId;
8 import org.onlab.onos.net.Link; 13 import org.onlab.onos.net.Link;
9 import org.onlab.onos.net.topology.ClusterId; 14 import org.onlab.onos.net.topology.ClusterId;
15 +import org.onlab.onos.net.topology.LinkWeight;
10 import org.onlab.onos.net.topology.TopoEdge; 16 import org.onlab.onos.net.topology.TopoEdge;
11 import org.onlab.onos.net.topology.TopoVertex; 17 import org.onlab.onos.net.topology.TopoVertex;
12 import org.onlab.onos.net.topology.TopologyCluster; 18 import org.onlab.onos.net.topology.TopologyCluster;
13 import org.onlab.onos.net.topology.TopologyDescription; 19 import org.onlab.onos.net.topology.TopologyDescription;
14 20
15 import java.util.Map; 21 import java.util.Map;
22 +import java.util.Objects;
16 import java.util.Set; 23 import java.util.Set;
17 24
25 +import static com.google.common.base.MoreObjects.toStringHelper;
26 +import static org.onlab.graph.GraphPathSearch.Result;
27 +import static org.onlab.onos.net.Link.Type.INDIRECT;
28 +
18 /** 29 /**
19 * Default implementation of an immutable topology data carrier. 30 * Default implementation of an immutable topology data carrier.
20 */ 31 */
21 -public class DefaultTopologyDescription implements TopologyDescription { 32 +class DefaultTopologyDescription implements TopologyDescription {
33 +
34 + private static final GraphPathSearch<TopoVertex, TopoEdge> DIJKSTRA =
35 + new DijkstraGraphSearch<>();
22 36
23 private final long nanos; 37 private final long nanos;
38 + private final Map<DeviceId, TopoVertex> vertexesById = Maps.newHashMap();
24 private final Graph<TopoVertex, TopoEdge> graph; 39 private final Graph<TopoVertex, TopoEdge> graph;
25 - private final Map<DeviceId, GraphPathSearch.Result<TopoVertex, TopoEdge>> results; 40 + private final Map<DeviceId, Result<TopoVertex, TopoEdge>> results;
26 private final Map<ClusterId, TopologyCluster> clusters; 41 private final Map<ClusterId, TopologyCluster> clusters;
27 - private final Multimap<ClusterId, DeviceId> clusterDevices; 42 +// private final Multimap<ClusterId, DeviceId> clusterDevices;
28 - private final Multimap<ClusterId, Link> clusterLinks; 43 +// private final Multimap<ClusterId, Link> clusterLinks;
29 - private final Map<DeviceId, TopologyCluster> deviceClusters; 44 +// private final Map<DeviceId, TopologyCluster> deviceClusters;
30 - 45 +
31 - public DefaultTopologyDescription(long nanos, Graph<TopoVertex, TopoEdge> graph, 46 +
32 - Map<DeviceId, GraphPathSearch.Result<TopoVertex, TopoEdge>> results, 47 + DefaultTopologyDescription(long nanos, Iterable<Device> devices, Iterable<Link> links) {
33 - Map<ClusterId, TopologyCluster> clusters,
34 - Multimap<ClusterId, DeviceId> clusterDevices,
35 - Multimap<ClusterId, Link> clusterLinks,
36 - Map<DeviceId, TopologyCluster> deviceClusters) {
37 this.nanos = nanos; 48 this.nanos = nanos;
38 - this.graph = graph; 49 + this.graph = buildGraph(devices, links);
39 - this.results = results; 50 + this.results = computeDefaultPaths();
40 - this.clusters = clusters; 51 + this.clusters = computeClusters();
41 - this.clusterDevices = clusterDevices; 52 +// this.clusterDevices = clusterDevices;
42 - this.clusterLinks = clusterLinks; 53 +// this.clusterLinks = clusterLinks;
43 - this.deviceClusters = deviceClusters; 54 +// this.deviceClusters = deviceClusters;
55 + }
56 +
57 + // Constructs the topology graph using the supplied devices and links.
58 + private Graph<TopoVertex, TopoEdge> buildGraph(Iterable<Device> devices,
59 + Iterable<Link> links) {
60 + Graph<TopoVertex, TopoEdge> graph =
61 + new AdjacencyListsGraph<>(buildVertexes(devices),
62 + buildEdges(links));
63 + return graph;
64 + }
65 +
66 + // Builds a set of topology vertexes from the specified list of devices
67 + private Set<TopoVertex> buildVertexes(Iterable<Device> devices) {
68 + Set<TopoVertex> vertexes = Sets.newHashSet();
69 + for (Device device : devices) {
70 + TopoVertex vertex = new TVertex(device.id());
71 + vertexesById.put(vertex.deviceId(), vertex);
72 + vertexes.add(vertex);
73 + }
74 + return vertexes;
75 + }
76 +
77 + // Builds a set of topology vertexes from the specified list of links
78 + private Set<TopoEdge> buildEdges(Iterable<Link> links) {
79 + Set<TopoEdge> edges = Sets.newHashSet();
80 + for (Link link : links) {
81 + edges.add(new TEdge(vertexOf(link.src()), vertexOf(link.dst()), link));
82 + }
83 + return edges;
84 + }
85 +
86 + // Computes the default shortest paths for all source/dest pairs using
87 + // the multi-path Dijkstra and hop-count as path cost.
88 + private Map<DeviceId, Result<TopoVertex, TopoEdge>> computeDefaultPaths() {
89 + LinkWeight weight = new HopCountLinkWeight(graph.getVertexes().size());
90 + Map<DeviceId, Result<TopoVertex, TopoEdge>> results = Maps.newHashMap();
91 +
92 + // Search graph paths for each source to all destinations.
93 + for (TopoVertex src : vertexesById.values()) {
94 + results.put(src.deviceId(), DIJKSTRA.search(graph, src, null, weight));
95 + }
96 + return results;
97 + }
98 +
99 + // Computes topology SCC clusters using Tarjan algorithm.
100 + private Map<ClusterId, TopologyCluster> computeClusters() {
101 + Map<ClusterId, TopologyCluster> clusters = Maps.newHashMap();
102 + return clusters;
103 + }
104 +
105 + // Fetches a vertex corresponding to the given connection point device.
106 + private TopoVertex vertexOf(ConnectPoint connectPoint) {
107 + DeviceId id = connectPoint.deviceId();
108 + TopoVertex vertex = vertexesById.get(id);
109 + if (vertex == null) {
110 + // If vertex does not exist, create one and register it.
111 + vertex = new TVertex(id);
112 + vertexesById.put(id, vertex);
113 + }
114 + return vertex;
44 } 115 }
45 116
46 @Override 117 @Override
...@@ -54,7 +125,7 @@ public class DefaultTopologyDescription implements TopologyDescription { ...@@ -54,7 +125,7 @@ public class DefaultTopologyDescription implements TopologyDescription {
54 } 125 }
55 126
56 @Override 127 @Override
57 - public GraphPathSearch.Result<TopoVertex, TopoEdge> pathResults(DeviceId srcDeviceId) { 128 + public Result<TopoVertex, TopoEdge> pathResults(DeviceId srcDeviceId) {
58 return results.get(srcDeviceId); 129 return results.get(srcDeviceId);
59 } 130 }
60 131
...@@ -75,6 +146,105 @@ public class DefaultTopologyDescription implements TopologyDescription { ...@@ -75,6 +146,105 @@ public class DefaultTopologyDescription implements TopologyDescription {
75 146
76 @Override 147 @Override
77 public TopologyCluster clusterFor(DeviceId deviceId) { 148 public TopologyCluster clusterFor(DeviceId deviceId) {
78 - return deviceClusters.get(deviceId); 149 + return null; // deviceClusters.get(deviceId);
150 + }
151 +
152 + // Implementation of the topology vertex backed by a device id
153 + private static class TVertex implements TopoVertex {
154 +
155 + private final DeviceId deviceId;
156 +
157 + public TVertex(DeviceId deviceId) {
158 + this.deviceId = deviceId;
159 + }
160 +
161 + @Override
162 + public DeviceId deviceId() {
163 + return deviceId;
164 + }
165 +
166 + @Override
167 + public int hashCode() {
168 + return Objects.hash(deviceId);
169 + }
170 +
171 + @Override
172 + public boolean equals(Object obj) {
173 + if (obj instanceof TVertex) {
174 + final TVertex other = (TVertex) obj;
175 + return Objects.equals(this.deviceId, other.deviceId);
176 + }
177 + return false;
178 + }
179 +
180 + @Override
181 + public String toString() {
182 + return deviceId.toString();
183 + }
184 + }
185 +
186 + // Implementation of the topology edge backed by a link
187 + private class TEdge implements TopoEdge {
188 + private final Link link;
189 + private final TopoVertex src;
190 + private final TopoVertex dst;
191 +
192 + public TEdge(TopoVertex src, TopoVertex dst, Link link) {
193 + this.src = src;
194 + this.dst = dst;
195 + this.link = link;
196 + }
197 +
198 + @Override
199 + public Link link() {
200 + return link;
201 + }
202 +
203 + @Override
204 + public TopoVertex src() {
205 + return src;
206 + }
207 +
208 + @Override
209 + public TopoVertex dst() {
210 + return dst;
211 + }
212 +
213 + @Override
214 + public int hashCode() {
215 + return Objects.hash(link);
216 + }
217 +
218 + @Override
219 + public boolean equals(Object obj) {
220 + if (obj instanceof TEdge) {
221 + final TEdge other = (TEdge) obj;
222 + return Objects.equals(this.link, other.link);
223 + }
224 + return false;
225 + }
226 +
227 + @Override
228 + public String toString() {
229 + return toStringHelper(this).add("src", src).add("dst", dst).toString();
230 + }
79 } 231 }
232 +
233 + // Link weight for measuring link cost as hop count with indirect links
234 + // being as expensive as traversing the entire graph to assume the worst.
235 + private class HopCountLinkWeight implements LinkWeight {
236 + private final int indirectLinkCost;
237 +
238 + public HopCountLinkWeight(int indirectLinkCost) {
239 + this.indirectLinkCost = indirectLinkCost;
240 + }
241 +
242 + @Override
243 + public double weight(TopoEdge edge) {
244 + // To force preference to use direct paths first, make indirect
245 + // links as expensive as the linear vertex traversal.
246 + return edge.link().type() == INDIRECT ? indirectLinkCost : 1;
247 + }
248 + }
249 +
80 } 250 }
......
...@@ -150,7 +150,8 @@ public class SimpleDeviceManager ...@@ -150,7 +150,8 @@ public class SimpleDeviceManager
150 } 150 }
151 151
152 // Personalized device provider service issued to the supplied provider. 152 // Personalized device provider service issued to the supplied provider.
153 - private class InternalDeviceProviderService extends AbstractProviderService<DeviceProvider> 153 + private class InternalDeviceProviderService
154 + extends AbstractProviderService<DeviceProvider>
154 implements DeviceProviderService { 155 implements DeviceProviderService {
155 156
156 InternalDeviceProviderService(DeviceProvider provider) { 157 InternalDeviceProviderService(DeviceProvider provider) {
......
...@@ -28,7 +28,8 @@ import static com.google.common.base.Preconditions.checkArgument; ...@@ -28,7 +28,8 @@ import static com.google.common.base.Preconditions.checkArgument;
28 import static org.onlab.onos.net.device.DeviceEvent.Type.*; 28 import static org.onlab.onos.net.device.DeviceEvent.Type.*;
29 29
30 /** 30 /**
31 - 31 + * Manages inventory of infrastructure DEVICES using trivial in-memory
32 + * structures implementation.
32 */ 33 */
33 class SimpleDeviceStore { 34 class SimpleDeviceStore {
34 35
......
...@@ -158,7 +158,7 @@ public class SimpleHostManager ...@@ -158,7 +158,7 @@ public class SimpleHostManager
158 158
159 // Posts the specified event to the local event dispatcher. 159 // Posts the specified event to the local event dispatcher.
160 private void post(HostEvent event) { 160 private void post(HostEvent event) {
161 - if (event != null && eventDispatcher != null) { 161 + if (event != null) {
162 eventDispatcher.post(event); 162 eventDispatcher.post(event);
163 } 163 }
164 } 164 }
......
...@@ -36,8 +36,8 @@ import com.google.common.collect.Sets; ...@@ -36,8 +36,8 @@ import com.google.common.collect.Sets;
36 @Component(immediate = true) 36 @Component(immediate = true)
37 @Service 37 @Service
38 public class SimpleLinkManager 38 public class SimpleLinkManager
39 -extends AbstractProviderRegistry<LinkProvider, LinkProviderService> 39 + extends AbstractProviderRegistry<LinkProvider, LinkProviderService>
40 -implements LinkService, LinkAdminService, LinkProviderRegistry { 40 + implements LinkService, LinkAdminService, LinkProviderRegistry {
41 41
42 private static final String DEVICE_ID_NULL = "Device ID cannot be null"; 42 private static final String DEVICE_ID_NULL = "Device ID cannot be null";
43 private static final String LINK_DESC_NULL = "Link description cannot be null"; 43 private static final String LINK_DESC_NULL = "Link description cannot be null";
...@@ -46,7 +46,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry { ...@@ -46,7 +46,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry {
46 private final Logger log = getLogger(getClass()); 46 private final Logger log = getLogger(getClass());
47 47
48 private final AbstractListenerRegistry<LinkEvent, LinkListener> 48 private final AbstractListenerRegistry<LinkEvent, LinkListener>
49 - listenerRegistry = new AbstractListenerRegistry<>(); 49 + listenerRegistry = new AbstractListenerRegistry<>();
50 50
51 private final SimpleLinkStore store = new SimpleLinkStore(); 51 private final SimpleLinkStore store = new SimpleLinkStore();
52 52
...@@ -79,7 +79,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry { ...@@ -79,7 +79,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry {
79 public Set<Link> getDeviceLinks(DeviceId deviceId) { 79 public Set<Link> getDeviceLinks(DeviceId deviceId) {
80 checkNotNull(deviceId, DEVICE_ID_NULL); 80 checkNotNull(deviceId, DEVICE_ID_NULL);
81 return Sets.union(store.getDeviceEgressLinks(deviceId), 81 return Sets.union(store.getDeviceEgressLinks(deviceId),
82 - store.getDeviceIngressLinks(deviceId)); 82 + store.getDeviceIngressLinks(deviceId));
83 } 83 }
84 84
85 @Override 85 @Override
...@@ -98,7 +98,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry { ...@@ -98,7 +98,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry {
98 public Set<Link> getLinks(ConnectPoint connectPoint) { 98 public Set<Link> getLinks(ConnectPoint connectPoint) {
99 checkNotNull(connectPoint, CONNECT_POINT_NULL); 99 checkNotNull(connectPoint, CONNECT_POINT_NULL);
100 return Sets.union(store.getEgressLinks(connectPoint), 100 return Sets.union(store.getEgressLinks(connectPoint),
101 - store.getIngressLinks(connectPoint)); 101 + store.getIngressLinks(connectPoint));
102 } 102 }
103 103
104 @Override 104 @Override
...@@ -146,8 +146,9 @@ implements LinkService, LinkAdminService, LinkProviderRegistry { ...@@ -146,8 +146,9 @@ implements LinkService, LinkAdminService, LinkProviderRegistry {
146 } 146 }
147 147
148 // Personalized link provider service issued to the supplied provider. 148 // Personalized link provider service issued to the supplied provider.
149 - private class InternalLinkProviderService extends AbstractProviderService<LinkProvider> 149 + private class InternalLinkProviderService
150 - implements LinkProviderService { 150 + extends AbstractProviderService<LinkProvider>
151 + implements LinkProviderService {
151 152
152 InternalLinkProviderService(LinkProvider provider) { 153 InternalLinkProviderService(LinkProvider provider) {
153 super(provider); 154 super(provider);
...@@ -157,27 +158,31 @@ implements LinkService, LinkAdminService, LinkProviderRegistry { ...@@ -157,27 +158,31 @@ implements LinkService, LinkAdminService, LinkProviderRegistry {
157 public void linkDetected(LinkDescription linkDescription) { 158 public void linkDetected(LinkDescription linkDescription) {
158 checkNotNull(linkDescription, LINK_DESC_NULL); 159 checkNotNull(linkDescription, LINK_DESC_NULL);
159 checkValidity(); 160 checkValidity();
160 - log.debug("Link {} detected", linkDescription);
161 LinkEvent event = store.createOrUpdateLink(provider().id(), 161 LinkEvent event = store.createOrUpdateLink(provider().id(),
162 - linkDescription); 162 + linkDescription);
163 - post(event); 163 + if (event != null) {
164 + log.debug("Link {} detected", linkDescription);
165 + post(event);
166 + }
164 } 167 }
165 168
166 @Override 169 @Override
167 public void linkVanished(LinkDescription linkDescription) { 170 public void linkVanished(LinkDescription linkDescription) {
168 checkNotNull(linkDescription, LINK_DESC_NULL); 171 checkNotNull(linkDescription, LINK_DESC_NULL);
169 checkValidity(); 172 checkValidity();
170 - log.info("Link {} vanished", linkDescription);
171 LinkEvent event = store.removeLink(linkDescription.src(), 173 LinkEvent event = store.removeLink(linkDescription.src(),
172 - linkDescription.dst()); 174 + linkDescription.dst());
173 - post(event); 175 + if (event != null) {
176 + log.info("Link {} vanished", linkDescription);
177 + post(event);
178 + }
174 } 179 }
175 180
176 @Override 181 @Override
177 public void linksVanished(ConnectPoint connectPoint) { 182 public void linksVanished(ConnectPoint connectPoint) {
178 checkNotNull(connectPoint, "Connect point cannot be null"); 183 checkNotNull(connectPoint, "Connect point cannot be null");
179 checkValidity(); 184 checkValidity();
180 - log.info("Link for connection point {} vanished", connectPoint); 185 + log.info("Links for connection point {} vanished", connectPoint);
181 removeLinks(getLinks(connectPoint)); 186 removeLinks(getLinks(connectPoint));
182 } 187 }
183 188
...@@ -185,7 +190,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry { ...@@ -185,7 +190,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry {
185 public void linksVanished(DeviceId deviceId) { 190 public void linksVanished(DeviceId deviceId) {
186 checkNotNull(deviceId, DEVICE_ID_NULL); 191 checkNotNull(deviceId, DEVICE_ID_NULL);
187 checkValidity(); 192 checkValidity();
188 - log.info("Link for device {} vanished", deviceId); 193 + log.info("Links for device {} vanished", deviceId);
189 removeLinks(getDeviceLinks(deviceId)); 194 removeLinks(getDeviceLinks(deviceId));
190 } 195 }
191 } 196 }
...@@ -200,7 +205,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry { ...@@ -200,7 +205,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry {
200 205
201 // Posts the specified event to the local event dispatcher. 206 // Posts the specified event to the local event dispatcher.
202 private void post(LinkEvent event) { 207 private void post(LinkEvent event) {
203 - if (event != null && eventDispatcher != null) { 208 + if (event != null) {
204 eventDispatcher.post(event); 209 eventDispatcher.post(event);
205 } 210 }
206 } 211 }
......
...@@ -25,7 +25,7 @@ import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED; ...@@ -25,7 +25,7 @@ import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED;
25 import static org.onlab.onos.net.link.LinkEvent.Type.LINK_UPDATED; 25 import static org.onlab.onos.net.link.LinkEvent.Type.LINK_UPDATED;
26 26
27 /** 27 /**
28 - * Manages inventory of infrastructure links using trivial in-memory link 28 + * Manages inventory of infrastructure links using trivial in-memory structures
29 * implementation. 29 * implementation.
30 */ 30 */
31 class SimpleLinkStore { 31 class SimpleLinkStore {
......
...@@ -72,25 +72,26 @@ public class SimpleTopologyManager ...@@ -72,25 +72,26 @@ public class SimpleTopologyManager
72 } 72 }
73 73
74 @Override 74 @Override
75 - protected TopologyProviderService createProviderService(TopologyProvider provider) { 75 + public Topology currentTopology() {
76 - return new InternalTopologyProviderService(provider); 76 + return store.currentTopology();
77 } 77 }
78 78
79 @Override 79 @Override
80 - public Topology currentTopology() { 80 + public boolean isLatest(Topology topology) {
81 - return null; 81 + checkNotNull(topology, TOPOLOGY_NULL);
82 + return store.isLatest(topology);
82 } 83 }
83 84
84 @Override 85 @Override
85 public Set<TopologyCluster> getClusters(Topology topology) { 86 public Set<TopologyCluster> getClusters(Topology topology) {
86 checkNotNull(topology, TOPOLOGY_NULL); 87 checkNotNull(topology, TOPOLOGY_NULL);
87 - return null; 88 + return store.getClusters(topology);
88 } 89 }
89 90
90 @Override 91 @Override
91 public Graph<TopoVertex, TopoEdge> getGraph(Topology topology) { 92 public Graph<TopoVertex, TopoEdge> getGraph(Topology topology) {
92 checkNotNull(topology, TOPOLOGY_NULL); 93 checkNotNull(topology, TOPOLOGY_NULL);
93 - return null; 94 + return store.getGraph(topology);
94 } 95 }
95 96
96 @Override 97 @Override
...@@ -98,7 +99,7 @@ public class SimpleTopologyManager ...@@ -98,7 +99,7 @@ public class SimpleTopologyManager
98 checkNotNull(topology, TOPOLOGY_NULL); 99 checkNotNull(topology, TOPOLOGY_NULL);
99 checkNotNull(src, DEVICE_ID_NULL); 100 checkNotNull(src, DEVICE_ID_NULL);
100 checkNotNull(dst, DEVICE_ID_NULL); 101 checkNotNull(dst, DEVICE_ID_NULL);
101 - return null; 102 + return store.getPaths(topology, src, dst);
102 } 103 }
103 104
104 @Override 105 @Override
...@@ -107,21 +108,21 @@ public class SimpleTopologyManager ...@@ -107,21 +108,21 @@ public class SimpleTopologyManager
107 checkNotNull(src, DEVICE_ID_NULL); 108 checkNotNull(src, DEVICE_ID_NULL);
108 checkNotNull(dst, DEVICE_ID_NULL); 109 checkNotNull(dst, DEVICE_ID_NULL);
109 checkNotNull(weight, "Link weight cannot be null"); 110 checkNotNull(weight, "Link weight cannot be null");
110 - return null; 111 + return store.getPaths(topology, src, dst, weight);
111 } 112 }
112 113
113 @Override 114 @Override
114 public boolean isInfrastructure(Topology topology, ConnectPoint connectPoint) { 115 public boolean isInfrastructure(Topology topology, ConnectPoint connectPoint) {
115 checkNotNull(topology, TOPOLOGY_NULL); 116 checkNotNull(topology, TOPOLOGY_NULL);
116 checkNotNull(connectPoint, CONNECTION_POINT_NULL); 117 checkNotNull(connectPoint, CONNECTION_POINT_NULL);
117 - return false; 118 + return store.isInfrastructure(topology, connectPoint);
118 } 119 }
119 120
120 @Override 121 @Override
121 public boolean isInBroadcastTree(Topology topology, ConnectPoint connectPoint) { 122 public boolean isInBroadcastTree(Topology topology, ConnectPoint connectPoint) {
122 checkNotNull(topology, TOPOLOGY_NULL); 123 checkNotNull(topology, TOPOLOGY_NULL);
123 checkNotNull(connectPoint, CONNECTION_POINT_NULL); 124 checkNotNull(connectPoint, CONNECTION_POINT_NULL);
124 - return false; 125 + return store.isInBroadcastTree(topology, connectPoint);
125 } 126 }
126 127
127 @Override 128 @Override
...@@ -135,6 +136,11 @@ public class SimpleTopologyManager ...@@ -135,6 +136,11 @@ public class SimpleTopologyManager
135 } 136 }
136 137
137 // Personalized host provider service issued to the supplied provider. 138 // Personalized host provider service issued to the supplied provider.
139 + @Override
140 + protected TopologyProviderService createProviderService(TopologyProvider provider) {
141 + return new InternalTopologyProviderService(provider);
142 + }
143 +
138 private class InternalTopologyProviderService 144 private class InternalTopologyProviderService
139 extends AbstractProviderService<TopologyProvider> 145 extends AbstractProviderService<TopologyProvider>
140 implements TopologyProviderService { 146 implements TopologyProviderService {
...@@ -147,8 +153,15 @@ public class SimpleTopologyManager ...@@ -147,8 +153,15 @@ public class SimpleTopologyManager
147 public void topologyChanged(TopologyDescription topoDescription, 153 public void topologyChanged(TopologyDescription topoDescription,
148 List<Event> reasons) { 154 List<Event> reasons) {
149 checkNotNull(topoDescription, "Topology description cannot be null"); 155 checkNotNull(topoDescription, "Topology description cannot be null");
150 - log.info("Topology changed due to: {}", 156 +
157 + log.info("Topology changed due to: {}", // to be removed soon
151 reasons == null ? "initial compute" : reasons); 158 reasons == null ? "initial compute" : reasons);
159 + TopologyEvent event = store.updateTopology(topoDescription, reasons);
160 + if (event != null) {
161 + log.info("Topology changed due to: {}",
162 + reasons == null ? "initial compute" : reasons);
163 + eventDispatcher.post(event);
164 + }
152 } 165 }
153 } 166 }
154 167
......
1 +package org.onlab.onos.net.trivial.impl;
2 +
3 +import org.apache.felix.scr.annotations.Activate;
4 +import org.apache.felix.scr.annotations.Component;
5 +import org.apache.felix.scr.annotations.Deactivate;
6 +import org.apache.felix.scr.annotations.Reference;
7 +import org.apache.felix.scr.annotations.ReferenceCardinality;
8 +import org.onlab.onos.event.AbstractEventAccumulator;
9 +import org.onlab.onos.event.Event;
10 +import org.onlab.onos.event.EventAccumulator;
11 +import org.onlab.onos.net.device.DeviceEvent;
12 +import org.onlab.onos.net.device.DeviceListener;
13 +import org.onlab.onos.net.device.DeviceService;
14 +import org.onlab.onos.net.link.LinkEvent;
15 +import org.onlab.onos.net.link.LinkListener;
16 +import org.onlab.onos.net.link.LinkService;
17 +import org.onlab.onos.net.provider.AbstractProvider;
18 +import org.onlab.onos.net.provider.ProviderId;
19 +import org.onlab.onos.net.topology.TopologyDescription;
20 +import org.onlab.onos.net.topology.TopologyProvider;
21 +import org.onlab.onos.net.topology.TopologyProviderRegistry;
22 +import org.onlab.onos.net.topology.TopologyProviderService;
23 +import org.slf4j.Logger;
24 +
25 +import java.util.List;
26 +import java.util.Timer;
27 +import java.util.concurrent.ExecutorService;
28 +
29 +import static java.util.concurrent.Executors.newFixedThreadPool;
30 +import static org.onlab.onos.net.device.DeviceEvent.Type.*;
31 +import static org.onlab.util.Tools.namedThreads;
32 +import static org.slf4j.LoggerFactory.getLogger;
33 +
34 +/**
35 + * Simple implementation of a network topology provider/computor.
36 + */
37 +@Component(immediate = true)
38 +public class SimpleTopologyProvider extends AbstractProvider
39 + implements TopologyProvider {
40 +
41 + // TODO: make these configurable
42 + private static final int MAX_EVENTS = 100;
43 + private static final int MAX_IDLE_MS = 50;
44 + private static final int MAX_BATCH_MS = 200;
45 + private static final int MAX_THREADS = 8;
46 +
47 + // FIXME: Replace with a system-wide timer instance
48 + private static final Timer TIMER = new Timer();
49 +
50 + private final Logger log = getLogger(getClass());
51 +
52 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
53 + protected TopologyProviderRegistry providerRegistry;
54 +
55 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
56 + protected DeviceService deviceService;
57 +
58 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
59 + protected LinkService linkService;
60 +
61 + private volatile boolean isStarted = false;
62 +
63 + private TopologyProviderService providerService;
64 + private DeviceListener deviceListener = new InnerDeviceListener();
65 + private LinkListener linkListener = new InnerLinkListener();
66 +
67 + private EventAccumulator accumulator;
68 + private ExecutorService executor;
69 +
70 + /**
71 + * Creates a provider with the supplier identifier.
72 + */
73 + public SimpleTopologyProvider() {
74 + super(new ProviderId("org.onlab.onos.provider.topology"));
75 + }
76 +
77 + @Activate
78 + public synchronized void activate() {
79 + executor = newFixedThreadPool(MAX_THREADS, namedThreads("topo-compute-%d"));
80 + accumulator = new TopologyChangeAccumulator();
81 +
82 + providerService = providerRegistry.register(this);
83 + deviceService.addListener(deviceListener);
84 + linkService.addListener(linkListener);
85 +
86 + isStarted = true;
87 + triggerTopologyBuild(null);
88 + log.info("Started");
89 + }
90 +
91 + @Deactivate
92 + public synchronized void deactivate() {
93 + deviceService.removeListener(deviceListener);
94 + linkService.removeListener(linkListener);
95 + providerRegistry.unregister(this);
96 + providerService = null;
97 +
98 + executor.shutdownNow();
99 + executor = null;
100 +
101 + isStarted = false;
102 + log.info("Stopped");
103 + }
104 +
105 + /**
106 + * Triggers assembly of topology data citing the specified events as the
107 + * reason.
108 + *
109 + * @param reasons events which triggered the topology change
110 + */
111 + private void triggerTopologyBuild(List<Event> reasons) {
112 + executor.execute(new TopologyBuilderTask(reasons));
113 + }
114 +
115 + // Builds the topology using the latest device and link information
116 + // and citing the specified events as reasons for the change.
117 + private void buildTopology(List<Event> reasons) {
118 + log.info("YO! Computing topology");
119 + if (isStarted) {
120 + TopologyDescription desc =
121 + new DefaultTopologyDescription(System.nanoTime(),
122 + deviceService.getDevices(),
123 + linkService.getLinks());
124 + providerService.topologyChanged(desc, reasons);
125 + }
126 + }
127 +
128 + // Callback for device events
129 + private class InnerDeviceListener implements DeviceListener {
130 + @Override
131 + public void event(DeviceEvent event) {
132 + DeviceEvent.Type type = event.type();
133 + if (type == DEVICE_ADDED || type == DEVICE_REMOVED ||
134 + type == DEVICE_AVAILABILITY_CHANGED) {
135 + accumulator.add(event);
136 + }
137 + }
138 + }
139 +
140 + // Callback for link events
141 + private class InnerLinkListener implements LinkListener {
142 + @Override
143 + public void event(LinkEvent event) {
144 + accumulator.add(event);
145 + }
146 + }
147 +
148 + // Event accumulator for paced triggering of topology assembly.
149 + private class TopologyChangeAccumulator
150 + extends AbstractEventAccumulator implements EventAccumulator {
151 +
152 + TopologyChangeAccumulator() {
153 + super(TIMER, MAX_EVENTS, MAX_BATCH_MS, MAX_IDLE_MS);
154 + }
155 +
156 + @Override
157 + public void processEvents(List<Event> events) {
158 + triggerTopologyBuild(events);
159 + }
160 +
161 + }
162 +
163 + // Task for building topology data in a separate thread.
164 + private class TopologyBuilderTask implements Runnable {
165 + private final List<Event> reasons;
166 +
167 + public TopologyBuilderTask(List<Event> reasons) {
168 + this.reasons = reasons;
169 + }
170 +
171 + @Override
172 + public void run() {
173 + buildTopology(reasons);
174 + }
175 + }
176 +
177 +}
1 package org.onlab.onos.net.trivial.impl; 1 package org.onlab.onos.net.trivial.impl;
2 2
3 +import org.onlab.graph.Graph;
4 +import org.onlab.onos.event.Event;
5 +import org.onlab.onos.net.ConnectPoint;
6 +import org.onlab.onos.net.DeviceId;
7 +import org.onlab.onos.net.Path;
8 +import org.onlab.onos.net.topology.LinkWeight;
9 +import org.onlab.onos.net.topology.TopoEdge;
10 +import org.onlab.onos.net.topology.TopoVertex;
11 +import org.onlab.onos.net.topology.Topology;
12 +import org.onlab.onos.net.topology.TopologyCluster;
13 +import org.onlab.onos.net.topology.TopologyDescription;
14 +import org.onlab.onos.net.topology.TopologyEvent;
15 +
16 +import java.util.List;
17 +import java.util.Set;
18 +
3 /** 19 /**
4 * Manages inventory of topology snapshots using trivial in-memory 20 * Manages inventory of topology snapshots using trivial in-memory
5 - * implementation. 21 + * structures implementation.
6 */ 22 */
7 -public class SimpleTopologyStore { 23 +class SimpleTopologyStore {
24 +
25 + private volatile DefaultTopology current;
26 +
27 + /**
28 + * Returns the current topology snapshot.
29 + *
30 + * @return current topology descriptor
31 + */
32 + Topology currentTopology() {
33 + return current;
34 + }
35 +
36 + /**
37 + * Indicates whether the topology is the latest.
38 + *
39 + * @param topology topology descriptor
40 + * @return true if topology is the most recent one
41 + */
42 + boolean isLatest(Topology topology) {
43 + // Topology is current only if it is the same as our current topology
44 + return topology == current;
45 + }
46 +
47 + /**
48 + * Returns the set of topology SCC clusters.
49 + *
50 + * @param topology topology descriptor
51 + * @return set of clusters
52 + */
53 + Set<TopologyCluster> getClusters(Topology topology) {
54 + return null;
55 + }
56 +
57 + /**
58 + * Returns the immutable graph view of the current topology.
59 + *
60 + * @param topology topology descriptor
61 + * @return graph view
62 + */
63 + Graph<TopoVertex, TopoEdge> getGraph(Topology topology) {
64 + return null;
65 + }
66 +
67 + /**
68 + * Returns the set of pre-computed shortest paths between src and dest.
69 + *
70 + * @param topology topology descriptor
71 + * @param src source device
72 + * @param dst destination device
73 + * @return set of shortest paths
74 + */
75 + Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst) {
76 + return null;
77 + }
78 +
79 + /**
80 + * Computes and returns the set of shortest paths between src and dest.
81 + *
82 + * @param topology topology descriptor
83 + * @param src source device
84 + * @param dst destination device
85 + * @param weight link weight function
86 + * @return set of shortest paths
87 + */
88 + Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst,
89 + LinkWeight weight) {
90 + return null;
91 + }
92 +
93 + /**
94 + * Indicates whether the given connect point is part of the network fabric.
95 + *
96 + * @param topology topology descriptor
97 + * @param connectPoint connection point
98 + * @return true if infrastructure; false otherwise
99 + */
100 + boolean isInfrastructure(Topology topology, ConnectPoint connectPoint) {
101 + return false;
102 + }
103 +
104 + /**
105 + * Indicates whether the given connect point is part of the broadcast tree.
106 + *
107 + * @param topology topology descriptor
108 + * @param connectPoint connection point
109 + * @return true if in broadcast tree; false otherwise
110 + */
111 + boolean isInBroadcastTree(Topology topology, ConnectPoint connectPoint) {
112 + return false;
113 + }
114 +
115 + /**
116 + * Generates a new topology snapshot from the specified description.
117 + *
118 + * @param topoDescription topology description
119 + * @param reasons list of events that triggered the update
120 + * @return topology update event or null if the description is old
121 + */
122 + TopologyEvent updateTopology(TopologyDescription topoDescription,
123 + List<Event> reasons) {
124 + return null;
125 + }
126 +
8 } 127 }
......
1 +package org.onlab.onos.event.impl;
2 +
3 +import org.junit.After;
4 +import org.junit.Before;
5 +import org.junit.Test;
6 +import org.onlab.onos.event.AbstractEvent;
7 +import org.onlab.onos.event.EventSink;
8 +
9 +import java.util.ArrayList;
10 +import java.util.List;
11 +import java.util.concurrent.CountDownLatch;
12 +import java.util.concurrent.TimeUnit;
13 +
14 +import static org.junit.Assert.assertEquals;
15 +
16 +/**
17 + * Test of the even dispatcher mechanism.
18 + */
19 +public class SimpleEventDispatcherTest {
20 +
21 + private final SimpleEventDispatcher dispatcher = new SimpleEventDispatcher();
22 + private final PrickleSink prickleSink = new PrickleSink();
23 + private final GooSink gooSink = new GooSink();
24 +
25 + @Before
26 + public void setUp() {
27 + dispatcher.activate();
28 + dispatcher.addSink(Prickle.class, prickleSink);
29 + dispatcher.addSink(Goo.class, gooSink);
30 + }
31 +
32 + @After
33 + public void tearDown() {
34 + dispatcher.removeSink(Goo.class);
35 + dispatcher.removeSink(Prickle.class);
36 + dispatcher.deactivate();
37 + }
38 +
39 + @Test
40 + public void post() throws Exception {
41 + prickleSink.latch = new CountDownLatch(1);
42 + dispatcher.post(new Prickle("yo"));
43 + prickleSink.latch.await(100, TimeUnit.MILLISECONDS);
44 + validate(prickleSink, "yo");
45 + validate(gooSink);
46 + }
47 +
48 + @Test
49 + public void postEventWithBadSink() throws Exception {
50 + gooSink.latch = new CountDownLatch(1);
51 + dispatcher.post(new Goo("boom"));
52 + gooSink.latch.await(100, TimeUnit.MILLISECONDS);
53 + validate(gooSink, "boom");
54 + validate(prickleSink);
55 + }
56 +
57 + @Test
58 + public void postEventWithNoSink() throws Exception {
59 + dispatcher.post(new Thing("boom"));
60 + validate(gooSink);
61 + validate(prickleSink);
62 + }
63 +
64 + private void validate(Sink sink, String... strings) {
65 + int i = 0;
66 + assertEquals("incorrect event count", strings.length, sink.subjects.size());
67 + for (String string : strings) {
68 + assertEquals("incorrect event", string, sink.subjects.get(i++));
69 + }
70 + }
71 +
72 + private enum Type { FOO };
73 +
74 + private static class Thing extends AbstractEvent<Type, String> {
75 + protected Thing(String subject) {
76 + super(Type.FOO, subject);
77 + }
78 + }
79 +
80 + private static class Prickle extends Thing {
81 + protected Prickle(String subject) {
82 + super(subject);
83 + }
84 + }
85 +
86 + private static class Goo extends Thing {
87 + protected Goo(String subject) {
88 + super(subject);
89 + }
90 + }
91 +
92 + private static class Sink {
93 + final List<String> subjects = new ArrayList<>();
94 + CountDownLatch latch;
95 +
96 + protected void process(String subject) {
97 + subjects.add(subject);
98 + latch.countDown();
99 + }
100 + }
101 +
102 + private static class PrickleSink extends Sink implements EventSink<Prickle> {
103 + @Override
104 + public void process(Prickle event) {
105 + process(event.subject());
106 + }
107 + }
108 +
109 + private static class GooSink extends Sink implements EventSink<Goo> {
110 + @Override
111 + public void process(Goo event) {
112 + process(event.subject());
113 + throw new IllegalStateException("BOOM!");
114 + }
115 + }
116 +
117 +}
...@@ -15,7 +15,8 @@ import static com.google.common.base.Preconditions.checkNotNull; ...@@ -15,7 +15,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
15 * @param <V> vertex type 15 * @param <V> vertex type
16 * @param <E> edge type 16 * @param <E> edge type
17 */ 17 */
18 -public class AdjacencyListsGraph<V extends Vertex, E extends Edge<V>> implements Graph<V, E> { 18 +public class AdjacencyListsGraph<V extends Vertex, E extends Edge<V>>
19 + implements Graph<V, E> {
19 20
20 private final Set<V> vertexes; 21 private final Set<V> vertexes;
21 private final Set<E> edges; 22 private final Set<E> edges;
......
1 +package org.onlab.graph;
2 +
3 +import java.util.ArrayList;
4 +import java.util.Collections;
5 +import java.util.HashMap;
6 +import java.util.HashSet;
7 +import java.util.List;
8 +import java.util.Map;
9 +import java.util.Set;
10 +
11 +/**
12 + * Tarjan algorithm for searching a graph and producing results describing
13 + * the graph SCC (strongly-connected components).
14 + */
15 +public class TarjanGraphSearch<V extends Vertex, E extends Edge<V>>
16 + implements GraphSearch<V, E> {
17 +
18 + /**
19 + * {@inheritDoc}
20 + * <p/>
21 + * This implementation produces results augmented with information on
22 + * SCCs within the graph.
23 + * <p/>
24 + * To prevent traversal of an edge, the {@link EdgeWeight#weight} should
25 + * return a negative value as an edge weight.
26 + */
27 + @Override
28 + public SCCResult<V, E> search(Graph<V, E> graph, EdgeWeight<V, E> weight) {
29 + SCCResult<V, E> result = new SCCResult<>(graph);
30 + for (V vertex : graph.getVertexes()) {
31 + VertexData data = result.data(vertex);
32 + if (data == null) {
33 + connect(graph, vertex, weight, result);
34 + }
35 + }
36 + return result.build();
37 + }
38 +
39 + /**
40 + * Scans the specified graph, using recursion, and produces SCC results.
41 + *
42 + * @param graph graph to search
43 + * @param vertex current vertex to scan and connect
44 + * @param weight optional edge weight
45 + * @param result graph search result
46 + * @return augmentation vertexData for the current vertex
47 + */
48 + private VertexData<V> connect(Graph<V, E> graph, V vertex,
49 + EdgeWeight<V, E> weight,
50 + SCCResult<V, E> result) {
51 + VertexData<V> data = result.addData(vertex);
52 +
53 + // Scan through all egress edges of the current vertex.
54 + for (E edge : graph.getEdgesFrom(vertex)) {
55 + V nextVertex = edge.dst();
56 +
57 + // If edge weight is negative, skip it.
58 + if (weight != null && weight.weight(edge) < 0) {
59 + continue;
60 + }
61 +
62 + // Attempt to get the augmentation vertexData for the next vertex.
63 + VertexData<V> nextData = result.data(nextVertex);
64 + if (nextData == null) {
65 + // Next vertex has not been visited yet, so do this now.
66 + nextData = connect(graph, nextVertex, weight, result);
67 + data.lowLink = Math.min(data.lowLink, nextData.lowLink);
68 +
69 + } else if (result.visited(nextData)) {
70 + // Next vertex has been visited, which means it is in the
71 + // same cluster as the current vertex.
72 + data.lowLink = Math.min(data.lowLink, nextData.index);
73 + }
74 + }
75 +
76 + if (data.lowLink == data.index) {
77 + result.addCluster(data);
78 + }
79 + return data;
80 + }
81 +
82 + /**
83 + * Graph search result augmented with SCC vertexData.
84 + */
85 + public static final class SCCResult<V extends Vertex, E extends Edge<V>>
86 + implements Result {
87 +
88 + private final Graph<V, E> graph;
89 + private List<Set<V>> clusterVertexes = new ArrayList<>();
90 + private List<Set<E>> clusterEdges = new ArrayList<>();
91 +
92 + private int index = 0;
93 + private final Map<V, VertexData<V>> vertexData = new HashMap<>();
94 + private final List<VertexData<V>> visited = new ArrayList<>();
95 +
96 + private SCCResult(Graph<V, E> graph) {
97 + this.graph = graph;
98 + }
99 +
100 + /**
101 + * Returns the number of SCC clusters in the graph.
102 + *
103 + * @return number of clusters
104 + */
105 + public int clusterCount() {
106 + return clusterEdges.size();
107 + }
108 +
109 + /**
110 + * Returns the list of strongly connected vertex clusters.
111 + *
112 + * @return list of strongly connected vertex sets
113 + */
114 + public List<Set<V>> clusterVertexes() {
115 + return clusterVertexes;
116 + }
117 +
118 + /**
119 + * Returns the list of edges linking strongly connected vertex clusters.
120 + *
121 + * @return list of strongly connected edge sets
122 + */
123 + public List<Set<E>> clusterEdges() {
124 + return clusterEdges;
125 + }
126 +
127 + // Gets the augmentation vertexData for the specified vertex
128 + private VertexData<V> data(V vertex) {
129 + return vertexData.get(vertex);
130 + }
131 +
132 + // Adds augmentation vertexData for the specified vertex
133 + private VertexData<V> addData(V vertex) {
134 + VertexData<V> d = new VertexData<>(vertex, index);
135 + vertexData.put(vertex, d);
136 + visited.add(0, d);
137 + index++;
138 + return d;
139 + }
140 +
141 + // Indicates whether the given vertex has been visited
142 + private boolean visited(VertexData data) {
143 + return visited.contains(data);
144 + }
145 +
146 + // Adds a new cluster for the specified vertex
147 + private void addCluster(VertexData data) {
148 + Set<V> vertexes = findClusterVertices(data);
149 + clusterVertexes.add(vertexes);
150 + clusterEdges.add(findClusterEdges(vertexes));
151 + }
152 +
153 + private Set<V> findClusterVertices(VertexData data) {
154 + VertexData<V> nextVertexData;
155 + Set<V> vertexes = new HashSet<>();
156 + do {
157 + nextVertexData = visited.remove(0);
158 + vertexes.add(nextVertexData.vertex);
159 + } while (data != nextVertexData);
160 + return Collections.unmodifiableSet(vertexes);
161 + }
162 +
163 + private Set<E> findClusterEdges(Set<V> vertexes) {
164 + Set<E> edges = new HashSet<>();
165 + for (V vertex : vertexes) {
166 + for (E edge : graph.getEdgesFrom(vertex)) {
167 + if (vertexes.contains((edge.dst()))) {
168 + edges.add(edge);
169 + }
170 + }
171 + }
172 + return Collections.unmodifiableSet(edges);
173 + }
174 +
175 + public SCCResult<V, E> build() {
176 + clusterVertexes = Collections.unmodifiableList(clusterVertexes);
177 + clusterEdges = Collections.unmodifiableList(clusterEdges);
178 + return this;
179 + }
180 + }
181 +
182 + // Augments the vertex to assist in determining SCC clusters.
183 + private static final class VertexData<V extends Vertex> {
184 + final V vertex;
185 + int index;
186 + int lowLink;
187 +
188 + private VertexData(V vertex, int index) {
189 + this.vertex = vertex;
190 + this.index = index;
191 + this.lowLink = index;
192 + }
193 + }
194 +
195 +}
...@@ -31,17 +31,17 @@ public class BellmanFordGraphSearchTest extends BreadthFirstSearchTest { ...@@ -31,17 +31,17 @@ public class BellmanFordGraphSearchTest extends BreadthFirstSearchTest {
31 31
32 @Test 32 @Test
33 public void searchGraphWithNegativeCycles() { 33 public void searchGraphWithNegativeCycles() {
34 - Set<TestVertex> vertexes = new HashSet<>(vertices()); 34 + Set<TestVertex> vertexes = new HashSet<>(vertexes());
35 vertexes.add(Z); 35 vertexes.add(Z);
36 36
37 Set<TestEdge> edges = new HashSet<>(edges()); 37 Set<TestEdge> edges = new HashSet<>(edges());
38 edges.add(new TestEdge(G, Z, 1.0)); 38 edges.add(new TestEdge(G, Z, 1.0));
39 edges.add(new TestEdge(Z, G, -2.0)); 39 edges.add(new TestEdge(Z, G, -2.0));
40 40
41 - g = new AdjacencyListsGraph<>(vertexes, edges); 41 + graph = new AdjacencyListsGraph<>(vertexes, edges);
42 42
43 GraphPathSearch<TestVertex, TestEdge> search = graphSearch(); 43 GraphPathSearch<TestVertex, TestEdge> search = graphSearch();
44 - Set<Path<TestVertex, TestEdge>> paths = search.search(g, A, H, weight).paths(); 44 + Set<Path<TestVertex, TestEdge>> paths = search.search(graph, A, H, weight).paths();
45 assertEquals("incorrect paths count", 1, paths.size()); 45 assertEquals("incorrect paths count", 1, paths.size());
46 46
47 Path p = paths.iterator().next(); 47 Path p = paths.iterator().next();
...@@ -50,10 +50,10 @@ public class BellmanFordGraphSearchTest extends BreadthFirstSearchTest { ...@@ -50,10 +50,10 @@ public class BellmanFordGraphSearchTest extends BreadthFirstSearchTest {
50 assertEquals("incorrect path length", 5, p.edges().size()); 50 assertEquals("incorrect path length", 5, p.edges().size());
51 assertEquals("incorrect path cost", 5.0, p.cost(), 0.1); 51 assertEquals("incorrect path cost", 5.0, p.cost(), 0.1);
52 52
53 - paths = search.search(g, A, G, weight).paths(); 53 + paths = search.search(graph, A, G, weight).paths();
54 assertEquals("incorrect paths count", 0, paths.size()); 54 assertEquals("incorrect paths count", 0, paths.size());
55 55
56 - paths = search.search(g, A, null, weight).paths(); 56 + paths = search.search(graph, A, null, weight).paths();
57 printPaths(paths); 57 printPaths(paths);
58 assertEquals("incorrect paths count", 6, paths.size()); 58 assertEquals("incorrect paths count", 6, paths.size());
59 } 59 }
......
...@@ -29,10 +29,10 @@ public class BreadthFirstSearchTest extends AbstractGraphPathSearchTest { ...@@ -29,10 +29,10 @@ public class BreadthFirstSearchTest extends AbstractGraphPathSearchTest {
29 29
30 // Executes the default test 30 // Executes the default test
31 protected void executeDefaultTest(int pathCount, int pathLength, double pathCost) { 31 protected void executeDefaultTest(int pathCount, int pathLength, double pathCost) {
32 - g = new AdjacencyListsGraph<>(vertices(), edges()); 32 + graph = new AdjacencyListsGraph<>(vertexes(), edges());
33 33
34 GraphPathSearch<TestVertex, TestEdge> search = graphSearch(); 34 GraphPathSearch<TestVertex, TestEdge> search = graphSearch();
35 - Set<Path<TestVertex, TestEdge>> paths = search.search(g, A, H, weight).paths(); 35 + Set<Path<TestVertex, TestEdge>> paths = search.search(graph, A, H, weight).paths();
36 assertEquals("incorrect paths count", 1, paths.size()); 36 assertEquals("incorrect paths count", 1, paths.size());
37 37
38 Path p = paths.iterator().next(); 38 Path p = paths.iterator().next();
...@@ -41,7 +41,7 @@ public class BreadthFirstSearchTest extends AbstractGraphPathSearchTest { ...@@ -41,7 +41,7 @@ public class BreadthFirstSearchTest extends AbstractGraphPathSearchTest {
41 assertEquals("incorrect path length", pathLength, p.edges().size()); 41 assertEquals("incorrect path length", pathLength, p.edges().size());
42 assertEquals("incorrect path cost", pathCost, p.cost(), 0.1); 42 assertEquals("incorrect path cost", pathCost, p.cost(), 0.1);
43 43
44 - paths = search.search(g, A, null, weight).paths(); 44 + paths = search.search(graph, A, null, weight).paths();
45 printPaths(paths); 45 printPaths(paths);
46 assertEquals("incorrect paths count", pathCount, paths.size()); 46 assertEquals("incorrect paths count", pathCount, paths.size());
47 } 47 }
......
...@@ -33,11 +33,11 @@ public class DepthFirstSearchTest extends AbstractGraphPathSearchTest { ...@@ -33,11 +33,11 @@ public class DepthFirstSearchTest extends AbstractGraphPathSearchTest {
33 33
34 protected void executeDefaultTest(int minLength, int maxLength, 34 protected void executeDefaultTest(int minLength, int maxLength,
35 double minCost, double maxCost) { 35 double minCost, double maxCost) {
36 - g = new AdjacencyListsGraph<>(vertices(), edges()); 36 + graph = new AdjacencyListsGraph<>(vertexes(), edges());
37 DepthFirstSearch<TestVertex, TestEdge> search = graphSearch(); 37 DepthFirstSearch<TestVertex, TestEdge> search = graphSearch();
38 38
39 DepthFirstSearch<TestVertex, TestEdge>.SpanningTreeResult result = 39 DepthFirstSearch<TestVertex, TestEdge>.SpanningTreeResult result =
40 - search.search(g, A, H, weight); 40 + search.search(graph, A, H, weight);
41 Set<Path<TestVertex, TestEdge>> paths = result.paths(); 41 Set<Path<TestVertex, TestEdge>> paths = result.paths();
42 assertEquals("incorrect path count", 1, paths.size()); 42 assertEquals("incorrect path count", 1, paths.size());
43 43
...@@ -57,12 +57,12 @@ public class DepthFirstSearchTest extends AbstractGraphPathSearchTest { ...@@ -57,12 +57,12 @@ public class DepthFirstSearchTest extends AbstractGraphPathSearchTest {
57 } 57 }
58 58
59 public void executeBroadSearch() { 59 public void executeBroadSearch() {
60 - g = new AdjacencyListsGraph<>(vertices(), edges()); 60 + graph = new AdjacencyListsGraph<>(vertexes(), edges());
61 DepthFirstSearch<TestVertex, TestEdge> search = graphSearch(); 61 DepthFirstSearch<TestVertex, TestEdge> search = graphSearch();
62 62
63 // Perform narrow path search to a specific destination. 63 // Perform narrow path search to a specific destination.
64 DepthFirstSearch<TestVertex, TestEdge>.SpanningTreeResult result = 64 DepthFirstSearch<TestVertex, TestEdge>.SpanningTreeResult result =
65 - search.search(g, A, null, weight); 65 + search.search(graph, A, null, weight);
66 assertEquals("incorrect paths count", 7, result.paths().size()); 66 assertEquals("incorrect paths count", 7, result.paths().size());
67 67
68 int[] types = new int[]{0, 0, 0, 0}; 68 int[] types = new int[]{0, 0, 0, 0};
......
...@@ -32,22 +32,22 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest { ...@@ -32,22 +32,22 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest {
32 32
33 @Test 33 @Test
34 public void noPath() { 34 public void noPath() {
35 - g = new AdjacencyListsGraph<>(of(A, B, C, D), 35 + graph = new AdjacencyListsGraph<>(of(A, B, C, D),
36 - of(new TestEdge(A, B, 1), 36 + of(new TestEdge(A, B, 1),
37 - new TestEdge(B, A, 1), 37 + new TestEdge(B, A, 1),
38 - new TestEdge(C, D, 1), 38 + new TestEdge(C, D, 1),
39 - new TestEdge(D, C, 1))); 39 + new TestEdge(D, C, 1)));
40 GraphPathSearch<TestVertex, TestEdge> gs = graphSearch(); 40 GraphPathSearch<TestVertex, TestEdge> gs = graphSearch();
41 - Set<Path<TestVertex, TestEdge>> paths = gs.search(g, A, B, weight).paths(); 41 + Set<Path<TestVertex, TestEdge>> paths = gs.search(graph, A, B, weight).paths();
42 printPaths(paths); 42 printPaths(paths);
43 assertEquals("incorrect paths count", 1, paths.size()); 43 assertEquals("incorrect paths count", 1, paths.size());
44 assertEquals("incorrect path cost", 1.0, paths.iterator().next().cost(), 0.1); 44 assertEquals("incorrect path cost", 1.0, paths.iterator().next().cost(), 0.1);
45 45
46 - paths = gs.search(g, A, D, weight).paths(); 46 + paths = gs.search(graph, A, D, weight).paths();
47 printPaths(paths); 47 printPaths(paths);
48 assertEquals("incorrect paths count", 0, paths.size()); 48 assertEquals("incorrect paths count", 0, paths.size());
49 49
50 - paths = gs.search(g, A, null, weight).paths(); 50 + paths = gs.search(graph, A, null, weight).paths();
51 printPaths(paths); 51 printPaths(paths);
52 assertEquals("incorrect paths count", 1, paths.size()); 52 assertEquals("incorrect paths count", 1, paths.size());
53 assertEquals("incorrect path cost", 1.0, paths.iterator().next().cost(), 0.1); 53 assertEquals("incorrect path cost", 1.0, paths.iterator().next().cost(), 0.1);
...@@ -55,40 +55,40 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest { ...@@ -55,40 +55,40 @@ public class DijkstraGraphSearchTest extends BreadthFirstSearchTest {
55 55
56 @Test 56 @Test
57 public void simpleMultiplePath() { 57 public void simpleMultiplePath() {
58 - g = new AdjacencyListsGraph<>(of(A, B, C, D), 58 + graph = new AdjacencyListsGraph<>(of(A, B, C, D),
59 - of(new TestEdge(A, B, 1), 59 + of(new TestEdge(A, B, 1),
60 - new TestEdge(A, C, 1), 60 + new TestEdge(A, C, 1),
61 - new TestEdge(B, D, 1), 61 + new TestEdge(B, D, 1),
62 - new TestEdge(C, D, 1))); 62 + new TestEdge(C, D, 1)));
63 - executeSearch(graphSearch(), g, A, D, weight, 2, 2.0); 63 + executeSearch(graphSearch(), graph, A, D, weight, 2, 2.0);
64 } 64 }
65 65
66 @Test 66 @Test
67 public void denseMultiplePath() { 67 public void denseMultiplePath() {
68 - g = new AdjacencyListsGraph<>(of(A, B, C, D, E, F, G), 68 + graph = new AdjacencyListsGraph<>(of(A, B, C, D, E, F, G),
69 - of(new TestEdge(A, B, 1), 69 + of(new TestEdge(A, B, 1),
70 - new TestEdge(A, C, 1), 70 + new TestEdge(A, C, 1),
71 - new TestEdge(B, D, 1), 71 + new TestEdge(B, D, 1),
72 - new TestEdge(C, D, 1), 72 + new TestEdge(C, D, 1),
73 - new TestEdge(D, E, 1), 73 + new TestEdge(D, E, 1),
74 - new TestEdge(D, F, 1), 74 + new TestEdge(D, F, 1),
75 - new TestEdge(E, G, 1), 75 + new TestEdge(E, G, 1),
76 - new TestEdge(F, G, 1), 76 + new TestEdge(F, G, 1),
77 - new TestEdge(A, G, 4))); 77 + new TestEdge(A, G, 4)));
78 - executeSearch(graphSearch(), g, A, G, weight, 5, 4.0); 78 + executeSearch(graphSearch(), graph, A, G, weight, 5, 4.0);
79 } 79 }
80 80
81 @Test 81 @Test
82 public void dualEdgeMultiplePath() { 82 public void dualEdgeMultiplePath() {
83 - g = new AdjacencyListsGraph<>(of(A, B, C, D, E, F, G, H), 83 + graph = new AdjacencyListsGraph<>(of(A, B, C, D, E, F, G, H),
84 - of(new TestEdge(A, B, 1), new TestEdge(A, C, 3), 84 + of(new TestEdge(A, B, 1), new TestEdge(A, C, 3),
85 - new TestEdge(B, D, 2), new TestEdge(B, C, 1), 85 + new TestEdge(B, D, 2), new TestEdge(B, C, 1),
86 - new TestEdge(B, E, 4), new TestEdge(C, E, 1), 86 + new TestEdge(B, E, 4), new TestEdge(C, E, 1),
87 - new TestEdge(D, H, 5), new TestEdge(D, E, 1), 87 + new TestEdge(D, H, 5), new TestEdge(D, E, 1),
88 - new TestEdge(E, F, 1), new TestEdge(F, D, 1), 88 + new TestEdge(E, F, 1), new TestEdge(F, D, 1),
89 - new TestEdge(F, G, 1), new TestEdge(F, H, 1), 89 + new TestEdge(F, G, 1), new TestEdge(F, H, 1),
90 - new TestEdge(A, E, 3), new TestEdge(B, D, 1))); 90 + new TestEdge(A, E, 3), new TestEdge(B, D, 1)));
91 - executeSearch(graphSearch(), g, A, E, weight, 3, 3.0); 91 + executeSearch(graphSearch(), graph, A, E, weight, 3, 3.0);
92 } 92 }
93 93
94 } 94 }
......
...@@ -19,7 +19,7 @@ public class GraphTest { ...@@ -19,7 +19,7 @@ public class GraphTest {
19 static final TestVertex H = new TestVertex("H"); 19 static final TestVertex H = new TestVertex("H");
20 static final TestVertex Z = new TestVertex("Z"); 20 static final TestVertex Z = new TestVertex("Z");
21 21
22 - protected Graph<TestVertex, TestEdge> g; 22 + protected Graph<TestVertex, TestEdge> graph;
23 23
24 protected EdgeWeight<TestVertex, TestEdge> weight = 24 protected EdgeWeight<TestVertex, TestEdge> weight =
25 new EdgeWeight<TestVertex, TestEdge>() { 25 new EdgeWeight<TestVertex, TestEdge>() {
...@@ -35,7 +35,7 @@ public class GraphTest { ...@@ -35,7 +35,7 @@ public class GraphTest {
35 } 35 }
36 } 36 }
37 37
38 - protected Set<TestVertex> vertices() { 38 + protected Set<TestVertex> vertexes() {
39 return of(A, B, C, D, E, F, G, H); 39 return of(A, B, C, D, E, F, G, H);
40 } 40 }
41 41
......
1 +package org.onlab.graph;
2 +
3 +import org.junit.Test;
4 +
5 +import static com.google.common.collect.ImmutableSet.of;
6 +import static org.junit.Assert.assertEquals;
7 +import static org.onlab.graph.TarjanGraphSearch.SCCResult;
8 +
9 +/**
10 + * Tarjan graph search tests.
11 + */
12 +public class TarjanGraphSearchTest extends GraphTest {
13 +
14 + private void validate(SCCResult<TestVertex, TestEdge> result, int cc) {
15 + System.out.println("Cluster count: " + result.clusterVertexes().size());
16 + System.out.println("Clusters: " + result.clusterVertexes());
17 + assertEquals("incorrect cluster count", cc, result.clusterCount());
18 + }
19 +
20 + private void validate(SCCResult<TestVertex, TestEdge> result,
21 + int i, int vc, int ec) {
22 + assertEquals("incorrect cluster count", vc, result.clusterVertexes().get(i).size());
23 + assertEquals("incorrect edge count", ec, result.clusterEdges().get(i).size());
24 + }
25 +
26 + @Test
27 + public void basic() {
28 + graph = new AdjacencyListsGraph<>(vertexes(), edges());
29 + TarjanGraphSearch<TestVertex, TestEdge> gs = new TarjanGraphSearch<>();
30 + SCCResult<TestVertex, TestEdge> result = gs.search(graph, null);
31 + validate(result, 6);
32 + }
33 +
34 + @Test
35 + public void singleCluster() {
36 + graph = new AdjacencyListsGraph<>(vertexes(),
37 + of(new TestEdge(A, B, 1),
38 + new TestEdge(B, C, 1),
39 + new TestEdge(C, D, 1),
40 + new TestEdge(D, E, 1),
41 + new TestEdge(E, F, 1),
42 + new TestEdge(F, G, 1),
43 + new TestEdge(G, H, 1),
44 + new TestEdge(H, A, 1)));
45 +
46 + TarjanGraphSearch<TestVertex, TestEdge> gs = new TarjanGraphSearch<>();
47 + SCCResult<TestVertex, TestEdge> result = gs.search(graph, null);
48 + validate(result, 1);
49 + validate(result, 0, 8, 8);
50 + }
51 +
52 + @Test
53 + public void twoUnconnectedCluster() {
54 + graph = new AdjacencyListsGraph<>(vertexes(),
55 + of(new TestEdge(A, B, 1),
56 + new TestEdge(B, C, 1),
57 + new TestEdge(C, D, 1),
58 + new TestEdge(D, A, 1),
59 + new TestEdge(E, F, 1),
60 + new TestEdge(F, G, 1),
61 + new TestEdge(G, H, 1),
62 + new TestEdge(H, E, 1)));
63 + TarjanGraphSearch<TestVertex, TestEdge> gs = new TarjanGraphSearch<>();
64 + SCCResult<TestVertex, TestEdge> result = gs.search(graph, null);
65 + validate(result, 2);
66 + validate(result, 0, 4, 4);
67 + validate(result, 1, 4, 4);
68 + }
69 +
70 + @Test
71 + public void twoWeaklyConnectedClusters() {
72 + graph = new AdjacencyListsGraph<>(vertexes(),
73 + of(new TestEdge(A, B, 1),
74 + new TestEdge(B, C, 1),
75 + new TestEdge(C, D, 1),
76 + new TestEdge(D, A, 1),
77 + new TestEdge(E, F, 1),
78 + new TestEdge(F, G, 1),
79 + new TestEdge(G, H, 1),
80 + new TestEdge(H, E, 1),
81 + new TestEdge(B, E, 1)));
82 + TarjanGraphSearch<TestVertex, TestEdge> gs = new TarjanGraphSearch<>();
83 + SCCResult<TestVertex, TestEdge> result = gs.search(graph, null);
84 + validate(result, 2);
85 + validate(result, 0, 4, 4);
86 + validate(result, 1, 4, 4);
87 + }
88 +
89 + @Test
90 + public void twoClustersConnectedWithIgnoredEdges() {
91 + graph = new AdjacencyListsGraph<>(vertexes(),
92 + of(new TestEdge(A, B, 1),
93 + new TestEdge(B, C, 1),
94 + new TestEdge(C, D, 1),
95 + new TestEdge(D, A, 1),
96 + new TestEdge(E, F, 1),
97 + new TestEdge(F, G, 1),
98 + new TestEdge(G, H, 1),
99 + new TestEdge(H, E, 1),
100 + new TestEdge(B, E, -1),
101 + new TestEdge(E, B, -1)));
102 +
103 + TarjanGraphSearch<TestVertex, TestEdge> gs = new TarjanGraphSearch<>();
104 + SCCResult<TestVertex, TestEdge> result = gs.search(graph, weight);
105 + validate(result, 2);
106 + validate(result, 0, 4, 4);
107 + validate(result, 1, 4, 4);
108 + }
109 +
110 +}