mirror of
https://gitlab.nic.cz/labs/bird.git
synced 2024-12-22 01:31:55 +00:00
Flock tests: show ospf topology (used in ospf-base)
This commit is contained in:
parent
c1bfc76a35
commit
8bb709187d
344
flock/ospf-base/dump-0003-ospf-neighbors.yaml
Normal file
344
flock/ospf-base/dump-0003-ospf-neighbors.yaml
Normal 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
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
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
|
||||
|
||||
class ThisTest(Test):
|
||||
@ -35,4 +35,5 @@ class ThisTest(Test):
|
||||
await asyncio.gather(*[
|
||||
DumpRIB(self, 30, "rib-startup")(),
|
||||
DumpLinuxKRT(self, 30, "fib-startup")(),
|
||||
DumpOSPFNeighbors(self, 30, "ospf-neighbors", protocols=["ospf4", "ospf6"])(),
|
||||
])
|
||||
|
@ -1,4 +1,5 @@
|
||||
from . import ShowRoute
|
||||
from .ShowRoute import ShowRouteParser
|
||||
from .ShowOSPF import ShowOSPFNeighborsParser
|
||||
|
||||
class Transport:
|
||||
pass
|
||||
@ -24,6 +25,16 @@ class CLI:
|
||||
async def disable(self, proto: str):
|
||||
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=[]):
|
||||
cmd = [ "show", "route" ]
|
||||
for t in table:
|
||||
@ -31,9 +42,8 @@ class CLI:
|
||||
cmd.append(t)
|
||||
|
||||
cmd += args
|
||||
return await self.cmd_send_parse(ShowRouteParser(), *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()}")
|
||||
|
||||
return ShowRoute.parse(result["out"].decode())
|
||||
async def show_ospf_neighbors(self, proto: str):
|
||||
return await self.cmd_send_parse(ShowOSPFNeighborsParser(),
|
||||
"show", "ospf", "neighbors", proto)
|
||||
|
68
python/BIRD/CLIParser.py
Normal file
68
python/BIRD/CLIParser.py
Normal 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
34
python/BIRD/ShowOSPF.py
Normal 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
|
@ -1,77 +1,11 @@
|
||||
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
|
||||
from .CLIParser import CLIParser, subparser, ParserException
|
||||
|
||||
class ShowRouteParser(CLIParser):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.result["tables"] = {}
|
||||
|
||||
@subparser(ShowRouteParser)
|
||||
class NothingParser(CLIParser):
|
||||
entryRegex = re.compile("^$")
|
||||
def enter(self, _):
|
||||
pass
|
||||
|
||||
def exit(self):
|
||||
pass
|
||||
|
||||
@subparser(ShowRouteParser)
|
||||
class TableParser(CLIParser):
|
||||
entryRegex = re.compile("^Table (.*):$")
|
||||
@ -153,12 +87,3 @@ class InternalRouteHandlingValuesParser(CLIParser):
|
||||
if k in self.parent.result:
|
||||
raise ParserException(f"Duplicate internal value {k} in route")
|
||||
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
|
||||
|
@ -239,6 +239,7 @@ class DumpCheck:
|
||||
seen = []
|
||||
try:
|
||||
async with asyncio.timeout(self.check_timeout) as to:
|
||||
i = 0
|
||||
while True:
|
||||
dump = await self.obtain()
|
||||
try:
|
||||
@ -252,6 +253,8 @@ class DumpCheck:
|
||||
print(f"Differs at {' -> '.join([str(s) for s in reversed(d.tree)])}: {d.a} != {d.b}")
|
||||
|
||||
seen.append(dump)
|
||||
i += 1
|
||||
print('\-|/'[i % 4] + "\010", end='', flush=True)
|
||||
await asyncio.sleep(self.check_retry_timeout)
|
||||
|
||||
except TimeoutError as e:
|
||||
@ -302,6 +305,27 @@ class DumpRIB(DumpOnMachines):
|
||||
del r[k]
|
||||
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):
|
||||
def __init__(self, *args, cmdargs=None, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@ -456,35 +480,6 @@ class Test:
|
||||
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__":
|
||||
name = sys.argv[1]
|
||||
mode = Test.CHECK
|
||||
|
Loading…
Reference in New Issue
Block a user