]>
git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/ospf6-topo1/test_ospf6_topo1.py
09dc538bafc86a2d554e500cb9add081ab7678b8
5 # Part of NetDEF Topology Tests
7 # Copyright (c) 2016 by
8 # Network Device Education Foundation, Inc. ("NetDEF")
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
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
29 SW1 - Stub Net 1 SW2 - Stub Net 2 \
30 fc00:1:1:1::/64 fc00:2:2:2::/64 \
31 \___________________/ \___________________/ |
35 +---------+---------+ +---------+---------+ |
37 | Quagga | | Quagga | |
38 | Rtr-ID: 10.0.0.1 | | Rtr-ID: 10.0.0.2 | |
39 +---------+---------+ +---------+---------+ |
41 \______ ___________/ OSPFv3
47 ~~ fc00:A:A:A::/64 ~~ |
50 | ::3 | SW3 - Stub Net 3 |
51 +---------+---------+ /-+ fc00:3:3:3::/64 |
54 | Rtr-ID: 10.0.0.3 | ::3 ___________/
55 +---------+---------+ \
61 ~~ fc00:B:B:B::/64 ~~ \
62 ~~~~~~~~~~~~~~~~~~ OSPFv3
65 +---------+---------+ /---- |
66 | R4 | | SW4 - Stub Net 4 |
67 | Quagga +------+ fc00:4:4:4::/64 |
68 | Rtr-ID: 10.0.0.4 | ::4 | /
69 +-------------------+ \---- /
81 from mininet
. topo
import Topo
82 from mininet
. net
import Mininet
83 from mininet
. node
import Node
, OVSSwitch
, Host
84 from mininet
. log
import setLogLevel
, info
85 from mininet
. cli
import CLI
87 from functools
import partial
88 from time
import sleep
95 "Converting Integer to DPID"
99 dpid
= '0' *( 16 - len ( dpid
))+ dpid
102 raise Exception ( 'Unable to derive default datapath ID - '
103 'please either specify a dpid or use a '
104 'canonical switch name such as s23.' )
106 class LinuxRouter ( Node
):
107 "A Node with IPv4/IPv6 forwarding enabled."
109 def config ( self
, ** params
):
110 super ( LinuxRouter
, self
). config (** params
)
111 # Enable forwarding on the router
112 self
. cmd ( 'sysctl net.ipv4.ip_forward=1' )
113 self
. cmd ( 'sysctl net.ipv6.conf.all.forwarding=1' )
116 Terminate generic LinuxRouter Mininet instance
118 self
. cmd ( 'sysctl net.ipv4.ip_forward=0' )
119 self
. cmd ( 'sysctl net.ipv6.conf.all.forwarding=0' )
120 super ( LinuxRouter
, self
). terminate ()
122 class QuaggaRouter ( Node
):
123 "A Node with IPv4/IPv6 forwarding enabled and Quagga as Routing Engine"
125 def config ( self
, ** params
):
126 super ( QuaggaRouter
, self
). config (** params
)
127 # Enable forwarding on the router
128 self
. cmd ( 'sysctl net.ipv4.ip_forward=1' )
129 self
. cmd ( 'sysctl net.ipv6.conf.all.forwarding=1' )
131 self
. cmd ( 'sysctl kernel.core_uses_pid=1' )
132 self
. cmd ( 'sysctl fs.suid_dumpable=2' )
133 self
. cmd ( "sysctl kernel.core_pattern=/tmp/ %s _ %%e _core-sig_ %%s- pid_ %% p.dmp" % self
. name
)
134 self
. cmd ( 'ulimit -c unlimited' )
135 # Set ownership of config files
136 self
. cmd ( 'chown quagga:quaggavty /etc/quagga' )
137 self
. daemons
= { 'zebra' : 0 , 'ripd' : 0 , 'ripngd' : 0 , 'ospfd' : 0 ,
138 'ospf6d' : 0 , 'isisd' : 0 , 'bgpd' : 0 , 'pimd' : 0 }
140 # Delete Running Quagga Daemons
141 rundaemons
= self
. cmd ( 'ls -1 /var/run/quagga/*.pid' )
142 for d
in StringIO
. StringIO ( rundaemons
):
143 self
. cmd ( 'kill -7 `cat %s `' % d
. rstrip ())
146 self
. cmd ( 'sysctl net.ipv4.ip_forward=0' )
147 self
. cmd ( 'sysctl net.ipv6.conf.all.forwarding=0' )
148 super ( QuaggaRouter
, self
). terminate ()
150 for interface
in self
. intfNames ():
151 self
. cmd ( 'ip address flush' , interface
)
152 def loadConf ( self
, daemon
, source
= None ):
153 # print "Daemons before:", self.daemons
154 if daemon
in self
. daemons
. keys ():
155 self
. daemons
[ daemon
] = 1
157 self
. cmd ( 'touch /etc/quagga/ %s .conf' % daemon
)
160 self
. cmd ( 'cp %s /etc/quagga/ %s .conf' % ( source
, daemon
))
162 self
. cmd ( 'chmod 640 /etc/quagga/ %s .conf' % daemon
)
164 self
. cmd ( 'chown quagga:quagga /etc/quagga/ %s .conf' % daemon
)
167 print ( "No daemon %s known" % daemon
)
168 # print "Daemons after:", self.daemons
169 def startQuagga ( self
):
170 # Disable integrated-vtysh-config
171 self
. cmd ( 'echo "no service integrated-vtysh-config" > /etc/quagga/vtysh.conf' )
172 with
open ( "/etc/quagga/vtysh.conf" , "w" ) as vtyshfile
:
173 vtyshfile
. write ( 'no service integrated-vtysh-config' )
174 self
. cmd ( 'chown quagga:quaggavty /etc/quagga/vtysh.conf' )
175 # Try to find relevant old logfiles in /tmp and delete them
176 map ( os
. remove
, glob
. glob ( "/tmp/* %s *.log" % self
. name
))
177 # Remove old core files
178 map ( os
. remove
, glob
. glob ( "/tmp/ %s *.dmp" % self
. name
))
179 # Remove IP addresses from OS first - we have them in zebra.conf
182 if self
. daemons
[ 'zebra' ] == 1 :
183 self
. cmd ( '/usr/lib/quagga/zebra -d' )
185 print ( ' %s : zebra started' % self
)
187 # Fix Link-Local Addresses
188 # Somehow (on Mininet only), Zebra removes the IPv6 Link-Local addresses on start. Fix this
189 self
. cmd ( 'for i in `ls /sys/class/net/` ; do mac=`cat /sys/class/net/$i/address`; IFS= \' : \' ; set $mac; unset IFS; ip address add dev $i scope link fe80::$(printf %0 2x $((0x$1 ^ 2)))$2:$ {3} ff:fe$4:$5$6/64; done' )
190 # Now start all the other daemons
191 for daemon
in self
. daemons
:
192 if ( self
. daemons
[ daemon
] == 1 ) and ( daemon
!= 'zebra' ):
193 self
. cmd ( '/usr/lib/quagga/ %s -d' % daemon
)
195 print ( ' %s : %s started' % ( self
, daemon
))
196 def checkQuaggaRunning ( self
):
199 daemonsRunning
= self
. cmd ( 'vtysh -c "show log" | grep "Logging configuration for"' )
200 for daemon
in self
. daemons
:
201 if ( self
. daemons
[ daemon
] == 1 ) and not ( daemon
in daemonsRunning
):
202 sys
. stderr
. write ( " %s : Daemon %s not running \n " % ( self
. name
, daemon
))
204 corefiles
= glob
. glob ( "/tmp/ %s _ %s _core*.dmp" % ( self
. name
, daemon
))
205 if ( len ( corefiles
) > 0 ):
206 backtrace
= subprocess
. check_output ([ "gdb /usr/lib/quagga/ %s %s --batch -ex bt 2> /dev/null" % ( daemon
, corefiles
[ 0 ])], shell
= True )
207 sys
. stderr
. write ( " \n %s : %s crashed. Core file found - Backtrace follows: \n " % ( self
. name
, daemon
))
208 sys
. stderr
. write ( " %s \n " % backtrace
)
210 # No core found - If we find matching logfile in /tmp, then print last 20 lines from it.
211 if os
. path
. isfile ( "/tmp/ %s-%s .log" % ( self
. name
, daemon
)):
212 log_tail
= subprocess
. check_output ([ "tail -n20 /tmp/ %s-%s .log 2> /dev/null" % ( self
. name
, daemon
)], shell
= True )
213 sys
. stderr
. write ( " \n From quagga %s %s log file: \n " % ( self
. name
, daemon
))
214 sys
. stderr
. write ( " %s \n " % log_tail
)
216 fatal_error
= " %s : Daemon %s not running" % ( self
. name
, daemon
)
217 assert False , " %s : Daemon %s not running" % ( self
. name
, daemon
)
218 def get_ipv6_linklocal ( self
):
219 "Get LinkLocal Addresses from interfaces"
223 ifaces
= self
. cmd ( 'ip -6 address' )
224 # Fix newlines (make them all the same)
225 ifaces
= ( ' \n ' . join ( ifaces
. splitlines ()) + ' \n ' ). splitlines ()
229 m
= re
. search ( '[0-9]+: ([^:@]+)[@if0-9:]+ <' , line
)
231 interface
= m
. group ( 1 )
233 m
= re
. search ( 'inet6 (fe80::[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+)[/0-9]* scope link' , line
)
237 if ( ll_per_if_count
> 1 ):
238 linklocal
+= [[ " %s-%s " % ( interface
, ll_per_if_count
), local
]]
240 linklocal
+= [[ interface
, local
]]
244 class LegacySwitch ( OVSSwitch
):
245 "A Legacy Switch without OpenFlow"
247 def __init__ ( self
, name
, ** params
):
248 OVSSwitch
.__ init
__ ( self
, name
, failMode
= 'standalone' , ** params
)
251 #####################################################
253 ## Network Topology Definition
255 #####################################################
257 class NetworkTopo ( Topo
):
258 "A Quagga Topology with direct peering router and IXP connection"
260 def build ( self
, ** _opts
):
262 quaggaPrivateDirs
= [ '/etc/quagga' ,
266 # Define Switches first
269 for i
in range ( 1 , 7 ):
270 switch
[ i
] = self
. addSwitch ( 'SW %s ' % i
, dpid
= int2dpid ( i
),
273 # Define Quagga Routers
276 for i
in range ( 1 , 5 ):
277 router
[ i
] = self
. addNode ( 'r %s ' % i
, cls
= QuaggaRouter
,
278 privateDirs
= quaggaPrivateDirs
)
280 # Wire up the switches and routers
283 for i
in range ( 1 , 5 ):
284 self
. addLink ( switch
[ i
], router
[ i
], intfName2
= 'r %s-s tubnet' % i
)
286 self
. addLink ( switch
[ 5 ], router
[ 1 ], intfName2
= 'r1-sw5' )
287 self
. addLink ( switch
[ 5 ], router
[ 2 ], intfName2
= 'r2-sw5' )
288 self
. addLink ( switch
[ 5 ], router
[ 3 ], intfName2
= 'r3-sw5' )
290 self
. addLink ( switch
[ 6 ], router
[ 3 ], intfName2
= 'r3-sw6' )
291 self
. addLink ( switch
[ 6 ], router
[ 4 ], intfName2
= 'r4-sw6' )
294 #####################################################
298 #####################################################
300 def setup_module ( module
):
303 print ( " \n\n ** %s : Setup Topology" % module
.__ name
__ )
304 print ( "****************************************** \n " )
306 print ( "Cleanup old Mininet runs" )
307 os
. system ( 'sudo mn -c > /dev/null 2>&1' )
309 thisDir
= os
. path
. dirname ( os
. path
. realpath ( __file__
))
312 net
= Mininet ( controller
= None , topo
= topo
)
315 # For debugging after starting net, but before starting Quagga, uncomment the next line
319 for i
in range ( 1 , 5 ):
320 net
[ 'r %s ' % i
]. loadConf ( 'zebra' , ' %s /r %s /zebra.conf' % ( thisDir
, i
))
321 net
[ 'r %s ' % i
]. loadConf ( 'ospf6d' , ' %s /r %s /ospf6d.conf' % ( thisDir
, i
))
322 net
[ 'r %s ' % i
]. startQuagga ()
324 # For debugging after starting Quagga daemons, uncomment the next line
328 def teardown_module ( module
):
331 print ( " \n\n ** %s : Shutdown Topology" % module
.__ name
__ )
332 print ( "****************************************** \n " )
334 # End - Shutdown network
338 def test_quagga_running ():
342 # Skip if previous fatal error condition is raised
343 if ( fatal_error
!= "" ):
344 pytest
. skip ( fatal_error
)
346 print ( " \n\n ** Check if Quagga is running on each Router node" )
347 print ( "****************************************** \n " )
351 for i
in range ( 1 , 5 ):
352 net
[ 'r %s ' % i
]. checkQuaggaRunning ()
355 def test_ospf6_converged ():
359 # Skip if previous fatal error condition is raised
360 if ( fatal_error
!= "" ):
361 pytest
. skip ( fatal_error
)
363 # Wait for OSPF6 to converge (All Neighbors in either Full or TwoWay State)
364 print ( " \n\n ** Verify OSPF6 daemons to converge" )
365 print ( "****************************************** \n " )
368 print ( "Timeout in %s : " % timeout
),
370 # Look for any node not yet converged
371 for i
in range ( 1 , 5 ):
372 notConverged
= net
[ 'r %s ' % i
]. cmd ( 'vtysh -c "show ipv6 ospf neigh" 2> /dev/null | grep ^[0-9] | grep -v Full' )
374 print ( 'Waiting for r %s ' %i)
385 # Bail out with error if a router fails to converge
386 ospfStatus
= net
[ 'r %s ' % i
]. cmd ( 'vtysh -c "show ipv6 ospf neigh"' )
387 fatal_error
= "OSPFv6 did not converge"
388 assert False , "OSPFv6 did not converge: \n %s " % ospfStatus
390 print ( "OSPFv3 converged." )
393 # Only wait if we actually went through a convergence
394 print ( " \n waiting 15s for routes to populate" )
397 def test_ospfv3_routingTable ():
401 # Skip if previous fatal error condition is raised
402 if ( fatal_error
!= "" ):
403 pytest
. skip ( fatal_error
)
405 thisDir
= os
. path
. dirname ( os
. path
. realpath ( __file__
))
407 # Verify OSPFv3 Routing Table
408 print ( " \n\n ** Verifing OSPFv3 Routing Table" )
409 print ( "****************************************** \n " )
411 for i
in range ( 1 , 5 ):
412 refTableFile
= ' %s /r %s /show_ipv6_route.ref' % ( thisDir
, i
)
413 if os
. path
. isfile ( refTableFile
):
414 # Read expected result from file
415 expected
= open ( refTableFile
). read (). rstrip ()
416 # Fix newlines (make them all the same)
417 expected
= ( ' \n ' . join ( expected
. splitlines ()) + ' \n ' ). splitlines ( 1 )
419 # Actual output from router
420 actual
= net
[ 'r %s ' % i
]. cmd ( 'vtysh -c "show ipv6 route" 2> /dev/null | grep "^O"' ). rstrip ()
421 # Mask out Link-Local mac address portion. They are random...
422 actual
= re
. sub ( r
" fe80::[0-9a-f:]+" , " fe80::XXXX:XXXX:XXXX:XXXX" , actual
)
423 # Drop timers on end of line (older Quagga Versions)
424 actual
= re
. sub ( r
", [0-2][0-9]:[0-5][0-9]:[0-5][0-9]" , "" , actual
)
425 # Fix newlines (make them all the same)
426 actual
= ( ' \n ' . join ( actual
. splitlines ()) + ' \n ' ). splitlines ( 1 )
429 diff
= '' . join ( difflib
. context_diff ( actual
, expected
,
430 fromfile
= "actual OSPFv3 IPv6 routing table" ,
431 tofile
= "expected OSPFv3 IPv6 routing table" ))
433 # Empty string if it matches, otherwise diff contains unified diff
435 sys
. stderr
. write ( 'r %s failed OSPFv3 (IPv6) Routing Table Check: \n %s \n ' % ( i
, diff
))
440 assert failures
== 0 , "OSPFv3 (IPv6) Routing Table verification failed for router r %s : \n %s " % ( i
, diff
)
442 # For debugging after starting Quagga daemons, uncomment the next line
445 def test_linux_ipv6_kernel_routingTable ():
449 # Skip if previous fatal error condition is raised
450 if ( fatal_error
!= "" ):
451 pytest
. skip ( fatal_error
)
453 thisDir
= os
. path
. dirname ( os
. path
. realpath ( __file__
))
455 # Verify Linux Kernel Routing Table
456 print ( " \n\n ** Verifing Linux IPv6 Kernel Routing Table" )
457 print ( "****************************************** \n " )
460 # Get a list of all current link-local addresses first as they change for
461 # each run and we need to translate them
463 for i
in range ( 1 , 5 ):
464 linklocals
+= net
[ 'r %s ' % i
]. get_ipv6_linklocal ()
466 # Now compare the routing tables (after substituting link-local addresses)
467 for i
in range ( 1 , 5 ):
468 refTableFile
= ' %s /r %s /ip_6_address.ref' % ( thisDir
, i
)
469 if os
. path
. isfile ( refTableFile
):
471 expected
= open ( refTableFile
). read (). rstrip ()
472 # Fix newlines (make them all the same)
473 expected
= ( ' \n ' . join ( expected
. splitlines ())). splitlines ( 1 )
475 # Actual output from router
476 actual
= net
[ 'r %s ' % i
]. cmd ( 'ip -6 route' ). rstrip ()
477 # Mask out Link-Local mac addresses
478 for ll
in linklocals
:
479 actual
= actual
. replace ( ll
[ 1 ], "fe80::__( %s )__" % ll
[ 0 ])
481 # Fix newlines (make them all the same)
482 actual
= ( ' \n ' . join ( actual
. splitlines ())). splitlines ( 1 )
485 # print("Router r%s table" % i)
486 # for line in actual:
487 # print(line.rstrip())
490 diff
= '' . join ( difflib
. context_diff ( actual
, expected
,
491 fromfile
= "actual IPv6 kernel routing table" ,
492 tofile
= "expected IPv6 kernel routing table" ))
494 # Empty string if it matches, otherwise diff contains unified diff
496 sys
. stderr
. write ( 'r %s failed Linux IPv6 Kernel Routing Table Check: \n %s \n ' % ( i
, diff
))
501 assert failures
== 0 , "Linux Kernel IPv6 Routing Table verification failed for router r %s : \n %s " % ( i
, diff
)
503 # For debugging after starting Quagga daemons, uncomment the next line
507 if __name__
== '__main__' :
510 # To suppress tracebacks, either use the following pytest call or add "--tb=no" to cli
511 # retval = pytest.main(["-s", "--tb=no"])
512 retval
= pytest
. main ([ "-s" ])