poor: Move everything into the QMainWindow and add basic UI

topo-mov
LEdoian 1 year ago
parent 87935c7e3a
commit 3cbb14c1af

@ -496,13 +496,13 @@ class BirdSocketTopologyProvider(TopologyProvider):
response = sock.request(request) response = sock.request(request)
except BirdError as e: except BirdError as e:
if e.text == 'There are multiple OSPF protocols running\n': if e.text == 'There are multiple OSPF protocols running\n':
protocols = self.find_runing_ospf(sock) protocols = self.find_running_ospf(sock)
raise BirdMultipleInstancesError(e, protocols) from e raise BirdMultipleInstancesError(e, protocols) from e
raise raise
parser = OspfFileTopologyParser(version=self.version, area=self.area) parser = OspfFileTopologyParser(version=self.version, area=self.area)
return parser.parse(response.text, freeze=self.freeze) return parser.parse(response.text, freeze=self.freeze)
def find_runing_ospf(self, bird): def find_running_ospf(self, bird):
resp = bird.request('show protocols') resp = bird.request('show protocols')
# There are three sections: the header, the table itself and a 0000 at # There are three sections: the header, the table itself and a 0000 at
# the end. # the end.

@ -1,160 +1,275 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Get topologies
import sys import sys
from birdvisu.providers import BirdSocketTopologyProvider, OspfFileTopologyProvider from birdvisu.providers import BirdSocketTopologyProvider, OspfFileTopologyProvider, OspfFileParseError
ref_topo_file = 'reference.ospf' ref_topo_file = 'reference.ospf'
if len(sys.argv) > 1:
ref_topo_file = sys.argv[1]
ref_topo = OspfFileTopologyProvider(ref_topo_file).get_topology()
def get_empty_topology():
from birdvisu.topo_v3 import TopologyV3
topo = TopologyV3()
topo.freeze()
return topo
if len(sys.argv) > 2 and sys.argv[2] == '--no-bird':
cur_topo = get_empty_topology()
else:
try:
cur_topo = BirdSocketTopologyProvider(instance='ospf1', area=0).get_topology()
except OSError as e:
# I know this is not how you print exceptions.
print('Cannot get topology from BIRD: {e}')
print('Will provide an empty topology.')
cur_topo = get_empty_topology()
# Create combined topology
from birdvisu.topo_v3 import TopologyV3 from birdvisu.topo_v3 import TopologyV3
combined_topology = TopologyV3.combine_topologies(reference=ref_topo, current=cur_topo)
combined_topology.freeze()
# Annotate it
from birdvisu.topo_v3 import VertexID from birdvisu.topo_v3 import VertexID
from birdvisu.annotations import AnnotatedTopology, AnnotatorID from birdvisu.annotations import AnnotatedTopology, AnnotatorID
from birdvisu.annotations.analysis import TopologyDifference, ShortestPathTree from birdvisu.annotations.analysis import TopologyDifference, ShortestPathTree
from ipaddress import IPv4Address from ipaddress import IPv4Address
annot_topo = AnnotatedTopology(combined_topology)
annotators = [
AnnotatorID(TopologyDifference),
AnnotatorID(ShortestPathTree, (VertexID(
family=None,
is_router=True,
address=None,
router_id=int(IPv4Address('172.23.100.10')),
dr_id=None,
discriminator=None
), 'current')),
]
for ann_id in annotators:
annot_topo.run_annotator(ann_id)
# ---
# Show it
#from birdvisu.visualisation import annotators
#from birdvisu import maps_new
# annotators.create_qgritems does not like being run without Qt initialization.
from PySide6 import QtCore, QtGui, QtWidgets from PySide6 import QtCore, QtGui, QtWidgets
app = QtWidgets.QApplication([]) app = QtWidgets.QApplication([])
#annotated_topology = maps_new.annotate_topology(combined_topology,
# # A semi-canonical set of annotators:
# [
# annotators.extract_positions,
# annotators.random_position,
# annotators.assign_brushes,
# annotators.create_qgritems,
# ]
# )
from random import randint from random import randint
from collections import defaultdict from collections import defaultdict
shapes = dict()
nei = defaultdict(lambda: [])
class MyGraphicsRectItem(QtWidgets.QGraphicsRectItem): class MyGraphicsRectItem(QtWidgets.QGraphicsRectItem):
#def __init__(self, *a, **kwa): def __init__(self, nei, shapes, *a, **kwa):
# return super().__init__(*a, **kwa) self.nei = nei
self.shapes = shapes
return super().__init__(*a, **kwa)
#def itemChange(self, change, val): #def itemChange(self, change, val):
# return super().itemChange(change, val) # return super().itemChange(change, val)
def mouseMoveEvent(self, evt): def mouseMoveEvent(self, evt):
vtxid = self.data(0) vtxid = self.data(0)
for e in nei[vtxid]: for e in self.nei[vtxid]:
x1 = self.x() x1 = self.x()
y1 = self.y() y1 = self.y()
other = e.source if e.source != vtxid else e.target other = e.source if e.source != vtxid else e.target
x2 = shapes[other].x() x2 = self.shapes[other].x()
y2 = shapes[other].y() y2 = self.shapes[other].y()
qlinef = QtCore.QLineF(x1, y1, x2, y2) qlinef = QtCore.QLineF(x1, y1, x2, y2)
shapes[e].setLine(qlinef) self.shapes[e].setLine(qlinef)
return super().mouseMoveEvent(evt) return super().mouseMoveEvent(evt)
for k, v in annot_topo.topology.vertices.items(): from enum import Enum, auto
size = 30 if k.is_router else 10 from PySide6.QtCore import Slot
x, y = randint(0, 1920), randint(0, 1080) from birdvisu.ospfsock import BirdSocketConnection
shape = MyGraphicsRectItem(-size/2, -size/2, size, size)
shape.setPos(x,y) class BirdTopologyLoader(QtWidgets.QDialog):
# TODO:brush def __init__(self, *a, **kwa):
label_text = str(IPv4Address(k.router_id)) if k.is_router else str(k.address) # Surprisingly works for all the possible addresses. super().__init__(*a, **kwa)
label = QtWidgets.QGraphicsSimpleTextItem(label_text, parent=shape) self.setModal(True)
label.setY(size*0.8) self.result_ = (None, None, None)
text_width = label.boundingRect().width() outer = QtWidgets.QVBoxLayout(self)
label.setX(-text_width/2) # Area
shape.setData(0, k) inner = QtWidgets.QHBoxLayout()
shape.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable | QtWidgets.QGraphicsItem.ItemIsSelectable) inner.addWidget(QtWidgets.QLabel('Area:'))
shapes[k] = shape line = QtWidgets.QLineEdit(self)
line.setText('-1')
for e in annot_topo.topology.edges: self.line = line
start = shapes[e.source].pos() inner.addWidget(line)
end = shapes[e.target].pos() outer.addLayout(inner)
qlinef = QtCore.QLineF(start, end)
line = QtWidgets.QGraphicsLineItem(qlinef) inner = QtWidgets.QHBoxLayout()
line.setData(0, e) inner.addWidget(QtWidgets.QLabel('Instance:'))
nei[e.source].append(e) combo = QtWidgets.QComboBox()
nei[e.target].append(e) combo.addItems(self.get_instances())
shapes[e] = line self.combo = combo
inner.addWidget(combo)
# Render the widget outer.addLayout(inner)
scene = QtWidgets.QGraphicsScene() inner = QtWidgets.QHBoxLayout()
inner.addWidget(QtWidgets.QLabel('Protocol:'))
#for tagsrc in [ combo = QtWidgets.QComboBox()
# annotated_topology.router_annotations.values(), combo.addItems(['Guess!', 'OSPFv2', 'OSPFv3'])
# annotated_topology.network_annotations.values(), self.combo2 = combo
# annotated_topology.link_annotations.values(), inner.addWidget(combo)
# ]: outer.addLayout(inner)
# for taglist in tagsrc:
# assert len(taglist) > 0 buttons = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
# assert isinstance(taglist[-1], QtWidgets.QGraphicsItem) buttons.accepted.connect(self.saveResult)
# scene.addItem(taglist[-1]) buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
for sh in shapes.values(): scene.addItem(sh) outer.addWidget(buttons)
view = QtWidgets.QGraphicsView(scene) @Slot()
view.setDragMode(view.DragMode.ScrollHandDrag) def saveResult(self):
#view.show() try:
area = int(self.line.text())
except ValueError:
main_window = QtWidgets.QMainWindow() area = int(IPv4Address(self.line.text()))
main_window.setCentralWidget(view) if area == -1: area = None
inst = self.combo.currentText()
menu = main_window.menuBar().addMenu('Hello') proto = {
act = QtGui.QAction('Hi') 'OSPFv2': 2,
#act.setToolTip('Howdy') 'OSPFv3': 3,
menu.addAction(act) 'Guess!': None,
}[self.combo2.currentText()]
self.result_ = (area, inst, proto)
def get_instances(self):
# Our code is stupid, so we initialise a BirdSocketTopologyProvider just to get instances and then drop it.
prov = BirdSocketTopologyProvider()
bird = BirdSocketConnection()
return prov.find_running_ospf(bird)
class MainWindow(QtWidgets.QMainWindow):
class Tool(Enum):
MoveTopology = auto()
ShortestPath = auto()
def __init__(self, *a, **kwa):
super().__init__(*a, **kwa)
self.ref_topo_provider = None
self.cur_topo_provider = None
self.annotators = self.get_annotators()
self.annot_topo = None
self.highlighter = None
self.tool = self.Tool.MoveTopology
self.scene = QtWidgets.QGraphicsScene()
self.view = QtWidgets.QGraphicsView(self.scene)
self.view.setDragMode(self.view.DragMode.ScrollHandDrag)
self.setCentralWidget(self.view)
self.statusbar = self.statusBar()
self.statusbar.showMessage('Hello!')
self.menubar = self.menuBar()
mode_menu = self.menubar.addMenu('&Mode')
short_path_act = QtGui.QAction("Sh. path &DAG", self)
short_path_act.triggered.connect(self.shortestPathMode)
mode_menu.addAction(short_path_act)
topo_menu = self.menubar.addMenu('&Topology')
open_ref_act = QtGui.QAction("&Load reference", self)
open_ref_act.triggered.connect(self.openRefTopology)
topo_menu.addAction(open_ref_act)
cur_topo_menu = topo_menu.addMenu("Load &current")
running_bird_act = QtGui.QAction('&BIRD', self)
running_bird_act.triggered.connect(self.curTopologyFromBird)
cur_topo_menu.addAction(running_bird_act)
from_file_act = QtGui.QAction('&from file', self)
from_file_act.triggered.connect(self.curTopologyFromFile)
cur_topo_menu.addAction(from_file_act)
refresh_act = QtGui.QAction("&Refresh", self)
refresh_act.triggered.connect(self.refreshTopologies)
topo_menu.addAction(refresh_act)
def get_annotators(self):
return [
AnnotatorID(TopologyDifference),
AnnotatorID(ShortestPathTree, (VertexID(
family=None,
is_router=True,
address=None,
router_id=int(IPv4Address('172.23.100.10')),
dr_id=None,
discriminator=None
), 'current')),
]
@Slot()
def shortestPathMode(self):
self.tool = self.Tool.ShortestPath
@Slot()
def openRefTopology(self):
filename = QtWidgets.QFileDialog.getOpenFileName(self, 'Open reference topology', '.', 'OSPF files(*.ospf);;All files(*)')[0]
if filename == '': return # Do nothing
self.ref_topo_provider = OspfFileTopologyProvider(filename)
try:
ref_topo = self.ref_topo_provider.get_topology()
except OspfFileParseError as e:
warning = QtWidgets.QMessageBox.critical(self, "Bad reference topology", f"The reference topology seems to be malformed: {e}.\nPlease select a valid reference topology.")
self.ref_topo_provider = None
return
self.refreshTopologies()
@Slot()
def curTopologyFromBird(self):
loader = BirdTopologyLoader(self)
loader.exec()
area, instance, version = loader.result_
self.cur_topo_provider = BirdSocketTopologyProvider(instance=instance, area=area, version=version)
try:
cur_topo = self.cur_topo_provider.get_topology()
except OspfFileParseError as e:
warning = QtWidgets.QMessageBox.critical(self, "Bad current topology", f"The current topology seems to be malformed: {e}.\nPlease select a valid topology.")
self.cur_topo_provider = None
return
self.refreshTopologies()
@Slot()
def curTopologyFromFile(self):
filename = QtWidgets.QFileDialog.getOpenFileName(self, 'Open current topology', '.', 'OSPF files(*.ospf);;All files(*)')[0]
if filename == '': return # Do nothing
self.cur_topo_provider = OspfFileTopologyProvider(filename)
try:
cur_topo = self.cur_topo_provider.get_topology()
except OspfFileParseError as e:
warning = QtWidgets.QMessageBox.critical(self, "Bad current topology", f"The current topology seems to be malformed: {e}.\nPlease select a valid topology.")
self.cur_topo_provider = None
return
self.refreshTopologies()
@Slot()
def refreshTopologies(self):
# Pre-checks:
msg = ''
if self.ref_topo_provider is None: msg += 'Please select reference topology. '
if self.cur_topo_provider is None: msg += 'Please select current topology. '
if msg:
self.statusbar.showMessage(msg)
# Nothing more to do.
return
# We just drop anything we had before, since we will be re-reading all the files.
try:
ref_topo = self.ref_topo_provider.get_topology()
except OspfFileParseError as e:
warning = QtWidgets.QMessageBox.critical(self, "Bad reference topology", f"The reference topology seems to be malformed: {e}.\nPlease select a valid reference topology.")
self.ref_topo_provider = None
return
try:
cur_topo = self.cur_topo_provider.get_topology()
except OspfFileParseError as e:
warning = QtWidgets.QMessageBox.critical(self, "Bad current topology", f"The current topology seems to be malformed: {e}.\nPlease select a valid topology.")
self.cur_topo_provider = None
return
self.scene.clear()
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.annotators:
self.annot_topo.run_annotator(ann_id)
# Draw it
self.set_current_highlighter()
self.ad_hoc_draw_visu()
def set_current_highlighter(self):
# TODO!
self.highlighter = self.annotators[-1]
def ad_hoc_draw_visu(self):
shapes = dict()
self.nei = defaultdict(lambda: [])
for k, v in self.annot_topo.topology.vertices.items():
size = 30 if k.is_router else 10
x, y = randint(0, 1920), randint(0, 1080)
shape = MyGraphicsRectItem(self.nei, shapes, -size/2, -size/2, size, size)
shape.setPos(x,y)
# TODO:brush
label_text = str(IPv4Address(k.router_id)) if k.is_router else str(k.address) # Surprisingly works for all the possible addresses.
label = QtWidgets.QGraphicsSimpleTextItem(label_text, parent=shape)
label.setY(size*0.8)
text_width = label.boundingRect().width()
label.setX(-text_width/2)
shape.setData(0, k)
shape.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable | QtWidgets.QGraphicsItem.ItemIsSelectable)
shapes[k] = shape
for e in self.annot_topo.topology.edges:
start = shapes[e.source].pos()
end = shapes[e.target].pos()
qlinef = QtCore.QLineF(start, end)
line = QtWidgets.QGraphicsLineItem(qlinef)
line.setData(0, e)
self.nei[e.source].append(e)
self.nei[e.target].append(e)
shapes[e] = line
if self.highlighter is not None and self.highlighter in self.annot_topo.annotations:
ann = self.annot_topo.annotations[self.highlighter]
for shk in ann.for_vertex.keys() | ann.for_edge.keys():
shapes[shk].setPen(QtGui.QPen(QtGui.QColor('blue')))
for sh in shapes.values(): self.scene.addItem(sh)
main_window = MainWindow()
main_window.show() main_window.show()
app.exec() app.exec()

Loading…
Cancel
Save