]> git.proxmox.com Git - mirror_frr.git/blame - tests/topotests/pim_igmp_vrf/test_pim_vrf.py
tests: Add IGMP/PIM VRF test
[mirror_frr.git] / tests / topotests / pim_igmp_vrf / test_pim_vrf.py
CommitLineData
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"""
26test_pim_vrf.py: Test PIM with VRFs.
27"""
28
29# Tests PIM with VRF
30#
31# R1 is split into 2 VRF: Blue and Red, the others are normal
32# routers and Hosts
33# There are 2 similar topologies with overlapping IPs in each
34# section.
35#
36# Test steps:
37# - setup_module()
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
42# R1, R11 and R12.
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
46# for vrf red.
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)
61# shutdown topology
62#
63
64TOPOLOGY = """
65 +----------+
66 | Host H2 |
67 | Source |
68 +----------+
69 .2 |
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+---------+ | | +---------+
75 =| = = R1 = = |=
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+---------+ +------------+ | +---------+
81 .4 |
82 +----------+
83 | Host H4 |
84 | Source |
85 +----------+
86"""
87
88import json
89import functools
90import os
91import sys
92import pytest
93import re
94import time
95from time import sleep
96import socket
97
98# Save the Current Working Directory to find configuration files.
99CWD = os.path.dirname(os.path.realpath(__file__))
100sys.path.append(os.path.join(CWD, "../"))
101
102# pylint: disable=C0413
103# Import topogen and topotest helpers
104from lib import topotest
105from lib.topogen import Topogen, TopoRouter, get_topogen
106from lib.topolog import logger
107from lib.topotest import iproute2_is_vrf_capable
108from lib.common_config import (
109 required_linux_kernel_version)
110
111# Required to instantiate the topology builder class.
112from mininet.topo import Topo
113
114pytestmark = [pytest.mark.pimd]
115
116
117#
118# Test global variables:
119# They are used to handle communicating with external application.
120#
121APP_SOCK_PATH = '/tmp/topotests/apps.sock'
122HELPER_APP_PATH = os.path.join(CWD, "../lib/mcast-tester.py")
123app_listener = None
124app_clients = {}
125
126def listen_to_applications():
127 "Start listening socket to connect with applications."
128 # Remove old socket.
129 try:
130 os.unlink(APP_SOCK_PATH)
131 except OSError:
132 pass
133
134 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM, 0)
135 sock.bind(APP_SOCK_PATH)
136 sock.listen(10)
137 global app_listener
138 app_listener = sock
139
140def 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] = {
145 'fd': conn[0],
146 'address': conn[1]
147 }
148
149def close_applications():
150 "Signal applications to stop and close all sockets."
151 global app_listener, app_clients
152
153 if app_listener:
154 # Close listening socket.
155 app_listener.close()
156
157 # Remove old socket.
158 try:
159 os.unlink(APP_SOCK_PATH)
160 except OSError:
161 pass
162
163 # Close all host connections.
164 for host in ["h1", "h2"]:
165 if app_clients.get(host) is None:
166 continue
167 app_clients[host]["fd"].close()
168
169 # Reset listener and clients data struct
170 app_listener = None
171 app_clients = {}
172
173
174class PIMVRFTopo(Topo):
175 "PIM VRF Test Topology"
176
177 def build(self):
178 tgen = get_topogen(self)
179
180 # Create the hosts
181 for hostNum in range(1,5):
182 tgen.add_router("h{}".format(hostNum))
183
184 # Create the main router
185 tgen.add_router("r1")
186
187 # Create the PIM RP routers
188 for rtrNum in range(11, 13):
189 tgen.add_router("r{}".format(rtrNum))
190
191 # Setup Switches and connections
192 for swNum in range(1, 5):
193 tgen.add_switch("sw{}".format(swNum))
194
195 ################
196 # 1st set of connections to routers for VRF red
197 ################
198
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"])
202
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"])
207
208 ################
209 # 2nd set of connections to routers for vrf blue
210 ################
211
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"])
215
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"])
220
221#####################################################
222#
223# Tests starting
224#
225#####################################################
226
227def setup_module(module):
228 logger.info("PIM IGMP VRF Topology: \n {}".format(TOPOLOGY))
229
230 tgen = Topogen(PIMVRFTopo, module.__name__)
231 tgen.start_topology()
232
233 vrf_setup_cmds = [
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",
242 ]
243
244 # Starting Routers
245 router_list = tgen.routers()
246
247 # Create VRF on r2 first and add it's interfaces
248 for cmd in vrf_setup_cmds:
249 tgen.net["r1"].cmd(cmd)
250
251 for rname, router in router_list.items():
252 logger.info("Loading router %s" % rname)
253 router.load_config(
254 TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
255 )
256 if rname[0] != 'h':
257 # Only load ospf on routers, not on end hosts
258 router.load_config(
259 TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname))
260 )
261 router.load_config(
262 TopoRouter.RD_PIM, os.path.join(CWD, "{}/pimd.conf".format(rname))
263 )
264 tgen.start_router()
265
266
267def teardown_module(module):
268 tgen = get_topogen()
269 tgen.stop_topology()
270 close_applications()
271
272
273def test_ospf_convergence():
274 "Test for OSPFv2 convergence"
275 tgen = get_topogen()
276
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")
281
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")
285
286 # Skip if previous fatal error condition is raised
287 if tgen.routers_have_failure():
288 pytest.skip(tgen.errors)
289
290 logger.info("Checking OSPFv2 convergence on router r1 for VRF blue")
291
292 router = tgen.gears["r1"]
293 reffile = os.path.join(CWD, "r1/ospf_blue_neighbor.json")
294 expected = json.loads(open(reffile).read())
295
296 test_func = functools.partial(
297 topotest.router_json_cmp, router, "show ip ospf vrf blue neighbor json", expected
298 )
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
302
303 logger.info("Checking OSPFv2 convergence on router r1 for VRF red")
304
305 router = tgen.gears["r1"]
306 reffile = os.path.join(CWD, "r1/ospf_red_neighbor.json")
307 expected = json.loads(open(reffile).read())
308
309 test_func = functools.partial(
310 topotest.router_json_cmp, router, "show ip ospf vrf red neighbor json", expected
311 )
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
315
316
317def test_pim_convergence():
318 "Test for PIM convergence"
319 tgen = get_topogen()
320
321 # Skip if previous fatal error condition is raised
322 if tgen.routers_have_failure():
323 pytest.skip(tgen.errors)
324
325 logger.info("Checking PIM convergence on router r1 for VRF red")
326
327 router = tgen.gears["r1"]
328 reffile = os.path.join(CWD, "r1/pim_red_neighbor.json")
329 expected = json.loads(open(reffile).read())
330
331 test_func = functools.partial(
332 topotest.router_json_cmp, router, "show ip pim vrf red neighbor json", expected
333 )
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
337
338 logger.info("Checking PIM convergence on router r1 for VRF blue")
339
340 router = tgen.gears["r1"]
341 reffile = os.path.join(CWD, "r1/pim_blue_neighbor.json")
342 expected = json.loads(open(reffile).read())
343
344 test_func = functools.partial(
345 topotest.router_json_cmp, router, "show ip pim vrf blue neighbor json", expected
346 )
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
350
351
352def test_vrf_pimreg_interfaces():
353 "Adding PIM RP in VRF information and verify pimreg interfaces"
354 tgen = get_topogen()
355
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")
359
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
365 )
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
369
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")
372
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
378 )
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
382
383
384##################################
385### Test PIM / IGMP with VRF
386##################################
387
388def check_mcast_entry(mcastaddr, pimrp, receiver, sender, vrf):
389 "Helper function to check RP"
390 tgen = get_topogen()
391
392 logger.info("Testing PIM for VRF {} entry using {}".format(vrf, mcastaddr));
393
394 # Start applications socket.
395 listen_to_applications()
396
397 tgen.gears[sender].run("{} --send='0.7' '{}' '{}' '{}' &".format(
398 HELPER_APP_PATH, APP_SOCK_PATH, mcastaddr, '{}-eth0'.format(sender)))
399 accept_host(sender)
400
401 tgen.gears[receiver].run("{} '{}' '{}' '{}' &".format(
402 HELPER_APP_PATH, APP_SOCK_PATH, mcastaddr, '{}-eth0'.format(receiver)))
403 accept_host(receiver)
404
405 logger.info("mcast join and source for {} started".format(mcastaddr))
406
407 # tgen.mininet_cli()
408
409 router = tgen.gears["r1"]
410 reffile = os.path.join(CWD, "r1/pim_{}_join.json".format(vrf))
411 expected = json.loads(open(reffile).read())
412
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),
416 expected
417 )
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
421
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())
426
427 test_func = functools.partial(
428 topotest.router_json_cmp, router, "show ip pim join json", expected
429 )
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
433
434 close_applications()
435 return
436
437
438def test_mcast_vrf_blue():
439 "Test vrf blue with 239.100.0.1"
440 tgen = get_topogen()
441
442 # Skip if previous fatal error condition is raised
443 if tgen.routers_have_failure():
444 pytest.skip(tgen.errors)
445
446 check_mcast_entry('239.100.0.1', 'r11', 'h1', 'h2', 'blue')
447
448
449def test_mcast_vrf_red():
450 "Test vrf red with 239.100.0.1"
451 tgen = get_topogen()
452
453 # Skip if previous fatal error condition is raised
454 if tgen.routers_have_failure():
455 pytest.skip(tgen.errors)
456
457 check_mcast_entry('239.100.0.1', 'r12', 'h3', 'h4', 'red')
458
459
460if __name__ == "__main__":
461 args = ["-s"] + sys.argv[1:]
462 sys.exit(pytest.main(args))