5 # Copyright (c) 2019 by
6 # Cumulus Networks, Inc
9 # Permission to use, copy, modify, and/or distribute this software
10 # for any purpose with or without fee is hereby granted, provided
11 # that the above copyright notice and this permission notice appear
14 # THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES
15 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR
17 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
18 # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
19 # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
20 # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
25 test_zebra_rib.py: Test some basic zebra <-> kernel interactions
31 from functools
import partial
36 # Save the Current Working Directory to find configuration files.
37 CWD
= os
.path
.dirname(os
.path
.realpath(__file__
))
38 sys
.path
.append(os
.path
.join(CWD
, "../"))
40 # pylint: disable=C0413
41 # Import topogen and topotest helpers
42 from lib
import topotest
43 from lib
.topogen
import Topogen
, TopoRouter
, get_topogen
44 from lib
.topolog
import logger
45 from time
import sleep
48 pytestmark
= [pytest
.mark
.sharpd
]
49 krel
= platform
.release()
52 def config_macvlan(tgen
, r_str
, device
, macvlan
):
53 "Creates specified macvlan interace on physical device"
55 if topotest
.version_cmp(krel
, "5.1") < 0:
58 router
= tgen
.gears
[r_str
]
60 "ip link add {} link {} type macvlan mode bridge".format(macvlan
, device
)
62 router
.run("ip link set {} up".format(macvlan
))
65 def setup_module(mod
):
66 "Sets up the pytest environment"
67 topodef
= {"s1": ("r1", "r1", "r1", "r1", "r1", "r1", "r1", "r1")}
68 tgen
= Topogen(topodef
, mod
.__name
__)
71 router_list
= tgen
.routers()
72 for rname
, router
in router_list
.items():
74 TopoRouter
.RD_ZEBRA
, os
.path
.join(CWD
, "{}/zebra.conf".format(rname
))
77 TopoRouter
.RD_SHARP
, os
.path
.join(CWD
, "{}/sharpd.conf".format(rname
))
80 # Macvlan interface for protodown func test */
81 config_macvlan(tgen
, "r1", "r1-eth0", "r1-eth0-macvlan")
82 # Initialize all routers.
86 def teardown_module(mod
):
87 "Teardown the pytest environment"
92 def test_zebra_kernel_admin_distance():
93 "Test some basic kernel routes added that should be accepted"
94 logger
.info("Test some basic kernel routes that should be accepted")
96 if tgen
.routers_have_failure():
97 pytest
.skip("skipped because of router(s) failure")
101 # Route with 255/8192 metric
106 def makekmetric(dist
, metric
):
107 return (dist
<< 24) + metric
110 "ip route add 4.5.1.0/24 via 192.168.210.2 dev r1-eth0 metric "
111 + str(makekmetric(255, 8192))
113 # Route with 1/1 metric
115 "ip route add 4.5.2.0/24 via 192.168.211.2 dev r1-eth1 metric "
116 + str(makekmetric(1, 1))
118 # Route with 10/1 metric
120 "ip route add 4.5.3.0/24 via 192.168.212.2 dev r1-eth2 metric "
121 + str(makekmetric(10, 1))
123 # Same route with a 160/1 metric
125 "ip route add 4.5.3.0/24 via 192.168.213.2 dev r1-eth3 metric "
126 + str(makekmetric(160, 1))
129 # Currently I believe we have a bug here with the same route and different
130 # metric. That needs to be properly resolved. Making a note for
131 # coming back around later and fixing this.
133 for i
in range(1, 2):
134 json_file
= "{}/r1/v4_route_{}.json".format(CWD
, i
)
135 expected
= json
.loads(open(json_file
).read())
138 topotest
.router_json_cmp
,
140 "show ip route 4.5.{}.0 json".format(i
),
143 _
, result
= topotest
.run_and_expect(test_func
, None, count
=20, wait
=0.5)
144 assertmsg
= '"r1" JSON output mismatches'
145 assert result
is None, assertmsg
149 def test_zebra_kernel_override():
150 "Test that a FRR route with a lower admin distance takes over"
151 logger
.info("Test kernel override with a better admin distance")
153 if tgen
.routers_have_failure():
154 pytest
.skip("skipped because of previous test failure")
156 r1
= tgen
.gears
["r1"]
157 r1
.vtysh_cmd("conf\nip route 4.5.1.0/24 192.168.216.3")
158 json_file
= "{}/r1/v4_route_1_static_override.json".format(CWD
)
159 expected
= json
.loads(open(json_file
).read())
162 topotest
.router_json_cmp
, r1
, "show ip route 4.5.1.0 json", expected
164 _
, result
= topotest
.run_and_expect(test_func
, None, count
=20, wait
=0.5)
165 assert result
is None, '"r1" JSON output mismatches'
168 "Test that the removal of the static route allows the kernel to take back over"
170 r1
.vtysh_cmd("conf\nno ip route 4.5.1.0/24 192.168.216.3")
171 json_file
= "{}/r1/v4_route_1.json".format(CWD
)
172 expected
= json
.loads(open(json_file
).read())
175 topotest
.router_json_cmp
, r1
, "show ip route 4.5.1.0 json", expected
177 _
, result
= topotest
.run_and_expect(test_func
, None, count
=20, wait
=0.5)
178 assert result
is None, '"r1" JSON output mismatches'
181 def test_route_map_usage():
182 "Test that FRR only reruns over routes associated with the routemap"
183 logger
.info("Test that FRR runs on selected re's on route-map changes")
185 if tgen
.routers_have_failure():
186 pytest
.skip("Skipped because of previous test failure")
188 thisDir
= os
.path
.dirname(os
.path
.realpath(__file__
))
190 r1
= tgen
.gears
["r1"]
191 # set the delay timer to 1 to improve test coverage (HA)
192 r1
.vtysh_cmd("conf\nzebra route-map delay-timer 1")
193 r1
.vtysh_cmd("conf\nroute-map static permit 10\nset src 192.168.215.1")
194 r1
.vtysh_cmd("conf\naccess-list 5 seq 5 permit 10.0.0.44/32")
195 r1
.vtysh_cmd("conf\naccess-list 10 seq 5 permit 10.0.1.0/24")
197 "conf\nroute-map sharp permit 10\nmatch ip address 10\nset src 192.168.214.1"
199 r1
.vtysh_cmd("conf\nroute-map sharp permit 20\nset src 192.168.213.1")
200 r1
.vtysh_cmd("conf\nip protocol static route-map static")
201 r1
.vtysh_cmd("conf\nip protocol sharp route-map sharp")
203 r1
.vtysh_cmd("conf\nip route 10.100.100.100/32 192.168.216.3")
204 r1
.vtysh_cmd("conf\nip route 10.100.100.101/32 10.0.0.44")
205 r1
.vtysh_cmd("sharp install route 10.0.0.0 nexthop 192.168.216.3 500")
207 def check_initial_routes_installed(router
):
208 output
= json
.loads(router
.vtysh_cmd("show ip route summ json"))
210 "routes": [{"type": "static", "rib": 2}, {"type": "sharp", "rib": 500}]
212 return topotest
.json_cmp(output
, expected
)
214 test_func
= partial(check_initial_routes_installed
, r1
)
215 success
, result
= topotest
.run_and_expect(test_func
, None, count
=40, wait
=1)
217 static_rmapfile
= "%s/r1/static_rmap.ref" % (thisDir
)
218 expected
= open(static_rmapfile
).read().rstrip()
219 expected
= ("\n".join(expected
.splitlines()) + "\n").rstrip()
221 "Does the show route-map static command run the correct number of times"
224 def check_static_map_correct_runs():
225 actual
= r1
.vtysh_cmd("show route-map static")
226 actual
= ("\n".join(actual
.splitlines()) + "\n").rstrip()
227 return topotest
.get_textdiff(
230 title1
="Actual Route-map output",
231 title2
="Expected Route-map output",
234 ok
, result
= topotest
.run_and_expect(
235 check_static_map_correct_runs
, "", count
=5, wait
=1
239 sharp_rmapfile
= "%s/r1/sharp_rmap.ref" % (thisDir
)
240 expected
= open(sharp_rmapfile
).read().rstrip()
241 expected
= ("\n".join(expected
.splitlines()) + "\n").rstrip()
242 logger
.info("Does the show route-map sharp command run the correct number of times")
244 def check_sharp_map_correct_runs():
245 actual
= r1
.vtysh_cmd("show route-map sharp")
246 actual
= ("\n".join(actual
.splitlines()) + "\n").rstrip()
247 return topotest
.get_textdiff(
250 title1
="Actual Route-map output",
251 title2
="Expected Route-map output",
254 ok
, result
= topotest
.run_and_expect(
255 check_sharp_map_correct_runs
, "", count
=5, wait
=1
260 "Add a extension to the static route-map to see the static route go away"
261 " and test that the routes installed are correct"
264 r1
.vtysh_cmd("conf\nroute-map sharp deny 5\nmatch ip address 5")
265 # we are only checking the kernel here as that this will give us the implied
266 # testing of both the route-map and staticd withdrawing the route
267 # let's spot check that the routes were installed correctly
269 sharp_ipfile
= "%s/r1/iproute.ref" % (thisDir
)
270 expected
= open(sharp_ipfile
).read().rstrip()
271 expected
= ("\n".join(expected
.splitlines()) + "\n").rstrip()
273 def check_routes_installed():
274 actual
= r1
.run("ip route show")
275 actual
= ("\n".join(actual
.splitlines()) + "\n").rstrip()
276 actual
= re
.sub(r
" nhid [0-9][0-9]", "", actual
)
277 actual
= re
.sub(r
" proto sharp", " proto XXXX", actual
)
278 actual
= re
.sub(r
" proto static", " proto XXXX", actual
)
279 actual
= re
.sub(r
" proto 194", " proto XXXX", actual
)
280 actual
= re
.sub(r
" proto 196", " proto XXXX", actual
)
281 actual
= re
.sub(r
" proto kernel", " proto XXXX", actual
)
282 actual
= re
.sub(r
" proto 2", " proto XXXX", actual
)
283 # Some platforms have double spaces? Why??????
284 actual
= re
.sub(r
" proto XXXX ", " proto XXXX ", actual
)
285 actual
= re
.sub(r
" metric", " metric", actual
)
286 actual
= re
.sub(r
" link ", " link ", actual
)
287 return topotest
.get_textdiff(
290 title1
="Actual ip route show",
291 title2
="Expected ip route show",
294 ok
, result
= topotest
.run_and_expect(check_routes_installed
, "", count
=5, wait
=1)
298 def test_protodown():
299 "Run protodown basic functionality test and report results."
303 if topotest
.version_cmp(krel
, "5.1") < 0:
304 tgen
.errors
= "kernel 5.1 needed for protodown tests"
305 pytest
.skip(tgen
.errors
)
307 r1
= tgen
.gears
["r1"]
309 # Set interface protodown on
310 r1
.vtysh_cmd("sharp interface r1-eth0-macvlan protodown")
312 # Timeout to wait for dplane to handle it
315 output
= r1
.vtysh_cmd("show interface r1-eth0-macvlan")
316 if re
.search(r
"protodown reasons:.*sharp", output
):
321 assert pdown
is True, "Interface r1-eth0-macvlan not set protodown"
323 # Set interface protodown off
324 r1
.vtysh_cmd("no sharp interface r1-eth0-macvlan protodown")
326 # Timeout to wait for dplane to handle it
329 output
= r1
.vtysh_cmd("show interface r1-eth0-macvlan")
330 if not re
.search(r
"protodown reasons:.*sharp", output
):
335 assert pdown
is False, "Interface r1-eth0-macvlan not set protodown off"
338 def test_memory_leak():
339 "Run the memory leak test and report results."
341 if not tgen
.is_memleak_enabled():
342 pytest
.skip("Memory leak test/report is disabled")
344 tgen
.report_memory_leaks()
347 if __name__
== "__main__":
348 args
= ["-s"] + sys
.argv
[1:]
349 sys
.exit(pytest
.main(args
))