Merge branch 'socket_api'

master
LEdoian 1 year ago
commit cafdd3cd07

2
.gitignore vendored

@ -6,3 +6,5 @@ __pycache__/
/env/ /env/
current.ospf current.ospf
/venv/

@ -144,7 +144,7 @@ class AnnotatedTopology:
# TODO: maybe class to handle sets of annotators? # TODO: maybe class to handle sets of annotators?
def annotate_topology(topology, def annotate_topology(topology,
annotators: Sequence[Callable[[AnnotatedTopology], AnnotatedTopology]], annotators: Sequence[Callable[[AnnotatedTopology], AnnotatedTopology]],
initial_annotation: AnnotatedTopology = None, initial_annotation: AnnotatedTopology | None = None,
) -> AnnotatedTopology: ) -> AnnotatedTopology:
"""Runs all the annotators and assigns all the tags. """Runs all the annotators and assigns all the tags.

@ -1,6 +1,6 @@
"""Common TopologyProviders""" """Common TopologyProviders"""
from birdvisu import ospffile from birdvisu import ospffile, ospfsock
from birdvisu.maps_new import TopologyProvider, Router, Network, Link, Topology from birdvisu.maps_new import TopologyProvider, Router, Network, Link, Topology
import subprocess import subprocess
import re import re
@ -16,7 +16,7 @@ class OspfDataTopologyProvider(TopologyProvider):
# feel trustworthy (I don't really understand them, but I think they # feel trustworthy (I don't really understand them, but I think they
# depend on designated routers and interface numbers, changes of which # depend on designated routers and interface numbers, changes of which
# should not cause us to think that is a different network) # should not cause us to think that is a different network)
self.network_renames = {} self.network_renames : dict[str,str] = {}
# We only care for areas: # We only care for areas:
# TODO: Should we support global visualisation configuration? # TODO: Should we support global visualisation configuration?
for directive, details in parsed: for directive, details in parsed:
@ -125,3 +125,15 @@ class RunningBirdTopologyProvider(TopologyProvider):
def get_topology(self): def get_topology(self):
return self.topology return self.topology
class BirdSocketTopologyProvider(TopologyProvider):
"""This one is dynamic, provides new topology every time."""
def __init__(self, socket_filename='/run/bird/bird.ctl'):
self.socket_filename = socket_filename
def get_topology(self):
birdsock = ospfsock.BirdSocketConnection()
resp = birdsock.request('show ospf state')
# TODO: Naming things this is not exactly parser…
parser = OspfDataTopologyProvider(resp.text)
return parser.get_topology()

@ -0,0 +1,82 @@
"""Simple utility to interface with BIRD using the control socket."""
from dataclasses import dataclass,field
import socket as sk
from string import digits
class BirdError(Exception):
def __init__(self, code, where, text):
self.code = code
self.start = where[0]
self.end = where[1]
self.text = text
@dataclass
class BirdResponse:
text: str = ''
codes: list[tuple[tuple[int, int], str]] = field(default_factory=list) # List of line ranges (inclusive) and the respective code.
def raise_exceptions(self) -> None:
# If the response contains any errors, raise them as BirdErrors
# TODO Py3.11: use ExceptionGroups?
# Until then, let's just report the first one?
assert len(self.codes) >= 1, "Not a valid response."
for where, code in self.codes:
if code.startswith('9'):
# Extract text
start = where[0] - 1
end = where[1]
print(start,end)
errorlines = self.text.splitlines(keepends=True)[start:end]
exc = BirdError(code, where, ''.join(errorlines))
raise exc
class BirdSocketConnection:
def __init__(self, sockpath='/run/bird/bird.ctl'):
self.socket = sk.socket(sk.AF_UNIX, sk.SOCK_STREAM)
self.socket.setblocking(False)
self.socket.connect(sockpath)
# Bird announces itself by printing version. We save the response just in case.
self.bird_prologue = self._parse_response()
def request(self, req: str) -> BirdResponse:
if not req.endswith('\n'):
req = req + '\n'
binreq = req.encode()
self.socket.send(binreq)
return self._parse_response()
def _parse_response(self) -> BirdResponse:
# We even read from the socket here in order to have the whole protocol
# described at one place. Therefore other parts of the code do not need
# to know anything about the protocol (maybe apart from the meaning of
# status codes, if they are interested…)
result = BirdResponse()
code : str | None = None # string because of leading zeroes, and it has no numeric interpretation anyway
start = None
cont = True
f = self.socket.makefile('r')
for i, line in enumerate(f, start=1):
assert cont, "WTF bad format (should not continue)."
if line.startswith(tuple(digits)):
if start is not None:
end = i-1
result.codes.append(((start, end), code))
code = line[:4]
start = i
cont = line[4] == '-'
text = line[5:]
else:
assert cont == True
text = line[1:]
result.text += text # Invariant: LF as line terminator
if not cont:
# Finalization. We do not have i after the end of the cycle,
# but this is the last iteration.
end = i
result.codes.append(((start, end), code))
break
result.raise_exceptions()
return result

@ -14,7 +14,7 @@ with open(ref_topo_file) as ref_file:
ref_topo = providers.OspfFileTopologyProvider(ref_file).get_topology() ref_topo = providers.OspfFileTopologyProvider(ref_file).get_topology()
try: try:
cur_topo = providers.RunningBirdTopologyProvider().get_topology() cur_topo = providers.BirdSocketTopologyProvider().get_topology()
except OSError: except OSError:
# HACK! # HACK!
import traceback as tb import traceback as tb
@ -95,6 +95,16 @@ for tagsrc in [
scene.addItem(taglist[-1]) scene.addItem(taglist[-1])
view = QtWidgets.QGraphicsView(scene) view = QtWidgets.QGraphicsView(scene)
view.show() #view.show()
main_window = QtWidgets.QMainWindow()
main_window.setCentralWidget(view)
menu = main_window.menuBar().addMenu('Hello')
act = QtGui.QAction('Hi')
#act.setToolTip('Howdy')
menu.addAction(act)
main_window.show()
app.exec() app.exec()

Loading…
Cancel
Save