]>
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 | ||
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 | ||
64 | TOPOLOGY = """ | |
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 | ||
88 | import json | |
89 | import functools | |
90 | import os | |
91 | import sys | |
92 | import pytest | |
93 | import re | |
94 | import time | |
95 | from time import sleep | |
96 | import socket | |
97 | ||
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, "../")) | |
101 | ||
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) | |
110 | ||
111 | # Required to instantiate the topology builder class. | |
112 | from mininet.topo import Topo | |
113 | ||
114 | pytestmark = [pytest.mark.pimd] | |
115 | ||
116 | ||
117 | # | |
118 | # Test global variables: | |
119 | # They are used to handle communicating with external application. | |
120 | # | |
121 | APP_SOCK_PATH = '/tmp/topotests/apps.sock' | |
122 | HELPER_APP_PATH = os.path.join(CWD, "../lib/mcast-tester.py") | |
123 | app_listener = None | |
124 | app_clients = {} | |
125 | ||
126 | def 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 | ||
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] = { | |
145 | 'fd': conn[0], | |
146 | 'address': conn[1] | |
147 | } | |
148 | ||
149 | def 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 | ||
174 | class 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 | ||
227 | def 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 | ||
267 | def teardown_module(module): | |
268 | tgen = get_topogen() | |
269 | tgen.stop_topology() | |
270 | close_applications() | |
271 | ||
272 | ||
273 | def 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 | ||
317 | def 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 | ||
352 | def 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 | ||
388 | def 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 | ||
438 | def 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 | ||
449 | def 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 | ||
460 | if __name__ == "__main__": | |
461 | args = ["-s"] + sys.argv[1:] | |
462 | sys.exit(pytest.main(args)) |