Nikhil Cheerla
Committed by Thomas Vachuska

[Emu] FlowAnalyzer app code and tests

Change-Id: I8b6f83f7d2e76a4e8cf770d9b0018be283e91bf3
......@@ -40,6 +40,38 @@
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onlab-junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-api</artifactId>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr.annotations</artifactId>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-cli</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.karaf.shell</groupId>
<artifactId>org.apache.karaf.shell.console</artifactId>
</dependency>
</dependencies>
</project>
......
/*
* Copyright 2015 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.flowanalyzer;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
/**
* Analyzes flows for cycles and black holes.
*/
@Command(scope = "onos", name = "flow-analysis",
description = "Analyzes flows for cycles and black holes")
public class FlowAnalysisCommand extends AbstractShellCommand {
@Override
protected void execute() {
FlowAnalyzer service = get(FlowAnalyzer.class);
print(service.analyze());
}
}
......@@ -21,12 +21,31 @@ import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.FlowEntry;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.host.HostService;
import org.onosproject.net.DeviceId;
import org.onosproject.net.HostId;
import org.onosproject.net.flow.criteria.Criteria;
import org.onosproject.net.flow.criteria.Criterion;
import org.onosproject.net.flow.criteria.PortCriterion;
import org.onosproject.net.flow.instructions.Instruction;
import org.onosproject.net.flow.instructions.Instructions;
import org.onosproject.net.topology.TopologyService;
import org.onosproject.net.topology.TopologyGraph;
import org.onosproject.net.link.LinkService;
import org.onosproject.net.Link;
import org.onosproject.net.topology.TopologyVertex;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import java.util.HashSet;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static org.slf4j.LoggerFactory.getLogger;
/**
......@@ -42,11 +61,10 @@ public class FlowAnalyzer {
protected FlowRuleService flowRuleService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected LinkService linkService;
protected TopologyService topologyService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostService hostService;
protected LinkService linkService;
@Activate
public void activate(ComponentContext context) {
......@@ -58,12 +76,193 @@ public class FlowAnalyzer {
log.info("Stopped");
}
TopologyGraph graph;
Map<FlowEntry, String> label = new HashMap<>();
Set<FlowEntry> ignoredFlows = new HashSet<>();
/**
* ...
* Analyzes and prints out a report on the status of every flow entry inside
* the network. The possible states are: Cleared (implying that the entry leads to
* a host), Cycle (implying that it is part of cycle), and Black Hole (implying
* that the entry does not lead to a single host).
*/
public void analyze() {
// TODO: implement this
public String analyze() {
graph = topologyService.getGraph(topologyService.currentTopology());
for (TopologyVertex v: graph.getVertexes()) {
DeviceId srcDevice = v.deviceId();
Iterable<FlowEntry> flowTable = flowRuleService.getFlowEntries(srcDevice);
for (FlowEntry flow: flowTable) {
dfs(flow);
}
}
//analyze the cycles to look for "critical flows" that can be removed
//to break the cycle
Set<FlowEntry> critpts = new HashSet<>();
for (FlowEntry flow: label.keySet()) {
if ("Cycle".equals(label.get(flow))) {
Map<FlowEntry, String> labelSaved = label;
label = new HashMap<FlowEntry, String>();
ignoredFlows.add(flow);
for (TopologyVertex v: graph.getVertexes()) {
DeviceId srcDevice = v.deviceId();
Iterable<FlowEntry> flowTable = flowRuleService.getFlowEntries(srcDevice);
for (FlowEntry flow1: flowTable) {
dfs(flow1);
}
}
boolean replacable = true;
for (FlowEntry flow2: label.keySet()) {
if ("Cleared".equals(labelSaved.get(flow2)) && !("Cleared".equals(label.get(flow2)))) {
replacable = false;
}
}
if (replacable) {
critpts.add(flow);
}
label = labelSaved;
}
}
for (FlowEntry flow: critpts) {
label.put(flow, "Cycle Critical Point");
}
String s = "\n";
for (FlowEntry flow: label.keySet()) {
s += ("Flow Rule: " + flowEntryRepresentation(flow) + "\n");
s += ("Analysis: " + label.get(flow) + "!\n\n");
}
s += ("Analyzed " + label.keySet().size() + " flows.");
//log.info(s);
return s;
}
public Map<FlowEntry, String> calcLabels() {
analyze();
return label;
}
public String analysisOutput() {
analyze();
String s = "\n";
for (FlowEntry flow: label.keySet()) {
s += ("Flow Rule: " + flowEntryRepresentation(flow) + "\n");
s += ("Analysis: " + label.get(flow) + "!\n\n");
}
return s;
}
private boolean dfs(FlowEntry flow) {
if (ignoredFlows.contains(flow)) {
return false;
}
if ("Cycle".equals(label.get(flow)) ||
"Black Hole".equals(label.get(flow)) ||
"Cleared".equals(label.get(flow)) ||
"NA".equals(label.get(flow)) ||
"Cycle Critical Point".equals(label.get(flow))) {
// This flow has already been analyzed and there is no need to analyze it further
return !"Black Hole".equals(label.get(flow));
}
if ("Visiting".equals(label.get(flow))) {
//you've detected a cycle because you reached the same entry again during your dfs
//let it continue so you can label the whole cycle
label.put(flow, "Cycle");
} else {
//otherwise, mark off the current flow entry as currently being visited
label.put(flow, "Visiting");
}
boolean pointsToLiveEntry = false;
List<Instruction> instructions = flow.treatment().allInstructions();
for (Instruction i: instructions) {
if (i instanceof Instructions.OutputInstruction) {
pointsToLiveEntry |= analyzeInstruction(i, flow);
}
if ("NA".equals(label.get(flow))) {
return pointsToLiveEntry;
}
}
if (!pointsToLiveEntry) {
//this entry does not point to any "live" entries thus must be a black hole
label.put(flow, "Black Hole");
} else if ("Visiting".equals(label.get(flow))) {
//the flow is not in a cycle or in a black hole
label.put(flow, "Cleared");
}
return pointsToLiveEntry;
}
private boolean analyzeInstruction(Instruction i, FlowEntry flow) {
boolean pointsToLiveEntry = false;
Instructions.OutputInstruction output = (Instructions.OutputInstruction) i;
PortNumber port = output.port();
PortNumber outPort = null;
DeviceId egress = null;
boolean hasHost = false;
ConnectPoint portPt = new ConnectPoint(flow.deviceId(), port);
for (Link l: linkService.getEgressLinks(portPt)) {
if (l.dst().elementId() instanceof DeviceId) {
egress = l.dst().deviceId();
outPort = l.dst().port();
} else if (l.dst().elementId() instanceof HostId) {
//the port leads to a host: therefore it is not a dead link
pointsToLiveEntry = true;
hasHost = true;
}
}
if (!topologyService.isInfrastructure(topologyService.currentTopology(), portPt) && egress == null) {
pointsToLiveEntry = true;
hasHost = true;
}
if (hasHost) {
return pointsToLiveEntry;
}
if (egress == null) {
//the port that the flow instructions tells you to send the packet
//to doesn't exist or is a controller port
label.put(flow, "NA");
return pointsToLiveEntry;
}
Iterable<FlowEntry> dstFlowTable = flowRuleService.getFlowEntries(egress);
Set<Criterion> flowCriteria = flow.selector().criteria();
//filter the criteria in order to remove port dependency
Set<Criterion> filteredCriteria = new HashSet<>();
for (Criterion criterion : flowCriteria) {
if (!(criterion instanceof PortCriterion)) {
filteredCriteria.add(criterion);
}
}
//ensure that the in port is equal to the port that it is coming in from
filteredCriteria.add(Criteria.matchInPort(outPort));
for (FlowEntry entry: dstFlowTable) {
if (ignoredFlows.contains(entry)) {
continue;
}
if (filteredCriteria.containsAll(entry.selector().criteria())) {
dfs(entry);
if (!"Black Hole".equals(label.get(entry))) {
//this entry is "live" i.e not a black hole
pointsToLiveEntry = true;
}
}
}
return pointsToLiveEntry;
}
public String flowEntryRepresentation(FlowEntry flow) {
return "Device: " + flow.deviceId() + ", " + flow.selector().criteria() + ", " + flow.treatment().immediate();
}
}
......
<!--
~ Copyright 2015 Open Networking Laboratory
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
<command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
<command>
<action class="org.onosproject.flowanalyzer.FlowAnalysisCommand"/>
</command>
</command-bundle>
</blueprint>
package org.onosproject.flowanalyzer;
import org.onlab.graph.MutableAdjacencyListsGraph;
import org.onosproject.net.topology.TopologyEdge;
import org.onosproject.net.topology.TopologyGraph;
import org.onosproject.net.topology.TopologyVertex;
import java.util.Set;
/**
* Default implementation of an immutable topology graph based on a generic
* implementation of adjacency lists graph.
*/
public class DefaultMutableTopologyGraph
extends MutableAdjacencyListsGraph<TopologyVertex, TopologyEdge>
implements TopologyGraph {
/**
* Creates a topology graph comprising of the specified vertexes and edges.
*
* @param vertexes set of graph vertexes
* @param edges set of graph edges
*/
public DefaultMutableTopologyGraph(Set<TopologyVertex> vertexes, Set<TopologyEdge> edges) {
super(vertexes, edges);
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.flowanalyzer;
import org.junit.Ignore;
import org.junit.Test;
import org.onosproject.core.DefaultApplicationId;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleExtPayLoad;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.instructions.Instructions;
import org.onosproject.net.topology.TopologyService;
import java.util.Arrays;
import java.util.TreeSet;
import static org.junit.Assert.assertEquals;
/**
* Created by nikcheerla on 7/20/15.
*/
public class FlowAnalyzerTest {
FlowRuleService flowRuleService = new MockFlowRuleService();
TopologyService topologyService;
MockLinkService linkService = new MockLinkService();
@Test
@Ignore("This needs to be reworked to be more robust")
public void basic() {
flowRuleService = new MockFlowRuleService();
flowRuleService.applyFlowRules(genFlow("ATL-001", 110, 90));
flowRuleService.applyFlowRules(genFlow("ATL-001", 110, 100));
flowRuleService.applyFlowRules(genFlow("ATL-001", 110, 150));
flowRuleService.applyFlowRules(genFlow("ATL-002", 80, 70));
flowRuleService.applyFlowRules(genFlow("ATL-003", 120, 130));
flowRuleService.applyFlowRules(genFlow("ATL-004", 50));
flowRuleService.applyFlowRules(genFlow("ATL-005", 140, 10));
linkService.addLink("H00:00:00:00:00:0660", 160, "ATL-005", 140);
linkService.addLink("ATL-005", 10, "ATL-004", 40);
linkService.addLink("ATL-004", 50, "ATL-002", 80);
linkService.addLink("ATL-002", 70, "ATL-001", 110);
linkService.addLink("ATL-001", 150, "H00:00:00:00:00:0770", 170);
linkService.addLink("ATL-001", 90, "ATL-004", 30);
linkService.addLink("ATL-001", 100, "ATL-003", 120);
linkService.addLink("ATL-003", 130, "ATL-005", 20);
topologyService = new MockTopologyService(linkService.createdGraph);
FlowAnalyzer flowAnalyzer = new FlowAnalyzer();
flowAnalyzer.flowRuleService = flowRuleService;
flowAnalyzer.linkService = linkService;
flowAnalyzer.topologyService = topologyService;
String labels = flowAnalyzer.analysisOutput();
String correctOutput = "Flow Rule: Device: atl-005, [IN_PORT{port=140}], [OUTPUT{port=10}]\n" +
"Analysis: Cleared!\n" +
"\n" +
"Flow Rule: Device: atl-003, [IN_PORT{port=120}], [OUTPUT{port=130}]\n" +
"Analysis: Black Hole!\n" +
"\n" +
"Flow Rule: Device: atl-001, [IN_PORT{port=110}], [OUTPUT{port=90}]\n" +
"Analysis: Cycle Critical Point!\n" +
"\n" +
"Flow Rule: Device: atl-004, [], [OUTPUT{port=50}]\n" +
"Analysis: Cycle!\n" +
"\n" +
"Flow Rule: Device: atl-001, [IN_PORT{port=110}], [OUTPUT{port=150}]\n" +
"Analysis: Cleared!\n" +
"\n" +
"Flow Rule: Device: atl-001, [IN_PORT{port=110}], [OUTPUT{port=100}]\n" +
"Analysis: Black Hole!\n" +
"\n" +
"Flow Rule: Device: atl-002, [IN_PORT{port=80}], [OUTPUT{port=70}]\n" +
"Analysis: Cycle!\n";
assertEquals("Wrong labels", new TreeSet(Arrays.asList(labels.replaceAll("\\s+", "").split("!"))),
new TreeSet(Arrays.asList(correctOutput.replaceAll("\\s+", "").split("!"))));
}
public FlowRule genFlow(String d, long inPort, long outPort) {
DeviceId device = DeviceId.deviceId(d);
TrafficSelector ts = DefaultTrafficSelector.builder().matchInPort(PortNumber.portNumber(inPort)).build();
TrafficTreatment tt = DefaultTrafficTreatment.builder()
.add(Instructions.createOutput(PortNumber.portNumber(outPort))).build();
return new DefaultFlowRule(device, ts, tt, 1, new DefaultApplicationId(5000, "of"),
50000, true, FlowRuleExtPayLoad.flowRuleExtPayLoad(new byte[5]));
}
public FlowRule genFlow(String d, long outPort) {
DeviceId device = DeviceId.deviceId(d);
TrafficSelector ts = DefaultTrafficSelector.builder().build();
TrafficTreatment tt = DefaultTrafficTreatment.builder()
.add(Instructions.createOutput(PortNumber.portNumber(outPort))).build();
return new DefaultFlowRule(device, ts, tt, 1, new DefaultApplicationId(5000, "of"),
50000, true, FlowRuleExtPayLoad.flowRuleExtPayLoad(new byte[5]));
}
}
package org.onosproject.flowanalyzer;
import com.google.common.collect.Sets;
import org.onosproject.core.ApplicationId;
import org.onosproject.net.DeviceId;
import org.onosproject.net.flow.DefaultFlowEntry;
import org.onosproject.net.flow.FlowEntry;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleOperations;
import org.onosproject.net.flow.FlowRuleServiceAdapter;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
/**
* Created by nikcheerla on 7/20/15.
*/
public class MockFlowRuleService extends FlowRuleServiceAdapter {
final Set<FlowRule> flows = Sets.newHashSet();
boolean success;
int errorFlow = -1;
public void setErrorFlow(int errorFlow) {
this.errorFlow = errorFlow;
}
public void setFuture(boolean success) {
this.success = success;
}
@Override
public void apply(FlowRuleOperations ops) {
AtomicBoolean thisSuccess = new AtomicBoolean(success);
ops.stages().forEach(stage -> stage.forEach(flow -> {
if (errorFlow == flow.rule().id().value()) {
thisSuccess.set(false);
} else {
switch (flow.type()) {
case ADD:
case MODIFY: //TODO is this the right behavior for modify?
flows.add(flow.rule());
break;
case REMOVE:
flows.remove(flow.rule());
break;
default:
break;
}
}
}));
if (thisSuccess.get()) {
ops.callback().onSuccess(ops);
} else {
ops.callback().onError(ops);
}
}
@Override
public int getFlowRuleCount() {
return flows.size();
}
@Override
public Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
return flows.stream()
.filter(flow -> flow.deviceId().equals(deviceId))
.map(DefaultFlowEntry::new)
.collect(Collectors.toList());
}
@Override
public void applyFlowRules(FlowRule... flowRules) {
for (FlowRule flow : flowRules) {
flows.add(flow);
}
}
@Override
public void removeFlowRules(FlowRule... flowRules) {
for (FlowRule flow : flowRules) {
flows.remove(flow);
}
}
@Override
public Iterable<FlowRule> getFlowRulesById(ApplicationId id) {
return flows.stream()
.filter(flow -> flow.appId() == id.id())
.collect(Collectors.toList());
}
@Override
public Iterable<FlowRule> getFlowRulesByGroupId(ApplicationId appId, short groupId) {
return flows.stream()
.filter(flow -> flow.appId() == appId.id() && flow.groupId().id() == groupId)
.collect(Collectors.toList());
}
}
package org.onosproject.flowanalyzer;
import org.onosproject.net.PortNumber;
import org.onosproject.net.link.LinkServiceAdapter;
import org.onosproject.net.topology.TopologyEdge;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.ElementId;
import org.onosproject.net.HostId;
import org.onosproject.net.Link;
import org.onosproject.net.Annotations;
import org.onosproject.net.provider.ProviderId;
import org.onosproject.net.topology.TopologyVertex;
import java.util.Set;
import java.util.ArrayList;
import java.util.List;
import java.util.HashSet;
import static org.onosproject.net.Link.State.ACTIVE;
/**
* Created by nikcheerla on 7/21/15.
*/
public class MockLinkService extends LinkServiceAdapter {
DefaultMutableTopologyGraph createdGraph = new DefaultMutableTopologyGraph(new HashSet<>(), new HashSet<>());
List<Link> links = new ArrayList<>();
@Override
public int getLinkCount() {
return links.size();
}
@Override
public Iterable<Link> getLinks() {
return links;
}
@Override
public Set<Link> getDeviceLinks(DeviceId deviceId) {
Set<Link> egress = getDeviceEgressLinks(deviceId);
egress.addAll(getDeviceIngressLinks(deviceId));
return egress;
}
@Override
public Set<Link> getDeviceEgressLinks(DeviceId deviceId) {
Set<Link> setL = new HashSet<>();
for (Link l: links) {
if (l.src().elementId() instanceof DeviceId && l.src().deviceId().equals(deviceId)) {
setL.add(l);
}
}
return setL;
}
@Override
public Set<Link> getDeviceIngressLinks(DeviceId deviceId) {
Set<Link> setL = new HashSet<>();
for (Link l: links) {
if (l.dst().elementId() instanceof DeviceId && l.dst().deviceId().equals(deviceId)) {
setL.add(l);
}
}
return setL;
}
@Override
public Set<Link> getEgressLinks(ConnectPoint pt) {
Set<Link> setL = new HashSet<>();
for (Link l: links) {
if (l.src().equals(pt)) {
setL.add(l);
}
}
return setL;
}
@Override
public Set<Link> getIngressLinks(ConnectPoint pt) {
Set<Link> setL = new HashSet<>();
for (Link l: links) {
if (l.dst().equals(pt)) {
setL.add(l);
}
}
return setL;
}
@Override
public Set<Link> getLinks(ConnectPoint pt) {
Set<Link> setL = new HashSet<>();
for (Link l: links) {
if (l.src().equals(pt) || l.dst().equals(pt)) {
setL.add(l);
}
}
return setL;
}
public void addLink(String device, long port, String device2, long port2) {
ElementId d1;
if (device.charAt(0) == 'H') {
device = device.substring(1, device.length());
d1 = HostId.hostId(device);
} else {
d1 = DeviceId.deviceId(device);
}
ElementId d2;
if (device2.charAt(0) == 'H') {
d2 = HostId.hostId(device2.substring(1, device2.length()));
} else {
d2 = DeviceId.deviceId(device2);
}
ConnectPoint src = new ConnectPoint(d1, PortNumber.portNumber(port));
ConnectPoint dst = new ConnectPoint(d2, PortNumber.portNumber(port2));
Link curLink;
curLink = new Link() {
@Override
public ConnectPoint src() {
return src;
}
@Override
public ConnectPoint dst() {
return dst;
}
@Override
public boolean isDurable() {
return true;
}
@Override
public Annotations annotations() {
return null;
}
@Override
public Type type() {
return null;
}
@Override
public ProviderId providerId() {
return null;
}
@Override
public State state() {
return ACTIVE;
}
};
links.add(curLink);
if (d1 instanceof DeviceId && d2 instanceof DeviceId) {
TopologyVertex v1 = () -> (DeviceId) d1, v2 = () -> (DeviceId) d2;
createdGraph.addVertex(v1);
createdGraph.addVertex(v2);
createdGraph.addEdge(new TopologyEdge() {
@Override
public Link link() {
return curLink;
}
@Override
public TopologyVertex src() {
return v1;
}
@Override
public TopologyVertex dst() {
return v2;
}
});
}
}
}
package org.onosproject.flowanalyzer;
import org.onosproject.net.topology.Topology;
import org.onosproject.net.topology.TopologyGraph;
import org.onosproject.net.topology.TopologyServiceAdapter;
/**
* Created by nikcheerla on 7/20/15.
*/
public class MockTopologyService extends TopologyServiceAdapter {
TopologyGraph cur;
public MockTopologyService(TopologyGraph g) {
cur = g;
}
@Override
public TopologyGraph getGraph(Topology topology) {
return cur;
}
}