]> git.proxmox.com Git - mirror_frr.git/blame - tests/topotests/all_protocol_startup/test_all_protocol_startup.py
bgpd: add counter of displayed show bgp summary when filtering
[mirror_frr.git] / tests / topotests / all_protocol_startup / test_all_protocol_startup.py
CommitLineData
4501fbca
MW
1#!/usr/bin/env python
2
3#
4# test_all_protocol_startup.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_all_protocol_startup.py: Test of all protocols at same time
27
28"""
29
30import os
31import re
32import sys
4501fbca 33import pytest
6a57e103 34import glob
4501fbca
MW
35from time import sleep
36
37from mininet.topo import Topo
38from mininet.net import Mininet
39from mininet.node import Node, OVSSwitch, Host
40from mininet.log import setLogLevel, info
41from mininet.cli import CLI
42from mininet.link import Intf
43
44from functools import partial
45
2c647bcd
DS
46pytestmark = [
47 pytest.mark.babeld,
48 pytest.mark.bgpd,
49 pytest.mark.isisd,
50 pytest.mark.nhrpd,
51 pytest.mark.ospfd,
52 pytest.mark.pbrd,
53 pytest.mark.ripd,
54]
6907ac7e 55
4501fbca
MW
56sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
57from lib import topotest
58
59fatal_error = ""
60
61
62#####################################################
63##
64## Network Topology Definition
65##
66#####################################################
67
701a0192 68
4501fbca
MW
69class NetworkTopo(Topo):
70 "All Protocol Startup Test"
71
72 def build(self, **_opts):
73
74 # Setup Routers
75 router = {}
76 #
77 # Setup Main Router
701a0192 78 router[1] = topotest.addRouter(self, "r1")
4501fbca
MW
79 #
80
81 # Setup Switches
82 switch = {}
83 #
84 for i in range(0, 10):
701a0192 85 switch[i] = self.addSwitch("sw%s" % i, cls=topotest.LegacySwitch)
86 self.addLink(switch[i], router[1], intfName2="r1-eth%s" % i)
4501fbca
MW
87
88
89#####################################################
90##
91## Tests starting
92##
93#####################################################
94
13f93db3 95
4501fbca
MW
96def setup_module(module):
97 global topo, net
98 global fatal_error
99
100 print("\n\n** %s: Setup Topology" % module.__name__)
101 print("******************************************\n")
102
103 print("Cleanup old Mininet runs")
701a0192 104 os.system("sudo mn -c > /dev/null 2>&1")
105 os.system("sudo rm /tmp/r* > /dev/null 2>&1")
4501fbca
MW
106
107 thisDir = os.path.dirname(os.path.realpath(__file__))
108 topo = NetworkTopo()
109
110 net = Mininet(controller=None, topo=topo)
111 net.start()
112
701a0192 113 if net["r1"].get_routertype() != "frr":
4501fbca 114 fatal_error = "Test is only implemented for FRR"
701a0192 115 sys.stderr.write("\n\nTest is only implemented for FRR - Skipping\n\n")
4501fbca 116 pytest.skip(fatal_error)
701a0192 117
4501fbca
MW
118 # Starting Routers
119 #
120 # Main router
121 for i in range(1, 2):
701a0192 122 net["r%s" % i].loadConf("zebra", "%s/r%s/zebra.conf" % (thisDir, i))
123 net["r%s" % i].loadConf("ripd", "%s/r%s/ripd.conf" % (thisDir, i))
124 net["r%s" % i].loadConf("ripngd", "%s/r%s/ripngd.conf" % (thisDir, i))
125 net["r%s" % i].loadConf("ospfd", "%s/r%s/ospfd.conf" % (thisDir, i))
126 if net["r1"].checkRouterVersion("<", "4.0"):
127 net["r%s" % i].loadConf(
128 "ospf6d", "%s/r%s/ospf6d.conf-pre-v4" % (thisDir, i)
129 )
11761ab0 130 else:
701a0192 131 net["r%s" % i].loadConf("ospf6d", "%s/r%s/ospf6d.conf" % (thisDir, i))
132 net["r%s" % i].loadConf("isisd", "%s/r%s/isisd.conf" % (thisDir, i))
133 net["r%s" % i].loadConf("bgpd", "%s/r%s/bgpd.conf" % (thisDir, i))
134 if net["r%s" % i].daemon_available("ldpd"):
4501fbca 135 # Only test LDPd if it's installed and Kernel >= 4.5
701a0192 136 net["r%s" % i].loadConf("ldpd", "%s/r%s/ldpd.conf" % (thisDir, i))
137 net["r%s" % i].loadConf("sharpd")
138 net["r%s" % i].loadConf("nhrpd", "%s/r%s/nhrpd.conf" % (thisDir, i))
139 net["r%s" % i].loadConf("babeld", "%s/r%s/babeld.conf" % (thisDir, i))
140 net["r%s" % i].loadConf("pbrd", "%s/r%s/pbrd.conf" % (thisDir, i))
141 net["r%s" % i].startRouter()
4501fbca 142
622c4996 143 # For debugging after starting FRR daemons, uncomment the next line
4501fbca
MW
144 # CLI(net)
145
146
147def teardown_module(module):
148 global net
149
150 print("\n\n** %s: Shutdown Topology" % module.__name__)
151 print("******************************************\n")
152
153 # End - Shutdown network
154 net.stop()
155
156
157def test_router_running():
158 global fatal_error
159 global net
160
161 # Skip if previous fatal error condition is raised
701a0192 162 if fatal_error != "":
4501fbca
MW
163 pytest.skip(fatal_error)
164
622c4996 165 print("\n\n** Check if FRR is running on each Router node")
4501fbca
MW
166 print("******************************************\n")
167 sleep(5)
168
169 # Starting Routers
170 for i in range(1, 2):
701a0192 171 fatal_error = net["r%s" % i].checkRouterRunning()
4501fbca
MW
172 assert fatal_error == "", fatal_error
173
622c4996 174 # For debugging after starting FRR daemons, uncomment the next line
4501fbca
MW
175 # CLI(net)
176
177
178def test_error_messages_vtysh():
179 global fatal_error
180 global net
181
182 # Skip if previous fatal error condition is raised
701a0192 183 if fatal_error != "":
4501fbca
MW
184 pytest.skip(fatal_error)
185
186 print("\n\n** Check for error messages on VTYSH")
187 print("******************************************\n")
188
189 failures = 0
190 for i in range(1, 2):
191 #
192 # First checking Standard Output
193 #
194
195 # VTYSH output from router
701a0192 196 vtystdout = net["r%s" % i].cmd('vtysh -c "show version" 2> /dev/null').rstrip()
4501fbca
MW
197
198 # Fix newlines (make them all the same)
701a0192 199 vtystdout = ("\n".join(vtystdout.splitlines()) + "\n").rstrip()
4501fbca
MW
200 # Drop everything starting with "FRRouting X.xx" message
201 vtystdout = re.sub(r"FRRouting [0-9]+.*", "", vtystdout, flags=re.DOTALL)
202
701a0192 203 if vtystdout == "":
4501fbca
MW
204 print("r%s StdOut ok" % i)
205
701a0192 206 assert vtystdout == "", "Vtysh StdOut Output check failed for router r%s" % i
798fb593 207
4501fbca
MW
208 #
209 # Second checking Standard Error
210 #
211
212 # VTYSH StdErr output from router
701a0192 213 vtystderr = net["r%s" % i].cmd('vtysh -c "show version" > /dev/null').rstrip()
4501fbca
MW
214
215 # Fix newlines (make them all the same)
701a0192 216 vtystderr = ("\n".join(vtystderr.splitlines()) + "\n").rstrip()
4501fbca 217 # # Drop everything starting with "FRRouting X.xx" message
701a0192 218 # vtystderr = re.sub(r"FRRouting [0-9]+.*", "", vtystderr, flags=re.DOTALL)
4501fbca 219
701a0192 220 if vtystderr == "":
4501fbca
MW
221 print("r%s StdErr ok" % i)
222
701a0192 223 assert vtystderr == "", "Vtysh StdErr Output check failed for router r%s" % i
4501fbca 224
7e7fc73b
MW
225 # Make sure that all daemons are running
226 for i in range(1, 2):
701a0192 227 fatal_error = net["r%s" % i].checkRouterRunning()
7e7fc73b
MW
228 assert fatal_error == "", fatal_error
229
622c4996 230 # For debugging after starting FRR daemons, uncomment the next line
4501fbca
MW
231 # CLI(net)
232
233
234def test_error_messages_daemons():
235 global fatal_error
236 global net
237
238 # Skip if previous fatal error condition is raised
701a0192 239 if fatal_error != "":
4501fbca
MW
240 pytest.skip(fatal_error)
241
242 print("\n\n** Check for error messages in daemons")
243 print("******************************************\n")
244
245 error_logs = ""
246
247 for i in range(1, 2):
701a0192 248 log = net["r%s" % i].getStdErr("ripd")
4501fbca
MW
249 if log:
250 error_logs += "r%s RIPd StdErr Output:\n" % i
251 error_logs += log
701a0192 252 log = net["r%s" % i].getStdErr("ripngd")
4501fbca
MW
253 if log:
254 error_logs += "r%s RIPngd StdErr Output:\n" % i
255 error_logs += log
701a0192 256 log = net["r%s" % i].getStdErr("ospfd")
4501fbca
MW
257 if log:
258 error_logs += "r%s OSPFd StdErr Output:\n" % i
259 error_logs += log
701a0192 260 log = net["r%s" % i].getStdErr("ospf6d")
4501fbca
MW
261 if log:
262 error_logs += "r%s OSPF6d StdErr Output:\n" % i
263 error_logs += log
701a0192 264 log = net["r%s" % i].getStdErr("isisd")
4501fbca
MW
265 # ISIS shows debugging enabled status on StdErr
266 # Remove these messages
267 log = re.sub(r"^IS-IS .* debugging is on.*", "", log).rstrip()
268 if log:
269 error_logs += "r%s ISISd StdErr Output:\n" % i
270 error_logs += log
701a0192 271 log = net["r%s" % i].getStdErr("bgpd")
4501fbca
MW
272 if log:
273 error_logs += "r%s BGPd StdErr Output:\n" % i
274 error_logs += log
701a0192 275 if net["r%s" % i].daemon_available("ldpd"):
276 log = net["r%s" % i].getStdErr("ldpd")
4501fbca
MW
277 if log:
278 error_logs += "r%s LDPd StdErr Output:\n" % i
279 error_logs += log
af39fbe7 280
701a0192 281 log = net["r1"].getStdErr("nhrpd")
960c3f25
MW
282 # NHRPD shows YANG model not embedded messages
283 # Ignore these
284 log = re.sub(r".*YANG model.*not embedded.*", "", log).rstrip()
af39fbe7
MS
285 if log:
286 error_logs += "r%s NHRPd StdErr Output:\n" % i
287 error_logs += log
288
701a0192 289 log = net["r1"].getStdErr("babeld")
af39fbe7
MS
290 if log:
291 error_logs += "r%s BABELd StdErr Output:\n" % i
292 error_logs += log
293
701a0192 294 log = net["r1"].getStdErr("pbrd")
af39fbe7
MS
295 if log:
296 error_logs += "r%s PBRd StdErr Output:\n" % i
297 error_logs += log
298
701a0192 299 log = net["r%s" % i].getStdErr("zebra")
4501fbca 300 if log:
960c3f25 301 error_logs += "r%s Zebra StdErr Output:\n" % i
4501fbca
MW
302 error_logs += log
303
304 if error_logs:
701a0192 305 sys.stderr.write(
306 "Failed check for StdErr Output on daemons:\n%s\n" % error_logs
307 )
4501fbca 308
08fa1af7 309 # Ignoring the issue if told to ignore (ie not yet fixed)
701a0192 310 if error_logs != "":
311 if os.environ.get("bamboo_TOPOTESTS_ISSUE_349") == "IGNORE":
312 sys.stderr.write(
313 "Known issue - IGNORING. See https://github.com/FRRouting/frr/issues/349\n"
314 )
315 pytest.skip(
316 "Known issue - IGNORING. See https://github.com/FRRouting/frr/issues/349"
317 )
08fa1af7 318
4501fbca
MW
319 assert error_logs == "", "Daemons report errors to StdErr"
320
622c4996 321 # For debugging after starting FRR daemons, uncomment the next line
4501fbca
MW
322 # CLI(net)
323
324
325def test_converge_protocols():
326 global fatal_error
327 global net
328
329 # Skip if previous fatal error condition is raised
701a0192 330 if fatal_error != "":
4501fbca
MW
331 pytest.skip(fatal_error)
332
333 thisDir = os.path.dirname(os.path.realpath(__file__))
334
335 print("\n\n** Waiting for protocols convergence")
336 print("******************************************\n")
337
338 # Not really implemented yet - just sleep 60 secs for now
339 sleep(60)
340
7e7fc73b 341 # Make sure that all daemons are running
556f76e1 342 failures = 0
7e7fc73b 343 for i in range(1, 2):
701a0192 344 fatal_error = net["r%s" % i].checkRouterRunning()
7e7fc73b
MW
345 assert fatal_error == "", fatal_error
346
701a0192 347 print("Show that v4 routes are right\n")
348 v4_routesFile = "%s/r%s/ipv4_routes.ref" % (thisDir, i)
0b25370e
DS
349 expected = (
350 net["r%s" % i].cmd("sort {} 2> /dev/null".format(v4_routesFile)).rstrip()
351 )
701a0192 352 expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1)
353
354 actual = (
355 net["r%s" % i]
356 .cmd(
00756aa3 357 "vtysh -c \"show ip route\" | sed -e '/^Codes: /,/^\s*$/d' | sort 2> /dev/null"
701a0192 358 )
359 .rstrip()
360 )
556f76e1
DS
361 # Drop time in last update
362 actual = re.sub(r" [0-2][0-9]:[0-5][0-9]:[0-5][0-9]", " XX:XX:XX", actual)
701a0192 363 actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1)
364 diff = topotest.get_textdiff(
365 actual,
366 expected,
367 title1="Actual IP Routing Table",
368 title2="Expected IP RoutingTable",
369 )
556f76e1 370 if diff:
701a0192 371 sys.stderr.write("r%s failed IP Routing table check:\n%s\n" % (i, diff))
556f76e1
DS
372 failures += 1
373 else:
701a0192 374 print("r%s ok" % i)
556f76e1
DS
375
376 assert failures == 0, "IP Routing table failed for r%s\n%s" % (i, diff)
377
378 failures = 0
379
380 print("Show that v6 routes are right\n")
701a0192 381 v6_routesFile = "%s/r%s/ipv6_routes.ref" % (thisDir, i)
0b25370e
DS
382 expected = (
383 net["r%s" % i].cmd("sort {} 2> /dev/null".format(v6_routesFile)).rstrip()
384 )
701a0192 385 expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1)
386
387 actual = (
388 net["r%s" % i]
389 .cmd(
00756aa3 390 "vtysh -c \"show ipv6 route\" | sed -e '/^Codes: /,/^\s*$/d' | sort 2> /dev/null"
701a0192 391 )
392 .rstrip()
393 )
556f76e1
DS
394 # Drop time in last update
395 actual = re.sub(r" [0-2][0-9]:[0-5][0-9]:[0-5][0-9]", " XX:XX:XX", actual)
701a0192 396 actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1)
397 diff = topotest.get_textdiff(
398 actual,
399 expected,
400 title1="Actual IPv6 Routing Table",
401 title2="Expected IPv6 RoutingTable",
402 )
556f76e1 403 if diff:
701a0192 404 sys.stderr.write("r%s failed IPv6 Routing table check:\n%s\n" % (i, diff))
556f76e1
DS
405 failures += 1
406 else:
701a0192 407 print("r%s ok" % i)
556f76e1
DS
408
409 assert failures == 0, "IPv6 Routing table failed for r%s\n%s" % (i, diff)
410
622c4996 411 # For debugging after starting FRR daemons, uncomment the next line
4501fbca
MW
412 ## CLI(net)
413
701a0192 414
887a232c
SW
415def route_get_nhg_id(route_str):
416 output = net["r1"].cmd('vtysh -c "show ip route %s nexthop-group"' % route_str)
417 match = re.search(r"Nexthop Group ID: (\d+)", output)
701a0192 418 assert match is not None, (
419 "Nexthop Group ID not found for sharpd route %s" % route_str
420 )
887a232c
SW
421
422 nhg_id = int(match.group(1))
423 return nhg_id
424
701a0192 425
8f4d7212 426def verify_nexthop_group(nhg_id, recursive=False, ecmp=0):
887a232c
SW
427 # Verify NHG is valid/installed
428 output = net["r1"].cmd('vtysh -c "show nexthop-group rib %d"' % nhg_id)
429
430 match = re.search(r"Valid", output)
431 assert match is not None, "Nexthop Group ID=%d not marked Valid" % nhg_id
432
8f4d7212
SW
433 if ecmp or recursive:
434 match = re.search(r"Depends:.*\n", output)
435 assert match is not None, "Nexthop Group ID=%d has no depends" % nhg_id
436
437 # list of IDs in group
438 depends = re.findall(r"\((\d+)\)", match.group(0))
439
440 if ecmp:
701a0192 441 assert len(depends) == ecmp, (
442 "Nexthop Group ID=%d doesn't match ecmp size" % nhg_id
443 )
8f4d7212
SW
444 else:
445 # If recursive, we need to look at its resolved group
701a0192 446 assert len(depends) == 1, (
447 "Nexthop Group ID=%d should only have one recursive depend" % nhg_id
448 )
8f4d7212
SW
449 resolved_id = int(depends[0])
450 verify_nexthop_group(resolved_id, False)
451
887a232c
SW
452 else:
453 match = re.search(r"Installed", output)
454 assert match is not None, "Nexthop Group ID=%d not marked Installed" % nhg_id
455
701a0192 456
8f4d7212 457def verify_route_nexthop_group(route_str, recursive=False, ecmp=0):
887a232c
SW
458 # Verify route and that zebra created NHGs for and they are valid/installed
459 nhg_id = route_get_nhg_id(route_str)
8f4d7212 460 verify_nexthop_group(nhg_id, recursive, ecmp)
887a232c 461
701a0192 462
8058df22
SW
463def test_nexthop_groups():
464 global fatal_error
465 global net
466
467 # Skip if previous fatal error condition is raised
701a0192 468 if fatal_error != "":
8058df22
SW
469 pytest.skip(fatal_error)
470
471 print("\n\n** Verifying Nexthop Groups")
472 print("******************************************\n")
473
887a232c
SW
474 ### Nexthop Group Tests
475
476 ## Basic test
477
8058df22 478 # Create a lib nexthop-group
701a0192 479 net["r1"].cmd(
480 'vtysh -c "c t" -c "nexthop-group basic" -c "nexthop 1.1.1.1" -c "nexthop 1.1.1.2"'
481 )
8058df22
SW
482
483 # Create with sharpd using nexthop-group
887a232c 484 net["r1"].cmd('vtysh -c "sharp install routes 2.2.2.1 nexthop-group basic 1"')
8058df22 485
887a232c 486 verify_route_nexthop_group("2.2.2.1/32")
8058df22 487
887a232c 488 ## Connected
8058df22 489
701a0192 490 net["r1"].cmd(
491 'vtysh -c "c t" -c "nexthop-group connected" -c "nexthop r1-eth1" -c "nexthop r1-eth2"'
492 )
8058df22 493
887a232c
SW
494 net["r1"].cmd('vtysh -c "sharp install routes 2.2.2.2 nexthop-group connected 1"')
495
496 verify_route_nexthop_group("2.2.2.2/32")
497
498 ## Recursive
499
701a0192 500 net["r1"].cmd(
501 'vtysh -c "c t" -c "nexthop-group basic-recursive" -c "nexthop 2.2.2.1"'
502 )
887a232c 503
701a0192 504 net["r1"].cmd(
505 'vtysh -c "sharp install routes 3.3.3.1 nexthop-group basic-recursive 1"'
506 )
887a232c
SW
507
508 verify_route_nexthop_group("3.3.3.1/32", True)
509
510 ## Duplicate
511
701a0192 512 net["r1"].cmd(
513 'vtysh -c "c t" -c "nexthop-group duplicate" -c "nexthop 2.2.2.1" -c "nexthop 1.1.1.1"'
514 )
887a232c
SW
515
516 net["r1"].cmd('vtysh -c "sharp install routes 3.3.3.2 nexthop-group duplicate 1"')
517
518 verify_route_nexthop_group("3.3.3.2/32")
519
520 ## Two 4-Way ECMP
521
701a0192 522 net["r1"].cmd(
523 'vtysh -c "c t" -c "nexthop-group fourA" -c "nexthop 1.1.1.1" -c "nexthop 1.1.1.2" \
524 -c "nexthop 1.1.1.3" -c "nexthop 1.1.1.4"'
525 )
887a232c
SW
526
527 net["r1"].cmd('vtysh -c "sharp install routes 4.4.4.1 nexthop-group fourA 1"')
528
529 verify_route_nexthop_group("4.4.4.1/32")
530
701a0192 531 net["r1"].cmd(
532 'vtysh -c "c t" -c "nexthop-group fourB" -c "nexthop 1.1.1.5" -c "nexthop 1.1.1.6" \
533 -c "nexthop 1.1.1.7" -c "nexthop 1.1.1.8"'
534 )
887a232c
SW
535
536 net["r1"].cmd('vtysh -c "sharp install routes 4.4.4.2 nexthop-group fourB 1"')
537
538 verify_route_nexthop_group("4.4.4.2/32")
539
540 ## Recursive to 8-Way ECMP
541
701a0192 542 net["r1"].cmd(
543 'vtysh -c "c t" -c "nexthop-group eight-recursive" -c "nexthop 4.4.4.1" -c "nexthop 4.4.4.2"'
544 )
887a232c 545
701a0192 546 net["r1"].cmd(
547 'vtysh -c "sharp install routes 5.5.5.1 nexthop-group eight-recursive 1"'
548 )
887a232c
SW
549
550 verify_route_nexthop_group("5.5.5.1/32")
551
13f93db3
SW
552 ## 4-way ECMP Routes Pointing to Each Other
553
554 # This is to check for a bug with NH resolution where
555 # routes would infintely resolve to each other blowing
556 # up the resolved-> nexthop pointer.
557
558 net["r1"].cmd(
559 'vtysh -c "c t" -c "nexthop-group infinite-recursive" -c "nexthop 6.6.6.1" -c "nexthop 6.6.6.2" \
560 -c "nexthop 6.6.6.3" -c "nexthop 6.6.6.4"'
561 )
562
563 # static route nexthops can recurse to
564
565 net["r1"].cmd('vtysh -c "c t" -c "ip route 6.6.6.0/24 1.1.1.1"')
566
567 # Make routes that point to themselves in ecmp
568
569 net["r1"].cmd(
570 'vtysh -c "sharp install routes 6.6.6.4 nexthop-group infinite-recursive 1"'
571 )
572
573 net["r1"].cmd(
574 'vtysh -c "sharp install routes 6.6.6.3 nexthop-group infinite-recursive 1"'
575 )
576
577 net["r1"].cmd(
578 'vtysh -c "sharp install routes 6.6.6.2 nexthop-group infinite-recursive 1"'
579 )
580
581 net["r1"].cmd(
582 'vtysh -c "sharp install routes 6.6.6.1 nexthop-group infinite-recursive 1"'
583 )
584
585 # Get routes and test if has too many (duplicate) nexthops
586 nhg_id = route_get_nhg_id("6.6.6.1/32")
587 output = net["r1"].cmd('vtysh -c "show nexthop-group rib %d"' % nhg_id)
588
589 dups = re.findall(r"(via 1\.1\.1\.1)", output)
590
591 # Should find 3, itself is inactive
592 assert len(dups) == 3, (
593 "Route 6.6.6.1/32 with Nexthop Group ID=%d has wrong number of resolved nexthops"
594 % nhg_id
595 )
596
887a232c
SW
597 ##CLI(net)
598
599 ## Remove all NHG routes
600
601 net["r1"].cmd('vtysh -c "sharp remove routes 2.2.2.1 1"')
602 net["r1"].cmd('vtysh -c "sharp remove routes 2.2.2.2 1"')
603 net["r1"].cmd('vtysh -c "sharp remove routes 3.3.3.1 1"')
604 net["r1"].cmd('vtysh -c "sharp remove routes 3.3.3.2 1"')
605 net["r1"].cmd('vtysh -c "sharp remove routes 4.4.4.1 1"')
606 net["r1"].cmd('vtysh -c "sharp remove routes 4.4.4.2 1"')
607 net["r1"].cmd('vtysh -c "sharp remove routes 5.5.5.1 1"')
13f93db3
SW
608 net["r1"].cmd('vtysh -c "sharp remove routes 6.6.6.1 4"')
609 net["r1"].cmd('vtysh -c "c t" -c "no ip route 6.6.6.0/24 1.1.1.1"')
4501fbca 610
701a0192 611
4501fbca
MW
612def test_rip_status():
613 global fatal_error
614 global net
615
616 # Skip if previous fatal error condition is raised
701a0192 617 if fatal_error != "":
4501fbca
MW
618 pytest.skip(fatal_error)
619
620 thisDir = os.path.dirname(os.path.realpath(__file__))
621
b2764f90 622 print("\n\n** Verifying RIP status")
4501fbca
MW
623 print("******************************************\n")
624 failures = 0
625 for i in range(1, 2):
701a0192 626 refTableFile = "%s/r%s/rip_status.ref" % (thisDir, i)
4501fbca
MW
627 if os.path.isfile(refTableFile):
628 # Read expected result from file
629 expected = open(refTableFile).read().rstrip()
630 # Fix newlines (make them all the same)
701a0192 631 expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1)
4501fbca
MW
632
633 # Actual output from router
701a0192 634 actual = (
635 net["r%s" % i]
636 .cmd('vtysh -c "show ip rip status" 2> /dev/null')
637 .rstrip()
638 )
639 # Drop time in next due
4501fbca
MW
640 actual = re.sub(r"in [0-9]+ seconds", "in XX seconds", actual)
641 # Drop time in last update
642 actual = re.sub(r" [0-2][0-9]:[0-5][0-9]:[0-5][0-9]", " XX:XX:XX", actual)
643 # Fix newlines (make them all the same)
701a0192 644 actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1)
4501fbca
MW
645
646 # Generate Diff
701a0192 647 diff = topotest.get_textdiff(
648 actual,
649 expected,
17070436 650 title1="actual IP RIP status",
701a0192 651 title2="expected IP RIP status",
652 )
4501fbca
MW
653
654 # Empty string if it matches, otherwise diff contains unified diff
655 if diff:
701a0192 656 sys.stderr.write("r%s failed IP RIP status check:\n%s\n" % (i, diff))
4501fbca
MW
657 failures += 1
658 else:
659 print("r%s ok" % i)
660
661 assert failures == 0, "IP RIP status failed for router r%s:\n%s" % (i, diff)
662
7e7fc73b
MW
663 # Make sure that all daemons are running
664 for i in range(1, 2):
701a0192 665 fatal_error = net["r%s" % i].checkRouterRunning()
7e7fc73b
MW
666 assert fatal_error == "", fatal_error
667
622c4996 668 # For debugging after starting FRR daemons, uncomment the next line
4501fbca
MW
669 # CLI(net)
670
671
672def test_ripng_status():
673 global fatal_error
674 global net
675
676 # Skip if previous fatal error condition is raised
701a0192 677 if fatal_error != "":
4501fbca
MW
678 pytest.skip(fatal_error)
679
680 thisDir = os.path.dirname(os.path.realpath(__file__))
681
b2764f90 682 print("\n\n** Verifying RIPng status")
4501fbca
MW
683 print("******************************************\n")
684 failures = 0
685 for i in range(1, 2):
701a0192 686 refTableFile = "%s/r%s/ripng_status.ref" % (thisDir, i)
4501fbca
MW
687 if os.path.isfile(refTableFile):
688 # Read expected result from file
689 expected = open(refTableFile).read().rstrip()
690 # Fix newlines (make them all the same)
701a0192 691 expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1)
4501fbca
MW
692
693 # Actual output from router
701a0192 694 actual = (
695 net["r%s" % i]
696 .cmd('vtysh -c "show ipv6 ripng status" 2> /dev/null')
697 .rstrip()
698 )
4501fbca
MW
699 # Mask out Link-Local mac address portion. They are random...
700 actual = re.sub(r" fe80::[0-9a-f:]+", " fe80::XXXX:XXXX:XXXX:XXXX", actual)
701a0192 701 # Drop time in next due
4501fbca
MW
702 actual = re.sub(r"in [0-9]+ seconds", "in XX seconds", actual)
703 # Drop time in last update
704 actual = re.sub(r" [0-2][0-9]:[0-5][0-9]:[0-5][0-9]", " XX:XX:XX", actual)
705 # Fix newlines (make them all the same)
701a0192 706 actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1)
4501fbca
MW
707
708 # Generate Diff
701a0192 709 diff = topotest.get_textdiff(
710 actual,
711 expected,
17070436 712 title1="actual IPv6 RIPng status",
701a0192 713 title2="expected IPv6 RIPng status",
714 )
4501fbca
MW
715
716 # Empty string if it matches, otherwise diff contains unified diff
717 if diff:
701a0192 718 sys.stderr.write(
719 "r%s failed IPv6 RIPng status check:\n%s\n" % (i, diff)
720 )
4501fbca
MW
721 failures += 1
722 else:
723 print("r%s ok" % i)
724
701a0192 725 assert failures == 0, "IPv6 RIPng status failed for router r%s:\n%s" % (
726 i,
727 diff,
728 )
4501fbca 729
7e7fc73b
MW
730 # Make sure that all daemons are running
731 for i in range(1, 2):
701a0192 732 fatal_error = net["r%s" % i].checkRouterRunning()
7e7fc73b
MW
733 assert fatal_error == "", fatal_error
734
622c4996 735 # For debugging after starting FRR daemons, uncomment the next line
4501fbca
MW
736 # CLI(net)
737
738
739def test_ospfv2_interfaces():
740 global fatal_error
741 global net
742
743 # Skip if previous fatal error condition is raised
701a0192 744 if fatal_error != "":
4501fbca
MW
745 pytest.skip(fatal_error)
746
747 thisDir = os.path.dirname(os.path.realpath(__file__))
748
b2764f90 749 print("\n\n** Verifying OSPFv2 interfaces")
4501fbca
MW
750 print("******************************************\n")
751 failures = 0
752 for i in range(1, 2):
701a0192 753 refTableFile = "%s/r%s/show_ip_ospf_interface.ref" % (thisDir, i)
4501fbca
MW
754 if os.path.isfile(refTableFile):
755 # Read expected result from file
756 expected = open(refTableFile).read().rstrip()
757 # Fix newlines (make them all the same)
701a0192 758 expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1)
4501fbca
MW
759
760 # Actual output from router
701a0192 761 actual = (
762 net["r%s" % i]
763 .cmd('vtysh -c "show ip ospf interface" 2> /dev/null')
764 .rstrip()
765 )
4501fbca
MW
766 # Mask out Bandwidth portion. They may change..
767 actual = re.sub(r"BW [0-9]+ Mbit", "BW XX Mbit", actual)
11761ab0 768 actual = re.sub(r"ifindex [0-9]", "ifindex X", actual)
c9d72a0b 769
701a0192 770 # Drop time in next due
4501fbca 771 actual = re.sub(r"Hello due in [0-9\.]+s", "Hello due in XX.XXXs", actual)
701a0192 772 actual = re.sub(
773 r"Hello due in [0-9\.]+ usecs", "Hello due in XX.XXXs", actual
774 )
985e6d50 775 # Fix 'MTU mismatch detection: enabled' vs 'MTU mismatch detection:enabled' - accept both
701a0192 776 actual = re.sub(
777 r"MTU mismatch detection:([a-z]+.*)",
778 r"MTU mismatch detection: \1",
779 actual,
780 )
4501fbca 781 # Fix newlines (make them all the same)
701a0192 782 actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1)
4501fbca
MW
783
784 # Generate Diff
701a0192 785 diff = topotest.get_textdiff(
786 actual,
787 expected,
17070436 788 title1="actual SHOW IP OSPF INTERFACE",
701a0192 789 title2="expected SHOW IP OSPF INTERFACE",
790 )
4501fbca
MW
791
792 # Empty string if it matches, otherwise diff contains unified diff
793 if diff:
701a0192 794 sys.stderr.write(
795 "r%s failed SHOW IP OSPF INTERFACE check:\n%s\n" % (i, diff)
796 )
4501fbca
MW
797 failures += 1
798 else:
799 print("r%s ok" % i)
800
08fa1af7 801 # Ignoring the issue if told to ignore (ie not yet fixed)
701a0192 802 if failures != 0:
803 if os.environ.get("bamboo_TOPOTESTS_ISSUE_348") == "IGNORE":
804 sys.stderr.write(
805 "Known issue - IGNORING. See https://github.com/FRRouting/frr/issues/348\n"
806 )
807 pytest.skip(
808 "Known issue - IGNORING. See https://github.com/FRRouting/frr/issues/348"
809 )
810
811 assert (
812 failures == 0
813 ), "SHOW IP OSPF INTERFACE failed for router r%s:\n%s" % (i, diff)
4501fbca 814
7e7fc73b
MW
815 # Make sure that all daemons are running
816 for i in range(1, 2):
701a0192 817 fatal_error = net["r%s" % i].checkRouterRunning()
7e7fc73b
MW
818 assert fatal_error == "", fatal_error
819
622c4996 820 # For debugging after starting FRR daemons, uncomment the next line
4501fbca
MW
821 # CLI(net)
822
823
824def test_isis_interfaces():
825 global fatal_error
826 global net
827
828 # Skip if previous fatal error condition is raised
701a0192 829 if fatal_error != "":
4501fbca
MW
830 pytest.skip(fatal_error)
831
832 thisDir = os.path.dirname(os.path.realpath(__file__))
833
b2764f90 834 print("\n\n** Verifying ISIS interfaces")
4501fbca
MW
835 print("******************************************\n")
836 failures = 0
837 for i in range(1, 2):
701a0192 838 refTableFile = "%s/r%s/show_isis_interface_detail.ref" % (thisDir, i)
4501fbca
MW
839 if os.path.isfile(refTableFile):
840 # Read expected result from file
841 expected = open(refTableFile).read().rstrip()
842 # Fix newlines (make them all the same)
701a0192 843 expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1)
4501fbca
MW
844
845 # Actual output from router
701a0192 846 actual = (
847 net["r%s" % i]
848 .cmd('vtysh -c "show isis interface detail" 2> /dev/null')
849 .rstrip()
850 )
4501fbca
MW
851 # Mask out Link-Local mac address portion. They are random...
852 actual = re.sub(r"fe80::[0-9a-f:]+", "fe80::XXXX:XXXX:XXXX:XXXX", actual)
853 # Mask out SNPA mac address portion. They are random...
854 actual = re.sub(r"SNPA: [0-9a-f\.]+", "SNPA: XXXX.XXXX.XXXX", actual)
6ae351e8 855 # Mask out Circuit ID number
701a0192 856 actual = re.sub(r"Circuit Id: 0x[0-9a-f]+", "Circuit Id: 0xXX", actual)
4501fbca 857 # Fix newlines (make them all the same)
701a0192 858 actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1)
4501fbca
MW
859
860 # Generate Diff
701a0192 861 diff = topotest.get_textdiff(
862 actual,
863 expected,
17070436 864 title1="actual SHOW ISIS INTERFACE DETAIL",
701a0192 865 title2="expected SHOW ISIS OSPF6 INTERFACE DETAIL",
866 )
4501fbca
MW
867
868 # Empty string if it matches, otherwise diff contains unified diff
869 if diff:
701a0192 870 sys.stderr.write(
871 "r%s failed SHOW ISIS INTERFACE DETAIL check:\n%s\n" % (i, diff)
872 )
4501fbca
MW
873 failures += 1
874 else:
875 print("r%s ok" % i)
876
701a0192 877 assert (
878 failures == 0
879 ), "SHOW ISIS INTERFACE DETAIL failed for router r%s:\n%s" % (i, diff)
4501fbca 880
7e7fc73b
MW
881 # Make sure that all daemons are running
882 for i in range(1, 2):
701a0192 883 fatal_error = net["r%s" % i].checkRouterRunning()
7e7fc73b
MW
884 assert fatal_error == "", fatal_error
885
622c4996 886 # For debugging after starting FRR daemons, uncomment the next line
4501fbca
MW
887 # CLI(net)
888
889
890def test_bgp_summary():
891 global fatal_error
892 global net
893
894 # Skip if previous fatal error condition is raised
701a0192 895 if fatal_error != "":
4501fbca
MW
896 pytest.skip(fatal_error)
897
898 thisDir = os.path.dirname(os.path.realpath(__file__))
899
b2764f90 900 print("\n\n** Verifying BGP Summary")
4501fbca
MW
901 print("******************************************\n")
902 failures = 0
903 for i in range(1, 2):
701a0192 904 refTableFile = "%s/r%s/show_ip_bgp_summary.ref" % (thisDir, i)
4501fbca
MW
905 if os.path.isfile(refTableFile):
906 # Read expected result from file
8c1d4cd5
LS
907 expected_original = open(refTableFile).read().rstrip()
908
909 for filter in ["", "remote-as internal", "remote-as external",
910 "remote-as 100", "remote-as 123",
911 "neighbor 192.168.7.10", "neighbor 192.168.7.10",
912 "neighbor fc00:0:0:8::1000",
913 "neighbor 10.0.0.1"]:
914 # Actual output from router
915 actual = (
916 net["r%s" % i]
917 .cmd('vtysh -c "show ip bgp summary ' + filter + '" 2> /dev/null')
918 .rstrip()
919 )
920 # Mask out "using XXiXX bytes" portion. They are random...
921 actual = re.sub(r"using [0-9]+ bytes", "using XXXX bytes", actual)
922 # Mask out "using XiXXX KiB" portion. They are random...
923 actual = re.sub(r"using [0-9]+ KiB", "using XXXX KiB", actual)
924
925 # Remove extra summaries which exist with newer versions
926
927 # Remove summary lines (changed recently)
928 actual = re.sub(r"Total number.*", "", actual)
929 actual = re.sub(r"Displayed.*", "", actual)
930 # Remove IPv4 Unicast Summary (Title only)
6cac2fcc 931 actual = re.sub(r"IPv4 Unicast Summary \(VRF default\):", "", actual)
8c1d4cd5 932 # Remove IPv4 Multicast Summary (all of it)
6cac2fcc 933 actual = re.sub(r"IPv4 Multicast Summary \(VRF default\):", "", actual)
8c1d4cd5
LS
934 actual = re.sub(r"No IPv4 Multicast neighbor is configured", "", actual)
935 # Remove IPv4 VPN Summary (all of it)
6cac2fcc 936 actual = re.sub(r"IPv4 VPN Summary \(VRF default\):", "", actual)
8c1d4cd5
LS
937 actual = re.sub(r"No IPv4 VPN neighbor is configured", "", actual)
938 # Remove IPv4 Encap Summary (all of it)
6cac2fcc 939 actual = re.sub(r"IPv4 Encap Summary \(VRF default\):", "", actual)
8c1d4cd5
LS
940 actual = re.sub(r"No IPv4 Encap neighbor is configured", "", actual)
941 # Remove Unknown Summary (all of it)
6cac2fcc 942 actual = re.sub(r"Unknown Summary \(VRF default\):", "", actual)
8c1d4cd5
LS
943 actual = re.sub(r"No Unknown neighbor is configured", "", actual)
944
6cac2fcc 945 actual = re.sub(r"IPv4 labeled-unicast Summary \(VRF default\):", "", actual)
8c1d4cd5
LS
946 actual = re.sub(
947 r"No IPv4 labeled-unicast neighbor is configured", "", actual
948 )
8b2e59e9 949
8c1d4cd5
LS
950 expected = expected_original
951 # apply filters on expected output
952 if "internal" in filter or "remote-as 100" in filter:
953 expected = re.sub(r".+\s+200\s+.+", "", expected)
954 elif "external" in filter:
955 expected = re.sub(r".+\s+100\s+.+Active.+", "", expected)
956 elif "remote-as 123" in filter:
957 expected = re.sub(
958 r"(192.168.7.(1|2)0|fc00:0:0:8::(1|2)000).+Active.+",
959 "", expected
960 )
ce1944f0 961 expected = expected + "% No matching neighbor\n"
8c1d4cd5
LS
962 elif "192.168.7.10" in filter:
963 expected = re.sub(
964 r"(192.168.7.20|fc00:0:0:8::(1|2)000).+Active.+",
965 "", expected
966 )
967 elif "fc00:0:0:8::1000" in filter:
968 expected = re.sub(
969 r"(192.168.7.(1|2)0|fc00:0:0:8::2000).+Active.+",
970 "", expected
971 )
972 elif "10.0.0.1" in filter:
c3c4e528 973 expected = "No such neighbor in VRF default"
8c1d4cd5
LS
974
975 # Strip empty lines
976 actual = actual.lstrip().rstrip()
977 expected = expected.lstrip().rstrip()
978 actual = re.sub(r"\n+", "\n", actual)
979 expected = re.sub(r"\n+", "\n", expected)
980
981 # reapply initial formatting
982 actual = re.sub(r"KiB of memory\n", "KiB of memory\n\n", actual)
983 expected = re.sub(r"KiB of memory\n", "KiB of memory\n\n", expected)
984
985 # realign expected neighbor columns if needed
986 try:
987 idx_actual = re.search(r"\n(Neighbor\s+V\s+)", actual).group(1).find("V")
988 idx_expected = re.search(r"\n(Neighbor\s+V\s+)", expected).group(1).find("V")
989 idx_diff = idx_expected - idx_actual
990 if idx_diff > 0:
991 # Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
992 expected = re.sub(" " * idx_diff + "V ", "V ", expected)
993 # 192.168.7.10 4 100 0 0 0 0 0 never Active
994 expected = re.sub(" " * idx_diff + "4 ", "4 ", expected)
995 except AttributeError:
996 pass
997
998 # Fix newlines (make them all the same)
999 actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1)
1000 expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1)
1001
1002 # Generate Diff
1003 diff = topotest.get_textdiff(
1004 actual,
1005 expected,
1006 title1="actual SHOW IP BGP SUMMARY " + filter.upper() ,
1007 title2="expected SHOW IP BGP SUMMARY " + filter.upper(),
1008 )
4501fbca 1009
8c1d4cd5
LS
1010 # Empty string if it matches, otherwise diff contains unified diff
1011 if diff:
1012 sys.stderr.write(
1013 "r%s failed SHOW IP BGP SUMMARY check:\n%s\n" % (i, diff)
1014 )
1015 failures += 1
1016 else:
1017 print("r%s ok" % i)
4501fbca 1018
8c1d4cd5
LS
1019 assert failures == 0, "SHOW IP BGP SUMMARY failed for router r%s:\n%s" % (
1020 i,
1021 diff,
701a0192 1022 )
4501fbca 1023
7e7fc73b
MW
1024 # Make sure that all daemons are running
1025 for i in range(1, 2):
701a0192 1026 fatal_error = net["r%s" % i].checkRouterRunning()
7e7fc73b
MW
1027 assert fatal_error == "", fatal_error
1028
622c4996 1029 # For debugging after starting FRR daemons, uncomment the next line
4501fbca
MW
1030 # CLI(net)
1031
1032
1033def test_bgp_ipv6_summary():
1034 global fatal_error
1035 global net
1036
1037 # Skip if previous fatal error condition is raised
701a0192 1038 if fatal_error != "":
4501fbca
MW
1039 pytest.skip(fatal_error)
1040
1041 thisDir = os.path.dirname(os.path.realpath(__file__))
1042
b2764f90 1043 print("\n\n** Verifying BGP IPv6 Summary")
4501fbca
MW
1044 print("******************************************\n")
1045 failures = 0
1046 for i in range(1, 2):
701a0192 1047 refTableFile = "%s/r%s/show_bgp_ipv6_summary.ref" % (thisDir, i)
4501fbca
MW
1048 if os.path.isfile(refTableFile):
1049 # Read expected result from file
1050 expected = open(refTableFile).read().rstrip()
1051 # Fix newlines (make them all the same)
701a0192 1052 expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1)
4501fbca
MW
1053
1054 # Actual output from router
701a0192 1055 actual = (
1056 net["r%s" % i]
1057 .cmd('vtysh -c "show bgp ipv6 summary" 2> /dev/null')
1058 .rstrip()
1059 )
4501fbca
MW
1060 # Mask out "using XXiXX bytes" portion. They are random...
1061 actual = re.sub(r"using [0-9]+ bytes", "using XXXX bytes", actual)
1062 # Mask out "using XiXXX KiB" portion. They are random...
1063 actual = re.sub(r"using [0-9]+ KiB", "using XXXX KiB", actual)
08fa1af7
MW
1064 #
1065 # Remove extra summaries which exist with newer versions
1066 #
1067 # Remove summary lines (changed recently)
701a0192 1068 actual = re.sub(r"Total number.*", "", actual)
1069 actual = re.sub(r"Displayed.*", "", actual)
08fa1af7 1070 # Remove IPv4 Unicast Summary (Title only)
6cac2fcc 1071 actual = re.sub(r"IPv6 Unicast Summary \(VRF default\):", "", actual)
08fa1af7 1072 # Remove IPv4 Multicast Summary (all of it)
6cac2fcc 1073 actual = re.sub(r"IPv6 Multicast Summary \(VRF default\):", "", actual)
701a0192 1074 actual = re.sub(r"No IPv6 Multicast neighbor is configured", "", actual)
08fa1af7 1075 # Remove IPv4 VPN Summary (all of it)
6cac2fcc 1076 actual = re.sub(r"IPv6 VPN Summary \(VRF default\):", "", actual)
701a0192 1077 actual = re.sub(r"No IPv6 VPN neighbor is configured", "", actual)
08fa1af7 1078 # Remove IPv4 Encap Summary (all of it)
6cac2fcc 1079 actual = re.sub(r"IPv6 Encap Summary \(VRF default\):", "", actual)
701a0192 1080 actual = re.sub(r"No IPv6 Encap neighbor is configured", "", actual)
08fa1af7 1081 # Remove Unknown Summary (all of it)
6cac2fcc 1082 actual = re.sub(r"Unknown Summary \(VRF default\):", "", actual)
701a0192 1083 actual = re.sub(r"No Unknown neighbor is configured", "", actual)
8b2e59e9
DS
1084
1085 # Remove Labeled Unicast Summary (all of it)
6cac2fcc 1086 actual = re.sub(r"IPv6 labeled-unicast Summary \(VRF default\):", "", actual)
701a0192 1087 actual = re.sub(
1088 r"No IPv6 labeled-unicast neighbor is configured", "", actual
1089 )
8b2e59e9 1090
08fa1af7
MW
1091 # Strip empty lines
1092 actual = actual.lstrip()
1093 actual = actual.rstrip()
1094 #
4501fbca 1095 # Fix newlines (make them all the same)
701a0192 1096 actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1)
4501fbca
MW
1097
1098 # Generate Diff
701a0192 1099 diff = topotest.get_textdiff(
1100 actual,
1101 expected,
17070436 1102 title1="actual SHOW BGP IPv6 SUMMARY",
701a0192 1103 title2="expected SHOW BGP IPv6 SUMMARY",
1104 )
4501fbca
MW
1105
1106 # Empty string if it matches, otherwise diff contains unified diff
1107 if diff:
701a0192 1108 sys.stderr.write(
1109 "r%s failed SHOW BGP IPv6 SUMMARY check:\n%s\n" % (i, diff)
1110 )
4501fbca
MW
1111 failures += 1
1112 else:
1113 print("r%s ok" % i)
1114
701a0192 1115 assert failures == 0, "SHOW BGP IPv6 SUMMARY failed for router r%s:\n%s" % (
1116 i,
1117 diff,
1118 )
4501fbca 1119
7e7fc73b
MW
1120 # Make sure that all daemons are running
1121 for i in range(1, 2):
701a0192 1122 fatal_error = net["r%s" % i].checkRouterRunning()
7e7fc73b
MW
1123 assert fatal_error == "", fatal_error
1124
622c4996 1125 # For debugging after starting FRR daemons, uncomment the next line
4501fbca
MW
1126 # CLI(net)
1127
9fa6ec14 1128
dda33b6e
DS
1129def test_nht():
1130 print("\n\n**** Test that nexthop tracking is at least nominally working ****\n")
1131
1132 thisDir = os.path.dirname(os.path.realpath(__file__))
1133
1134 for i in range(1, 2):
1135 nhtFile = "%s/r%s/ip_nht.ref" % (thisDir, i)
1136 expected = open(nhtFile).read().rstrip()
1137 expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1)
1138
9fa6ec14 1139 actual = net["r%s" % i].cmd('vtysh -c "show ip nht" 2> /dev/null').rstrip()
dda33b6e
DS
1140 actual = re.sub(r"fd [0-9][0-9]", "fd XX", actual)
1141 actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1)
1142
9fa6ec14 1143 diff = topotest.get_textdiff(
1144 actual,
1145 expected,
1146 title1="Actual `show ip nht`",
1147 title2="Expected `show ip nht`",
1148 )
dda33b6e
DS
1149
1150 if diff:
1151 assert 0, "r%s failed ip nht check:\n%s\n" % (i, diff)
1152 else:
1153 print("show ip nht is ok\n")
1154
1155 nhtFile = "%s/r%s/ipv6_nht.ref" % (thisDir, i)
1156 expected = open(nhtFile).read().rstrip()
1157 expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1)
1158
9fa6ec14 1159 actual = net["r%s" % i].cmd('vtysh -c "show ipv6 nht" 2> /dev/null').rstrip()
dda33b6e
DS
1160 actual = re.sub(r"fd [0-9][0-9]", "fd XX", actual)
1161 actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1)
1162
9fa6ec14 1163 diff = topotest.get_textdiff(
1164 actual,
1165 expected,
1166 title1="Actual `show ip nht`",
1167 title2="Expected `show ip nht`",
1168 )
dda33b6e
DS
1169
1170 if diff:
1171 assert 0, "r%s failed ipv6 nht check:\n%s\n" % (i, diff)
1172 else:
1173 print("show ipv6 nht is ok\n")
4501fbca 1174
9fa6ec14 1175
4501fbca
MW
1176def test_bgp_ipv4():
1177 global fatal_error
1178 global net
1179
1180 # Skip if previous fatal error condition is raised
701a0192 1181 if fatal_error != "":
4501fbca
MW
1182 pytest.skip(fatal_error)
1183
1184 thisDir = os.path.dirname(os.path.realpath(__file__))
1185
b2764f90 1186 print("\n\n** Verifying BGP IPv4")
4501fbca 1187 print("******************************************\n")
6a57e103 1188 diffresult = {}
4501fbca 1189 for i in range(1, 2):
11761ab0 1190 success = 0
701a0192 1191 for refTableFile in glob.glob("%s/r%s/show_bgp_ipv4*.ref" % (thisDir, i)):
11761ab0
MS
1192 if os.path.isfile(refTableFile):
1193 # Read expected result from file
1194 expected = open(refTableFile).read().rstrip()
1195 # Fix newlines (make them all the same)
701a0192 1196 expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1)
11761ab0
MS
1197
1198 # Actual output from router
701a0192 1199 actual = (
1200 net["r%s" % i].cmd('vtysh -c "show bgp ipv4" 2> /dev/null').rstrip()
1201 )
11761ab0 1202 # Remove summary line (changed recently)
701a0192 1203 actual = re.sub(r"Total number.*", "", actual)
1204 actual = re.sub(r"Displayed.*", "", actual)
11761ab0
MS
1205 actual = actual.rstrip()
1206 # Fix newlines (make them all the same)
701a0192 1207 actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1)
11761ab0
MS
1208
1209 # Generate Diff
701a0192 1210 diff = topotest.get_textdiff(
1211 actual,
1212 expected,
11761ab0 1213 title1="actual SHOW BGP IPv4",
701a0192 1214 title2="expected SHOW BGP IPv4",
1215 )
11761ab0
MS
1216
1217 # Empty string if it matches, otherwise diff contains unified diff
1218 if diff:
1219 diffresult[refTableFile] = diff
1220 else:
1221 success = 1
1222 print("template %s matched: r%s ok" % (refTableFile, i))
1223 break
1224
1225 if not success:
701a0192 1226 resultstr = "No template matched.\n"
e7294b32 1227 for f in diffresult.keys():
701a0192 1228 resultstr += "template %s: r%s failed SHOW BGP IPv4 check:\n%s\n" % (
1229 f,
1230 i,
1231 diffresult[f],
1232 )
11761ab0 1233 raise AssertionError(
701a0192 1234 "SHOW BGP IPv4 failed for router r%s:\n%s" % (i, resultstr)
1235 )
4501fbca 1236
7e7fc73b
MW
1237 # Make sure that all daemons are running
1238 for i in range(1, 2):
701a0192 1239 fatal_error = net["r%s" % i].checkRouterRunning()
7e7fc73b
MW
1240 assert fatal_error == "", fatal_error
1241
622c4996 1242 # For debugging after starting FRR daemons, uncomment the next line
4501fbca
MW
1243 # CLI(net)
1244
1245
1246def test_bgp_ipv6():
1247 global fatal_error
1248 global net
1249
1250 # Skip if previous fatal error condition is raised
701a0192 1251 if fatal_error != "":
4501fbca
MW
1252 pytest.skip(fatal_error)
1253
1254 thisDir = os.path.dirname(os.path.realpath(__file__))
1255
b2764f90 1256 print("\n\n** Verifying BGP IPv6")
4501fbca 1257 print("******************************************\n")
6a57e103 1258 diffresult = {}
4501fbca 1259 for i in range(1, 2):
11761ab0 1260 success = 0
701a0192 1261 for refTableFile in glob.glob("%s/r%s/show_bgp_ipv6*.ref" % (thisDir, i)):
11761ab0
MS
1262 if os.path.isfile(refTableFile):
1263 # Read expected result from file
1264 expected = open(refTableFile).read().rstrip()
1265 # Fix newlines (make them all the same)
701a0192 1266 expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1)
11761ab0
MS
1267
1268 # Actual output from router
701a0192 1269 actual = (
1270 net["r%s" % i].cmd('vtysh -c "show bgp ipv6" 2> /dev/null').rstrip()
1271 )
11761ab0 1272 # Remove summary line (changed recently)
701a0192 1273 actual = re.sub(r"Total number.*", "", actual)
1274 actual = re.sub(r"Displayed.*", "", actual)
11761ab0
MS
1275 actual = actual.rstrip()
1276 # Fix newlines (make them all the same)
701a0192 1277 actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1)
11761ab0
MS
1278
1279 # Generate Diff
701a0192 1280 diff = topotest.get_textdiff(
1281 actual,
1282 expected,
11761ab0 1283 title1="actual SHOW BGP IPv6",
701a0192 1284 title2="expected SHOW BGP IPv6",
1285 )
11761ab0
MS
1286
1287 # Empty string if it matches, otherwise diff contains unified diff
1288 if diff:
1289 diffresult[refTableFile] = diff
1290 else:
1291 success = 1
1292 print("template %s matched: r%s ok" % (refTableFile, i))
1293
1294 if not success:
701a0192 1295 resultstr = "No template matched.\n"
e7294b32 1296 for f in diffresult.keys():
701a0192 1297 resultstr += "template %s: r%s failed SHOW BGP IPv6 check:\n%s\n" % (
1298 f,
1299 i,
1300 diffresult[f],
1301 )
11761ab0 1302 raise AssertionError(
701a0192 1303 "SHOW BGP IPv6 failed for router r%s:\n%s" % (i, resultstr)
1304 )
4501fbca 1305
7e7fc73b
MW
1306 # Make sure that all daemons are running
1307 for i in range(1, 2):
701a0192 1308 fatal_error = net["r%s" % i].checkRouterRunning()
7e7fc73b
MW
1309 assert fatal_error == "", fatal_error
1310
622c4996 1311 # For debugging after starting FRR daemons, uncomment the next line
4501fbca
MW
1312 # CLI(net)
1313
701a0192 1314
41fce07c
DS
1315def test_route_map():
1316 global fatal_error
1317 global net
1318
701a0192 1319 if fatal_error != "":
41fce07c
DS
1320 pytest.skip(fatal_error)
1321
1322 thisDir = os.path.dirname(os.path.realpath(__file__))
1323
1324 print("\n\n** Verifying some basic routemap forward references\n")
1325 print("*******************************************************\n")
1326 failures = 0
1327 for i in range(1, 2):
701a0192 1328 refroutemap = "%s/r%s/show_route_map.ref" % (thisDir, i)
41fce07c
DS
1329 if os.path.isfile(refroutemap):
1330 expected = open(refroutemap).read().rstrip()
701a0192 1331 expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1)
41fce07c 1332
701a0192 1333 actual = (
1334 net["r%s" % i].cmd('vtysh -c "show route-map" 2> /dev/null').rstrip()
1335 )
1336 actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1)
41fce07c 1337
701a0192 1338 diff = topotest.get_textdiff(
1339 actual,
1340 expected,
1341 title1="actual show route-map",
1342 title2="expected show route-map",
1343 )
41fce07c
DS
1344
1345 if diff:
701a0192 1346 sys.stderr.write(
1347 "r%s failed show route-map command Check:\n%s\n" % (i, diff)
1348 )
41fce07c
DS
1349 failures += 1
1350 else:
701a0192 1351 print("r%s ok" % i)
1352
1353 assert (
1354 failures == 0
1355 ), "Show route-map command failed for router r%s:\n%s" % (i, diff)
4501fbca
MW
1356
1357
887a232c
SW
1358def test_nexthop_groups_with_route_maps():
1359 global fatal_error
1360 global net
1361
1362 # Skip if previous fatal error condition is raised
701a0192 1363 if fatal_error != "":
887a232c
SW
1364 pytest.skip(fatal_error)
1365
1366 print("\n\n** Verifying Nexthop Groups With Route-Maps")
1367 print("******************************************\n")
1368
1369 ### Nexthop Group With Route-Map Tests
1370
1371 # Create a lib nexthop-group
701a0192 1372 net["r1"].cmd(
1373 'vtysh -c "c t" -c "nexthop-group test" -c "nexthop 1.1.1.1" -c "nexthop 1.1.1.2"'
1374 )
887a232c
SW
1375
1376 ## Route-Map Proto Source
1377
1378 route_str = "2.2.2.1"
1379 src_str = "192.168.0.1"
1380
701a0192 1381 net["r1"].cmd(
1382 'vtysh -c "c t" -c "route-map NH-SRC permit 111" -c "set src %s"' % src_str
1383 )
887a232c
SW
1384 net["r1"].cmd('vtysh -c "c t" -c "ip protocol sharp route-map NH-SRC"')
1385
1386 net["r1"].cmd('vtysh -c "sharp install routes %s nexthop-group test 1"' % route_str)
1387
1388 verify_route_nexthop_group("%s/32" % route_str)
1389
1390 # Only a valid test on linux using nexthop objects
1391 if sys.platform.startswith("linux"):
701a0192 1392 output = net["r1"].cmd("ip route show %s/32" % route_str)
887a232c 1393 match = re.search(r"src %s" % src_str, output)
701a0192 1394 assert match is not None, "Route %s/32 not installed with src %s" % (
1395 route_str,
1396 src_str,
1397 )
887a232c
SW
1398
1399 # Remove NHG routes and route-map
1400 net["r1"].cmd('vtysh -c "sharp remove routes %s 1"' % route_str)
1401 net["r1"].cmd('vtysh -c "c t" -c "no ip protocol sharp route-map NH-SRC"')
701a0192 1402 net["r1"].cmd(
1403 'vtysh -c "c t" -c "no route-map NH-SRC permit 111" -c "set src %s"' % src_str
1404 )
887a232c
SW
1405 net["r1"].cmd('vtysh -c "c t" -c "no route-map NH-SRC"')
1406
1407 ## Route-Map Deny/Permit with same nexthop group
1408
1409 permit_route_str = "3.3.3.1"
1410 deny_route_str = "3.3.3.2"
1411
701a0192 1412 net["r1"].cmd(
1413 'vtysh -c "c t" -c "ip prefix-list NOPE seq 5 permit %s/32"' % permit_route_str
1414 )
1415 net["r1"].cmd(
1416 'vtysh -c "c t" -c "route-map NOPE permit 111" -c "match ip address prefix-list NOPE"'
1417 )
887a232c
SW
1418 net["r1"].cmd('vtysh -c "c t" -c "route-map NOPE deny 222"')
1419 net["r1"].cmd('vtysh -c "c t" -c "ip protocol sharp route-map NOPE"')
1420
1421 # This route should be permitted
701a0192 1422 net["r1"].cmd(
1423 'vtysh -c "sharp install routes %s nexthop-group test 1"' % permit_route_str
1424 )
887a232c
SW
1425
1426 verify_route_nexthop_group("%s/32" % permit_route_str)
1427
1428 # This route should be denied
701a0192 1429 net["r1"].cmd(
1430 'vtysh -c "sharp install routes %s nexthop-group test 1"' % deny_route_str
1431 )
887a232c
SW
1432
1433 nhg_id = route_get_nhg_id(deny_route_str)
1434 output = net["r1"].cmd('vtysh -c "show nexthop-group rib %d"' % nhg_id)
1435
1436 match = re.search(r"Valid", output)
1437 assert match is None, "Nexthop Group ID=%d should not be marked Valid" % nhg_id
1438
1439 match = re.search(r"Installed", output)
1440 assert match is None, "Nexthop Group ID=%d should not be marked Installed" % nhg_id
1441
1442 # Remove NHG routes and route-map
1443 net["r1"].cmd('vtysh -c "sharp remove routes %s 1"' % permit_route_str)
1444 net["r1"].cmd('vtysh -c "sharp remove routes %s 1"' % deny_route_str)
1445 net["r1"].cmd('vtysh -c "c t" -c "no ip protocol sharp route-map NOPE"')
1446 net["r1"].cmd('vtysh -c "c t" -c "no route-map NOPE permit 111"')
1447 net["r1"].cmd('vtysh -c "c t" -c "no route-map NOPE deny 222"')
1448 net["r1"].cmd('vtysh -c "c t" -c "no route-map NOPE"')
701a0192 1449 net["r1"].cmd(
1450 'vtysh -c "c t" -c "no ip prefix-list NOPE seq 5 permit %s/32"'
1451 % permit_route_str
1452 )
1453
887a232c 1454
8f4d7212
SW
1455def test_nexthop_group_replace():
1456 global fatal_error
1457 global net
1458
1459 # Skip if previous fatal error condition is raised
701a0192 1460 if fatal_error != "":
8f4d7212
SW
1461 pytest.skip(fatal_error)
1462
1463 print("\n\n** Verifying Nexthop Groups")
1464 print("******************************************\n")
1465
1466 ### Nexthop Group Tests
1467
1468 ## 2-Way ECMP Directly Connected
1469
701a0192 1470 net["r1"].cmd(
1471 'vtysh -c "c t" -c "nexthop-group replace" -c "nexthop 1.1.1.1 r1-eth1 onlink" -c "nexthop 1.1.1.2 r1-eth2 onlink"'
1472 )
8f4d7212
SW
1473
1474 # Create with sharpd using nexthop-group
1475 net["r1"].cmd('vtysh -c "sharp install routes 3.3.3.1 nexthop-group replace 1"')
1476
1477 verify_route_nexthop_group("3.3.3.1/32")
1478
1479 # Change the nexthop group
701a0192 1480 net["r1"].cmd(
1481 'vtysh -c "c t" -c "nexthop-group replace" -c "no nexthop 1.1.1.1 r1-eth1 onlink" -c "nexthop 1.1.1.3 r1-eth1 onlink" -c "nexthop 1.1.1.4 r1-eth4 onlink"'
1482 )
8f4d7212
SW
1483
1484 # Verify it updated. We can just check install and ecmp count here.
1485 verify_route_nexthop_group("3.3.3.1/32", False, 3)
1486
701a0192 1487
4501fbca
MW
1488def test_mpls_interfaces():
1489 global fatal_error
1490 global net
1491
1492 # Skip if previous fatal error condition is raised
701a0192 1493 if fatal_error != "":
4501fbca
MW
1494 pytest.skip(fatal_error)
1495
1496 # Skip if no LDP installed or old kernel
701a0192 1497 if net["r1"].daemon_available("ldpd") == False:
4501fbca
MW
1498 pytest.skip("No MPLS or kernel < 4.5")
1499
1500 thisDir = os.path.dirname(os.path.realpath(__file__))
1501
b2764f90 1502 print("\n\n** Verifying MPLS Interfaces")
4501fbca
MW
1503 print("******************************************\n")
1504 failures = 0
1505 for i in range(1, 2):
701a0192 1506 refTableFile = "%s/r%s/show_mpls_ldp_interface.ref" % (thisDir, i)
4501fbca
MW
1507 if os.path.isfile(refTableFile):
1508 # Read expected result from file
1509 expected = open(refTableFile).read().rstrip()
1510 # Fix newlines (make them all the same)
701a0192 1511 expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1)
4501fbca
MW
1512
1513 # Actual output from router
701a0192 1514 actual = (
1515 net["r%s" % i]
1516 .cmd('vtysh -c "show mpls ldp interface" 2> /dev/null')
1517 .rstrip()
1518 )
4501fbca
MW
1519 # Mask out Timer in Uptime
1520 actual = re.sub(r" [0-9][0-9]:[0-9][0-9]:[0-9][0-9] ", " xx:xx:xx ", actual)
1521 # Fix newlines (make them all the same)
701a0192 1522 actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1)
4501fbca
MW
1523
1524 # Generate Diff
701a0192 1525 diff = topotest.get_textdiff(
1526 actual,
1527 expected,
17070436 1528 title1="actual MPLS LDP interface status",
701a0192 1529 title2="expected MPLS LDP interface status",
1530 )
4501fbca
MW
1531
1532 # Empty string if it matches, otherwise diff contains unified diff
1533 if diff:
701a0192 1534 sys.stderr.write(
1535 "r%s failed MPLS LDP Interface status Check:\n%s\n" % (i, diff)
1536 )
4501fbca
MW
1537 failures += 1
1538 else:
1539 print("r%s ok" % i)
1540
701a0192 1541 if failures > 0:
4501fbca
MW
1542 fatal_error = "MPLS LDP Interface status failed"
1543
701a0192 1544 assert (
1545 failures == 0
1546 ), "MPLS LDP Interface status failed for router r%s:\n%s" % (i, diff)
4501fbca 1547
7e7fc73b
MW
1548 # Make sure that all daemons are running
1549 for i in range(1, 2):
701a0192 1550 fatal_error = net["r%s" % i].checkRouterRunning()
7e7fc73b
MW
1551 assert fatal_error == "", fatal_error
1552
622c4996 1553 # For debugging after starting FRR daemons, uncomment the next line
4501fbca
MW
1554 # CLI(net)
1555
1556
1557def test_shutdown_check_stderr():
1558 global fatal_error
1559 global net
1560
1561 # Skip if previous fatal error condition is raised
701a0192 1562 if fatal_error != "":
4501fbca
MW
1563 pytest.skip(fatal_error)
1564
b2764f90 1565 print("\n\n** Verifying unexpected STDERR output from daemons")
4501fbca
MW
1566 print("******************************************\n")
1567
701a0192 1568 if os.environ.get("TOPOTESTS_CHECK_STDERR") is None:
1569 print(
1570 "SKIPPED final check on StdErr output: Disabled (TOPOTESTS_CHECK_STDERR undefined)\n"
1571 )
1572 pytest.skip("Skipping test for Stderr output")
4501fbca
MW
1573
1574 thisDir = os.path.dirname(os.path.realpath(__file__))
1575
50c40bde
MW
1576 print("thisDir=" + thisDir)
1577
701a0192 1578 net["r1"].stopRouter()
4501fbca 1579
701a0192 1580 log = net["r1"].getStdErr("ripd")
8e957dbb
MW
1581 if log:
1582 print("\nRIPd StdErr Log:\n" + log)
701a0192 1583 log = net["r1"].getStdErr("ripngd")
8e957dbb
MW
1584 if log:
1585 print("\nRIPngd StdErr Log:\n" + log)
701a0192 1586 log = net["r1"].getStdErr("ospfd")
8e957dbb
MW
1587 if log:
1588 print("\nOSPFd StdErr Log:\n" + log)
701a0192 1589 log = net["r1"].getStdErr("ospf6d")
8e957dbb
MW
1590 if log:
1591 print("\nOSPF6d StdErr Log:\n" + log)
701a0192 1592 log = net["r1"].getStdErr("isisd")
8e957dbb
MW
1593 if log:
1594 print("\nISISd StdErr Log:\n" + log)
701a0192 1595 log = net["r1"].getStdErr("bgpd")
8e957dbb
MW
1596 if log:
1597 print("\nBGPd StdErr Log:\n" + log)
af39fbe7 1598
701a0192 1599 log = net["r1"].getStdErr("nhrpd")
af39fbe7
MS
1600 if log:
1601 print("\nNHRPd StdErr Log:\n" + log)
1602
701a0192 1603 log = net["r1"].getStdErr("pbrd")
af39fbe7
MS
1604 if log:
1605 print("\nPBRd StdErr Log:\n" + log)
1606
701a0192 1607 log = net["r1"].getStdErr("babeld")
af39fbe7
MS
1608 if log:
1609 print("\nBABELd StdErr Log:\n" + log)
1610
701a0192 1611 if net["r1"].daemon_available("ldpd"):
1612 log = net["r1"].getStdErr("ldpd")
8e957dbb
MW
1613 if log:
1614 print("\nLDPd StdErr Log:\n" + log)
701a0192 1615 log = net["r1"].getStdErr("zebra")
8e957dbb
MW
1616 if log:
1617 print("\nZebra StdErr Log:\n" + log)
4501fbca
MW
1618
1619
50c40bde
MW
1620def test_shutdown_check_memleak():
1621 global fatal_error
1622 global net
1623
1624 # Skip if previous fatal error condition is raised
701a0192 1625 if fatal_error != "":
50c40bde
MW
1626 pytest.skip(fatal_error)
1627
701a0192 1628 if os.environ.get("TOPOTESTS_CHECK_MEMLEAK") is None:
1629 print(
1630 "SKIPPED final check on Memory leaks: Disabled (TOPOTESTS_CHECK_MEMLEAK undefined)\n"
1631 )
1632 pytest.skip("Skipping test for memory leaks")
1633
50c40bde
MW
1634 thisDir = os.path.dirname(os.path.realpath(__file__))
1635
1636 for i in range(1, 2):
701a0192 1637 net["r%s" % i].stopRouter()
1638 net["r%s" % i].report_memory_leaks(
1639 os.environ.get("TOPOTESTS_CHECK_MEMLEAK"), os.path.basename(__file__)
1640 )
50c40bde
MW
1641
1642
701a0192 1643if __name__ == "__main__":
4501fbca 1644
701a0192 1645 setLogLevel("info")
4501fbca
MW
1646 # To suppress tracebacks, either use the following pytest call or add "--tb=no" to cli
1647 # retval = pytest.main(["-s", "--tb=no"])
1648 retval = pytest.main(["-s"])
1649 sys.exit(retval)