]>
Commit | Line | Data |
---|---|---|
51118489 MW |
1 | #!/usr/bin/env python |
2 | ||
3 | # | |
4 | # test_pim_vrf.py | |
5 | # Part of NetDEF Topology Tests | |
6 | # | |
7 | # Copyright (c) 2020 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 | """ | |
26 | test_pim_vrf.py: Test PIM with VRFs. | |
27 | """ | |
28 | ||
d7d21c3a CH |
29 | # XXX clean up in later commit to avoid conflict on rebase |
30 | # pylint: disable=C0413 | |
31 | ||
51118489 MW |
32 | # Tests PIM with VRF |
33 | # | |
34 | # R1 is split into 2 VRF: Blue and Red, the others are normal | |
35 | # routers and Hosts | |
36 | # There are 2 similar topologies with overlapping IPs in each | |
a53c08bc | 37 | # section. |
51118489 MW |
38 | # |
39 | # Test steps: | |
40 | # - setup_module() | |
41 | # Create topology. Hosts are only using zebra/staticd, | |
42 | # no PIM, no OSPF (using IGMPv2 for multicast) | |
43 | # - test_ospf_convergence() | |
44 | # Wait for OSPF convergence in each VRF. OSPF is run on | |
45 | # R1, R11 and R12. | |
46 | # - test_pim_convergence() | |
47 | # Wait for PIM convergence in each VRF. PIM is run on | |
48 | # R1, R11 and R12. R11 is the RP for vrf blue, R12 is RP | |
49 | # for vrf red. | |
50 | # - test_vrf_pimreg_interfaces() | |
a53c08bc | 51 | # Adding PIM RP in VRF information and verify pimreg |
51118489 MW |
52 | # interfaces in VRF blue and red |
53 | # - test_mcast_vrf_blue() | |
a53c08bc | 54 | # Start multicast stream for group 239.100.0.1 from Host |
51118489 MW |
55 | # H2 and join from Host H1 on vrf blue |
56 | # Verify PIM JOIN status on R1 and R11 | |
57 | # Stop multicast after verification | |
58 | # - test_mcast_vrf_red() | |
a53c08bc | 59 | # Start multicast stream for group 239.100.0.1 from Host |
51118489 MW |
60 | # H4 and join from Host H3 on vrf blue |
61 | # Verify PIM JOIN status on R1 and R12 | |
62 | # Stop multicast after verification | |
63 | # - teardown_module(module) | |
64 | # shutdown topology | |
65 | # | |
66 | ||
67 | TOPOLOGY = """ | |
68 | +----------+ | |
69 | | Host H2 | | |
70 | | Source | | |
71 | +----------+ | |
72 | .2 | | |
73 | +---------+ +------------+ | +---------+ | |
74 | | Host H1 | 192.168.100.0/24 | | .1 | .11 | Host H2 | | |
75 | | receive |------------------| VRF Blue |---------+--------| PIM RP | | |
76 | |IGMP JOIN| .10 .1 | | 192.168.101.0/24 | | | |
77 | +---------+ | | +---------+ | |
78 | =| = = R1 = = |= | |
79 | +---------+ | | +---------+ | |
80 | | Host H3 | 192.168.100.0/24 | | 192.168.101.0/24 | Host H4 | | |
81 | | receive |------------------| VRF Red |---------+--------| PIM RP | | |
82 | |IGMP JOIN| .20 .1 | | .1 | .12 | | | |
83 | +---------+ +------------+ | +---------+ | |
84 | .4 | | |
85 | +----------+ | |
86 | | Host H4 | | |
87 | | Source | | |
88 | +----------+ | |
89 | """ | |
90 | ||
91 | import json | |
92 | import functools | |
93 | import os | |
94 | import sys | |
95 | import pytest | |
51118489 MW |
96 | |
97 | # Save the Current Working Directory to find configuration files. | |
98 | CWD = os.path.dirname(os.path.realpath(__file__)) | |
99 | sys.path.append(os.path.join(CWD, "../")) | |
100 | ||
101 | # pylint: disable=C0413 | |
102 | # Import topogen and topotest helpers | |
103 | from lib import topotest | |
104 | from lib.topogen import Topogen, TopoRouter, get_topogen | |
105 | from lib.topolog import logger | |
106 | from lib.topotest import iproute2_is_vrf_capable | |
a53c08bc | 107 | from lib.common_config import required_linux_kernel_version |
1973df1d | 108 | from lib.pim import McastTesterHelper |
51118489 | 109 | |
51118489 | 110 | |
4be92408 | 111 | pytestmark = [pytest.mark.ospfd, pytest.mark.pimd] |
51118489 MW |
112 | |
113 | ||
1973df1d | 114 | def build_topo(tgen): |
a53c08bc | 115 | for hostNum in range(1, 5): |
1973df1d CH |
116 | tgen.add_router("h{}".format(hostNum)) |
117 | ||
118 | # Create the main router | |
119 | tgen.add_router("r1") | |
120 | ||
121 | # Create the PIM RP routers | |
122 | for rtrNum in range(11, 13): | |
123 | tgen.add_router("r{}".format(rtrNum)) | |
124 | ||
125 | # Setup Switches and connections | |
126 | for swNum in range(1, 5): | |
127 | tgen.add_switch("sw{}".format(swNum)) | |
128 | ||
129 | ################ | |
130 | # 1st set of connections to routers for VRF red | |
131 | ################ | |
132 | ||
133 | # Add connections H1 to R1 switch sw1 | |
134 | tgen.gears["h1"].add_link(tgen.gears["sw1"]) | |
135 | tgen.gears["r1"].add_link(tgen.gears["sw1"]) | |
136 | ||
137 | # Add connections R1 to R1x switch sw2 | |
138 | tgen.gears["r1"].add_link(tgen.gears["sw2"]) | |
139 | tgen.gears["h2"].add_link(tgen.gears["sw2"]) | |
140 | tgen.gears["r11"].add_link(tgen.gears["sw2"]) | |
141 | ||
142 | ################ | |
143 | # 2nd set of connections to routers for vrf blue | |
144 | ################ | |
145 | ||
146 | # Add connections H1 to R1 switch sw1 | |
147 | tgen.gears["h3"].add_link(tgen.gears["sw3"]) | |
148 | tgen.gears["r1"].add_link(tgen.gears["sw3"]) | |
149 | ||
150 | # Add connections R1 to R1x switch sw2 | |
151 | tgen.gears["r1"].add_link(tgen.gears["sw4"]) | |
152 | tgen.gears["h4"].add_link(tgen.gears["sw4"]) | |
153 | tgen.gears["r12"].add_link(tgen.gears["sw4"]) | |
51118489 | 154 | |
a53c08bc | 155 | |
51118489 MW |
156 | ##################################################### |
157 | # | |
158 | # Tests starting | |
159 | # | |
160 | ##################################################### | |
161 | ||
a53c08bc | 162 | |
51118489 MW |
163 | def setup_module(module): |
164 | logger.info("PIM IGMP VRF Topology: \n {}".format(TOPOLOGY)) | |
165 | ||
1973df1d | 166 | tgen = Topogen(build_topo, module.__name__) |
51118489 MW |
167 | tgen.start_topology() |
168 | ||
c72e51ac DS |
169 | # Required linux kernel version for this suite to run. |
170 | result = required_linux_kernel_version("4.19") | |
171 | if result is not True: | |
172 | pytest.skip("Kernel requirements are not met") | |
173 | ||
51118489 MW |
174 | vrf_setup_cmds = [ |
175 | "ip link add name blue type vrf table 11", | |
176 | "ip link add name red type vrf table 12", | |
177 | "ip link set dev blue up", | |
178 | "ip link set dev red up", | |
179 | "ip link set dev r1-eth0 vrf blue up", | |
180 | "ip link set dev r1-eth1 vrf blue up", | |
181 | "ip link set dev r1-eth2 vrf red up", | |
182 | "ip link set dev r1-eth3 vrf red up", | |
183 | ] | |
184 | ||
185 | # Starting Routers | |
186 | router_list = tgen.routers() | |
187 | ||
188 | # Create VRF on r2 first and add it's interfaces | |
189 | for cmd in vrf_setup_cmds: | |
190 | tgen.net["r1"].cmd(cmd) | |
191 | ||
192 | for rname, router in router_list.items(): | |
193 | logger.info("Loading router %s" % rname) | |
194 | router.load_config( | |
195 | TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) | |
196 | ) | |
a53c08bc | 197 | if rname[0] != "h": |
51118489 MW |
198 | # Only load ospf on routers, not on end hosts |
199 | router.load_config( | |
200 | TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname)) | |
201 | ) | |
202 | router.load_config( | |
203 | TopoRouter.RD_PIM, os.path.join(CWD, "{}/pimd.conf".format(rname)) | |
204 | ) | |
1973df1d | 205 | |
51118489 MW |
206 | tgen.start_router() |
207 | ||
208 | ||
209 | def teardown_module(module): | |
210 | tgen = get_topogen() | |
211 | tgen.stop_topology() | |
51118489 MW |
212 | |
213 | ||
214 | def test_ospf_convergence(): | |
215 | "Test for OSPFv2 convergence" | |
216 | tgen = get_topogen() | |
217 | ||
51118489 MW |
218 | # iproute2 needs to support VRFs for this suite to run. |
219 | if not iproute2_is_vrf_capable(): | |
220 | pytest.skip("Installed iproute2 version does not support VRFs") | |
221 | ||
222 | # Skip if previous fatal error condition is raised | |
223 | if tgen.routers_have_failure(): | |
224 | pytest.skip(tgen.errors) | |
225 | ||
226 | logger.info("Checking OSPFv2 convergence on router r1 for VRF blue") | |
227 | ||
228 | router = tgen.gears["r1"] | |
229 | reffile = os.path.join(CWD, "r1/ospf_blue_neighbor.json") | |
230 | expected = json.loads(open(reffile).read()) | |
231 | ||
232 | test_func = functools.partial( | |
a53c08bc CH |
233 | topotest.router_json_cmp, |
234 | router, | |
235 | "show ip ospf vrf blue neighbor json", | |
236 | expected, | |
51118489 MW |
237 | ) |
238 | _, res = topotest.run_and_expect(test_func, None, count=60, wait=2) | |
239 | assertmsg = "OSPF router R1 did not converge on VRF blue" | |
240 | assert res is None, assertmsg | |
241 | ||
242 | logger.info("Checking OSPFv2 convergence on router r1 for VRF red") | |
243 | ||
244 | router = tgen.gears["r1"] | |
245 | reffile = os.path.join(CWD, "r1/ospf_red_neighbor.json") | |
246 | expected = json.loads(open(reffile).read()) | |
247 | ||
248 | test_func = functools.partial( | |
249 | topotest.router_json_cmp, router, "show ip ospf vrf red neighbor json", expected | |
250 | ) | |
251 | _, res = topotest.run_and_expect(test_func, None, count=60, wait=2) | |
252 | assertmsg = "OSPF router R1 did not converge on VRF red" | |
253 | assert res is None, assertmsg | |
254 | ||
255 | ||
256 | def test_pim_convergence(): | |
257 | "Test for PIM convergence" | |
258 | tgen = get_topogen() | |
259 | ||
260 | # Skip if previous fatal error condition is raised | |
261 | if tgen.routers_have_failure(): | |
262 | pytest.skip(tgen.errors) | |
263 | ||
264 | logger.info("Checking PIM convergence on router r1 for VRF red") | |
265 | ||
266 | router = tgen.gears["r1"] | |
267 | reffile = os.path.join(CWD, "r1/pim_red_neighbor.json") | |
268 | expected = json.loads(open(reffile).read()) | |
269 | ||
270 | test_func = functools.partial( | |
271 | topotest.router_json_cmp, router, "show ip pim vrf red neighbor json", expected | |
272 | ) | |
273 | _, res = topotest.run_and_expect(test_func, None, count=30, wait=2) | |
274 | assertmsg = "PIM router R1 did not converge for VRF red" | |
275 | assert res is None, assertmsg | |
276 | ||
277 | logger.info("Checking PIM convergence on router r1 for VRF blue") | |
278 | ||
279 | router = tgen.gears["r1"] | |
280 | reffile = os.path.join(CWD, "r1/pim_blue_neighbor.json") | |
281 | expected = json.loads(open(reffile).read()) | |
282 | ||
283 | test_func = functools.partial( | |
284 | topotest.router_json_cmp, router, "show ip pim vrf blue neighbor json", expected | |
285 | ) | |
286 | _, res = topotest.run_and_expect(test_func, None, count=30, wait=2) | |
287 | assertmsg = "PIM router R1 did not converge for VRF blue" | |
288 | assert res is None, assertmsg | |
289 | ||
290 | ||
291 | def test_vrf_pimreg_interfaces(): | |
292 | "Adding PIM RP in VRF information and verify pimreg interfaces" | |
293 | tgen = get_topogen() | |
294 | ||
295 | r1 = tgen.gears["r1"] | |
296 | r1.vtysh_cmd("conf\ninterface blue\nip pim") | |
297 | r1.vtysh_cmd("conf\nvrf blue\nip pim rp 192.168.0.11 239.100.0.1/32\nexit-vrf") | |
298 | ||
299 | # Check pimreg11 interface on R1, VRF blue | |
300 | reffile = os.path.join(CWD, "r1/pim_blue_pimreg11.json") | |
301 | expected = json.loads(open(reffile).read()) | |
302 | test_func = functools.partial( | |
a53c08bc CH |
303 | topotest.router_json_cmp, |
304 | r1, | |
305 | "show ip pim vrf blue inter pimreg11 json", | |
306 | expected, | |
51118489 MW |
307 | ) |
308 | _, res = topotest.run_and_expect(test_func, None, count=5, wait=2) | |
309 | assertmsg = "PIM router R1, VRF blue (table 11) pimreg11 interface missing or incorrect status" | |
310 | assert res is None, assertmsg | |
311 | ||
312 | r1.vtysh_cmd("conf\ninterface red\nip pim") | |
313 | r1.vtysh_cmd("conf\nvrf red\nip pim rp 192.168.0.12 239.100.0.1/32\nexit-vrf") | |
314 | ||
315 | # Check pimreg12 interface on R1, VRF red | |
316 | reffile = os.path.join(CWD, "r1/pim_red_pimreg12.json") | |
317 | expected = json.loads(open(reffile).read()) | |
318 | test_func = functools.partial( | |
a53c08bc CH |
319 | topotest.router_json_cmp, |
320 | r1, | |
321 | "show ip pim vrf red inter pimreg12 json", | |
322 | expected, | |
51118489 MW |
323 | ) |
324 | _, res = topotest.run_and_expect(test_func, None, count=5, wait=2) | |
325 | assertmsg = "PIM router R1, VRF red (table 12) pimreg12 interface missing or incorrect status" | |
326 | assert res is None, assertmsg | |
327 | ||
328 | ||
329 | ################################## | |
330 | ### Test PIM / IGMP with VRF | |
331 | ################################## | |
332 | ||
a53c08bc | 333 | |
51118489 MW |
334 | def check_mcast_entry(mcastaddr, pimrp, receiver, sender, vrf): |
335 | "Helper function to check RP" | |
336 | tgen = get_topogen() | |
337 | ||
a53c08bc | 338 | logger.info("Testing PIM for VRF {} entry using {}".format(vrf, mcastaddr)) |
51118489 | 339 | |
1973df1d CH |
340 | with McastTesterHelper(tgen) as helper: |
341 | helper.run(sender, ["--send=0.7", mcastaddr, str(sender) + "-eth0"]) | |
342 | helper.run(receiver, [mcastaddr, str(receiver) + "-eth0"]) | |
51118489 | 343 | |
1973df1d | 344 | logger.info("mcast join and source for {} started".format(mcastaddr)) |
51118489 | 345 | |
1973df1d CH |
346 | router = tgen.gears["r1"] |
347 | reffile = os.path.join(CWD, "r1/pim_{}_join.json".format(vrf)) | |
348 | expected = json.loads(open(reffile).read()) | |
51118489 | 349 | |
1973df1d CH |
350 | logger.info("verifying pim join on r1 for {} on VRF {}".format(mcastaddr, vrf)) |
351 | test_func = functools.partial( | |
a53c08bc CH |
352 | topotest.router_json_cmp, |
353 | router, | |
354 | "show ip pim vrf {} join json".format(vrf), | |
355 | expected, | |
1973df1d CH |
356 | ) |
357 | _, res = topotest.run_and_expect(test_func, None, count=10, wait=2) | |
358 | assertmsg = "PIM router r1 did not show join status on VRF {}".format(vrf) | |
359 | assert res is None, assertmsg | |
51118489 | 360 | |
1973df1d CH |
361 | logger.info("verifying pim join on PIM RP {} for {}".format(pimrp, mcastaddr)) |
362 | router = tgen.gears[pimrp] | |
363 | reffile = os.path.join(CWD, "{}/pim_{}_join.json".format(pimrp, vrf)) | |
364 | expected = json.loads(open(reffile).read()) | |
51118489 | 365 | |
1973df1d CH |
366 | test_func = functools.partial( |
367 | topotest.router_json_cmp, router, "show ip pim join json", expected | |
368 | ) | |
369 | _, res = topotest.run_and_expect(test_func, None, count=10, wait=2) | |
a53c08bc CH |
370 | assertmsg = ( |
371 | "PIM router {} did not get selected as the PIM RP for VRF {}".format( | |
372 | pimrp, vrf | |
373 | ) | |
374 | ) | |
1973df1d | 375 | assert res is None, assertmsg |
51118489 MW |
376 | |
377 | ||
378 | def test_mcast_vrf_blue(): | |
379 | "Test vrf blue with 239.100.0.1" | |
380 | tgen = get_topogen() | |
381 | ||
382 | # Skip if previous fatal error condition is raised | |
383 | if tgen.routers_have_failure(): | |
384 | pytest.skip(tgen.errors) | |
385 | ||
a53c08bc | 386 | check_mcast_entry("239.100.0.1", "r11", "h1", "h2", "blue") |
51118489 MW |
387 | |
388 | ||
389 | def test_mcast_vrf_red(): | |
390 | "Test vrf red with 239.100.0.1" | |
391 | tgen = get_topogen() | |
392 | ||
393 | # Skip if previous fatal error condition is raised | |
394 | if tgen.routers_have_failure(): | |
395 | pytest.skip(tgen.errors) | |
396 | ||
a53c08bc | 397 | check_mcast_entry("239.100.0.1", "r12", "h3", "h4", "red") |
51118489 MW |
398 | |
399 | ||
400 | if __name__ == "__main__": | |
401 | args = ["-s"] + sys.argv[1:] | |
402 | sys.exit(pytest.main(args)) |