]> git.proxmox.com Git - mirror_frr.git/blame - tests/topotests/isis-topo1/test_isis_topo1.py
Merge pull request #3394 from karamalla0406/frr3360
[mirror_frr.git] / tests / topotests / isis-topo1 / test_isis_topo1.py
CommitLineData
3aefe207
RZ
1#!/usr/bin/env python
2
3#
4# test_isis_topo1.py
5# Part of NetDEF Topology Tests
6#
7# Copyright (c) 2017 by
8# Network Device Education Foundation, Inc. ("NetDEF")
9#
10# Permission to use, copy, modify, and/or distribute this software
11# for any purpose with or without fee is hereby granted, provided
12# that the above copyright notice and this permission notice appear
13# in all copies.
14#
15# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES
16# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR
18# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
19# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
20# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
21# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
22# OF THIS SOFTWARE.
23#
24
25"""
26test_isis_topo1.py: Test ISIS topology.
27"""
28
67f1e9ed 29import collections
9e2201b8 30import functools
67f1e9ed 31import json
3aefe207 32import os
67f1e9ed 33import re
3aefe207
RZ
34import sys
35import pytest
cf469a23 36import time
3aefe207
RZ
37
38CWD = os.path.dirname(os.path.realpath(__file__))
39sys.path.append(os.path.join(CWD, '../'))
40
41# pylint: disable=C0413
42from lib import topotest
43from lib.topogen import Topogen, TopoRouter, get_topogen
44from lib.topolog import logger
45
46from mininet.topo import Topo
47
48
49class ISISTopo1(Topo):
50 "Simple two layer ISIS topology"
51 def build(self, *_args, **_opts):
52 "Build function"
53 tgen = get_topogen(self)
54
55 # Add ISIS routers:
56 # r1 r2
57 # | sw1 | sw2
58 # r3 r4
59 # | |
60 # sw3 sw4
61 # \ /
62 # r5
63 for routern in range(1, 6):
64 tgen.add_router('r{}'.format(routern))
65
66 # r1 <- sw1 -> r3
67 sw = tgen.add_switch('sw1')
68 sw.add_link(tgen.gears['r1'])
69 sw.add_link(tgen.gears['r3'])
70
71 # r2 <- sw2 -> r4
72 sw = tgen.add_switch('sw2')
73 sw.add_link(tgen.gears['r2'])
74 sw.add_link(tgen.gears['r4'])
75
76 # r3 <- sw3 -> r5
77 sw = tgen.add_switch('sw3')
78 sw.add_link(tgen.gears['r3'])
79 sw.add_link(tgen.gears['r5'])
80
81 # r4 <- sw4 -> r5
82 sw = tgen.add_switch('sw4')
83 sw.add_link(tgen.gears['r4'])
84 sw.add_link(tgen.gears['r5'])
85
86
87def setup_module(mod):
88 "Sets up the pytest environment"
89 tgen = Topogen(ISISTopo1, mod.__name__)
90 tgen.start_topology()
91
92 # For all registered routers, load the zebra configuration file
93 for rname, router in tgen.routers().iteritems():
94 router.load_config(
95 TopoRouter.RD_ZEBRA,
96 os.path.join(CWD, '{}/zebra.conf'.format(rname))
97 )
98 router.load_config(
99 TopoRouter.RD_ISIS,
100 os.path.join(CWD, '{}/isisd.conf'.format(rname))
101 )
102
103 # After loading the configurations, this function loads configured daemons.
104 tgen.start_router()
105
fc7752c1
RZ
106 has_version_20 = False
107 for router in tgen.routers().values():
108 if router.has_version('<', '3'):
109 has_version_20 = True
110
111 if has_version_20:
112 logger.info('Skipping ISIS tests for FRR 2.0')
113 tgen.set_error('ISIS has convergence problems with IPv6')
114
3aefe207
RZ
115
116def teardown_module(mod):
117 "Teardown the pytest environment"
118 tgen = get_topogen()
119
120 # This function tears down the whole topology.
121 tgen.stop_topology()
122
123
124def test_isis_convergence():
125 "Wait for the protocol to converge before starting to test"
126 tgen = get_topogen()
127 # Don't run this test if we have any failure.
128 if tgen.routers_have_failure():
129 pytest.skip(tgen.errors)
130
cf469a23 131 logger.info("waiting for ISIS protocol to converge")
67f1e9ed
RZ
132 # Code to generate the json files.
133 # for rname, router in tgen.routers().iteritems():
134 # open('/tmp/{}_topology.json'.format(rname), 'w').write(
135 # json.dumps(show_isis_topology(router), indent=2, sort_keys=True)
136 # )
9e2201b8
RZ
137
138 for rname, router in tgen.routers().iteritems():
139 filename = '{0}/{1}/{1}_topology.json'.format(CWD, rname)
140 expected = json.loads(open(filename).read())
141
142 def compare_isis_topology(router, expected):
143 "Helper function to test ISIS topology convergence."
cf469a23 144 actual = show_isis_topology(router)
9e2201b8
RZ
145 return topotest.json_cmp(actual, expected)
146
147 test_func = functools.partial(compare_isis_topology, router, expected)
148 (result, diff) = topotest.run_and_expect(test_func, None,
149 wait=0.5, count=120)
150 assert result, 'ISIS did not converge on {}:\n{}'.format(rname, diff)
151
3aefe207 152
e4d08d5b
RZ
153def test_isis_route_installation():
154 "Check whether all expected routes are present"
155 tgen = get_topogen()
156 # Don't run this test if we have any failure.
157 if tgen.routers_have_failure():
158 pytest.skip(tgen.errors)
159
160 logger.info('Checking routers for installed ISIS routes')
161
162 # Check for routes in 'show ip route json'
163 for rname, router in tgen.routers().iteritems():
164 filename = '{0}/{1}/{1}_route.json'.format(CWD, rname)
165 expected = json.loads(open(filename, 'r').read())
166 actual = router.vtysh_cmd('show ip route json', isjson=True)
29614f56
RZ
167
168 # Older FRR versions don't list interfaces in some ISIS routes
169 if router.has_version('<', '3.1'):
170 for network, routes in expected.iteritems():
171 for route in routes:
172 if route['protocol'] != 'isis':
173 continue
174
175 for nexthop in route['nexthops']:
d43cd0fd
RZ
176 nexthop.pop('interfaceIndex', None)
177 nexthop.pop('interfaceName', None)
29614f56 178
e4d08d5b
RZ
179 assertmsg = "Router '{}' routes mismatch".format(rname)
180 assert topotest.json_cmp(actual, expected) is None, assertmsg
181
182
2d013cda
RZ
183def test_isis_linux_route_installation():
184 "Check whether all expected routes are present and installed in the OS"
185 tgen = get_topogen()
186 # Don't run this test if we have any failure.
187 if tgen.routers_have_failure():
188 pytest.skip(tgen.errors)
189
190 logger.info('Checking routers for installed ISIS routes in OS')
191
192 # Check for routes in `ip route`
193 for rname, router in tgen.routers().iteritems():
194 filename = '{0}/{1}/{1}_route_linux.json'.format(CWD, rname)
195 expected = json.loads(open(filename, 'r').read())
196 actual = topotest.ip4_route(router)
29614f56
RZ
197
198 # Older FRR versions install routes using different proto
199 if router.has_version('<', '3.1'):
200 for network, netoptions in expected.iteritems():
201 if 'proto' in netoptions and netoptions['proto'] == '187':
202 netoptions['proto'] = 'zebra'
203
2d013cda
RZ
204 assertmsg = "Router '{}' OS routes mismatch".format(rname)
205 assert topotest.json_cmp(actual, expected) is None, assertmsg
88f83773
RZ
206
207
208def test_isis_route6_installation():
209 "Check whether all expected routes are present"
210 tgen = get_topogen()
211 # Don't run this test if we have any failure.
212 if tgen.routers_have_failure():
213 pytest.skip(tgen.errors)
214
215 logger.info('Checking routers for installed ISIS IPv6 routes')
216
217 # Check for routes in 'show ip route json'
218 for rname, router in tgen.routers().iteritems():
219 filename = '{0}/{1}/{1}_route6.json'.format(CWD, rname)
220 expected = json.loads(open(filename, 'r').read())
221 actual = router.vtysh_cmd('show ipv6 route json', isjson=True)
222
223 # Older FRR versions don't list interfaces in some ISIS routes
224 if router.has_version('<', '3.1'):
225 for network, routes in expected.iteritems():
226 for route in routes:
227 # Older versions display different metrics for IPv6 routes
228 route.pop('metric', None)
229
230 if route['protocol'] != 'isis':
231 continue
232
233 for nexthop in route['nexthops']:
234 nexthop.pop('interfaceIndex', None)
235 nexthop.pop('interfaceName', None)
236
237 assertmsg = "Router '{}' routes mismatch".format(rname)
238 assert topotest.json_cmp(actual, expected) is None, assertmsg
d4368260
RZ
239
240
241def test_isis_linux_route6_installation():
242 "Check whether all expected routes are present and installed in the OS"
243 tgen = get_topogen()
244 # Don't run this test if we have any failure.
245 if tgen.routers_have_failure():
246 pytest.skip(tgen.errors)
247
248 logger.info('Checking routers for installed ISIS IPv6 routes in OS')
249
250 # Check for routes in `ip route`
251 for rname, router in tgen.routers().iteritems():
252 filename = '{0}/{1}/{1}_route6_linux.json'.format(CWD, rname)
253 expected = json.loads(open(filename, 'r').read())
254 actual = topotest.ip6_route(router)
255
256 # Older FRR versions install routes using different proto
257 if router.has_version('<', '3.1'):
258 for network, netoptions in expected.iteritems():
259 if 'proto' in netoptions and netoptions['proto'] == '187':
260 netoptions['proto'] = 'zebra'
261
262 assertmsg = "Router '{}' OS routes mismatch".format(rname)
263 assert topotest.json_cmp(actual, expected) is None, assertmsg
2d013cda
RZ
264
265
3aefe207
RZ
266def test_memory_leak():
267 "Run the memory leak test and report results."
268 tgen = get_topogen()
269 if not tgen.is_memleak_enabled():
270 pytest.skip('Memory leak test/report is disabled')
271
272 tgen.report_memory_leaks()
273
274
275if __name__ == '__main__':
276 args = ["-s"] + sys.argv[1:]
277 sys.exit(pytest.main(args))
67f1e9ed
RZ
278
279
280#
281# Auxiliary functions
282#
283
284
285def dict_merge(dct, merge_dct):
286 """
287 Recursive dict merge. Inspired by :meth:``dict.update()``, instead of
288 updating only top-level keys, dict_merge recurses down into dicts nested
289 to an arbitrary depth, updating keys. The ``merge_dct`` is merged into
290 ``dct``.
291 :param dct: dict onto which the merge is executed
292 :param merge_dct: dct merged into dct
293 :return: None
294
295 Source:
296 https://gist.github.com/angstwad/bf22d1822c38a92ec0a9
297 """
298 for k, v in merge_dct.iteritems():
299 if (k in dct and isinstance(dct[k], dict)
300 and isinstance(merge_dct[k], collections.Mapping)):
301 dict_merge(dct[k], merge_dct[k])
302 else:
303 dct[k] = merge_dct[k]
304
305
306def parse_topology(lines, level):
307 """
308 Parse the output of 'show isis topology level-X' into a Python dict.
309 """
310 areas = {}
67f1e9ed 311 area = None
5836fac2 312 ipv = None
67f1e9ed
RZ
313
314 for line in lines:
5836fac2
RZ
315 area_match = re.match(r"Area (.+):", line)
316 if area_match:
67f1e9ed 317 area = area_match.group(1)
5836fac2
RZ
318 if area not in areas:
319 areas[area] = {
320 level: {
321 'ipv4': [],
322 'ipv6': []
323 }
324 }
325 ipv = None
326 continue
327 elif area is None:
67f1e9ed
RZ
328 continue
329
5836fac2
RZ
330 if re.match(r"IS\-IS paths to level-. routers that speak IPv6", line):
331 ipv = 'ipv6'
67f1e9ed 332 continue
5836fac2
RZ
333 if re.match(r"IS\-IS paths to level-. routers that speak IP", line):
334 ipv = 'ipv4'
67f1e9ed
RZ
335 continue
336
337 item_match = re.match(
338 r"([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+)", line)
339 if item_match is not None:
16b31141
RZ
340 # Skip header
341 if (item_match.group(1) == 'Vertex' and
342 item_match.group(2) == 'Type' and
343 item_match.group(3) == 'Metric' and
344 item_match.group(4) == 'Next-Hop' and
345 item_match.group(5) == 'Interface' and
346 item_match.group(6) == 'Parent'):
347 continue
348
5836fac2 349 areas[area][level][ipv].append({
67f1e9ed
RZ
350 'vertex': item_match.group(1),
351 'type': item_match.group(2),
352 'metric': item_match.group(3),
353 'next-hop': item_match.group(4),
354 'interface': item_match.group(5),
355 'parent': item_match.group(6),
356 })
357 continue
358
359 item_match = re.match(r"([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+)", line)
360 if item_match is not None:
5836fac2 361 areas[area][level][ipv].append({
67f1e9ed
RZ
362 'vertex': item_match.group(1),
363 'type': item_match.group(2),
364 'metric': item_match.group(3),
365 'parent': item_match.group(4),
366 })
367 continue
368
369 item_match = re.match(r"([^ ]+)", line)
370 if item_match is not None:
5836fac2 371 areas[area][level][ipv].append({'vertex': item_match.group(1)})
67f1e9ed
RZ
372 continue
373
67f1e9ed
RZ
374 return areas
375
376
377def show_isis_topology(router):
378 """
379 Get the ISIS topology in a dictionary format.
380
381 Sample:
382 {
383 'area-name': {
384 'level-1': [
385 {
386 'vertex': 'r1'
387 }
388 ],
389 'level-2': [
390 {
391 'vertex': '10.0.0.1/24',
392 'type': 'IP',
393 'parent': '0',
394 'metric': 'internal'
395 }
396 ]
397 },
398 'area-name-2': {
399 'level-2': [
400 {
401 "interface": "rX-ethY",
402 "metric": "Z",
403 "next-hop": "rA",
404 "parent": "rC(B)",
405 "type": "TE-IS",
406 "vertex": "rD"
407 }
408 ]
409 }
410 }
411 """
412 l1out = topotest.normalize_text(
413 router.vtysh_cmd('show isis topology level-1')
414 ).splitlines()
415 l2out = topotest.normalize_text(
416 router.vtysh_cmd('show isis topology level-2')
417 ).splitlines()
418
419 l1 = parse_topology(l1out, 'level-1')
420 l2 = parse_topology(l2out, 'level-2')
421
422 dict_merge(l1, l2)
423 return l1