]> git.proxmox.com Git - mirror_frr.git/blob - doc/developer/topotests-jsontopo.rst
Merge pull request #5706 from mjstapp/fix_nh_debug_show
[mirror_frr.git] / doc / developer / topotests-jsontopo.rst
1 .. _topotests-json:
2
3 Topotests with JSON
4 ===================
5
6 Overview
7 --------
8
9 On top of current topotests framework following enhancements are done:
10
11
12 * Creating the topology and assigning IPs to router' interfaces dynamically.
13 It is achieved by using json file, in which user specify the number of
14 routers, links to each router, interfaces for the routers and protocol
15 configurations for all routers.
16
17 * Creating the configurations dynamically. It is achieved by using
18 :file:`/usr/lib/frr/frr-reload.py` utility, which takes running configuration
19 and the newly created configuration for any particular router and creates a
20 delta file(diff file) and loads it to router.
21
22
23 Logging of test case executions
24 -------------------------------
25
26 * The user can enable logging of testcases execution messages into log file by
27 adding ``frrtest_log_dir = /tmp/topotests/`` in :file:`pytest.ini`.
28 * Router's current configuration can be displyed on console or sent to logs by
29 adding ``show_router_config = True`` in :file:`pytest.ini`.
30
31 Log file name will be displayed when we start execution:
32
33 .. code-block:: console
34
35 root@test:# python ./test_topo_json_single_link.py
36
37 Logs will be sent to logfile:
38 /tmp/topotests/test_topo_json_single_link_11:57:01.353797
39
40 Note: directory "/tmp/topotests/" is created by topotests by default, making
41 use of same directory to save execution logs.
42
43 Guidelines
44 ----------
45
46 Writing New Tests
47 ^^^^^^^^^^^^^^^^^
48
49 This section will guide you in all recommended steps to produce a standard
50 topology test.
51
52 This is the recommended test writing routine:
53
54 * Create a json file , which will have routers and protocol configurations
55 * Create topology from json
56 * Create configuration from json
57 * Write the tests
58 * Create a Pull Request
59
60
61 File Hierarchy
62 ^^^^^^^^^^^^^^
63
64 Before starting to write any tests one must know the file hierarchy. The
65 repository hierarchy looks like this:
66
67 .. code-block:: console
68
69 $ cd path/to/topotests
70 $ find ./*
71 ...
72 ./example-topojson-test # the basic example test topology-1
73 ./example-topojson-test/test_example_topojson.json # input json file, having
74 topology, interfaces, bgp and other configuration
75 ./example-topojson-test/test_example_topojson.py # test script to write and
76 execute testcases
77 ...
78 ./lib # shared test/topology functions
79 ./lib/topojson.py # library to create topology and configurations dynamically
80 from json file
81 ./lib/common_config.py # library to create protocol's common configurations ex-
82 static_routes, prefix_lists, route_maps etc.
83 ./lib/bgp.py # library to create only bgp configurations
84
85 Defining the Topology and initial configuration in JSON file
86 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
87
88 The first step to write a new test is to define the topology and initial
89 configuration. User has to define topology and initial configuration in JSON
90 file. Here is an example of JSON file::
91
92 BGP neihghborship with single phy-link, sample JSON file:
93 {
94 "ipv4base": "192.168.0.0",
95 "ipv4mask": 30,
96 "ipv6base": "fd00::",
97 "ipv6mask": 64,
98 "link_ip_start": {"ipv4": "192.168.0.0", "v4mask": 30, "ipv6": "fd00::", "v6mask": 64},
99 "lo_prefix": {"ipv4": "1.0.", "v4mask": 32, "ipv6": "2001:DB8:F::", "v6mask": 128},
100 "routers": {
101 "r1": {
102 "links": {
103 "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"},
104 "r2": {"ipv4": "auto", "ipv6": "auto"},
105 "r3": {"ipv4": "auto", "ipv6": "auto"}
106 },
107 "bgp": {
108 "local_as": "64512",
109 "address_family": {
110 "ipv4": {
111 "unicast": {
112 "neighbor": {
113 "r2": {
114 "dest_link": {
115 "r1": {}
116 }
117 },
118 "r3": {
119 "dest_link": {
120 "r1": {}
121 }
122 }
123 }
124 }
125 }
126 }
127 }
128 },
129 "r2": {
130 "links": {
131 "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"},
132 "r1": {"ipv4": "auto", "ipv6": "auto"},
133 "r3": {"ipv4": "auto", "ipv6": "auto"}
134 },
135 "bgp": {
136 "local_as": "64512",
137 "address_family": {
138 "ipv4": {
139 "unicast": {
140 "redistribute": [
141 {
142 "redist_type": "static"
143 }
144 ],
145 "neighbor": {
146 "r1": {
147 "dest_link": {
148 "r2": {}
149 }
150 },
151 "r3": {
152 "dest_link": {
153 "r2": {}
154 }
155 }
156 }
157 }
158 }
159 }
160 }
161 }
162 ...
163
164
165 BGP neighboship with loopback interface, sample JSON file::
166
167 {
168 "ipv4base": "192.168.0.0",
169 "ipv4mask": 30,
170 "ipv6base": "fd00::",
171 "ipv6mask": 64,
172 "link_ip_start": {"ipv4": "192.168.0.0", "v4mask": 30, "ipv6": "fd00::", "v6mask": 64},
173 "lo_prefix": {"ipv4": "1.0.", "v4mask": 32, "ipv6": "2001:DB8:F::", "v6mask": 128},
174 "routers": {
175 "r1": {
176 "links": {
177 "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback",
178 "add_static_route":"yes"},
179 "r2": {"ipv4": "auto", "ipv6": "auto"}
180 },
181 "bgp": {
182 "local_as": "64512",
183 "address_family": {
184 "ipv4": {
185 "unicast": {
186 "neighbor": {
187 "r2": {
188 "dest_link": {
189 "lo": {
190 "source_link": "lo"
191 }
192 }
193 }
194 }
195 }
196 }
197 }
198 },
199 "static_routes": [
200 {
201 "network": "1.0.2.17/32",
202 "next_hop": "192.168.0.1
203 }
204 ]
205 },
206 "r2": {
207 "links": {
208 "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback",
209 "add_static_route":"yes"},
210 "r1": {"ipv4": "auto", "ipv6": "auto"},
211 "r3": {"ipv4": "auto", "ipv6": "auto"}
212 },
213 "bgp": {
214 "local_as": "64512",
215 "address_family": {
216 "ipv4": {
217 "unicast": {
218 "redistribute": [
219 {
220 "redist_type": "static"
221 }
222 ],
223 "neighbor": {
224 "r1": {
225 "dest_link": {
226 "lo": {
227 "source_link": "lo"
228 }
229 }
230 },
231 "r3": {
232 "dest_link": {
233 "lo": {
234 "source_link": "lo"
235 }
236 }
237 }
238 }
239 }
240 }
241 }
242 },
243 "static_routes": [
244 {
245 "network": "192.0.20.1/32",
246 "no_of_ip": 9,
247 "admin_distance": 100,
248 "next_hop": "192.168.0.1",
249 "tag": 4001
250 }
251 ],
252 }
253 ...
254
255 BGP neighborship with Multiple phy-links, sample JSON file::
256
257 {
258 "ipv4base": "192.168.0.0",
259 "ipv4mask": 30,
260 "ipv6base": "fd00::",
261 "ipv6mask": 64,
262 "link_ip_start": {"ipv4": "192.168.0.0", "v4mask": 30, "ipv6": "fd00::", "v6mask": 64},
263 "lo_prefix": {"ipv4": "1.0.", "v4mask": 32, "ipv6": "2001:DB8:F::", "v6mask": 128},
264 "routers": {
265 "r1": {
266 "links": {
267 "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"},
268 "r2-link1": {"ipv4": "auto", "ipv6": "auto"},
269 "r2-link2": {"ipv4": "auto", "ipv6": "auto"}
270 },
271 "bgp": {
272 "local_as": "64512",
273 "address_family": {
274 "ipv4": {
275 "unicast": {
276 "neighbor": {
277 "r2": {
278 "dest_link": {
279 "r1-link1": {}
280 }
281 }
282 }
283 }
284 }
285 }
286 }
287 },
288 "r2": {
289 "links": {
290 "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"},
291 "r1-link1": {"ipv4": "auto", "ipv6": "auto"},
292 "r1-link2": {"ipv4": "auto", "ipv6": "auto"},
293 "r3-link1": {"ipv4": "auto", "ipv6": "auto"},
294 "r3-link2": {"ipv4": "auto", "ipv6": "auto"}
295 },
296 "bgp": {
297 "local_as": "64512",
298 "address_family": {
299 "ipv4": {
300 "unicast": {
301 "redistribute": [
302 {
303 "redist_type": "static"
304 }
305 ],
306 "neighbor": {
307 "r1": {
308 "dest_link": {
309 "r2-link1": {}
310 }
311 },
312 "r3": {
313 "dest_link": {
314 "r2-link1": {}
315 }
316 }
317 }
318 }
319 }
320 }
321 }
322 }
323 ...
324
325
326 JSON File Explained
327 """""""""""""""""""
328
329 Mandatory keywords/options in JSON:
330
331 * ``ipv4base`` : base ipv4 address to generate ips, ex - 192.168.0.0
332 * ``ipv4mask`` : mask for ipv4 address, ex - 30
333 * ``ipv6base`` : base ipv6 address to generate ips, ex - fd00:
334 * ``ipv6mask`` : mask for ipv6 address, ex - 64
335 * ``link_ip_start`` : physical interface base ipv4 and ipv6 address
336 * ``lo_prefix`` : loopback interface base ipv4 and ipv6 address
337 * ``routers`` : user can add number of routers as per topology, router's name
338 can be any logical name, ex- r1 or a0.
339 * ``r1`` : name of the router
340 * ``lo`` : loopback interface dict, ipv4 and/or ipv6 addresses generated automatically
341 * ``type`` : type of interface, to identify loopback interface
342 * ``links`` : physical interfaces dict, ipv4 and/or ipv6 addresses generated
343 automatically
344 * ``r2-link1`` : it will be used when routers have multiple links. 'r2' is router
345 name, 'link' is any logical name, '1' is to identify link number,
346 router name and link must be seperated by hyphen (``-``), ex- a0-peer1
347
348 Optional keywords/options in JSON:
349
350 * ``bgp`` : bgp configuration
351 * ``local_as`` : Local AS number
352 * ``unicast`` : All SAFI configuration
353 * ``neighbor``: All neighbor details
354 * ``dest_link`` : Destination link to which router will connect
355 * ``router_id`` : bgp router-id
356 * ``source_link`` : if user wants to establish bgp neighborship with loopback
357 interface, add ``source_link``: ``lo``
358 * ``keepalivetimer`` : Keep alive timer for BGP neighbor
359 * ``holddowntimer`` : Hold down timer for BGP neighbor
360 * ``static_routes`` : create static routes for routers
361 * ``redistribute`` : redistribute static and/or connected routes
362 * ``prefix_lists`` : create Prefix-lists for routers
363
364 Building topology and configurations
365 """"""""""""""""""""""""""""""""""""
366
367 Topology and initial configuration will be created in setup_module(). Following
368 is the sample code::
369
370 class TemplateTopo(Topo):
371 def build(self, *_args, **_opts):
372 "Build function"
373 tgen = get_topogen(self)
374
375 # Building topology from json file
376 build_topo_from_json(tgen, topo)
377
378 def setup_module(mod):
379 tgen = Topogen(TemplateTopo, mod.__name__)
380
381 # Starting topology, create tmp files which are loaded to routers
382 # to start deamons and then start routers
383 start_topology(tgen)
384
385 # Creating configuration from JSON
386 build_config_from_json(tgen, topo)
387
388 def teardown_module(mod):
389 tgen = get_topogen()
390
391 # Stop toplogy and Remove tmp files
392 stop_topology(tgen)
393
394
395 * Note: Topology will be created in setup module but routers will not be
396 started until we load zebra.conf and bgpd.conf to routers. For all routers
397 dirs will be created in /tmp/topotests/<test_folder_name>/<router_name>
398 zebra.conf and bgpd.conf empty files will be created and laoded to routers.
399 All folder and files are deleted in teardown module..
400
401 Creating configuration files
402 """"""""""""""""""""""""""""
403
404 Router's configuration would be saved in config file frr_json.conf. Common
405 configurations are like, static routes, prefixlists and route maps etc configs,
406 these configs can be used by any other protocols as it is.
407 BGP config will be specific to BGP protocol testing.
408
409 * JSON file is passed to API build_config_from_json(), which looks for
410 configuration tags in JSON file.
411 * If tag is found in JSON, configuration is created as per input and written
412 to file frr_json.conf
413 * Once JSON parsing is over, frr_json.conf is loaded onto respective router.
414 Config loading is done using 'vtysh -f <file>'. Initial config at this point
415 is also saved frr_json_initial.conf. This file can be used to reset
416 configuration on router, during the course of execution.
417 * Reset of configuration is done using frr "reload.py" utility, which
418 calculates the difference between router's running config and user's config
419 and loads delta file to router. API used - reset_config_on_router()
420
421 Writing Tests
422 """""""""""""
423
424 Test topologies should always be bootstrapped from the
425 example-test/test_example.py, because it contains important boilerplate code
426 that can't be avoided, like:
427
428 imports: os, sys, pytest, topotest/topogen and mininet topology class
429
430 The global variable CWD (Current Working directory): which is most likely going
431 to be used to reference the routers configuration file location
432
433 Example:
434
435
436 * The topology class that inherits from Mininet Topo class;
437
438 .. code-block:: python
439
440 class TemplateTopo(Topo):
441 def build(self, *_args, **_opts):
442 tgen = get_topogen(self)
443 # topology build code
444
445
446 * pytest setup_module() and teardown_module() to start the topology:
447
448 .. code-block:: python
449
450 def setup_module(_m):
451 tgen = Topogen(TemplateTopo)
452
453 # Starting topology, create tmp files which are loaded to routers
454 # to start deamons and then start routers
455 start_topology(tgen, CWD)
456
457 def teardown_module(_m):
458 tgen = get_topogen()
459
460 # Stop toplogy and Remove tmp files
461 stop_topology(tgen, CWD)
462
463
464 * ``__main__`` initialization code (to support running the script directly)
465
466 .. code-block:: python
467
468 if **name** == '\ **main**\ ':
469 sys.exit(pytest.main(["-s"]))
470