]> git.proxmox.com Git - mirror_frr.git/blob - doc/developer/topotests.rst
lib: Fix memory leak in in Link State
[mirror_frr.git] / doc / developer / topotests.rst
1 .. _topotests:
2
3 Topotests
4 =========
5
6 Topotests is a suite of topology tests for FRR built on top of micronet.
7
8 Installation and Setup
9 ----------------------
10
11 Topotests run under python3. Additionally, for ExaBGP (which is used
12 in some of the BGP tests) an older python2 version (and the python2
13 version of ``pip``) must be installed.
14
15 Tested with Ubuntu 20.04,Ubuntu 18.04, and Debian 11.
16
17 Instructions are the same for all setups (i.e. ExaBGP is only used for
18 BGP tests).
19
20 Installing Topotest Requirements
21 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
22
23 .. code:: shell
24
25 apt-get install gdb
26 apt-get install iproute2
27 apt-get install net-tools
28 apt-get install python3-pip
29 python3 -m pip install wheel
30 python3 -m pip install 'pytest>=6.2.4'
31 python3 -m pip install 'pytest-xdist>=2.3.0'
32 python3 -m pip install 'scapy>=2.4.5'
33 python3 -m pip install xmltodict
34 # Use python2 pip to install older ExaBGP
35 python2 -m pip install 'exabgp<4.0.0'
36 useradd -d /var/run/exabgp/ -s /bin/false exabgp
37
38 # To enable the gRPC topotest install:
39 python3 -m pip install grpcio grpcio-tools
40
41 # Install Socat tool to run PIMv6 tests,
42 # Socat code can be taken from below url,
43 # which has latest changes done for PIMv6,
44 # join and traffic:
45 https://github.com/opensourcerouting/socat/
46
47
48 Enable Coredumps
49 """"""""""""""""
50
51 Optional, will give better output.
52
53 .. code:: shell
54
55 disable apport (which move core files)
56
57 Set ``enabled=0`` in ``/etc/default/apport``.
58
59 Next, update security limits by changing :file:`/etc/security/limits.conf` to::
60
61 #<domain> <type> <item> <value>
62 * soft core unlimited
63 root soft core unlimited
64 * hard core unlimited
65 root hard core unlimited
66
67 Reboot for options to take effect.
68
69 SNMP Utilities Installation
70 """""""""""""""""""""""""""
71
72 To run SNMP test you need to install SNMP utilities and MIBs. Unfortunately
73 there are some errors in the upstream MIBS which need to be patched up. The
74 following steps will get you there on Ubuntu 20.04.
75
76 .. code:: shell
77
78 apt install libsnmp-dev
79 apt install snmpd snmp
80 apt install snmp-mibs-downloader
81 download-mibs
82 wget https://raw.githubusercontent.com/FRRouting/frr-mibs/main/iana/IANA-IPPM-METRICS-REGISTRY-MIB -O /usr/share/snmp/mibs/iana/IANA-IPPM-METRICS-REGISTRY-MIB
83 wget https://raw.githubusercontent.com/FRRouting/frr-mibs/main/ietf/SNMPv2-PDU -O /usr/share/snmp/mibs/ietf/SNMPv2-PDU
84 wget https://raw.githubusercontent.com/FRRouting/frr-mibs/main/ietf/IPATM-IPMC-MIB -O /usr/share/snmp/mibs/ietf/IPATM-IPMC-MIB
85 edit /etc/snmp/snmp.conf to look like this
86 # As the snmp packages come without MIB files due to license reasons, loading
87 # of MIBs is disabled by default. If you added the MIBs you can reenable
88 # loading them by commenting out the following line.
89 mibs +ALL
90
91
92 FRR Installation
93 ^^^^^^^^^^^^^^^^
94
95 FRR needs to be installed separately. It is assume to be configured like the
96 standard Ubuntu Packages:
97
98 - Binaries in :file:`/usr/lib/frr`
99 - State Directory :file:`/var/run/frr`
100 - Running under user ``frr``, group ``frr``
101 - vtygroup: ``frrvty``
102 - config directory: :file:`/etc/frr`
103 - For FRR Packages, install the dbg package as well for coredump decoding
104
105 No FRR config needs to be done and no FRR daemons should be run ahead of the
106 test. They are all started as part of the test.
107
108 Manual FRR build
109 """"""""""""""""
110
111 If you prefer to manually build FRR, then use the following suggested config:
112
113 .. code:: shell
114
115 ./configure \
116 --prefix=/usr \
117 --localstatedir=/var/run/frr \
118 --sbindir=/usr/lib/frr \
119 --sysconfdir=/etc/frr \
120 --enable-vtysh \
121 --enable-pimd \
122 --enable-sharpd \
123 --enable-multipath=64 \
124 --enable-user=frr \
125 --enable-group=frr \
126 --enable-vty-group=frrvty \
127 --enable-snmp=agentx \
128 --with-pkg-extra-version=-my-manual-build
129
130 And create ``frr`` user and ``frrvty`` group as follows:
131
132 .. code:: shell
133
134 addgroup --system --gid 92 frr
135 addgroup --system --gid 85 frrvty
136 adduser --system --ingroup frr --home /var/run/frr/ \
137 --gecos "FRRouting suite" --shell /bin/false frr
138 usermod -G frrvty frr
139
140 Executing Tests
141 ---------------
142
143 Configure your sudo environment
144 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
145
146 Topotests must be run as root. Normally this will be accomplished through the
147 use of the ``sudo`` command. In order for topotests to be able to open new
148 windows (either XTerm or byobu/screen/tmux windows) certain environment
149 variables must be passed through the sudo command. One way to do this is to
150 specify the ``-E`` flag to ``sudo``. This will carry over most if not all
151 your environment variables include ``PATH``. For example:
152
153 .. code:: shell
154
155 sudo -E python3 -m pytest -s -v
156
157 If you do not wish to use ``-E`` (e.g., to avoid ``sudo`` inheriting
158 ``PATH``) you can modify your `/etc/sudoers` config file to specifically pass
159 the environment variables required by topotests. Add the following commands to
160 your ``/etc/sudoers`` config file.
161
162 .. code:: shell
163
164 Defaults env_keep="TMUX"
165 Defaults env_keep+="TMUX_PANE"
166 Defaults env_keep+="STY"
167 Defaults env_keep+="DISPLAY"
168
169 If there was already an ``env_keep`` configuration there be sure to use the
170 ``+=`` rather than ``=`` on the first line above as well.
171
172
173 Execute all tests in distributed test mode
174 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
175
176 .. code:: shell
177
178 sudo -E pytest -s -v -nauto --dist=loadfile
179
180 The above command must be executed from inside the topotests directory.
181
182 All test\_\* scripts in subdirectories are detected and executed (unless
183 disabled in ``pytest.ini`` file). Pytest will execute up to N tests in parallel
184 where N is based on the number of cores on the host.
185
186 Analyze Test Results (``analyze.py``)
187 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
188
189 By default router and execution logs are saved in ``/tmp/topotests`` and an XML
190 results file is saved in ``/tmp/topotests.xml``. An analysis tool ``analyze.py``
191 is provided to archive and analyze these results after the run completes.
192
193 After the test run completes one should pick an archive directory to store the
194 results in and pass this value to ``analyze.py``. On first execution the results
195 are copied to that directory from ``/tmp``, and subsequent runs use that
196 directory for analyzing the results. Below is an example of this which also
197 shows the default behavior which is to display all failed and errored tests in
198 the run.
199
200 .. code:: shell
201
202 ~/frr/tests/topotests# ./analyze.py -Ar run-save
203 bgp_multiview_topo1/test_bgp_multiview_topo1.py::test_bgp_converge
204 ospf_basic_functionality/test_ospf_lan.py::test_ospf_lan_tc1_p0
205 bgp_gr_functionality_topo2/test_bgp_gr_functionality_topo2.py::test_BGP_GR_10_p2
206 bgp_multiview_topo1/test_bgp_multiview_topo1.py::test_bgp_routingTable
207
208 Here we see that 4 tests have failed. We an dig deeper by displaying the
209 captured logs and errors. First let's redisplay the results enumerated by adding
210 the ``-E`` flag
211
212 .. code:: shell
213
214 ~/frr/tests/topotests# ./analyze.py -Ar run-save -E
215 0 bgp_multiview_topo1/test_bgp_multiview_topo1.py::test_bgp_converge
216 1 ospf_basic_functionality/test_ospf_lan.py::test_ospf_lan_tc1_p0
217 2 bgp_gr_functionality_topo2/test_bgp_gr_functionality_topo2.py::test_BGP_GR_10_p2
218 3 bgp_multiview_topo1/test_bgp_multiview_topo1.py::test_bgp_routingTable
219
220 Now to look at the error message for a failed test we use ``-T N`` where N is
221 the number of the test we are interested in along with ``--errmsg`` option.
222
223 .. code:: shell
224
225 ~/frr/tests/topotests# ./analyze.py -Ar run-save -T0 --errmsg
226 bgp_multiview_topo1/test_bgp_multiview_topo1.py::test_bgp_converge: AssertionError: BGP did not converge:
227
228 IPv4 Unicast Summary (VIEW 1):
229 BGP router identifier 172.30.1.1, local AS number 100 vrf-id -1
230 BGP table version 1
231 RIB entries 1, using 184 bytes of memory
232 Peers 3, using 2169 KiB of memory
233
234 Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc
235 172.16.1.1 4 65001 0 0 0 0 0 never Connect 0 N/A
236 172.16.1.2 4 65002 0 0 0 0 0 never Connect 0 N/A
237 172.16.1.5 4 65005 0 0 0 0 0 never Connect 0 N/A
238
239 Total number of neighbors 3
240
241 assert False
242
243 Now to look at the full text of the error for a failed test we use ``-T N``
244 where N is the number of the test we are interested in along with ``--errtext``
245 option.
246
247 .. code:: shell
248
249 ~/frr/tests/topotests# ./analyze.py -Ar run-save -T0 --errtext
250 bgp_multiview_topo1/test_bgp_multiview_topo1.py::test_bgp_converge: def test_bgp_converge():
251 "Check for BGP converged on all peers and BGP views"
252
253 global fatal_error
254 global net
255 [...]
256 else:
257 # Bail out with error if a router fails to converge
258 bgpStatus = net["r%s" % i].cmd('vtysh -c "show ip bgp view %s summary"' % view)
259 > assert False, "BGP did not converge:\n%s" % bgpStatus
260 E AssertionError: BGP did not converge:
261 E
262 E IPv4 Unicast Summary (VIEW 1):
263 E BGP router identifier 172.30.1.1, local AS number 100 vrf-id -1
264 [...]
265 E Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc
266 E 172.16.1.1 4 65001 0 0 0 0 0 never Connect 0 N/A
267 E 172.16.1.2 4 65002 0 0 0 0 0 never Connect 0 N/A
268 [...]
269
270 To look at the full capture for a test including the stdout and stderr which
271 includes full debug logs, just use the ``-T N`` option without the ``--errmsg``
272 or ``--errtext`` options.
273
274 .. code:: shell
275
276 ~/frr/tests/topotests# ./analyze.py -Ar run-save -T0
277 @classname: bgp_multiview_topo1.test_bgp_multiview_topo1
278 @name: test_bgp_converge
279 @time: 141.401
280 @message: AssertionError: BGP did not converge:
281 [...]
282 system-out: --------------------------------- Captured Log ---------------------------------
283 2021-08-09 02:55:06,581 DEBUG: lib.micronet_compat.topo: Topo(unnamed): Creating
284 2021-08-09 02:55:06,581 DEBUG: lib.micronet_compat.topo: Topo(unnamed): addHost r1
285 [...]
286 2021-08-09 02:57:16,932 DEBUG: topolog.r1: LinuxNamespace(r1): cmd_status("['/bin/bash', '-c', 'vtysh -c "show ip bgp view 1 summary" 2> /dev/null | grep ^[0-9] | grep -vP " 11\\s+(\\d+)"']", kwargs: {'encoding': 'utf-8', 'stdout': -1, 'stderr': -2, 'shell': False})
287 2021-08-09 02:57:22,290 DEBUG: topolog.r1: LinuxNamespace(r1): cmd_status("['/bin/bash', '-c', 'vtysh -c "show ip bgp view 1 summary" 2> /dev/null | grep ^[0-9] | grep -vP " 11\\s+(\\d+)"']", kwargs: {'encoding': 'utf-8', 'stdout': -1, 'stderr': -2, 'shell': False})
288 2021-08-09 02:57:27,636 DEBUG: topolog.r1: LinuxNamespace(r1): cmd_status("['/bin/bash', '-c', 'vtysh -c "show ip bgp view 1 summary"']", kwargs: {'encoding': 'utf-8', 'stdout': -1, 'stderr': -2, 'shell': False})
289 --------------------------------- Captured Out ---------------------------------
290 system-err: --------------------------------- Captured Err ---------------------------------
291
292
293 Execute single test
294 ^^^^^^^^^^^^^^^^^^^
295
296 .. code:: shell
297
298 cd test_to_be_run
299 ./test_to_be_run.py
300
301 For example, and assuming you are inside the frr directory:
302
303 .. code:: shell
304
305 cd tests/topotests/bgp_l3vpn_to_bgp_vrf
306 ./test_bgp_l3vpn_to_bgp_vrf.py
307
308 For further options, refer to pytest documentation.
309
310 Test will set exit code which can be used with ``git bisect``.
311
312 For the simulated topology, see the description in the python file.
313
314 StdErr log from daemos after exit
315 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
316
317 To enable the reporting of any messages seen on StdErr after the daemons exit,
318 the following env variable can be set::
319
320 export TOPOTESTS_CHECK_STDERR=Yes
321
322 (The value doesn't matter at this time. The check is whether the env
323 variable exists or not.) There is no pass/fail on this reporting; the
324 Output will be reported to the console.
325
326 Collect Memory Leak Information
327 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
328
329 FRR processes can report unfreed memory allocations upon exit. To
330 enable the reporting of memory leaks, define an environment variable
331 ``TOPOTESTS_CHECK_MEMLEAK`` with the file prefix, i.e.::
332
333 export TOPOTESTS_CHECK_MEMLEAK="/home/mydir/memleak_"
334
335 This will enable the check and output to console and the writing of
336 the information to files with the given prefix (followed by testname),
337 ie :file:`/home/mydir/memcheck_test_bgp_multiview_topo1.txt` in case
338 of a memory leak.
339
340 Running Topotests with AddressSanitizer
341 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
342
343 Topotests can be run with AddressSanitizer. It requires GCC 4.8 or newer.
344 (Ubuntu 16.04 as suggested here is fine with GCC 5 as default). For more
345 information on AddressSanitizer, see
346 https://github.com/google/sanitizers/wiki/AddressSanitizer.
347
348 The checks are done automatically in the library call of ``checkRouterRunning``
349 (ie at beginning of tests when there is a check for all daemons running). No
350 changes or extra configuration for topotests is required beside compiling the
351 suite with AddressSanitizer enabled.
352
353 If a daemon crashed, then the errorlog is checked for AddressSanitizer output.
354 If found, then this is added with context (calling test) to
355 :file:`/tmp/AddressSanitizer.txt` in Markdown compatible format.
356
357 Compiling for GCC AddressSanitizer requires to use ``gcc`` as a linker as well
358 (instead of ``ld``). Here is a suggest way to compile frr with AddressSanitizer
359 for ``master`` branch:
360
361 .. code:: shell
362
363 git clone https://github.com/FRRouting/frr.git
364 cd frr
365 ./bootstrap.sh
366 ./configure \
367 --enable-address-sanitizer \
368 --prefix=/usr/lib/frr --sysconfdir=/etc/frr \
369 --localstatedir=/var/run/frr \
370 --sbindir=/usr/lib/frr --bindir=/usr/lib/frr \
371 --with-moduledir=/usr/lib/frr/modules \
372 --enable-multipath=0 --enable-rtadv \
373 --enable-tcp-zebra --enable-fpm --enable-pimd \
374 --enable-sharpd
375 make
376 sudo make install
377 # Create symlink for vtysh, so topotest finds it in /usr/lib/frr
378 sudo ln -s /usr/lib/frr/vtysh /usr/bin/
379
380 and create ``frr`` user and ``frrvty`` group as shown above.
381
382 Debugging Topotest Failures
383 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
384
385 Install and run tests inside ``tmux`` or ``byobu`` for best results.
386
387 ``XTerm`` is also fully supported. GNU ``screen`` can be used in most
388 situations; however, it does not work as well with launching ``vtysh`` or shell
389 on error.
390
391 For the below debugging options which launch programs or CLIs, topotest should
392 be run within ``tmux`` (or ``screen``)_, as ``gdb``, the shell or ``vtysh`` will
393 be launched using that windowing program, otherwise ``xterm`` will be attempted
394 to launch the given programs.
395
396 NOTE: you must run the topotest (pytest) such that your DISPLAY, STY or TMUX
397 environment variables are carried over. You can do this by passing the
398 ``-E`` flag to ``sudo`` or you can modify your ``/etc/sudoers`` config to
399 automatically pass that environment variable through to the ``sudo``
400 environment.
401
402 .. _screen: https://www.gnu.org/software/screen/
403 .. _tmux: https://github.com/tmux/tmux/wiki
404
405 Spawning Debugging CLI, ``vtysh`` or Shells on Routers on Test Failure
406 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
407
408 One can have a debugging CLI invoked on test failures by specifying the
409 ``--cli-on-error`` CLI option as shown in the example below.
410
411 .. code:: shell
412
413 sudo -E pytest --cli-on-error all-protocol-startup
414
415 The debugging CLI can run shell or vtysh commands on any combination of routers
416 It can also open shells or vtysh in their own windows for any combination of
417 routers. This is usually the most useful option when debugging failures. Here is
418 the help command from within a CLI launched on error:
419
420 .. code:: shell
421
422 test_bgp_multiview_topo1/test_bgp_routingTable> help
423
424 Commands:
425 help :: this help
426 sh [hosts] <shell-command> :: execute <shell-command> on <host>
427 term [hosts] :: open shell terminals for hosts
428 vtysh [hosts] :: open vtysh terminals for hosts
429 [hosts] <vtysh-command> :: execute vtysh-command on hosts
430
431 test_bgp_multiview_topo1/test_bgp_routingTable> r1 show int br
432 ------ Host: r1 ------
433 Interface Status VRF Addresses
434 --------- ------ --- ---------
435 erspan0 down default
436 gre0 down default
437 gretap0 down default
438 lo up default
439 r1-eth0 up default 172.16.1.254/24
440 r1-stub up default 172.20.0.1/28
441
442 ----------------------
443 test_bgp_multiview_topo1/test_bgp_routingTable>
444
445 Additionally, one can have ``vtysh`` or a shell launched on all routers when a
446 test fails. To launch the given process on each router after a test failure
447 specify one of ``--shell-on-error`` or ``--vtysh-on-error``.
448
449 Spawning ``vtysh`` or Shells on Routers
450 """""""""""""""""""""""""""""""""""""""
451
452 Topotest can automatically launch a shell or ``vtysh`` for any or all routers in
453 a test. This is enabled by specifying 1 of 2 CLI arguments ``--shell`` or
454 ``--vtysh``. Both of these options can be set to a single router value, multiple
455 comma-seperated values, or ``all``.
456
457 When either of these options are specified topotest will pause after setup and
458 each test to allow for inspection of the router state.
459
460 Here's an example of launching ``vtysh`` on routers ``rt1`` and ``rt2``.
461
462 .. code:: shell
463
464 sudo -E pytest --vtysh=rt1,rt2 all-protocol-startup
465
466 Debugging with GDB
467 """"""""""""""""""
468
469 Topotest can automatically launch any daemon with ``gdb``, possibly setting
470 breakpoints for any test run. This is enabled by specifying 1 or 2 CLI arguments
471 ``--gdb-routers`` and ``--gdb-daemons``. Additionally ``--gdb-breakpoints`` can
472 be used to automatically set breakpoints in the launched ``gdb`` processes.
473
474 Each of these options can be set to a single value, multiple comma-seperated
475 values, or ``all``. If ``--gdb-routers`` is empty but ``--gdb_daemons`` is set
476 then the given daemons will be launched in ``gdb`` on all routers in the test.
477 Likewise if ``--gdb_routers`` is set, but ``--gdb_daemons`` is empty then all
478 daemons on the given routers will be launched in ``gdb``.
479
480 Here's an example of launching ``zebra`` and ``bgpd`` inside ``gdb`` on router
481 ``r1`` with a breakpoint set on ``nb_config_diff``
482
483 .. code:: shell
484
485 sudo -E pytest --gdb-routers=r1 \
486 --gdb-daemons=bgpd,zebra \
487 --gdb-breakpoints=nb_config_diff \
488 all-protocol-startup
489
490 Detecting Memleaks with Valgrind
491 """"""""""""""""""""""""""""""""
492
493 Topotest can automatically launch all daemons with ``valgrind`` to check for
494 memleaks. This is enabled by specifying 1 or 2 CLI arguments.
495 ``--valgrind-memleaks`` will enable general memleak detection, and
496 ``--valgrind-extra`` enables extra functionality including generating a
497 suppression file. The suppression file ``tools/valgrind.supp`` is used when
498 memleak detection is enabled.
499
500 .. code:: shell
501
502 sudo -E pytest --valgrind-memleaks all-protocol-startup
503
504 .. _topotests_docker:
505
506 Running Tests with Docker
507 -------------------------
508
509 There is a Docker image which allows to run topotests.
510
511 Quickstart
512 ^^^^^^^^^^
513
514 If you have Docker installed, you can run the topotests in Docker. The easiest
515 way to do this, is to use the make targets from this repository.
516
517 Your current user needs to have access to the Docker daemon. Alternatively you
518 can run these commands as root.
519
520 .. code:: console
521
522 make topotests
523
524 This command will pull the most recent topotests image from Dockerhub, compile
525 FRR inside of it, and run the topotests.
526
527 Advanced Usage
528 ^^^^^^^^^^^^^^
529
530 Internally, the topotests make target uses a shell script to pull the image and
531 spawn the Docker container.
532
533 There are several environment variables which can be used to modify the
534 behavior of the script, these can be listed by calling it with ``-h``:
535
536 .. code:: console
537
538 ./tests/topotests/docker/frr-topotests.sh -h
539
540 For example, a volume is used to cache build artifacts between multiple runs of
541 the image. If you need to force a complete recompile, you can set
542 ``TOPOTEST_CLEAN``:
543
544 .. code:: console
545
546 TOPOTEST_CLEAN=1 ./tests/topotests/docker/frr-topotests.sh
547
548 By default, ``frr-topotests.sh`` will build frr and run pytest. If you append
549 arguments and the first one starts with ``/`` or ``./``, they will replace the
550 call to pytest. If the appended arguments do not match this patttern, they will
551 be provided to pytest as arguments. So, to run a specific test with more
552 verbose logging:
553
554 .. code:: console
555
556 ./tests/topotests/docker/frr-topotests.sh -vv -s all-protocol-startup/test_all_protocol_startup.py
557
558 And to compile FRR but drop into a shell instead of running pytest:
559
560 .. code:: console
561
562 ./tests/topotests/docker/frr-topotests.sh /bin/bash
563
564 Development
565 ^^^^^^^^^^^
566
567 The Docker image just includes all the components to run the topotests, but not
568 the topotests themselves. So if you just want to write tests and don't want to
569 make changes to the environment provided by the Docker image. You don't need to
570 build your own Docker image if you do not want to.
571
572 When developing new tests, there is one caveat though: The startup script of
573 the container will run a ``git-clean`` on its copy of the FRR tree to avoid any
574 pollution of the container with build artefacts from the host. This will also
575 result in your newly written tests being unavailable in the container unless at
576 least added to the index with ``git-add``.
577
578 If you do want to test changes to the Docker image, you can locally build the
579 image and run the tests without pulling from the registry using the following
580 commands:
581
582 .. code:: console
583
584 make topotests-build
585 TOPOTEST_PULL=0 make topotests
586
587
588 .. _topotests-guidelines:
589
590 Guidelines
591 ----------
592
593 Executing Tests
594 ^^^^^^^^^^^^^^^
595
596 To run the whole suite of tests the following commands must be executed at the
597 top level directory of topotest:
598
599 .. code:: shell
600
601 $ # Change to the top level directory of topotests.
602 $ cd path/to/topotests
603 $ # Tests must be run as root, since micronet requires it.
604 $ sudo -E pytest
605
606 In order to run a specific test, you can use the following command:
607
608 .. code:: shell
609
610 $ # running a specific topology
611 $ sudo -E pytest ospf-topo1/
612 $ # or inside the test folder
613 $ cd ospf-topo1
614 $ sudo -E pytest # to run all tests inside the directory
615 $ sudo -E pytest test_ospf_topo1.py # to run a specific test
616 $ # or outside the test folder
617 $ cd ..
618 $ sudo -E pytest ospf-topo1/test_ospf_topo1.py # to run a specific one
619
620 The output of the tested daemons will be available at the temporary folder of
621 your machine:
622
623 .. code:: shell
624
625 $ ls /tmp/topotest/ospf-topo1.test_ospf-topo1/r1
626 ...
627 zebra.err # zebra stderr output
628 zebra.log # zebra log file
629 zebra.out # zebra stdout output
630 ...
631
632 You can also run memory leak tests to get reports:
633
634 .. code:: shell
635
636 $ # Set the environment variable to apply to a specific test...
637 $ sudo -E env TOPOTESTS_CHECK_MEMLEAK="/tmp/memleak_report_" pytest ospf-topo1/test_ospf_topo1.py
638 $ # ...or apply to all tests adding this line to the configuration file
639 $ echo 'memleak_path = /tmp/memleak_report_' >> pytest.ini
640 $ # You can also use your editor
641 $ $EDITOR pytest.ini
642 $ # After running tests you should see your files:
643 $ ls /tmp/memleak_report_*
644 memleak_report_test_ospf_topo1.txt
645
646 Writing a New Test
647 ^^^^^^^^^^^^^^^^^^
648
649 This section will guide you in all recommended steps to produce a standard
650 topology test.
651
652 This is the recommended test writing routine:
653
654 - Write a topology (Graphviz recommended)
655 - Obtain configuration files
656 - Write the test itself
657 - Format the new code using `black <https://github.com/psf/black>`_
658 - Create a Pull Request
659
660 Some things to keep in mind:
661
662 - BGP tests MUST use generous convergence timeouts - you must ensure
663 that any test involving BGP uses a convergence timeout of at least
664 130 seconds.
665 - Topotests are run on a range of Linux versions: if your test
666 requires some OS-specific capability (like mpls support, or vrf
667 support), there are test functions available in the libraries that
668 will help you determine whether your test should run or be skipped.
669 - Avoid including unstable data in your test: don't rely on link-local
670 addresses or ifindex values, for example, because these can change
671 from run to run.
672 - Using sleep is almost never appropriate. As an example: if the test resets the
673 peers in BGP, the test should look for the peers re-converging instead of just
674 sleeping an arbitrary amount of time and continuing on. See
675 ``verify_bgp_convergence`` as a good example of this. In particular look at
676 it's use of the ``@retry`` decorator. If you are having troubles figuring out
677 what to look for, please do not be afraid to ask.
678 - Don't duplicate effort. There exists many protocol utility functions that can
679 be found in their eponymous module under ``tests/topotests/lib/`` (e.g.,
680 ``ospf.py``)
681
682
683
684 Topotest File Hierarchy
685 """""""""""""""""""""""
686
687 Before starting to write any tests one must know the file hierarchy. The
688 repository hierarchy looks like this:
689
690 .. code:: shell
691
692 $ cd path/to/topotest
693 $ find ./*
694 ...
695 ./README.md # repository read me
696 ./GUIDELINES.md # this file
697 ./conftest.py # test hooks - pytest related functions
698 ./example-test # example test folder
699 ./example-test/__init__.py # python package marker - must always exist.
700 ./example-test/test_template.jpg # generated topology picture - see next section
701 ./example-test/test_template.dot # Graphviz dot file
702 ./example-test/test_template.py # the topology plus the test
703 ...
704 ./ospf-topo1 # the ospf topology test
705 ./ospf-topo1/r1 # router 1 configuration files
706 ./ospf-topo1/r1/zebra.conf # zebra configuration file
707 ./ospf-topo1/r1/ospfd.conf # ospf configuration file
708 ./ospf-topo1/r1/ospfroute.txt # 'show ip ospf' output reference file
709 # removed other for shortness sake
710 ...
711 ./lib # shared test/topology functions
712 ./lib/topogen.py # topogen implementation
713 ./lib/topotest.py # topotest implementation
714
715 Guidelines for creating/editing topotest:
716
717 - New topologies that don't fit the existing directories should create its own
718 - Always remember to add the ``__init__.py`` to new folders, this makes auto
719 complete engines and pylint happy
720 - Router (Quagga/FRR) specific code should go on topotest.py
721 - Generic/repeated router actions should have an abstraction in
722 topogen.TopoRouter.
723 - Generic/repeated non-router code should go to topotest.py
724 - pytest related code should go to conftest.py (e.g. specialized asserts)
725
726 Defining the Topology
727 """""""""""""""""""""
728
729 The first step to write a new test is to define the topology. This step can be
730 done in many ways, but the recommended is to use Graphviz to generate a drawing
731 of the topology. It allows us to see the topology graphically and to see the
732 names of equipment, links and addresses.
733
734 Here is an example of Graphviz dot file that generates the template topology
735 :file:`tests/topotests/example-test/test_template.dot` (the inlined code might
736 get outdated, please see the linked file)::
737
738 graph template {
739 label="template";
740
741 # Routers
742 r1 [
743 shape=doubleoctagon,
744 label="r1",
745 fillcolor="#f08080",
746 style=filled,
747 ];
748 r2 [
749 shape=doubleoctagon,
750 label="r2",
751 fillcolor="#f08080",
752 style=filled,
753 ];
754
755 # Switches
756 s1 [
757 shape=oval,
758 label="s1\n192.168.0.0/24",
759 fillcolor="#d0e0d0",
760 style=filled,
761 ];
762 s2 [
763 shape=oval,
764 label="s2\n192.168.1.0/24",
765 fillcolor="#d0e0d0",
766 style=filled,
767 ];
768
769 # Connections
770 r1 -- s1 [label="eth0\n.1"];
771
772 r1 -- s2 [label="eth1\n.100"];
773 r2 -- s2 [label="eth0\n.1"];
774 }
775
776 Here is the produced graph:
777
778 .. graphviz::
779
780 graph template {
781 label="template";
782
783 # Routers
784 r1 [
785 shape=doubleoctagon,
786 label="r1",
787 fillcolor="#f08080",
788 style=filled,
789 ];
790 r2 [
791 shape=doubleoctagon,
792 label="r2",
793 fillcolor="#f08080",
794 style=filled,
795 ];
796
797 # Switches
798 s1 [
799 shape=oval,
800 label="s1\n192.168.0.0/24",
801 fillcolor="#d0e0d0",
802 style=filled,
803 ];
804 s2 [
805 shape=oval,
806 label="s2\n192.168.1.0/24",
807 fillcolor="#d0e0d0",
808 style=filled,
809 ];
810
811 # Connections
812 r1 -- s1 [label="eth0\n.1"];
813
814 r1 -- s2 [label="eth1\n.100"];
815 r2 -- s2 [label="eth0\n.1"];
816 }
817
818 Generating / Obtaining Configuration Files
819 """"""""""""""""""""""""""""""""""""""""""
820
821 In order to get the configuration files or command output for each router, we
822 need to run the topology and execute commands in ``vtysh``. The quickest way to
823 achieve that is writing the topology building code and running the topology.
824
825 To bootstrap your test topology, do the following steps:
826
827 - Copy the template test
828
829 .. code:: shell
830
831 $ mkdir new-topo/
832 $ touch new-topo/__init__.py
833 $ cp example-test/test_template.py new-topo/test_new_topo.py
834
835 - Modify the template according to your dot file
836
837 Here is the template topology described in the previous section in python code:
838
839 .. code:: py
840
841 topodef = {
842 "s1": "r1"
843 "s2": ("r1", "r2")
844 }
845
846 If more specialized topology definitions, or router initialization arguments are
847 required a build function can be used instead of a dictionary:
848
849 .. code:: py
850
851 def build_topo(tgen):
852 "Build function"
853
854 # Create 2 routers
855 for routern in range(1, 3):
856 tgen.add_router("r{}".format(routern))
857
858 # Create a switch with just one router connected to it to simulate a
859 # empty network.
860 switch = tgen.add_switch("s1")
861 switch.add_link(tgen.gears["r1"])
862
863 # Create a connection between r1 and r2
864 switch = tgen.add_switch("s2")
865 switch.add_link(tgen.gears["r1"])
866 switch.add_link(tgen.gears["r2"])
867
868 - Run the topology
869
870 Topogen allows us to run the topology without running any tests, you can do
871 that using the following example commands:
872
873 .. code:: shell
874
875 $ # Running your bootstraped topology
876 $ sudo -E pytest -s --topology-only new-topo/test_new_topo.py
877 $ # Running the test_template.py topology
878 $ sudo -E pytest -s --topology-only example-test/test_template.py
879 $ # Running the ospf_topo1.py topology
880 $ sudo -E pytest -s --topology-only ospf-topo1/test_ospf_topo1.py
881
882 Parameters explanation:
883
884 .. program:: pytest
885
886 .. option:: -s
887
888 Actives input/output capture. If this is not specified a new window will be
889 opened for the interactive CLI, otherwise it will be activated inline.
890
891 .. option:: --topology-only
892
893 Don't run any tests, just build the topology.
894
895 After executing the commands above, you should get the following terminal
896 output:
897
898 .. code:: shell
899
900 frr/tests/topotests# sudo -E pytest -s --topology-only ospf_topo1/test_ospf_topo1.py
901 ============================= test session starts ==============================
902 platform linux -- Python 3.9.2, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
903 rootdir: /home/chopps/w/frr/tests/topotests, configfile: pytest.ini
904 plugins: forked-1.3.0, xdist-2.3.0
905 collected 11 items
906
907 [...]
908 unet>
909
910 The last line shows us that we are now using the CLI (Command Line
911 Interface), from here you can call your router ``vtysh`` or even bash.
912
913 Here's the help text:
914
915 .. code:: shell
916
917 unet> help
918
919 Commands:
920 help :: this help
921 sh [hosts] <shell-command> :: execute <shell-command> on <host>
922 term [hosts] :: open shell terminals for hosts
923 vtysh [hosts] :: open vtysh terminals for hosts
924 [hosts] <vtysh-command> :: execute vtysh-command on hosts
925
926 Here are some commands example:
927
928 .. code:: shell
929
930 unet> sh r1 ping 10.0.3.1
931 PING 10.0.3.1 (10.0.3.1) 56(84) bytes of data.
932 64 bytes from 10.0.3.1: icmp_seq=1 ttl=64 time=0.576 ms
933 64 bytes from 10.0.3.1: icmp_seq=2 ttl=64 time=0.083 ms
934 64 bytes from 10.0.3.1: icmp_seq=3 ttl=64 time=0.088 ms
935 ^C
936 --- 10.0.3.1 ping statistics ---
937 3 packets transmitted, 3 received, 0% packet loss, time 1998ms
938 rtt min/avg/max/mdev = 0.083/0.249/0.576/0.231 ms
939
940 unet> r1 show run
941 Building configuration...
942
943 Current configuration:
944 !
945 frr version 8.1-dev-my-manual-build
946 frr defaults traditional
947 hostname r1
948 log file /tmp/topotests/ospf_topo1.test_ospf_topo1/r1/zebra.log
949 [...]
950 end
951
952 unet> show daemons
953 ------ Host: r1 ------
954 zebra ospfd ospf6d staticd
955 ------- End: r1 ------
956 ------ Host: r2 ------
957 zebra ospfd ospf6d staticd
958 ------- End: r2 ------
959 ------ Host: r3 ------
960 zebra ospfd ospf6d staticd
961 ------- End: r3 ------
962 ------ Host: r4 ------
963 zebra ospfd ospf6d staticd
964 ------- End: r4 ------
965
966 After you successfully configured your topology, you can obtain the
967 configuration files (per-daemon) using the following commands:
968
969 .. code:: shell
970
971 unet> sh r3 vtysh -d ospfd
972
973 Hello, this is FRRouting (version 3.1-devrzalamena-build).
974 Copyright 1996-2005 Kunihiro Ishiguro, et al.
975
976 r1# show running-config
977 Building configuration...
978
979 Current configuration:
980 !
981 frr version 3.1-devrzalamena-build
982 frr defaults traditional
983 no service integrated-vtysh-config
984 !
985 log file ospfd.log
986 !
987 router ospf
988 ospf router-id 10.0.255.3
989 redistribute kernel
990 redistribute connected
991 redistribute static
992 network 10.0.3.0/24 area 0
993 network 10.0.10.0/24 area 0
994 network 172.16.0.0/24 area 1
995 !
996 line vty
997 !
998 end
999 r1#
1000
1001 You can also login to the node specified by nsenter using bash, etc.
1002 A pid file for each node will be created in the relevant test dir.
1003 You can run scripts inside the node, or use vtysh's <tab> or <?> feature.
1004
1005 .. code:: shell
1006
1007 [unet shell]
1008 # cd tests/topotests/srv6_locator
1009 # ./test_srv6_locator.py --topology-only
1010 unet> r1 show segment-routing srv6 locator
1011 Locator:
1012 Name ID Prefix Status
1013 -------------------- ------- ------------------------ -------
1014 loc1 1 2001:db8:1:1::/64 Up
1015 loc2 2 2001:db8:2:2::/64 Up
1016
1017 [Another shell]
1018 # nsenter -a -t $(cat /tmp/topotests/srv6_locator.test_srv6_locator/r1.pid) bash --norc
1019 # vtysh
1020 r1# r1 show segment-routing srv6 locator
1021 Locator:
1022 Name ID Prefix Status
1023 -------------------- ------- ------------------------ -------
1024 loc1 1 2001:db8:1:1::/64 Up
1025 loc2 2 2001:db8:2:2::/64 Up
1026
1027 Writing Tests
1028 """""""""""""
1029
1030 Test topologies should always be bootstrapped from
1031 :file:`tests/topotests/example_test/test_template.py` because it contains
1032 important boilerplate code that can't be avoided, like:
1033
1034 Example:
1035
1036 .. code:: py
1037
1038 # For all routers arrange for:
1039 # - starting zebra using config file from <rtrname>/zebra.conf
1040 # - starting ospfd using an empty config file.
1041 for rname, router in router_list.items():
1042 router.load_config(TopoRouter.RD_ZEBRA, "zebra.conf")
1043 router.load_config(TopoRouter.RD_OSPF)
1044
1045
1046 - The topology definition or build function
1047
1048 .. code:: py
1049
1050 topodef = {
1051 "s1": ("r1", "r2"),
1052 "s2": ("r2", "r3")
1053 }
1054
1055 def build_topo(tgen):
1056 # topology build code
1057 ...
1058
1059 - pytest setup/teardown fixture to start the topology and supply ``tgen``
1060 argument to tests.
1061
1062 .. code:: py
1063
1064
1065 @pytest.fixture(scope="module")
1066 def tgen(request):
1067 "Setup/Teardown the environment and provide tgen argument to tests"
1068
1069 tgen = Topogen(topodef, module.__name__)
1070 # or
1071 tgen = Topogen(build_topo, module.__name__)
1072
1073 ...
1074
1075 # Start and configure the router daemons
1076 tgen.start_router()
1077
1078 # Provide tgen as argument to each test function
1079 yield tgen
1080
1081 # Teardown after last test runs
1082 tgen.stop_topology()
1083
1084
1085 Requirements:
1086
1087 - Directory name for a new topotest must not contain hyphen (``-``) characters.
1088 To separate words, use underscores (``_``). For example, ``tests/topotests/bgp_new_example``.
1089 - Test code should always be declared inside functions that begin with the
1090 ``test_`` prefix. Functions beginning with different prefixes will not be run
1091 by pytest.
1092 - Configuration files and long output commands should go into separated files
1093 inside folders named after the equipment.
1094 - Tests must be able to run without any interaction. To make sure your test
1095 conforms with this, run it without the :option:`-s` parameter.
1096 - Use `black <https://github.com/psf/black>`_ code formatter before creating
1097 a pull request. This ensures we have a unified code style.
1098 - Mark test modules with pytest markers depending on the daemons used during the
1099 tests (see :ref:`topotests-markers`)
1100 - Always use IPv4 :rfc:`5737` (``192.0.2.0/24``, ``198.51.100.0/24``,
1101 ``203.0.113.0/24``) and IPv6 :rfc:`3849` (``2001:db8::/32``) ranges reserved
1102 for documentation.
1103
1104 Tips:
1105
1106 - Keep results in stack variables, so people inspecting code with ``pdb`` can
1107 easily print their values.
1108
1109 Don't do this:
1110
1111 .. code:: py
1112
1113 assert foobar(router1, router2)
1114
1115 Do this instead:
1116
1117 .. code:: py
1118
1119 result = foobar(router1, router2)
1120 assert result
1121
1122 - Use ``assert`` messages to indicate where the test failed.
1123
1124 Example:
1125
1126 .. code:: py
1127
1128 for router in router_list:
1129 # ...
1130 assert condition, 'Router "{}" condition failed'.format(router.name)
1131
1132 Debugging Execution
1133 ^^^^^^^^^^^^^^^^^^^
1134
1135 The most effective ways to inspect topology tests are:
1136
1137 - Run pytest with ``--pdb`` option. This option will cause a pdb shell to
1138 appear when an assertion fails
1139
1140 Example: ``pytest -s --pdb ospf-topo1/test_ospf_topo1.py``
1141
1142 - Set a breakpoint in the test code with ``pdb``
1143
1144 Example:
1145
1146 .. code:: py
1147
1148 # Add the pdb import at the beginning of the file
1149 import pdb
1150 # ...
1151
1152 # Add a breakpoint where you think the problem is
1153 def test_bla():
1154 # ...
1155 pdb.set_trace()
1156 # ...
1157
1158 The `Python Debugger <https://docs.python.org/2.7/library/pdb.html>`__ (pdb)
1159 shell allows us to run many useful operations like:
1160
1161 - Setting breaking point on file/function/conditions (e.g. ``break``,
1162 ``condition``)
1163 - Inspecting variables (e.g. ``p`` (print), ``pp`` (pretty print))
1164 - Running python code
1165
1166 .. tip::
1167
1168 The TopoGear (equipment abstraction class) implements the ``__str__`` method
1169 that allows the user to inspect equipment information.
1170
1171 Example of pdb usage:
1172
1173 .. code:: shell
1174
1175 > /media/sf_src/topotests/ospf-topo1/test_ospf_topo1.py(121)test_ospf_convergence()
1176 -> for rnum in range(1, 5):
1177 (Pdb) help
1178 Documented commands (type help <topic>):
1179 ========================================
1180 EOF bt cont enable jump pp run unt
1181 a c continue exit l q s until
1182 alias cl d h list quit step up
1183 args clear debug help n r tbreak w
1184 b commands disable ignore next restart u whatis
1185 break condition down j p return unalias where
1186
1187 Miscellaneous help topics:
1188 ==========================
1189 exec pdb
1190
1191 Undocumented commands:
1192 ======================
1193 retval rv
1194
1195 (Pdb) list
1196 116 title2="Expected output")
1197 117
1198 118 def test_ospf_convergence():
1199 119 "Test OSPF daemon convergence"
1200 120 pdb.set_trace()
1201 121 -> for rnum in range(1, 5):
1202 122 router = 'r{}'.format(rnum)
1203 123
1204 124 # Load expected results from the command
1205 125 reffile = os.path.join(CWD, '{}/ospfroute.txt'.format(router))
1206 126 expected = open(reffile).read()
1207 (Pdb) step
1208 > /media/sf_src/topotests/ospf-topo1/test_ospf_topo1.py(122)test_ospf_convergence()
1209 -> router = 'r{}'.format(rnum)
1210 (Pdb) step
1211 > /media/sf_src/topotests/ospf-topo1/test_ospf_topo1.py(125)test_ospf_convergence()
1212 -> reffile = os.path.join(CWD, '{}/ospfroute.txt'.format(router))
1213 (Pdb) print rnum
1214 1
1215 (Pdb) print router
1216 r1
1217 (Pdb) tgen = get_topogen()
1218 (Pdb) pp tgen.gears[router]
1219 <lib.topogen.TopoRouter object at 0x7f74e06c9850>
1220 (Pdb) pp str(tgen.gears[router])
1221 'TopoGear<name="r1",links=["r1-eth0"<->"s1-eth0","r1-eth1"<->"s3-eth0"]> TopoRouter<>'
1222 (Pdb) l 125
1223 120 pdb.set_trace()
1224 121 for rnum in range(1, 5):
1225 122 router = 'r{}'.format(rnum)
1226 123
1227 124 # Load expected results from the command
1228 125 -> reffile = os.path.join(CWD, '{}/ospfroute.txt'.format(router))
1229 126 expected = open(reffile).read()
1230 127
1231 128 # Run test function until we get an result. Wait at most 60 seconds.
1232 129 test_func = partial(compare_show_ip_ospf, router, expected)
1233 130 result, diff = topotest.run_and_expect(test_func, '',
1234 (Pdb) router1 = tgen.gears[router]
1235 (Pdb) router1.vtysh_cmd('show ip ospf route')
1236 '============ OSPF network routing table ============\r\nN 10.0.1.0/24 [10] area: 0.0.0.0\r\n directly attached to r1-eth0\r\nN 10.0.2.0/24 [20] area: 0.0.0.0\r\n via 10.0.3.3, r1-eth1\r\nN 10.0.3.0/24 [10] area: 0.0.0.0\r\n directly attached to r1-eth1\r\nN 10.0.10.0/24 [20] area: 0.0.0.0\r\n via 10.0.3.1, r1-eth1\r\nN IA 172.16.0.0/24 [20] area: 0.0.0.0\r\n via 10.0.3.1, r1-eth1\r\nN IA 172.16.1.0/24 [30] area: 0.0.0.0\r\n via 10.0.3.1, r1-eth1\r\n\r\n============ OSPF router routing table =============\r\nR 10.0.255.2 [10] area: 0.0.0.0, ASBR\r\n via 10.0.3.3, r1-eth1\r\nR 10.0.255.3 [10] area: 0.0.0.0, ABR, ASBR\r\n via 10.0.3.1, r1-eth1\r\nR 10.0.255.4 IA [20] area: 0.0.0.0, ASBR\r\n via 10.0.3.1, r1-eth1\r\n\r\n============ OSPF external routing table ===========\r\n\r\n\r\n'
1237 (Pdb) tgen.cli()
1238 unet>
1239
1240 To enable more debug messages in other Topogen subsystems, more
1241 logging messages can be displayed by modifying the test configuration file
1242 ``pytest.ini``:
1243
1244 .. code:: ini
1245
1246 [topogen]
1247 # Change the default verbosity line from 'info'...
1248 #verbosity = info
1249 # ...to 'debug'
1250 verbosity = debug
1251
1252 Instructions for use, write or debug topologies can be found in :ref:`topotests-guidelines`.
1253 To learn/remember common code snippets see :ref:`topotests-snippets`.
1254
1255 Before creating a new topology, make sure that there isn't one already that
1256 does what you need. If nothing is similar, then you may create a new topology,
1257 preferably, using the newest template
1258 (:file:`tests/topotests/example-test/test_template.py`).
1259
1260 .. include:: topotests-markers.rst
1261
1262 .. include:: topotests-snippets.rst
1263
1264 License
1265 -------
1266
1267 All the configs and scripts are licensed under a ISC-style license. See Python
1268 scripts for details.