5 # Part of NetDEF Topology Tests
7 # Copyright (c) 2020 by
8 # Network Device Education Foundation, Inc. ("NetDEF")
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
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
26 test_pim_vrf.py: Test PIM with VRFs.
31 # R1 is split into 2 VRF: Blue and Red, the others are normal
33 # There are 2 similar topologies with overlapping IPs in each
38 # Create topology. Hosts are only using zebra/staticd,
39 # no PIM, no OSPF (using IGMPv2 for multicast)
40 # - test_ospf_convergence()
41 # Wait for OSPF convergence in each VRF. OSPF is run on
43 # - test_pim_convergence()
44 # Wait for PIM convergence in each VRF. PIM is run on
45 # R1, R11 and R12. R11 is the RP for vrf blue, R12 is RP
47 # - test_vrf_pimreg_interfaces()
48 # Adding PIM RP in VRF information and verify pimreg
49 # interfaces in VRF blue and red
50 # - test_mcast_vrf_blue()
51 # Start multicast stream for group 239.100.0.1 from Host
52 # H2 and join from Host H1 on vrf blue
53 # Verify PIM JOIN status on R1 and R11
54 # Stop multicast after verification
55 # - test_mcast_vrf_red()
56 # Start multicast stream for group 239.100.0.1 from Host
57 # H4 and join from Host H3 on vrf blue
58 # Verify PIM JOIN status on R1 and R12
59 # Stop multicast after verification
60 # - teardown_module(module)
70 +---------+ +------------+ | +---------+
71 | Host H1 | 192.168.100.0/24 | | .1 | .11 | Host H2 |
72 | receive |------------------| VRF Blue |---------+--------| PIM RP |
73 |IGMP JOIN| .10 .1 | | 192.168.101.0/24 | |
74 +---------+ | | +---------+
76 +---------+ | | +---------+
77 | Host H3 | 192.168.100.0/24 | | 192.168.101.0/24 | Host H4 |
78 | receive |------------------| VRF Red |---------+--------| PIM RP |
79 |IGMP JOIN| .20 .1 | | .1 | .12 | |
80 +---------+ +------------+ | +---------+
95 from time
import sleep
98 # Save the Current Working Directory to find configuration files.
99 CWD
= os
.path
.dirname(os
.path
.realpath(__file__
))
100 sys
.path
.append(os
.path
.join(CWD
, "../"))
102 # pylint: disable=C0413
103 # Import topogen and topotest helpers
104 from lib
import topotest
105 from lib
.topogen
import Topogen
, TopoRouter
, get_topogen
106 from lib
.topolog
import logger
107 from lib
.topotest
import iproute2_is_vrf_capable
108 from lib
.common_config
import (
109 required_linux_kernel_version
)
111 # Required to instantiate the topology builder class.
112 from mininet
.topo
import Topo
114 pytestmark
= [pytest
.mark
.ospfd
, pytest
.mark
.pimd
]
118 # Test global variables:
119 # They are used to handle communicating with external application.
121 APP_SOCK_PATH
= '/tmp/topotests/apps.sock'
122 HELPER_APP_PATH
= os
.path
.join(CWD
, "../lib/mcast-tester.py")
126 def listen_to_applications():
127 "Start listening socket to connect with applications."
130 os
.unlink(APP_SOCK_PATH
)
134 sock
= socket
.socket(socket
.AF_UNIX
, socket
.SOCK_STREAM
, 0)
135 sock
.bind(APP_SOCK_PATH
)
140 def accept_host(host
):
141 "Accept connection from application running in hosts."
142 global app_listener
, app_clients
143 conn
= app_listener
.accept()
144 app_clients
[host
] = {
149 def close_applications():
150 "Signal applications to stop and close all sockets."
151 global app_listener
, app_clients
154 # Close listening socket.
159 os
.unlink(APP_SOCK_PATH
)
163 # Close all host connections.
164 for host
in ["h1", "h2"]:
165 if app_clients
.get(host
) is None:
167 app_clients
[host
]["fd"].close()
169 # Reset listener and clients data struct
174 class PIMVRFTopo(Topo
):
175 "PIM VRF Test Topology"
178 tgen
= get_topogen(self
)
181 for hostNum
in range(1,5):
182 tgen
.add_router("h{}".format(hostNum
))
184 # Create the main router
185 tgen
.add_router("r1")
187 # Create the PIM RP routers
188 for rtrNum
in range(11, 13):
189 tgen
.add_router("r{}".format(rtrNum
))
191 # Setup Switches and connections
192 for swNum
in range(1, 5):
193 tgen
.add_switch("sw{}".format(swNum
))
196 # 1st set of connections to routers for VRF red
199 # Add connections H1 to R1 switch sw1
200 tgen
.gears
["h1"].add_link(tgen
.gears
["sw1"])
201 tgen
.gears
["r1"].add_link(tgen
.gears
["sw1"])
203 # Add connections R1 to R1x switch sw2
204 tgen
.gears
["r1"].add_link(tgen
.gears
["sw2"])
205 tgen
.gears
["h2"].add_link(tgen
.gears
["sw2"])
206 tgen
.gears
["r11"].add_link(tgen
.gears
["sw2"])
209 # 2nd set of connections to routers for vrf blue
212 # Add connections H1 to R1 switch sw1
213 tgen
.gears
["h3"].add_link(tgen
.gears
["sw3"])
214 tgen
.gears
["r1"].add_link(tgen
.gears
["sw3"])
216 # Add connections R1 to R1x switch sw2
217 tgen
.gears
["r1"].add_link(tgen
.gears
["sw4"])
218 tgen
.gears
["h4"].add_link(tgen
.gears
["sw4"])
219 tgen
.gears
["r12"].add_link(tgen
.gears
["sw4"])
221 #####################################################
225 #####################################################
227 def setup_module(module
):
228 logger
.info("PIM IGMP VRF Topology: \n {}".format(TOPOLOGY
))
230 tgen
= Topogen(PIMVRFTopo
, module
.__name
__)
231 tgen
.start_topology()
234 "ip link add name blue type vrf table 11",
235 "ip link add name red type vrf table 12",
236 "ip link set dev blue up",
237 "ip link set dev red up",
238 "ip link set dev r1-eth0 vrf blue up",
239 "ip link set dev r1-eth1 vrf blue up",
240 "ip link set dev r1-eth2 vrf red up",
241 "ip link set dev r1-eth3 vrf red up",
245 router_list
= tgen
.routers()
247 # Create VRF on r2 first and add it's interfaces
248 for cmd
in vrf_setup_cmds
:
249 tgen
.net
["r1"].cmd(cmd
)
251 for rname
, router
in router_list
.items():
252 logger
.info("Loading router %s" % rname
)
254 TopoRouter
.RD_ZEBRA
, os
.path
.join(CWD
, "{}/zebra.conf".format(rname
))
257 # Only load ospf on routers, not on end hosts
259 TopoRouter
.RD_OSPF
, os
.path
.join(CWD
, "{}/ospfd.conf".format(rname
))
262 TopoRouter
.RD_PIM
, os
.path
.join(CWD
, "{}/pimd.conf".format(rname
))
267 def teardown_module(module
):
273 def test_ospf_convergence():
274 "Test for OSPFv2 convergence"
277 # Required linux kernel version for this suite to run.
278 result
= required_linux_kernel_version("4.15")
279 if result
is not True:
280 pytest
.skip("Kernel requirements are not met")
282 # iproute2 needs to support VRFs for this suite to run.
283 if not iproute2_is_vrf_capable():
284 pytest
.skip("Installed iproute2 version does not support VRFs")
286 # Skip if previous fatal error condition is raised
287 if tgen
.routers_have_failure():
288 pytest
.skip(tgen
.errors
)
290 logger
.info("Checking OSPFv2 convergence on router r1 for VRF blue")
292 router
= tgen
.gears
["r1"]
293 reffile
= os
.path
.join(CWD
, "r1/ospf_blue_neighbor.json")
294 expected
= json
.loads(open(reffile
).read())
296 test_func
= functools
.partial(
297 topotest
.router_json_cmp
, router
, "show ip ospf vrf blue neighbor json", expected
299 _
, res
= topotest
.run_and_expect(test_func
, None, count
=60, wait
=2)
300 assertmsg
= "OSPF router R1 did not converge on VRF blue"
301 assert res
is None, assertmsg
303 logger
.info("Checking OSPFv2 convergence on router r1 for VRF red")
305 router
= tgen
.gears
["r1"]
306 reffile
= os
.path
.join(CWD
, "r1/ospf_red_neighbor.json")
307 expected
= json
.loads(open(reffile
).read())
309 test_func
= functools
.partial(
310 topotest
.router_json_cmp
, router
, "show ip ospf vrf red neighbor json", expected
312 _
, res
= topotest
.run_and_expect(test_func
, None, count
=60, wait
=2)
313 assertmsg
= "OSPF router R1 did not converge on VRF red"
314 assert res
is None, assertmsg
317 def test_pim_convergence():
318 "Test for PIM convergence"
321 # Skip if previous fatal error condition is raised
322 if tgen
.routers_have_failure():
323 pytest
.skip(tgen
.errors
)
325 logger
.info("Checking PIM convergence on router r1 for VRF red")
327 router
= tgen
.gears
["r1"]
328 reffile
= os
.path
.join(CWD
, "r1/pim_red_neighbor.json")
329 expected
= json
.loads(open(reffile
).read())
331 test_func
= functools
.partial(
332 topotest
.router_json_cmp
, router
, "show ip pim vrf red neighbor json", expected
334 _
, res
= topotest
.run_and_expect(test_func
, None, count
=30, wait
=2)
335 assertmsg
= "PIM router R1 did not converge for VRF red"
336 assert res
is None, assertmsg
338 logger
.info("Checking PIM convergence on router r1 for VRF blue")
340 router
= tgen
.gears
["r1"]
341 reffile
= os
.path
.join(CWD
, "r1/pim_blue_neighbor.json")
342 expected
= json
.loads(open(reffile
).read())
344 test_func
= functools
.partial(
345 topotest
.router_json_cmp
, router
, "show ip pim vrf blue neighbor json", expected
347 _
, res
= topotest
.run_and_expect(test_func
, None, count
=30, wait
=2)
348 assertmsg
= "PIM router R1 did not converge for VRF blue"
349 assert res
is None, assertmsg
352 def test_vrf_pimreg_interfaces():
353 "Adding PIM RP in VRF information and verify pimreg interfaces"
356 r1
= tgen
.gears
["r1"]
357 r1
.vtysh_cmd("conf\ninterface blue\nip pim")
358 r1
.vtysh_cmd("conf\nvrf blue\nip pim rp 192.168.0.11 239.100.0.1/32\nexit-vrf")
360 # Check pimreg11 interface on R1, VRF blue
361 reffile
= os
.path
.join(CWD
, "r1/pim_blue_pimreg11.json")
362 expected
= json
.loads(open(reffile
).read())
363 test_func
= functools
.partial(
364 topotest
.router_json_cmp
, r1
, "show ip pim vrf blue inter pimreg11 json", expected
366 _
, res
= topotest
.run_and_expect(test_func
, None, count
=5, wait
=2)
367 assertmsg
= "PIM router R1, VRF blue (table 11) pimreg11 interface missing or incorrect status"
368 assert res
is None, assertmsg
370 r1
.vtysh_cmd("conf\ninterface red\nip pim")
371 r1
.vtysh_cmd("conf\nvrf red\nip pim rp 192.168.0.12 239.100.0.1/32\nexit-vrf")
373 # Check pimreg12 interface on R1, VRF red
374 reffile
= os
.path
.join(CWD
, "r1/pim_red_pimreg12.json")
375 expected
= json
.loads(open(reffile
).read())
376 test_func
= functools
.partial(
377 topotest
.router_json_cmp
, r1
, "show ip pim vrf red inter pimreg12 json", expected
379 _
, res
= topotest
.run_and_expect(test_func
, None, count
=5, wait
=2)
380 assertmsg
= "PIM router R1, VRF red (table 12) pimreg12 interface missing or incorrect status"
381 assert res
is None, assertmsg
384 ##################################
385 ### Test PIM / IGMP with VRF
386 ##################################
388 def check_mcast_entry(mcastaddr
, pimrp
, receiver
, sender
, vrf
):
389 "Helper function to check RP"
392 logger
.info("Testing PIM for VRF {} entry using {}".format(vrf
, mcastaddr
));
394 # Start applications socket.
395 listen_to_applications()
397 tgen
.gears
[sender
].run("{} --send='0.7' '{}' '{}' '{}' &".format(
398 HELPER_APP_PATH
, APP_SOCK_PATH
, mcastaddr
, '{}-eth0'.format(sender
)))
401 tgen
.gears
[receiver
].run("{} '{}' '{}' '{}' &".format(
402 HELPER_APP_PATH
, APP_SOCK_PATH
, mcastaddr
, '{}-eth0'.format(receiver
)))
403 accept_host(receiver
)
405 logger
.info("mcast join and source for {} started".format(mcastaddr
))
409 router
= tgen
.gears
["r1"]
410 reffile
= os
.path
.join(CWD
, "r1/pim_{}_join.json".format(vrf
))
411 expected
= json
.loads(open(reffile
).read())
413 logger
.info("verifying pim join on r1 for {} on VRF {}".format(mcastaddr
, vrf
))
414 test_func
= functools
.partial(
415 topotest
.router_json_cmp
, router
, "show ip pim vrf {} join json".format(vrf
),
418 _
, res
= topotest
.run_and_expect(test_func
, None, count
=10, wait
=2)
419 assertmsg
= "PIM router r1 did not show join status on VRF".format(vrf
)
420 assert res
is None, assertmsg
422 logger
.info("verifying pim join on PIM RP {} for {}".format(pimrp
, mcastaddr
))
423 router
= tgen
.gears
[pimrp
]
424 reffile
= os
.path
.join(CWD
, "{}/pim_{}_join.json".format(pimrp
, vrf
))
425 expected
= json
.loads(open(reffile
).read())
427 test_func
= functools
.partial(
428 topotest
.router_json_cmp
, router
, "show ip pim join json", expected
430 _
, res
= topotest
.run_and_expect(test_func
, None, count
=10, wait
=2)
431 assertmsg
= "PIM router {} did not get selected as the PIM RP for VRF {}".format(pimrp
, vrf
)
432 assert res
is None, assertmsg
438 def test_mcast_vrf_blue():
439 "Test vrf blue with 239.100.0.1"
442 # Skip if previous fatal error condition is raised
443 if tgen
.routers_have_failure():
444 pytest
.skip(tgen
.errors
)
446 check_mcast_entry('239.100.0.1', 'r11', 'h1', 'h2', 'blue')
449 def test_mcast_vrf_red():
450 "Test vrf red with 239.100.0.1"
453 # Skip if previous fatal error condition is raised
454 if tgen
.routers_have_failure():
455 pytest
.skip(tgen
.errors
)
457 check_mcast_entry('239.100.0.1', 'r12', 'h3', 'h4', 'red')
460 if __name__
== "__main__":
461 args
= ["-s"] + sys
.argv
[1:]
462 sys
.exit(pytest
.main(args
))