]>
Commit | Line | Data |
---|---|---|
ee88563a | 1 | #!/usr/bin/env python |
acddc0ed | 2 | # SPDX-License-Identifier: ISC |
ee88563a JM |
3 | |
4 | # | |
5 | # Part of NetDEF Topology Tests | |
6 | # | |
7 | # Copyright (c) 2021 Arista Networks, Inc. | |
8 | # | |
ee88563a JM |
9 | |
10 | """ | |
11 | test_bgp_peer-type_multipath-relax.py: | |
12 | ||
13 | Test the effects of the "bgp bestpath peer-type multipath-relax" command | |
14 | ||
15 | - enabling the command allows eBGP, iBGP, and confed routes to be multipath | |
16 | - the choice of best path is not affected | |
17 | - disabling the command removes iBGP/confed routes from multipath | |
1a9cb083 JM |
18 | - enabling the command does not forgive eBGP routes of the requirement |
19 | (when enabled) that next hops resolve over connected routes | |
20 | - a mixed-type multipath next hop, when published to zebra, does not | |
21 | require resolving next hops over connected routes | |
22 | - with the command enabled, an all-eBGP multipath next hop still requires | |
23 | resolving next hops over connected routes when published to zebra | |
ee88563a JM |
24 | |
25 | Topology used by the test: | |
26 | ||
27 | eBGP +------+ iBGP | |
28 | peer1 ---- | r1 | ---- peer3 | |
29 | | | | |
30 | peer2 ---- r2 ---- | | ---- peer4 | |
31 | iBGP confed +------+ eBGP | |
32 | ||
33 | r2 is present in this topology because ExaBGP does not currently support | |
34 | confederations so we use FRR to advertise the required AS_CONFED_SEQUENCE. | |
35 | ||
36 | Routes are advertised from different peers to form interesting multipaths. | |
37 | ||
38 | peer1 peer2 peer3 peer4 multipath on r1 | |
39 | ||
40 | 203.0.113.0/30 x x x all 3 | |
41 | 203.0.113.4/30 x x confed-iBGP | |
42 | 203.0.113.8/30 x x eBGP-only | |
43 | ||
44 | There is also a BGP-advertised route used only for recursively resolving | |
45 | next hops. | |
46 | """ | |
47 | ||
48 | import functools | |
49 | import json | |
50 | import os | |
51 | import pytest | |
52 | import sys | |
53 | ||
54 | CWD = os.path.dirname(os.path.realpath(__file__)) | |
55 | sys.path.append(os.path.join(CWD, "../")) | |
56 | ||
57 | # pylint: disable=C0413 | |
58 | from lib import topotest | |
59 | from lib.topogen import Topogen, TopoRouter, get_topogen | |
60 | from lib.topolog import logger | |
ee88563a | 61 | |
bf3a0a9a DS |
62 | pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] |
63 | ||
ee88563a | 64 | |
e82b531d CH |
65 | def build_topo(tgen): |
66 | "Build function" | |
67 | ||
68 | # Set up routers | |
69 | tgen.add_router("r1") # DUT | |
70 | tgen.add_router("r2") | |
71 | ||
72 | # Set up peers | |
73 | for peern in range(1, 5): | |
74 | peer = tgen.add_exabgp_peer( | |
75 | "peer{}".format(peern), | |
76 | ip="10.0.{}.2/24".format(peern), | |
77 | defaultRoute="via 10.0.{}.1".format(peern), | |
78 | ) | |
79 | if peern == 2: | |
80 | tgen.add_link(tgen.gears["r2"], peer) | |
81 | else: | |
82 | tgen.add_link(tgen.gears["r1"], peer) | |
83 | tgen.add_link(tgen.gears["r1"], tgen.gears["r2"]) | |
ee88563a JM |
84 | |
85 | ||
86 | def setup_module(mod): | |
87 | "Sets up the pytest environment" | |
e82b531d | 88 | tgen = Topogen(build_topo, mod.__name__) |
ee88563a JM |
89 | tgen.start_topology() |
90 | ||
91 | # For all registered routers, load the zebra configuration file | |
92 | for rname, router in tgen.routers().items(): | |
93 | router.run("/bin/bash {}/setup_vrfs".format(CWD)) | |
94 | router.load_config( | |
95 | TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) | |
96 | ) | |
97 | router.load_config( | |
98 | TopoRouter.RD_STATIC, os.path.join(CWD, "{}/staticd.conf".format(rname)) | |
99 | ) | |
100 | router.load_config( | |
101 | TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) | |
102 | ) | |
103 | ||
104 | # After loading the configurations, this function loads configured daemons. | |
105 | tgen.start_router() | |
106 | ||
107 | # Start up exabgp peers | |
108 | peers = tgen.exabgp_peers() | |
109 | for peer in peers: | |
110 | fifo_in = "/var/run/exabgp_{}.in".format(peer) | |
111 | if os.path.exists(fifo_in): | |
112 | os.remove(fifo_in) | |
113 | os.mkfifo(fifo_in, 0o777) | |
114 | logger.info("Starting ExaBGP on peer {}".format(peer)) | |
115 | peer_dir = os.path.join(CWD, peer) | |
116 | env_file = os.path.join(CWD, "exabgp.env") | |
117 | peers[peer].start(peer_dir, env_file) | |
118 | ||
119 | ||
120 | def teardown_module(mod): | |
121 | "Teardown the pytest environment" | |
122 | tgen = get_topogen() | |
123 | ||
124 | # This function tears down the whole topology. | |
125 | tgen.stop_topology() | |
126 | ||
127 | ||
128 | def test_bgp_peer_type_multipath_relax(): | |
129 | tgen = get_topogen() | |
130 | ||
131 | # Don't run this test if we have any failure. | |
132 | if tgen.routers_have_failure(): | |
133 | pytest.skip(tgen.errors) | |
134 | ||
135 | def exabgp_cmd(peer, cmd): | |
136 | pipe = open("/run/exabgp_{}.in".format(peer), "w") | |
137 | with pipe: | |
138 | pipe.write(cmd) | |
139 | pipe.close() | |
140 | ||
141 | # Prefixes used in the test | |
142 | prefix1 = "203.0.113.0/30" | |
143 | prefix2 = "203.0.113.4/30" | |
144 | prefix3 = "203.0.113.8/30" | |
145 | # Next hops used for iBGP/confed routes | |
146 | resolved_nh1 = "198.51.100.1" | |
147 | resolved_nh2 = "198.51.100.2" | |
148 | # BGP route used for recursive resolution | |
149 | bgp_resolving_prefix = "198.51.100.0/24" | |
150 | # Next hop that will require non-connected recursive resolution | |
151 | ebgp_resolved_nh = "198.51.100.10" | |
152 | ||
153 | # Send a non-connected route to resolve others | |
154 | exabgp_cmd( | |
155 | "peer3", "announce route {} next-hop self\n".format(bgp_resolving_prefix) | |
156 | ) | |
157 | router = tgen.gears["r1"] | |
158 | ||
159 | # It seems that if you write to the exabgp socket too quickly in | |
160 | # succession, requests get lost. So verify prefix1 now instead of | |
161 | # after all the prefixes are advertised. | |
162 | logger.info("Create and verify mixed-type multipaths") | |
163 | exabgp_cmd( | |
164 | "peer1", | |
165 | "announce route {} next-hop {} as-path [ 64499 ]\n".format( | |
166 | prefix1, resolved_nh1 | |
167 | ), | |
168 | ) | |
169 | exabgp_cmd( | |
170 | "peer2", | |
171 | "announce route {} next-hop {} as-path [ 64499 ]\n".format( | |
172 | prefix1, resolved_nh2 | |
173 | ), | |
174 | ) | |
175 | exabgp_cmd("peer4", "announce route {} next-hop self\n".format(prefix1)) | |
176 | reffile = os.path.join(CWD, "r1/prefix1.json") | |
177 | expected = json.loads(open(reffile).read()) | |
178 | test_func = functools.partial( | |
179 | topotest.router_json_cmp, | |
180 | router, | |
181 | "show ip bgp {} json".format(prefix1), | |
182 | expected, | |
183 | ) | |
184 | _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) | |
185 | assertMsg = "Mixed-type multipath not found" | |
186 | assert res is None, assertMsg | |
187 | ||
188 | logger.info("Create and verify eBGP and iBGP+confed multipaths") | |
189 | exabgp_cmd( | |
190 | "peer1", | |
191 | "announce route {} next-hop {} as-path [ 64499 ]\n".format( | |
192 | prefix2, resolved_nh1 | |
193 | ), | |
194 | ) | |
195 | exabgp_cmd( | |
196 | "peer2", | |
197 | "announce route {} next-hop {} as-path [ 64499 ]\n".format( | |
198 | prefix2, resolved_nh2 | |
199 | ), | |
200 | ) | |
201 | exabgp_cmd("peer3", "announce route {} next-hop self".format(prefix3)) | |
202 | exabgp_cmd("peer4", "announce route {} next-hop self".format(prefix3)) | |
203 | reffile = os.path.join(CWD, "r1/multipath.json") | |
204 | expected = json.loads(open(reffile).read()) | |
205 | test_func = functools.partial( | |
206 | topotest.router_json_cmp, router, "show ip bgp json", expected | |
207 | ) | |
208 | _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) | |
209 | assertMsg = "Not all expected multipaths found" | |
210 | assert res is None, assertMsg | |
211 | ||
212 | logger.info("Toggle peer-type multipath-relax and verify the changes") | |
213 | router.vtysh_cmd( | |
214 | "conf\n router bgp 64510\n no bgp bestpath peer-type multipath-relax\n" | |
215 | ) | |
216 | # This file verifies "multipath" is not set | |
217 | reffile = os.path.join(CWD, "r1/not-multipath.json") | |
218 | expected = json.loads(open(reffile).read()) | |
219 | test_func = functools.partial( | |
220 | topotest.router_json_cmp, router, "show ip bgp json", expected | |
221 | ) | |
222 | _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) | |
223 | assertMsg = "Disabling peer-type multipath-relax did not take effect" | |
224 | assert res is None, assertMsg | |
225 | ||
226 | router.vtysh_cmd( | |
227 | "conf\n router bgp 64510\n bgp bestpath peer-type multipath-relax\n" | |
228 | ) | |
229 | reffile = os.path.join(CWD, "r1/multipath.json") | |
230 | expected = json.loads(open(reffile).read()) | |
231 | test_func = functools.partial( | |
232 | topotest.router_json_cmp, router, "show ip bgp json", expected | |
233 | ) | |
234 | _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) | |
235 | assertMsg = "Reenabling peer-type multipath-relax did not take effect" | |
236 | assert res is None, assertMsg | |
237 | ||
1a9cb083 JM |
238 | logger.info("Check recursive resolution of eBGP next hops is not affected") |
239 | # eBGP next hop resolution rejects recursively resolved next hops by | |
240 | # default, even with peer-type multipath-relax | |
241 | exabgp_cmd( | |
242 | "peer4", "announce route {} next-hop {}\n".format(prefix3, ebgp_resolved_nh) | |
243 | ) | |
244 | reffile = os.path.join(CWD, "r1/prefix3-no-recursive.json") | |
245 | expected = json.loads(open(reffile).read()) | |
246 | test_func = functools.partial( | |
247 | topotest.router_json_cmp, | |
248 | router, | |
249 | "show ip bgp {} json".format(prefix3), | |
250 | expected, | |
251 | ) | |
252 | _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) | |
253 | assertMsg = "Recursive eBGP next hop not as expected for {}".format(prefix3) | |
254 | assert res is None, assertMsg | |
255 | ||
256 | exabgp_cmd( | |
257 | "peer4", "announce route {} next-hop {}\n".format(prefix1, ebgp_resolved_nh) | |
258 | ) | |
259 | reffile = os.path.join(CWD, "r1/prefix1-no-recursive.json") | |
260 | expected = json.loads(open(reffile).read()) | |
261 | test_func = functools.partial( | |
262 | topotest.router_json_cmp, | |
263 | router, | |
264 | "show ip bgp {} json".format(prefix1), | |
265 | expected, | |
266 | ) | |
267 | _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) | |
268 | assertMsg = "Recursive eBGP next hop not as expected for {}".format(prefix1) | |
269 | assert res is None, assertMsg | |
270 | ||
271 | # When other config allows recursively resolved eBGP next hops, | |
272 | # such next hops in all-eBGP multipaths should be valid | |
273 | router.vtysh_cmd("conf\n router bgp 64510\n neighbor 10.0.4.2 ebgp-multihop\n") | |
274 | reffile = os.path.join(CWD, "r1/prefix3-recursive.json") | |
275 | expected = json.loads(open(reffile).read()) | |
276 | test_func = functools.partial( | |
277 | topotest.router_json_cmp, | |
278 | router, | |
279 | "show ip bgp {} json".format(prefix3), | |
280 | expected, | |
281 | ) | |
282 | _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) | |
283 | assertMsg = "Recursive eBGP next hop not as expected for {}".format(prefix3) | |
284 | assert res is None, assertMsg | |
285 | ||
286 | reffile = os.path.join(CWD, "r1/prefix1-recursive.json") | |
287 | expected = json.loads(open(reffile).read()) | |
288 | test_func = functools.partial( | |
289 | topotest.router_json_cmp, | |
290 | router, | |
291 | "show ip bgp {} json".format(prefix1), | |
292 | expected, | |
293 | ) | |
294 | _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) | |
295 | assertMsg = "Recursive eBGP next hop not as expected for {}".format(prefix1) | |
296 | assert res is None, assertMsg | |
297 | ||
298 | logger.info("Check mixed-type multipath next hop recursive resolution in FIB") | |
299 | # There are now two eBGP-learned routes with a recursively resolved next; | |
300 | # hop; one is all-eBGP multipath, and the other is iBGP/eBGP/ | |
301 | # confed-external. The peer-type multipath-relax feature only enables | |
302 | # recursive resolution in FIB if any next hop is iBGP/confed-learned. The | |
303 | # all-eBGP multipath will have only one valid next hop in the FIB. | |
304 | reffile = os.path.join(CWD, "r1/prefix3-ip-route.json") | |
305 | expected = json.loads(open(reffile).read()) | |
306 | test_func = functools.partial( | |
307 | topotest.router_json_cmp, | |
308 | router, | |
309 | "show ip route {} json".format(prefix3), | |
310 | expected, | |
311 | ) | |
312 | _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) | |
313 | assertMsg = "FIB next hops mismatch for all-eBGP multipath" | |
314 | assert res is None, assertMsg | |
315 | ||
316 | # check confed-external enables recursively resolved next hops by itself | |
317 | exabgp_cmd( | |
318 | "peer1", | |
319 | "withdraw route {} next-hop {} as-path [ 64499 ]\n".format( | |
320 | prefix1, resolved_nh1 | |
321 | ), | |
322 | ) | |
323 | reffile = os.path.join(CWD, "r1/prefix1-eBGP-confed.json") | |
324 | expected = json.loads(open(reffile).read()) | |
325 | test_func = functools.partial( | |
326 | topotest.router_json_cmp, | |
327 | router, | |
328 | "show ip route {} json".format(prefix1), | |
329 | expected, | |
330 | ) | |
331 | _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) | |
332 | assertMsg = "FIB next hops mismatch for eBGP+confed-external multipath" | |
333 | assert res is None, assertMsg | |
334 | ||
335 | # check iBGP by itself | |
336 | exabgp_cmd( | |
337 | "peer1", | |
338 | "announce route {} next-hop {} as-path [ 64499 ]\n".format( | |
339 | prefix1, resolved_nh1 | |
340 | ), | |
341 | ) | |
342 | exabgp_cmd( | |
343 | "peer2", | |
344 | "withdraw route {} next-hop {} as-path [ 64499 ]\n".format( | |
345 | prefix1, resolved_nh2 | |
346 | ), | |
347 | ) | |
348 | reffile = os.path.join(CWD, "r1/prefix1-eBGP-iBGP.json") | |
349 | expected = json.loads(open(reffile).read()) | |
350 | test_func = functools.partial( | |
351 | topotest.router_json_cmp, | |
352 | router, | |
353 | "show ip route {} json".format(prefix1), | |
354 | expected, | |
355 | ) | |
356 | _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) | |
357 | assertMsg = "FIB next hops mismatch for eBGP+iBGP multipath" | |
358 | assert res is None, assertMsg | |
359 | ||
ee88563a JM |
360 | |
361 | def test_memory_leak(): | |
362 | "Run the memory leak test and report results." | |
363 | tgen = get_topogen() | |
364 | if not tgen.is_memleak_enabled(): | |
365 | pytest.skip("Memory leak test/report is disabled") | |
366 | ||
367 | tgen.report_memory_leaks() | |
368 | ||
369 | ||
370 | if __name__ == "__main__": | |
371 | args = ["-s"] + sys.argv[1:] | |
372 | sys.exit(pytest.main(args)) |