0
0
mirror of https://gitlab.nic.cz/labs/bird.git synced 2024-10-18 01:54:08 +00:00

Flock tests: show ospf topology (used in ospf-base)

This commit is contained in:
Maria Matejka 2024-07-26 20:53:27 +02:00
parent c1bfc76a35
commit 8bb709187d
7 changed files with 489 additions and 112 deletions

View File

@ -0,0 +1,344 @@
m1:
ospf4:
ospf4:
neighbors:
192.0.2.1:
interface: ve31
ip: 192.0.2.1
position: PtP
priority: '1'
state: Full
192.0.2.18:
interface: ve12
ip: 192.0.2.18
position: PtP
priority: '1'
state: Full
ospf6:
ospf6:
neighbors:
192.0.2.1:
interface: ve31
ip: fe80::ac55:22ff:fe32:3610
position: PtP
priority: '1'
state: Full
192.0.2.18:
interface: ve12
ip: fe80::5c38:8eff:fe25:1d68
position: PtP
priority: '1'
state: Full
m2:
ospf4:
ospf4:
neighbors:
192.0.2.1:
interface: ve23
ip: 192.0.2.34
position: PtP
priority: '1'
state: Full
192.0.2.2:
interface: ve12
ip: 192.0.2.17
position: PtP
priority: '1'
state: Full
ospf6:
ospf6:
neighbors:
192.0.2.1:
interface: ve23
ip: fe80::7cda:1aff:feae:9831
position: PtP
priority: '1'
state: Full
192.0.2.2:
interface: ve12
ip: fe80::4cfd:f0ff:fe23:167
position: PtP
priority: '1'
state: Full
m3:
ospf4:
ospf4:
neighbors:
192.0.2.18:
interface: ve23
ip: 192.0.2.33
position: PtP
priority: '1'
state: Full
192.0.2.2:
interface: ve31
ip: 192.0.2.2
position: PtP
priority: '1'
state: Full
192.0.2.49:
interface: multi
ip: 192.0.2.99
position: "Other\t"
priority: '1'
state: 2-Way
192.0.2.82:
interface: multi
ip: 192.0.2.100
position: BDR
priority: '1'
state: Full
192.0.2.98:
interface: multi
ip: 192.0.2.98
position: DR
priority: '1'
state: Full
ospf6:
ospf6:
neighbors:
192.0.2.18:
interface: ve23
ip: fe80::8c41:8bff:fe8f:9446
position: PtP
priority: '1'
state: Full
192.0.2.2:
interface: ve31
ip: fe80::5c82:ccff:fe47:46df
position: PtP
priority: '1'
state: Full
192.0.2.49:
interface: multi
ip: fe80::5c5b:1eff:fe50:3793
position: "Other\t"
priority: '1'
state: 2-Way
192.0.2.82:
interface: multi
ip: fe80::9ce1:eaff:fe4b:b4f9
position: BDR
priority: '1'
state: Full
192.0.2.98:
interface: multi
ip: fe80::2cf6:51ff:feaa:3b4f
position: DR
priority: '1'
state: Full
m4:
ospf4:
ospf4:
neighbors:
192.0.2.1:
interface: multi
ip: 192.0.2.97
position: "Other\t"
priority: '1'
state: Full
192.0.2.49:
interface: multi
ip: 192.0.2.99
position: "Other\t"
priority: '1'
state: Full
192.0.2.82:
interface: multi
ip: 192.0.2.100
position: BDR
priority: '1'
state: Full
ospf6:
ospf6:
neighbors:
192.0.2.1:
interface: multi
ip: fe80::dc8f:6bff:fe0f:6ad9
position: "Other\t"
priority: '1'
state: Full
192.0.2.49:
interface: multi
ip: fe80::5c5b:1eff:fe50:3793
position: "Other\t"
priority: '1'
state: Full
192.0.2.82:
interface: multi
ip: fe80::9ce1:eaff:fe4b:b4f9
position: BDR
priority: '1'
state: Full
m5:
ospf4:
ospf4:
neighbors:
192.0.2.1:
interface: multi
ip: 192.0.2.97
position: "Other\t"
priority: '1'
state: 2-Way
192.0.2.50:
interface: ve57
ip: 192.0.2.50
position: PtP
priority: '1'
state: Full
192.0.2.82:
interface: multi
ip: 192.0.2.100
position: BDR
priority: '1'
state: Full
192.0.2.98:
interface: multi
ip: 192.0.2.98
position: DR
priority: '1'
state: Full
ospf6:
ospf6:
neighbors:
192.0.2.1:
interface: multi
ip: fe80::dc8f:6bff:fe0f:6ad9
position: "Other\t"
priority: '1'
state: 2-Way
192.0.2.50:
interface: ve57
ip: fe80::9c97:afff:fe62:f695
position: PtP
priority: '1'
state: Full
192.0.2.82:
interface: multi
ip: fe80::9ce1:eaff:fe4b:b4f9
position: BDR
priority: '1'
state: Full
192.0.2.98:
interface: multi
ip: fe80::2cf6:51ff:feaa:3b4f
position: DR
priority: '1'
state: Full
m6:
ospf4:
ospf4:
neighbors:
192.0.2.1:
interface: multi
ip: 192.0.2.97
position: "Other\t"
priority: '1'
state: Full
192.0.2.49:
interface: multi
ip: 192.0.2.99
position: "Other\t"
priority: '1'
state: Full
192.0.2.66:
interface: ve86
ip: 192.0.2.81
position: PtP
priority: '1'
state: Full
192.0.2.98:
interface: multi
ip: 192.0.2.98
position: DR
priority: '1'
state: Full
ospf6:
ospf6:
neighbors:
192.0.2.1:
interface: multi
ip: fe80::dc8f:6bff:fe0f:6ad9
position: "Other\t"
priority: '1'
state: Full
192.0.2.49:
interface: multi
ip: fe80::5c5b:1eff:fe50:3793
position: "Other\t"
priority: '1'
state: Full
192.0.2.66:
interface: ve86
ip: fe80::6cc9:f2ff:feb9:754f
position: PtP
priority: '1'
state: Full
192.0.2.98:
interface: multi
ip: fe80::2cf6:51ff:feaa:3b4f
position: DR
priority: '1'
state: Full
m7:
ospf4:
ospf4:
neighbors:
192.0.2.49:
interface: ve57
ip: 192.0.2.49
position: PtP
priority: '1'
state: Full
192.0.2.66:
interface: ve78
ip: 192.0.2.66
position: PtP
priority: '1'
state: Full
ospf6:
ospf6:
neighbors:
192.0.2.49:
interface: ve57
ip: fe80::2c33:44ff:fe0f:454b
position: PtP
priority: '1'
state: Full
192.0.2.66:
interface: ve78
ip: fe80::8c67:1aff:fe6f:1193
position: PtP
priority: '1'
state: Full
m8:
ospf4:
ospf4:
neighbors:
192.0.2.50:
interface: ve78
ip: 192.0.2.65
position: PtP
priority: '1'
state: Full
192.0.2.82:
interface: ve86
ip: 192.0.2.82
position: PtP
priority: '1'
state: Full
ospf6:
ospf6:
neighbors:
192.0.2.50:
interface: ve78
ip: fe80::ac21:71ff:fea9:abe
position: PtP
priority: '1'
state: Full
192.0.2.82:
interface: ve86
ip: fe80::5cf4:d5ff:fe79:c89
position: PtP
priority: '1'
state: Full

