|
|
|
@ -2,7 +2,7 @@
|
|
|
|
|
|
|
|
|
|
from birdvisu.annotations import AnnotatedTopology, AnnotatorID
|
|
|
|
|
from birdvisu.annotations.analysis import TopologyDifference, ShortestPathTree
|
|
|
|
|
from birdvisu.annotations.layout import MegaStyler
|
|
|
|
|
from birdvisu.annotations.layout import PlaceVerticesFromFile, PlaceUnplacedVertices, EdgeWidthByCost, HighlightTopoDiff, HighlightSPDAG, HighlightShortestPath
|
|
|
|
|
from birdvisu.ospfsock import BirdSocketConnection
|
|
|
|
|
from birdvisu.providers import BirdSocketTopologyProvider, OspfFileTopologyProvider, OspfFileParseError
|
|
|
|
|
from birdvisu.topo_v3 import TopologyV3, VertexID
|
|
|
|
@ -78,9 +78,11 @@ class BirdTopologyLoader(QtWidgets.QDialog):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MainWindow(QtWidgets.QMainWindow):
|
|
|
|
|
class Tool(Enum):
|
|
|
|
|
MoveTopology = auto()
|
|
|
|
|
class Mode(Enum):
|
|
|
|
|
ShortestPath = auto()
|
|
|
|
|
ShortestPathDAG = auto()
|
|
|
|
|
TopologyDifference = auto()
|
|
|
|
|
EdgeWeight = auto()
|
|
|
|
|
|
|
|
|
|
def create_menus(self):
|
|
|
|
|
print('Creating menus…')
|
|
|
|
@ -112,14 +114,21 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
|
|
|
refresh_act.triggered.connect(self.refreshTopologies)
|
|
|
|
|
topo_menu.addAction(refresh_act)
|
|
|
|
|
|
|
|
|
|
positions = self.menubar.addMenu('&Positions')
|
|
|
|
|
loadp = QtGui.QAction('Load from file', self)
|
|
|
|
|
loadp.triggered.connect(self.load_positions)
|
|
|
|
|
positions.addAction(loadp)
|
|
|
|
|
|
|
|
|
|
def __init__(self, *a, **kwa):
|
|
|
|
|
super().__init__(*a, **kwa)
|
|
|
|
|
self.ref_topo_provider = None
|
|
|
|
|
self.cur_topo_provider = None
|
|
|
|
|
self.set_initial_annotators()
|
|
|
|
|
self.annot_topo = None
|
|
|
|
|
# The actual graph to show: list of neighbours and of edges
|
|
|
|
|
self.visu_graph: tuple[dict[VertexID, set[VertexID]], set[VertexID, VertexID]] = None
|
|
|
|
|
self.highlighter = None
|
|
|
|
|
self.tool = self.Tool.MoveTopology
|
|
|
|
|
self.mode = None
|
|
|
|
|
self.positions: dict[VertexID, tuple[float, float]] = dict()
|
|
|
|
|
self.scene = QtWidgets.QGraphicsScene()
|
|
|
|
|
self.view = QtWidgets.QGraphicsView(self.scene)
|
|
|
|
|
self.view.setDragMode(self.view.DragMode.ScrollHandDrag)
|
|
|
|
@ -127,6 +136,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
|
|
|
self.statusbar = self.statusBar()
|
|
|
|
|
self.statusbar.showMessage('Hello!')
|
|
|
|
|
self.create_menus()
|
|
|
|
|
self.edgeWeightMode()
|
|
|
|
|
self.autoLoad()
|
|
|
|
|
|
|
|
|
|
#Hack
|
|
|
|
@ -137,27 +147,76 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
|
|
|
self.cur_topo_provider = BirdSocketTopologyProvider(instance='gennet4', area=1, version=2)
|
|
|
|
|
self.refreshTopologies()
|
|
|
|
|
|
|
|
|
|
@Slot()
|
|
|
|
|
def load_positions(self):
|
|
|
|
|
filename = QtWidgets.QFileDialog.getOpenFileName(self, 'Open vertex positions', '.', 'OSPF files(*.visu);;All files(*)')[0]
|
|
|
|
|
if filename == '': return # Do nothing
|
|
|
|
|
self.positions_from_file(filename)
|
|
|
|
|
|
|
|
|
|
def positions_from_file(self, fn):
|
|
|
|
|
if self.visu_graph is None: return # nothing to do yet.
|
|
|
|
|
# Let's be frank: this used to be StyleAnnotators and it is not re-implemented.
|
|
|
|
|
pvff = AnnotatorID(PlaceVerticesFromFile, fn)
|
|
|
|
|
self.annot_topo.run_annotator(pvff)
|
|
|
|
|
placements = PlaceUnplacedVertices(pvff).annotate(self.annot_topo)
|
|
|
|
|
self.positions = {v: d['position'] for v, d in placements.for_vertex.items()}
|
|
|
|
|
# Apply the positions:
|
|
|
|
|
for v, pos in self.positions.items():
|
|
|
|
|
x, y = pos
|
|
|
|
|
self.graphicsitems[v].setPos(x, y)
|
|
|
|
|
# Fix all the edges probably
|
|
|
|
|
for e in self.visu_graph[1]:
|
|
|
|
|
self.graphicsitems[e].update_line()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Slot()
|
|
|
|
|
def savePositions(self):
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
@Slot()
|
|
|
|
|
def apply_styles(self):
|
|
|
|
|
if self.visu_graph is None: return # First need graph.
|
|
|
|
|
styles_ant = self.highlighter.annotate(self.annot_topo)
|
|
|
|
|
for_vertex = styles_ant.for_vertex
|
|
|
|
|
for_edge_ant = styles_ant.for_edge
|
|
|
|
|
# We must resolve conflicts
|
|
|
|
|
for_edge: dict[tuple[VertexID, VertexID], tuple[Edge, dict]] = dict() # → edge, styling dict.
|
|
|
|
|
for e, sty in for_edge_ant.items():
|
|
|
|
|
a, b = tuple(sorted((e.source, e.target)))
|
|
|
|
|
if (a,b) not in for_edge:
|
|
|
|
|
for_edge[(a,b)] = (e, sty)
|
|
|
|
|
if e.cost == 0: continue # A collision that we do not care about
|
|
|
|
|
oe, _sty = for_edge[(a,b)]
|
|
|
|
|
if oe.cost == 0 or oe.cost > e.cost:
|
|
|
|
|
for_edge[(a,b)] = (e, sty)
|
|
|
|
|
# Actually apply the style:
|
|
|
|
|
for v, sty in for_vertex.items():
|
|
|
|
|
self.graphicsitems[v].apply_style(sty)
|
|
|
|
|
for e, tup in for_edge.items():
|
|
|
|
|
self.graphicsitems[e].apply_style(tup[1])
|
|
|
|
|
|
|
|
|
|
@Slot()
|
|
|
|
|
def dagMode(self):
|
|
|
|
|
self.mode = self.Mode.ShortestPathDAG
|
|
|
|
|
self.highlighter = HighlightSPDAG(...)
|
|
|
|
|
self.apply_styles()
|
|
|
|
|
|
|
|
|
|
@Slot()
|
|
|
|
|
def topoDiffMode(self):
|
|
|
|
|
self.mode = self.Mode.TopologyDifference
|
|
|
|
|
self.highlighter = HighlightTopoDiff(None)
|
|
|
|
|
self.apply_styles()
|
|
|
|
|
|
|
|
|
|
def set_initial_annotators(self):
|
|
|
|
|
# We have three kinds of annotators:
|
|
|
|
|
# - The essential analytic ones (TopologyDifference)
|
|
|
|
|
# - The one that describes the current tool (when that is Annotator backed, like for ShortestPathTree)
|
|
|
|
|
# - The styling ones, that actually help visualise stuff (MegaStyler)
|
|
|
|
|
self.essential_annotators = [AnnotatorID(TopologyDifference)]
|
|
|
|
|
self.current_annotators = [AnnotatorID(ShortestPathTree, (VertexID(
|
|
|
|
|
family=None,
|
|
|
|
|
is_router=True,
|
|
|
|
|
address=None,
|
|
|
|
|
router_id=int(IPv4Address('172.23.100.10')),
|
|
|
|
|
dr_id=None,
|
|
|
|
|
discriminator=None
|
|
|
|
|
), 'current')),
|
|
|
|
|
]
|
|
|
|
|
self.styling_annotators = [AnnotatorID(MegaStyler, tuple(self.essential_annotators + self.current_annotators))]
|
|
|
|
|
@Slot()
|
|
|
|
|
def edgeWeightMode(self):
|
|
|
|
|
self.mode = self.Mode.EdgeWeight
|
|
|
|
|
self.highlighter = EdgeWidthByCost(None)
|
|
|
|
|
self.apply_styles()
|
|
|
|
|
|
|
|
|
|
@Slot()
|
|
|
|
|
def shortestPathMode(self):
|
|
|
|
|
self.tool = self.Tool.ShortestPath
|
|
|
|
|
self.mode = self.Mode.ShortestPath
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
@Slot()
|
|
|
|
|
def openRefTopology(self):
|
|
|
|
@ -226,40 +285,45 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
|
|
|
combined_topology = TopologyV3.combine_topologies(reference=ref_topo, current=cur_topo)
|
|
|
|
|
combined_topology.freeze()
|
|
|
|
|
self.annot_topo = AnnotatedTopology(combined_topology)
|
|
|
|
|
for ann_id in self.essential_annotators + self.current_annotators + self.styling_annotators:
|
|
|
|
|
self.annot_topo.run_annotator(ann_id)
|
|
|
|
|
# Draw it
|
|
|
|
|
self.topo_to_graph()
|
|
|
|
|
self.draw_visu()
|
|
|
|
|
|
|
|
|
|
def topo_to_graph(self):
|
|
|
|
|
"""Converts an AnnotatedTopology to a graph"""
|
|
|
|
|
# We actually do not care about the annotations
|
|
|
|
|
topo = self.annot_topo.topology
|
|
|
|
|
neighbours = {v: set() for v in topo.vertices.keys()} # Neighbour lists
|
|
|
|
|
edges: set[tuple[VertexID, VertexID]] = set()
|
|
|
|
|
for e in topo.edges:
|
|
|
|
|
neighbours[e.source].add(e.target)
|
|
|
|
|
neighbours[e.target].add(e.source)
|
|
|
|
|
first = min([e.source, e.target])
|
|
|
|
|
second = max([e.source, e.target])
|
|
|
|
|
edges.add((first, second))
|
|
|
|
|
self.visu_graph = (neighbours, edges)
|
|
|
|
|
|
|
|
|
|
def draw_visu(self):
|
|
|
|
|
# just take the result of the MegaStyler and create vertices according to the style.
|
|
|
|
|
megastyler = self.styling_annotators[-1]
|
|
|
|
|
assert megastyler.annotator == MegaStyler
|
|
|
|
|
msann = self.annot_topo.annotations[megastyler]
|
|
|
|
|
# These two dictionaries are used from outside, so that QGraphicsItems
|
|
|
|
|
# can resolve graph relations (e.g. to draw the line between the
|
|
|
|
|
# correct Vertices)
|
|
|
|
|
self.graphicsitems: dict[VertexID, QGraphicsItem] = dict()
|
|
|
|
|
self.topologyitems: dict[QGraphicsItem, VertexID|Edge] = dict()
|
|
|
|
|
for vtxid, style in msann.for_vertex.items():
|
|
|
|
|
gritem = self.create_vertex(vtxid, style)
|
|
|
|
|
self.graphicsitems: dict[VertexID|tuple[VertexID, VertexID], QGraphicsItem] = dict()
|
|
|
|
|
for vtxid in self.visu_graph[0].keys():
|
|
|
|
|
gritem = self.create_vertex(vtxid)
|
|
|
|
|
self.graphicsitems[vtxid] = gritem
|
|
|
|
|
self.topologyitems[gritem] = vtxid
|
|
|
|
|
self.scene.addItem(gritem)
|
|
|
|
|
for edge, style in msann.for_edge.items():
|
|
|
|
|
gritem = self.create_edge(edge, style)
|
|
|
|
|
for edge in self.visu_graph[1]:
|
|
|
|
|
gritem = self.create_edge(edge)
|
|
|
|
|
self.graphicsitems[edge] = gritem
|
|
|
|
|
self.topologyitems[gritem] = edge
|
|
|
|
|
self.scene.addItem(gritem)
|
|
|
|
|
self.positions_from_file(None)
|
|
|
|
|
self.apply_styles()
|
|
|
|
|
|
|
|
|
|
def create_vertex(self, vtxid, style):
|
|
|
|
|
def create_vertex(self, vtxid):
|
|
|
|
|
if vtxid.is_router:
|
|
|
|
|
return RouterGraphicsItem(vtxid, style, self)
|
|
|
|
|
return RouterGraphicsItem(vtxid, self)
|
|
|
|
|
else:
|
|
|
|
|
return NetworkGraphicsItem(vtxid, style, self)
|
|
|
|
|
return NetworkGraphicsItem(vtxid, self)
|
|
|
|
|
|
|
|
|
|
def create_edge(self, edge):
|
|
|
|
|
return EdgeGraphicsItem(edge, self)
|
|
|
|
|
|
|
|
|
|
def create_edge(self, edge, style):
|
|
|
|
|
return EdgeGraphicsItem(edge, style, self)
|
|
|
|
|
|
|
|
|
|
main_window = MainWindow()
|
|
|
|
|
main_window.show()
|
|
|
|
|