]>
Commit | Line | Data |
---|---|---|
586e15c4 MW |
1 | #!/usr/bin/env python |
2 | ||
3 | # | |
4 | # test_bgp_multiview_topo1.py | |
5 | # Part of NetDEF Topology Tests | |
6 | # | |
7 | # Copyright (c) 2016 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 | """ | |
594b1259 | 26 | test_bgp_multiview_topo1.py: Simple Quagga/FRR Route-Server Test |
586e15c4 | 27 | |
04df53ab MW |
28 | +----------+ +----------+ +----------+ +----------+ +----------+ |
29 | | peer1 | | peer2 | | peer3 | | peer4 | | peer5 | | |
30 | | AS 65001 | | AS 65002 | | AS 65003 | | AS 65004 | | AS 65005 | | |
31 | +-----+----+ +-----+----+ +-----+----+ +-----+----+ +-----+----+ | |
32 | | .1 | .2 | .3 | .4 | .5 | |
33 | | ______/ / / _________/ | |
34 | \ / ________________/ / / | |
35 | | | / _________________________/ / +----------+ | |
36 | | | | / __________________________/ ___| peer6 | | |
37 | | | | | / ____________________________/.6 | AS 65006 | | |
38 | | | | | | / _________________________ +----------+ | |
39 | | | | | | | / __________________ \ +----------+ | |
40 | | | | | | | | / \ \___| peer7 | | |
41 | | | | | | | | | \ .7 | AS 65007 | | |
42 | ~~~~~~~~~~~~~~~~~~~~~ \ +----------+ | |
43 | ~~ SW1 ~~ \ +----------+ | |
44 | ~~ Switch ~~ \_____| peer8 | | |
45 | ~~ 172.16.1.0/24 ~~ .8 | AS 65008 | | |
46 | ~~~~~~~~~~~~~~~~~~~~~ +----------+ | |
47 | | | |
48 | | .254 | |
49 | +---------+---------+ | |
594b1259 | 50 | | FRR R1 | |
04df53ab MW |
51 | | BGP Multi-View | |
52 | | Peer 1-3 > View 1 | | |
53 | | Peer 4-5 > View 2 | | |
54 | | Peer 6-8 > View 3 | | |
55 | +---------+---------+ | |
56 | | .1 | |
57 | | | |
58 | ~~~~~~~~~~~~~ Stub Network is redistributed | |
59 | ~~ SW0 ~~ into each BGP view with different | |
60 | ~~ 172.20.0.1/28 ~~ attributes (using route-map) | |
61 | ~~ Stub Switch ~~ | |
62 | ~~~~~~~~~~~~~ | |
63 | """ | |
586e15c4 MW |
64 | |
65 | import os | |
66 | import re | |
67 | import sys | |
68 | import difflib | |
594b1259 MW |
69 | import pytest |
70 | from time import sleep | |
586e15c4 MW |
71 | |
72 | from mininet.topo import Topo | |
73 | from mininet.net import Mininet | |
74 | from mininet.node import Node, OVSSwitch, Host | |
75 | from mininet.log import setLogLevel, info | |
76 | from mininet.cli import CLI | |
77 | from mininet.link import Intf | |
78 | ||
79 | from functools import partial | |
586e15c4 | 80 | |
594b1259 MW |
81 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
82 | from lib import topotest | |
586e15c4 | 83 | |
2cda38e4 MW |
84 | fatal_error = "" |
85 | ||
586e15c4 MW |
86 | |
87 | ##################################################### | |
88 | ## | |
89 | ## Network Topology Definition | |
90 | ## | |
91 | ##################################################### | |
92 | ||
93 | class NetworkTopo(Topo): | |
594b1259 | 94 | "BGP Multiview Topology 1" |
586e15c4 MW |
95 | |
96 | def build(self, **_opts): | |
97 | ||
586e15c4 MW |
98 | exabgpPrivateDirs = ['/etc/exabgp', |
99 | '/var/run/exabgp', | |
100 | '/var/log'] | |
594b1259 | 101 | |
586e15c4 | 102 | # Setup Routers |
594b1259 | 103 | router = {} |
586e15c4 | 104 | for i in range(1, 2): |
594b1259 | 105 | router[i] = topotest.addRouter(self, 'r%s' % i) |
586e15c4 MW |
106 | |
107 | # Setup Provider BGP peers | |
108 | peer = {} | |
109 | for i in range(1, 9): | |
110 | peer[i] = self.addHost('peer%s' % i, ip='172.16.1.%s/24' % i, | |
111 | defaultRoute='via 172.16.1.254', | |
112 | privateDirs=exabgpPrivateDirs) | |
113 | ||
114 | # Setup Switches | |
115 | switch = {} | |
116 | # First switch is for a dummy interface (for local network) | |
594b1259 MW |
117 | switch[0] = self.addSwitch('sw0', cls=topotest.LegacySwitch) |
118 | self.addLink(switch[0], router[1], intfName2='r1-stub') | |
586e15c4 | 119 | # Second switch is for connection to all peering routers |
594b1259 MW |
120 | switch[1] = self.addSwitch('sw1', cls=topotest.LegacySwitch) |
121 | self.addLink(switch[1], router[1], intfName2='r1-eth0') | |
586e15c4 MW |
122 | for j in range(1, 9): |
123 | self.addLink(switch[1], peer[j], intfName2='peer%s-eth0' % j) | |
124 | ||
125 | ||
126 | ##################################################### | |
127 | ## | |
128 | ## Tests starting | |
129 | ## | |
130 | ##################################################### | |
131 | ||
132 | def setup_module(module): | |
133 | global topo, net | |
134 | ||
135 | print("\n\n** %s: Setup Topology" % module.__name__) | |
136 | print("******************************************\n") | |
137 | ||
138 | print("Cleanup old Mininet runs") | |
139 | os.system('sudo mn -c > /dev/null 2>&1') | |
140 | ||
141 | thisDir = os.path.dirname(os.path.realpath(__file__)) | |
142 | topo = NetworkTopo() | |
143 | ||
144 | net = Mininet(controller=None, topo=topo) | |
145 | net.start() | |
146 | ||
147 | # Starting Routers | |
148 | for i in range(1, 2): | |
149 | net['r%s' % i].loadConf('zebra', '%s/r%s/zebra.conf' % (thisDir, i)) | |
150 | net['r%s' % i].loadConf('bgpd', '%s/r%s/bgpd.conf' % (thisDir, i)) | |
594b1259 | 151 | net['r%s' % i].startRouter() |
586e15c4 MW |
152 | |
153 | # Starting PE Hosts and init ExaBGP on each of them | |
154 | print('*** Starting BGP on all 8 Peers in 10s') | |
155 | sleep(10) | |
156 | for i in range(1, 9): | |
157 | net['peer%s' % i].cmd('cp %s/exabgp.env /etc/exabgp/exabgp.env' % thisDir) | |
158 | net['peer%s' % i].cmd('cp %s/peer%s/* /etc/exabgp/' % (thisDir, i)) | |
159 | net['peer%s' % i].cmd('chmod 644 /etc/exabgp/*') | |
160 | net['peer%s' % i].cmd('chmod 755 /etc/exabgp/*.py') | |
161 | net['peer%s' % i].cmd('chown -R exabgp:exabgp /etc/exabgp') | |
162 | net['peer%s' % i].cmd('exabgp -e /etc/exabgp/exabgp.env /etc/exabgp/exabgp.cfg') | |
163 | print('peer%s' % i), | |
164 | print('') | |
165 | ||
594b1259 | 166 | # For debugging after starting Quagga/FRR daemons, uncomment the next line |
586e15c4 MW |
167 | # CLI(net) |
168 | ||
169 | def teardown_module(module): | |
170 | global net | |
171 | ||
172 | print("\n\n** %s: Shutdown Topology" % module.__name__) | |
173 | print("******************************************\n") | |
174 | ||
175 | # Shutdown - clean up everything | |
176 | print('*** Killing BGP on Peer routers') | |
177 | # Killing ExaBGP | |
178 | for i in range(1, 9): | |
179 | net['peer%s' % i].cmd('kill `cat /var/run/exabgp/exabgp.pid`') | |
180 | ||
181 | # End - Shutdown network | |
182 | net.stop() | |
183 | ||
594b1259 | 184 | def test_router_running(): |
2cda38e4 | 185 | global fatal_error |
586e15c4 MW |
186 | global net |
187 | ||
2cda38e4 MW |
188 | # Skip if previous fatal error condition is raised |
189 | if (fatal_error != ""): | |
190 | pytest.skip(fatal_error) | |
191 | ||
b36d3e1c | 192 | print("\n\n** Check if FRR/Quagga is running on each Router node") |
586e15c4 MW |
193 | print("******************************************\n") |
194 | sleep(5) | |
195 | ||
594b1259 | 196 | # Starting Routers |
e631b518 | 197 | for i in range(1, 2): |
594b1259 MW |
198 | fatal_error = net['r%s' % i].checkRouterRunning() |
199 | assert fatal_error == "", fatal_error | |
200 | ||
201 | # For debugging after starting FRR/Quagga daemons, uncomment the next line | |
202 | # CLI(net) | |
203 | ||
586e15c4 MW |
204 | |
205 | def test_bgp_converge(): | |
206 | "Check for BGP converged on all peers and BGP views" | |
207 | ||
2cda38e4 | 208 | global fatal_error |
586e15c4 MW |
209 | global net |
210 | ||
2cda38e4 MW |
211 | # Skip if previous fatal error condition is raised |
212 | if (fatal_error != ""): | |
213 | pytest.skip(fatal_error) | |
214 | ||
586e15c4 MW |
215 | # Wait for BGP to converge (All Neighbors in either Full or TwoWay State) |
216 | print("\n\n** Verify for BGP to converge") | |
217 | print("******************************************\n") | |
218 | timeout = 60 | |
219 | while timeout > 0: | |
220 | print("Timeout in %s: " % timeout), | |
221 | sys.stdout.flush() | |
222 | # Look for any node not yet converged | |
223 | for i in range(1, 2): | |
224 | for view in range(1, 4): | |
225 | notConverged = net['r%s' % i].cmd('vtysh -c "show ip bgp view %s summary" 2> /dev/null | grep ^[0-9] | grep -v " 11$"' % view) | |
226 | if notConverged: | |
227 | print('Waiting for r%s, view %s' % (i, view)) | |
228 | sys.stdout.flush() | |
229 | break | |
230 | if notConverged: | |
231 | break | |
232 | if notConverged: | |
233 | sleep(5) | |
234 | timeout -= 5 | |
235 | else: | |
236 | print('Done') | |
237 | break | |
238 | else: | |
239 | # Bail out with error if a router fails to converge | |
5b7a4ad3 | 240 | bgpStatus = net['r%s' % i].cmd('vtysh -c "show ip bgp view %s summary"' % view) |
586e15c4 MW |
241 | assert False, "BGP did not converge:\n%s" % bgpStatus |
242 | ||
b36d3e1c MW |
243 | # Wait for an extra 30s to announce all routes |
244 | print('Waiting 30s for routes to be announced'); | |
245 | sleep(30) | |
246 | ||
586e15c4 MW |
247 | print("BGP converged.") |
248 | ||
249 | # if timeout < 60: | |
250 | # # Only wait if we actually went through a convergence | |
251 | # print("\nwaiting 15s for routes to populate") | |
252 | # sleep(15) | |
253 | ||
7e7fc73b MW |
254 | # Make sure that all daemons are running |
255 | for i in range(1, 2): | |
256 | fatal_error = net['r%s' % i].checkRouterRunning() | |
257 | assert fatal_error == "", fatal_error | |
258 | ||
594b1259 | 259 | # For debugging after starting Quagga/FRR daemons, uncomment the next line |
586e15c4 MW |
260 | # CLI(net) |
261 | ||
262 | def test_bgp_routingTable(): | |
2cda38e4 | 263 | global fatal_error |
586e15c4 MW |
264 | global net |
265 | ||
2cda38e4 MW |
266 | # Skip if previous fatal error condition is raised |
267 | if (fatal_error != ""): | |
268 | pytest.skip(fatal_error) | |
269 | ||
586e15c4 MW |
270 | thisDir = os.path.dirname(os.path.realpath(__file__)) |
271 | ||
b2764f90 | 272 | print("\n\n** Verifying BGP Routing Tables") |
586e15c4 MW |
273 | print("******************************************\n") |
274 | failures = 0 | |
275 | for i in range(1, 2): | |
276 | for view in range(1, 4): | |
277 | refTableFile = '%s/r%s/show_ip_bgp_view_%s.ref' % (thisDir, i, view) | |
278 | if os.path.isfile(refTableFile): | |
279 | # Read expected result from file | |
280 | expected = open(refTableFile).read().rstrip() | |
281 | # Fix newlines (make them all the same) | |
282 | expected = ('\n'.join(expected.splitlines()) + '\n').splitlines(1) | |
283 | ||
284 | # Actual output from router | |
285 | actual = net['r%s' % i].cmd('vtysh -c "show ip bgp view %s" 2> /dev/null' % view).rstrip() | |
286 | ||
287 | # Fix inconsitent spaces between 0.99.24 and newer versions of Quagga... | |
288 | actual = re.sub('0 0', '0 0', actual) | |
289 | actual = re.sub(r'([0-9]) 32768', r'\1 32768', actual) | |
290 | # Remove summary line (changed recently) | |
291 | actual = re.sub(r'Total number.*', '', actual) | |
292 | actual = re.sub(r'Displayed.*', '', actual) | |
293 | actual = actual.rstrip() | |
d98b7d63 MW |
294 | # Fix table version (ignore it) |
295 | actual = re.sub(r'(BGP table version is )[0-9]+', r'\1XXX', actual) | |
586e15c4 MW |
296 | |
297 | # Fix newlines (make them all the same) | |
298 | actual = ('\n'.join(actual.splitlines()) + '\n').splitlines(1) | |
299 | ||
300 | # Generate Diff | |
3b6bac2a MW |
301 | diff = ''.join(difflib.context_diff(actual, expected, |
302 | fromfile="actual BGP routing table", | |
303 | tofile="expected BGP routing table")) | |
586e15c4 MW |
304 | # Empty string if it matches, otherwise diff contains unified diff |
305 | ||
306 | if diff: | |
307 | sys.stderr.write('r%s failed Routing Table Check for view %s:\n%s\n' | |
308 | % (i, view, diff)) | |
309 | failures += 1 | |
310 | else: | |
311 | print("r%s ok" % i) | |
312 | ||
313 | assert failures == 0, "Routing Table verification failed for router r%s, view %s:\n%s" % (i, view, diff) | |
314 | ||
7e7fc73b MW |
315 | # Make sure that all daemons are running |
316 | for i in range(1, 2): | |
317 | fatal_error = net['r%s' % i].checkRouterRunning() | |
318 | assert fatal_error == "", fatal_error | |
319 | ||
594b1259 | 320 | # For debugging after starting FRR/Quagga daemons, uncomment the next line |
586e15c4 MW |
321 | # CLI(net) |
322 | ||
50c40bde | 323 | |
99561211 MW |
324 | def test_shutdown_check_stderr(): |
325 | global fatal_error | |
326 | global net | |
327 | ||
328 | # Skip if previous fatal error condition is raised | |
329 | if (fatal_error != ""): | |
330 | pytest.skip(fatal_error) | |
331 | ||
332 | if os.environ.get('TOPOTESTS_CHECK_STDERR') is None: | |
50c40bde MW |
333 | print("SKIPPED final check on StdErr output: Disabled (TOPOTESTS_CHECK_STDERR undefined)\n") |
334 | pytest.skip('Skipping test for Stderr output') | |
99561211 MW |
335 | |
336 | thisDir = os.path.dirname(os.path.realpath(__file__)) | |
337 | ||
b2764f90 | 338 | print("\n\n** Verifying unexpected STDERR output from daemons") |
99561211 MW |
339 | print("******************************************\n") |
340 | ||
341 | net['r1'].stopRouter() | |
342 | ||
343 | log = net['r1'].getStdErr('bgpd') | |
344 | print("\nBGPd StdErr Log:\n" + log) | |
345 | log = net['r1'].getStdErr('zebra') | |
346 | print("\nZebra StdErr Log:\n" + log) | |
347 | ||
586e15c4 | 348 | |
50c40bde MW |
349 | def test_shutdown_check_memleak(): |
350 | global fatal_error | |
351 | global net | |
352 | ||
353 | # Skip if previous fatal error condition is raised | |
354 | if (fatal_error != ""): | |
355 | pytest.skip(fatal_error) | |
356 | ||
357 | if os.environ.get('TOPOTESTS_CHECK_MEMLEAK') is None: | |
358 | print("SKIPPED final check on Memory leaks: Disabled (TOPOTESTS_CHECK_MEMLEAK undefined)\n") | |
359 | pytest.skip('Skipping test for memory leaks') | |
360 | ||
361 | thisDir = os.path.dirname(os.path.realpath(__file__)) | |
362 | ||
363 | net['r1'].stopRouter() | |
364 | net['r1'].report_memory_leaks(os.environ.get('TOPOTESTS_CHECK_MEMLEAK'), os.path.basename(__file__)) | |
365 | ||
366 | ||
586e15c4 MW |
367 | if __name__ == '__main__': |
368 | ||
369 | setLogLevel('info') | |
2cda38e4 MW |
370 | # To suppress tracebacks, either use the following pytest call or add "--tb=no" to cli |
371 | # retval = pytest.main(["-s", "--tb=no"]) | |
586e15c4 MW |
372 | retval = pytest.main(["-s"]) |
373 | sys.exit(retval) |