View File

@ -1,7 +1,7 @@
#!/usr/bin/python3 #!/usr/bin/python3
import asyncio import asyncio
from python.BIRD.Test import Test, BIRDInstance, DumpRIB, DumpLinuxKRT from python.BIRD.Test import Test, BIRDInstance, DumpRIB, DumpLinuxKRT, DumpOSPFNeighbors
from python.BIRD.LogChecker import LogExpectedStub from python.BIRD.LogChecker import LogExpectedStub
class ThisTest(Test): class ThisTest(Test):
@ -35,4 +35,5 @@ class ThisTest(Test):
await asyncio.gather(*[ await asyncio.gather(*[
DumpRIB(self, 30, "rib-startup")(), DumpRIB(self, 30, "rib-startup")(),
DumpLinuxKRT(self, 30, "fib-startup")(), DumpLinuxKRT(self, 30, "fib-startup")(),
DumpOSPFNeighbors(self, 30, "ospf-neighbors", protocols=["ospf4", "ospf6"])(),
]) ])

View File

@ -1,4 +1,5 @@
from . import ShowRoute from .ShowRoute import ShowRouteParser
from .ShowOSPF import ShowOSPFNeighborsParser
class Transport: class Transport:
pass pass
@ -24,6 +25,16 @@ class CLI:
async def disable(self, proto: str): async def disable(self, proto: str):
return await self.transport.send_cmd("disable", proto) return await self.transport.send_cmd("disable", proto)
async def cmd_send_parse(self, parser, *cmd):
result = await self.transport.send_cmd(*cmd)
if len(result["err"]):
raise Exception(f"Command {cmd} returned {result['err'].decode()}, stdout={result['out'].decode()}")
for line in result["out"].decode().split("\n"):
parser = parser.parse(line)
return parser.parse(None).result
async def show_route(self, table=["all"], args=[]): async def show_route(self, table=["all"], args=[]):
cmd = [ "show", "route" ] cmd = [ "show", "route" ]
for t in table: for t in table:
@ -31,9 +42,8 @@ class CLI:
cmd.append(t) cmd.append(t)
cmd += args cmd += args
return await self.cmd_send_parse(ShowRouteParser(), *cmd)
result = await self.transport.send_cmd(*cmd) async def show_ospf_neighbors(self, proto: str):
if len(result["err"]): return await self.cmd_send_parse(ShowOSPFNeighborsParser(),
raise Exception(f"Command {cmd} returned {result['err'].decode()}, stdout={result['out'].decode()}") "show", "ospf", "neighbors", proto)
return ShowRoute.parse(result["out"].decode())

