]> git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/msdp_topo1/test_msdp_topo1.py
Merge pull request #12798 from donaldsharp/rib_match_multicast
[mirror_frr.git] / tests / topotests / msdp_topo1 / test_msdp_topo1.py
1 #!/usr/bin/env python
2 # SPDX-License-Identifier: ISC
3
4 #
5 # test_msdp_topo1.py
6 # Part of NetDEF Topology Tests
7 #
8 # Copyright (c) 2021 by
9 # Network Device Education Foundation, Inc. ("NetDEF")
10 #
11
12 """
13 test_msdp_topo1.py: Test the FRR PIM MSDP peer.
14 """
15
16 import os
17 import sys
18 import json
19 from functools import partial
20 import pytest
21
22 # Save the Current Working Directory to find configuration files.
23 CWD = os.path.dirname(os.path.realpath(__file__))
24 sys.path.append(os.path.join(CWD, "../"))
25
26 # pylint: disable=C0413
27 # Import topogen and topotest helpers
28 from lib import topotest
29
30 # Required to instantiate the topology builder class.
31 from lib.topogen import Topogen, TopoRouter, get_topogen
32 from lib.topolog import logger
33
34 from lib.pim import McastTesterHelper
35
36 pytestmark = [pytest.mark.bgpd, pytest.mark.pimd]
37
38 app_helper = McastTesterHelper()
39
40
41 def build_topo(tgen):
42 "Build function"
43
44 # Create 4 routers
45 for routern in range(1, 5):
46 tgen.add_router("r{}".format(routern))
47
48 switch = tgen.add_switch("s1")
49 switch.add_link(tgen.gears["r1"])
50 switch.add_link(tgen.gears["r2"])
51
52 switch = tgen.add_switch("s2")
53 switch.add_link(tgen.gears["r1"])
54 switch.add_link(tgen.gears["r3"])
55
56 switch = tgen.add_switch("s3")
57 switch.add_link(tgen.gears["r2"])
58 switch.add_link(tgen.gears["r4"])
59
60 switch = tgen.add_switch("s4")
61 # switch.add_link(tgen.gears["r3"])
62 switch.add_link(tgen.gears["r4"])
63
64 switch = tgen.add_switch("s5")
65 switch.add_link(tgen.gears["r4"])
66
67 # Create a host connected and direct at r4:
68 tgen.add_host("h1", "192.168.4.100/24", "via 192.168.4.1")
69 switch.add_link(tgen.gears["h1"])
70
71 # Create a host connected and direct at r1:
72 switch = tgen.add_switch("s6")
73 tgen.add_host("h2", "192.168.10.100/24", "via 192.168.10.1")
74 switch.add_link(tgen.gears["r1"])
75 switch.add_link(tgen.gears["h2"])
76
77
78 def setup_module(mod):
79 "Sets up the pytest environment"
80 tgen = Topogen(build_topo, mod.__name__)
81 tgen.start_topology()
82
83 router_list = tgen.routers()
84 for rname, router in router_list.items():
85 daemon_file = "{}/{}/zebra.conf".format(CWD, rname)
86 if os.path.isfile(daemon_file):
87 router.load_config(TopoRouter.RD_ZEBRA, daemon_file)
88
89 daemon_file = "{}/{}/bgpd.conf".format(CWD, rname)
90 if os.path.isfile(daemon_file):
91 router.load_config(TopoRouter.RD_BGP, daemon_file)
92
93 daemon_file = "{}/{}/pimd.conf".format(CWD, rname)
94 if os.path.isfile(daemon_file):
95 router.load_config(TopoRouter.RD_PIM, daemon_file)
96
97 # Initialize all routers.
98 tgen.start_router()
99
100 app_helper.init(tgen)
101
102
103 def teardown_module(mod):
104 "Teardown the pytest environment"
105 tgen = get_topogen()
106 app_helper.cleanup()
107 tgen.stop_topology()
108
109
110 def test_bgp_convergence():
111 "Wait for BGP protocol convergence"
112 tgen = get_topogen()
113 if tgen.routers_have_failure():
114 pytest.skip(tgen.errors)
115
116 logger.info("waiting for protocols to converge")
117
118 def expect_loopback_route(router, iptype, route, proto):
119 "Wait until route is present on RIB for protocol."
120 logger.info("waiting route {} in {}".format(route, router))
121 test_func = partial(
122 topotest.router_json_cmp,
123 tgen.gears[router],
124 "show {} route json".format(iptype),
125 {route: [{"protocol": proto}]},
126 )
127 _, result = topotest.run_and_expect(test_func, None, count=130, wait=1)
128 assertmsg = '"{}" convergence failure'.format(router)
129 assert result is None, assertmsg
130
131 # Wait for R1
132 expect_loopback_route("r1", "ip", "10.254.254.2/32", "bgp")
133 expect_loopback_route("r1", "ip", "10.254.254.3/32", "bgp")
134 expect_loopback_route("r1", "ip", "10.254.254.4/32", "bgp")
135
136 # Wait for R2
137 expect_loopback_route("r2", "ip", "10.254.254.1/32", "bgp")
138 expect_loopback_route("r2", "ip", "10.254.254.3/32", "bgp")
139 expect_loopback_route("r2", "ip", "10.254.254.4/32", "bgp")
140
141 # Wait for R3
142 expect_loopback_route("r3", "ip", "10.254.254.1/32", "bgp")
143 expect_loopback_route("r3", "ip", "10.254.254.2/32", "bgp")
144 expect_loopback_route("r3", "ip", "10.254.254.4/32", "bgp")
145
146 # Wait for R4
147 expect_loopback_route("r4", "ip", "10.254.254.1/32", "bgp")
148 expect_loopback_route("r4", "ip", "10.254.254.2/32", "bgp")
149 expect_loopback_route("r4", "ip", "10.254.254.3/32", "bgp")
150
151
152 def _test_mroute_install():
153 "Test that multicast routes propagated and installed"
154 tgen = get_topogen()
155 if tgen.routers_have_failure():
156 pytest.skip(tgen.errors)
157
158 #
159 # Test R1 mroute
160 #
161 expect_1 = {
162 "229.1.2.3": {
163 "192.168.10.100": {
164 "iif": "r1-eth2",
165 "flags": "SFT",
166 "oil": {
167 "r1-eth0": {"source": "192.168.10.100", "group": "229.1.2.3"},
168 "r1-eth1": None,
169 },
170 }
171 }
172 }
173 # Create a deep copy of `expect_1`.
174 expect_2 = json.loads(json.dumps(expect_1))
175 # The route will be either via R2 or R3.
176 expect_2["229.1.2.3"]["192.168.10.100"]["oil"]["r1-eth0"] = None
177 expect_2["229.1.2.3"]["192.168.10.100"]["oil"]["r1-eth1"] = {
178 "source": "192.168.10.100",
179 "group": "229.1.2.3",
180 }
181
182 def test_r1_mroute():
183 "Test r1 multicast routing table function"
184 out = tgen.gears["r1"].vtysh_cmd("show ip mroute json", isjson=True)
185 if topotest.json_cmp(out, expect_1) is None:
186 return None
187 return topotest.json_cmp(out, expect_2)
188
189 logger.info("Waiting for R1 multicast routes")
190 _, val = topotest.run_and_expect(test_r1_mroute, None, count=55, wait=2)
191 assert val is None, "multicast route convergence failure"
192
193 #
194 # Test routers 2 and 3.
195 #
196 # NOTE: only one of the paths will get the multicast route.
197 #
198 expect_r2 = {
199 "229.1.2.3": {
200 "192.168.10.100": {
201 "iif": "r2-eth0",
202 "flags": "S",
203 "oil": {
204 "r2-eth1": {
205 "source": "192.168.10.100",
206 "group": "229.1.2.3",
207 }
208 },
209 }
210 }
211 }
212 expect_r3 = {
213 "229.1.2.3": {
214 "192.168.10.100": {
215 "iif": "r3-eth0",
216 "flags": "S",
217 "oil": {
218 "r3-eth1": {
219 "source": "192.168.10.100",
220 "group": "229.1.2.3",
221 }
222 },
223 }
224 }
225 }
226
227 def test_r2_r3_mroute():
228 "Test r2/r3 multicast routing table function"
229 r2_out = tgen.gears["r2"].vtysh_cmd("show ip mroute json", isjson=True)
230 r3_out = tgen.gears["r3"].vtysh_cmd("show ip mroute json", isjson=True)
231
232 if topotest.json_cmp(r2_out, expect_r2) is not None:
233 return topotest.json_cmp(r3_out, expect_r3)
234
235 return topotest.json_cmp(r2_out, expect_r2)
236
237 logger.info("Waiting for R2 and R3 multicast routes")
238 _, val = topotest.run_and_expect(test_r2_r3_mroute, None, count=55, wait=2)
239 assert val is None, "multicast route convergence failure"
240
241 #
242 # Test router 4
243 #
244 expect_4 = {
245 "229.1.2.3": {
246 "*": {
247 "iif": "lo",
248 "flags": "SC",
249 "oil": {
250 "pimreg": {
251 "source": "*",
252 "group": "229.1.2.3",
253 "inboundInterface": "lo",
254 "outboundInterface": "pimreg",
255 },
256 "r4-eth2": {
257 "source": "*",
258 "group": "229.1.2.3",
259 "inboundInterface": "lo",
260 "outboundInterface": "r4-eth2",
261 },
262 },
263 },
264 "192.168.10.100": {
265 "iif": "r4-eth0",
266 "flags": "ST",
267 "oil": {
268 "r4-eth2": {
269 "source": "192.168.10.100",
270 "group": "229.1.2.3",
271 "inboundInterface": "r4-eth0",
272 "outboundInterface": "r4-eth2",
273 }
274 },
275 },
276 }
277 }
278
279 test_func = partial(
280 topotest.router_json_cmp,
281 tgen.gears["r4"],
282 "show ip mroute json",
283 expect_4,
284 )
285 logger.info("Waiting for R4 multicast routes")
286 _, val = topotest.run_and_expect(test_func, None, count=55, wait=2)
287 assert val is None, "multicast route convergence failure"
288
289
290 def test_mroute_install():
291 tgen = get_topogen()
292 # pytest.skip("FOO")
293 if tgen.routers_have_failure():
294 pytest.skip(tgen.errors)
295
296 logger.info("Starting helper1")
297 mcastaddr = "229.1.2.3"
298 app_helper.run("h1", [mcastaddr, "h1-eth0"])
299
300 logger.info("Starting helper2")
301 app_helper.run("h2", ["--send=0.7", mcastaddr, "h2-eth0"])
302
303 _test_mroute_install()
304
305
306 def test_msdp():
307 """
308 Test MSDP convergence.
309
310 MSDP non meshed groups must propagate the whole SA database (not just
311 their own) to all peers because not all peers talk with each other.
312
313 This setup leads to a potential loop that can be prevented by checking
314 the route's first AS in AS path: it must match the remote eBGP AS number.
315 """
316 tgen = get_topogen()
317 if tgen.routers_have_failure():
318 pytest.skip(tgen.errors)
319
320 r1_expect = {
321 "192.168.0.2": {
322 "peer": "192.168.0.2",
323 "local": "192.168.0.1",
324 "state": "established",
325 },
326 "192.168.1.2": {
327 "peer": "192.168.1.2",
328 "local": "192.168.1.1",
329 "state": "established",
330 },
331 }
332 r1_sa_expect = {
333 "229.1.2.3": {
334 "192.168.10.100": {
335 "source": "192.168.10.100",
336 "group": "229.1.2.3",
337 "rp": "-",
338 "local": "yes",
339 "sptSetup": "-",
340 }
341 }
342 }
343 r2_expect = {
344 "192.168.0.1": {
345 "peer": "192.168.0.1",
346 "local": "192.168.0.2",
347 "state": "established",
348 },
349 "192.168.2.2": {
350 "peer": "192.168.2.2",
351 "local": "192.168.2.1",
352 "state": "established",
353 },
354 }
355 # Only R2 or R3 will get this SA.
356 r2_r3_sa_expect = {
357 "229.1.2.3": {
358 "192.168.10.100": {
359 "source": "192.168.10.100",
360 "group": "229.1.2.3",
361 "rp": "192.168.1.1",
362 "local": "no",
363 "sptSetup": "no",
364 }
365 }
366 }
367 r3_expect = {
368 "192.168.1.1": {
369 "peer": "192.168.1.1",
370 "local": "192.168.1.2",
371 "state": "established",
372 },
373 # "192.169.3.2": {
374 # "peer": "192.168.3.2",
375 # "local": "192.168.3.1",
376 # "state": "established"
377 # }
378 }
379 r4_expect = {
380 "192.168.2.1": {
381 "peer": "192.168.2.1",
382 "local": "192.168.2.2",
383 "state": "established",
384 },
385 # "192.168.3.1": {
386 # "peer": "192.168.3.1",
387 # "local": "192.168.3.2",
388 # "state": "established"
389 # }
390 }
391 r4_sa_expect = {
392 "229.1.2.3": {
393 "192.168.10.100": {
394 "source": "192.168.10.100",
395 "group": "229.1.2.3",
396 "rp": "192.168.1.1",
397 "local": "no",
398 "sptSetup": "yes",
399 }
400 }
401 }
402
403 for router in [
404 ("r1", r1_expect, r1_sa_expect),
405 ("r2", r2_expect, r2_r3_sa_expect),
406 ("r3", r3_expect, r2_r3_sa_expect),
407 ("r4", r4_expect, r4_sa_expect),
408 ]:
409 test_func = partial(
410 topotest.router_json_cmp,
411 tgen.gears[router[0]],
412 "show ip msdp peer json",
413 router[1],
414 )
415 logger.info("Waiting for {} msdp peer data".format(router[0]))
416 _, val = topotest.run_and_expect(test_func, None, count=30, wait=1)
417 assert val is None, "multicast route convergence failure"
418
419 test_func = partial(
420 topotest.router_json_cmp,
421 tgen.gears[router[0]],
422 "show ip msdp sa json",
423 router[2],
424 )
425 logger.info("Waiting for {} msdp SA data".format(router[0]))
426 _, val = topotest.run_and_expect(test_func, None, count=30, wait=1)
427 assert val is None, "multicast route convergence failure"
428
429
430 def test_memory_leak():
431 "Run the memory leak test and report results."
432 tgen = get_topogen()
433 if not tgen.is_memleak_enabled():
434 pytest.skip("Memory leak test/report is disabled")
435
436 tgen.report_memory_leaks()
437
438
439 if __name__ == "__main__":
440 args = ["-s"] + sys.argv[1:]
441 sys.exit(pytest.main(args))