From: Rafael Zalamena Date: Wed, 13 Dec 2017 19:55:28 +0000 (-0200) Subject: isis-topo1: test ISIS topology convergence X-Git-Tag: frr-7.1-dev~151^2~169 X-Git-Url: https://git.puffer.fish/?a=commitdiff_plain;h=67f1e9ed09c6a650facccf02a8adc5a5ce3f9be9;p=matthieu%2Ffrr.git isis-topo1: test ISIS topology convergence Add function to parse 'show isis topology' and expect the correct convergence result. Signed-off-by: Rafael Zalamena --- diff --git a/tests/topotests/isis-topo1/r1/r1_topology.json b/tests/topotests/isis-topo1/r1/r1_topology.json new file mode 100644 index 0000000000..86849d1419 --- /dev/null +++ b/tests/topotests/isis-topo1/r1/r1_topology.json @@ -0,0 +1,28 @@ +{ + "1": { + "level-1": [ + { + "vertex": "r1" + } + ], + "level-2": [ + { + "vertex": "r1" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP", + "vertex": "10.0.20.0/24" + }, + { + "interface": "r1-eth0", + "metric": "10", + "next-hop": "r3", + "parent": "r1(4)", + "type": "TE-IS", + "vertex": "r3" + } + ] + } +} diff --git a/tests/topotests/isis-topo1/r2/r2_topology.json b/tests/topotests/isis-topo1/r2/r2_topology.json new file mode 100644 index 0000000000..a6471b280f --- /dev/null +++ b/tests/topotests/isis-topo1/r2/r2_topology.json @@ -0,0 +1,28 @@ +{ + "1": { + "level-1": [ + { + "vertex": "r2" + } + ], + "level-2": [ + { + "vertex": "r2" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP", + "vertex": "10.0.21.0/24" + }, + { + "interface": "r2-eth0", + "metric": "10", + "next-hop": "r4", + "parent": "r2(4)", + "type": "TE-IS", + "vertex": "r4" + } + ] + } +} diff --git a/tests/topotests/isis-topo1/r3/r3_topology.json b/tests/topotests/isis-topo1/r3/r3_topology.json new file mode 100644 index 0000000000..ebc0255526 --- /dev/null +++ b/tests/topotests/isis-topo1/r3/r3_topology.json @@ -0,0 +1,42 @@ +{ + "1": { + "level-1": [ + { + "vertex": "r3" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP", + "vertex": "10.0.10.0/24" + }, + { + "interface": "r3-eth1", + "metric": "10", + "next-hop": "r5", + "parent": "r3(4)", + "type": "TE-IS", + "vertex": "r5" + } + ], + "level-2": [ + { + "vertex": "r3" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP", + "vertex": "10.0.20.0/24" + }, + { + "interface": "r3-eth0", + "metric": "10", + "next-hop": "r1", + "parent": "r3(4)", + "type": "TE-IS", + "vertex": "r1" + } + ] + } +} diff --git a/tests/topotests/isis-topo1/r4/r4_topology.json b/tests/topotests/isis-topo1/r4/r4_topology.json new file mode 100644 index 0000000000..eaab35b69c --- /dev/null +++ b/tests/topotests/isis-topo1/r4/r4_topology.json @@ -0,0 +1,42 @@ +{ + "1": { + "level-1": [ + { + "vertex": "r4" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP", + "vertex": "10.0.11.0/24" + }, + { + "interface": "r4-eth1", + "metric": "10", + "next-hop": "r5", + "parent": "r4(4)", + "type": "TE-IS", + "vertex": "r5" + } + ], + "level-2": [ + { + "vertex": "r4" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP", + "vertex": "10.0.21.0/24" + }, + { + "interface": "r4-eth0", + "metric": "10", + "next-hop": "r2", + "parent": "r4(4)", + "type": "TE-IS", + "vertex": "r2" + } + ] + } +} diff --git a/tests/topotests/isis-topo1/r5/r5_topology.json b/tests/topotests/isis-topo1/r5/r5_topology.json new file mode 100644 index 0000000000..5294340b1b --- /dev/null +++ b/tests/topotests/isis-topo1/r5/r5_topology.json @@ -0,0 +1,38 @@ +{ + "1": { + "level-1": [ + { + "vertex": "r5" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP", + "vertex": "10.0.10.0/24" + }, + { + "metric": "internal", + "parent": "0", + "type": "IP", + "vertex": "10.0.11.0/24" + }, + { + "interface": "r5-eth0", + "metric": "10", + "next-hop": "r3", + "parent": "r5(4)", + "type": "TE-IS", + "vertex": "r3" + }, + { + "interface": "r5-eth1", + "metric": "10", + "next-hop": "r4", + "parent": "r5(4)", + "type": "TE-IS", + "vertex": "r4" + } + ], + "level-2": [] + } +} diff --git a/tests/topotests/isis-topo1/test_isis_topo1.py b/tests/topotests/isis-topo1/test_isis_topo1.py index 38308510df..1da2204225 100644 --- a/tests/topotests/isis-topo1/test_isis_topo1.py +++ b/tests/topotests/isis-topo1/test_isis_topo1.py @@ -26,7 +26,10 @@ test_isis_topo1.py: Test ISIS topology. """ +import collections +import json import os +import re import sys import pytest @@ -114,7 +117,20 @@ def test_isis_convergence(): if tgen.routers_have_failure(): pytest.skip(tgen.errors) - topotest.sleep(10, "waiting for ISIS protocol to converge") + topotest.sleep(45, "waiting for ISIS protocol to converge") + + # Code to generate the json files. + # for rname, router in tgen.routers().iteritems(): + # open('/tmp/{}_topology.json'.format(rname), 'w').write( + # json.dumps(show_isis_topology(router), indent=2, sort_keys=True) + # ) + + for rname, router in tgen.routers().iteritems(): + filename = '{0}/{1}/{1}_topology.json'.format(CWD, rname) + expected = json.loads(open(filename, 'r').read()) + actual = show_isis_topology(router) + assertmsg = "Router '{}' topology mismatch".format(rname) + assert topotest.json_cmp(actual, expected) is None, assertmsg def test_memory_leak(): @@ -129,3 +145,135 @@ def test_memory_leak(): if __name__ == '__main__': args = ["-s"] + sys.argv[1:] sys.exit(pytest.main(args)) + + +# +# Auxiliary functions +# + + +def dict_merge(dct, merge_dct): + """ + Recursive dict merge. Inspired by :meth:``dict.update()``, instead of + updating only top-level keys, dict_merge recurses down into dicts nested + to an arbitrary depth, updating keys. The ``merge_dct`` is merged into + ``dct``. + :param dct: dict onto which the merge is executed + :param merge_dct: dct merged into dct + :return: None + + Source: + https://gist.github.com/angstwad/bf22d1822c38a92ec0a9 + """ + for k, v in merge_dct.iteritems(): + if (k in dct and isinstance(dct[k], dict) + and isinstance(merge_dct[k], collections.Mapping)): + dict_merge(dct[k], merge_dct[k]) + else: + dct[k] = merge_dct[k] + + +def parse_topology(lines, level): + """ + Parse the output of 'show isis topology level-X' into a Python dict. + """ + areas = {} + in_area = False + area = None + + for line in lines: + if not in_area: + area_match = re.match(r"Area (.+):", line) + if not area_match: + continue + + area = area_match.group(1) + areas[area] = {level: []} + in_area = True + continue + + if re.match(r"IS\-IS paths to", line): + continue + if re.match(r"Vertex Type Metric Next\-Hop Interface Parent", line): + continue + + item_match = re.match( + r"([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+)", line) + if item_match is not None: + areas[area][level].append({ + 'vertex': item_match.group(1), + 'type': item_match.group(2), + 'metric': item_match.group(3), + 'next-hop': item_match.group(4), + 'interface': item_match.group(5), + 'parent': item_match.group(6), + }) + continue + + item_match = re.match(r"([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+)", line) + if item_match is not None: + areas[area][level].append({ + 'vertex': item_match.group(1), + 'type': item_match.group(2), + 'metric': item_match.group(3), + 'parent': item_match.group(4), + }) + continue + + item_match = re.match(r"([^ ]+)", line) + if item_match is not None: + areas[area][level].append({'vertex': item_match.group(1)}) + continue + + in_area = False + + return areas + + +def show_isis_topology(router): + """ + Get the ISIS topology in a dictionary format. + + Sample: + { + 'area-name': { + 'level-1': [ + { + 'vertex': 'r1' + } + ], + 'level-2': [ + { + 'vertex': '10.0.0.1/24', + 'type': 'IP', + 'parent': '0', + 'metric': 'internal' + } + ] + }, + 'area-name-2': { + 'level-2': [ + { + "interface": "rX-ethY", + "metric": "Z", + "next-hop": "rA", + "parent": "rC(B)", + "type": "TE-IS", + "vertex": "rD" + } + ] + } + } + """ + l1out = topotest.normalize_text( + router.vtysh_cmd('show isis topology level-1') + ).splitlines() + l2out = topotest.normalize_text( + router.vtysh_cmd('show isis topology level-2') + ).splitlines() + + l1 = parse_topology(l1out, 'level-1') + l2 = parse_topology(l2out, 'level-2') + + dict_merge(l1, l2) + return l1