68
python/BIRD/CLIParser.py Normal file
View File

@ -0,0 +1,68 @@
import re
class ParserException(Exception):
pass
def subparser(base):
def decorator(cls):
base.subparsers = { **base.subparsers, cls.entryRegex: cls }
return cls
return decorator
class CLIParser:
subparsers = {}
def __init__(self, groups=None, parent=None):
self.result = {}
self.parent = parent
self.cur = None
if parent is None:
assert(groups is None)
return
self.enter(groups)
def parse(self, line: str):
assert(self.cur == None)
if line is not None:
for k,v in self.subparsers.items():
if m := k.match(line):
self.cur = (c := v(groups=m.groups(), parent=self))
while c.cur is not None:
c = c.cur
return c
elif self.parent is None:
return self
try:
self.exit()
self.parent.cur = None
return self.parent.parse(line)
except Exception as e:
print(f"Failed to parse line: {line}")
raise e
def exit(self):
raise ParserException(f"Failed to match line to all regexes")
@subparser(CLIParser)
class VersionParser(CLIParser):
entryRegex = re.compile("BIRD ([0-9a-z._-]+) ready.")
def enter(self, groups):
self.version ,= groups
def exit(self):
if "version" in self.parent.result:
raise ParserException(f"Duplicate version line")
self.parent.result["version"] = self.version
@subparser(CLIParser)
class NothingParser(CLIParser):
entryRegex = re.compile("^$")
def enter(self, _):
pass
def exit(self):
pass

34
python/BIRD/ShowOSPF.py Normal file
View File

@ -0,0 +1,34 @@
import re
from .CLIParser import CLIParser, subparser, ParserException
class ShowOSPFNeighborsParser(CLIParser):
pass
@subparser(ShowOSPFNeighborsParser)
class ShowOSPFNeighborsProtocolParser(CLIParser):
entryRegex = re.compile("^(.*):$")
def enter(self, groups):
self.name ,= groups
def exit(self):
self.parent.result[self.name] = self.result
@subparser(ShowOSPFNeighborsProtocolParser)
class ShowOSPFNeighborsHeaderParser(CLIParser):
entryRegex = re.compile("^Router ID Pri State DTime Interface Router IP$")
def enter(self, _):
pass
def exit(self):
self.parent.result["neighbors"] = self.result
@subparser(ShowOSPFNeighborsHeaderParser)
class ShowOSPFNeighborsHeaderParser(CLIParser):
entryRegex = re.compile("([^ ]+)\s+([^ ]+)\s+([^/]+)/([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)$")
def enter(self, groups):
self.id, *rest = groups
self.result = dict(zip(
["priority", "state", "position", "timeout", "interface", "ip"], rest))
def exit(self):
self.parent.result[self.id] = self.result

View File

