]>
git.proxmox.com Git - mirror_frr.git/blob - tests/topotests/lib/topotest.py
5 # Library of helper functions for 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
37 from lib
. topolog
import logger
39 from mininet
. topo
import Topo
40 from mininet
. net
import Mininet
41 from mininet
. node
import Node
, OVSSwitch
, Host
42 from mininet
. log
import setLogLevel
, info
43 from mininet
. cli
import CLI
44 from mininet
. link
import Intf
46 class json_cmp_result ( object ):
47 "json_cmp result class for better assertion messages"
52 def add_error ( self
, error
):
53 "Append error message to the result"
54 self
. errors
. append ( error
)
57 "Returns True if there were errors, otherwise False."
58 return len ( self
. errors
) > 0
61 def json_cmp ( d1
, d2
, reason
= False ):
63 JSON compare function. Receives two parameters:
65 * `d2`: json subset which we expect
67 Returns `None` when all keys that `d1` has matches `d2`,
68 otherwise a string containing what failed.
70 Note: key absence can be tested by adding a key with value `None`.
72 squeue
= [( d1
, d2
, 'json' )]
73 result
= json_cmp_result ()
76 s1
, s2
= set ( nd1
), set ( nd2
)
78 # Expect all required fields to exist.
79 s2_req
= set ([ key
for key
in nd2
if nd2
[ key
] is not None ])
82 result
. add_error ( 'expected key(s) {} in {} (have {})' . format (
83 str ( list ( diff
)), parent
, str ( list ( s1
))))
85 for key
in s2
. intersection ( s1
):
86 # Test for non existence of key in d2
88 result
. add_error ( '"{}" should not exist in {} (have {})' . format (
89 key
, parent
, str ( s1
)))
91 # If nd1 key is a dict, we have to recurse in it later.
92 if isinstance ( nd2
[ key
], type ({})):
93 if not isinstance ( nd1
[ key
], type ({})):
95 '{}["{}"] has different type than expected ' . format ( parent
, key
) +
96 '(have {}, expected {})' . format ( type ( nd1
[ key
]), type ( nd2
[ key
])))
98 nparent
= '{}["{}"]' . format ( parent
, key
)
99 squeue
. append (( nd1
[ key
], nd2
[ key
], nparent
))
102 if isinstance ( nd2
[ key
], type ([])):
103 if not isinstance ( nd1
[ key
], type ([])):
105 '{}["{}"] has different type than expected ' . format ( parent
, key
) +
106 '(have {}, expected {})' . format ( type ( nd1
[ key
]), type ( nd2
[ key
])))
109 if len ( nd2
[ key
]) > len ( nd1
[ key
]):
111 '{}["{}"] too few items ' . format ( parent
, key
) +
112 '(have ({}) "{}", expected ({}) "{}")' . format (
113 len ( nd1
[ key
]), str ( nd1
[ key
]), len ( nd2
[ key
]), str ( nd2
[ key
])))
116 # List all unmatched items errors
118 for expected
in nd2
[ key
]:
120 for value
in nd1
[ key
]:
121 if json_cmp ({ 'json' : value
}, { 'json' : expected
}) is None :
128 unmatched
. append ( expected
)
130 # If there are unmatched items, error out.
133 '{}["{}"] value is different (have "{}", expected "{}")' . format (
134 parent
, key
, str ( nd1
[ key
]), str ( nd2
[ key
])))
137 # Compare JSON values
138 if nd1
[ key
] != nd2
[ key
]:
140 '{}["{}"] value is different (have "{}", expected "{}")' . format (
141 parent
, key
, str ( nd1
[ key
]), str ( nd2
[ key
])))
144 if result
. has_errors ():
149 def run_and_expect ( func
, what
, count
= 20 , wait
= 3 ):
151 Run `func` and compare the result with `what`. Do it for `count` times
152 waiting `wait` seconds between tries. By default it tries 20 times with
153 3 seconds delay between tries.
155 Returns (True, func-return) on success or
156 (False, func-return) on failure.
164 return ( True , result
)
165 return ( False , result
)
169 "Converting Integer to DPID"
173 dpid
= '0' *( 16 - len ( dpid
))+ dpid
176 raise Exception ( 'Unable to derive default datapath ID - '
177 'please either specify a dpid or use a '
178 'canonical switch name such as s23.' )
181 "Check whether pid exists in the current process table."
187 except OSError as err
:
188 if err
. errno
== errno
. ESRCH
:
189 # ESRCH == No such process
191 elif err
. errno
== errno
. EPERM
:
192 # EPERM clearly means there's a process to deny access to
195 # According to "man 2 kill" possible error values are
196 # (EINVAL, EPERM, ESRCH)
201 def get_textdiff ( text1
, text2
, title1
= "" , title2
= "" ):
202 "Returns empty string if same or formatted diff"
204 diff
= ' \n ' . join ( difflib
. unified_diff ( text1
, text2
,
205 fromfile
= title1
, tofile
= title2
))
206 # Clean up line endings
207 diff
= os
. linesep
. join ([ s
for s
in diff
. splitlines () if s
])
210 def difflines ( text1
, text2
, title1
= '' , title2
= '' ):
211 "Wrapper for get_textdiff to avoid string transformations."
212 text1
= ( ' \n ' . join ( text1
. rstrip (). splitlines ()) + ' \n ' ). splitlines ( 1 )
213 text2
= ( ' \n ' . join ( text2
. rstrip (). splitlines ()) + ' \n ' ). splitlines ( 1 )
214 return get_textdiff ( text1
, text2
, title1
, title2
)
216 def get_file ( content
):
218 Generates a temporary file in '/tmp' with `content` and returns the file name.
220 fde
= tempfile
. NamedTemporaryFile ( mode
= 'w' , delete
= False )
226 def normalize_text ( text
):
228 Strips formating spaces/tabs and carriage returns.
230 text
= re
. sub ( r
'[ \t]+' , ' ' , text
)
231 text
= re
. sub ( r
'\r' , '' , text
)
234 def version_cmp ( v1
, v2
):
236 Compare two version strings and returns:
238 * `-1`: if `v1` is less than `v2`
239 * `0`: if `v1` is equal to `v2`
240 * `1`: if `v1` is greater than `v2`
242 Raises `ValueError` if versions are not well formated.
244 vregex
= r
'(?P<whole>\d+(\.(\d+))*)'
245 v1m
= re
. match ( vregex
, v1
)
246 v2m
= re
. match ( vregex
, v2
)
247 if v1m
is None or v2m
is None :
248 raise ValueError ( "got a invalid version string" )
251 v1g
= v1m
. group ( 'whole' ). split ( '.' )
252 v2g
= v2m
. group ( 'whole' ). split ( '.' )
254 # Get the longest version string
259 # Reverse list because we are going to pop the tail
262 for _
in range ( vnum
):
291 Gets a structured return of the command 'ip route'. It can be used in
292 conjuction with json_cmp() to provide accurate assert explanations.
307 output
= normalize_text ( node
. run ( 'ip route' )). splitlines ()
310 columns
= line
. split ( ' ' )
311 route
= result
[ columns
[ 0 ]] = {}
313 for column
in columns
:
315 route
[ 'dev' ] = column
317 route
[ 'via' ] = column
319 route
[ 'proto' ] = column
321 route
[ 'metric' ] = column
323 route
[ 'scope' ] = column
330 Gets a structured return of the command 'ip -6 route'. It can be used in
331 conjuction with json_cmp() to provide accurate assert explanations.
345 output
= normalize_text ( node
. run ( 'ip -6 route' )). splitlines ()
348 columns
= line
. split ( ' ' )
349 route
= result
[ columns
[ 0 ]] = {}
351 for column
in columns
:
353 route
[ 'dev' ] = column
355 route
[ 'via' ] = column
357 route
[ 'proto' ] = column
359 route
[ 'metric' ] = column
361 route
[ 'pref' ] = column
366 def sleep ( amount
, reason
= None ):
368 Sleep wrapper that registers in the log the amount of sleep
371 logger
. info ( 'Sleeping for {} seconds' . format ( amount
))
373 logger
. info ( reason
+ ' ({} seconds)' . format ( amount
))
377 def checkAddressSanitizerError ( output
, router
, component
):
378 "Checks for AddressSanitizer in output. If found, then logs it and returns true, false otherwise"
380 addressSantizerError
= re
. search ( '(==[0-9]+==)ERROR: AddressSanitizer: ([^\s]*) ' , output
)
381 if addressSantizerError
:
382 sys
. stderr
. write ( " %s : %s triggered an exception by AddressSanitizer \n " % ( router
, component
))
383 # Sanitizer Error found in log
384 pidMark
= addressSantizerError
. group ( 1 )
385 addressSantizerLog
= re
. search ( ' %s (.*) %s ' % ( pidMark
, pidMark
), output
, re
. DOTALL
)
386 if addressSantizerLog
:
387 callingTest
= os
. path
. basename ( sys
._ current
_ frames
(). values ()[ 0 ]. f_back
. f_back
. f_globals
[ '__file__' ])
388 callingProc
= sys
._ getframe
( 2 ). f_code
. co_name
389 with
open ( "/tmp/AddressSanitzer.txt" , "a" ) as addrSanFile
:
390 sys
. stderr
. write ( ' \n ' . join ( addressSantizerLog
. group ( 1 ). splitlines ()) + ' \n ' )
391 addrSanFile
. write ( "## Error: %s \n\n " % addressSantizerError
. group ( 2 ))
392 addrSanFile
. write ( "### AddressSanitizer error in topotest ` %s `, test ` %s `, router ` %s ` \n\n " % ( callingTest
, callingProc
, router
))
393 addrSanFile
. write ( ' ' + ' \n ' . join ( addressSantizerLog
. group ( 1 ). splitlines ()) + ' \n ' )
394 addrSanFile
. write ( " \n --------------- \n " )
398 def addRouter ( topo
, name
):
399 "Adding a FRRouter (or Quagga) to Topology"
401 MyPrivateDirs
= [ '/etc/frr' ,
406 return topo
. addNode ( name
, cls
= Router
, privateDirs
= MyPrivateDirs
)
408 def set_sysctl ( node
, sysctl
, value
):
409 "Set a sysctl value and return None on success or an error string"
410 valuestr
= '{}' . format ( value
)
411 command
= "sysctl {0} = {1} " . format ( sysctl
, valuestr
)
412 cmdret
= node
. cmd ( command
)
414 matches
= re
. search ( r
'([^ ]+) = ([^\s]+)' , cmdret
)
417 if matches
. group ( 1 ) != sysctl
:
419 if matches
. group ( 2 ) != valuestr
:
424 def assert_sysctl ( node
, sysctl
, value
):
425 "Set and assert that the sysctl is set with the specified value."
426 assert set_sysctl ( node
, sysctl
, value
) is None
428 class LinuxRouter ( Node
):
429 "A Node with IPv4/IPv6 forwarding enabled."
431 def config ( self
, ** params
):
432 super ( LinuxRouter
, self
). config (** params
)
433 # Enable forwarding on the router
434 assert_sysctl ( self
, 'net.ipv4.ip_forward' , 1 )
435 assert_sysctl ( self
, 'net.ipv6.conf.all.forwarding' , 1 )
438 Terminate generic LinuxRouter Mininet instance
440 set_sysctl ( self
, 'net.ipv4.ip_forward' , 0 )
441 set_sysctl ( self
, 'net.ipv6.conf.all.forwarding' , 0 )
442 super ( LinuxRouter
, self
). terminate ()
445 "A Node with IPv4/IPv6 forwarding enabled and Quagga as Routing Engine"
447 def __init__ ( self
, name
, ** params
):
448 super ( Router
, self
) .__ init
__ ( name
, ** params
)
449 self
. logdir
= params
. get ( 'logdir' , '/tmp' )
450 self
. daemondir
= None
451 self
. routertype
= 'frr'
452 self
. daemons
= { 'zebra' : 0 , 'ripd' : 0 , 'ripngd' : 0 , 'ospfd' : 0 ,
453 'ospf6d' : 0 , 'isisd' : 0 , 'bgpd' : 0 , 'pimd' : 0 ,
454 'ldpd' : 0 , 'eigrpd' : 0 , 'nhrpd' : 0 }
456 def _config_frr ( self
, ** params
):
457 "Configure FRR binaries"
458 self
. daemondir
= params
. get ( 'frrdir' )
459 if self
. daemondir
is None :
460 self
. daemondir
= '/usr/lib/frr'
462 zebra_path
= os
. path
. join ( self
. daemondir
, 'zebra' )
463 if not os
. path
. isfile ( zebra_path
):
464 raise Exception ( "FRR zebra binary doesn't exist at {}" . format ( zebra_path
))
466 def _config_quagga ( self
, ** params
):
467 "Configure Quagga binaries"
468 self
. daemondir
= params
. get ( 'quaggadir' )
469 if self
. daemondir
is None :
470 self
. daemondir
= '/usr/lib/quagga'
472 zebra_path
= os
. path
. join ( self
. daemondir
, 'zebra' )
473 if not os
. path
. isfile ( zebra_path
):
474 raise Exception ( "Quagga zebra binary doesn't exist at {}" . format ( zebra_path
))
476 # pylint: disable=W0221
477 # Some params are only meaningful for the parent class.
478 def config ( self
, ** params
):
479 super ( Router
, self
). config (** params
)
481 # User did not specify the daemons directory, try to autodetect it.
482 self
. daemondir
= params
. get ( 'daemondir' )
483 if self
. daemondir
is None :
484 self
. routertype
= params
. get ( 'routertype' , 'frr' )
485 if self
. routertype
== 'quagga' :
486 self
._ config
_ quagga
(** params
)
488 self
._ config
_ frr
(** params
)
490 # Test the provided path
491 zpath
= os
. path
. join ( self
. daemondir
, 'zebra' )
492 if not os
. path
. isfile ( zpath
):
493 raise Exception ( 'No zebra binary found in {}' . format ( zpath
))
494 # Allow user to specify routertype when the path was specified.
495 if params
. get ( 'routertype' ) is not None :
496 self
. routertype
= self
. params
. get ( 'routertype' )
498 # Enable forwarding on the router
499 assert_sysctl ( self
, 'net.ipv4.ip_forward' , 1 )
500 assert_sysctl ( self
, 'net.ipv6.conf.all.forwarding' , 1 )
502 assert_sysctl ( self
, 'kernel.core_uses_pid' , 1 )
503 assert_sysctl ( self
, 'fs.suid_dumpable' , 2 )
504 corefile
= '{}/{}_ %e _core-sig_ %s- pid_%p.dmp' . format ( self
. logdir
, self
. name
)
505 assert_sysctl ( self
, 'kernel.core_pattern' , corefile
)
506 self
. cmd ( 'ulimit -c unlimited' )
507 # Set ownership of config files
508 self
. cmd ( 'chown {0} : {0} vty /etc/ {0} ' . format ( self
. routertype
))
511 # Delete Running Quagga or FRR Daemons
513 # rundaemons = self.cmd('ls -1 /var/run/%s/*.pid' % self.routertype)
514 # for d in StringIO.StringIO(rundaemons):
515 # self.cmd('kill -7 `cat %s`' % d.rstrip())
518 set_sysctl ( self
, 'net.ipv4.ip_forward' , 0 )
519 set_sysctl ( self
, 'net.ipv6.conf.all.forwarding' , 0 )
520 super ( Router
, self
). terminate ()
521 def stopRouter ( self
):
522 # Stop Running Quagga or FRR Daemons
523 rundaemons
= self
. cmd ( 'ls -1 /var/run/ %s /*.pid' % self
. routertype
)
524 if rundaemons
is not None :
525 for d
in StringIO
. StringIO ( rundaemons
):
526 daemonpid
= self
. cmd ( 'cat %s ' % d
. rstrip ()). rstrip ()
527 if ( daemonpid
. isdigit () and pid_exists ( int ( daemonpid
))):
528 self
. cmd ( 'kill -7 %s ' % daemonpid
)
531 for interface
in self
. intfNames ():
532 self
. cmd ( 'ip address flush' , interface
)
533 def loadConf ( self
, daemon
, source
= None ):
534 # print "Daemons before:", self.daemons
535 if daemon
in self
. daemons
. keys ():
536 self
. daemons
[ daemon
] = 1
538 self
. cmd ( 'touch /etc/ %s / %s .conf' % ( self
. routertype
, daemon
))
541 self
. cmd ( 'cp %s /etc/ %s / %s .conf' % ( source
, self
. routertype
, daemon
))
543 self
. cmd ( 'chmod 640 /etc/ %s / %s .conf' % ( self
. routertype
, daemon
))
545 self
. cmd ( 'chown %s : %s /etc/ %s / %s .conf' % ( self
. routertype
, self
. routertype
, self
. routertype
, daemon
))
548 logger
. warning ( 'No daemon {} known' . format ( daemon
))
549 # print "Daemons after:", self.daemons
550 def startRouter ( self
):
551 # Disable integrated-vtysh-config
552 self
. cmd ( 'echo "no service integrated-vtysh-config" >> /etc/ %s /vtysh.conf' % self
. routertype
)
553 self
. cmd ( 'chown %s : %s vty /etc/ %s /vtysh.conf' % ( self
. routertype
, self
. routertype
, self
. routertype
))
554 # TODO remove the following lines after all tests are migrated to Topogen.
555 # Try to find relevant old logfiles in /tmp and delete them
556 map ( os
. remove
, glob
. glob ( "/tmp/* %s *.log" % self
. name
))
557 # Remove old core files
558 map ( os
. remove
, glob
. glob ( "/tmp/ %s *.dmp" % self
. name
))
559 # Remove IP addresses from OS first - we have them in zebra.conf
561 # If ldp is used, check for LDP to be compiled and Linux Kernel to be 4.5 or higher
562 # No error - but return message and skip all the tests
563 if self
. daemons
[ 'ldpd' ] == 1 :
564 ldpd_path
= os
. path
. join ( self
. daemondir
, 'ldpd' )
565 if not os
. path
. isfile ( ldpd_path
):
566 logger
. warning ( "LDP Test, but no ldpd compiled or installed" )
567 return "LDP Test, but no ldpd compiled or installed"
569 if version_cmp ( platform
. release (), '4.5' ) < 0 :
570 logger
. warning ( "LDP Test need Linux Kernel 4.5 minimum" )
571 return "LDP Test need Linux Kernel 4.5 minimum"
573 # Check if required kernel modules are available with a dryrun modprobe
574 # Silent accept of modprobe command assumes ok status
575 if self
. cmd ( '/sbin/modprobe -n mpls-router' ) != "" :
576 logger
. warning ( "LDP Test needs mpls-router kernel module" )
577 return "LDP Test needs mpls-router kernel module"
578 if self
. cmd ( '/sbin/modprobe -n mpls-iptunnel' ) != "" :
579 logger
. warning ( "LDP Test needs mpls-iptunnel kernel module" )
580 return "LDP Test needs mpls-router kernel module"
582 self
. cmd ( '/sbin/modprobe mpls-router' )
583 self
. cmd ( '/sbin/modprobe mpls-iptunnel' )
584 self
. cmd ( 'echo 100000 > /proc/sys/net/mpls/platform_labels' )
585 # Init done - now restarting daemons
588 def restartRouter ( self
):
589 # Starts actuall daemons without init (ie restart)
591 if self
. daemons
[ 'zebra' ] == 1 :
592 zebra_path
= os
. path
. join ( self
. daemondir
, 'zebra' )
593 self
. cmd ( ' {0} > {1} / {2} -zebra.out 2> {1} / {2} -zebra.err &' . format (
594 zebra_path
, self
. logdir
, self
. name
597 logger
. debug ( '{}: {} zebra started' . format ( self
, self
. routertype
))
599 # Fix Link-Local Addresses
600 # Somehow (on Mininet only), Zebra removes the IPv6 Link-Local addresses on start. Fix this
601 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' )
602 # Now start all the other daemons
603 for daemon
in self
. daemons
:
604 # Skip disabled daemons and zebra
605 if self
. daemons
[ daemon
] == 0 or daemon
== 'zebra' :
608 daemon_path
= os
. path
. join ( self
. daemondir
, daemon
)
609 self
. cmd ( ' {0} > {1} / {2} - {3} .out 2> {1} / {2} - {3} .err &' . format (
610 daemon_path
, self
. logdir
, self
. name
, daemon
613 logger
. debug ( '{}: {} {} started' . format ( self
, self
. routertype
, daemon
))
614 def getStdErr ( self
, daemon
):
615 return self
. getLog ( 'err' , daemon
)
616 def getStdOut ( self
, daemon
):
617 return self
. getLog ( 'out' , daemon
)
618 def getLog ( self
, log
, daemon
):
619 return self
. cmd ( 'cat {}/{}-{}.{}' . format ( self
. logdir
, self
. name
, daemon
, log
))
620 def checkRouterRunning ( self
):
621 "Check if router daemons are running and collect crashinfo they don't run"
625 daemonsRunning
= self
. cmd ( 'vtysh -c "show log" | grep "Logging configuration for"' )
626 # Look for AddressSanitizer Errors in vtysh output and append to /tmp/AddressSanitzer.txt if found
627 if checkAddressSanitizerError ( daemonsRunning
, self
. name
, "vtysh" ):
628 return " %s : vtysh killed by AddressSanitizer" % ( self
. name
)
630 for daemon
in self
. daemons
:
631 if ( self
. daemons
[ daemon
] == 1 ) and not ( daemon
in daemonsRunning
):
632 sys
. stderr
. write ( " %s : Daemon %s not running \n " % ( self
. name
, daemon
))
634 corefiles
= glob
. glob ( '{}/{}_{}_core*.dmp' . format (
635 self
. logdir
, self
. name
, daemon
))
636 if ( len ( corefiles
) > 0 ):
637 daemon_path
= os
. path
. join ( self
. daemondir
, daemon
)
638 backtrace
= subprocess
. check_output ([
639 "gdb {} {} --batch -ex bt 2> /dev/null" . format ( daemon_path
, corefiles
[ 0 ])
641 sys
. stderr
. write ( " \n %s : %s crashed. Core file found - Backtrace follows: \n " % ( self
. name
, daemon
))
642 sys
. stderr
. write ( " %s \n " % backtrace
)
644 # No core found - If we find matching logfile in /tmp, then print last 20 lines from it.
645 if os
. path
. isfile ( '{}/{}-{}.log' . format ( self
. logdir
, self
. name
, daemon
)):
646 log_tail
= subprocess
. check_output ([
647 "tail -n20 {}/{}-{}.log 2> /dev/null" . format (
648 self
. logdir
, self
. name
, daemon
)
650 sys
. stderr
. write ( " \n From %s %s %s log file: \n " % ( self
. routertype
, self
. name
, daemon
))
651 sys
. stderr
. write ( " %s \n " % log_tail
)
653 # Look for AddressSanitizer Errors and append to /tmp/AddressSanitzer.txt if found
654 if checkAddressSanitizerError ( self
. getStdErr ( daemon
), self
. name
, daemon
):
655 return " %s : Daemon %s not running - killed by AddressSanitizer" % ( self
. name
, daemon
)
657 return " %s : Daemon %s not running" % ( self
. name
, daemon
)
659 def get_ipv6_linklocal ( self
):
660 "Get LinkLocal Addresses from interfaces"
664 ifaces
= self
. cmd ( 'ip -6 address' )
665 # Fix newlines (make them all the same)
666 ifaces
= ( ' \n ' . join ( ifaces
. splitlines ()) + ' \n ' ). splitlines ()
670 m
= re
. search ( '[0-9]+: ([^:@]+)[@if0-9:]+ <' , line
)
672 interface
= m
. group ( 1 )
674 m
= re
. search ( 'inet6 (fe80::[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+)[/0-9]* scope link' , line
)
678 if ( ll_per_if_count
> 1 ):
679 linklocal
+= [[ " %s-%s " % ( interface
, ll_per_if_count
), local
]]
681 linklocal
+= [[ interface
, local
]]
683 def daemon_available ( self
, daemon
):
684 "Check if specified daemon is installed (and for ldp if kernel supports MPLS)"
686 daemon_path
= os
. path
. join ( self
. daemondir
, daemon
)
687 if not os
. path
. isfile ( daemon_path
):
689 if ( daemon
== 'ldpd' ):
690 if version_cmp ( platform
. release (), '4.5' ) < 0 :
692 if self
. cmd ( '/sbin/modprobe -n mpls-router' ) != "" :
694 if self
. cmd ( '/sbin/modprobe -n mpls-iptunnel' ) != "" :
698 def get_routertype ( self
):
699 "Return the type of Router (frr or quagga)"
701 return self
. routertype
702 def report_memory_leaks ( self
, filename_prefix
, testscript
):
703 "Report Memory Leaks to file prefixed with given string"
706 filename
= filename_prefix
+ re
. sub ( r
"\.py" , "" , testscript
) + ".txt"
707 for daemon
in self
. daemons
:
708 if ( self
. daemons
[ daemon
] == 1 ):
709 log
= self
. getStdErr ( daemon
)
710 if "memstats" in log
:
712 logger
. info ( ' \n Router {} {} StdErr Log: \n {}' . format (
713 self
. name
, daemon
, log
))
716 # Check if file already exists
717 fileexists
= os
. path
. isfile ( filename
)
718 leakfile
= open ( filename
, "a" )
720 # New file - add header
721 leakfile
. write ( "# Memory Leak Detection for topotest %s \n\n " % testscript
)
722 leakfile
. write ( "## Router %s \n " % self
. name
)
723 leakfile
. write ( "### Process %s \n " % daemon
)
724 log
= re
. sub ( "core_handler: " , "" , log
)
725 log
= re
. sub ( r
"(showing active allocations in memory group [a-zA-Z0-9]+)" , r
"\n#### \1\n" , log
)
726 log
= re
. sub ( "memstats: " , " " , log
)
733 class LegacySwitch ( OVSSwitch
):
734 "A Legacy Switch without OpenFlow"
736 def __init__ ( self
, name
, ** params
):
737 OVSSwitch
.__ init
__ ( self
, name
, failMode
= 'standalone' , ** params
)