@ -1,77 +1,11 @@
import re import re
from .CLIParser import CLIParser, subparser, ParserException
class ParserException(Exception):
pass
def subparser(base):
def decorator(cls):
base.subparsers = { **base.subparsers, cls.entryRegex: cls }
return cls
return decorator
class CLIParser:
subparsers = {}
def __init__(self, groups=None, parent=None):
self.result = {}
self.parent = parent
self.cur = None
if parent is None:
assert(groups is None)
return
self.enter(groups)
def parse(self, line: str):
assert(self.cur == None)
if line is not None:
for k,v in self.subparsers.items():
if m := k.match(line):
self.cur = (c := v(groups=m.groups(), parent=self))
while c.cur is not None:
c = c.cur
return c
elif self.parent is None:
return self
try:
self.exit()
self.parent.cur = None
return self.parent.parse(line)
except Exception as e:
print(f"Failed to parse line: {line}")
raise e
def exit(self):
raise ParserException(f"Failed to match line to all regexes")
@subparser(CLIParser)
class VersionParser(CLIParser):
entryRegex = re.compile("BIRD ([0-9a-z._-]+) ready.")
def enter(self, groups):
self.version ,= groups
def exit(self):
if "version" in self.parent.result:
raise ParserException(f"Duplicate version line")
self.parent.result["version"] = self.version
class ShowRouteParser(CLIParser): class ShowRouteParser(CLIParser):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.result["tables"] = {} self.result["tables"] = {}
@subparser(ShowRouteParser)
class NothingParser(CLIParser):
entryRegex = re.compile("^$")
def enter(self, _):
pass
def exit(self):
pass
@subparser(ShowRouteParser) @subparser(ShowRouteParser)
class TableParser(CLIParser): class TableParser(CLIParser):
entryRegex = re.compile("^Table (.*):$") entryRegex = re.compile("^Table (.*):$")
@ -153,12 +87,3 @@ class InternalRouteHandlingValuesParser(CLIParser):
if k in self.parent.result: if k in self.parent.result:
raise ParserException(f"Duplicate internal value {k} in route") raise ParserException(f"Duplicate internal value {k} in route")
self.parent.result[k] = v self.parent.result[k] = v
def parse(data: str):
parser = ShowRouteParser()
for line in data.split("\n"):
parser = parser.parse(line)
parser = parser.parse(None)
return parser.result

View File

@ -239,6 +239,7 @@ class DumpCheck:
seen = [] seen = []
try: try:
async with asyncio.timeout(self.check_timeout) as to: async with asyncio.timeout(self.check_timeout) as to:
i = 0
while True: while True:
dump = await self.obtain() dump = await self.obtain()
try: try:
@ -252,6 +253,8 @@ class DumpCheck:
print(f"Differs at {' -> '.join([str(s) for s in reversed(d.tree)])}: {d.a} != {d.b}") print(f"Differs at {' -> '.join([str(s) for s in reversed(d.tree)])}: {d.a} != {d.b}")
seen.append(dump) seen.append(dump)
i += 1
print('\-|/'[i % 4] + "\010", end='', flush=True)
await asyncio.sleep(self.check_retry_timeout) await asyncio.sleep(self.check_retry_timeout)
except TimeoutError as e: except TimeoutError as e:
@ -302,6 +305,27 @@ class DumpRIB(DumpOnMachines):
del r[k] del r[k]
return d return d
class DumpOSPFNeighbors(DumpOnMachines):
def __init__(self, *args, protocols, **kwargs):
super().__init__(*args, **kwargs)
self.protocols = protocols
async def obtain_on_machine(self, mach):
d = await dict_gather({
p: mach.show_ospf_neighbors(proto=p)
for p in self.protocols
})
for p in d.values():
assert("version" in p)
del p["version"]
for pp in p.values():
for n in pp["neighbors"].values():
assert("timeout" in n)
del n["timeout"]
return d
class DumpLinuxKRT(DumpOnMachines): class DumpLinuxKRT(DumpOnMachines):
def __init__(self, *args, cmdargs=None, **kwargs): def __init__(self, *args, cmdargs=None, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -456,35 +480,6 @@ class Test:
await self.cleanup() await self.cleanup()
async def krt_dump(self, timeout, name, *args, full=True, machines=None, check_timeout=10, check_retry_timeout=0.5):
# Collect machines to dump
if machines is None:
machines = self.machine_index.values()
else:
machines = [
m if isinstance(m, CLI) else self.machine_index[m]
for m in machines
]
raw = await dict_gather({
(mach.mach.name, fam):
mach.mach.hypervisor.run_in(mach.mach.name, "ip", "-j", f"-{fam}", "route", "show", *args)
for mach in machines
for fam in ("4", "6", "M")
})
for k,v in raw.items():
if v["ret"] != 0 or len(v["err"]) != 0:
raise Exception(f"Failed to gather krt dump for {k}: ret={v['ret']}, {v['err']}")
dump = dict_expand({ k: json.loads(v["out"]) for k,v in raw.items()})
print(dump)
name = "krt.yaml"
with open(name, "w") as y:
yaml.dump(dump, y)
if __name__ == "__main__": if __name__ == "__main__":
name = sys.argv[1] name = sys.argv[1]
mode = Test.CHECK mode = Test.CHECK